*/saml: fully migrate to xmlsec, remove signxml dependency

This commit is contained in:
Jens Langhammer 2020-11-12 22:31:50 +01:00
parent 085247e2dc
commit e5e4824920
21 changed files with 520 additions and 136 deletions

View File

@ -10,8 +10,7 @@ extension-pkg-whitelist=lxml,xmlsec
const-rgx=[a-zA-Z0-9_]{1,40}$ const-rgx=[a-zA-Z0-9_]{1,40}$
ignored-modules=django-otp ignored-modules=django-otp
generated-members=xmlsec.constants.*,xmlsec.tree.* generated-members=xmlsec.constants.*,xmlsec.tree.*,xmlsec.template.*
ignore=migrations ignore=migrations
max-attributes=12 max-attributes=12
max-branches=20
jobs=12

View File

@ -35,7 +35,6 @@ qrcode = "*"
requests-oauthlib = "*" requests-oauthlib = "*"
sentry-sdk = "*" sentry-sdk = "*"
service_identity = "*" service_identity = "*"
signxml = "*"
structlog = "*" structlog = "*"
swagger-spec-validator = "*" swagger-spec-validator = "*"
urllib3 = {extras = ["secure"],version = "*"} urllib3 = {extras = ["secure"],version = "*"}

125
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "e8817160c5045ec2c6a1b0f355fb3fb8d25733e2fe6872f3a333741e1c8d15f1" "sha256": "db46a3d60f8aaad487f4ec1f65c454fd03da204eceb9d2c98c412c3029941261"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -28,6 +28,7 @@
"sha256:5b9062d5c0812335c75434bf17ce33d7a20ecfedaa0733faec7379868eb4068a", "sha256:5b9062d5c0812335c75434bf17ce33d7a20ecfedaa0733faec7379868eb4068a",
"sha256:fcd5b3baeeb7fc19b3486ff6d10543099d40ae1f5c9196eae695d1cde1b2f784" "sha256:fcd5b3baeeb7fc19b3486ff6d10543099d40ae1f5c9196eae695d1cde1b2f784"
], ],
"markers": "python_version >= '3.6'",
"version": "==5.0.2" "version": "==5.0.2"
}, },
"asgiref": { "asgiref": {
@ -35,6 +36,7 @@
"sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17",
"sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"
], ],
"markers": "python_version >= '3.5'",
"version": "==3.3.1" "version": "==3.3.1"
}, },
"async-timeout": { "async-timeout": {
@ -42,6 +44,7 @@
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
], ],
"markers": "python_full_version >= '3.5.3'",
"version": "==3.0.1" "version": "==3.0.1"
}, },
"attrs": { "attrs": {
@ -49,6 +52,7 @@
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.3.0" "version": "==20.3.0"
}, },
"autobahn": { "autobahn": {
@ -56,6 +60,7 @@
"sha256:24ce276d313e84d68241c3aef30d484f352b90a40168981b3640312c821df77b", "sha256:24ce276d313e84d68241c3aef30d484f352b90a40168981b3640312c821df77b",
"sha256:86bbce30cdd407137c57670993a8f9bfdfe3f8e994b889181d85e844d5aa8dfb" "sha256:86bbce30cdd407137c57670993a8f9bfdfe3f8e994b889181d85e844d5aa8dfb"
], ],
"markers": "python_version >= '3.5'",
"version": "==20.7.1" "version": "==20.7.1"
}, },
"automat": { "automat": {
@ -74,23 +79,25 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:b091cf6581dc137f100789240d628a105c989cf8f559b863fd15e18c1a29b714" "sha256:51c419d890ae216b9b031be31f3182739dc3deb5b64351f286bffca2818ddb35",
"sha256:d70d21ea137d786e84124639a62be42f92f4b09472ebfb761156057c92dc5366"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.16.17" "version": "==1.16.18"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:33f650b2d63cc1f2d5239947c9ecdadfd8ceeb4ab8bdefa0a711ac175a43bf44", "sha256:288d43e85f12e3c1d6a0535a585a182ca04e8c6e742ebaaf15357a0e3b37ca7a",
"sha256:81184afc24d19d730c1ded84513fbfc9e88409c329de5df1151bb45ac30dfce4" "sha256:bba18b5c4eef3eb2dc39b1b1f8959ba01ac27e7e12e413e281b0fb242990c0f5"
], ],
"version": "==1.19.17" "version": "==1.19.18"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
"sha256:513d4ff98dd27f85743a8dc0e92f55ddb1b49e060c2d5961512855cda2c01a98", "sha256:513d4ff98dd27f85743a8dc0e92f55ddb1b49e060c2d5961512855cda2c01a98",
"sha256:bbaa39c3dede00175df2dc2b03d0cf18dd2d32a7de7beb68072d13043c9edb20" "sha256:bbaa39c3dede00175df2dc2b03d0cf18dd2d32a7de7beb68072d13043c9edb20"
], ],
"markers": "python_version ~= '3.5'",
"version": "==4.1.1" "version": "==4.1.1"
}, },
"celery": { "celery": {
@ -177,6 +184,7 @@
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2" "version": "==7.1.2"
}, },
"click-didyoumean": { "click-didyoumean": {
@ -253,6 +261,7 @@
"sha256:0052c9887600c57054a5867d4b0240159fa009faa3bcf6a1627271d9cdcb005a", "sha256:0052c9887600c57054a5867d4b0240159fa009faa3bcf6a1627271d9cdcb005a",
"sha256:c22b692707f514de9013651ecb687f2abe4f35cf6fe292ece634e9f1737bc7e3" "sha256:c22b692707f514de9013651ecb687f2abe4f35cf6fe292ece634e9f1737bc7e3"
], ],
"markers": "python_version >= '3.6'",
"version": "==3.0.1" "version": "==3.0.1"
}, },
"defusedxml": { "defusedxml": {
@ -380,13 +389,6 @@
"index": "pypi", "index": "pypi",
"version": "==1.19.4" "version": "==1.19.4"
}, },
"eight": {
"hashes": [
"sha256:0a7f0e7725f2a478a97676cf9c49266d95f922f8ed621ec314eeccb333927dc2",
"sha256:d148aa1fac6cafb5ff806ff634914b05e3f9357aa8dbd82cd7908821d7f93f43"
],
"version": "==1.0.0"
},
"facebook-sdk": { "facebook-sdk": {
"hashes": [ "hashes": [
"sha256:2e987b3e0f466a6f4ee77b935eb023dba1384134f004a2af21f1cfff7fe0806e", "sha256:2e987b3e0f466a6f4ee77b935eb023dba1384134f004a2af21f1cfff7fe0806e",
@ -399,6 +401,7 @@
"hashes": [ "hashes": [
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.18.2" "version": "==0.18.2"
}, },
"google-auth": { "google-auth": {
@ -406,6 +409,7 @@
"sha256:5176db85f1e7e837a646cd9cede72c3c404ccf2e3373d9ee14b2db88febad440", "sha256:5176db85f1e7e837a646cd9cede72c3c404ccf2e3373d9ee14b2db88febad440",
"sha256:b728625ff5dfce8f9e56a499c8a4eb51443a67f20f6d28b67d5774c310ec4b6b" "sha256:b728625ff5dfce8f9e56a499c8a4eb51443a67f20f6d28b67d5774c310ec4b6b"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.23.0" "version": "==1.23.0"
}, },
"gunicorn": { "gunicorn": {
@ -472,6 +476,7 @@
"sha256:e64be68255234bb489a574c4f2f8df7029c98c81ec4d160d6cd836e7f0679390", "sha256:e64be68255234bb489a574c4f2f8df7029c98c81ec4d160d6cd836e7f0679390",
"sha256:e82d6b930e02e80e5109b678c663a9ed210680ded81c1abaf54635d88d1da298" "sha256:e82d6b930e02e80e5109b678c663a9ed210680ded81c1abaf54635d88d1da298"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.0" "version": "==1.1.0"
}, },
"httptools": { "httptools": {
@ -517,6 +522,7 @@
"sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417",
"sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2" "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"
], ],
"markers": "python_version >= '3.5'",
"version": "==0.5.1" "version": "==0.5.1"
}, },
"itypes": { "itypes": {
@ -531,6 +537,7 @@
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.11.2" "version": "==2.11.2"
}, },
"jmespath": { "jmespath": {
@ -538,6 +545,7 @@
"sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9",
"sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f" "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.0" "version": "==0.10.0"
}, },
"jsonschema": { "jsonschema": {
@ -552,20 +560,24 @@
"sha256:6dc509178ac4269b0e66ab4881f70a2035c33d3a622e20585f965986a5182006", "sha256:6dc509178ac4269b0e66ab4881f70a2035c33d3a622e20585f965986a5182006",
"sha256:f4965fba0a4718d47d470beeb5d6446e3357a62402b16c510b6a2f251e05ac3c" "sha256:f4965fba0a4718d47d470beeb5d6446e3357a62402b16c510b6a2f251e05ac3c"
], ],
"markers": "python_version >= '3.6'",
"version": "==5.0.2" "version": "==5.0.2"
}, },
"kubernetes": { "kubernetes": {
"hashes": [ "hashes": [
"sha256:72f095a1cd593401ff26b3b8d71749340394ca6d8413770ea28ce18efd5bcf4c", "sha256:23c85d8571df8f56e773f1a413bc081537536dc47e2b5e8dc2e6262edb2c57ca",
"sha256:9a339a32d6c79e6461cb6050c3662cb4e33058b508d8d34ee5d5206add395828" "sha256:ec52ea01d52e2ec3da255992f7e859f3a76f2bdb51cf65ba8cd71dfc309d8daa"
], ],
"index": "pypi", "index": "pypi",
"version": "==12.0.0" "version": "==12.0.1"
}, },
"ldap3": { "ldap3": {
"hashes": [ "hashes": [
"sha256:8f59a7b5399555b22db06f153daa76c77ded2dd84bc0f0ffe5b0b33901b6eac4",
"sha256:7c3738570766f5e5e74a56fade15470f339d5c436d821cf476ef27da0a4de8b0",
"sha256:37d633e20fa360c302b1263c96fe932d40622d0119f1bddcb829b03462eeeeb7", "sha256:37d633e20fa360c302b1263c96fe932d40622d0119f1bddcb829b03462eeeeb7",
"sha256:7c3738570766f5e5e74a56fade15470f339d5c436d821cf476ef27da0a4de8b0" "sha256:10bdd23b612e942ce90ea4dbc744dfd88735949833e46c5467a2dcf68e60f469",
"sha256:bed71c6ce2f70a00a330eed0c8370664c065239d45bcbe1b82517b6f6eed7f25"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.8.1" "version": "==2.8.1"
@ -649,6 +661,7 @@
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.1" "version": "==1.1.1"
}, },
"msgpack": { "msgpack": {
@ -679,6 +692,7 @@
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
"sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.1.0" "version": "==3.1.0"
}, },
"packaging": { "packaging": {
@ -701,6 +715,7 @@
"sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c", "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c",
"sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63" "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"
], ],
"markers": "python_full_version >= '3.6.1'",
"version": "==3.0.8" "version": "==3.0.8"
}, },
"psycopg2-binary": { "psycopg2-binary": {
@ -746,15 +761,37 @@
}, },
"pyasn1": { "pyasn1": {
"hashes": [ "hashes": [
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba" "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3",
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"
], ],
"version": "==0.4.8" "version": "==0.4.8"
}, },
"pyasn1-modules": { "pyasn1-modules": {
"hashes": [ "hashes": [
"sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
"sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
"sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb",
"sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
"sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74" "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
"sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405",
"sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
"sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
"sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
"sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"
], ],
"version": "==0.2.8" "version": "==0.2.8"
}, },
@ -763,6 +800,7 @@
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.20" "version": "==2.20"
}, },
"pycryptodome": { "pycryptodome": {
@ -844,6 +882,7 @@
"sha256:f20a62397e09704049ce9007bea4f6bad965ba9336a760c6f4ef1b4192e12d6d", "sha256:f20a62397e09704049ce9007bea4f6bad965ba9336a760c6f4ef1b4192e12d6d",
"sha256:f81f7311250d9480e36dec819127897ae772e7e8de07abfabe931b8566770b8e" "sha256:f81f7311250d9480e36dec819127897ae772e7e8de07abfabe931b8566770b8e"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.9.9" "version": "==3.9.9"
}, },
"pyhamcrest": { "pyhamcrest": {
@ -851,6 +890,7 @@
"sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316", "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316",
"sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29" "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29"
], ],
"markers": "python_version >= '3.5'",
"version": "==2.0.2" "version": "==2.0.2"
}, },
"pyjwkest": { "pyjwkest": {
@ -872,12 +912,14 @@
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7" "version": "==2.4.7"
}, },
"pyrsistent": { "pyrsistent": {
"hashes": [ "hashes": [
"sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"
], ],
"markers": "python_version >= '3.5'",
"version": "==0.17.3" "version": "==0.17.3"
}, },
"python-dateutil": { "python-dateutil": {
@ -885,6 +927,7 @@
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1" "version": "==2.8.1"
}, },
"python-dotenv": { "python-dotenv": {
@ -931,6 +974,7 @@
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2", "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
"sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24" "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==3.5.3" "version": "==3.5.3"
}, },
"requests": { "requests": {
@ -938,10 +982,12 @@
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8", "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998" "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.0" "version": "==2.25.0"
}, },
"requests-oauthlib": { "requests-oauthlib": {
"hashes": [ "hashes": [
"sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc",
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a" "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"
], ],
@ -990,7 +1036,7 @@
"sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2", "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2",
"sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f" "sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"
], ],
"markers": "platform_python_implementation == 'CPython' and python_version < '3.9'", "markers": "python_version < '3.9' and platform_python_implementation == 'CPython'",
"version": "==0.2.2" "version": "==0.2.2"
}, },
"s3transfer": { "s3transfer": {
@ -1016,19 +1062,12 @@
"index": "pypi", "index": "pypi",
"version": "==18.1.0" "version": "==18.1.0"
}, },
"signxml": {
"hashes": [
"sha256:b70e151d10d99cbc74a50a3344f508ee481fe3c376d61cd1cae850912d303d19",
"sha256:bab03a6823c9a5b225d1e6266ce66b5d08c4ebfb42029fdb5d3e588b8128c86d"
],
"index": "pypi",
"version": "==2.8.1"
},
"six": { "six": {
"hashes": [ "hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0" "version": "==1.15.0"
}, },
"sqlparse": { "sqlparse": {
@ -1036,6 +1075,7 @@
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
], ],
"markers": "python_version >= '3.5'",
"version": "==0.4.1" "version": "==0.4.1"
}, },
"structlog": { "structlog": {
@ -1083,6 +1123,7 @@
"sha256:f058bd0168271de4dcdc39845b52dd0a4a2fecf5f1246335f13f5e96eaebb467", "sha256:f058bd0168271de4dcdc39845b52dd0a4a2fecf5f1246335f13f5e96eaebb467",
"sha256:f3c19e5bd42bbe4bf345704ad7c326c74d3fd7a1b3844987853bef180be638d4" "sha256:f3c19e5bd42bbe4bf345704ad7c326c74d3fd7a1b3844987853bef180be638d4"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==20.3.0" "version": "==20.3.0"
}, },
"txaio": { "txaio": {
@ -1090,6 +1131,7 @@
"sha256:17938f2bca4a9cabce61346758e482ca4e600160cbc28e861493eac74a19539d", "sha256:17938f2bca4a9cabce61346758e482ca4e600160cbc28e861493eac74a19539d",
"sha256:38a469daf93c37e5527cb062653d6393ae11663147c42fab7ddc3f6d00d434ae" "sha256:38a469daf93c37e5527cb062653d6393ae11663147c42fab7ddc3f6d00d434ae"
], ],
"markers": "python_version >= '3.5'",
"version": "==20.4.1" "version": "==20.4.1"
}, },
"uritemplate": { "uritemplate": {
@ -1097,6 +1139,7 @@
"sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f", "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f",
"sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae" "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.0.1" "version": "==3.0.1"
}, },
"urllib3": { "urllib3": {
@ -1108,7 +1151,6 @@
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
], ],
"index": "pypi", "index": "pypi",
"markers": null,
"version": "==1.26.2" "version": "==1.26.2"
}, },
"uvicorn": { "uvicorn": {
@ -1141,6 +1183,7 @@
"sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30", "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30",
"sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e" "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"
], ],
"markers": "python_version >= '3.6'",
"version": "==5.0.0" "version": "==5.0.0"
}, },
"watchgod": { "watchgod": {
@ -1265,6 +1308,7 @@
"sha256:f37d45fab14ffef9d33a0dc3bc59ce0c5313e2253323312d47739192da94f5fd", "sha256:f37d45fab14ffef9d33a0dc3bc59ce0c5313e2253323312d47739192da94f5fd",
"sha256:f44906f70205d456d503105023041f1e63aece7623b31c390a0103db4de17537" "sha256:f44906f70205d456d503105023041f1e63aece7623b31c390a0103db4de17537"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==5.2.0" "version": "==5.2.0"
} }
}, },
@ -1281,6 +1325,7 @@
"sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17",
"sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"
], ],
"markers": "python_version >= '3.5'",
"version": "==3.3.1" "version": "==3.3.1"
}, },
"astroid": { "astroid": {
@ -1288,6 +1333,7 @@
"sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1", "sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1",
"sha256:d8506842a3faf734b81599c8b98dcc423de863adcc1999248480b18bd31a0f38" "sha256:d8506842a3faf734b81599c8b98dcc423de863adcc1999248480b18bd31a0f38"
], ],
"markers": "python_version >= '3.5'",
"version": "==2.4.1" "version": "==2.4.1"
}, },
"attrs": { "attrs": {
@ -1295,6 +1341,7 @@
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.3.0" "version": "==20.3.0"
}, },
"autopep8": { "autopep8": {
@ -1324,6 +1371,7 @@
"sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410",
"sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6" "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"
], ],
"markers": "python_version >= '3.5'",
"version": "==1.0.1" "version": "==1.0.1"
}, },
"bumpversion": { "bumpversion": {
@ -1339,6 +1387,7 @@
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2" "version": "==7.1.2"
}, },
"colorama": { "colorama": {
@ -1417,6 +1466,7 @@
"sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839", "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839",
"sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b" "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.8.4" "version": "==3.8.4"
}, },
"flake8-polyfill": { "flake8-polyfill": {
@ -1431,6 +1481,7 @@
"sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac",
"sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9" "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"
], ],
"markers": "python_version >= '3.4'",
"version": "==4.0.5" "version": "==4.0.5"
}, },
"gitpython": { "gitpython": {
@ -1438,6 +1489,7 @@
"sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b", "sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b",
"sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8" "sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8"
], ],
"markers": "python_version >= '3.4'",
"version": "==3.1.11" "version": "==3.1.11"
}, },
"iniconfig": { "iniconfig": {
@ -1452,6 +1504,7 @@
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==4.3.21" "version": "==4.3.21"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
@ -1478,6 +1531,7 @@
"sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
"sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.4.3" "version": "==1.4.3"
}, },
"mccabe": { "mccabe": {
@ -1514,6 +1568,7 @@
"sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9", "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9",
"sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00" "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"
], ],
"markers": "python_version >= '2.6'",
"version": "==5.5.1" "version": "==5.5.1"
}, },
"pep8-naming": { "pep8-naming": {
@ -1528,6 +1583,7 @@
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1" "version": "==0.13.1"
}, },
"prospector": { "prospector": {
@ -1542,6 +1598,7 @@
"sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
"sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.9.0" "version": "==1.9.0"
}, },
"pycodestyle": { "pycodestyle": {
@ -1549,6 +1606,7 @@
"sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
"sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.6.0" "version": "==2.6.0"
}, },
"pydocstyle": { "pydocstyle": {
@ -1556,6 +1614,7 @@
"sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325", "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325",
"sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678" "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"
], ],
"markers": "python_version >= '3.5'",
"version": "==5.1.1" "version": "==5.1.1"
}, },
"pyflakes": { "pyflakes": {
@ -1563,6 +1622,7 @@
"sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
"sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.2.0" "version": "==2.2.0"
}, },
"pylint": { "pylint": {
@ -1605,6 +1665,7 @@
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7" "version": "==2.4.7"
}, },
"pytest": { "pytest": {
@ -1718,6 +1779,7 @@
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0" "version": "==1.15.0"
}, },
"smmap": { "smmap": {
@ -1725,6 +1787,7 @@
"sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4", "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4",
"sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24" "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.0.4" "version": "==3.0.4"
}, },
"snowballstemmer": { "snowballstemmer": {
@ -1739,6 +1802,7 @@
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
], ],
"markers": "python_version >= '3.5'",
"version": "==0.4.1" "version": "==0.4.1"
}, },
"stevedore": { "stevedore": {
@ -1746,6 +1810,7 @@
"sha256:5e1ab03eaae06ef6ce23859402de785f08d97780ed774948ef16c4652c41bc62", "sha256:5e1ab03eaae06ef6ce23859402de785f08d97780ed774948ef16c4652c41bc62",
"sha256:f845868b3a3a77a2489d226568abe7328b5c2d4f6a011cc759dfa99144a521f0" "sha256:f845868b3a3a77a2489d226568abe7328b5c2d4f6a011cc759dfa99144a521f0"
], ],
"markers": "python_version >= '3.6'",
"version": "==3.2.2" "version": "==3.2.2"
}, },
"toml": { "toml": {
@ -1753,6 +1818,7 @@
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2" "version": "==0.10.2"
}, },
"typed-ast": { "typed-ast": {
@ -1807,7 +1873,6 @@
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
], ],
"index": "pypi", "index": "pypi",
"markers": null,
"version": "==1.26.2" "version": "==1.26.2"
}, },
"wrapt": { "wrapt": {

View File

@ -9,7 +9,7 @@ curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs sudo apt-get install -y nodejs
sudo npm install -g yarn sudo npm install -g yarn
# Setup python # Setup python
sudo apt install -y python3.8 python3-pip sudo apt install -y python3.8 python3-pip libxmlsec1-dev pkg-config
# Setup docker # Setup docker
sudo pip3 install pipenv sudo pip3 install pipenv

View File

@ -0,0 +1,69 @@
# Generated by Django 3.1.3 on 2020-11-12 20:16
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from passbook.sources.saml.processors import constants
def update_algorithms(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
SAMLProvider = apps.get_model("passbook_providers_saml", "SAMLProvider")
signature_translation_map = {
"rsa-sha1": constants.RSA_SHA1,
"rsa-sha256": constants.RSA_SHA256,
"ecdsa-sha256": constants.RSA_SHA256,
"dsa-sha1": constants.DSA_SHA1,
}
digest_translation_map = {
"sha1": constants.SHA1,
"sha256": constants.SHA256,
}
for source in SAMLProvider.objects.all():
source.signature_algorithm = signature_translation_map.get(
source.signature_algorithm, constants.RSA_SHA256
)
source.digest_algorithm = digest_translation_map.get(
source.digest_algorithm, constants.SHA256
)
source.save()
class Migration(migrations.Migration):
dependencies = [
("passbook_providers_saml", "0008_auto_20201112_1036"),
]
operations = [
migrations.AlterField(
model_name="samlprovider",
name="digest_algorithm",
field=models.CharField(
choices=[
(constants.SHA1, "SHA1"),
(constants.SHA256, "SHA256"),
(constants.SHA384, "SHA384"),
(constants.SHA512, "SHA512"),
],
default=constants.SHA256,
max_length=50,
),
),
migrations.AlterField(
model_name="samlprovider",
name="signature_algorithm",
field=models.CharField(
choices=[
(constants.RSA_SHA1, "RSA-SHA1"),
(constants.RSA_SHA256, "RSA-SHA256"),
(constants.RSA_SHA384, "RSA-SHA384"),
(constants.RSA_SHA512, "RSA-SHA512"),
(constants.DSA_SHA1, "DSA-SHA1"),
],
default=constants.RSA_SHA256,
max_length=50,
),
),
]

View File

@ -13,6 +13,17 @@ from passbook.core.models import PropertyMapping, Provider
from passbook.crypto.models import CertificateKeyPair from passbook.crypto.models import CertificateKeyPair
from passbook.lib.utils.template import render_to_string from passbook.lib.utils.template import render_to_string
from passbook.lib.utils.time import timedelta_string_validator from passbook.lib.utils.time import timedelta_string_validator
from passbook.sources.saml.processors.constants import (
DSA_SHA1,
RSA_SHA1,
RSA_SHA256,
RSA_SHA384,
RSA_SHA512,
SHA1,
SHA256,
SHA384,
SHA512,
)
LOGGER = get_logger() LOGGER = get_logger()
@ -80,20 +91,23 @@ class SAMLProvider(Provider):
digest_algorithm = models.CharField( digest_algorithm = models.CharField(
max_length=50, max_length=50,
choices=( choices=(
("sha1", _("SHA1")), (SHA1, _("SHA1")),
("sha256", _("SHA256")), (SHA256, _("SHA256")),
(SHA384, _("SHA384")),
(SHA512, _("SHA512")),
), ),
default="sha256", default=SHA256,
) )
signature_algorithm = models.CharField( signature_algorithm = models.CharField(
max_length=50, max_length=50,
choices=( choices=(
("rsa-sha1", _("RSA-SHA1")), (RSA_SHA1, _("RSA-SHA1")),
("rsa-sha256", _("RSA-SHA256")), (RSA_SHA256, _("RSA-SHA256")),
("ecdsa-sha256", _("ECDSA-SHA256")), (RSA_SHA384, _("RSA-SHA384")),
("dsa-sha1", _("DSA-SHA1")), (RSA_SHA512, _("RSA-SHA512")),
(DSA_SHA1, _("DSA-SHA1")),
), ),
default="rsa-sha256", default=RSA_SHA256,
) )
verification_kp = models.ForeignKey( verification_kp = models.ForeignKey(

View File

@ -2,10 +2,10 @@
from hashlib import sha256 from hashlib import sha256
from types import GeneratorType from types import GeneratorType
import xmlsec
from django.http import HttpRequest from django.http import HttpRequest
from lxml import etree # nosec from lxml import etree # nosec
from lxml.etree import Element, SubElement # nosec from lxml.etree import Element, SubElement # nosec
from signxml import XMLSigner, XMLVerifier, strip_pem_header
from structlog import get_logger from structlog import get_logger
from passbook.core.exceptions import PropertyMappingExpressionException from passbook.core.exceptions import PropertyMappingExpressionException
@ -16,14 +16,15 @@ from passbook.providers.saml.utils import get_random_id
from passbook.providers.saml.utils.time import get_time_string from passbook.providers.saml.utils.time import get_time_string
from passbook.sources.saml.exceptions import UnsupportedNameIDFormat from passbook.sources.saml.exceptions import UnsupportedNameIDFormat
from passbook.sources.saml.processors.constants import ( from passbook.sources.saml.processors.constants import (
DIGEST_ALGORITHM_TRANSLATION_MAP,
NS_MAP, NS_MAP,
NS_SAML_ASSERTION, NS_SAML_ASSERTION,
NS_SAML_PROTOCOL, NS_SAML_PROTOCOL,
NS_SIGNATURE,
SAML_NAME_ID_FORMAT_EMAIL, SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT, SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT, SAML_NAME_ID_FORMAT_TRANSIENT,
SAML_NAME_ID_FORMAT_X509, SAML_NAME_ID_FORMAT_X509,
SIGN_ALGORITHM_TRANSFORM_MAP,
) )
LOGGER = get_logger() LOGGER = get_logger()
@ -186,12 +187,16 @@ class AssertionProcessor:
assertion.append(self.get_issuer()) assertion.append(self.get_issuer())
if self.provider.signing_kp: if self.provider.signing_kp:
# We need a placeholder signature as SAML requires the signature to be between sign_algorithm_transform = SIGN_ALGORITHM_TRANSFORM_MAP.get(
# Issuer and subject self.provider.signature_algorithm, xmlsec.constants.TransformRsaSha1
signature_placeholder = SubElement(
assertion, f"{{{NS_SIGNATURE}}}Signature", nsmap=NS_MAP
) )
signature_placeholder.attrib["Id"] = "placeholder" signature = xmlsec.template.create(
assertion,
xmlsec.constants.TransformExclC14N,
sign_algorithm_transform,
ns="ds", # type: ignore
)
assertion.append(signature)
assertion.append(self.get_assertion_subject()) assertion.append(self.get_assertion_subject())
assertion.append(self.get_assertion_conditions()) assertion.append(self.get_assertion_conditions())
@ -223,20 +228,36 @@ class AssertionProcessor:
"""Build string XML Response and sign if signing is enabled.""" """Build string XML Response and sign if signing is enabled."""
root_response = self.get_response() root_response = self.get_response()
if self.provider.signing_kp: if self.provider.signing_kp:
signer = XMLSigner( digest_algorithm_transform = DIGEST_ALGORITHM_TRANSLATION_MAP.get(
c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#", self.provider.digest_algorithm, xmlsec.constants.TransformSha1
signature_algorithm=self.provider.signature_algorithm,
digest_algorithm=self.provider.digest_algorithm,
) )
x509_data = strip_pem_header( assertion = root_response.xpath("//saml:Assertion", namespaces=NS_MAP)[0]
self.provider.signing_kp.certificate_data xmlsec.tree.add_ids(assertion, ["ID"])
).replace("\n", "") signature_node = xmlsec.tree.find_node(
signed = signer.sign( assertion, xmlsec.constants.NodeSignature
root_response,
key=self.provider.signing_kp.private_key,
cert=[x509_data],
reference_uri=self._assertion_id,
) )
XMLVerifier().verify(signed, x509_cert=x509_data) ref = xmlsec.template.add_reference(
return etree.tostring(signed).decode("utf-8") # nosec signature_node,
digest_algorithm_transform,
uri="#" + self._assertion_id,
)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformEnveloped)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformExclC14N)
key_info = xmlsec.template.ensure_key_info(signature_node)
xmlsec.template.add_x509_data(key_info)
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
self.provider.signing_kp.key_data,
xmlsec.constants.KeyDataFormatPem,
None,
)
key.load_cert_from_memory(
self.provider.signing_kp.certificate_data,
xmlsec.constants.KeyDataFormatCertPem,
)
ctx.key = key
ctx.sign(signature_node)
return etree.tostring(root_response).decode("utf-8") # nosec return etree.tostring(root_response).decode("utf-8") # nosec

View File

@ -4,9 +4,9 @@ from typing import Iterator, Optional
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import reverse from django.shortcuts import reverse
from lxml.etree import Element, SubElement, tostring # nosec from lxml.etree import Element, SubElement, tostring # nosec
from signxml.util import strip_pem_header
from passbook.providers.saml.models import SAMLProvider from passbook.providers.saml.models import SAMLProvider
from passbook.providers.saml.utils.encoding import strip_pem_header
from passbook.sources.saml.processors.constants import ( from passbook.sources.saml.processors.constants import (
NS_MAP, NS_MAP,
NS_SAML_METADATA, NS_SAML_METADATA,
@ -42,7 +42,7 @@ class MetadataProcessor:
) )
x509_certificate.text = strip_pem_header( x509_certificate.text = strip_pem_header(
self.provider.signing_kp.certificate_data.replace("\r", "") self.provider.signing_kp.certificate_data.replace("\r", "")
).replace("\n", "") )
return key_descriptor return key_descriptor
return None return None

View File

@ -14,6 +14,7 @@ from passbook.providers.saml.models import SAMLProvider
from passbook.providers.saml.utils.encoding import decode_base64_and_inflate from passbook.providers.saml.utils.encoding import decode_base64_and_inflate
from passbook.sources.saml.processors.constants import ( from passbook.sources.saml.processors.constants import (
DSA_SHA1, DSA_SHA1,
NS_MAP,
NS_SAML_PROTOCOL, NS_SAML_PROTOCOL,
RSA_SHA1, RSA_SHA1,
RSA_SHA256, RSA_SHA256,
@ -77,18 +78,24 @@ class AuthNRequestParser:
def parse(self, saml_request: str, relay_state: Optional[str]) -> AuthNRequest: def parse(self, saml_request: str, relay_state: Optional[str]) -> AuthNRequest:
"""Validate and parse raw request with enveloped signautre.""" """Validate and parse raw request with enveloped signautre."""
decoded_xml = decode_base64_and_inflate(saml_request) decoded_xml = b64decode(saml_request.encode()).decode()
verifier = self.provider.verification_kp verifier = self.provider.verification_kp
root = etree.fromstring(decoded_xml) # nosec root = etree.fromstring(decoded_xml) # nosec
xmlsec.tree.add_ids(root, ["ID"]) xmlsec.tree.add_ids(root, ["ID"])
signature_node = xmlsec.tree.find_node(root, xmlsec.constants.NodeSignature) signature_nodes = root.xpath(
"/samlp:AuthnRequest/ds:Signature", namespaces=NS_MAP
if verifier and not signature_node: )
if len(signature_nodes) != 1:
raise CannotHandleAssertion(ERROR_SIGNATURE_REQUIRED_BUT_ABSENT) raise CannotHandleAssertion(ERROR_SIGNATURE_REQUIRED_BUT_ABSENT)
if signature_node: signature_node = signature_nodes[0]
if verifier and signature_node is None:
raise CannotHandleAssertion(ERROR_SIGNATURE_REQUIRED_BUT_ABSENT)
if signature_node is not None:
if not verifier: if not verifier:
raise CannotHandleAssertion(ERROR_SIGNATURE_EXISTS_BUT_NO_VERIFIER) raise CannotHandleAssertion(ERROR_SIGNATURE_EXISTS_BUT_NO_VERIFIER)

View File

@ -1,4 +1,6 @@
"""Test AuthN Request generator and parser""" """Test AuthN Request generator and parser"""
from base64 import b64encode
from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.middleware import SessionMiddleware
from django.http.request import HttpRequest, QueryDict from django.http.request import HttpRequest, QueryDict
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
@ -6,10 +8,9 @@ from guardian.utils import get_anonymous_user
from passbook.crypto.models import CertificateKeyPair from passbook.crypto.models import CertificateKeyPair
from passbook.flows.models import Flow from passbook.flows.models import Flow
from passbook.providers.saml.models import SAMLProvider from passbook.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from passbook.providers.saml.processors.assertion import AssertionProcessor from passbook.providers.saml.processors.assertion import AssertionProcessor
from passbook.providers.saml.processors.request_parser import AuthNRequestParser from passbook.providers.saml.processors.request_parser import AuthNRequestParser
from passbook.providers.saml.utils.encoding import deflate_and_base64_encode
from passbook.sources.saml.exceptions import MismatchedRequestID from passbook.sources.saml.exceptions import MismatchedRequestID
from passbook.sources.saml.models import SAMLSource from passbook.sources.saml.models import SAMLSource
from passbook.sources.saml.processors.constants import SAML_NAME_ID_FORMAT_EMAIL from passbook.sources.saml.processors.constants import SAML_NAME_ID_FORMAT_EMAIL
@ -67,7 +68,7 @@ class TestAuthNRequest(TestCase):
def setUp(self): def setUp(self):
cert = CertificateKeyPair.objects.first() cert = CertificateKeyPair.objects.first()
self.provider = SAMLProvider.objects.create( self.provider: SAMLProvider = SAMLProvider.objects.create(
authorization_flow=Flow.objects.get( authorization_flow=Flow.objects.get(
slug="default-provider-authorization-implicit-consent" slug="default-provider-authorization-implicit-consent"
), ),
@ -75,6 +76,8 @@ class TestAuthNRequest(TestCase):
signing_kp=cert, signing_kp=cert,
verification_kp=cert, verification_kp=cert,
) )
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
self.provider.save()
self.source = SAMLSource.objects.create( self.source = SAMLSource.objects.create(
slug="provider", slug="provider",
issuer="passbook", issuer="passbook",
@ -95,11 +98,39 @@ class TestAuthNRequest(TestCase):
request = request_proc.build_auth_n() request = request_proc.build_auth_n()
# Now we check the ID and signature # Now we check the ID and signature
parsed_request = AuthNRequestParser(self.provider).parse( parsed_request = AuthNRequestParser(self.provider).parse(
deflate_and_base64_encode(request), "test_state" b64encode(request.encode()).decode(), "test_state"
) )
self.assertEqual(parsed_request.id, request_proc.request_id) self.assertEqual(parsed_request.id, request_proc.request_id)
self.assertEqual(parsed_request.relay_state, "test_state") self.assertEqual(parsed_request.relay_state, "test_state")
def test_request_full_signed(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/")
http_request.user = get_anonymous_user()
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
response = response_proc.build_response()
# Now parse the response (source)
http_request.POST = QueryDict(mutable=True)
http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
response_parser = ResponseProcessor(self.source)
response_parser.parse(http_request)
def test_request_id_invalid(self): def test_request_id_invalid(self):
"""Test generated AuthNRequest with invalid request ID""" """Test generated AuthNRequest with invalid request ID"""
http_request = self.factory.get("/") http_request = self.factory.get("/")
@ -119,7 +150,7 @@ class TestAuthNRequest(TestCase):
# To get an assertion we need a parsed request (parsed by provider) # To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse( parsed_request = AuthNRequestParser(self.provider).parse(
deflate_and_base64_encode(request), "test_state" b64encode(request.encode()).decode(), "test_state"
) )
# Now create a response and convert it to string (provider) # Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request) response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
@ -127,7 +158,7 @@ class TestAuthNRequest(TestCase):
# Now parse the response (source) # Now parse the response (source)
http_request.POST = QueryDict(mutable=True) http_request.POST = QueryDict(mutable=True)
http_request.POST["SAMLResponse"] = deflate_and_base64_encode(response) http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
response_parser = ResponseProcessor(self.source) response_parser = ResponseProcessor(self.source)

View File

@ -2,6 +2,9 @@
import base64 import base64
import zlib import zlib
PEM_HEADER = "-----BEGIN CERTIFICATE-----"
PEM_FOOTER = "-----END CERTIFICATE-----"
def decode_base64_and_inflate(encoded: str, encoding="utf-8") -> str: def decode_base64_and_inflate(encoded: str, encoding="utf-8") -> str:
"""Base64 decode and ZLib decompress b64string""" """Base64 decode and ZLib decompress b64string"""
@ -22,3 +25,8 @@ def deflate_and_base64_encode(inflated: str, encoding="utf-8"):
def nice64(src: str) -> str: def nice64(src: str) -> str:
"""Returns src base64-encoded and formatted nicely for our XML. """ """Returns src base64-encoded and formatted nicely for our XML. """
return base64.b64encode(src.encode()).decode("utf-8").replace("\n", "") return base64.b64encode(src.encode()).decode("utf-8").replace("\n", "")
def strip_pem_header(cert: str) -> str:
"""Remove PEM Headers"""
return cert.replace(PEM_HEADER, "").replace(PEM_FOOTER, "").replace("\n", "")

View File

@ -127,7 +127,7 @@ class SAMLSSOBindingPOSTView(SAMLSSOView):
def check_saml_request(self) -> Optional[HttpRequest]: def check_saml_request(self) -> Optional[HttpRequest]:
"""Handle POST bindings""" """Handle POST bindings"""
if REQUEST_KEY_SAML_REQUEST not in self.request.POST: if REQUEST_KEY_SAML_REQUEST not in self.request.POST:
LOGGER.info("handle_saml_request: SAML payload missing") LOGGER.info("check_saml_request: SAML payload missing")
return bad_request_message( return bad_request_message(
self.request, "The SAML request payload is missing." self.request, "The SAML request payload is missing."
) )

View File

@ -12,3 +12,7 @@ class UnsupportedNameIDFormat(SentryIgnoredException):
class MismatchedRequestID(SentryIgnoredException): class MismatchedRequestID(SentryIgnoredException):
"""Exception raised when the returned request ID doesn't match the saved ID.""" """Exception raised when the returned request ID doesn't match the saved ID."""
class InvalidSignature(SentryIgnoredException):
"""Signature of XML Object is either missing or invalid"""

View File

@ -0,0 +1,70 @@
# Generated by Django 3.1.3 on 2020-11-12 20:16
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from passbook.sources.saml.processors import constants
def update_algorithms(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
SAMLSource = apps.get_model("passbook_sources_saml", "SAMLSource")
signature_translation_map = {
"rsa-sha1": constants.RSA_SHA1,
"rsa-sha256": constants.RSA_SHA256,
"ecdsa-sha256": constants.RSA_SHA256,
"dsa-sha1": constants.DSA_SHA1,
}
digest_translation_map = {
"sha1": constants.SHA1,
"sha256": constants.SHA256,
}
for source in SAMLSource.objects.all():
source.signature_algorithm = signature_translation_map.get(
source.signature_algorithm, constants.RSA_SHA256
)
source.digest_algorithm = digest_translation_map.get(
source.digest_algorithm, constants.SHA256
)
source.save()
class Migration(migrations.Migration):
dependencies = [
("passbook_sources_saml", "0007_auto_20201112_1055"),
]
operations = [
migrations.AlterField(
model_name="samlsource",
name="signature_algorithm",
field=models.CharField(
choices=[
(constants.RSA_SHA1, "RSA-SHA1"),
(constants.RSA_SHA256, "RSA-SHA256"),
(constants.RSA_SHA384, "RSA-SHA384"),
(constants.RSA_SHA512, "RSA-SHA512"),
(constants.DSA_SHA1, "DSA-SHA1"),
],
default=constants.RSA_SHA256,
max_length=50,
),
),
migrations.AlterField(
model_name="samlsource",
name="digest_algorithm",
field=models.CharField(
choices=[
(constants.SHA1, "SHA1"),
(constants.SHA256, "SHA256"),
(constants.SHA384, "SHA384"),
(constants.SHA512, "SHA512"),
],
default=constants.SHA256,
max_length=50,
),
),
migrations.RunPython(update_algorithms),
]

View File

@ -13,11 +13,20 @@ from passbook.core.types import UILoginButton
from passbook.crypto.models import CertificateKeyPair from passbook.crypto.models import CertificateKeyPair
from passbook.lib.utils.time import timedelta_string_validator from passbook.lib.utils.time import timedelta_string_validator
from passbook.sources.saml.processors.constants import ( from passbook.sources.saml.processors.constants import (
DSA_SHA1,
RSA_SHA1,
RSA_SHA256,
RSA_SHA384,
RSA_SHA512,
SAML_NAME_ID_FORMAT_EMAIL, SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT, SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT, SAML_NAME_ID_FORMAT_TRANSIENT,
SAML_NAME_ID_FORMAT_WINDOWS, SAML_NAME_ID_FORMAT_WINDOWS,
SAML_NAME_ID_FORMAT_X509, SAML_NAME_ID_FORMAT_X509,
SHA1,
SHA256,
SHA384,
SHA512,
) )
@ -109,20 +118,23 @@ class SAMLSource(Source):
digest_algorithm = models.CharField( digest_algorithm = models.CharField(
max_length=50, max_length=50,
choices=( choices=(
("sha1", _("SHA1")), (SHA1, _("SHA1")),
("sha256", _("SHA256")), (SHA256, _("SHA256")),
(SHA384, _("SHA384")),
(SHA512, _("SHA512")),
), ),
default="sha256", default=SHA256,
) )
signature_algorithm = models.CharField( signature_algorithm = models.CharField(
max_length=50, max_length=50,
choices=( choices=(
("rsa-sha1", _("RSA-SHA1")), (RSA_SHA1, _("RSA-SHA1")),
("rsa-sha256", _("RSA-SHA256")), (RSA_SHA256, _("RSA-SHA256")),
("ecdsa-sha256", _("ECDSA-SHA256")), (RSA_SHA384, _("RSA-SHA384")),
("dsa-sha1", _("DSA-SHA1")), (RSA_SHA512, _("RSA-SHA512")),
(DSA_SHA1, _("DSA-SHA1")),
), ),
default="rsa-sha256", default=RSA_SHA256,
) )
@property @property

View File

@ -1,4 +1,6 @@
"""SAML Source processor constants""" """SAML Source processor constants"""
import xmlsec
NS_SAML_PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol" NS_SAML_PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
NS_SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion" NS_SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
NS_SAML_METADATA = "urn:oasis:names:tc:SAML:2.0:metadata" NS_SAML_METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
@ -27,3 +29,23 @@ RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
SHA512 = "http://www.w3.org/2001/04/xmlenc#sha512"
SIGN_ALGORITHM_TRANSFORM_MAP = {
DSA_SHA1: xmlsec.constants.TransformDsaSha1,
RSA_SHA1: xmlsec.constants.TransformRsaSha1,
RSA_SHA256: xmlsec.constants.TransformRsaSha256,
RSA_SHA384: xmlsec.constants.TransformRsaSha384,
RSA_SHA512: xmlsec.constants.TransformRsaSha512,
}
DIGEST_ALGORITHM_TRANSLATION_MAP = {
SHA1: xmlsec.constants.TransformSha1,
SHA256: xmlsec.constants.TransformSha256,
SHA384: xmlsec.constants.TransformSha384,
SHA512: xmlsec.constants.TransformSha512,
}

View File

@ -3,8 +3,8 @@ from typing import Iterator, Optional
from django.http import HttpRequest from django.http import HttpRequest
from lxml.etree import Element, SubElement, tostring # nosec from lxml.etree import Element, SubElement, tostring # nosec
from signxml.util import strip_pem_header
from passbook.providers.saml.utils.encoding import strip_pem_header
from passbook.sources.saml.models import SAMLSource from passbook.sources.saml.models import SAMLSource
from passbook.sources.saml.processors.constants import ( from passbook.sources.saml.processors.constants import (
NS_MAP, NS_MAP,

View File

@ -7,21 +7,17 @@ import xmlsec
from django.http import HttpRequest from django.http import HttpRequest
from lxml import etree # nosec from lxml import etree # nosec
from lxml.etree import Element # nosec from lxml.etree import Element # nosec
from signxml import XMLSigner
from passbook.providers.saml.utils import get_random_id from passbook.providers.saml.utils import get_random_id
from passbook.providers.saml.utils.encoding import deflate_and_base64_encode from passbook.providers.saml.utils.encoding import deflate_and_base64_encode
from passbook.providers.saml.utils.time import get_time_string from passbook.providers.saml.utils.time import get_time_string
from passbook.sources.saml.models import SAMLSource from passbook.sources.saml.models import SAMLSource
from passbook.sources.saml.processors.constants import ( from passbook.sources.saml.processors.constants import (
DSA_SHA1, DIGEST_ALGORITHM_TRANSLATION_MAP,
NS_MAP, NS_MAP,
NS_SAML_ASSERTION, NS_SAML_ASSERTION,
NS_SAML_PROTOCOL, NS_SAML_PROTOCOL,
RSA_SHA1, SIGN_ALGORITHM_TRANSFORM_MAP,
RSA_SHA256,
RSA_SHA384,
RSA_SHA512,
) )
SESSION_REQUEST_ID = "passbook_source_saml_request_id" SESSION_REQUEST_ID = "passbook_source_saml_request_id"
@ -71,6 +67,19 @@ class RequestProcessor:
auth_n_request.attrib["Version"] = "2.0" auth_n_request.attrib["Version"] = "2.0"
# Create issuer object # Create issuer object
auth_n_request.append(self.get_issuer()) auth_n_request.append(self.get_issuer())
if self.source.signing_kp:
sign_algorithm_transform = SIGN_ALGORITHM_TRANSFORM_MAP.get(
self.source.signature_algorithm, xmlsec.constants.TransformRsaSha1
)
signature = xmlsec.template.create(
auth_n_request,
xmlsec.constants.TransformExclC14N,
sign_algorithm_transform,
ns="ds", # type: ignore
)
auth_n_request.append(signature)
# Create NameID Policy Object # Create NameID Policy Object
auth_n_request.append(self.get_name_id_policy()) auth_n_request.append(self.get_name_id_policy())
return auth_n_request return auth_n_request
@ -81,16 +90,38 @@ class RequestProcessor:
auth_n_request = self.get_auth_n() auth_n_request = self.get_auth_n()
if self.source.signing_kp: if self.source.signing_kp:
signed_request = XMLSigner( xmlsec.tree.add_ids(auth_n_request, ["ID"])
c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#",
signature_algorithm=self.source.signature_algorithm, ctx = xmlsec.SignatureContext()
digest_algorithm=self.source.digest_algorithm,
).sign( key = xmlsec.Key.from_memory(
auth_n_request, self.source.signing_kp.key_data, xmlsec.constants.KeyDataFormatPem, None
cert=self.source.signing_kp.certificate_data,
key=self.source.signing_kp.key_data,
) )
return etree.tostring(signed_request).decode() key.load_cert_from_memory(
self.source.signing_kp.certificate_data,
xmlsec.constants.KeyDataFormatCertPem,
)
ctx.key = key
digest_algorithm_transform = DIGEST_ALGORITHM_TRANSLATION_MAP.get(
self.source.digest_algorithm, xmlsec.constants.TransformSha1
)
signature_node = xmlsec.tree.find_node(
auth_n_request, xmlsec.constants.NodeSignature
)
ref = xmlsec.template.add_reference(
signature_node,
digest_algorithm_transform,
uri="#" + auth_n_request.attrib["ID"],
)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformEnveloped)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformExclC14N)
key_info = xmlsec.template.ensure_key_info(signature_node)
xmlsec.template.add_x509_data(key_info)
ctx.sign(signature_node)
return etree.tostring(auth_n_request).decode() return etree.tostring(auth_n_request).decode()
@ -111,14 +142,7 @@ class RequestProcessor:
response_dict["RelayState"] = self.relay_state response_dict["RelayState"] = self.relay_state
if self.source.signing_kp: if self.source.signing_kp:
sign_algorithm_transform_map = { sign_algorithm_transform = SIGN_ALGORITHM_TRANSFORM_MAP.get(
DSA_SHA1: xmlsec.constants.TransformDsaSha1,
RSA_SHA1: xmlsec.constants.TransformRsaSha1,
RSA_SHA256: xmlsec.constants.TransformRsaSha256,
RSA_SHA384: xmlsec.constants.TransformRsaSha384,
RSA_SHA512: xmlsec.constants.TransformRsaSha512,
}
sign_algorithm_transform = sign_algorithm_transform_map.get(
self.source.signature_algorithm, xmlsec.constants.TransformRsaSha1 self.source.signature_algorithm, xmlsec.constants.TransformRsaSha1
) )

View File

@ -1,11 +1,12 @@
"""passbook saml source processor""" """passbook saml source processor"""
from typing import TYPE_CHECKING, Dict from base64 import b64decode
from typing import TYPE_CHECKING, Any, Dict
from defusedxml import ElementTree import xmlsec
from defusedxml.lxml import fromstring
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from signxml import XMLVerifier
from structlog import get_logger from structlog import get_logger
from passbook.core.models import User from passbook.core.models import User
@ -18,14 +19,15 @@ from passbook.flows.planner import (
from passbook.flows.views import SESSION_KEY_PLAN from passbook.flows.views import SESSION_KEY_PLAN
from passbook.lib.utils.urls import redirect_with_qs from passbook.lib.utils.urls import redirect_with_qs
from passbook.policies.utils import delete_none_keys from passbook.policies.utils import delete_none_keys
from passbook.providers.saml.utils.encoding import decode_base64_and_inflate
from passbook.sources.saml.exceptions import ( from passbook.sources.saml.exceptions import (
InvalidSignature,
MismatchedRequestID, MismatchedRequestID,
MissingSAMLResponse, MissingSAMLResponse,
UnsupportedNameIDFormat, UnsupportedNameIDFormat,
) )
from passbook.sources.saml.models import SAMLSource from passbook.sources.saml.models import SAMLSource
from passbook.sources.saml.processors.constants import ( from passbook.sources.saml.processors.constants import (
NS_MAP,
SAML_NAME_ID_FORMAT_EMAIL, SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT, SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT, SAML_NAME_ID_FORMAT_TRANSIENT,
@ -49,7 +51,7 @@ class ResponseProcessor:
_source: SAMLSource _source: SAMLSource
_root: "Element" _root: Any
_root_xml: str _root_xml: str
def __init__(self, source: SAMLSource): def __init__(self, source: SAMLSource):
@ -61,20 +63,36 @@ class ResponseProcessor:
raw_response = request.POST.get("SAMLResponse", None) raw_response = request.POST.get("SAMLResponse", None)
if not raw_response: if not raw_response:
raise MissingSAMLResponse("Request does not contain 'SAMLResponse'") raise MissingSAMLResponse("Request does not contain 'SAMLResponse'")
# relay_state = request.POST.get('RelayState', None)
# Check if response is compressed, b64 decode it # Check if response is compressed, b64 decode it
self._root_xml = decode_base64_and_inflate(raw_response) self._root_xml = b64decode(raw_response.encode()).decode()
self._root = ElementTree.fromstring(self._root_xml) self._root = fromstring(self._root_xml)
self._verify_signed() if self._source.signing_kp:
self._verify_signed()
self._verify_request_id(request) self._verify_request_id(request)
def _verify_signed(self): def _verify_signed(self):
"""Verify SAML Response's Signature""" """Verify SAML Response's Signature"""
verifier = XMLVerifier() signature_nodes = self._root.xpath(
verifier.verify( "/samlp:Response/saml:Assertion/ds:Signature", namespaces=NS_MAP
self._root_xml, x509_cert=self._source.signing_kp.certificate_data
) )
if len(signature_nodes) != 1:
raise InvalidSignature()
signature_node = signature_nodes[0]
xmlsec.tree.add_ids(self._root, ["ID"])
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
self._source.signing_kp.certificate_data,
xmlsec.constants.KeyDataFormatCertPem,
)
ctx.key = key
ctx.set_enabled_key_data([xmlsec.constants.KeyDataX509])
try:
ctx.verify(signature_node)
except (xmlsec.InternalError, xmlsec.VerificationError) as exc:
raise InvalidSignature from exc
LOGGER.debug("Successfully verified signautre") LOGGER.debug("Successfully verified signautre")
def _verify_request_id(self, request: HttpRequest): def _verify_request_id(self, request: HttpRequest):

View File

@ -8,7 +8,7 @@ from django.utils.http import urlencode
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from signxml import InvalidSignature from xmlsec import VerificationError
from passbook.lib.views import bad_request_message from passbook.lib.views import bad_request_message
from passbook.providers.saml.utils.encoding import nice64 from passbook.providers.saml.utils.encoding import nice64
@ -77,7 +77,7 @@ class ACSView(View):
processor.parse(request) processor.parse(request)
except MissingSAMLResponse as exc: except MissingSAMLResponse as exc:
return bad_request_message(request, str(exc)) return bad_request_message(request, str(exc))
except InvalidSignature as exc: except VerificationError as exc:
return bad_request_message(request, str(exc)) return bad_request_message(request, str(exc))
try: try:

View File

@ -7485,16 +7485,19 @@ definitions:
title: Digest algorithm title: Digest algorithm
type: string type: string
enum: enum:
- sha1 - http://www.w3.org/2000/09/xmldsig#sha1
- sha256 - http://www.w3.org/2001/04/xmlenc#sha256
- http://www.w3.org/2001/04/xmldsig-more#sha384
- http://www.w3.org/2001/04/xmlenc#sha512
signature_algorithm: signature_algorithm:
title: Signature algorithm title: Signature algorithm
type: string type: string
enum: enum:
- rsa-sha1 - http://www.w3.org/2000/09/xmldsig#rsa-sha1
- rsa-sha256 - http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
- ecdsa-sha256 - http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
- dsa-sha1 - http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
- http://www.w3.org/2000/09/xmldsig#dsa-sha1
signing_kp: signing_kp:
title: Signing Keypair title: Signing Keypair
description: Keypair used to sign outgoing Responses going to the Service description: Keypair used to sign outgoing Responses going to the Service
@ -7779,7 +7782,6 @@ definitions:
- name - name
- slug - slug
- sso_url - sso_url
- signing_kp
type: object type: object
properties: properties:
name: name:
@ -7851,6 +7853,30 @@ definitions:
- REDIRECT - REDIRECT
- POST - POST
- POST_AUTO - POST_AUTO
signing_kp:
title: Singing Keypair
description: Keypair which is used to sign outgoing requests. Leave empty
to disable signing.
type: string
format: uuid
x-nullable: true
digest_algorithm:
title: Digest algorithm
type: string
enum:
- http://www.w3.org/2000/09/xmldsig#sha1
- http://www.w3.org/2001/04/xmlenc#sha256
- http://www.w3.org/2001/04/xmldsig-more#sha384
- http://www.w3.org/2001/04/xmlenc#sha512
signature_algorithm:
title: Signature algorithm
type: string
enum:
- http://www.w3.org/2000/09/xmldsig#rsa-sha1
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
- http://www.w3.org/2000/09/xmldsig#dsa-sha1
temporary_user_delete_after: temporary_user_delete_after:
title: Delete temporary users after title: Delete temporary users after
description: "Time offset when temporary users should be deleted. This only\ description: "Time offset when temporary users should be deleted. This only\
@ -7858,11 +7884,6 @@ definitions:
\ log out manually. (Format: hours=1;minutes=2;seconds=3)." \ log out manually. (Format: hours=1;minutes=2;seconds=3)."
type: string type: string
minLength: 1 minLength: 1
signing_kp:
title: Singing Keypair
description: Keypair which is used to sign outgoing requests.
type: string
format: uuid
Stage: Stage:
description: Stage Serializer description: Stage Serializer
required: required: