This commit is contained in:
Jens L 2020-09-11 23:21:11 +02:00 committed by GitHub
parent 3f5d30e6fe
commit 23cccebb96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 403 additions and 254 deletions

View File

@ -1,7 +1,7 @@
all: lint-fix lint coverage gen all: lint-fix lint coverage gen
coverage: coverage:
coverage run --concurrency=multiprocessing manage.py test passbook --failfast coverage run --concurrency=multiprocessing manage.py test --failfast
coverage combine coverage combine
coverage html coverage html
coverage report coverage report

View File

@ -59,5 +59,6 @@ docker = "*"
pylint = "*" pylint = "*"
pylint-django = "*" pylint-django = "*"
selenium = "*" selenium = "*"
unittest-xml-reporting = "*"
prospector = "*" prospector = "*"
pytest = "*"
pytest-django = "*"

167
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "a798bbd0b97857cac136c1743b8d6ad8bf8c3d95e2760c71d324bb2a7f47f678" "sha256": "80570636236962f4b934a884817292de9f7bb48520aa964afc2959b0f795fb57"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -28,6 +28,7 @@
"sha256:70cdb10628468ff14e57ec2f751c7aa9e48e7e3651cfd62d431213c0c4e58f21", "sha256:70cdb10628468ff14e57ec2f751c7aa9e48e7e3651cfd62d431213c0c4e58f21",
"sha256:aa7f313fb887c91f15474c1229907a04dac0b8135822d6603437803424c0aa59" "sha256:aa7f313fb887c91f15474c1229907a04dac0b8135822d6603437803424c0aa59"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.6.1" "version": "==2.6.1"
}, },
"asgiref": { "asgiref": {
@ -35,6 +36,7 @@
"sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a", "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
"sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed" "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
], ],
"markers": "python_version >= '3.5'",
"version": "==3.2.10" "version": "==3.2.10"
}, },
"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:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594",
"sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.2.0" "version": "==20.2.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": {
@ -92,6 +97,7 @@
"sha256:513d4ff98dd27f85743a8dc0e92f55ddb1b49e060c2d5961512855cda2c01a98", "sha256:513d4ff98dd27f85743a8dc0e92f55ddb1b49e060c2d5961512855cda2c01a98",
"sha256:bbaa39c3dede00175df2dc2b03d0cf18dd2d32a7de7beb68072d13043c9edb20" "sha256:bbaa39c3dede00175df2dc2b03d0cf18dd2d32a7de7beb68072d13043c9edb20"
], ],
"markers": "python_version ~= '3.5'",
"version": "==4.1.1" "version": "==4.1.1"
}, },
"celery": { "celery": {
@ -170,6 +176,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"
}, },
"constantly": { "constantly": {
@ -338,6 +345,7 @@
"sha256:6dd02d5a4bd2516fb93f80360673bf540c3b6641fec8766b1da2870a5aa00b32", "sha256:6dd02d5a4bd2516fb93f80360673bf540c3b6641fec8766b1da2870a5aa00b32",
"sha256:8b1ac62c581dbc5799b03e535854b92fc4053ecfe74bad3f9c05782063d4196b" "sha256:8b1ac62c581dbc5799b03e535854b92fc4053ecfe74bad3f9c05782063d4196b"
], ],
"markers": "python_version >= '3.5'",
"version": "==3.11.1" "version": "==3.11.1"
}, },
"djangorestframework-guardian": { "djangorestframework-guardian": {
@ -354,6 +362,7 @@
"sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827",
"sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.15.2" "version": "==0.15.2"
}, },
"drf-yasg": { "drf-yasg": {
@ -383,6 +392,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": {
@ -390,6 +400,7 @@
"sha256:bcbd9f970e7144fe933908aa286d7a12c44b7deb6d78a76871f0377a29d09789", "sha256:bcbd9f970e7144fe933908aa286d7a12c44b7deb6d78a76871f0377a29d09789",
"sha256:f4d5093f13b1b1c0a434ab1dc851cd26a983f86a4d75c95239974e33ed406a87" "sha256:f4d5093f13b1b1c0a434ab1dc851cd26a983f86a4d75c95239974e33ed406a87"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.21.1" "version": "==1.21.1"
}, },
"gunicorn": { "gunicorn": {
@ -456,6 +467,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": {
@ -502,6 +514,7 @@
"sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417",
"sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2" "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"
], ],
"markers": "python_version >= '3.5'",
"version": "==0.5.1" "version": "==0.5.1"
}, },
"itypes": { "itypes": {
@ -516,6 +529,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": {
@ -523,6 +537,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": {
@ -537,6 +552,7 @@
"sha256:be48cdffb54a2194d93ad6533d73f69408486483d189fe9f5990ee24255b0e0a", "sha256:be48cdffb54a2194d93ad6533d73f69408486483d189fe9f5990ee24255b0e0a",
"sha256:ca1b45faac8c0b18493d02a8571792f3c40291cf2bcf1f55afed3d8f3aa7ba74" "sha256:ca1b45faac8c0b18493d02a8571792f3c40291cf2bcf1f55afed3d8f3aa7ba74"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.6.11" "version": "==4.6.11"
}, },
"kubernetes": { "kubernetes": {
@ -549,8 +565,11 @@
}, },
"ldap3": { "ldap3": {
"hashes": [ "hashes": [
"sha256:10bdd23b612e942ce90ea4dbc744dfd88735949833e46c5467a2dcf68e60f469",
"sha256:37d633e20fa360c302b1263c96fe932d40622d0119f1bddcb829b03462eeeeb7", "sha256:37d633e20fa360c302b1263c96fe932d40622d0119f1bddcb829b03462eeeeb7",
"sha256:7c3738570766f5e5e74a56fade15470f339d5c436d821cf476ef27da0a4de8b0" "sha256:7c3738570766f5e5e74a56fade15470f339d5c436d821cf476ef27da0a4de8b0",
"sha256:8f59a7b5399555b22db06f153daa76c77ded2dd84bc0f0ffe5b0b33901b6eac4",
"sha256:bed71c6ce2f70a00a330eed0c8370664c065239d45bcbe1b82517b6f6eed7f25"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.8.1" "version": "==2.8.1"
@ -628,6 +647,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": {
@ -658,6 +678,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": {
@ -715,15 +736,37 @@
}, },
"pyasn1": { "pyasn1": {
"hashes": [ "hashes": [
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba" "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
], ],
"version": "==0.4.8" "version": "==0.4.8"
}, },
"pyasn1-modules": { "pyasn1-modules": {
"hashes": [ "hashes": [
"sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
"sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
"sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
"sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
"sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74" "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
"sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb",
"sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
"sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
"sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
"sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
"sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"
], ],
"version": "==0.2.8" "version": "==0.2.8"
}, },
@ -732,6 +775,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": {
@ -803,6 +847,7 @@
"sha256:ea4d4b58f9bc34e224ef4b4604a6be03d72ef1f8c486391f970205f6733dbc46", "sha256:ea4d4b58f9bc34e224ef4b4604a6be03d72ef1f8c486391f970205f6733dbc46",
"sha256:f60b3484ce4be04f5da3777c51c5140d3fe21cdd6674f2b6568f41c8130bcdeb" "sha256:f60b3484ce4be04f5da3777c51c5140d3fe21cdd6674f2b6568f41c8130bcdeb"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.9.8" "version": "==3.9.8"
}, },
"pyhamcrest": { "pyhamcrest": {
@ -810,6 +855,7 @@
"sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316", "sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316",
"sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29" "sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29"
], ],
"markers": "python_version >= '3.5'",
"version": "==2.0.2" "version": "==2.0.2"
}, },
"pyjwkest": { "pyjwkest": {
@ -831,19 +877,21 @@
"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:27515d2d5db0629c7dadf6fbe76973eb56f098c1b01d36de42eb69220d2c19e4" "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3"
], ],
"version": "==0.17.2" "version": "==0.16.0"
}, },
"python-dateutil": { "python-dateutil": {
"hashes": [ "hashes": [
"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"
}, },
"pytz": { "pytz": {
@ -883,6 +931,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": {
@ -890,12 +939,14 @@
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.24.0" "version": "==2.24.0"
}, },
"requests-oauthlib": { "requests-oauthlib": {
"hashes": [ "hashes": [
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a" "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
"sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.3.0" "version": "==1.3.0"
@ -940,7 +991,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": {
@ -979,6 +1030,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"
}, },
"sqlparse": { "sqlparse": {
@ -986,6 +1038,7 @@
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.3.1" "version": "==0.3.1"
}, },
"structlog": { "structlog": {
@ -1033,6 +1086,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": {
@ -1040,6 +1094,7 @@
"sha256:17938f2bca4a9cabce61346758e482ca4e600160cbc28e861493eac74a19539d", "sha256:17938f2bca4a9cabce61346758e482ca4e600160cbc28e861493eac74a19539d",
"sha256:38a469daf93c37e5527cb062653d6393ae11663147c42fab7ddc3f6d00d434ae" "sha256:38a469daf93c37e5527cb062653d6393ae11663147c42fab7ddc3f6d00d434ae"
], ],
"markers": "python_version >= '3.5'",
"version": "==20.4.1" "version": "==20.4.1"
}, },
"uritemplate": { "uritemplate": {
@ -1047,6 +1102,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": {
@ -1058,7 +1114,6 @@
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
], ],
"index": "pypi", "index": "pypi",
"markers": null,
"version": "==1.25.10" "version": "==1.25.10"
}, },
"uvicorn": { "uvicorn": {
@ -1089,6 +1144,7 @@
"sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87", "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87",
"sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af" "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.3.0" "version": "==1.3.0"
}, },
"websocket-client": { "websocket-client": {
@ -1123,6 +1179,7 @@
"sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36",
"sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b" "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"
], ],
"markers": "python_full_version >= '3.6.1'",
"version": "==8.1" "version": "==8.1"
}, },
"zope.interface": { "zope.interface": {
@ -1168,6 +1225,7 @@
"sha256:f68bf937f113b88c866d090fea0bc52a098695173fc613b055a17ff0cf9683b6", "sha256:f68bf937f113b88c866d090fea0bc52a098695173fc613b055a17ff0cf9683b6",
"sha256:fb55c182a3f7b84c1a2d6de5fa7b1a05d4660d866b91dbf8d74549c57a1499e8" "sha256:fb55c182a3f7b84c1a2d6de5fa7b1a05d4660d866b91dbf8d74549c57a1499e8"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==5.1.0" "version": "==5.1.0"
} }
}, },
@ -1184,6 +1242,7 @@
"sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a", "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
"sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed" "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
], ],
"markers": "python_version >= '3.5'",
"version": "==3.2.10" "version": "==3.2.10"
}, },
"astroid": { "astroid": {
@ -1191,6 +1250,7 @@
"sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1", "sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1",
"sha256:d8506842a3faf734b81599c8b98dcc423de863adcc1999248480b18bd31a0f38" "sha256:d8506842a3faf734b81599c8b98dcc423de863adcc1999248480b18bd31a0f38"
], ],
"markers": "python_version >= '3.5'",
"version": "==2.4.1" "version": "==2.4.1"
}, },
"attrs": { "attrs": {
@ -1198,6 +1258,7 @@
"sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594", "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594",
"sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc" "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.2.0" "version": "==20.2.0"
}, },
"autopep8": { "autopep8": {
@ -1228,6 +1289,7 @@
"sha256:477f0e18a0d58e50bb3dbc9af7fcda464fd0ebfc7a6151d8888602d7153171a0", "sha256:477f0e18a0d58e50bb3dbc9af7fcda464fd0ebfc7a6151d8888602d7153171a0",
"sha256:cd4f3a231305e405ed8944d8ff35bd742d9bc740ad62f483bd0ca21ce7131984" "sha256:cd4f3a231305e405ed8944d8ff35bd742d9bc740ad62f483bd0ca21ce7131984"
], ],
"markers": "python_version >= '3.5'",
"version": "==1.0.0" "version": "==1.0.0"
}, },
"bumpversion": { "bumpversion": {
@ -1257,6 +1319,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": {
@ -1343,6 +1406,7 @@
"sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c", "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c",
"sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208" "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.8.3" "version": "==3.8.3"
}, },
"flake8-polyfill": { "flake8-polyfill": {
@ -1357,6 +1421,7 @@
"sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac",
"sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9" "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"
], ],
"markers": "python_version >= '3.4'",
"version": "==4.0.5" "version": "==4.0.5"
}, },
"gitpython": { "gitpython": {
@ -1364,6 +1429,7 @@
"sha256:080bf8e2cf1a2b907634761c2eaefbe83b69930c94c66ad11b65a8252959f912", "sha256:080bf8e2cf1a2b907634761c2eaefbe83b69930c94c66ad11b65a8252959f912",
"sha256:1858f4fd089abe92ae465f01d5aaaf55e937eca565fb2c1fce35a51b5f85c910" "sha256:1858f4fd089abe92ae465f01d5aaaf55e937eca565fb2c1fce35a51b5f85c910"
], ],
"markers": "python_version >= '3.4'",
"version": "==3.1.8" "version": "==3.1.8"
}, },
"idna": { "idna": {
@ -1373,11 +1439,19 @@
], ],
"version": "==2.10" "version": "==2.10"
}, },
"iniconfig": {
"hashes": [
"sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437",
"sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"
],
"version": "==1.0.1"
},
"isort": { "isort": {
"hashes": [ "hashes": [
"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": {
@ -1404,6 +1478,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": {
@ -1413,6 +1488,22 @@
], ],
"version": "==0.6.1" "version": "==0.6.1"
}, },
"more-itertools": {
"hashes": [
"sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20",
"sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"
],
"markers": "python_version >= '3.5'",
"version": "==8.5.0"
},
"packaging": {
"hashes": [
"sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
"sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
],
"index": "pypi",
"version": "==20.4"
},
"pathspec": { "pathspec": {
"hashes": [ "hashes": [
"sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0", "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0",
@ -1425,6 +1516,7 @@
"sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea", "sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea",
"sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15" "sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15"
], ],
"markers": "python_version >= '2.6'",
"version": "==5.5.0" "version": "==5.5.0"
}, },
"pep8-naming": { "pep8-naming": {
@ -1434,6 +1526,14 @@
], ],
"version": "==0.10.0" "version": "==0.10.0"
}, },
"pluggy": {
"hashes": [
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1"
},
"prospector": { "prospector": {
"hashes": [ "hashes": [
"sha256:43e5e187c027336b0e4c4aa6a82d66d3b923b5ec5b51968126132e32f9d14a2f" "sha256:43e5e187c027336b0e4c4aa6a82d66d3b923b5ec5b51968126132e32f9d14a2f"
@ -1441,11 +1541,20 @@
"index": "pypi", "index": "pypi",
"version": "==1.3.0" "version": "==1.3.0"
}, },
"py": {
"hashes": [
"sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
"sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.9.0"
},
"pycodestyle": { "pycodestyle": {
"hashes": [ "hashes": [
"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": {
@ -1453,6 +1562,7 @@
"sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325", "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325",
"sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678" "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"
], ],
"markers": "python_version >= '3.5'",
"version": "==5.1.1" "version": "==5.1.1"
}, },
"pyflakes": { "pyflakes": {
@ -1460,6 +1570,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": {
@ -1497,6 +1608,30 @@
], ],
"version": "==0.6" "version": "==0.6"
}, },
"pyparsing": {
"hashes": [
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pytest": {
"hashes": [
"sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4",
"sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"
],
"index": "pypi",
"version": "==6.0.1"
},
"pytest-django": {
"hashes": [
"sha256:64f99d565dd9497af412fcab2989fe40982c1282d4118ff422b407f3f7275ca5",
"sha256:664e5f42242e5e182519388f01b9f25d824a9feb7cd17d8f863c8d776f38baf9"
],
"index": "pypi",
"version": "==3.9.0"
},
"pytz": { "pytz": {
"hashes": [ "hashes": [
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
@ -1552,6 +1687,7 @@
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.24.0" "version": "==2.24.0"
}, },
"requirements-detector": { "requirements-detector": {
@ -1579,6 +1715,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": {
@ -1586,6 +1723,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": {
@ -1600,6 +1738,7 @@
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.3.1" "version": "==0.3.1"
}, },
"stevedore": { "stevedore": {
@ -1607,6 +1746,7 @@
"sha256:a34086819e2c7a7f86d5635363632829dab8014e5fd7be2454c7cba84ac7514e", "sha256:a34086819e2c7a7f86d5635363632829dab8014e5fd7be2454c7cba84ac7514e",
"sha256:ddc09a744dc224c84ec8e8efcb70595042d21c97c76df60daee64c9ad53bc7ee" "sha256:ddc09a744dc224c84ec8e8efcb70595042d21c97c76df60daee64c9ad53bc7ee"
], ],
"markers": "python_version >= '3.6'",
"version": "==3.2.1" "version": "==3.2.1"
}, },
"toml": { "toml": {
@ -1642,14 +1782,6 @@
], ],
"version": "==1.4.1" "version": "==1.4.1"
}, },
"unittest-xml-reporting": {
"hashes": [
"sha256:7bf515ea8cb244255a25100cd29db611a73f8d3d0aaf672ed3266307e14cc1ca",
"sha256:984cebba69e889401bfe3adb9088ca376b3a1f923f0590d005126c1bffd1a695"
],
"index": "pypi",
"version": "==3.0.4"
},
"urllib3": { "urllib3": {
"extras": [ "extras": [
"secure" "secure"
@ -1659,7 +1791,6 @@
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
], ],
"index": "pypi", "index": "pypi",
"markers": null,
"version": "==1.25.10" "version": "==1.25.10"
}, },
"websocket-client": { "websocket-client": {

View File

@ -2,7 +2,7 @@ version: '3.7'
services: services:
chrome: chrome:
image: selenium/standalone-chrome-debug:3.141.59-20200525 image: selenium/standalone-chrome-debug:3.141.59-20200719
volumes: volumes:
- /dev/shm:/dev/shm - /dev/shm:/dev/shm
network_mode: host network_mode: host

View File

@ -1,13 +1,12 @@
"""Test Enroll flow""" """Test Enroll flow"""
from time import sleep from sys import platform
from typing import Any, Dict, Optional
from unittest.case import skipUnless
from django.test import override_settings from django.test import override_settings
from docker import DockerClient, from_env
from docker.models.containers import Container
from docker.types import Healthcheck from docker.types import Healthcheck
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support import expected_conditions as ec
from structlog import get_logger
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
@ -18,41 +17,23 @@ from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage
from passbook.stages.user_login.models import UserLoginStage from passbook.stages.user_login.models import UserLoginStage
from passbook.stages.user_write.models import UserWriteStage from passbook.stages.user_write.models import UserWriteStage
LOGGER = get_logger()
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestFlowsEnroll(SeleniumTestCase): class TestFlowsEnroll(SeleniumTestCase):
"""Test Enroll flow""" """Test Enroll flow"""
def setUp(self): def get_container_specs(self) -> Optional[Dict[str, Any]]:
self.container = self.setup_client() return {
super().setUp() "image": "mailhog/mailhog:v1.0.1",
"detach": True,
def setup_client(self) -> Container: "network_mode": "host",
"""Setup test IdP container""" "auto_remove": True,
client: DockerClient = from_env() "healthcheck": Healthcheck(
container = client.containers.run(
image="mailhog/mailhog:v1.0.1",
detach=True,
network_mode="host",
auto_remove=True,
healthcheck=Healthcheck(
test=["CMD", "wget", "--spider", "http://localhost:8025"], test=["CMD", "wget", "--spider", "http://localhost:8025"],
interval=5 * 100 * 1000000, interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000, start_period=1 * 100 * 1000000,
), ),
) }
while True:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
LOGGER.info("Container failed healthcheck")
sleep(1)
def tearDown(self):
self.container.kill()
super().tearDown()
def test_enroll_2_step(self): def test_enroll_2_step(self):
"""Test 2-step enroll flow""" """Test 2-step enroll flow"""
@ -220,21 +201,25 @@ class TestFlowsEnroll(SeleniumTestCase):
self.driver.find_element(By.ID, "id_name").send_keys("some name") self.driver.find_element(By.ID, "id_name").send_keys("some name")
self.driver.find_element(By.ID, "id_email").send_keys("foo@bar.baz") self.driver.find_element(By.ID, "id_email").send_keys("foo@bar.baz")
self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click()
sleep(3) # Wait for the success message so we know the email is sent
self.wait.until(
ec.presence_of_element_located((By.CSS_SELECTOR, ".pf-c-form > p"))
)
# Open Mailhog # Open Mailhog
self.driver.get("http://localhost:8025") self.driver.get("http://localhost:8025")
# Click on first message # Click on first message
self.wait.until(
ec.presence_of_element_located((By.CLASS_NAME, "msglist-message"))
)
self.driver.find_element(By.CLASS_NAME, "msglist-message").click() self.driver.find_element(By.CLASS_NAME, "msglist-message").click()
sleep(3)
self.driver.switch_to.frame(self.driver.find_element(By.CLASS_NAME, "tab-pane")) self.driver.switch_to.frame(self.driver.find_element(By.CLASS_NAME, "tab-pane"))
self.driver.find_element(By.ID, "confirm").click() self.driver.find_element(By.ID, "confirm").click()
self.driver.close() self.driver.close()
self.driver.switch_to.window(self.driver.window_handles[0]) self.driver.switch_to.window(self.driver.window_handles[0])
# We're now logged in # We're now logged in
sleep(3)
self.wait.until( self.wait.until(
ec.presence_of_element_located( ec.presence_of_element_located(
(By.XPATH, "//a[contains(@href, '/-/user/')]") (By.XPATH, "//a[contains(@href, '/-/user/')]")

View File

@ -1,10 +1,14 @@
"""test default login flow""" """test default login flow"""
from sys import platform
from unittest.case import skipUnless
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestFlowsLogin(SeleniumTestCase): class TestFlowsLogin(SeleniumTestCase):
"""test default login flow""" """test default login flow"""

View File

@ -1,7 +1,6 @@
"""test stage setup flows (password change)""" """test stage setup flows (password change)"""
import string from sys import platform
from random import SystemRandom from unittest.case import skipUnless
from time import sleep
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
@ -9,9 +8,11 @@ from selenium.webdriver.common.keys import Keys
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.models import Flow, FlowDesignation from passbook.flows.models import Flow, FlowDesignation
from passbook.providers.oauth2.generators import generate_client_secret
from passbook.stages.password.models import PasswordStage from passbook.stages.password.models import PasswordStage
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestFlowsStageSetup(SeleniumTestCase): class TestFlowsStageSetup(SeleniumTestCase):
"""test stage setup flows""" """test stage setup flows"""
@ -27,10 +28,7 @@ class TestFlowsStageSetup(SeleniumTestCase):
stage.change_flow = flow stage.change_flow = flow
stage.save() stage.save()
new_password = "".join( new_password = generate_client_secret()
SystemRandom().choice(string.ascii_uppercase + string.digits)
for _ in range(8)
)
self.driver.get( self.driver.get(
f"{self.live_server_url}/flows/default-authentication-flow/?next=%2F" f"{self.live_server_url}/flows/default-authentication-flow/?next=%2F"
@ -48,7 +46,7 @@ class TestFlowsStageSetup(SeleniumTestCase):
self.driver.find_element(By.ID, "id_password_repeat").send_keys(new_password) self.driver.find_element(By.ID, "id_password_repeat").send_keys(new_password)
self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click() self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click()
sleep(2) self.wait_for_url(self.url("passbook_core:user-settings"))
# Because USER() is cached, we need to get the user manually here # Because USER() is cached, we need to get the user manually here
user = User.objects.get(username=USER().username) user = User.objects.get(username=USER().username)
self.assertTrue(user.check_password(new_password)) self.assertTrue(user.check_password(new_password))

View File

@ -1,12 +1,11 @@
"""test OAuth Provider flow""" """test OAuth Provider flow"""
from time import sleep from sys import platform
from typing import Any, Dict, Optional
from unittest.case import skipUnless
from docker import DockerClient, from_env
from docker.models.containers import Container
from docker.types import Healthcheck from docker.types import Healthcheck
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from structlog import get_logger
from e2e.utils import USER, SeleniumTestCase from e2e.utils import USER, SeleniumTestCase
from passbook.core.models import Application from passbook.core.models import Application
@ -19,32 +18,29 @@ from passbook.providers.oauth2.generators import (
) )
from passbook.providers.oauth2.models import ClientTypes, OAuth2Provider, ResponseTypes from passbook.providers.oauth2.models import ClientTypes, OAuth2Provider, ResponseTypes
LOGGER = get_logger()
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestProviderOAuth2Github(SeleniumTestCase): class TestProviderOAuth2Github(SeleniumTestCase):
"""test OAuth Provider flow""" """test OAuth Provider flow"""
def setUp(self): def setUp(self):
self.client_id = generate_client_id() self.client_id = generate_client_id()
self.client_secret = generate_client_secret() self.client_secret = generate_client_secret()
self.container = self.setup_client()
super().setUp() super().setUp()
def setup_client(self) -> Container: def get_container_specs(self) -> Optional[Dict[str, Any]]:
"""Setup client grafana container which we test OAuth against""" """Setup client grafana container which we test OAuth against"""
client: DockerClient = from_env() return {
container = client.containers.run( "image": "grafana/grafana:7.1.0",
image="grafana/grafana:7.1.0", "detach": True,
detach=True, "network_mode": "host",
network_mode="host", "auto_remove": True,
auto_remove=True, "healthcheck": Healthcheck(
healthcheck=Healthcheck(
test=["CMD", "wget", "--spider", "http://localhost:3000"], test=["CMD", "wget", "--spider", "http://localhost:3000"],
interval=5 * 100 * 1000000, interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000, start_period=1 * 100 * 1000000,
), ),
environment={ "environment": {
"GF_AUTH_GITHUB_ENABLED": "true", "GF_AUTH_GITHUB_ENABLED": "true",
"GF_AUTH_GITHUB_ALLOW_SIGN_UP": "true", "GF_AUTH_GITHUB_ALLOW_SIGN_UP": "true",
"GF_AUTH_GITHUB_CLIENT_ID": self.client_id, "GF_AUTH_GITHUB_CLIENT_ID": self.client_id,
@ -61,22 +57,10 @@ class TestProviderOAuth2Github(SeleniumTestCase):
), ),
"GF_LOG_LEVEL": "debug", "GF_LOG_LEVEL": "debug",
}, },
) }
while True:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
LOGGER.info("Container failed healthcheck")
sleep(1)
def tearDown(self):
self.container.kill()
super().tearDown()
def test_authorization_consent_implied(self): def test_authorization_consent_implied(self):
"""test OAuth Provider flow (default authorization flow with implied consent)""" """test OAuth Provider flow (default authorization flow with implied consent)"""
sleep(1)
# Bootstrap all needed objects # Bootstrap all needed objects
authorization_flow = Flow.objects.get( authorization_flow = Flow.objects.get(
slug="default-provider-authorization-implicit-consent" slug="default-provider-authorization-implicit-consent"
@ -129,7 +113,6 @@ class TestProviderOAuth2Github(SeleniumTestCase):
def test_authorization_consent_explicit(self): def test_authorization_consent_explicit(self):
"""test OAuth Provider flow (default authorization flow with explicit consent)""" """test OAuth Provider flow (default authorization flow with explicit consent)"""
sleep(1)
# Bootstrap all needed objects # Bootstrap all needed objects
authorization_flow = Flow.objects.get( authorization_flow = Flow.objects.get(
slug="default-provider-authorization-explicit-consent" slug="default-provider-authorization-explicit-consent"
@ -167,8 +150,13 @@ class TestProviderOAuth2Github(SeleniumTestCase):
By.XPATH, "/html/body/div[2]/div/main/div/form/div[2]/ul/li[1]" By.XPATH, "/html/body/div[2]/div/main/div/form/div[2]/ul/li[1]"
).text, ).text,
) )
sleep(1) self.driver.find_element(
self.driver.find_element(By.CSS_SELECTOR, "[type=submit]").click() By.CSS_SELECTOR,
(
"form[action='/flows/b/default-provider-authorization-explicit-consent/'] "
"[type=submit]"
),
).click()
self.wait_for_url("http://localhost:3000/?orgId=1") self.wait_for_url("http://localhost:3000/?orgId=1")
self.driver.find_element(By.XPATH, "//a[contains(@href, '/profile')]").click() self.driver.find_element(By.XPATH, "//a[contains(@href, '/profile')]").click()
@ -197,7 +185,6 @@ class TestProviderOAuth2Github(SeleniumTestCase):
def test_denied(self): def test_denied(self):
"""test OAuth Provider flow (default authorization flow, denied)""" """test OAuth Provider flow (default authorization flow, denied)"""
sleep(1)
# Bootstrap all needed objects # Bootstrap all needed objects
authorization_flow = Flow.objects.get( authorization_flow = Flow.objects.get(
slug="default-provider-authorization-explicit-consent" slug="default-provider-authorization-explicit-consent"

View File

@ -1,8 +1,9 @@
"""test OAuth2 OpenID Provider flow""" """test OAuth2 OpenID Provider flow"""
from sys import platform
from time import sleep from time import sleep
from typing import Any, Dict, Optional
from unittest.case import skipUnless
from docker import DockerClient, from_env
from docker.models.containers import Container
from docker.types import Healthcheck from docker.types import Healthcheck
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
@ -34,29 +35,27 @@ from passbook.providers.oauth2.models import (
LOGGER = get_logger() LOGGER = get_logger()
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestProviderOAuth2OIDC(SeleniumTestCase): class TestProviderOAuth2OIDC(SeleniumTestCase):
"""test OAuth with OpenID Provider flow""" """test OAuth with OpenID Provider flow"""
def setUp(self): def setUp(self):
self.client_id = generate_client_id() self.client_id = generate_client_id()
self.client_secret = generate_client_secret() self.client_secret = generate_client_secret()
self.container = self.setup_client()
super().setUp() super().setUp()
def setup_client(self) -> Container: def get_container_specs(self) -> Optional[Dict[str, Any]]:
"""Setup client grafana container which we test OIDC against""" return {
client: DockerClient = from_env() "image": "grafana/grafana:7.1.0",
container = client.containers.run( "detach": True,
image="grafana/grafana:7.1.0", "network_mode": "host",
detach=True, "auto_remove": True,
network_mode="host", "healthcheck": Healthcheck(
auto_remove=True,
healthcheck=Healthcheck(
test=["CMD", "wget", "--spider", "http://localhost:3000"], test=["CMD", "wget", "--spider", "http://localhost:3000"],
interval=5 * 100 * 1000000, interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000, start_period=1 * 100 * 1000000,
), ),
environment={ "environment": {
"GF_AUTH_GENERIC_OAUTH_ENABLED": "true", "GF_AUTH_GENERIC_OAUTH_ENABLED": "true",
"GF_AUTH_GENERIC_OAUTH_CLIENT_ID": self.client_id, "GF_AUTH_GENERIC_OAUTH_CLIENT_ID": self.client_id,
"GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET": self.client_secret, "GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET": self.client_secret,
@ -72,18 +71,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
), ),
"GF_LOG_LEVEL": "debug", "GF_LOG_LEVEL": "debug",
}, },
) }
while True:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
LOGGER.info("Container failed healthcheck")
sleep(1)
def tearDown(self):
self.container.kill()
super().tearDown()
def test_redirect_uri_error(self): def test_redirect_uri_error(self):
"""test OpenID Provider flow (invalid redirect URI, check error message)""" """test OpenID Provider flow (invalid redirect URI, check error message)"""

View File

@ -1,5 +1,7 @@
"""test SAML Provider flow""" """test SAML Provider flow"""
from sys import platform
from time import sleep from time import sleep
from unittest.case import skipUnless
from docker import DockerClient, from_env from docker import DockerClient, from_env
from docker.models.containers import Container from docker.models.containers import Container
@ -23,6 +25,7 @@ from passbook.providers.saml.models import (
LOGGER = get_logger() LOGGER = get_logger()
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestProviderSAML(SeleniumTestCase): class TestProviderSAML(SeleniumTestCase):
"""test SAML Provider flow""" """test SAML Provider flow"""
@ -60,10 +63,6 @@ class TestProviderSAML(SeleniumTestCase):
LOGGER.info("Container failed healthcheck") LOGGER.info("Container failed healthcheck")
sleep(1) sleep(1)
def tearDown(self):
self.container.kill()
super().tearDown()
def test_sp_initiated_implicit(self): def test_sp_initiated_implicit(self):
"""test SAML Provider flow SP-initiated flow (implicit consent)""" """test SAML Provider flow SP-initiated flow (implicit consent)"""
# Bootstrap all needed objects # Bootstrap all needed objects

View File

@ -1,5 +1,7 @@
"""test SAML Source""" """test SAML Source"""
from sys import platform
from time import sleep from time import sleep
from unittest.case import skipUnless
from docker import DockerClient, from_env from docker import DockerClient, from_env
from docker.models.containers import Container from docker.models.containers import Container
@ -68,6 +70,7 @@ Sm75WXsflOxuTn08LbgGc4s=
-----END PRIVATE KEY-----""" -----END PRIVATE KEY-----"""
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestSourceSAML(SeleniumTestCase): class TestSourceSAML(SeleniumTestCase):
"""test SAML Source flow""" """test SAML Source flow"""
@ -103,10 +106,6 @@ class TestSourceSAML(SeleniumTestCase):
LOGGER.info("Container failed healthcheck") LOGGER.info("Container failed healthcheck")
sleep(1) sleep(1)
def tearDown(self):
self.container.kill()
super().tearDown()
def test_idp_redirect(self): def test_idp_redirect(self):
"""test SAML Source With redirect binding""" """test SAML Source With redirect binding"""
sleep(1) sleep(1)

View File

@ -1,8 +1,9 @@
"""test OAuth Source""" """test OAuth Source"""
from os.path import abspath from os.path import abspath
from time import sleep from sys import platform
from typing import Any, Dict, Optional
from unittest.case import skipUnless
from docker import DockerClient, from_env
from docker.models.containers import Container from docker.models.containers import Container
from docker.types import Healthcheck from docker.types import Healthcheck
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
@ -21,6 +22,7 @@ CONFIG_PATH = "/tmp/dex.yml"
LOGGER = get_logger() LOGGER = get_logger()
@skipUnless(platform.startswith("linux"), "requires local docker")
class TestSourceOAuth(SeleniumTestCase): class TestSourceOAuth(SeleniumTestCase):
"""test OAuth Source flow""" """test OAuth Source flow"""
@ -28,7 +30,7 @@ class TestSourceOAuth(SeleniumTestCase):
def setUp(self): def setUp(self):
self.client_secret = generate_client_secret() self.client_secret = generate_client_secret()
self.container = self.setup_client() self.prepare_dex_config()
super().setUp() super().setUp()
def prepare_dex_config(self): def prepare_dex_config(self):
@ -66,34 +68,23 @@ class TestSourceOAuth(SeleniumTestCase):
with open(CONFIG_PATH, "w+") as _file: with open(CONFIG_PATH, "w+") as _file:
safe_dump(config, _file) safe_dump(config, _file)
def setup_client(self) -> Container: def get_container_specs(self) -> Optional[Dict[str, Any]]:
"""Setup test Dex container""" return {
self.prepare_dex_config() "image": "quay.io/dexidp/dex:v2.24.0",
client: DockerClient = from_env() "detach": True,
container = client.containers.run( "network_mode": "host",
image="quay.io/dexidp/dex:v2.24.0", "auto_remove": True,
detach=True, "command": "serve /config.yml",
network_mode="host", "healthcheck": Healthcheck(
auto_remove=True,
command="serve /config.yml",
healthcheck=Healthcheck(
test=["CMD", "wget", "--spider", "http://localhost:5556/dex/healthz"], test=["CMD", "wget", "--spider", "http://localhost:5556/dex/healthz"],
interval=5 * 100 * 1000000, interval=5 * 100 * 1000000,
start_period=1 * 100 * 1000000, start_period=1 * 100 * 1000000,
), ),
volumes={abspath(CONFIG_PATH): {"bind": "/config.yml", "mode": "ro"}}, "volumes": {abspath(CONFIG_PATH): {"bind": "/config.yml", "mode": "ro"}},
) }
while True:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
LOGGER.info("Container failed healthcheck")
sleep(1)
def create_objects(self): def create_objects(self):
"""Create required objects""" """Create required objects"""
sleep(1)
# Bootstrap all needed objects # Bootstrap all needed objects
authentication_flow = Flow.objects.get(slug="default-source-authentication") authentication_flow = Flow.objects.get(slug="default-source-authentication")
enrollment_flow = Flow.objects.get(slug="default-source-enrollment") enrollment_flow = Flow.objects.get(slug="default-source-enrollment")
@ -111,10 +102,6 @@ class TestSourceOAuth(SeleniumTestCase):
consumer_secret=self.client_secret, consumer_secret=self.client_secret,
) )
def tearDown(self):
self.container.kill()
super().tearDown()
def test_oauth_enroll(self): def test_oauth_enroll(self):
"""test OAuth Source With With OIDC""" """test OAuth Source With With OIDC"""
self.create_objects() self.create_objects()
@ -141,6 +128,7 @@ class TestSourceOAuth(SeleniumTestCase):
) )
self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click() self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
self.wait.until(ec.presence_of_element_located((By.NAME, "username")))
# At this point we've been redirected back # At this point we've been redirected back
# and we're asked for the username # and we're asked for the username
self.driver.find_element(By.NAME, "username").click() self.driver.find_element(By.NAME, "username").click()

View File

@ -4,13 +4,16 @@ from glob import glob
from importlib.util import module_from_spec, spec_from_file_location from importlib.util import module_from_spec, spec_from_file_location
from inspect import getmembers, isfunction from inspect import getmembers, isfunction
from os import environ, makedirs from os import environ, makedirs
from time import time from time import sleep, time
from typing import Any, Dict, Optional
from django.apps import apps from django.apps import apps
from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.db import connection, transaction from django.db import connection, transaction
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.shortcuts import reverse from django.shortcuts import reverse
from docker import DockerClient, from_env
from docker.models.containers import Container
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.remote.webdriver import WebDriver
@ -30,15 +33,35 @@ def USER() -> User: # noqa
class SeleniumTestCase(StaticLiveServerTestCase): class SeleniumTestCase(StaticLiveServerTestCase):
"""StaticLiveServerTestCase which automatically creates a Webdriver instance""" """StaticLiveServerTestCase which automatically creates a Webdriver instance"""
container: Optional[Container] = None
def setUp(self): def setUp(self):
super().setUp() super().setUp()
makedirs("selenium_screenshots/", exist_ok=True) makedirs("selenium_screenshots/", exist_ok=True)
self.driver = self._get_driver() self.driver = self._get_driver()
self.driver.maximize_window() self.driver.maximize_window()
self.driver.implicitly_wait(30) self.driver.implicitly_wait(10)
self.wait = WebDriverWait(self.driver, 50) self.wait = WebDriverWait(self.driver, 30)
self.apply_default_data() self.apply_default_data()
self.logger = get_logger() self.logger = get_logger()
if specs := self.get_container_specs():
self.container = self._start_container(specs)
def _start_container(self, specs: Dict[str, Any]) -> Container:
client: DockerClient = from_env()
container = client.containers.run(**specs)
while True:
container.reload()
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
if status == "healthy":
return container
self.logger.info("Container failed healthcheck")
sleep(1)
def get_container_specs(self) -> Optional[Dict[str, Any]]:
"""Optionally get container specs which will launched on setup, wait for the container to
be healthy, and deleted again on tearDown"""
return None
def _get_driver(self) -> WebDriver: def _get_driver(self) -> WebDriver:
return webdriver.Remote( return webdriver.Remote(
@ -57,6 +80,8 @@ class SeleniumTestCase(StaticLiveServerTestCase):
self.logger.warning( self.logger.warning(
line["message"], source=line["source"], level=line["level"] line["message"], source=line["source"], level=line["level"]
) )
if self.container:
self.container.kill()
self.driver.quit() self.driver.quit()
super().tearDown() super().tearDown()

View File

@ -1,9 +1,10 @@
"""Gunicorn config""" """Gunicorn config"""
from multiprocessing import cpu_count
from pathlib import Path
import structlog import structlog
bind = "0.0.0.0:8000" bind = "0.0.0.0:8000"
workers = 2
threads = 4
user = "passbook" user = "passbook"
group = "passbook" group = "passbook"
@ -40,3 +41,11 @@ logconfig_dict = {
"gunicorn": {"handlers": ["console"], "level": "INFO", "propagate": False}, "gunicorn": {"handlers": ["console"], "level": "INFO", "propagate": False},
}, },
} }
# if we're running in kubernetes, use fixed workers because we can scale with more pods
# otherwise (assume docker-compose), use as much as we can
if Path("/var/run/secrets/kubernetes.io").exists():
workers = 2
else:
worker = cpu_count()
threads = 4

View File

@ -5,7 +5,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin

View File

@ -5,7 +5,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin

View File

@ -7,7 +7,7 @@ from django.contrib.auth.mixins import (
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpRequest, HttpResponse, JsonResponse from django.http import HttpRequest, HttpResponse, JsonResponse
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import DetailView, FormView, ListView, UpdateView from django.views.generic import DetailView, FormView, ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin

View File

@ -5,7 +5,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin

View File

@ -5,7 +5,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin

View File

@ -10,7 +10,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http import HttpResponse from django.http import HttpResponse
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import FormView from django.views.generic import FormView
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin

View File

@ -6,7 +6,7 @@ from django.contrib.auth.mixins import (
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import QuerySet from django.db.models import QuerySet
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin

View File

@ -5,7 +5,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import ( from passbook.admin.views.utils import (

View File

@ -5,7 +5,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import ( from passbook.admin.views.utils import (

View File

@ -5,7 +5,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import ( from passbook.admin.views.utils import (

View File

@ -5,7 +5,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.admin.views.utils import ( from passbook.admin.views.utils import (

View File

@ -5,7 +5,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin

View File

@ -6,7 +6,7 @@ from django.contrib.auth.mixins import (
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView from django.views.generic import ListView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin

View File

@ -5,7 +5,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView, UpdateView from django.views.generic import ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin

View File

@ -1,7 +1,7 @@
"""passbook Token administration""" """passbook Token administration"""
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import ListView from django.views.generic import ListView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin from guardian.mixins import PermissionListMixin, PermissionRequiredMixin

View File

@ -9,7 +9,7 @@ from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import DetailView, ListView, UpdateView from django.views.generic import DetailView, ListView, UpdateView
from guardian.mixins import ( from guardian.mixins import (
PermissionListMixin, PermissionListMixin,

View File

@ -1,6 +1,5 @@
"""api v2 urls""" """api v2 urls"""
from django.conf.urls import url from django.urls import path, re_path
from django.urls import path
from drf_yasg import openapi from drf_yasg import openapi
from drf_yasg.views import get_schema_view from drf_yasg.views import get_schema_view
from rest_framework import routers from rest_framework import routers
@ -119,7 +118,7 @@ SchemaView = get_schema_view(
) )
urlpatterns = [ urlpatterns = [
url( re_path(
r"^swagger(?P<format>\.json|\.yaml)$", r"^swagger(?P<format>\.json|\.yaml)$",
SchemaView.without_ui(cache_timeout=0), SchemaView.without_ui(cache_timeout=0),
name="schema-json", name="schema-json",

View File

@ -1,5 +1,5 @@
"""passbook core utils view""" """passbook core utils view"""
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import TemplateView from django.views.generic import TemplateView

View File

@ -52,7 +52,7 @@ def create_default_source_enrollment_flow(
# PromptStage to ask user for their username # PromptStage to ask user for their username
prompt_stage, _ = PromptStage.objects.using(db_alias).update_or_create( prompt_stage, _ = PromptStage.objects.using(db_alias).update_or_create(
name="default-source-enrollment-username-prompt", name="Welcome to passbook! Please select a username.",
) )
prompt, _ = Prompt.objects.using(db_alias).update_or_create( prompt, _ = Prompt.objects.using(db_alias).update_or_create(
field_key="username", field_key="username",

View File

@ -115,11 +115,12 @@ const updateFormAction = (form) => {
for (let index = 0; index < form.elements.length; index++) { for (let index = 0; index < form.elements.length; index++) {
const element = form.elements[index]; const element = form.elements[index];
if (element.value === form.action) { if (element.value === form.action) {
console.log("Found Form action URL in form elements, not changing form action."); console.log("pb-flow: Found Form action URL in form elements, not changing form action.");
return false; return false;
} }
} }
form.action = flowBodyUrl; form.action = flowBodyUrl;
console.log(`pb-flow: updated form.action ${flowBodyUrl}`);
return true; return true;
}; };
const checkAutosubmit = (form) => { const checkAutosubmit = (form) => {
@ -129,11 +130,11 @@ const checkAutosubmit = (form) => {
}; };
const setFormSubmitHandlers = () => { const setFormSubmitHandlers = () => {
document.querySelectorAll("#flow-body form").forEach(form => { document.querySelectorAll("#flow-body form").forEach(form => {
console.log(`Checking for autosubmit attribute ${form}`); console.log(`pb-flow: Checking for autosubmit attribute ${form}`);
checkAutosubmit(form); checkAutosubmit(form);
console.log(`Setting action for form ${form}`); console.log(`pb-flow: Setting action for form ${form}`);
updateFormAction(form); updateFormAction(form);
console.log(`Adding handler for form ${form}`); console.log(`pb-flow: Adding handler for form ${form}`);
form.addEventListener('submit', (e) => { form.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
let formData = new FormData(form); let formData = new FormData(form);
@ -145,6 +146,7 @@ const setFormSubmitHandlers = () => {
updateCard(data); updateCard(data);
}); });
}); });
form.classList.add("pb-flow-wrapped");
}); });
}; };

View File

@ -3,7 +3,7 @@ from unittest.mock import MagicMock, PropertyMock, patch
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.flows.exceptions import EmptyFlowException, FlowNonApplicableException from passbook.flows.exceptions import EmptyFlowException, FlowNonApplicableException
from passbook.flows.markers import ReevaluateMarker, StageMarker from passbook.flows.markers import ReevaluateMarker, StageMarker
@ -247,7 +247,7 @@ class TestFlowExecutor(TestCase):
response = self.client.post(exec_url) response = self.client.post(exec_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )
@ -293,7 +293,7 @@ class TestFlowExecutor(TestCase):
# First request, run the planner # First request, run the planner
response = self.client.get(exec_url) response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn("dummy1", force_text(response.content)) self.assertIn("dummy1", force_str(response.content))
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN] plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
@ -316,13 +316,13 @@ class TestFlowExecutor(TestCase):
# but it won't save it, hence we cant' check the plan # but it won't save it, hence we cant' check the plan
response = self.client.get(exec_url) response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn("dummy4", force_text(response.content)) self.assertIn("dummy4", force_str(response.content))
# fourth request, this confirms the last stage (dummy4) # fourth request, this confirms the last stage (dummy4)
# We do this request without the patch, so the policy results in false # We do this request without the patch, so the policy results in false
response = self.client.post(exec_url) response = self.client.post(exec_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )

View File

@ -14,7 +14,7 @@ from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils import dateformat, timezone from django.utils import dateformat, timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from jwkest.jwk import Key, RSAKey, SYMKey, import_rsa_key from jwkest.jwk import Key, RSAKey, SYMKey, import_rsa_key
from jwkest.jws import JWS from jwkest.jws import JWS

View File

@ -82,7 +82,7 @@ def extract_client_auth(request: HttpRequest) -> Tuple[str, str]:
b64_user_pass = auth_header.split()[1] b64_user_pass = auth_header.split()[1]
try: try:
user_pass = b64decode(b64_user_pass).decode("utf-8").split(":") user_pass = b64decode(b64_user_pass).decode("utf-8").split(":")
client_id, client_secret = tuple(user_pass) client_id, client_secret = user_pass
except (ValueError, Error): except (ValueError, Error):
client_id = client_secret = "" client_id = client_secret = ""
else: else:

View File

@ -2,7 +2,7 @@
from typing import Any, Dict, List from typing import Any, Dict, List
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from structlog import get_logger from structlog import get_logger

View File

@ -5,7 +5,7 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from structlog import get_logger from structlog import get_logger
from passbook.core.models import PropertyMapping, Provider from passbook.core.models import PropertyMapping, Provider

View File

@ -12,7 +12,6 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
import importlib import importlib
import os import os
import sys
from json import dumps from json import dumps
import structlog import structlog
@ -156,6 +155,7 @@ DJANGO_REDIS_IGNORE_EXCEPTIONS = True
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default" SESSION_CACHE_ALIAS = "default"
SESSION_COOKIE_SAMESITE = "lax"
MIDDLEWARE = [ MIDDLEWARE = [
"django_prometheus.middleware.PrometheusBeforeMiddleware", "django_prometheus.middleware.PrometheusBeforeMiddleware",
@ -372,15 +372,9 @@ LOGGING = {
} }
TEST = False TEST = False
TEST_RUNNER = "xmlrunner.extra.djangotestrunner.XMLTestRunner" TEST_RUNNER = "passbook.root.test_runner.PytestTestRunner"
LOG_LEVEL = CONFIG.y("log_level").upper() LOG_LEVEL = CONFIG.y("log_level").upper()
TEST_OUTPUT_FILE_NAME = "unittest.xml"
if len(sys.argv) >= 2 and sys.argv[1] == "test":
LOG_LEVEL = "DEBUG"
TEST = True
CELERY_TASK_ALWAYS_EAGER = True
_LOGGING_HANDLER_MAP = { _LOGGING_HANDLER_MAP = {
"": LOG_LEVEL, "": LOG_LEVEL,
@ -431,7 +425,6 @@ for _app in INSTALLED_APPS:
pass pass
if DEBUG: if DEBUG:
SESSION_COOKIE_SAMESITE = None
INSTALLED_APPS.append("debug_toolbar") INSTALLED_APPS.append("debug_toolbar")
MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware") MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware")

View File

@ -0,0 +1,35 @@
"""Integrate ./manage.py test with pytest"""
from django.conf import settings
class PytestTestRunner:
"""Runs pytest to discover and run tests."""
def __init__(self, verbosity=1, failfast=False, keepdb=False, **_):
self.verbosity = verbosity
self.failfast = failfast
self.keepdb = keepdb
settings.TEST = True
settings.CELERY_TASK_ALWAYS_EAGER = True
def run_tests(self, test_labels):
"""Run pytest and return the exitcode.
It translates some of Django's test command option to pytest's.
"""
import pytest
argv = []
if self.verbosity == 0:
argv.append("--quiet")
if self.verbosity == 2:
argv.append("--verbose")
if self.verbosity == 3:
argv.append("-vv")
if self.failfast:
argv.append("--exitfirst")
if self.keepdb:
argv.append("--reuse-db")
argv.extend(test_labels)
return pytest.main(argv)

View File

@ -15,7 +15,7 @@ admin.site.login = RedirectView.as_view(
pattern_name="passbook_flows:default-authentication" pattern_name="passbook_flows:default-authentication"
) )
admin.site.logout = RedirectView.as_view( admin.site.logout = RedirectView.as_view(
pattern_name="passbook_flows:default-invalidate" pattern_name="passbook_flows:default-invalidation"
) )
handler400 = error.BadRequestView.as_view() handler400 = error.BadRequestView.as_view()

View File

@ -5,7 +5,7 @@ from urllib.parse import parse_qs, urlencode
from django.http import HttpRequest from django.http import HttpRequest
from django.utils.crypto import constant_time_compare, get_random_string from django.utils.crypto import constant_time_compare, get_random_string
from django.utils.encoding import force_text from django.utils.encoding import force_str
from requests import Session from requests import Session
from requests.exceptions import RequestException from requests.exceptions import RequestException
from requests_oauthlib import OAuth1 from requests_oauthlib import OAuth1
@ -111,7 +111,7 @@ class OAuthClient(BaseOAuthClient):
def get_request_token(self, request, callback): def get_request_token(self, request, callback):
"Fetch the OAuth request token. Only required for OAuth 1.0." "Fetch the OAuth request token. Only required for OAuth 1.0."
callback = force_text(request.build_absolute_uri(callback)) callback = force_str(request.build_absolute_uri(callback))
try: try:
response = self.session.request( response = self.session.request(
"post", "post",
@ -128,7 +128,7 @@ class OAuthClient(BaseOAuthClient):
def get_redirect_args(self, request, callback): def get_redirect_args(self, request, callback):
"Get request parameters for redirect url." "Get request parameters for redirect url."
callback = force_text(request.build_absolute_uri(callback)) callback = force_str(request.build_absolute_uri(callback))
raw_token = self.get_request_token(request, callback) raw_token = self.get_request_token(request, callback)
token, secret = self.parse_raw_token(raw_token) token, secret = self.parse_raw_token(raw_token)
if token is not None and secret is not None: if token is not None and secret is not None:

View File

@ -6,7 +6,7 @@ from django.contrib import messages
from django.http import Http404, HttpRequest, HttpResponse from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import View from django.views.generic import View
from structlog import get_logger from structlog import get_logger

View File

@ -6,7 +6,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views.generic import TemplateView, View from django.views.generic import TemplateView, View
from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection from passbook.sources.oauth.models import OAuthSource, UserOAuthSourceConnection

View File

@ -2,7 +2,7 @@
from django.conf import settings from django.conf import settings
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.markers import StageMarker from passbook.flows.markers import StageMarker
@ -50,6 +50,6 @@ class TestCaptchaStage(TestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )

View File

@ -3,7 +3,7 @@ from time import sleep
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.core.models import Application, User from passbook.core.models import Application, User
from passbook.core.tasks import clean_expired_models from passbook.core.tasks import clean_expired_models
@ -49,7 +49,7 @@ class TestConsentStage(TestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )
self.assertFalse(UserConsent.objects.filter(user=self.user).exists()) self.assertFalse(UserConsent.objects.filter(user=self.user).exists())
@ -80,7 +80,7 @@ class TestConsentStage(TestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )
self.assertTrue( self.assertTrue(
@ -117,7 +117,7 @@ class TestConsentStage(TestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )
self.assertTrue( self.assertTrue(

View File

@ -1,7 +1,7 @@
"""dummy tests""" """dummy tests"""
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
@ -44,7 +44,7 @@ class TestDummyStage(TestCase):
response = self.client.post(url, {}) response = self.client.post(url, {})
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )

View File

@ -4,7 +4,7 @@ from unittest.mock import MagicMock, patch
from django.core import mail from django.core import mail
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.core.models import Token, User from passbook.core.models import Token, User
from passbook.flows.markers import StageMarker from passbook.flows.markers import StageMarker
@ -114,7 +114,7 @@ class TestEmailStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )

View File

@ -1,7 +1,7 @@
"""identification tests""" """identification tests"""
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
@ -56,7 +56,7 @@ class TestIdentificationStage(TestCase):
response = self.client.post(url, form_data) response = self.client.post(url, form_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )
@ -101,7 +101,7 @@ class TestIdentificationStage(TestCase):
), ),
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn(flow.slug, force_text(response.content)) self.assertIn(flow.slug, force_str(response.content))
def test_recovery_flow(self): def test_recovery_flow(self):
"""Test that recovery flow is linked correctly""" """Test that recovery flow is linked correctly"""
@ -122,4 +122,4 @@ class TestIdentificationStage(TestCase):
), ),
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn(flow.slug, force_text(response.content)) self.assertIn(flow.slug, force_str(response.content))

View File

@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from passbook.core.models import User from passbook.core.models import User
@ -59,7 +59,7 @@ class TestUserLoginStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_flows:denied")}, {"type": "redirect", "to": reverse("passbook_flows:denied")},
) )
@ -86,7 +86,7 @@ class TestUserLoginStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )
@ -125,6 +125,6 @@ class TestUserLoginStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )

View File

@ -2,7 +2,7 @@
from typing import Any, Dict from typing import Any, Dict
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.utils.encoding import force_text from django.utils.encoding import force_str
from django.views.generic import FormView from django.views.generic import FormView
from django_otp.plugins.otp_totp.models import TOTPDevice from django_otp.plugins.otp_totp.models import TOTPDevice
from lxml.etree import tostring # nosec from lxml.etree import tostring # nosec
@ -35,7 +35,7 @@ class OTPTimeStageView(FormView, StageView):
"""Get QR Code SVG as string based on `device`""" """Get QR Code SVG as string based on `device`"""
qr_code = QRCode(image_factory=SvgFillImage) qr_code = QRCode(image_factory=SvgFillImage)
qr_code.add_data(device.config_url) qr_code.add_data(device.config_url)
return force_text(tostring(qr_code.make_image().get_image())) return force_str(tostring(qr_code.make_image().get_image()))
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER) user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)

View File

@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.markers import StageMarker from passbook.flows.markers import StageMarker
@ -61,7 +61,7 @@ class TestPasswordStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_flows:denied")}, {"type": "redirect", "to": reverse("passbook_flows:denied")},
) )
@ -84,7 +84,7 @@ class TestPasswordStage(TestCase):
), ),
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn(flow.slug, force_text(response.content)) self.assertIn(flow.slug, force_str(response.content))
def test_valid_password(self): def test_valid_password(self):
"""Test with a valid pending user and valid password""" """Test with a valid pending user and valid password"""
@ -106,7 +106,7 @@ class TestPasswordStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )
@ -154,6 +154,6 @@ class TestPasswordStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_flows:denied")}, {"type": "redirect", "to": reverse("passbook_flows:denied")},
) )

View File

@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.markers import StageMarker from passbook.flows.markers import StageMarker
@ -110,9 +110,9 @@ class TestPromptStage(TestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
for prompt in self.stage.fields.all(): for prompt in self.stage.fields.all():
self.assertIn(prompt.field_key, force_text(response.content)) self.assertIn(prompt.field_key, force_str(response.content))
self.assertIn(prompt.label, force_text(response.content)) self.assertIn(prompt.label, force_str(response.content))
self.assertIn(prompt.placeholder, force_text(response.content)) self.assertIn(prompt.placeholder, force_str(response.content))
def test_valid_form_with_policy(self) -> PromptForm: def test_valid_form_with_policy(self) -> PromptForm:
"""Test form validation""" """Test form validation"""
@ -164,7 +164,7 @@ class TestPromptStage(TestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )

View File

@ -1,7 +1,7 @@
"""delete tests""" """delete tests"""
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.markers import StageMarker from passbook.flows.markers import StageMarker
@ -44,7 +44,7 @@ class TestUserDeleteStage(TestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_flows:denied")}, {"type": "redirect", "to": reverse("passbook_flows:denied")},
) )
@ -83,7 +83,7 @@ class TestUserDeleteStage(TestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )

View File

@ -1,7 +1,7 @@
"""login tests""" """login tests"""
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.markers import StageMarker from passbook.flows.markers import StageMarker
@ -50,7 +50,7 @@ class TestUserLoginStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )
@ -71,7 +71,7 @@ class TestUserLoginStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_flows:denied")}, {"type": "redirect", "to": reverse("passbook_flows:denied")},
) )
@ -93,7 +93,7 @@ class TestUserLoginStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_flows:denied")}, {"type": "redirect", "to": reverse("passbook_flows:denied")},
) )

