From 666a2b4d3943554ca72d930ee5991c8de48ecdde Mon Sep 17 00:00:00 2001 From: Prasanth Date: Wed, 13 Dec 2023 23:08:47 +0530 Subject: [PATCH] Azure Auth Support (#209) * chore upgrade `jsonschema` to 4.20.0 * chore upgrade `textx` to 4.0.1 * chore upgrade `restrictedpython` to 7.0 * chore upgrade `faker` to 20.1.0 * add new package `msal` to 1.26.0 * Add azure auth support * update badssl certificates --- Pipfile | 11 +- Pipfile.lock | 342 +++++++++--------- dothttp/__init__.py | 47 ++- dothttp/__version__.py | 2 +- dothttp/azure_auth.py | 158 ++++++++ dothttp/exceptions.py | 6 + dothttp/http.tx | 38 +- dothttp/parse_models.py | 58 ++- dothttp/request_base.py | 7 + examples/auth/azure_auth.http | 19 + requirements.txt | 9 +- test/core/requests/azureauth.http | 12 + .../root_cert/certs/badssl.com-client.p12 | Bin 2789 -> 2789 bytes test/core/root_cert/certs/cert.crt | 32 +- test/core/root_cert/certs/key.key | 2 +- test/core/root_cert/certs/no-password.pem | 32 +- test/core/test_azure_auth.py | 199 ++++++++++ 17 files changed, 764 insertions(+), 210 deletions(-) create mode 100644 dothttp/azure_auth.py create mode 100644 examples/auth/azure_auth.http create mode 100644 test/core/requests/azureauth.http create mode 100644 test/core/test_azure_auth.py diff --git a/Pipfile b/Pipfile index 67dbd8f..06d7e29 100644 --- a/Pipfile +++ b/Pipfile @@ -4,23 +4,24 @@ verify_ssl = true name = "pypi" [packages] -jsonschema = "==4.19.2" +jsonschema = "==4.20.0" jstyleson = "==0.0.2" requests = "==2.31.0" -textx = "==3.1.1" +textx = "==4.0.1" js2py = "==0.74" requests-pkcs12 = "==1.22" parsys-requests-unixsocket = "==0.3.1" requests-aws4auth = "==1.2.3" requests-ntlm = "==1.2.0" -restrictedpython = "==6.2" -faker = "==20.0.0" +restrictedpython = "==7.0" +faker = "==20.1.0" requests-hawk = "==1.2.1" pyyaml = "==6.0.1" toml = "==0.10.2" -python-magic = "*" +msal = "==1.26.0" [dev-packages] +python-magic = "*" waitress = "==2.1.1" flask = "*" pyperf = "==2.6.2" diff --git a/Pipfile.lock b/Pipfile.lock index f2a9d54..60284ba 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d784583412a858aa01836229bf6d8e5acf5904b07a34fc2d1516351425d753ee" + "sha256": "1421ec33b65712f236f9e8f48e766e06c0356b6ce0d30b80293872910213e32b" }, "pipfile-spec": 6, "requires": { @@ -33,11 +33,11 @@ }, "certifi": { "hashes": [ - "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", - "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", + "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" ], "markers": "python_version >= '3.6'", - "version": "==2023.7.22" + "version": "==2023.11.17" }, "cffi": { "hashes": [ @@ -195,49 +195,48 @@ }, "cryptography": { "hashes": [ - "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf", - "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84", - "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e", - "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8", - "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7", - "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1", - "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88", - "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86", - "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179", - "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81", - "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20", - "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548", - "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d", - "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d", - "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5", - "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1", - "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147", - "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936", - "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797", - "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696", - "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72", - "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da", - "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723" + "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960", + "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a", + "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc", + "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a", + "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf", + "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1", + "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39", + "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406", + "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a", + "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a", + "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c", + "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be", + "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15", + "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2", + "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d", + "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157", + "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003", + "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248", + "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a", + "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec", + "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309", + "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7", + "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d" ], "markers": "python_version >= '3.7'", - "version": "==41.0.5" + "version": "==41.0.7" }, "faker": { "hashes": [ - "sha256:171b27ba106cf69e30a91ac471407c2362bd6af27738e2461dc441aeff5eed91", - "sha256:df44b68b9d231e784f4bfe616d781576cfef9f0c5d9a17671bf84dc10d7b44d6" + "sha256:562a3a09c3ed3a1a7b20e13d79f904dfdfc5e740f72813ecf95e4cf71e5a2f52", + "sha256:aeb3e26742863d1e387f9d156f1c36e14af63bf5e6f36fb39b8c27f6a903be38" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==20.0.0" + "version": "==20.1.0" }, "idna": { "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" ], "markers": "python_version >= '3.5'", - "version": "==3.4" + "version": "==3.6" }, "js2py": { "hashes": [ @@ -249,19 +248,19 @@ }, "jsonschema": { "hashes": [ - "sha256:c9ff4d7447eed9592c23a12ccee508baf0dd0d59650615e847feb6cdca74f392", - "sha256:eee9e502c788e89cb166d4d37f43084e3b64ab405c795c03d343a4dbc2c810fc" + "sha256:4f614fd46d8d61258610998997743ec5492a648b33cf478c1ddc23ed4598a5fa", + "sha256:ed6231f0429ecf966f5bc8dfef245998220549cbbcf140f913b7464c52c3b6b3" ], "index": "pypi", - "version": "==4.19.2" + "version": "==4.20.0" }, "jsonschema-specifications": { "hashes": [ - "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1", - "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb" + "sha256:9472fc4fea474cd74bea4a2b190daeccb5a9e4db2ea80efcf7a1b582fc9a81b8", + "sha256:e74ba7c0a65e8cb49dc26837d6cfe576557084a8b423ed16a420984228104f93" ], "markers": "python_version >= '3.8'", - "version": "==2023.7.1" + "version": "==2023.11.2" }, "jstyleson": { "hashes": [ @@ -277,6 +276,14 @@ ], "version": "==1.1.0" }, + "msal": { + "hashes": [ + "sha256:224756079fe338be838737682b49f8ebc20a87c1c5eeaf590daae4532b83de15", + "sha256:be77ba6a8f49c9ff598bbcdc5dfcf1c9842f3044300109af738e8c3e371065b5" + ], + "index": "pypi", + "version": "==1.26.0" + }, "parsys-requests-unixsocket": { "hashes": [ "sha256:e40ace8ddb9e7549b98db11fb1a930d6648af0ece0330650aa9253cbc8cecf02" @@ -298,6 +305,17 @@ ], "version": "==2.7.1" }, + "pyjwt": { + "extras": [ + "crypto" + ], + "hashes": [ + "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de", + "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320" + ], + "markers": "python_version >= '3.7'", + "version": "==2.8.0" + }, "pyspnego": { "hashes": [ "sha256:3d5c5c28dbd0cd6a679acf45219630254db3c0e5ad4a16de521caa0585b088c0", @@ -314,14 +332,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.8.2" }, - "python-magic": { - "hashes": [ - "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", - "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3" - ], - "index": "pypi", - "version": "==0.4.27" - }, "pyyaml": { "hashes": [ "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", @@ -380,11 +390,11 @@ }, "referencing": { "hashes": [ - "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf", - "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0" + "sha256:689e64fe121843dcfd57b71933318ef1f91188ffb45367332700a86ac8fd6161", + "sha256:bdcd3efb936f82ff86f993093f6da7435c7de69a3b3a5a06678a6050184bee99" ], "markers": "python_version >= '3.8'", - "version": "==0.30.2" + "version": "==0.32.0" }, "requests": { "hashes": [ @@ -428,116 +438,116 @@ }, "restrictedpython": { "hashes": [ - "sha256:7c2ffa4904300d67732f841d8a975dcdc53eba4c1cdc9d84b97684ef12304a3d", - "sha256:db73eb7e3b39650f0d21d10cc8dda9c0e2986e621c94b0c5de32fb0dee3a08af" + "sha256:53704afbbc350fdc8fb245441367be671c9f8380869201b2e8452e74fce3db14", + "sha256:8bb40a822090bed9c7b814d69345b0796db70cc86715d141efc937862f37c6d2" ], "index": "pypi", - "version": "==6.2" + "version": "==7.0" }, "rpds-py": { "hashes": [ - "sha256:0525847f83f506aa1e28eb2057b696fe38217e12931c8b1b02198cfe6975e142", - "sha256:05942656cb2cb4989cd50ced52df16be94d344eae5097e8583966a1d27da73a5", - "sha256:0831d3ecdea22e4559cc1793f22e77067c9d8c451d55ae6a75bf1d116a8e7f42", - "sha256:0853da3d5e9bc6a07b2486054a410b7b03f34046c123c6561b535bb48cc509e1", - "sha256:08e6e7ff286254016b945e1ab632ee843e43d45e40683b66dd12b73791366dd1", - "sha256:0a38612d07a36138507d69646c470aedbfe2b75b43a4643f7bd8e51e52779624", - "sha256:0bedd91ae1dd142a4dc15970ed2c729ff6c73f33a40fa84ed0cdbf55de87c777", - "sha256:0c5441b7626c29dbd54a3f6f3713ec8e956b009f419ffdaaa3c80eaf98ddb523", - "sha256:0e9e976e0dbed4f51c56db10831c9623d0fd67aac02853fe5476262e5a22acb7", - "sha256:0fadfdda275c838cba5102c7f90a20f2abd7727bf8f4a2b654a5b617529c5c18", - "sha256:1096ca0bf2d3426cbe79d4ccc91dc5aaa73629b08ea2d8467375fad8447ce11a", - "sha256:171d9a159f1b2f42a42a64a985e4ba46fc7268c78299272ceba970743a67ee50", - "sha256:188912b22b6c8225f4c4ffa020a2baa6ad8fabb3c141a12dbe6edbb34e7f1425", - "sha256:1b4cf9ab9a0ae0cb122685209806d3f1dcb63b9fccdf1424fb42a129dc8c2faa", - "sha256:1e04581c6117ad9479b6cfae313e212fe0dfa226ac727755f0d539cd54792963", - "sha256:1fa73ed22c40a1bec98d7c93b5659cd35abcfa5a0a95ce876b91adbda170537c", - "sha256:2124f9e645a94ab7c853bc0a3644e0ca8ffbe5bb2d72db49aef8f9ec1c285733", - "sha256:240687b5be0f91fbde4936a329c9b7589d9259742766f74de575e1b2046575e4", - "sha256:25740fb56e8bd37692ed380e15ec734be44d7c71974d8993f452b4527814601e", - "sha256:27ccc93c7457ef890b0dd31564d2a05e1aca330623c942b7e818e9e7c2669ee4", - "sha256:281c8b219d4f4b3581b918b816764098d04964915b2f272d1476654143801aa2", - "sha256:2d34a5450a402b00d20aeb7632489ffa2556ca7b26f4a63c35f6fccae1977427", - "sha256:301bd744a1adaa2f6a5e06c98f1ac2b6f8dc31a5c23b838f862d65e32fca0d4b", - "sha256:30e5ce9f501fb1f970e4a59098028cf20676dee64fc496d55c33e04bbbee097d", - "sha256:33ab498f9ac30598b6406e2be1b45fd231195b83d948ebd4bd77f337cb6a2bff", - "sha256:35585a8cb5917161f42c2104567bb83a1d96194095fc54a543113ed5df9fa436", - "sha256:389c0e38358fdc4e38e9995e7291269a3aead7acfcf8942010ee7bc5baee091c", - "sha256:3acadbab8b59f63b87b518e09c4c64b142e7286b9ca7a208107d6f9f4c393c5c", - "sha256:3b7a64d43e2a1fa2dd46b678e00cabd9a49ebb123b339ce799204c44a593ae1c", - "sha256:3c8c0226c71bd0ce9892eaf6afa77ae8f43a3d9313124a03df0b389c01f832de", - "sha256:429349a510da82c85431f0f3e66212d83efe9fd2850f50f339341b6532c62fe4", - "sha256:466030a42724780794dea71eb32db83cc51214d66ab3fb3156edd88b9c8f0d78", - "sha256:47aeceb4363851d17f63069318ba5721ae695d9da55d599b4d6fb31508595278", - "sha256:48aa98987d54a46e13e6954880056c204700c65616af4395d1f0639eba11764b", - "sha256:4b2416ed743ec5debcf61e1242e012652a4348de14ecc7df3512da072b074440", - "sha256:4d0a675a7acbbc16179188d8c6d0afb8628604fc1241faf41007255957335a0b", - "sha256:4eb74d44776b0fb0782560ea84d986dffec8ddd94947f383eba2284b0f32e35e", - "sha256:4f8a1d990dc198a6c68ec3d9a637ba1ce489b38cbfb65440a27901afbc5df575", - "sha256:513ccbf7420c30e283c25c82d5a8f439d625a838d3ba69e79a110c260c46813f", - "sha256:5210a0018c7e09c75fa788648617ebba861ae242944111d3079034e14498223f", - "sha256:54cdfcda59251b9c2f87a05d038c2ae02121219a04d4a1e6fc345794295bdc07", - "sha256:56dd500411d03c5e9927a1eb55621e906837a83b02350a9dc401247d0353717c", - "sha256:57ec6baec231bb19bb5fd5fc7bae21231860a1605174b11585660236627e390e", - "sha256:5f1519b080d8ce0a814f17ad9fb49fb3a1d4d7ce5891f5c85fc38631ca3a8dc4", - "sha256:6174d6ad6b58a6bcf67afbbf1723420a53d06c4b89f4c50763d6fa0a6ac9afd2", - "sha256:68172622a5a57deb079a2c78511c40f91193548e8ab342c31e8cb0764d362459", - "sha256:6915fc9fa6b3ec3569566832e1bb03bd801c12cea030200e68663b9a87974e76", - "sha256:6b75b912a0baa033350367a8a07a8b2d44fd5b90c890bfbd063a8a5f945f644b", - "sha256:6f5dcb658d597410bb7c967c1d24eaf9377b0d621358cbe9d2ff804e5dd12e81", - "sha256:6f8d7fe73d1816eeb5378409adc658f9525ecbfaf9e1ede1e2d67a338b0c7348", - "sha256:7036316cc26b93e401cedd781a579be606dad174829e6ad9e9c5a0da6e036f80", - "sha256:7188ddc1a8887194f984fa4110d5a3d5b9b5cd35f6bafdff1b649049cbc0ce29", - "sha256:761531076df51309075133a6bc1db02d98ec7f66e22b064b1d513bc909f29743", - "sha256:7979d90ee2190d000129598c2b0c82f13053dba432b94e45e68253b09bb1f0f6", - "sha256:8015835494b21aa7abd3b43fdea0614ee35ef6b03db7ecba9beb58eadf01c24f", - "sha256:81c4d1a3a564775c44732b94135d06e33417e829ff25226c164664f4a1046213", - "sha256:81cf9d306c04df1b45971c13167dc3bad625808aa01281d55f3cf852dde0e206", - "sha256:88857060b690a57d2ea8569bca58758143c8faa4639fb17d745ce60ff84c867e", - "sha256:8c567c664fc2f44130a20edac73e0a867f8e012bf7370276f15c6adc3586c37c", - "sha256:91bd2b7cf0f4d252eec8b7046fa6a43cee17e8acdfc00eaa8b3dbf2f9a59d061", - "sha256:9620650c364c01ed5b497dcae7c3d4b948daeae6e1883ae185fef1c927b6b534", - "sha256:9b007c2444705a2dc4a525964fd4dd28c3320b19b3410da6517cab28716f27d3", - "sha256:9bf9acce44e967a5103fcd820fc7580c7b0ab8583eec4e2051aec560f7b31a63", - "sha256:a239303acb0315091d54c7ff36712dba24554993b9a93941cf301391d8a997ee", - "sha256:a2baa6be130e8a00b6cbb9f18a33611ec150b4537f8563bddadb54c1b74b8193", - "sha256:a54917b7e9cd3a67e429a630e237a90b096e0ba18897bfb99ee8bd1068a5fea0", - "sha256:a689e1ded7137552bea36305a7a16ad2b40be511740b80748d3140614993db98", - "sha256:a952ae3eb460c6712388ac2ec706d24b0e651b9396d90c9a9e0a69eb27737fdc", - "sha256:aa32205358a76bf578854bf31698a86dc8b2cb591fd1d79a833283f4a403f04b", - "sha256:b2287c09482949e0ca0c0eb68b2aca6cf57f8af8c6dfd29dcd3bc45f17b57978", - "sha256:b6b0e17d39d21698185097652c611f9cf30f7c56ccec189789920e3e7f1cee56", - "sha256:b710bf7e7ae61957d5c4026b486be593ed3ec3dca3e5be15e0f6d8cf5d0a4990", - "sha256:b8e11715178f3608874508f08e990d3771e0b8c66c73eb4e183038d600a9b274", - "sha256:b92aafcfab3d41580d54aca35a8057341f1cfc7c9af9e8bdfc652f83a20ced31", - "sha256:bec29b801b4adbf388314c0d050e851d53762ab424af22657021ce4b6eb41543", - "sha256:c694bee70ece3b232df4678448fdda245fd3b1bb4ba481fb6cd20e13bb784c46", - "sha256:c6b52b7028b547866c2413f614ee306c2d4eafdd444b1ff656bf3295bf1484aa", - "sha256:cb41ad20064e18a900dd427d7cf41cfaec83bcd1184001f3d91a1f76b3fcea4e", - "sha256:cd316dbcc74c76266ba94eb021b0cc090b97cca122f50bd7a845f587ff4bf03f", - "sha256:ced40cdbb6dd47a032725a038896cceae9ce267d340f59508b23537f05455431", - "sha256:d1c562a9bb72244fa767d1c1ab55ca1d92dd5f7c4d77878fee5483a22ffac808", - "sha256:d389ff1e95b6e46ebedccf7fd1fadd10559add595ac6a7c2ea730268325f832c", - "sha256:d56b1cd606ba4cedd64bb43479d56580e147c6ef3f5d1c5e64203a1adab784a2", - "sha256:d72a4315514e5a0b9837a086cb433b004eea630afb0cc129de76d77654a9606f", - "sha256:d9e7f29c00577aff6b318681e730a519b235af292732a149337f6aaa4d1c5e31", - "sha256:dbc25baa6abb205766fb8606f8263b02c3503a55957fcb4576a6bb0a59d37d10", - "sha256:e57919c32ee295a2fca458bb73e4b20b05c115627f96f95a10f9f5acbd61172d", - "sha256:e5bbe011a2cea9060fef1bb3d668a2fd8432b8888e6d92e74c9c794d3c101595", - "sha256:e6aea5c0eb5b0faf52c7b5c4a47c8bb64437173be97227c819ffa31801fa4e34", - "sha256:e888be685fa42d8b8a3d3911d5604d14db87538aa7d0b29b1a7ea80d354c732d", - "sha256:eebaf8c76c39604d52852366249ab807fe6f7a3ffb0dd5484b9944917244cdbe", - "sha256:efbe0b5e0fd078ed7b005faa0170da4f72666360f66f0bb2d7f73526ecfd99f9", - "sha256:efddca2d02254a52078c35cadad34762adbae3ff01c6b0c7787b59d038b63e0d", - "sha256:f05450fa1cd7c525c0b9d1a7916e595d3041ac0afbed2ff6926e5afb6a781b7f", - "sha256:f12d69d568f5647ec503b64932874dade5a20255736c89936bf690951a5e79f5", - "sha256:f45321224144c25a62052035ce96cbcf264667bcb0d81823b1bbc22c4addd194", - "sha256:f62581d7e884dd01ee1707b7c21148f61f2febb7de092ae2f108743fcbef5985", - "sha256:f8832a4f83d4782a8f5a7b831c47e8ffe164e43c2c148c8160ed9a6d630bc02a", - "sha256:fa35ad36440aaf1ac8332b4a4a433d4acd28f1613f0d480995f5cfd3580e90b7" + "sha256:06d218e4464d31301e943b65b2c6919318ea6f69703a351961e1baaf60347276", + "sha256:12ecf89bd54734c3c2c79898ae2021dca42750c7bcfb67f8fb3315453738ac8f", + "sha256:15253fff410873ebf3cfba1cc686a37711efcd9b8cb30ea21bb14a973e393f60", + "sha256:188435794405c7f0573311747c85a96b63c954a5f2111b1df8018979eca0f2f0", + "sha256:1ceebd0ae4f3e9b2b6b553b51971921853ae4eebf3f54086be0565d59291e53d", + "sha256:244e173bb6d8f3b2f0c4d7370a1aa341f35da3e57ffd1798e5b2917b91731fd3", + "sha256:25b28b3d33ec0a78e944aaaed7e5e2a94ac811bcd68b557ca48a0c30f87497d2", + "sha256:25ea41635d22b2eb6326f58e608550e55d01df51b8a580ea7e75396bafbb28e9", + "sha256:29d311e44dd16d2434d5506d57ef4d7036544fc3c25c14b6992ef41f541b10fb", + "sha256:2a1472956c5bcc49fb0252b965239bffe801acc9394f8b7c1014ae9258e4572b", + "sha256:2a7bef6977043673750a88da064fd513f89505111014b4e00fbdd13329cd4e9a", + "sha256:2ac26f50736324beb0282c819668328d53fc38543fa61eeea2c32ea8ea6eab8d", + "sha256:2e72f750048b32d39e87fc85c225c50b2a6715034848dbb196bf3348aa761fa1", + "sha256:31e220a040b89a01505128c2f8a59ee74732f666439a03e65ccbf3824cdddae7", + "sha256:35f53c76a712e323c779ca39b9a81b13f219a8e3bc15f106ed1e1462d56fcfe9", + "sha256:38d4f822ee2f338febcc85aaa2547eb5ba31ba6ff68d10b8ec988929d23bb6b4", + "sha256:38f9bf2ad754b4a45b8210a6c732fe876b8a14e14d5992a8c4b7c1ef78740f53", + "sha256:3a44c8440183b43167fd1a0819e8356692bf5db1ad14ce140dbd40a1485f2dea", + "sha256:3ab96754d23372009638a402a1ed12a27711598dd49d8316a22597141962fe66", + "sha256:3c55d7f2d817183d43220738270efd3ce4e7a7b7cbdaefa6d551ed3d6ed89190", + "sha256:46e1ed994a0920f350a4547a38471217eb86f57377e9314fbaaa329b71b7dfe3", + "sha256:4a5375c5fff13f209527cd886dc75394f040c7d1ecad0a2cb0627f13ebe78a12", + "sha256:4c2d26aa03d877c9730bf005621c92da263523a1e99247590abbbe252ccb7824", + "sha256:4c4e314d36d4f31236a545696a480aa04ea170a0b021e9a59ab1ed94d4c3ef27", + "sha256:4d0c10d803549427f427085ed7aebc39832f6e818a011dcd8785e9c6a1ba9b3e", + "sha256:4dcc5ee1d0275cb78d443fdebd0241e58772a354a6d518b1d7af1580bbd2c4e8", + "sha256:51967a67ea0d7b9b5cd86036878e2d82c0b6183616961c26d825b8c994d4f2c8", + "sha256:530190eb0cd778363bbb7596612ded0bb9fef662daa98e9d92a0419ab27ae914", + "sha256:5379e49d7e80dca9811b36894493d1c1ecb4c57de05c36f5d0dd09982af20211", + "sha256:5493569f861fb7b05af6d048d00d773c6162415ae521b7010197c98810a14cab", + "sha256:5a4c1058cdae6237d97af272b326e5f78ee7ee3bbffa6b24b09db4d828810468", + "sha256:5d75d6d220d55cdced2f32cc22f599475dbe881229aeddba6c79c2e9df35a2b3", + "sha256:5d97e9ae94fb96df1ee3cb09ca376c34e8a122f36927230f4c8a97f469994bff", + "sha256:5feae2f9aa7270e2c071f488fab256d768e88e01b958f123a690f1cc3061a09c", + "sha256:603d5868f7419081d616dab7ac3cfa285296735e7350f7b1e4f548f6f953ee7d", + "sha256:61d42d2b08430854485135504f672c14d4fc644dd243a9c17e7c4e0faf5ed07e", + "sha256:61dbc1e01dc0c5875da2f7ae36d6e918dc1b8d2ce04e871793976594aad8a57a", + "sha256:65cfed9c807c27dee76407e8bb29e6f4e391e436774bcc769a037ff25ad8646e", + "sha256:67a429520e97621a763cf9b3ba27574779c4e96e49a27ff8a1aa99ee70beb28a", + "sha256:6aadae3042f8e6db3376d9e91f194c606c9a45273c170621d46128f35aef7cd0", + "sha256:6ba8858933f0c1a979781272a5f65646fca8c18c93c99c6ddb5513ad96fa54b1", + "sha256:6bc568b05e02cd612be53900c88aaa55012e744930ba2eeb56279db4c6676eb3", + "sha256:729408136ef8d45a28ee9a7411917c9e3459cf266c7e23c2f7d4bb8ef9e0da42", + "sha256:751758d9dd04d548ec679224cc00e3591f5ebf1ff159ed0d4aba6a0746352452", + "sha256:76d59d4d451ba77f08cb4cd9268dec07be5bc65f73666302dbb5061989b17198", + "sha256:79bf58c08f0756adba691d480b5a20e4ad23f33e1ae121584cf3a21717c36dfa", + "sha256:7de12b69d95072394998c622cfd7e8cea8f560db5fca6a62a148f902a1029f8b", + "sha256:7f55cd9cf1564b7b03f238e4c017ca4794c05b01a783e9291065cb2858d86ce4", + "sha256:80e5acb81cb49fd9f2d5c08f8b74ffff14ee73b10ca88297ab4619e946bcb1e1", + "sha256:87a90f5545fd61f6964e65eebde4dc3fa8660bb7d87adb01d4cf17e0a2b484ad", + "sha256:881df98f0a8404d32b6de0fd33e91c1b90ed1516a80d4d6dc69d414b8850474c", + "sha256:8a776a29b77fe0cc28fedfd87277b0d0f7aa930174b7e504d764e0b43a05f381", + "sha256:8c2a61c0e4811012b0ba9f6cdcb4437865df5d29eab5d6018ba13cee1c3064a0", + "sha256:8fa6bd071ec6d90f6e7baa66ae25820d57a8ab1b0a3c6d3edf1834d4b26fafa2", + "sha256:96f2975fb14f39c5fe75203f33dd3010fe37d1c4e33177feef1107b5ced750e3", + "sha256:96fb0899bb2ab353f42e5374c8f0789f54e0a94ef2f02b9ac7149c56622eaf31", + "sha256:97163a1ab265a1073a6372eca9f4eeb9f8c6327457a0b22ddfc4a17dcd613e74", + "sha256:9c95a1a290f9acf7a8f2ebbdd183e99215d491beea52d61aa2a7a7d2c618ddc6", + "sha256:9d94d78418203904730585efa71002286ac4c8ac0689d0eb61e3c465f9e608ff", + "sha256:a6ba2cb7d676e9415b9e9ac7e2aae401dc1b1e666943d1f7bc66223d3d73467b", + "sha256:aa0379c1935c44053c98826bc99ac95f3a5355675a297ac9ce0dfad0ce2d50ca", + "sha256:ac96d67b37f28e4b6ecf507c3405f52a40658c0a806dffde624a8fcb0314d5fd", + "sha256:ade2ccb937060c299ab0dfb2dea3d2ddf7e098ed63ee3d651ebfc2c8d1e8632a", + "sha256:aefbdc934115d2f9278f153952003ac52cd2650e7313750390b334518c589568", + "sha256:b07501b720cf060c5856f7b5626e75b8e353b5f98b9b354a21eb4bfa47e421b1", + "sha256:b5267feb19070bef34b8dea27e2b504ebd9d31748e3ecacb3a4101da6fcb255c", + "sha256:b5f6328e8e2ae8238fc767703ab7b95785521c42bb2b8790984e3477d7fa71ad", + "sha256:b8996ffb60c69f677245f5abdbcc623e9442bcc91ed81b6cd6187129ad1fa3e7", + "sha256:b981a370f8f41c4024c170b42fbe9e691ae2dbc19d1d99151a69e2c84a0d194d", + "sha256:b9d121be0217787a7d59a5c6195b0842d3f701007333426e5154bf72346aa658", + "sha256:bcef4f2d3dc603150421de85c916da19471f24d838c3c62a4f04c1eb511642c1", + "sha256:bed0252c85e21cf73d2d033643c945b460d6a02fc4a7d644e3b2d6f5f2956c64", + "sha256:bfdfbe6a36bc3059fff845d64c42f2644cf875c65f5005db54f90cdfdf1df815", + "sha256:c0095b8aa3e432e32d372e9a7737e65b58d5ed23b9620fea7cb81f17672f1fa1", + "sha256:c1f41d32a2ddc5a94df4b829b395916a4b7f103350fa76ba6de625fcb9e773ac", + "sha256:c45008ca79bad237cbc03c72bc5205e8c6f66403773929b1b50f7d84ef9e4d07", + "sha256:c82bbf7e03748417c3a88c1b0b291288ce3e4887a795a3addaa7a1cfd9e7153e", + "sha256:c918621ee0a3d1fe61c313f2489464f2ae3d13633e60f520a8002a5e910982ee", + "sha256:d204957169f0b3511fb95395a9da7d4490fb361763a9f8b32b345a7fe119cb45", + "sha256:d329896c40d9e1e5c7715c98529e4a188a1f2df51212fd65102b32465612b5dc", + "sha256:d3a61e928feddc458a55110f42f626a2a20bea942ccedb6fb4cee70b4830ed41", + "sha256:d48db29bd47814671afdd76c7652aefacc25cf96aad6daefa82d738ee87461e2", + "sha256:d5593855b5b2b73dd8413c3fdfa5d95b99d657658f947ba2c4318591e745d083", + "sha256:d79c159adea0f1f4617f54aa156568ac69968f9ef4d1e5fefffc0a180830308e", + "sha256:db09b98c7540df69d4b47218da3fbd7cb466db0fb932e971c321f1c76f155266", + "sha256:ddf23960cb42b69bce13045d5bc66f18c7d53774c66c13f24cf1b9c144ba3141", + "sha256:e06cfea0ece444571d24c18ed465bc93afb8c8d8d74422eb7026662f3d3f779b", + "sha256:e7c564c58cf8f248fe859a4f0fe501b050663f3d7fbc342172f259124fb59933", + "sha256:e86593bf8637659e6a6ed58854b6c87ec4e9e45ee8a4adfd936831cef55c2d21", + "sha256:eaffbd8814bb1b5dc3ea156a4c5928081ba50419f9175f4fc95269e040eff8f0", + "sha256:ee353bb51f648924926ed05e0122b6a0b1ae709396a80eb583449d5d477fcdf7", + "sha256:ee6faebb265e28920a6f23a7d4c362414b3f4bb30607141d718b991669e49ddc", + "sha256:efe093acc43e869348f6f2224df7f452eab63a2c60a6c6cd6b50fd35c4e075ba", + "sha256:f03a1b3a4c03e3e0161642ac5367f08479ab29972ea0ffcd4fa18f729cd2be0a", + "sha256:f0d320e70b6b2300ff6029e234e79fe44e9dbbfc7b98597ba28e054bd6606a57", + "sha256:f252dfb4852a527987a9156cbcae3022a30f86c9d26f4f17b8c967d7580d65d2", + "sha256:f5f4424cb87a20b016bfdc157ff48757b89d2cc426256961643d443c6c277007", + "sha256:f8eae66a1304de7368932b42d801c67969fd090ddb1a7a24f27b435ed4bed68f", + "sha256:fdb82eb60d31b0c033a8e8ee9f3fc7dfbaa042211131c29da29aea8531b4f18f" ], "markers": "python_version >= '3.8'", - "version": "==0.12.0" + "version": "==0.13.2" }, "six": { "hashes": [ @@ -549,11 +559,11 @@ }, "textx": { "hashes": [ - "sha256:1f2bd936309f5f9092567e3f5a0c513d8292d5852c63a72096fe5a57018d0f50", - "sha256:e2fb7d090ea4a71f8bf2f0303770275c42a5f73854b4e7f6adfcdbe368fab0fa" + "sha256:023bdb338ec4d6f8ad4c8b58e8bc16975920a5bd379428d8b027c1a7d02a42b4", + "sha256:84aff5c95fd2c947402fcbe83eeeddc23aabcfed3464ab84184ef193c52d831a" ], "index": "pypi", - "version": "==3.1.1" + "version": "==4.0.1" }, "toml": { "hashes": [ @@ -573,11 +583,11 @@ }, "urllib3": { "hashes": [ - "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84", - "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e" + "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", + "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" ], - "markers": "python_version >= '3.7'", - "version": "==2.0.7" + "markers": "python_version >= '3.8'", + "version": "==2.1.0" } }, "develop": { @@ -764,6 +774,14 @@ "index": "pypi", "version": "==4.0.0" }, + "python-magic": { + "hashes": [ + "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", + "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3" + ], + "index": "pypi", + "version": "==0.4.27" + }, "waitress": { "hashes": [ "sha256:c549f5b2b4afd44d9d97d7cec79f3ef581e25d832827f415dc175327af674aa8", diff --git a/dothttp/__init__.py b/dothttp/__init__.py index 7c15e52..4bfcfad 100644 --- a/dothttp/__init__.py +++ b/dothttp/__init__.py @@ -42,11 +42,17 @@ from .dsl_jsonparser import json_or_array_to_json from .exceptions import * -from .parse_models import MultidefHttp, AuthWrap, DigestAuth, BasicAuth, Line, NtlmAuthWrap, Query, Http, NameWrap, UrlWrap, Header, \ +from .parse_models import AzureAuthCli, AzureAuthType, AzureAuthWrap, MultidefHttp, AuthWrap, DigestAuth, BasicAuth, Line, NtlmAuthWrap, Query, Http, NameWrap, UrlWrap, Header, \ MultiPartFile, FilesWrap, TripleOrDouble, Payload as ParsePayload, Certificate, P12Certificate, ExtraArg, \ - AWS_REGION_LIST, AWS_SERVICES_LIST, AwsAuthWrap, TestScript, ScriptType, HawkAuth + AWS_REGION_LIST, AWS_SERVICES_LIST, AwsAuthWrap, TestScript, ScriptType, HawkAuth, AzureAuthCertificate, \ + AzureAuthDeviceCode, AzureAuthServicePrincipal from .property_schema import property_schema from .property_util import PropertyProvider +try: + from .azure_auth import AzureAuth +except: + # this is for dothttp-wasm, where msal most likely not installed + AzureAuth = None try: import magic @@ -320,6 +326,9 @@ def get_http_from_req(self): aws_auth.signing_key.secret_key, aws_auth.service, aws_auth.region)) + elif isinstance(self.auth, AzureAuth): + auth_wrap = AuthWrap( + azure_auth=self.auth.azure_auth_wrap) certificate = None if self.certificate: certificate = Certificate(*self.certificate) @@ -1051,12 +1060,44 @@ def load_auth(self): aws_service ) else: - # region and aws_service can be extracted from url + # aws service and region can be extracted from url # somehow library is not supporting those # with current state, we are not support this use case # we may come back # all four parameters are required and are to be non empty raise DothttpAwsAuthException(access_id=access_id) + elif azure_auth := auth_wrap.azure_auth: + azure_auth: AzureAuthWrap = azure_auth + if sp_auth := azure_auth.azure_spsecret_auth: + azure_auth_wrap = AzureAuthWrap(azure_spsecret_auth=AzureAuthServicePrincipal( + tenant_id=self.get_updated_content(sp_auth.tenant_id), + client_id=self.get_updated_content(sp_auth.client_id), + client_secret=self.get_updated_content(sp_auth.client_secret), + scope = self.get_updated_content(sp_auth.scope or "https://management.azure.com/.default") + ), azure_auth_type=AzureAuthType.SERVICE_PRINCIPAL) + elif cert_auth := azure_auth.azure_spcert_auth: + azure_auth_wrap = AzureAuthWrap(azure_spcert_auth=AzureAuthCertificate( + tenant_id=self.get_updated_content(cert_auth.tenant_id), + client_id=self.get_updated_content(cert_auth.client_id), + certificate_path=self.get_updated_content(cert_auth.certificate_path), + scope=self.get_updated_content(cert_auth.scope or "https://management.azure.com/.default") + ), azure_auth_type=AzureAuthType.CERTIFICATE) + elif azure_auth.azure_cli_auth: + azure_auth_wrap = AzureAuthWrap( + azure_cli_auth=AzureAuthCli( + scope=self.get_updated_content( + azure_auth.azure_cli_auth.scope or "https://management.azure.com/.default" + ) + ), azure_auth_type=AzureAuthType.CLI) + elif azure_auth.auth.azure_device_code: + azure_auth_wrap = AzureAuthWrap( + azure_device_code=AzureAuthDeviceCode( + scope=self.get_updated_content( + azure_auth.azure_device_code.scope or "https://management.azure.com/.default" + ) + ), azure_auth_type=AzureAuthType.DEVICE_CODE) + + self.httpdef.auth = AzureAuth(azure_auth_wrap) def get_current_or_base(self, attr_key) -> Any: if getattr(self.http, attr_key): diff --git a/dothttp/__version__.py b/dothttp/__version__.py index d4f4ae2..adb9508 100644 --- a/dothttp/__version__.py +++ b/dothttp/__version__.py @@ -1 +1 @@ -__version__ = '0.0.43a1' +__version__ = '0.0.43a2' diff --git a/dothttp/azure_auth.py b/dothttp/azure_auth.py new file mode 100644 index 0000000..203e022 --- /dev/null +++ b/dothttp/azure_auth.py @@ -0,0 +1,158 @@ +import json +import msal +import os +import pickle +import subprocess +import time +import logging + +from requests.auth import AuthBase +from requests.models import PreparedRequest + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.serialization import pkcs12 + +from .exceptions import DothttpAzureAuthException +from .parse_models import AzureAuthWrap, AzureAuthType, AzureAuthSP, AzureAuthCertificate + +AZURE_CLI_TOKEN_STORE_PATH = os.path.expanduser('~/.dothttp.azure-cli.pkl') + +AZURE_SP_TOKEN_STORE_PATH = os.path.expanduser( + '~/.dothttp.msal_token_cache.pkl') + +request_logger = logging.getLogger("request") + + +def load_private_key_and_thumbprint(cert_path, password=None): + extension = os.path.splitext(cert_path)[1].lower() + with open(cert_path, "rb") as cert_file: + cert_data = cert_file.read() + + if extension == '.pem': + private_key = serialization.load_pem_private_key( + cert_data, password, default_backend()) + cert = x509.load_pem_x509_certificate(cert_data, default_backend()) + elif extension == '.cer': + cert = x509.load_der_x509_certificate(cert_data, default_backend()) + private_key = None # .cer files do not contain private key + elif extension == '.pfx' or extension == '.p12': + private_key, cert, _ = pkcs12.load_key_and_certificates( + cert_data, password, default_backend()) + else: + raise ValueError(f"Unsupported certificate format {extension}") + + if private_key is not None: + private_key_bytes = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + ) + else: + private_key_bytes = None + + thumbprint = cert.fingerprint(hashes.SHA1()).hex() + + return private_key_bytes, thumbprint + +class AzureAuth(AuthBase): + + def __init__(self, azure_auth_wrap: AzureAuthWrap): + self.azure_auth_wrap = azure_auth_wrap + self.token_cache = msal.SerializableTokenCache() + try: + # Try to load the token cache from a file + with open(AZURE_SP_TOKEN_STORE_PATH, 'rb') as token_cache_file: + self.token_cache.deserialize( + json.dumps(pickle.load(token_cache_file))) + except FileNotFoundError: + # If the file does not exist, initialize a new token cache + pass + + def __call__(self, r: PreparedRequest) -> PreparedRequest: + if self.azure_auth_wrap.azure_auth_type == AzureAuthType.SERVICE_PRINCIPAL: + self.acquire_token_silently_or_ondemand( + r, self.azure_auth_wrap.azure_spsecret_auth) + self.save_token_cache() + elif self.azure_auth_wrap.azure_auth_type == AzureAuthType.CERTIFICATE: + self.acquire_token_silently_or_ondemand( + r, self.azure_auth_wrap.azure_spcert_auth) + self.save_token_cache() + # For device code and cli authentication, we use the access token directly + # in future we can use msal to get the access token for device code + elif self.azure_auth_wrap.azure_auth_type in [AzureAuthType.CLI, AzureAuthType.DEVICE_CODE]: + access_token = None + expires_on = None + # Try to load the access token and its expiry time from a file + scope = self.azure_auth_wrap.azure_cli_auth.scope if self.azure_auth_wrap.azure_cli_auth else self.azure_auth_wrap.azure_device_code.scope + + if os.path.exists(AZURE_CLI_TOKEN_STORE_PATH): + request_logger.debug("azure cli token already exists, using") + with open(AZURE_CLI_TOKEN_STORE_PATH, 'rb') as token_file: + data = pickle.load(token_file) + scope_wise_store = data.get(scope, {}) + access_token = scope_wise_store.get('access_token', None) + expires_on = scope_wise_store.get('expires_on', None) + # Get the current time in seconds since the Epoch + current_time = time.time() + + + # If the file does not exist or the token has expired, get a new access token + if not access_token or not expires_on or current_time >= expires_on: + request_logger.debug( + "azure cli token store cached not availabile or expired") + # get token from cli by invoking az account get-access-token + result = subprocess.run( + ["az", "account", "get-access-token", "--scope", scope], capture_output=True, text=True) + result_json = json.loads(result.stdout) + access_token = result_json['accessToken'] + # Convert the expiresOn field to seconds since the Epoch + expires_on = time.mktime(time.strptime(result_json['expiresOn'], '%Y-%m-%d %H:%M:%S.%f')) + # Save the new access token and its expiry time to the file + with open(AZURE_CLI_TOKEN_STORE_PATH, 'wb') as token_file: + scope_wise_store = dict() + scope_wise_store[scope] = { + 'access_token': access_token, 'expires_on': expires_on} + pickle.dump(scope_wise_store, token_file) + request_logger.debug( + "computed or fetched azure cli token access bearer token and appeneded") + r.headers["Authorization"] = f"Bearer {access_token}" + return r + + def acquire_token_silently_or_ondemand(self, r, auth_wrap: AzureAuthSP): + kwargs = { + "client_id": auth_wrap.client_id, + "authority": f"https://login.microsoftonline.com/{auth_wrap.tenant_id}", + "token_cache": self.token_cache + } + if isinstance(auth_wrap, AzureAuthCertificate): + try: + private_key_bytes, thumbprint = load_private_key_and_thumbprint( + auth_wrap.certificate_path, auth_wrap.certificate_password) + kwargs["client_credential"] = { + "private_key": private_key_bytes, + "thumbprint": thumbprint + } + except Exception as e: + request_logger.error( + "loading private key failed with error", e) + raise DothttpAzureAuthException(message=str(e)) + else: + kwargs["client_credential"] = auth_wrap.client_secret + app = self.create_confidential_app(kwargs) + accounts = app.get_accounts() + if accounts: + result = app.acquire_token_silent(scopes=[auth_wrap.scope], account=accounts[0]) + if not accounts or "access_token" not in result: + result = app.acquire_token_for_client(scopes=[auth_wrap.scope]) + r.headers["Authorization"] = f"Bearer {result['access_token']}" + + def create_confidential_app(self, kwargs): + return msal.ConfidentialClientApplication(**kwargs) + + def save_token_cache(self): + with open(AZURE_SP_TOKEN_STORE_PATH, 'wb') as token_cache_file: + pickle.dump(json.loads(self.token_cache.serialize()), + token_cache_file) diff --git a/dothttp/exceptions.py b/dothttp/exceptions.py index a071802..52dcf25 100644 --- a/dothttp/exceptions.py +++ b/dothttp/exceptions.py @@ -100,3 +100,9 @@ class ScriptException(DotHttpException): "AWSAuth expects all(access_id, secret_token, region, service) to be non empty access_id:`{access_id}`") class DothttpAwsAuthException(DotHttpException): pass + + +@exception_wrapper( + "AzureAuth exception: {message}") +class DothttpAzureAuthException(DotHttpException): + pass diff --git a/dothttp/http.tx b/dothttp/http.tx index 3beea6e..0f49316 100644 --- a/dothttp/http.tx +++ b/dothttp/http.tx @@ -45,7 +45,12 @@ HEADER: ; AUTHWRAP: - digest_auth = DIGESTAUTH | basic_auth = BASICAUTH | ntlm_auth = NTLMAUTH | hawk_auth=HAWKAUTH | aws_auth = AWSAUTH + digest_auth = DIGESTAUTH + | basic_auth = BASICAUTH + | ntlm_auth = NTLMAUTH + | hawk_auth=HAWKAUTH + | aws_auth = AWSAUTH + | azure_auth = AZUREAUTH ; CERTAUTH: @@ -80,6 +85,37 @@ AWSAUTH: | 'awsauth' '(' ('access_id' '=' ) ? access_id=DotString ',' ('secret_key' '=')? secret_token=DotString ',' ('service' '=')? service=DotString (',' ('region' '=')? region=DotString)? ')' ; +AZUREAUTH: + azure_spcert_auth = AZURECERTIFICATEAUTH + // for service principal + | azure_spsecret_auth = AZURESERVICEPRINCIPALAUTH + // if you don't have both, use device_code flow + // in this case, we use azure cli to get auth_token + | azure_device_auth = AZUREDEVICEAUTH + | azure_cli_auth = AZURECLIAUTH +; + +AZURESERVICEPRINCIPALAUTH: + + 'azurespsecret' '(' ('tenant_id' '=')? tenant_id=DotString ',' ('client_id' '=')? client_id=DotString ',' ('client_secret' '=')? client_secret=DotString (',' ('scope' '=')? scope=DotString )? ')' + +; + +AZURECERTIFICATEAUTH: + // for certificate based + 'azurespcert' '(' ('tenant_id' '=')? tenant_id=DotString ',' ('client_id' '=')? client_id=DotString ',' ('certificate_path' '=')? certificate_path=DotString (',' ('scope' '=')? scope=DotString )?')' + +; + +AZUREDEVICEAUTH: + 'azuredevice' '(' (('scope' '=')? scope=DotString )? ')' +; + +AZURECLIAUTH: + 'azurecli' '(' (('scope' '=')? scope=DotString )? ')' +; + + EXTRA_ARG: // there can be more clear=CLEAR_SESSION | insecure=INSECURE diff --git a/dothttp/parse_models.py b/dothttp/parse_models.py index b72ef56..c0aa6ac 100644 --- a/dothttp/parse_models.py +++ b/dothttp/parse_models.py @@ -5,7 +5,6 @@ from dothttp import DotHttpException - @dataclass class NameWrap: name: str @@ -51,6 +50,62 @@ class NtlmAuthWrap: password: str +@dataclass +class AzureAuthCertificate: + tenant_id: str = None + client_id: str = None + certificate_path: str = None + certificate_password: Optional[str] = None + scope : Optional[str] = None + +@dataclass +class AzureAuthServicePrincipal: + tenant_id: Optional[str] = None + client_id: Optional[str] = None + client_secret: Optional[str] = None + scope : Optional[str] = None + + +@dataclass +class AzureAuthDeviceCode: + scope : Optional[str] = None + + +@dataclass +class AzureAuthCli: + scope : Optional[str] = None + + +class AzureAuthType(enum.Enum): + SERVICE_PRINCIPAL = "service_principal" + CERTIFICATE = "certificate" + DEVICE_CODE = "device_code" + CLI = "cli" + + @staticmethod + def get_azure_auth_type(auth_type: str): + if auth_type == "service_principal": + return AzureAuthType.SERVICE_PRINCIPAL + elif auth_type == "certificate": + return AzureAuthType.CERTIFICATE + elif auth_type == "device_code": + return AzureAuthType.DEVICE_CODE + elif auth_type == "cli": + return AzureAuthType.CLI + else: + raise DotHttpException("unknown auth type") + +@dataclass +class AzureAuthWrap: + azure_auth_type: AzureAuthType + azure_spsecret_auth: Optional[AzureAuthServicePrincipal] = None + azure_spcert_auth: Optional[AzureAuthCertificate] = None + azure_device_code: Optional[AzureAuthDeviceCode] = None + azure_cli_auth: Optional[AzureAuthCli] = None + + +AzureAuthSP = Union[AzureAuthCertificate, AzureAuthServicePrincipal] + @dataclass class AuthWrap: digest_auth: Optional[DigestAuth] = None @@ -58,6 +113,7 @@ class AuthWrap: aws_auth: Optional[AwsAuthWrap] = None ntlm_auth: Optional[NtlmAuthWrap] = None hawk_auth: Optional[HawkAuth] = None + azure_auth: Optional[AzureAuthWrap] = None @dataclass diff --git a/dothttp/request_base.py b/dothttp/request_base.py index 34bdea0..40eb092 100644 --- a/dothttp/request_base.py +++ b/dothttp/request_base.py @@ -299,6 +299,13 @@ def format_http(http: Http): output_str += f'{new_line}awsauth(access_id="{aws_auth.access_id}", secret_key="{aws_auth.secret_token}", service="{aws_auth.service}")' else: output_str += f'{new_line}awsauth(access_id="{aws_auth.access_id}", secret_key="{aws_auth.secret_token}" )' + elif azure_auth := auth_wrap.azure_auth: + if sp_auth := azure_auth.azure_spsecret_auth: + output_str += f'{new_line}azurespsecret(tenant_id="{sp_auth.tenant_id}", client_id="{sp_auth.client_id}", client_secret="{sp_auth.client_secret}", scope="{sp_auth.scope}")' + elif cert_auth := azure_auth.azure_spcert_auth: + output_str += f'{new_line}azurespcert("tenant_id={cert_auth.tenant_id}", client_id="{cert_auth.client_id}", client_secret="{cert_auth.certificate_path}", scope="{cert_auth.scope}")' + else: + output_str += f'{new_line}azurecli( scope = "{azure_auth.scope}")' if lines := http.lines: def check_for_quotes(line): quote_type, value = quote_or_unquote(line.header.value) diff --git a/examples/auth/azure_auth.http b/examples/auth/azure_auth.http new file mode 100644 index 0000000..f541dc6 --- /dev/null +++ b/examples/auth/azure_auth.http @@ -0,0 +1,19 @@ + +# python -m dothttp examples/example.http --property subscriptionId= resourceGroupName= --debug +@name("azureauth") +GET https://management.azure.com/ +// azurecli() +azurespsecret("{{tenantId}}", "{{clientId}}", "{{secretValue}}") + +@name('subscriptions'): "azureauth" +GET /subscriptions/{{subscriptionId}}/ + +@name('resourcegroups'): "subscriptions" +GET /resourceGroups/{{resourceGroupName}}/ + +@name('containerservice'): "resourcegroups" +GET /providers/Microsoft.ContainerService/ + +@name('managedClusters'): "containerservice" +GET /managedClusters +?"api-version"="2021-07-01" diff --git a/requirements.txt b/requirements.txt index 392ef04..ab5b93a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,15 @@ -jsonschema==4.19.2 +jsonschema==4.20.0 jstyleson==0.0.2 -textx==4.0.0 +textx==4.0.1 js2py==0.74 requests_pkcs12==1.22 parsys-requests-unixsocket==0.3.1 requests-aws4auth==1.2.3 requests_ntlm==1.2.0 -RestrictedPython==6.2 -Faker==20.0.0 +RestrictedPython==7.0 +Faker==20.1.0 requests-hawk==1.2.1 +msal==1.26.0 PyYaml==6.0.1 toml==0.10.2 requests==2.31.0 \ No newline at end of file diff --git a/test/core/requests/azureauth.http b/test/core/requests/azureauth.http new file mode 100644 index 0000000..eb80b2f --- /dev/null +++ b/test/core/requests/azureauth.http @@ -0,0 +1,12 @@ +@name("azure_sp") +GET "https://management.azure.com/" +azurespsecret(tenant_id="{{tenant_id}}", client_id="{{client_id}}", client_secret="{{secret_value}}", scope="{{scope}}") + + +@name("azure_cert") +GET "https://management.azure.com/" +azurespcert(tenant_id="{{tenant_id}}", client_id="{{client_id}}", certificate_path="{{certificate_path}}", scope="{{scope}}") + +@name("azure_cli") +GET "https://management.azure.com/" +azurecli(scope="{{scope}}") diff --git a/test/core/root_cert/certs/badssl.com-client.p12 b/test/core/root_cert/certs/badssl.com-client.p12 index 406a4c86c4beb073f5089d00c93ecd621c15d9d3..be99b920217fc7478bdfdc8d9b8b990fa1026dd1 100644 GIT binary patch delta 2658 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 Q2$`<$Ap!yj00v15(*OVf diff --git a/test/core/root_cert/certs/cert.crt b/test/core/root_cert/certs/cert.crt index 64e2c97..20b7419 100644 --- a/test/core/root_cert/certs/cert.crt +++ b/test/core/root_cert/certs/cert.crt @@ -1,15 +1,15 @@ Bag Attributes - localKeyID: 77 23 D2 3A C0 2C 87 E2 AD 98 3F 06 68 F2 54 33 B6 05 0E FE + localKeyID: 57 CC 99 3F 7C 3E D2 81 83 06 43 E2 9A 5E 12 06 9F 13 82 D0 subject=C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Client Certificate issuer=C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Client Root Certificate Authority -----BEGIN CERTIFICATE----- -MIIEnTCCAoWgAwIBAgIJAPYAapdmy98xMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +MIIEnTCCAoWgAwIBAgIJAMn8nqcEWekMMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp c2NvMQ8wDQYDVQQKDAZCYWRTU0wxMTAvBgNVBAMMKEJhZFNTTCBDbGllbnQgUm9v -dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjExMjA0MDAwODE5WhcNMjMxMjA0 -MDAwODE5WjBvMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG +dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjMxMTI5MjIzNDAzWhcNMjUxMTI4 +MjIzNDAzWjBvMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG A1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0GA1UECgwGQmFkU1NMMSIwIAYDVQQDDBlC YWRTU0wgQ2xpZW50IENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAxzdfEeseTs/rukjly6MSLHM+Rh0enA3Ai4Mj2sdl31x3SbPoen08 @@ -19,20 +19,20 @@ DeZTf2ES66abA5pOp60Q6OEdwg/vCUJfarhKDpi9tj3P6qToy9Y4DiBUhOct4MG8 w5XwmKAC+Vfm8tb7tMiUoU0yvKKOcL6YXBXxB2kPcOYxYNobXavfVBEdwSrjQ7i/ s3o6hkGQlm9F7JPEuVgbl/Jdwa64OYIqjQIDAQABoy0wKzAJBgNVHRMEAjAAMBEG CWCGSAGG+EIBAQQEAwIHgDALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIB -ABlLNovFvSrULgLvJmKX/boSWQOhWE0HDX6bVKyTs48gf7y3DXSOD+bHkBNHL0he -m4HRFSarj+x389oiPEti5i12Ng9OLLHwSHK+7AfnrkhLHA8ML3NWw0GBr5DgdsIv -7MJdGIrXPQwTN5j++ICyY588TfGHH8vU5qb5PrSqClLZSSHU05FTr/Dc1B8hKjjl -d/FKOidLo1YDLFUjaB9x1mZPUic/C489lyPfWqPqoMRd5i/XShST5FPvfGuKRd5q -XKDkrn+GaQ/4iDDdCgekDCCPhOwuulavNxBDjShwZt1TeUrZNSM3U4GeZfyrVBIu -Tr+gBK4IkD9d/vP7sa2NQszF0wRQt3m1wvSWxPz91eH+MQU1dNPzg1hnQgKKIrUC -NTab/CAmSQfKC1thR15sPg5bE0kwJd1AJ1AqTrYxI0VITUV8Gka3tSAp3aKZ2LBg -gYHLI2Rv9jXe5Yx5Dckf3l+YSFp/3dSDkFOgEuZm2FfZl4vNBR+coohpB9+2jRWL -K+4fIkCJba+Y2cEd5usJE18MTH9FU/JKDwzC+eO9SNLFUw3zGUsSwgZsBHP6kiQN -suia9q4M5f+68kzM4+0NU8HwwyzZEtmTBhktKHijExixdvjlMAZ8hAOsFifsevI0 -02dUYvtxoHaeXh4jpYHVNnsIf/74uLagiPHtVf7+9UZV +ACh1uH1cx+ThMJJscvhUAKFCseowrjh6msHGyNApZH6cN3F9XCXroHIE7l0/Qnzc +2AwIYLVeuo7mxNCxCN7aj16nC+rkmcP7j5Si9LQi6f0mi+XXuL+6Wx4wve3D6fua +vxuAvLxVxOH02zNzBvfTxhGasjnxwoOA4bKQr2/2LG+AKNpWNWBI7ufTJiWzJH3T +u+MRWsHRfNjbgSnOCqnL2r147Ry/L4H15qQwtRErUCZ6oaJbM1zvawvRFZBJ3RYF +WynmTQlq84OzWqy091tbR6WEsA5MY12tjRCSwTJd665ZxiGV4Hu5pZgX5iNlheII +TPoZw1fLc4KvQxYUAmDzo3XDUJA18+t2neb1UGB1kQNQVckLwwlpBln7CFoJSisM +gezc4baqRNIFQG19M9uBRpIyq3cucXzbnniLaVSMveY98Wp9VXvC4lNvRKuhxceD +WKnZSHkaBd7wH8IuH2/EvVn3OYkYQutOFs9tMHfchVTLqI5XLVvYD6zsxIvs2w0K +GsxMqeXoS8m+R/GLmppp2NTQRj4ZhuM+bKq2ywISkTEhEQzkL8IjAEv6Pfd+LLWh +wJhbVEwWduH9cR3KNlS4OGtg0EZaln3giGu2LUIOkiBsdM5yZlhQadMdsDZriIkW +FjcUKwwbLq/omV8WIlu0HA/klNxwnjihNcMbSKzECtEJ -----END CERTIFICATE----- Bag Attributes - localKeyID: 77 23 D2 3A C0 2C 87 E2 AD 98 3F 06 68 F2 54 33 B6 05 0E FE + localKeyID: 57 CC 99 3F 7C 3E D2 81 83 06 43 E2 9A 5E 12 06 9F 13 82 D0 Key Attributes: -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHN18R6x5Oz+u6 diff --git a/test/core/root_cert/certs/key.key b/test/core/root_cert/certs/key.key index eca8ba7..f3e3ec9 100644 --- a/test/core/root_cert/certs/key.key +++ b/test/core/root_cert/certs/key.key @@ -1,5 +1,5 @@ Bag Attributes - localKeyID: 77 23 D2 3A C0 2C 87 E2 AD 98 3F 06 68 F2 54 33 B6 05 0E FE + localKeyID: 57 CC 99 3F 7C 3E D2 81 83 06 43 E2 9A 5E 12 06 9F 13 82 D0 Key Attributes: -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHN18R6x5Oz+u6 diff --git a/test/core/root_cert/certs/no-password.pem b/test/core/root_cert/certs/no-password.pem index 64e2c97..20b7419 100644 --- a/test/core/root_cert/certs/no-password.pem +++ b/test/core/root_cert/certs/no-password.pem @@ -1,15 +1,15 @@ Bag Attributes - localKeyID: 77 23 D2 3A C0 2C 87 E2 AD 98 3F 06 68 F2 54 33 B6 05 0E FE + localKeyID: 57 CC 99 3F 7C 3E D2 81 83 06 43 E2 9A 5E 12 06 9F 13 82 D0 subject=C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Client Certificate issuer=C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Client Root Certificate Authority -----BEGIN CERTIFICATE----- -MIIEnTCCAoWgAwIBAgIJAPYAapdmy98xMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +MIIEnTCCAoWgAwIBAgIJAMn8nqcEWekMMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp c2NvMQ8wDQYDVQQKDAZCYWRTU0wxMTAvBgNVBAMMKEJhZFNTTCBDbGllbnQgUm9v -dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjExMjA0MDAwODE5WhcNMjMxMjA0 -MDAwODE5WjBvMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG +dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjMxMTI5MjIzNDAzWhcNMjUxMTI4 +MjIzNDAzWjBvMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG A1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0GA1UECgwGQmFkU1NMMSIwIAYDVQQDDBlC YWRTU0wgQ2xpZW50IENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAxzdfEeseTs/rukjly6MSLHM+Rh0enA3Ai4Mj2sdl31x3SbPoen08 @@ -19,20 +19,20 @@ DeZTf2ES66abA5pOp60Q6OEdwg/vCUJfarhKDpi9tj3P6qToy9Y4DiBUhOct4MG8 w5XwmKAC+Vfm8tb7tMiUoU0yvKKOcL6YXBXxB2kPcOYxYNobXavfVBEdwSrjQ7i/ s3o6hkGQlm9F7JPEuVgbl/Jdwa64OYIqjQIDAQABoy0wKzAJBgNVHRMEAjAAMBEG CWCGSAGG+EIBAQQEAwIHgDALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIB -ABlLNovFvSrULgLvJmKX/boSWQOhWE0HDX6bVKyTs48gf7y3DXSOD+bHkBNHL0he -m4HRFSarj+x389oiPEti5i12Ng9OLLHwSHK+7AfnrkhLHA8ML3NWw0GBr5DgdsIv -7MJdGIrXPQwTN5j++ICyY588TfGHH8vU5qb5PrSqClLZSSHU05FTr/Dc1B8hKjjl -d/FKOidLo1YDLFUjaB9x1mZPUic/C489lyPfWqPqoMRd5i/XShST5FPvfGuKRd5q -XKDkrn+GaQ/4iDDdCgekDCCPhOwuulavNxBDjShwZt1TeUrZNSM3U4GeZfyrVBIu -Tr+gBK4IkD9d/vP7sa2NQszF0wRQt3m1wvSWxPz91eH+MQU1dNPzg1hnQgKKIrUC -NTab/CAmSQfKC1thR15sPg5bE0kwJd1AJ1AqTrYxI0VITUV8Gka3tSAp3aKZ2LBg -gYHLI2Rv9jXe5Yx5Dckf3l+YSFp/3dSDkFOgEuZm2FfZl4vNBR+coohpB9+2jRWL -K+4fIkCJba+Y2cEd5usJE18MTH9FU/JKDwzC+eO9SNLFUw3zGUsSwgZsBHP6kiQN -suia9q4M5f+68kzM4+0NU8HwwyzZEtmTBhktKHijExixdvjlMAZ8hAOsFifsevI0 -02dUYvtxoHaeXh4jpYHVNnsIf/74uLagiPHtVf7+9UZV +ACh1uH1cx+ThMJJscvhUAKFCseowrjh6msHGyNApZH6cN3F9XCXroHIE7l0/Qnzc +2AwIYLVeuo7mxNCxCN7aj16nC+rkmcP7j5Si9LQi6f0mi+XXuL+6Wx4wve3D6fua +vxuAvLxVxOH02zNzBvfTxhGasjnxwoOA4bKQr2/2LG+AKNpWNWBI7ufTJiWzJH3T +u+MRWsHRfNjbgSnOCqnL2r147Ry/L4H15qQwtRErUCZ6oaJbM1zvawvRFZBJ3RYF +WynmTQlq84OzWqy091tbR6WEsA5MY12tjRCSwTJd665ZxiGV4Hu5pZgX5iNlheII +TPoZw1fLc4KvQxYUAmDzo3XDUJA18+t2neb1UGB1kQNQVckLwwlpBln7CFoJSisM +gezc4baqRNIFQG19M9uBRpIyq3cucXzbnniLaVSMveY98Wp9VXvC4lNvRKuhxceD +WKnZSHkaBd7wH8IuH2/EvVn3OYkYQutOFs9tMHfchVTLqI5XLVvYD6zsxIvs2w0K +GsxMqeXoS8m+R/GLmppp2NTQRj4ZhuM+bKq2ywISkTEhEQzkL8IjAEv6Pfd+LLWh +wJhbVEwWduH9cR3KNlS4OGtg0EZaln3giGu2LUIOkiBsdM5yZlhQadMdsDZriIkW +FjcUKwwbLq/omV8WIlu0HA/klNxwnjihNcMbSKzECtEJ -----END CERTIFICATE----- Bag Attributes - localKeyID: 77 23 D2 3A C0 2C 87 E2 AD 98 3F 06 68 F2 54 33 B6 05 0E FE + localKeyID: 57 CC 99 3F 7C 3E D2 81 83 06 43 E2 9A 5E 12 06 9F 13 82 D0 Key Attributes: -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHN18R6x5Oz+u6 diff --git a/test/core/test_azure_auth.py b/test/core/test_azure_auth.py new file mode 100644 index 0000000..1fbb4f0 --- /dev/null +++ b/test/core/test_azure_auth.py @@ -0,0 +1,199 @@ +import os +import unittest +from unittest.mock import MagicMock, patch + +from requests import PreparedRequest + +from dothttp.azure_auth import AzureAuth +from dothttp.parse_models import AzureAuthServicePrincipal +from dothttp.parse_models import AzureAuthWrap, AzureAuthType +from dothttp.request_base import HttpFileFormatter # replace with your actual module +from test import TestBase + +dir_path = os.path.dirname(os.path.realpath(__file__)) +base_dir = f"{dir_path}/requests" +filename = f"{base_dir}/azureauth.http" + + + +class TestDothttpLoading(TestBase): + """Tests for `azure_auth.py`.""" + + + def base( + self, + target, + auth_wrap_type: AzureAuthType, + **kwargs): + tenant_id = kwargs.get("tenant_id", "dummy_tenant") + client_id = kwargs.get("client_id", "dummy_client") + secret_value = kwargs.get("secret_value", "dummy_secret") + certificate_path = kwargs.get("certificate_path", "dummy_certificate_path") + scope = kwargs.get("scope", "dummy_scope") + properties = kwargs.get("properties", []) + request = self.get_req_comp(file=filename, properties=properties, target=target) + request.load() + request.load_def() + auth: AzureAuth = request.httpdef.auth + self.assertEqual( + auth_wrap_type, + auth.azure_auth_wrap.azure_auth_type, + "auth type is different from expected") + if auth_wrap_type == AzureAuthType.SERVICE_PRINCIPAL: + self.assertEqual( + tenant_id, + auth.azure_auth_wrap.azure_spsecret_auth.tenant_id, + "tenant_id is different from expected") + self.assertEqual( + client_id, + auth.azure_auth_wrap.azure_spsecret_auth.client_id, + "client_id is different from expected") + self.assertEqual( + secret_value, + auth.azure_auth_wrap.azure_spsecret_auth.client_secret, + "secret_value is different from expected") + self.assertEqual( + scope, + auth.azure_auth_wrap.azure_spsecret_auth.scope, + "scope is different from expected") + elif auth_wrap_type == AzureAuthType.CERTIFICATE: + self.assertEqual( + tenant_id, + auth.azure_auth_wrap.azure_spcert_auth.tenant_id, + "tenant_id is different from expected") + self.assertEqual( + client_id, + auth.azure_auth_wrap.azure_spcert_auth.client_id, + "client_id is different from expected") + self.assertEqual( + certificate_path, + auth.azure_auth_wrap.azure_spcert_auth.certificate_path, + "certificate_path is different from expected") + self.assertEqual( + scope, + auth.azure_auth_wrap.azure_spcert_auth.scope, + "scope is different from expected") + elif auth_wrap_type == AzureAuthType.CLI: + self.assertEqual( + scope, + auth.azure_auth_wrap.azure_cli_auth.scope, + "scope is different from expected") + return request + def test_azure_auth_load(self): + """Test azure_auth.""" + properties = ["tenant_id=dummy_tenant", + "client_id=dummy_client", + "secret_value=dummy_secret", + "scope=dummy_scope", + "certificate_path=dummy_certificate_path" + ] + self.base("azure_sp", AzureAuthType.SERVICE_PRINCIPAL, properties=properties, ) + self.base("azure_cert", AzureAuthType.CERTIFICATE, properties=properties) + self.base("azure_cli", AzureAuthType.CLI, properties=properties) + + +class TestAzureAuth(unittest.TestCase): + @patch('msal.application.ConfidentialClientApplication') # replace with your actual module + def test_service_principal_auth(self, MockConfidentialClientApplication): + # Setup + mock_app = MockConfidentialClientApplication.return_value + mock_app.acquire_token_for_client.return_value = {'access_token': 'test_token', 'expires_in': 3600} + + azure_auth = AzureAuth( + azure_auth_wrap=AzureAuthWrap( + azure_auth_type=AzureAuthType.SERVICE_PRINCIPAL, + azure_spsecret_auth=AzureAuthServicePrincipal( + tenant_id="tenant_id", + client_id="client_id", + client_secret="client_secret", + scope="https://management.azure.com/.default") + ) + ) + + azure_auth.create_confidential_app = MagicMock(return_value=mock_app) + + r = PreparedRequest() + r.prepare_headers({}) + + azure_auth(r) + + # Verify + self.assertEqual(r.headers['Authorization'], 'Bearer test_token') + + mock_app.acquire_token_for_client.assert_called_once_with(scopes=['https://management.azure.com/.default']) + mock_app.get_accounts.assert_called_once() # This is called to get cached token + + mock_app.get_accounts.return_value = [None] + mock_app.acquire_token_silent.return_value = {'access_token': 'test_token2', 'expires_in': 3600} + azure_auth(r) + self.assertEqual(r.headers['Authorization'], 'Bearer test_token2') + + +class TestAzureEnd2End(TestBase): + @patch('msal.application.ConfidentialClientApplication') # replace with your actual module + def test_service_principal_auth(self, MockConfidentialClientApplication): + # Setup + mock_app = MockConfidentialClientApplication.return_value + mock_app.acquire_token_for_client.return_value = {'access_token': 'test_token', 'expires_in': 3600} + mock_app.get_accounts.return_value = [None] + mock_app.acquire_token_silent.return_value = {'access_token': 'test_token2', 'expires_in': 3600} + + # set variables + properties = ["tenant_id=dummy_tenant", + "client_id=dummy_client", + "secret_value=dummy_secret", + "scope=dummy_scope", + "certificate_path=dummy_certificate_path" + ] + # get request from parsing file + req_comp = self.get_req_comp(file=filename, properties=properties, target="azure_sp") + # load http_def + req_comp.load_def() + + # mock create_confidential_app + req_comp.httpdef.auth.create_confidential_app = MagicMock(return_value=mock_app) + + # get prepared request + prep_req = req_comp.get_request() + # Verify + + # verify token is fetched from cache + mock_app.acquire_token_silent.assert_called_once_with(scopes=['dummy_scope'], account=None) + # verify acquire token not called + mock_app.acquire_token_for_client.assert_not_called() + + # verify token generated from acquire token silent is used + self.assertEqual(prep_req.headers['Authorization'], 'Bearer test_token2') + mock_app.get_accounts.return_value = [] + + # verify http file format generation (which does not substitute variables) + self.assertEqual("""@name("azure_sp") +GET "https://management.azure.com/" +azurespsecret(tenant_id="{{tenant_id}}", client_id="{{client_id}}", client_secret="{{secret_value}}", scope="{{scope}}") + + +""", HttpFileFormatter.format_http(req_comp.http)) + + + # verify variable substitution by using http_def (which substitutes variables) + self.assertEqual("""@name("azure_sp") +GET "https://management.azure.com/" +azurespsecret(tenant_id="dummy_tenant", client_id="dummy_client", client_secret="dummy_secret", scope="dummy_scope") + + + +""", HttpFileFormatter.format_http(req_comp.httpdef.get_http_from_req())) + + + + + + + + + + + + # Add more test methods for AzureAuthCertificate and AzureAuthCli +if __name__ == '__main__': + unittest.main() \ No newline at end of file