diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..98dca648c2c4510337f3b6cf9f25e112f8a12530 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +clients.json +examples/ +rmq.log \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000000000000000000000000000000000000..3956649ab33d5e6dd107b6583293810ddc7a2acd --- /dev/null +++ b/Pipfile @@ -0,0 +1,20 @@ +[[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] + +[requires] +python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000000000000000000000000000000000000..5e5da41e3be47a76e766b2ab2bb56cf5c531482e --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,370 @@ +{ + "_meta": { + "hash": { + "sha256": "d216458acf3005c6f459eb1d74f7d5bbda1a7b5e1e042efe855739943c4674d4" + }, + "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:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d", + "sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd", + "sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146", + "sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7", + "sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436", + "sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0", + "sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828", + "sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b", + "sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55", + "sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36", + "sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50", + "sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2", + "sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a", + "sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8", + "sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0", + "sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548", + "sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320", + "sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748", + "sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249", + "sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959", + "sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f", + "sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0", + "sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd", + "sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220", + "sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c", + "sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722" + ], + "index": "pypi", + "version": "==38.0.3" + }, + "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:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.18.2" + }, + "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:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b", + "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313" + ], + "markers": "python_version < '3.10'", + "version": "==5.1.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:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + }, + "marshmallow": { + "hashes": [ + "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78", + "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b" + ], + "index": "pypi", + "version": "==3.19.0" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "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_full_version >= '3.6.8'", + "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:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427", + "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2" + ], + "version": "==2022.6" + }, + "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:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1", + "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8" + ], + "markers": "python_version >= '3.7'", + "version": "==3.10.0" + } + }, + "develop": {} +} diff --git a/README.md b/README.md index f4374047eb3d69ef26107ec97983ba0790f1bf06..fdd1d8c6eabedc62f68e586631a22caf790b6824 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,93 @@ # Communications Backbone -Communications Backbone by C2 Team (NOC) \ No newline at end of file +Communications Backbone by C2 Team (NOC) + +## DJ prototype + +I did this in freedom-fire-day so it doesn't currently follow the flask template. + +It's also a bit weird because it writes to a local ignored `clients.json` file +instead of using a database. I did this as a placeholder because we've not yet +decided what the infrastructure looks like. + +### Data flow + +- Client A sends to `client-a-outbox` (or POSTs to API /send - not yet implemented) +- Messages are forwarded from `client-a-outbox` to `soar-publish` +- Messages are published from `soar-publish` with the given topic read from the message +- Subscribers listen to for messages +- Subscription is delivered to `client-b-inbox` +- Client B reads from `client-b-inbox (or GETs from API /receive) + +There is a parallel flow when a client sends to `client-a-notify` in which case the +messages are delivered through the broadcast exchange to all clients `client-x-inbox`. + +### Auth placeholder + +As a proxy for proper authentication, when you post a client a random secret is +returned in the response. To send to / receive from the bus you then call the API +with the client_id and secret and it checks they match. The client_id determines +which queues it reads from. + +Subsequent requests to the client endpoint return the client_id but not the secret. + +### Setup + +``` +pipenv install +``` + +### Running + +#### RabbitMQ + +`docker run --rm -p 5672:5672 -d --hostname rmq --name rmq rabbitmq:management` + +#### API + +``` +pipenv run python api.py +``` + +#### Create some clients + +`POST` to `http://localhost:3000/clients` + +#### Event bus + +``` +pipenv run python soar_bus.py +``` + +#### Send / Receive directly + +``` +# Send a message +pipenv run python client_send.py noc-c2-outbox 'soar.noc.slocum.something' from noc-c2 +``` + +``` +# Receive messages +pipenv run python client_read.py noc-sfmc-inbox +``` + +#### Receive via API + +As a placeholder for proper authentication you post the `client_id` and +`secret` and it checks that a client with that id exists and that the +secret matches before allowing the request. + +This should be replaced with a proper auth layer. + +`GET http://localhost:5000/receive?client_id=[client_id]&secret=[secret]` + +### Components + +- `soar_bus.py` - Run all the components threaded based on existing clients +- `soar_forward.py` - Listen for messages on queue A and forward messages to queue B +- `soar_publish.py` - Listen for messages on queue A and publish on exchange B +- `soar_broadcast.py` - Listen for messages on queue A and broadcast on exchange B +- `soar_subscribe.py` - Create subscriptions to both the publish and broadcast exchange - deliver to queue A + (I think this should probably be 2 separate functions to keep things nice and simple) +- `soar_push.py` - Not yet implemented - Listen for messages on queue A and POST to the client's webhook URL + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/api.py b/api.py new file mode 100644 index 0000000000000000000000000000000000000000..064555687ee72e96b3156f2d81f98cba705f225e --- /dev/null +++ b/api.py @@ -0,0 +1,26 @@ +from flask import Flask +from flask_restful import Api +from endpoints.clients import Client, ClientList +from endpoints.receive import Receive +from endpoints.send import Send +from endpoints.notify import Notify +from endpoints.token import Token +from flask_cors import CORS +from models.token import TokenModel + +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") + +if __name__ == "__main__": + app.run(debug=True, port=8087) diff --git a/client_read.py b/client_read.py new file mode 100644 index 0000000000000000000000000000000000000000..fe860600bd0efc63cbf8eba2b87547e1333784a6 --- /dev/null +++ b/client_read.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +import pika, sys, os, json + + +def main(): + connection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost")) + channel = connection.channel() + + queue_name = sys.argv[1] + + channel.queue_declare(queue=queue_name, durable=True) + + def callback(ch, method, properties, body): + message = json.loads(body.decode()) + print(" [x] Received %r" % message) + + channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True) + + print(" [*] Waiting for messages. To exit press CTRL+C") + channel.start_consuming() + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("Interrupted") + try: + sys.exit(0) + except SystemExit: + os._exit(0) diff --git a/client_send.py b/client_send.py new file mode 100644 index 0000000000000000000000000000000000000000..42197be815668bbf2a21fe3d679e6accf8bfb521 --- /dev/null +++ b/client_send.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +import pika +import sys +import json + + +connection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost")) +channel = connection.channel() + +queue_name = sys.argv[1] +topic = sys.argv[2] +message = " ".join(sys.argv[3:]) or "Hello World!" +body = json.dumps({"topic": topic, "message": message}) +channel.queue_declare(queue=queue_name, durable=True) +channel.basic_publish(exchange="", routing_key=queue_name, body=body) +connection.close() diff --git a/endpoints/__init__.py b/endpoints/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/endpoints/auth_resource.py b/endpoints/auth_resource.py new file mode 100644 index 0000000000000000000000000000000000000000..5ebd2019ecabb8d29407de80b42a7f8a35c59176 --- /dev/null +++ b/endpoints/auth_resource.py @@ -0,0 +1,25 @@ +import json +from flask_restful import Resource, abort +from models.token import TokenModel + +class AuthResource(Resource): + + def __init__(self): + self.token = TokenModel() + with open("clients.json", "r") as clients_file: + self.clients = json.load(clients_file) + + def auth(self, request): + allow = False + auth = request.headers.get('Authorization', False) + if auth: + token = auth.split(' ').pop() + parsed = self.token.validate(token) + 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 diff --git a/endpoints/clients.py b/endpoints/clients.py new file mode 100644 index 0000000000000000000000000000000000000000..a079e934d451dd14a85251c4f94d6d9faa669058 --- /dev/null +++ b/endpoints/clients.py @@ -0,0 +1,140 @@ +from flask_restful import Resource, request, abort +from marshmallow import Schema, fields +import json +import os +import random +import string + + +class ClientSchema(Schema): + client_id = fields.Str(required=True) + client_name = fields.Str(required=True) + subscription = fields.Str(required=True) + + +class ClientsFile: + 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) + except FileNotFoundError as error: + self.clients = {} + self.save() + + 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) + +clients_file = ClientsFile() + +# Client +class Client(Resource): + clients_file = None + def __init__(self): + self.schema = ClientSchema() + self.clients_file = ClientsFile() + + 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, todo_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 = ClientsFile() + + 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 = clients_file.find(args["client_id"]) + if client: + abort(403, message="Duplicate client id: {}".format(client_id)) + else: + client = clients_file.add(args) + return client, 201 diff --git a/endpoints/hello.py b/endpoints/hello.py new file mode 100644 index 0000000000000000000000000000000000000000..7bb0bab0de35c7cebf2d22fac1f1213b16ef90df --- /dev/null +++ b/endpoints/hello.py @@ -0,0 +1,6 @@ +from flask_restful import Resource + + +class HelloWorld(Resource): + def get(self): + return {"hello": "world"} diff --git a/endpoints/notify.py b/endpoints/notify.py new file mode 100644 index 0000000000000000000000000000000000000000..a953db879eee9b7b7b22ce34de44e57f93d9e772 --- /dev/null +++ b/endpoints/notify.py @@ -0,0 +1,40 @@ +import json +from flask_restful import request, abort +from marshmallow import Schema, fields +import pika +from endpoints.auth_resource import AuthResource + + +class NotifySchema(Schema): + body = fields.Str(required=True) + +class Notify(AuthResource): + clients = None + schema = None + + def __init__(self): + super().__init__() + self.schema = NotifySchema() + + def post(self): + args = request.get_json() + errors = self.schema.validate(args) + if errors: + abort(400, message=str(errors)) + + allow = False + body = args.get("body") + message = { + 'topic': 'broadcast', + 'message': body, + } + + allow = self.auth(request) + + if allow: + notify_queue = self.client['client_id'] + "-broadcast" + connection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost")) + channel = connection.channel() + channel.queue_declare(queue=notify_queue, durable=True) + channel.basic_publish(exchange="", routing_key=notify_queue, body=json.dumps(message)) + connection.close() diff --git a/endpoints/receive.py b/endpoints/receive.py new file mode 100644 index 0000000000000000000000000000000000000000..bc1febaa41d5fbd2e724c2bf77521842952c6c88 --- /dev/null +++ b/endpoints/receive.py @@ -0,0 +1,50 @@ +from flask_restful import request, abort +from marshmallow import Schema, fields +import pika +import json +from models.token import TokenModel +from endpoints.auth_resource import AuthResource + + +class ReceiveQuerySchema(Schema): + max_messages = fields.Int(required=False) + + +class Receive(AuthResource): + clients = None + schema = None + + def __init__(self): + super().__init__() + self.schema = ReceiveQuerySchema() + + def get(self): + errors = self.schema.validate(request.args) + if errors: + abort(400, message=str(errors)) + + messages = [] + max_messages = request.args.get("max_messages", 10) + + allow = self.auth(request) + if allow: + inbox_queue = self.client['client_id'] + "-inbox" + + if allow: + connection = pika.BlockingConnection( + pika.ConnectionParameters(host="localhost") + ) + channel = connection.channel() + channel.queue_declare(queue=inbox_queue, durable=True) + while len(messages) < max_messages: + method_frame, header_frame, body = channel.basic_get(inbox_queue) + if method_frame: + print(method_frame, header_frame, body) + channel.basic_ack(method_frame.delivery_tag) + messages.append(json.loads(body.decode())) + else: + print("No message returned") + break + channel.close() + connection.close() + return messages diff --git a/endpoints/send.py b/endpoints/send.py new file mode 100644 index 0000000000000000000000000000000000000000..900e8a5c63d066921315e9e93e22fb8d4eb59d9a --- /dev/null +++ b/endpoints/send.py @@ -0,0 +1,40 @@ +import json +from flask_restful import request, abort +from marshmallow import Schema, fields +import pika +from endpoints.auth_resource import AuthResource + +class SendSchema(Schema): + body = fields.Str(required=True) + topic = fields.Str(required=True) + +class Send(AuthResource): + clients = None + schema = None + + def __init__(self): + super().__init__() + self.schema = SendSchema() + + def post(self): + args = request.get_json() + errors = self.schema.validate(args) + if errors: + 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" + + connection = pika.BlockingConnection(pika.ConnectionParameters(host="localhost")) + channel = connection.channel() + channel.queue_declare(queue=outbox_queue, durable=True) + message = { + 'topic': topic, + 'message': body, + } + channel.basic_publish(exchange="", routing_key=outbox_queue, body=json.dumps(message)) + connection.close() diff --git a/endpoints/token.py b/endpoints/token.py new file mode 100644 index 0000000000000000000000000000000000000000..01dbedf3f94c83c5663f9fb25c7fc7d9e6f7c797 --- /dev/null +++ b/endpoints/token.py @@ -0,0 +1,43 @@ +import json +from flask_restful import Resource, request, abort +from marshmallow import Schema, fields +import pika +from models.token import TokenModel + + +class TokenQuerySchema(Schema): + client_id = fields.Str(required=True) + secret = fields.Str(required=True) + + +class Token(Resource): + clients = None + schema = None + model = None + + def __init__(self): + self.schema = TokenQuerySchema() + self.model = TokenModel() + with open("clients.json", "r") as clients_file: + self.clients = json.load(clients_file) + + def get(self): + errors = self.schema.validate(request.args) + if errors: + abort(400, message=str(errors)) + + token = None + allow = False + max_messages = request.args.get("max_messages", 10) + client_id = request.args.get("client_id") + if client_id in self.clients: + client = self.clients.get(client_id) + if request.args.get("secret") == client.get("secret"): + allow = True + + if allow: + token = self.model.get(client_id) + + else: + abort(403, message="Invalid client credentials") + return token \ No newline at end of file diff --git a/models/token.py b/models/token.py new file mode 100644 index 0000000000000000000000000000000000000000..6721a9971abe59a20d249f52e5bddbe8d4974acc --- /dev/null +++ b/models/token.py @@ -0,0 +1,90 @@ +from cryptography.fernet import Fernet,InvalidToken +import datetime +import os +import json + + +TOKENS = {} + + +class TokenModel(): + clients = None + schema = None + key = None + fernet = None + token_lifetime_hours = None + 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()) + + def getKey(self): + key = os.getenv(self.env_secret) + print(key) + if not key: + key = Fernet.generate_key().decode() + os.environ[self.env_secret] = key + self.key = key + return self.key + + def setSecret(self): + if not os.getenv(self.env_secret): + os.environ[self.env_secret] = self.getKey() + + 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 = self.fernet.encrypt(json.dumps(token_content).encode()).decode() + return { + 'token': token, + 'expiry': expiry + } + except KeyError as e: + return None + + def decrypt(self, token): + try: + content = json.loads(self.fernet.decrypt(token.encode()).decode()) + 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 + + def validate(self, token): + 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']: + response.update(content) + else: + del TOKENS[token] + else: + del TOKENS[token] + return response + + + diff --git a/soar_broadcast.py b/soar_broadcast.py new file mode 100644 index 0000000000000000000000000000000000000000..de5062db99981096a513d372b2acd6dcd0fa28d4 --- /dev/null +++ b/soar_broadcast.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +import pika + + +def get_connection(): + return pika.BlockingConnection(pika.ConnectionParameters(host="localhost")) + + +def deliver(body, broadcast_exchange): + print("broadcast") + deliver_connection = get_connection() + deliver_channel = deliver_connection.channel() + deliver_channel.exchange_declare( + exchange=broadcast_exchange, exchange_type="fanout" + ) + deliver_channel.basic_publish( + exchange=broadcast_exchange, routing_key="", body=body + ) + deliver_connection.close() + + +def listen(queue_name, broadcast_exchange): + def bcast_callback(ch, method, properties, body): + delivered = deliver(body, broadcast_exchange) + ch.basic_ack(delivery_tag=method.delivery_tag) + + listen_connection = get_connection() + listen_channel = listen_connection.channel() + listen_channel.queue_declare(queue=queue_name, durable=True) + listen_channel.basic_consume(queue=queue_name, on_message_callback=bcast_callback) + listen_channel.start_consuming() + + +def broadcast(queue_name, broadcast_exchange="soar_broadcast"): + listen(queue_name, broadcast_exchange) diff --git a/soar_bus.py b/soar_bus.py new file mode 100644 index 0000000000000000000000000000000000000000..3576ea3a75a390ec4cd2de7ce111d7435d5eabf4 --- /dev/null +++ b/soar_bus.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python + +# Queues +# (per client) +# [client_id]-inbox - receive subscriptions +# [client_id]-outbox - send messages to the bus +# [client-id]-broadcast - send message to all subscribers? - eg notify of downtime +# soar-publisher - fan-in from client-outboxes +# soar-dlq - undeliverables +# soar-broadcast - admin messages forwarded to all client-inboxes regardless of subscriptions + +import concurrent.futures +from endpoints.clients import ClientsFile +from soar_broadcast import broadcast +from soar_forward import forward +from soar_publish import publish +from soar_subscribe import subscribe + +THREADS = [] +EXCHANGES = { + "publish": "soar_publish", + "broadcast": "soar_broadcast", +} + + +def main(): + clients_file = ClientsFile() + clients = clients_file.get() + + with concurrent.futures.ProcessPoolExecutor() as executor: + # publish + thread = executor.submit(publish, "soar-publish", EXCHANGES.get("publish")) + THREADS.append(thread) + + for (id, client) in clients.items(): + # forward + thread = executor.submit(forward, f"{id}-outbox", "soar-publish") + THREADS.append(thread) + # broadcast + thread = executor.submit( + broadcast, f"{id}-broadcast", EXCHANGES.get("broadcast") + ) + THREADS.append(thread) + # subscribe + thread = executor.submit( + subscribe, + f"{id}-inbox", + client["subscription"], + EXCHANGES.get("publish"), + EXCHANGES.get("broadcast"), + ) + THREADS.append(thread) + # push + # TODO - add optional webhook target to client and post to webhook target + # if present + + +if __name__ == "__main__": + main() diff --git a/soar_forward.py b/soar_forward.py new file mode 100644 index 0000000000000000000000000000000000000000..855d02f30ff49adeeaaa7e031cfb7aeca7812af4 --- /dev/null +++ b/soar_forward.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +import pika + + +def get_connection(): + return pika.BlockingConnection(pika.ConnectionParameters(host="localhost")) + + +def deliver(body, queue_name): + print("forward to: %s" % queue_name) + deliver_connection = get_connection() + deliver_channel = deliver_connection.channel() + deliver_channel.queue_declare(queue=queue_name, durable=True) + deliver_channel.basic_publish(exchange="", routing_key=queue_name, body=body) + deliver_connection.close() + + +def listen(from_queue_name, to_queue_name): + def fwd_callback(ch, method, properties, body): + delivered = deliver(body, to_queue_name) + ch.basic_ack(delivery_tag=method.delivery_tag) + + listen_connection = get_connection() + listen_channel = listen_connection.channel() + listen_channel.queue_declare(queue=from_queue_name, durable=True) + listen_channel.basic_consume( + queue=from_queue_name, on_message_callback=fwd_callback + ) + listen_channel.start_consuming() + + +def forward(from_queue_name, to_queue_name): + listen(from_queue_name, to_queue_name) diff --git a/soar_publish.py b/soar_publish.py new file mode 100644 index 0000000000000000000000000000000000000000..52121faae63eead99d410d4534e3888f11842eb0 --- /dev/null +++ b/soar_publish.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +import pika +import json +import sys + + +def get_connection(): + return pika.BlockingConnection(pika.ConnectionParameters(host="localhost")) + + +def deliver(body, topic, publish_exchange): + print("publish on topic: %s" % topic) + deliver_connection = get_connection() + deliver_channel = deliver_connection.channel() + deliver_channel.exchange_declare(exchange=publish_exchange, exchange_type="topic") + deliver_channel.basic_publish( + exchange=publish_exchange, routing_key=topic, body=body + ) + deliver_connection.close() + + +def listen(queue_name, publish_exchange): + def pub_callback(ch, method, properties, body): + message = json.loads(body.decode()) + topic = message["topic"] + deliver(body, topic, publish_exchange) + ch.basic_ack(delivery_tag=method.delivery_tag) + + listen_connection = get_connection() + listen_channel = listen_connection.channel() + listen_channel.queue_declare(queue=queue_name, durable=True) + listen_channel.basic_consume(queue=queue_name, on_message_callback=pub_callback) + listen_channel.start_consuming() + + +def publish(queue_name, publish_exchange="soar_publish"): + listen(queue_name, publish_exchange) + + +if __name__ == "__main__": + queue_name = sys.argv[1] + publish(queue_name) diff --git a/soar_push.py b/soar_push.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/soar_subscribe.py b/soar_subscribe.py new file mode 100644 index 0000000000000000000000000000000000000000..9ce847738e26c9ccc4530c5b0476b98d0f0b9cff --- /dev/null +++ b/soar_subscribe.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +import pika + + +def get_connection(): + return pika.BlockingConnection(pika.ConnectionParameters(host="localhost")) + + +def subscribe( + queue_name, + topic, + publish_exchange="soar_publish", + broadcast_exchange="soar_broadcast", +): + adm_connection = get_connection() + admin_channel = adm_connection.channel() + admin_channel.exchange_declare(exchange=broadcast_exchange, exchange_type="fanout") + admin_channel.queue_bind(exchange=broadcast_exchange, queue=queue_name) + sub_connection = get_connection() + subscriber_channel = sub_connection.channel() + subscriber_channel.exchange_declare( + exchange=publish_exchange, exchange_type="topic" + ) + subscriber_channel.queue_bind( + exchange=publish_exchange, queue=queue_name, routing_key=topic + )