View File

@ -1,7 +1,7 @@
"""logout tests""" """logout tests"""
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.markers import StageMarker from passbook.flows.markers import StageMarker
@ -50,7 +50,7 @@ class TestUserLogoutStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )

View File

@ -4,7 +4,7 @@ from random import SystemRandom
from django.shortcuts import reverse from django.shortcuts import reverse
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils.encoding import force_text from django.utils.encoding import force_str
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.markers import StageMarker from passbook.flows.markers import StageMarker
@ -59,7 +59,7 @@ class TestUserWriteStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )
user_qs = User.objects.filter( user_qs = User.objects.filter(
@ -97,7 +97,7 @@ class TestUserWriteStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")}, {"type": "redirect", "to": reverse("passbook_core:overview")},
) )
user_qs = User.objects.filter( user_qs = User.objects.filter(
@ -124,7 +124,7 @@ class TestUserWriteStage(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertJSONEqual( self.assertJSONEqual(
force_text(response.content), force_str(response.content),
{"type": "redirect", "to": reverse("passbook_flows:denied")}, {"type": "redirect", "to": reverse("passbook_flows:denied")},
) )

6
pytest.ini Normal file
View File

@ -0,0 +1,6 @@
[pytest]
DJANGO_SETTINGS_MODULE = passbook.root.settings
# -- recommended but optional:
python_files = tests.py test_*.py *_tests.py
junit_family = xunit2
addopts = -p no:celery --junitxml=unittest.xml