Unverified Commit be57512a authored by Dan Jones's avatar Dan Jones
Browse files

tests: unit tests for models and api endpoints

- done
  - client and token model
  - client and token api endpoints
  - send api endpoint
- todo
  - notify api endpoint
  - receive api endpoint
parent 61d88a37
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
pubsubpy = "*"
pika = "*"
pyrabbit = "*"
flask = "*"
flask-restful = "*"
marshmallow = "*"
bson = "*"
flask-cors = "*"
cryptography = "*"
[dev-packages]
pytest = "*"
pytest-rabbitmq = "*"
pytest-mock = "*"
black = "*"
[requires]
python_version = "3.8"
{
"_meta": {
"hash": {
"sha256": "21c6756e40ab45e59ba28a311bc17728bc285e1ad8de901d4a4213355880027a"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"amqp": {
"hashes": [
"sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2",
"sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"
],
"markers": "python_version >= '3.6'",
"version": "==5.1.1"
},
"aniso8601": {
"hashes": [
"sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f",
"sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"
],
"version": "==9.0.1"
},
"bson": {
"hashes": [
"sha256:d6511b2ab051139a9123c184de1a04227262173ad593429d21e443d6462d6590"
],
"index": "pypi",
"version": "==0.5.10"
},
"cffi": {
"hashes": [
"sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5",
"sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef",
"sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104",
"sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426",
"sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405",
"sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375",
"sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a",
"sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e",
"sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc",
"sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf",
"sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185",
"sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497",
"sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3",
"sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35",
"sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c",
"sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83",
"sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21",
"sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca",
"sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984",
"sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac",
"sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd",
"sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee",
"sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a",
"sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2",
"sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192",
"sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7",
"sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585",
"sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f",
"sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e",
"sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27",
"sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b",
"sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e",
"sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e",
"sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d",
"sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c",
"sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415",
"sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82",
"sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02",
"sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314",
"sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325",
"sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c",
"sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3",
"sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914",
"sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045",
"sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d",
"sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9",
"sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5",
"sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2",
"sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c",
"sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3",
"sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2",
"sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8",
"sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d",
"sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d",
"sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9",
"sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162",
"sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76",
"sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4",
"sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e",
"sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9",
"sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6",
"sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b",
"sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01",
"sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"
],
"version": "==1.15.1"
},
"click": {
"hashes": [
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"cryptography": {
"hashes": [
"sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4",
"sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f",
"sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502",
"sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41",
"sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965",
"sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e",
"sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc",
"sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad",
"sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505",
"sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388",
"sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6",
"sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2",
"sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac",
"sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695",
"sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6",
"sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336",
"sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0",
"sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c",
"sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106",
"sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a",
"sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"
],
"index": "pypi",
"version": "==39.0.1"
},
"flask": {
"hashes": [
"sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b",
"sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"
],
"index": "pypi",
"version": "==2.2.2"
},
"flask-cors": {
"hashes": [
"sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438",
"sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"
],
"index": "pypi",
"version": "==3.0.10"
},
"flask-restful": {
"hashes": [
"sha256:4970c49b6488e46c520b325f54833374dc2b98e211f1b272bd4b0c516232afe2",
"sha256:ccec650b835d48192138c85329ae03735e6ced58e9b2d9c2146d6c84c06fa53e"
],
"index": "pypi",
"version": "==0.3.9"
},
"future": {
"hashes": [
"sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.18.3"
},
"httplib2": {
"hashes": [
"sha256:987c8bb3eb82d3fa60c68699510a692aa2ad9c4bd4f123e51dfb1488c14cdd01",
"sha256:fc144f091c7286b82bec71bdbd9b27323ba709cc612568d3000893bfd9cb4b34"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.21.0"
},
"importlib-metadata": {
"hashes": [
"sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad",
"sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"
],
"markers": "python_version < '3.10'",
"version": "==6.0.0"
},
"itsdangerous": {
"hashes": [
"sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
"sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"jinja2": {
"hashes": [
"sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
"sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.2"
},
"kombu": {
"hashes": [
"sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610",
"sha256:8b213b24293d3417bcf0d2f5537b7f756079e3ea232a8386dcc89a59fd2361a4"
],
"markers": "python_version >= '3.7'",
"version": "==5.2.4"
},
"markupsafe": {
"hashes": [
"sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed",
"sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc",
"sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2",
"sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460",
"sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7",
"sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0",
"sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1",
"sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa",
"sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03",
"sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323",
"sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65",
"sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013",
"sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036",
"sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f",
"sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4",
"sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419",
"sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2",
"sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619",
"sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a",
"sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a",
"sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd",
"sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7",
"sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666",
"sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65",
"sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859",
"sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625",
"sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff",
"sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156",
"sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd",
"sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba",
"sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f",
"sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1",
"sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094",
"sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a",
"sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513",
"sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed",
"sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d",
"sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3",
"sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147",
"sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c",
"sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603",
"sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601",
"sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a",
"sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1",
"sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d",
"sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3",
"sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54",
"sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2",
"sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6",
"sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"marshmallow": {
"hashes": [
"sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78",
"sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b"
],
"index": "pypi",
"version": "==3.19.0"
},
"packaging": {
"hashes": [
"sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
"sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
],
"markers": "python_version >= '3.7'",
"version": "==23.0"
},
"pika": {
"hashes": [
"sha256:89f5e606646caebe3c00cbdbc4c2c609834adde45d7507311807b5775edac8e0",
"sha256:beb19ff6dd1547f99a29acc2c6987ebb2ba7c44bf44a3f8e305877c5ef7d2fdc"
],
"index": "pypi",
"version": "==1.3.1"
},
"pubsubpy": {
"hashes": [
"sha256:58e394d14dd172fc03caff172adf3817d980bb6b8cb46cd18a362f8aa6e530c6",
"sha256:b29fa140615935ac03801ccd1de137ce4d33b741465b9002f290538ce966f2e9"
],
"index": "pypi",
"version": "==2.3.0"
},
"pycparser": {
"hashes": [
"sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
"sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
],
"version": "==2.21"
},
"pyparsing": {
"hashes": [
"sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
"sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
],
"markers": "python_version >= '3.1'",
"version": "==3.0.9"
},
"pyrabbit": {
"hashes": [
"sha256:50b8995fbfde14820ddc97292312c8f0c77054748c2b018138d03d94e400c39c"
],
"index": "pypi",
"version": "==1.1.0"
},
"python-dateutil": {
"hashes": [
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.2"
},
"pytz": {
"hashes": [
"sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0",
"sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"
],
"version": "==2022.7.1"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"vine": {
"hashes": [
"sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30",
"sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"
],
"markers": "python_version >= '3.6'",
"version": "==5.0.0"
},
"werkzeug": {
"hashes": [
"sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f",
"sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"
],
"markers": "python_version >= '3.7'",
"version": "==2.2.2"
},
"zipp": {
"hashes": [
"sha256:6c4fe274b8f85ec73c37a8e4e3fa00df9fb9335da96fb789e3b96b318e5097b3",
"sha256:a3cac813d40993596b39ea9e93a18e8a2076d5c378b8bc88ec32ab264e04ad02"
],
"markers": "python_version >= '3.7'",
"version": "==3.12.1"
}
},
"develop": {
"attrs": {
"hashes": [
"sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
"sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"
],
"markers": "python_version >= '3.6'",
"version": "==22.2.0"
},
"black": {
"hashes": [
"sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd",
"sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555",
"sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481",
"sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468",
"sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9",
"sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a",
"sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958",
"sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580",
"sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26",
"sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32",
"sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8",
"sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753",
"sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b",
"sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074",
"sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651",
"sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24",
"sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6",
"sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad",
"sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac",
"sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221",
"sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06",
"sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27",
"sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648",
"sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739",
"sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104"
],
"index": "pypi",
"version": "==23.1.0"
},
"click": {
"hashes": [
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"exceptiongroup": {
"hashes": [
"sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e",
"sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"
],
"markers": "python_version < '3.11'",
"version": "==1.1.0"
},
"iniconfig": {
"hashes": [
"sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
"sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"
],
"markers": "python_version >= '3.7'",
"version": "==2.0.0"
},
"mirakuru": {
"hashes": [
"sha256:ec84d4d81b4bca96cb0e598c6b3d198a92f036a0c1223c881482c02a98508226",
"sha256:fdb67d141cc9f7abd485a515d618daf3272c3e6ff48380749997ff8e8c5f2cb2"
],
"markers": "python_version >= '3.7'",
"version": "==2.4.2"
},
"mypy-extensions": {
"hashes": [
"sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
"sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.0"
},
"packaging": {
"hashes": [
"sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
"sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
],
"markers": "python_version >= '3.7'",
"version": "==23.0"
},
"pamqp": {
"hashes": [
"sha256:2f81b5c186f668a67f165193925b6bfd83db4363a6222f599517f29ecee60b02",
"sha256:5cd0f5a85e89f20d5f8e19285a1507788031cfca4a9ea6f067e3cf18f5e294e8"
],
"version": "==2.3.0"
},
"pathspec": {
"hashes": [
"sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229",
"sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"
],
"markers": "python_version >= '3.7'",
"version": "==0.11.0"
},
"platformdirs": {
"hashes": [
"sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9",
"sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"
],
"markers": "python_version >= '3.7'",
"version": "==3.0.0"
},
"pluggy": {
"hashes": [
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
],
"markers": "python_version >= '3.6'",
"version": "==1.0.0"
},
"port-for": {
"hashes": [
"sha256:232bd999015b7fbdf19f90f3a9298cc742252d67650108123940bfc75c6f4d4e",
"sha256:31860afde6cb552e1830c927def3288350c8fbbe9aea8aed8150ed9d1aa0de81"
],
"markers": "python_version >= '3.7'",
"version": "==0.6.3"
},
"psutil": {
"hashes": [
"sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff",
"sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1",
"sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62",
"sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549",
"sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08",
"sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7",
"sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e",
"sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe",
"sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24",
"sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad",
"sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94",
"sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8",
"sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7",
"sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"
],
"markers": "sys_platform != 'cygwin'",
"version": "==5.9.4"
},
"pytest": {
"hashes": [
"sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5",
"sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"
],
"index": "pypi",
"version": "==7.2.1"
},
"pytest-mock": {
"hashes": [
"sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b",
"sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"
],
"index": "pypi",
"version": "==3.10.0"
},
"pytest-rabbitmq": {
"hashes": [
"sha256:694ef26a6b85ec3b67428cd91a3abc35231c0a90adc70dbea59e94a6950dd07e",
"sha256:a9306b3e8c53440663fbc1baa7d77f574b9612403192fafa09224cd8824e19c1"
],
"index": "pypi",
"version": "==2.2.1"
},
"rabbitpy": {
"hashes": [
"sha256:58be8ccef6d64010d98c77b0966be148aafb48b867359e885d9cd671361fe5ba",
"sha256:c6a6d0e8e51d0859fbb9bb78c8eeddd7b1ec79957db633673a8741d0cedf4830"
],
"version": "==2.0.1"
},
"tomli": {
"hashes": [
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_version < '3.11'",
"version": "==2.0.1"
},
"typing-extensions": {
"hashes": [
"sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa",
"sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"
],
"markers": "python_version < '3.10'",
"version": "==4.4.0"
}
}
}
......@@ -7,25 +7,31 @@ from endpoints.notify import Notify
from endpoints.receive import Receive
from endpoints.send import Send
from endpoints.token import Token
from models.token import TokenModel
from models.token_model import TokenModel
import os
token = TokenModel()
token.setSecret()
app = Flask(__name__)
api = Api(app)
CORS(app, resources={r"*": {"origins": "http://localhost:8086"}})
api.add_resource(ClientList, "/client")
api.add_resource(Client, "/client/<client_id>")
api.add_resource(Receive, "/receive")
api.add_resource(Send, "/send")
api.add_resource(Notify, "/notify")
api.add_resource(Token, "/token")
def create_app():
app = Flask(__name__)
api = Api(app)
CORS(app, resources={r"*": {"origins": "http://localhost:8086"}})
api.add_resource(ClientList, "/client")
api.add_resource(Client, "/client/<client_id>")
api.add_resource(Receive, "/receive")
api.add_resource(Send, "/send")
api.add_resource(Notify, "/notify")
api.add_resource(Token, "/token")
return app
flask_host = os.getenv("FLASK_HOST", "localhost") # Sets to whatever MQ_HOST is, or defaults to localhost
if __name__ == "__main__":
app = create_app()
app.run(debug=False, port=8087, host=flask_host)
import flask
import json
import pytest
from unittest.mock import patch, mock_open, call
from werkzeug.exceptions import HTTPException
from api import create_app
@pytest.mark.usefixtures("mock_clients")
def test_get_client(mock_clients):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, app.test_client() as app_test_client:
response = app_test_client.get("/client")
assert response.status_code == 200
assert len(response.json.keys()) == 2
assert "client-1" in response.json
@pytest.mark.usefixtures("mock_clients")
def test_get_client_1(mock_clients):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, app.test_client() as app_test_client:
response = app_test_client.get("/client/client-1")
assert response.status_code == 200
assert "client_id" in response.json
assert response.json["client_id"] == "client-1"
assert "client_name" in response.json
assert "secret" not in response.json
@pytest.mark.usefixtures("mock_clients", "mock_new_client")
def test_post_client(mock_clients, mock_new_client):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, app.test_client() as app_test_client:
response = app_test_client.post("/client", json=mock_new_client)
assert response.status_code == 201
assert "client_id" in response.json
assert response.json["client_id"] == "client-3"
assert "client_name" in response.json
assert "secret" in response.json
@pytest.mark.usefixtures("mock_clients")
def test_put_client_1(mock_clients):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, app.test_client() as app_test_client:
response = app_test_client.get("/client/client-1")
client_1 = response.json
print(client_1)
client_1["subscription"] = "soar.client-1.#"
response = app_test_client.put("/client/client-1", json=client_1)
print(response.data)
assert response.status_code == 201
assert "client_id" in response.json
assert response.json["client_id"] == "client-1"
assert response.json["subscription"] == "soar.client-1.#"
assert "client_name" in response.json
assert "secret" in response.json
@pytest.mark.usefixtures("mock_clients")
def test_put_client_1(mock_clients):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, app.test_client() as app_test_client:
response = app_test_client.get("/client/client-1")
client_1 = response.json
print(client_1)
client_1["subscription"] = "soar.client-1.#"
response = app_test_client.put("/client/client-1", json=client_1)
assert response.status_code == 201
assert "client_id" in response.json
assert response.json["client_id"] == "client-1"
assert response.json["subscription"] == "soar.client-1.#"
assert "client_name" in response.json
assert "secret" in response.json
@pytest.mark.usefixtures("mock_clients")
def test_delete_client_1(mock_clients):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, app.test_client() as app_test_client:
response = app_test_client.delete("/client/client-1")
assert response.status_code == 204
import flask
import json
import pytest
from unittest.mock import patch, mock_open, call
from werkzeug.exceptions import HTTPException
from api import create_app
from endpoints import send
from conftest import get_auth_header
@pytest.mark.usefixtures(
"mock_clients", "mock_client_credentials", "mock_post_send", "mock_message_send"
)
def test_post_send(
mock_clients, mock_client_credentials, mock_post_send, mock_message_send
):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, patch.object(
send, "write_to_queue"
) as mock_write_to_queue, app.test_client() as app_test_client:
auth_header = get_auth_header(app_test_client, mock_client_credentials)
response = app_test_client.post(
"/send", json=mock_post_send, headers=auth_header
)
assert response.status_code == 200
mock_write_to_queue.assert_called_once_with(
queue_name="client-1-outbox", msg=json.dumps(mock_message_send)
)
@pytest.mark.usefixtures("mock_clients", "mock_post_send")
def test_post_send_no_token(mock_clients, mock_post_send):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, patch.object(
send, "write_to_queue"
) as mock_write_to_queue, app.test_client() as app_test_client:
response = app_test_client.post("/send", json=mock_post_send)
assert response.status_code == 403
mock_write_to_queue.assert_not_called()
@pytest.mark.usefixtures("mock_clients", "mock_post_send")
def test_post_send_invalid_token(mock_clients, mock_post_send):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, patch.object(
send, "write_to_queue"
) as mock_write_to_queue, app.test_client() as app_test_client:
auth_header = {"Authorization": "made-up-token"}
response = app_test_client.post(
"/send", json=mock_post_send, headers=auth_header
)
assert response.status_code == 403
mock_write_to_queue.assert_not_called()
import flask
import json
import pytest
from unittest.mock import patch, mock_open, call
from werkzeug.exceptions import HTTPException
from api import create_app
@pytest.mark.usefixtures("mock_clients", "mock_client_credentials")
def test_get_token(mock_clients, mock_client_credentials):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, app.test_client() as app_test_client:
response = app_test_client.get("/token", query_string=mock_client_credentials)
assert response.status_code == 200
assert "token" in response.json
@pytest.mark.usefixtures("mock_clients", "mock_invalid_credentials")
def test_get_token_invalid_credentials(mock_clients, mock_invalid_credentials):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, app.test_client() as app_test_client:
response = app_test_client.get("/token", query_string=mock_invalid_credentials)
assert response.status_code == 403
assert "token" not in response.json
@pytest.mark.usefixtures("mock_clients", "mock_client_credentials")
def test_get_token_no_credentials(mock_clients, mock_client_credentials):
app = create_app()
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, app.test_client() as app_test_client:
response = app_test_client.get("/token")
assert response.status_code == 400
assert "token" not in response.json
import pytest
def clients():
return {
"client-1": {
"client_id": "client-1",
"client_name": "Client 1",
"subscription": "soar.#",
"secret": "abc123",
},
"client-2": {
"client_id": "client-2",
"client_name": "Client 2",
"subscription": "soar.client-2.#",
"secret": "xyz789",
},
}
def get_auth_header(client, credentials):
token_response = client.get("/token", query_string=credentials)
if token_response.status_code == 200:
token = token_response.json["token"]
return {"Authorization": f"Bearer {token}"}
else:
return None
@pytest.fixture
def mock_clients():
return clients()
@pytest.fixture
def mock_new_client():
return {
"client_id": "client-3",
"client_name": "Client 3",
"subscription": "soar.client-3.#",
}
@pytest.fixture
def mock_client_credentials():
mock_clients = clients()
return {
"client_id": mock_clients["client-1"]["client_id"],
"secret": mock_clients["client-1"]["secret"],
}
@pytest.fixture
def mock_invalid_credentials():
return {"client_id": "client-invalid", "secret": "fake-secret"}
@pytest.fixture
def mock_token_secret():
return "2UrRyeb9c6hq8Gj9nmI5safPz9LpPeUFtifeMNx4GQo="
def posts():
return {
"send": {
"topic": "soar.client-1.message",
"body": "this is a pub/sub message from client-1",
},
"notify": {"body": "this is a broadcast message from client-1"},
}
@pytest.fixture
def mock_post_send():
return posts()["send"]
@pytest.fixture
def mock_message_send():
post = posts()["send"]
return {"topic": post["topic"], "message": post["body"]}
@pytest.fixture
def mock_post_notify():
return posts()["notify"]
@pytest.fixture
def mock_message_notify():
post = posts()["send"]
return {"message": post["body"]}
......@@ -3,6 +3,7 @@ FROM python:alpine3.17
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY requirements-dev.txt requirements-dev.txt
RUN pip install -r requirements-dev.txt
ENTRYPOINT [ "python" ]
\ No newline at end of file
......@@ -2,28 +2,26 @@ import json
import os
from flask_restful import Resource, abort
from models.token import TokenModel
from models.token_model import TokenModel
class AuthResource(Resource):
def __init__(self):
def __init__(self):
self.token = TokenModel()
with open("./data/clients.json", "r") as clients_file:
self.clients = json.load(clients_file)
def auth(self, request):
def auth(self, request):
allow = False
auth = request.headers.get('Authorization', False)
auth = request.headers.get("Authorization", False)
if auth:
token = auth.split(' ').pop()
token = auth.split(" ").pop()
parsed = self.token.validate(token)
if parsed['valid']:
client = self.clients.get(parsed['client_id'])
if client:
if parsed["valid"]:
client = self.clients.get(parsed["client_id"])
if client:
self.client = client
allow = True
if not allow:
abort(403, message="Invalid token")
return allow
\ No newline at end of file
return allow
from flask_restful import Resource, request, abort
from marshmallow import Schema, fields
import json
import os
import random
import string
from models.client_model import ClientModel
class ClientSchema(Schema):
client_id = fields.Str(required=True)
client_name = fields.Str(required=True)
subscription = fields.Str(required=True)
# Client
class Client(Resource):
clients_file = None
def __init__(self):
self.schema = ClientSchema()
self.clients_file = ClientModel()
def get(self, client_id):
client = self.clients_file.find(client_id)
del client["secret"]
if not client:
abort(404, message="No client with id: {}".format(client_id))
return client
def delete(self, client_id):
client = self.clients_file.find(client_id)
if not client:
abort(404, message="No client with id: {}".format(client_id))
else:
self.clients_file.remove(client)
return client, 204
def put(self, client_id):
args = request.get_json()
errors = self.schema.validate(args)
if errors:
abort(400, message=str(errors))
client = self.clients_file.find(client_id)
if not client:
abort(404, message="No client with id: {}".format(client_id))
else:
client = self.clients_file.update(args)
return client, 201
# ClientList
class ClientList(Resource):
def __init__(self):
self.schema = ClientSchema()
self.clients_file = ClientModel()
def get(self):
return {
client_id: (client, client.pop("secret", None))[0]
for client_id, client in self.clients_file.get().items()
}
def post(self):
args = request.get_json()
errors = self.schema.validate(args)
if errors:
abort(400, message=str(errors))
client = self.clients_file.find(args["client_id"])
if client:
abort(403, message="Duplicate client id: {}".format(client_id))
else:
client = self.clients_file.add(args)
return client, 201
......@@ -10,6 +10,7 @@ from rmq import write_to_queue
class NotifySchema(Schema):
body = fields.Str(required=True)
class Notify(AuthResource):
clients = None
schema = None
......@@ -17,7 +18,7 @@ class Notify(AuthResource):
def __init__(self):
super().__init__()
self.schema = NotifySchema()
def post(self):
args = request.get_json()
errors = self.schema.validate(args)
......@@ -27,12 +28,12 @@ class Notify(AuthResource):
allow = False
body = args.get("body")
message = {
'topic': 'broadcast',
'message': body,
"topic": "broadcast",
"message": body,
}
allow = self.auth(request)
if allow:
notify_queue = self.client['client_id'] + "-broadcast"
write_to_queue(queue_name=notify_queue, msg=json.dumps(message))
\ No newline at end of file
notify_queue = self.client["client_id"] + "-broadcast"
write_to_queue(queue_name=notify_queue, msg=json.dumps(message))
......@@ -26,5 +26,5 @@ class Receive(AuthResource):
allow = self.auth(request)
if allow:
inbox_queue = self.client['client_id'] + "-inbox"
return read_from_queue(queue_name=inbox_queue, max_msgs=max_messages)
\ No newline at end of file
inbox_queue = self.client["client_id"] + "-inbox"
return read_from_queue(queue_name=inbox_queue, max_msgs=max_messages)
......@@ -11,6 +11,7 @@ class SendSchema(Schema):
body = fields.Str(required=True)
topic = fields.Str(required=True)
class Send(AuthResource):
clients = None
schema = None
......@@ -18,7 +19,7 @@ class Send(AuthResource):
def __init__(self):
super().__init__()
self.schema = SendSchema()
def post(self):
args = request.get_json()
errors = self.schema.validate(args)
......@@ -26,14 +27,14 @@ class Send(AuthResource):
abort(400, message=str(errors))
allow = self.auth(request)
if allow:
body = args.get("body")
topic = args.get("topic")
outbox_queue = self.client['client_id'] + "-outbox"
outbox_queue = self.client["client_id"] + "-outbox"
message = {
'topic': topic,
'message': body,
"topic": topic,
"message": body,
}
write_to_queue(queue_name=outbox_queue, msg=json.dumps(message))
\ No newline at end of file
write_to_queue(queue_name=outbox_queue, msg=json.dumps(message))
import json
import json
from flask_restful import Resource, request, abort
from marshmallow import Schema, fields
from models.token import TokenModel
from models.token_model import TokenModel
class TokenQuerySchema(Schema):
client_id = fields.Str(required=True)
......@@ -18,7 +18,7 @@ class Token(Resource):
self.model = TokenModel()
with open("./data/clients.json", "r") as clients_file:
self.clients = json.load(clients_file)
def get(self):
errors = self.schema.validate(request.args)
if errors:
......@@ -38,4 +38,4 @@ class Token(Resource):
else:
abort(403, message="Invalid client credentials")
return token
\ No newline at end of file
return token
"""
The backbone doesn't have a relational database or other backend state provider
It just saves its configuration to the local filesystem
In docker data is saved to a mounted volume for persistence
POSTS to /client create entries in clients.json. The return from the post
contains a generated secret but subsequent calls to GET /client or
GET /client/{id} do not return the secret.
Each time .find is called the .get method is called which checks the
mtime of the file and reloads it if newer. This means that new clients
should be returned
"""
import json
import os
import random
import string
class ClientModel:
file = "clients.json"
mtime = 0
clients = {}
parser = None
def __init__(self):
self.get()
def get(self):
try:
mtime = os.path.getmtime(self.file)
if mtime > self.mtime:
with open(self.file, "r") as client_file:
self.clients = json.load(client_file)
self.mtime = mtime
except FileNotFoundError as error:
self.clients = {}
self.save()
self.mtime = os.path.getmtime(self.file)
return self.clients
def find(self, client_id):
self.get()
if client_id in self.clients:
client = self.clients[client_id]
else:
client = None
return client
def add(self, client):
client["secret"] = self.secret()
self.clients[client["client_id"]] = client
self.save()
return client
def remove(self, client):
del self.clients[client["client_id"]]
self.save()
def update(self, client_updates):
client = self.find(client_updates["client_id"])
client.update(client_updates)
self.clients[client["client_id"]] = client
self.save()
return client
def save(self):
try:
with open(self.file, "w") as client_file:
client_file.write(json.dumps(self.clients, indent=2))
return True
except OSError as error:
print(str(error))
return False
def secret(self, chars=36):
res = "".join(
random.choices(
string.ascii_lowercase + string.ascii_uppercase + string.digits, k=chars
)
)
return str(res)
import json
import os
import pytest
import re
from unittest.mock import patch, mock_open, call
from client_model import ClientModel
@pytest.mark.usefixtures("mock_clients")
def test_init_with_clients(mock_clients):
# File is read on __init__
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open:
clients_file = ClientModel()
assert len(clients_file.clients.keys()) == 2
assert "client-1" in clients_file.clients.keys()
def test_init_no_clients_file():
# Handles FileNotFound on __init__
with patch("builtins.open", side_effect=FileNotFoundError()) as mock_file_open:
clients_file = ClientModel()
assert len(clients_file.clients.keys()) == 0
assert "client-1" not in clients_file.clients.keys()
@pytest.mark.usefixtures("mock_clients")
def test_get_unmodified(mock_clients):
# If modtime is unchanged file is not read
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, patch("os.path.getmtime", return_value=3) as mock_getmtime:
clients_file = ClientModel()
clients = clients_file.get()
assert mock_file_open.call_count == 1
@pytest.mark.usefixtures("mock_clients")
def test_get_modified(mock_clients):
# If modtime has changed file is re-read
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open, patch("os.path.getmtime", return_value=3) as mock_getmtime:
print(os.path.getmtime("clients.json"))
clients_file = ClientModel()
mock_getmtime.return_value = 4
print(os.path.getmtime("clients.json"))
clients = clients_file.get()
assert mock_file_open.call_count == 2
@pytest.mark.usefixtures("mock_clients")
def test_find_existing(mock_clients):
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open:
clients_file = ClientModel()
client = clients_file.find("client-1")
assert client is not None
assert client.get("subscription") == "soar.#"
@pytest.mark.usefixtures("mock_clients")
def test_find_nonexisting(mock_clients):
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open:
clients_file = ClientModel()
client = clients_file.find("client-3")
assert client is None
@pytest.mark.usefixtures("mock_clients", "mock_new_client")
def test_add(mock_clients, mock_new_client):
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open:
clients_file = ClientModel()
client = clients_file.add(mock_new_client)
assert client["client_id"] in clients_file.clients
assert "secret" in client
@pytest.mark.usefixtures("mock_clients")
def test_remove(mock_clients):
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open:
clients_file = ClientModel()
client = clients_file.find("client-2")
clients_file.remove(client)
assert len(clients_file.clients) == 1
assert "client-2" not in clients_file.clients
@pytest.mark.usefixtures("mock_clients")
def test_update(mock_clients):
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open:
clients_file = ClientModel()
client = clients_file.find("client-1")
assert client["subscription"] == "soar.#"
client["subscription"] = "soar.client-1.#"
clients_file.update(client)
client = clients_file.find("client-1")
assert len(clients_file.clients) == 2
assert client["subscription"] == "soar.client-1.#"
@pytest.mark.usefixtures("mock_clients")
def test_save(mock_clients):
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open:
clients_file = ClientModel()
mock_file_open.assert_any_call("clients.json", "r")
clients_file.save()
mock_file_open.assert_any_call("clients.json", "w")
mock_file_open().write.has_any_call(json.dumps(mock_clients))
@pytest.mark.usefixtures("mock_clients")
def test_secret(mock_clients):
with patch(
"builtins.open", mock_open(read_data=json.dumps(mock_clients))
) as mock_file_open:
secret_pattern = "^[A-Za-z0-9]+$"
clients_file = ClientModel()
secret = clients_file.secret()
assert len(secret) == 36
assert re.match(secret_pattern, secret)
secret = clients_file.secret(48)
assert len(secret) == 48
from cryptography.fernet import Fernet,InvalidToken
from cryptography.fernet import Fernet, InvalidToken
import datetime
import os
import json
......@@ -7,84 +7,73 @@ import json
TOKENS = {}
class TokenModel():
class TokenModel:
clients = None
schema = None
key = None
fernet = None
token_lifetime_hours = None
env_lifetime = 'SOAR_TOKEN_LIFETIME'
env_secret = 'SOAR_TOKEN_SECRET'
env_lifetime = "SOAR_TOKEN_LIFETIME"
env_secret = "SOAR_TOKEN_SECRET"
def __init__(self):
self.getFernet()
self.token_lifetime_hours = os.getenv(self.env_lifetime, 24)
def getFernet(self):
self.fernet = Fernet(self.getKey().encode())
self.token_lifetime_hours = int(os.getenv(self.env_lifetime, 24))
def getKey(self):
def getFernet(self):
self.fernet = Fernet(self.getKey().encode())
def getKey(self):
key = os.getenv(self.env_secret)
print(key)
if not key:
if not key:
key = Fernet.generate_key().decode()
os.environ[self.env_secret] = key
self.key = key
self.key = key
return self.key
def setSecret(self):
def setSecret(self):
if not os.getenv(self.env_secret):
os.environ[self.env_secret] = self.getKey()
os.environ[self.env_secret] = self.getKey()
def getExpiry(self):
def getExpiry(self):
now = datetime.datetime.utcnow()
expires = now + datetime.timedelta(hours=self.token_lifetime_hours)
return expires.isoformat()
def encrypt(self, client_id):
try:
expiry = self.getExpiry()
token_content = {
'client_id': client_id,
'expiry': expiry
}
token_content = {"client_id": client_id, "expiry": expiry}
token = self.fernet.encrypt(json.dumps(token_content).encode()).decode()
return {
'token': token,
'expiry': expiry
}
except KeyError as e:
return {"token": token, "expiry": expiry}
except KeyError as e:
return None
def decrypt(self, token):
try:
try:
content = json.loads(self.fernet.decrypt(token.encode()).decode())
return content
except (InvalidToken,KeyError) as e:
return content
except (InvalidToken, KeyError) as e:
return None
def get(self, client_id):
response = self.encrypt(client_id)
TOKENS[response['token']] = client_id
return response
TOKENS[response["token"]] = client_id
return response
def validate(self, token):
response = {
'valid': False
}
response = {"valid": False}
if token in TOKENS:
content = self.decrypt(token)
if content:
now = datetime.datetime.utcnow()
expires = datetime.datetime.fromisoformat(content['expiry'])
response['valid'] = expires > now
if response['valid']:
expires = datetime.datetime.fromisoformat(content["expiry"])
response["valid"] = expires > now
if response["valid"]:
response.update(content)
else:
else:
del TOKENS[token]
else:
del TOKENS[token]
return response
return response
import cryptography
from datetime import datetime, timedelta
import json
import math
import os
import pytest
import re
from unittest.mock import patch, mock_open, call
from token_model import TokenModel
def test_init():
token = TokenModel()
assert type(token.fernet) == cryptography.fernet.Fernet
assert token.token_lifetime_hours == 24
def test_get_fernet_no_env():
token = TokenModel()
token.getFernet()
assert type(token.fernet) == cryptography.fernet.Fernet
@pytest.mark.usefixtures("mock_token_secret")
def test_get_fernet_env(mock_token_secret):
with patch.dict(os.environ, {"SOAR_TOKEN_SECRET": mock_token_secret}):
token = TokenModel()
token.getFernet()
assert type(token.fernet) == cryptography.fernet.Fernet
assert token.key == mock_token_secret
def test_get_key_no_env():
token = TokenModel()
secret = token.getKey()
assert secret is not None
assert token.key is not None
assert secret == token.key
@pytest.mark.usefixtures("mock_token_secret")
def test_get_key_env(mock_token_secret):
with patch.dict(os.environ, {"SOAR_TOKEN_SECRET": mock_token_secret}):
token = TokenModel()
secret = token.getKey()
assert secret == mock_token_secret
assert token.key == mock_token_secret
def test_set_secret():
with patch.dict(os.environ):
token = TokenModel()
token.setSecret()
assert os.getenv(token.env_secret) == token.key
def test_get_expiry_no_env():
token = TokenModel()
now = datetime.utcnow()
expected = now + timedelta(hours=token.token_lifetime_hours)
returned = datetime.strptime(token.getExpiry(), "%Y-%m-%dT%H:%M:%S.%f")
diff_seconds = abs(int((returned - expected).total_seconds()))
# Although the expected and returned are calculated separately
# the difference is generally a few microseconds.
assert diff_seconds == 0
lifetime = math.ceil((returned - now).total_seconds())
assert lifetime >= 60 * 60 * 24
def test_get_expiry_env():
with patch.dict(os.environ, {"SOAR_TOKEN_LIFETIME": "1"}):
token = TokenModel()
now = datetime.utcnow()
expected = now + timedelta(hours=token.token_lifetime_hours)
returned = datetime.fromisoformat(token.getExpiry())
diff_seconds = abs(int((returned - expected).total_seconds()))
assert diff_seconds == 0
lifetime = math.ceil((returned - now).total_seconds())
assert lifetime >= 60 * 60
def test_encrypt_decrypt_no_env():
token = TokenModel()
clear = "test"
cipher = token.encrypt(clear)
decoded = token.decrypt(cipher["token"])
assert decoded["client_id"] == clear
assert decoded["expiry"] == cipher["expiry"]
@pytest.mark.usefixtures("mock_token_secret")
def test_encrypt_decrypt_env(mock_token_secret):
with patch.dict(os.environ, {"SOAR_TOKEN_SECRET": mock_token_secret}):
token = TokenModel()
clear = "test"
cipher = token.encrypt(clear)
decoded = token.decrypt(cipher["token"])
assert decoded["client_id"] == clear
assert decoded["expiry"] == cipher["expiry"]
# A separate instance with the same secret
# is able to decode existing encrypted data
token = TokenModel()
decoded = token.decrypt(cipher["token"])
assert decoded["client_id"] == clear
assert decoded["expiry"] == cipher["expiry"]
def test_get():
# get returns an encrypted token
token = TokenModel()
response = token.get("test")
assert "token" in response
assert "expiry" in response
decoded = token.decrypt(response["token"])
assert "client_id" in decoded
assert decoded["client_id"] == "test"
def test_validate():
token = TokenModel()
valid_response = token.get("test")
valid_token = valid_response["token"]
validation = token.validate(valid_token)
assert validation["valid"]
assert "client_id" in validation
invalid_token = "abc" + valid_token[3:]
validation = token.validate(invalid_token)
assert not validation["valid"]
assert "client_id" not in validation
-r requirements.txt
black==23.1.0
pytest==7.2.1
pytest-mock==3.10.0
pytest-rabbitmq==2.2.1
\ No newline at end of file
......@@ -16,7 +16,7 @@ jinja2==3.1.2 ; python_version >= '3.7'
kombu==5.2.4 ; python_version >= '3.7'
markupsafe==2.1.1 ; python_version >= '3.7'
marshmallow==3.19.0
packaging==21.3 ; python_version >= '3.6'
packaging>=22.0 ; python_version >= '3.6'
pika==1.3.1
pubsubpy==2.3.0
pycparser==2.21
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment