Flow exporting/importing (#187)

* stages/*: Add SerializerModel as base model, implement serializer property

* flows: add initial flow exporter and importer

* policies/*: implement .serializer for all policies

* root: fix missing dacite requirement
This commit is contained in:
Jens L 2020-08-22 00:42:15 +02:00 committed by GitHub
parent 8b17e8be99
commit 0e0898c3cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 681 additions and 21 deletions

View File

@ -38,6 +38,7 @@ signxml = "*"
structlog = "*" structlog = "*"
swagger-spec-validator = "*" swagger-spec-validator = "*"
urllib3 = {extras = ["secure"],version = "*"} urllib3 = {extras = ["secure"],version = "*"}
dacite = "*"
[requires] [requires]
python_version = "3.8" python_version = "3.8"

97
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "616f5d355c42881b7ea70d4623bf885cff043d4c58913287960923df49c09909" "sha256": "8f099b73d5993a0693261bf3d2b0e696d4f4d7ddd69a10d3db8ffe59a8ebd805"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -21,6 +21,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": {
@ -28,6 +29,7 @@
"sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a", "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
"sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed" "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
], ],
"markers": "python_version >= '3.5'",
"version": "==3.2.10" "version": "==3.2.10"
}, },
"attrs": { "attrs": {
@ -35,6 +37,7 @@
"sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a", "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a",
"sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff" "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.1.0" "version": "==20.1.0"
}, },
"billiard": { "billiard": {
@ -46,6 +49,7 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:0d9cbeb5c8ca67650cc963c77e2e3b3ab5dffeeee16e03d61d740755f8fc7c44",
"sha256:df73edf3bd6f191870212e04ae9a8bc6245fd6749f464e9fb950392a8d15bd8c" "sha256:df73edf3bd6f191870212e04ae9a8bc6245fd6749f464e9fb950392a8d15bd8c"
], ],
"index": "pypi", "index": "pypi",
@ -151,6 +155,14 @@
], ],
"version": "==2.9.2" "version": "==2.9.2"
}, },
"dacite": {
"hashes": [
"sha256:764c96e0304cb189628686689a163a6a3a8ce7bf3465f0a2d882a8b42f88108f",
"sha256:f7f269647ede90f8702728eb7dcb972051511c81b853a93c962fbd31f1753b9f"
],
"index": "pypi",
"version": "==1.5.1"
},
"defusedxml": { "defusedxml": {
"hashes": [ "hashes": [
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
@ -257,6 +269,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": {
@ -273,6 +286,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": {
@ -302,6 +316,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"
}, },
"idna": { "idna": {
@ -316,6 +331,7 @@
"sha256:88b101b2668a1d81d6d72d4c2018e53bc6c7fc544c987849da1c7f77545c3bc9", "sha256:88b101b2668a1d81d6d72d4c2018e53bc6c7fc544c987849da1c7f77545c3bc9",
"sha256:f576e85132d34f5bf7df5183c2c6f94cfb32e528f53065345cf71329ba0b8924" "sha256:f576e85132d34f5bf7df5183c2c6f94cfb32e528f53065345cf71329ba0b8924"
], ],
"markers": "python_version >= '3.5'",
"version": "==0.5.0" "version": "==0.5.0"
}, },
"itypes": { "itypes": {
@ -330,6 +346,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": {
@ -337,6 +354,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": {
@ -351,12 +369,16 @@
"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"
}, },
"ldap3": { "ldap3": {
"hashes": [ "hashes": [
"sha256:b399c39e80b6459e349b33fbe9787c1bcbf86de05994d41806a05c06f3e7574d",
"sha256:bdaf568cd30fc0006c8bb4f5e6014554afeb0c4bbea1677de9706e278a4057e7",
"sha256:df27407f4991f25bd669b5bb1bc8cb9ddf44a3e713ff6b3afeb3b3c26502f88f",
"sha256:59d1adcd5ead263387039e2a37d7cd772a2006b1cdb3ecfcbaab5192a601c515", "sha256:59d1adcd5ead263387039e2a37d7cd772a2006b1cdb3ecfcbaab5192a601c515",
"sha256:df27407f4991f25bd669b5bb1bc8cb9ddf44a3e713ff6b3afeb3b3c26502f88f" "sha256:7abbb3e5f4522114e0230ec175b60ae968b938d1f8a7d8bce7789f78d871fb9f"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.8" "version": "==2.8"
@ -434,6 +456,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"
}, },
"oauthlib": { "oauthlib": {
@ -441,6 +464,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": {
@ -496,15 +520,37 @@
}, },
"pyasn1": { "pyasn1": {
"hashes": [ "hashes": [
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba" "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"
], ],
"version": "==0.4.8" "version": "==0.4.8"
}, },
"pyasn1-modules": { "pyasn1-modules": {
"hashes": [ "hashes": [
"sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74" "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
"sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
"sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
"sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
"sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
"sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb",
"sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
"sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
"sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
"sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"
], ],
"version": "==0.2.8" "version": "==0.2.8"
}, },
@ -513,6 +559,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": {
@ -584,6 +631,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"
}, },
"pyjwkest": { "pyjwkest": {
@ -605,6 +653,7 @@
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
], ],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7" "version": "==2.4.7"
}, },
"pyrsistent": { "pyrsistent": {
@ -618,6 +667,7 @@
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1" "version": "==2.8.1"
}, },
"pytz": { "pytz": {
@ -675,6 +725,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": {
@ -682,12 +733,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:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a" "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc",
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.3.0" "version": "==1.3.0"
@ -721,7 +774,7 @@
"sha256:ed5b3698a2bb241b7f5cbbe277eaa7fe48b07a58784fba4f75224fd066d253ad", "sha256:ed5b3698a2bb241b7f5cbbe277eaa7fe48b07a58784fba4f75224fd066d253ad",
"sha256:f9dcc1ae73f36e8059589b601e8e4776b9976effd76c21ad6a855a74318efd6e" "sha256:f9dcc1ae73f36e8059589b601e8e4776b9976effd76c21ad6a855a74318efd6e"
], ],
"markers": "platform_python_implementation == 'CPython' and python_version < '3.9'", "markers": "python_version < '3.9' and platform_python_implementation == 'CPython'",
"version": "==0.2.0" "version": "==0.2.0"
}, },
"s3transfer": { "s3transfer": {
@ -760,6 +813,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": {
@ -767,6 +821,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": {
@ -790,6 +845,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": {
@ -801,7 +857,6 @@
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
], ],
"index": "pypi", "index": "pypi",
"markers": null,
"version": "==1.25.10" "version": "==1.25.10"
}, },
"vine": { "vine": {
@ -809,6 +864,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"
} }
}, },
@ -825,6 +881,7 @@
"sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a", "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
"sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed" "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
], ],
"markers": "python_version >= '3.5'",
"version": "==3.2.10" "version": "==3.2.10"
}, },
"astroid": { "astroid": {
@ -832,6 +889,7 @@
"sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1", "sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1",
"sha256:d8506842a3faf734b81599c8b98dcc423de863adcc1999248480b18bd31a0f38" "sha256:d8506842a3faf734b81599c8b98dcc423de863adcc1999248480b18bd31a0f38"
], ],
"markers": "python_version >= '3.5'",
"version": "==2.4.1" "version": "==2.4.1"
}, },
"attrs": { "attrs": {
@ -839,6 +897,7 @@
"sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a", "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a",
"sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff" "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.1.0" "version": "==20.1.0"
}, },
"autopep8": { "autopep8": {
@ -869,6 +928,7 @@
"sha256:477f0e18a0d58e50bb3dbc9af7fcda464fd0ebfc7a6151d8888602d7153171a0", "sha256:477f0e18a0d58e50bb3dbc9af7fcda464fd0ebfc7a6151d8888602d7153171a0",
"sha256:cd4f3a231305e405ed8944d8ff35bd742d9bc740ad62f483bd0ca21ce7131984" "sha256:cd4f3a231305e405ed8944d8ff35bd742d9bc740ad62f483bd0ca21ce7131984"
], ],
"markers": "python_version >= '3.5'",
"version": "==1.0.0" "version": "==1.0.0"
}, },
"bumpversion": { "bumpversion": {
@ -898,6 +958,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": {
@ -966,11 +1027,11 @@
}, },
"docker": { "docker": {
"hashes": [ "hashes": [
"sha256:431a268f2caf85aa30613f9642da274c62f6ee8bae7d70d968e01529f7d6af93", "sha256:13966471e8bc23b36bfb3a6fb4ab75043a5ef1dac86516274777576bed3b9828",
"sha256:ba118607b0ba6bfc1b236ec32019a355c47b5d012d01d976467d4692ef443929" "sha256:bad94b8dd001a8a4af19ce4becc17f41b09f228173ffe6a4e0355389eef142f2"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.3.0" "version": "==4.3.1"
}, },
"dodgy": { "dodgy": {
"hashes": [ "hashes": [
@ -984,6 +1045,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": {
@ -998,6 +1060,7 @@
"sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac",
"sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9" "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"
], ],
"markers": "python_version >= '3.4'",
"version": "==4.0.5" "version": "==4.0.5"
}, },
"gitpython": { "gitpython": {
@ -1005,6 +1068,7 @@
"sha256:2db287d71a284e22e5c2846042d0602465c7434d910406990d5b74df4afb0858", "sha256:2db287d71a284e22e5c2846042d0602465c7434d910406990d5b74df4afb0858",
"sha256:fa3b92da728a457dd75d62bb5f3eb2816d99a7fe6c67398e260637a40e3fafb5" "sha256:fa3b92da728a457dd75d62bb5f3eb2816d99a7fe6c67398e260637a40e3fafb5"
], ],
"markers": "python_version >= '3.4'",
"version": "==3.1.7" "version": "==3.1.7"
}, },
"idna": { "idna": {
@ -1019,6 +1083,7 @@
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==4.3.21" "version": "==4.3.21"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
@ -1045,6 +1110,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": {
@ -1087,6 +1153,7 @@
"sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
"sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.6.0" "version": "==2.6.0"
}, },
"pydocstyle": { "pydocstyle": {
@ -1094,6 +1161,7 @@
"sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586", "sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586",
"sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5" "sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"
], ],
"markers": "python_version >= '3.5'",
"version": "==5.0.2" "version": "==5.0.2"
}, },
"pyflakes": { "pyflakes": {
@ -1101,6 +1169,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": {
@ -1193,6 +1262,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": {
@ -1220,6 +1290,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": {
@ -1227,6 +1298,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": {
@ -1241,6 +1313,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": {
@ -1248,6 +1321,7 @@
"sha256:38791aa5bed922b0a844513c5f9ed37774b68edc609e5ab8ab8d8fe0ce4315e5", "sha256:38791aa5bed922b0a844513c5f9ed37774b68edc609e5ab8ab8d8fe0ce4315e5",
"sha256:c8f4f0ebbc394e52ddf49de8bcc3cf8ad2b4425ebac494106bbc5e3661ac7633" "sha256:c8f4f0ebbc394e52ddf49de8bcc3cf8ad2b4425ebac494106bbc5e3661ac7633"
], ],
"markers": "python_version >= '3.6'",
"version": "==3.2.0" "version": "==3.2.0"
}, },
"toml": { "toml": {
@ -1300,7 +1374,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

@ -7,9 +7,11 @@ from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
from rest_framework.serializers import BaseSerializer
from structlog import get_logger from structlog import get_logger
from passbook.core.types import UIUserSettings from passbook.core.types import UIUserSettings
from passbook.lib.models import SerializerModel
from passbook.policies.models import PolicyBindingModel from passbook.policies.models import PolicyBindingModel
if TYPE_CHECKING: if TYPE_CHECKING:
@ -38,7 +40,7 @@ class FlowDesignation(models.TextChoices):
STAGE_SETUP = "stage_setup" STAGE_SETUP = "stage_setup"
class Stage(models.Model): class Stage(SerializerModel):
"""Stage is an instance of a component used in a flow. This can verify the user, """Stage is an instance of a component used in a flow. This can verify the user,
enroll the user or offer a way of recovery""" enroll the user or offer a way of recovery"""
@ -81,7 +83,7 @@ def in_memory_stage(view: Type["StageView"]) -> Stage:
return stage return stage
class Flow(PolicyBindingModel): class Flow(SerializerModel, PolicyBindingModel):
"""Flow describes how a series of Stages should be executed to authenticate/enroll/recover """Flow describes how a series of Stages should be executed to authenticate/enroll/recover
a user. Additionally, policies can be applied, to specify which users a user. Additionally, policies can be applied, to specify which users
have access to this flow.""" have access to this flow."""
@ -95,6 +97,12 @@ class Flow(PolicyBindingModel):
stages = models.ManyToManyField(Stage, through="FlowStageBinding", blank=True) stages = models.ManyToManyField(Stage, through="FlowStageBinding", blank=True)
@property
def serializer(self) -> BaseSerializer:
from passbook.flows.api import FlowSerializer
return FlowSerializer
@staticmethod @staticmethod
def with_policy(request: HttpRequest, **flow_filter) -> Optional["Flow"]: def with_policy(request: HttpRequest, **flow_filter) -> Optional["Flow"]:
"""Get a Flow by `**flow_filter` and check if the request from `request` can access it.""" """Get a Flow by `**flow_filter` and check if the request from `request` can access it."""
@ -128,7 +136,7 @@ class Flow(PolicyBindingModel):
verbose_name_plural = _("Flows") verbose_name_plural = _("Flows")
class FlowStageBinding(PolicyBindingModel): class FlowStageBinding(SerializerModel, PolicyBindingModel):
"""Relationship between Flow and Stage. Order is required and unique for """Relationship between Flow and Stage. Order is required and unique for
each flow-stage Binding. Additionally, policies can be specified, which determine if each flow-stage Binding. Additionally, policies can be specified, which determine if
this Binding applies to the current user""" this Binding applies to the current user"""
@ -149,6 +157,12 @@ class FlowStageBinding(PolicyBindingModel):
objects = InheritanceManager() objects = InheritanceManager()
@property
def serializer(self) -> BaseSerializer:
from passbook.flows.api import FlowStageBindingSerializer
return FlowStageBindingSerializer
def __str__(self) -> str: def __str__(self) -> str:
return f"Flow Binding {self.target} -> {self.stage}" return f"Flow Binding {self.target} -> {self.stage}"

View File

@ -0,0 +1,119 @@
"""Test flow transfer"""
from json import dumps
from django.test import TransactionTestCase
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
from passbook.flows.transfer.common import DataclassEncoder
from passbook.flows.transfer.exporter import FlowExporter
from passbook.flows.transfer.importer import FlowImporter
from passbook.policies.expression.models import ExpressionPolicy
from passbook.policies.models import PolicyBinding
from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage
from passbook.stages.user_login.models import UserLoginStage
class TestFlowTransfer(TransactionTestCase):
"""Test flow transfer"""
def test_bundle_invalid_format(self):
"""Test bundle with invalid format"""
importer = FlowImporter('{"version": 3}')
self.assertFalse(importer.validate())
importer = FlowImporter(
'{"version": 1,"entries":[{"identifier":"","attrs":{},"model": "passbook_core.User"}]}'
)
self.assertFalse(importer.validate())
def test_export_validate_import(self):
"""Test export and validate it"""
login_stage = UserLoginStage.objects.create(name="default-authentication-login")
flow = Flow.objects.create(
slug="test",
designation=FlowDesignation.AUTHENTICATION,
name="Welcome to passbook!",
)
FlowStageBinding.objects.update_or_create(
target=flow, stage=login_stage, order=0,
)
exporter = FlowExporter(flow)
export = exporter.export()
self.assertEqual(len(export.entries), 3)
export_json = dumps(export, cls=DataclassEncoder)
importer = FlowImporter(export_json)
self.assertTrue(importer.validate())
flow.delete()
login_stage.delete()
self.assertTrue(importer.apply())
self.assertTrue(Flow.objects.filter(slug="test").exists())
def test_export_validate_import_policies(self):
"""Test export and validate it"""
flow_policy = ExpressionPolicy.objects.create(
name="default-source-authentication-if-sso", expression="return True",
)
flow = Flow.objects.create(
slug="default-source-authentication",
designation=FlowDesignation.AUTHENTICATION,
name="Welcome to passbook!",
)
PolicyBinding.objects.create(policy=flow_policy, target=flow, order=0)
user_login = UserLoginStage.objects.create(
name="default-source-authentication-login"
)
FlowStageBinding.objects.create(target=flow, stage=user_login, order=0)
exporter = FlowExporter(flow)
export = exporter.export()
export_json = dumps(export, cls=DataclassEncoder)
importer = FlowImporter(export_json)
self.assertTrue(importer.validate())
self.assertTrue(importer.apply())
def test_export_validate_import_prompt(self):
"""Test export and validate it"""
# First stage fields
username_prompt = Prompt.objects.create(
field_key="username", label="Username", order=0, type=FieldTypes.TEXT
)
password = Prompt.objects.create(
field_key="password", label="Password", order=1, type=FieldTypes.PASSWORD
)
password_repeat = Prompt.objects.create(
field_key="password_repeat",
label="Password (repeat)",
order=2,
type=FieldTypes.PASSWORD,
)
# Stages
first_stage = PromptStage.objects.create(name="prompt-stage-first")
first_stage.fields.set([username_prompt, password, password_repeat])
first_stage.save()
# Password checking policy
password_policy = ExpressionPolicy.objects.create(
name="policy-enrollment-password-equals",
expression="return request.context['password'] == request.context['password_repeat']",
)
PolicyBinding.objects.create(
target=first_stage, policy=password_policy, order=0
)
flow = Flow.objects.create(
name="default-enrollment-flow",
slug="default-enrollment-flow",
designation=FlowDesignation.ENROLLMENT,
)
FlowStageBinding.objects.create(target=flow, stage=first_stage, order=0)
exporter = FlowExporter(flow)
export = exporter.export()
export_json = dumps(export, cls=DataclassEncoder)
importer = FlowImporter(export_json)
self.assertTrue(importer.validate())
self.assertTrue(importer.apply())

View File

View File

@ -0,0 +1,59 @@
"""transfer common classes"""
from dataclasses import asdict, dataclass, field, is_dataclass
from json.encoder import JSONEncoder
from typing import Any, Dict, List
from uuid import UUID
from passbook.lib.models import SerializerModel
from passbook.lib.sentry import SentryIgnoredException
def get_attrs(obj: SerializerModel) -> Dict[str, Any]:
"""Get object's attributes via their serializer, and covert it to a normal dict"""
data = dict(obj.serializer(obj).data)
if "policies" in data:
data.pop("policies")
if "stages" in data:
data.pop("stages")
return data
@dataclass
class FlowBundleEntry:
"""Single entry of a bundle"""
identifier: str
model: str
attrs: Dict[str, Any]
@staticmethod
def from_model(model: SerializerModel) -> "FlowBundleEntry":
"""Convert a SerializerModel instance to a Bundle Entry"""
return FlowBundleEntry(
identifier=model.pk,
model=f"{model._meta.app_label}.{model._meta.model_name}",
attrs=get_attrs(model),
)
@dataclass
class FlowBundle:
"""Dataclass used for a full export"""
version: int = field(default=1)
entries: List[FlowBundleEntry] = field(default_factory=list)
class DataclassEncoder(JSONEncoder):
"""Convert FlowBundleEntry to json"""
def default(self, o):
if is_dataclass(o):
return asdict(o)
if isinstance(o, UUID):
return str(o)
return super().default(o)
class EntryInvalidError(SentryIgnoredException):
"""Error raised when an entry is invalid"""

View File

@ -0,0 +1,78 @@
"""Flow exporter"""
from json import dumps
from typing import Iterator
from passbook.flows.models import Flow, FlowStageBinding, Stage
from passbook.flows.transfer.common import DataclassEncoder, FlowBundle, FlowBundleEntry
from passbook.policies.models import Policy, PolicyBinding, PolicyBindingModel
from passbook.stages.prompt.models import PromptStage
class FlowExporter:
"""Export flow with attached stages into json"""
flow: Flow
with_policies: bool
with_stage_prompts: bool
def __init__(self, flow: Flow):
self.flow = flow
self.with_policies = True
self.with_stage_prompts = True
def walk_stages(self) -> Iterator[FlowBundleEntry]:
"""Convert all stages attached to self.flow into FlowBundleEntry objects"""
stages = (
Stage.objects.filter(flow=self.flow).select_related().select_subclasses()
)
for stage in stages:
if isinstance(stage, PromptStage):
pass
yield FlowBundleEntry.from_model(stage)
def walk_stage_bindings(self) -> Iterator[FlowBundleEntry]:
"""Convert all bindings attached to self.flow into FlowBundleEntry objects"""
bindings = FlowStageBinding.objects.filter(target=self.flow).select_related()
for binding in bindings:
yield FlowBundleEntry.from_model(binding)
def walk_policies(self) -> Iterator[FlowBundleEntry]:
"""Walk over all policies and their respective bindings"""
pbm_uuids = [self.flow.pbm_uuid]
for stage_subclass in Stage.__subclasses__():
if issubclass(stage_subclass, PolicyBindingModel):
pbm_uuids += stage_subclass.objects.filter(flow=self.flow).values_list(
"pbm_uuid", flat=True
)
pbm_uuids += FlowStageBinding.objects.filter(target=self.flow).values_list(
"pbm_uuid", flat=True
)
policies = Policy.objects.filter(bindings__in=pbm_uuids).select_related()
for policy in policies:
yield FlowBundleEntry.from_model(policy)
bindings = PolicyBinding.objects.filter(target__in=pbm_uuids).select_related()
for binding in bindings:
yield FlowBundleEntry.from_model(binding)
def walk_stage_prompts(self) -> Iterator[FlowBundleEntry]:
"""Walk over all prompts associated with any PromptStages"""
prompt_stages = PromptStage.objects.filter(flow=self.flow)
for stage in prompt_stages:
for prompt in stage.fields.all():
yield FlowBundleEntry.from_model(prompt)
def export(self) -> FlowBundle:
"""Create a list of all objects including the flow"""
bundle = FlowBundle()
bundle.entries.append(FlowBundleEntry.from_model(self.flow))
if self.with_stage_prompts:
bundle.entries.extend(self.walk_stage_prompts())
bundle.entries.extend(self.walk_stages())
bundle.entries.extend(self.walk_stage_bindings())
if self.with_policies:
bundle.entries.extend(self.walk_policies())
return bundle
def export_to_string(self) -> str:
"""Call export and convert it to json"""
return dumps(self.export(), cls=DataclassEncoder)

View File

@ -0,0 +1,134 @@
"""Flow importer"""
from json import loads
from typing import Type
from dacite import from_dict
from dacite.exceptions import DaciteError
from django.apps import apps
from django.db import transaction
from django.db.models import Model
from rest_framework.serializers import BaseSerializer, Serializer
from structlog import BoundLogger, get_logger
from passbook.flows.models import Flow, FlowStageBinding, Stage
from passbook.flows.transfer.common import (
EntryInvalidError,
FlowBundle,
FlowBundleEntry,
)
from passbook.lib.models import SerializerModel
from passbook.policies.models import Policy, PolicyBinding, PolicyBindingModel
from passbook.stages.prompt.models import Prompt
ALLOWED_MODELS = (Flow, FlowStageBinding, Stage, Policy, PolicyBinding, Prompt)
class FlowImporter:
"""Import Flow from json"""
__import: FlowBundle
logger: BoundLogger
def __init__(self, json_input: str):
self.logger = get_logger()
import_dict = loads(json_input)
try:
self.__import = from_dict(FlowBundle, import_dict)
except DaciteError as exc:
raise EntryInvalidError from exc
def validate(self) -> bool:
"""Validate loaded flow export, ensure all models are allowed
and serializers have no errors"""
if self.__import.version != 1:
self.logger.warning("Invalid bundle version")
return False
for entry in self.__import.entries:
try:
self._validate_single(entry)
except EntryInvalidError as exc:
self.logger.warning(exc)
return False
return True
def __get_pk_filed(self, model_class: Type[Model]) -> str:
fields = model_class._meta.get_fields()
pks = []
for field in fields:
# Ignore base PK from pbm as that isn't the same pk we exported
if field.model in [PolicyBindingModel]:
continue
# Ignore primary keys with _ptr suffix as those are surrogate and not what we exported
if field.name.endswith("_ptr"):
continue
if hasattr(field, "primary_key"):
if field.primary_key:
pks.append(field.name)
if len(pks) > 1:
self.logger.debug(
"Found more than one fields with primary_key=True, using pk", pks=pks
)
return "pk"
return pks[0]
def _validate_single(self, entry: FlowBundleEntry) -> BaseSerializer:
"""Validate a single entry"""
model_app_label, model_name = entry.model.split(".")
model: SerializerModel = apps.get_model(model_app_label, model_name)
if not isinstance(model(), ALLOWED_MODELS):
raise EntryInvalidError(f"Model {model} not allowed")
# If we try to validate without referencing a possible instance
# we'll get a duplicate error, hence we load the model here and return
# the full serializer for later usage
existing_models = model.objects.filter(pk=entry.identifier)
serializer_kwargs = {"data": entry.attrs}
if existing_models.exists():
self.logger.debug(
"initialise serializer with instance", instance=existing_models.first()
)
serializer_kwargs["instance"] = existing_models.first()
else:
self.logger.debug("initialise new instance", pk=entry.identifier)
serializer: Serializer = model().serializer(**serializer_kwargs)
is_valid = serializer.is_valid()
if not is_valid:
raise EntryInvalidError(f"Serializer errors {serializer.errors}")
if not existing_models.exists():
# only insert the PK if we're creating a new model, otherwise we get
# an integrity error
model_pk = self.__get_pk_filed(model)
serializer.validated_data[model_pk] = entry.identifier
return serializer
def apply(self) -> bool:
"""Apply (create/update) flow json, in database transaction"""
transaction.set_autocommit(False)
successful = self._apply_models()
if not successful:
self.logger.debug("Reverting changes due to error")
transaction.rollback()
transaction.set_autocommit(True)
return False
self.logger.debug("Committing changes")
transaction.commit()
transaction.set_autocommit(True)
return True
def _apply_models(self) -> bool:
"""Apply (create/update) flow json"""
for entry in self.__import.entries:
model_app_label, model_name = entry.model.split(".")
model: SerializerModel = apps.get_model(model_app_label, model_name)
# Validate each single entry
try:
serializer = self._validate_single(entry)
except EntryInvalidError as exc:
self.logger.error("entry not valid", entry=entry, error=exc)
return False
model = serializer.save()
self.logger.debug("updated model", model=model, pk=model.pk)
return True

View File

@ -1,6 +1,19 @@
"""Generic models""" """Generic models"""
from django.db import models from django.db import models
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
from rest_framework.serializers import BaseSerializer
class SerializerModel(models.Model):
"""Base Abstract Model which has a serializer"""
@property
def serializer(self) -> BaseSerializer:
"""Get serializer for this model"""
raise NotImplementedError
class Meta:
abstract = True
class CreatedUpdatedModel(models.Model): class CreatedUpdatedModel(models.Model):

View File

@ -6,6 +6,7 @@ from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import BaseSerializer
from structlog import get_logger from structlog import get_logger
from passbook.policies.models import Policy from passbook.policies.models import Policy
@ -24,6 +25,12 @@ class DummyPolicy(Policy):
wait_min = models.IntegerField(default=5) wait_min = models.IntegerField(default=5)
wait_max = models.IntegerField(default=30) wait_max = models.IntegerField(default=30)
@property
def serializer(self) -> BaseSerializer:
from passbook.policies.dummy.api import DummyPolicySerializer
return DummyPolicySerializer
def form(self) -> Type[ModelForm]: def form(self) -> Type[ModelForm]:
from passbook.policies.dummy.forms import DummyPolicyForm from passbook.policies.dummy.forms import DummyPolicyForm

View File

@ -6,6 +6,7 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.serializers import BaseSerializer
from structlog import get_logger from structlog import get_logger
from passbook.policies.models import Policy from passbook.policies.models import Policy
@ -21,6 +22,12 @@ class PasswordExpiryPolicy(Policy):
deny_only = models.BooleanField(default=False) deny_only = models.BooleanField(default=False)
days = models.IntegerField() days = models.IntegerField()
@property
def serializer(self) -> BaseSerializer:
from passbook.policies.expiry.api import PasswordExpiryPolicySerializer
return PasswordExpiryPolicySerializer
def form(self) -> Type[ModelForm]: def form(self) -> Type[ModelForm]:
from passbook.policies.expiry.forms import PasswordExpiryPolicyForm from passbook.policies.expiry.forms import PasswordExpiryPolicyForm

View File

@ -4,6 +4,7 @@ from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.serializers import BaseSerializer
from passbook.policies.expression.evaluator import PolicyEvaluator from passbook.policies.expression.evaluator import PolicyEvaluator
from passbook.policies.models import Policy from passbook.policies.models import Policy
@ -15,6 +16,12 @@ class ExpressionPolicy(Policy):
expression = models.TextField() expression = models.TextField()
@property
def serializer(self) -> BaseSerializer:
from passbook.policies.expression.api import ExpressionPolicySerializer
return ExpressionPolicySerializer
def form(self) -> Type[ModelForm]: def form(self) -> Type[ModelForm]:
from passbook.policies.expression.forms import ExpressionPolicyForm from passbook.policies.expression.forms import ExpressionPolicyForm

View File

@ -4,6 +4,7 @@ from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.serializers import BaseSerializer
from passbook.core.models import Group from passbook.core.models import Group
from passbook.policies.models import Policy from passbook.policies.models import Policy
@ -15,6 +16,14 @@ class GroupMembershipPolicy(Policy):
group = models.ForeignKey(Group, null=True, blank=True, on_delete=models.SET_NULL) group = models.ForeignKey(Group, null=True, blank=True, on_delete=models.SET_NULL)
@property
def serializer(self) -> BaseSerializer:
from passbook.policies.group_membership.api import (
GroupMembershipPolicySerializer,
)
return GroupMembershipPolicySerializer
def form(self) -> Type[ModelForm]: def form(self) -> Type[ModelForm]:
from passbook.policies.group_membership.forms import GroupMembershipPolicyForm from passbook.policies.group_membership.forms import GroupMembershipPolicyForm

View File

@ -6,6 +6,7 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from requests import get from requests import get
from rest_framework.serializers import BaseSerializer
from structlog import get_logger from structlog import get_logger
from passbook.policies.models import Policy, PolicyResult from passbook.policies.models import Policy, PolicyResult
@ -27,6 +28,12 @@ class HaveIBeenPwendPolicy(Policy):
allowed_count = models.IntegerField(default=0) allowed_count = models.IntegerField(default=0)
@property
def serializer(self) -> BaseSerializer:
from passbook.policies.hibp.api import HaveIBeenPwendPolicySerializer
return HaveIBeenPwendPolicySerializer
def form(self) -> Type[ModelForm]: def form(self) -> Type[ModelForm]:
from passbook.policies.hibp.forms import HaveIBeenPwnedPolicyForm from passbook.policies.hibp.forms import HaveIBeenPwnedPolicyForm

View File

@ -6,11 +6,13 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
from rest_framework.serializers import BaseSerializer
from passbook.lib.models import ( from passbook.lib.models import (
CreatedUpdatedModel, CreatedUpdatedModel,
InheritanceAutoManager, InheritanceAutoManager,
InheritanceForeignKey, InheritanceForeignKey,
SerializerModel,
) )
from passbook.policies.exceptions import PolicyException from passbook.policies.exceptions import PolicyException
from passbook.policies.types import PolicyRequest, PolicyResult from passbook.policies.types import PolicyRequest, PolicyResult
@ -32,7 +34,7 @@ class PolicyBindingModel(models.Model):
verbose_name_plural = _("Policy Binding Models") verbose_name_plural = _("Policy Binding Models")
class PolicyBinding(models.Model): class PolicyBinding(SerializerModel):
"""Relationship between a Policy and a PolicyBindingModel.""" """Relationship between a Policy and a PolicyBindingModel."""
policy_binding_uuid = models.UUIDField( policy_binding_uuid = models.UUIDField(
@ -55,6 +57,12 @@ class PolicyBinding(models.Model):
order = models.IntegerField() order = models.IntegerField()
@property
def serializer(self) -> BaseSerializer:
from passbook.policies.api import PolicyBindingSerializer
return PolicyBindingSerializer
def __str__(self) -> str: def __str__(self) -> str:
return f"PolicyBinding policy={self.policy} target={self.target} order={self.order}" return f"PolicyBinding policy={self.policy} target={self.target} order={self.order}"
@ -65,7 +73,7 @@ class PolicyBinding(models.Model):
unique_together = ("policy", "target", "order") unique_together = ("policy", "target", "order")
class Policy(CreatedUpdatedModel): class Policy(SerializerModel, CreatedUpdatedModel):
"""Policies which specify if a user is authorized to use an Application. Can be overridden by """Policies which specify if a user is authorized to use an Application. Can be overridden by
other types to add other fields, more logic, etc.""" other types to add other fields, more logic, etc."""

View File

@ -5,6 +5,7 @@ from typing import Type
from django.db import models from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.serializers import BaseSerializer
from structlog import get_logger from structlog import get_logger
from passbook.policies.models import Policy from passbook.policies.models import Policy
@ -30,6 +31,12 @@ class PasswordPolicy(Policy):
symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ") symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ")
error_message = models.TextField() error_message = models.TextField()
@property
def serializer(self) -> BaseSerializer:
from passbook.policies.password.api import PasswordPolicySerializer
return PasswordPolicySerializer
def form(self) -> Type[ModelForm]: def form(self) -> Type[ModelForm]:
from passbook.policies.password.forms import PasswordPolicyForm from passbook.policies.password.forms import PasswordPolicyForm

View File

@ -5,6 +5,7 @@ from django.core.cache import cache
from django.db import models from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from rest_framework.serializers import BaseSerializer
from passbook.core.models import User from passbook.core.models import User
from passbook.lib.utils.http import get_client_ip from passbook.lib.utils.http import get_client_ip
@ -22,6 +23,12 @@ class ReputationPolicy(Policy):
check_username = models.BooleanField(default=True) check_username = models.BooleanField(default=True)
threshold = models.IntegerField(default=-5) threshold = models.IntegerField(default=-5)
@property
def serializer(self) -> BaseSerializer:
from passbook.policies.reputation.api import ReputationPolicySerializer
return ReputationPolicySerializer
def form(self) -> Type[ModelForm]: def form(self) -> Type[ModelForm]:
from passbook.policies.reputation.forms import ReputationPolicyForm from passbook.policies.reputation.forms import ReputationPolicyForm

View File

@ -5,6 +5,7 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -23,6 +24,12 @@ class CaptchaStage(Stage):
) )
) )
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.captcha.api import CaptchaStageSerializer
return CaptchaStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.captcha.stage import CaptchaStageView from passbook.stages.captcha.stage import CaptchaStageView

View File

@ -5,6 +5,7 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.core.models import Application, ExpiringModel, User from passbook.core.models import Application, ExpiringModel, User
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -37,6 +38,12 @@ class ConsentStage(Stage):
), ),
) )
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.consent.api import ConsentStageSerializer
return ConsentStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.consent.stage import ConsentStageView from passbook.stages.consent.stage import ConsentStageView

View File

@ -4,6 +4,7 @@ from typing import Type
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -13,6 +14,12 @@ class DummyStage(Stage):
__debug_only__ = True __debug_only__ = True
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.dummy.api import DummyStageSerializer
return DummyStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.dummy.stage import DummyStageView from passbook.stages.dummy.stage import DummyStageView

View File

@ -7,6 +7,7 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -44,6 +45,12 @@ class EmailStage(Stage):
choices=EmailTemplates.choices, default=EmailTemplates.PASSWORD_RESET choices=EmailTemplates.choices, default=EmailTemplates.PASSWORD_RESET
) )
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.email.api import EmailStageSerializer
return EmailStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.email.stage import EmailStageView from passbook.stages.email.stage import EmailStageView

View File

@ -6,6 +6,7 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.flows.models import Flow, Stage from passbook.flows.models import Flow, Stage
@ -56,6 +57,12 @@ class IdentificationStage(Stage):
), ),
) )
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.identification.api import IdentificationStageSerializer
return IdentificationStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.identification.stage import IdentificationStageView from passbook.stages.identification.stage import IdentificationStageView

View File

@ -6,6 +6,7 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.core.models import User from passbook.core.models import User
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -26,6 +27,12 @@ class InvitationStage(Stage):
), ),
) )
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.invitation.api import InvitationStageSerializer
return InvitationStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.invitation.stage import InvitationStageView from passbook.stages.invitation.stage import InvitationStageView

View File

@ -6,6 +6,7 @@ from django.forms import ModelForm
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.core.types import UIUserSettings from passbook.core.types import UIUserSettings
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -16,6 +17,12 @@ class OTPStaticStage(Stage):
token_count = models.IntegerField(default=6) token_count = models.IntegerField(default=6)
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.otp_static.api import OTPStaticStageSerializer
return OTPStaticStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.otp_static.stage import OTPStaticStageView from passbook.stages.otp_static.stage import OTPStaticStageView

View File

@ -6,6 +6,7 @@ from django.forms import ModelForm
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.core.types import UIUserSettings from passbook.core.types import UIUserSettings
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -23,6 +24,12 @@ class OTPTimeStage(Stage):
digits = models.IntegerField(choices=TOTPDigits.choices) digits = models.IntegerField(choices=TOTPDigits.choices)
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.otp_time.api import OTPTimeStageSerializer
return OTPTimeStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.otp_time.stage import OTPTimeStageView from passbook.stages.otp_time.stage import OTPTimeStageView

View File

@ -5,6 +5,7 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.flows.models import NotConfiguredAction, Stage from passbook.flows.models import NotConfiguredAction, Stage
@ -16,6 +17,12 @@ class OTPValidateStage(Stage):
choices=NotConfiguredAction.choices, default=NotConfiguredAction.SKIP choices=NotConfiguredAction.choices, default=NotConfiguredAction.SKIP
) )
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.otp_validate.api import OTPValidateStageSerializer
return OTPValidateStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.otp_validate.stage import OTPValidateStageView from passbook.stages.otp_validate.stage import OTPValidateStageView

View File

@ -8,6 +8,7 @@ from django.shortcuts import reverse
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.core.types import UIUserSettings from passbook.core.types import UIUserSettings
from passbook.flows.models import Flow, Stage from passbook.flows.models import Flow, Stage
@ -35,6 +36,12 @@ class PasswordStage(Stage):
), ),
) )
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.password.api import PasswordStageSerializer
return PasswordStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.password.stage import PasswordStageView from passbook.stages.password.stage import PasswordStageView

View File

@ -7,8 +7,10 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.flows.models import Stage from passbook.flows.models import Stage
from passbook.lib.models import SerializerModel
from passbook.policies.models import PolicyBindingModel from passbook.policies.models import PolicyBindingModel
from passbook.stages.prompt.widgets import HorizontalRuleWidget, StaticTextWidget from passbook.stages.prompt.widgets import HorizontalRuleWidget, StaticTextWidget
@ -40,7 +42,7 @@ class FieldTypes(models.TextChoices):
STATIC = "static", _("Static: Static value, displayed as-is.") STATIC = "static", _("Static: Static value, displayed as-is.")
class Prompt(models.Model): class Prompt(SerializerModel):
"""Single Prompt, part of a prompt stage.""" """Single Prompt, part of a prompt stage."""
prompt_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4) prompt_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
@ -51,10 +53,16 @@ class Prompt(models.Model):
label = models.TextField() label = models.TextField()
type = models.CharField(max_length=100, choices=FieldTypes.choices) type = models.CharField(max_length=100, choices=FieldTypes.choices)
required = models.BooleanField(default=True) required = models.BooleanField(default=True)
placeholder = models.TextField() placeholder = models.TextField(blank=True)
order = models.IntegerField(default=0) order = models.IntegerField(default=0)
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.prompt.api import PromptSerializer
return PromptSerializer
@property @property
def field(self): def field(self):
"""Return instantiated form input field""" """Return instantiated form input field"""
@ -120,6 +128,12 @@ class PromptStage(PolicyBindingModel, Stage):
fields = models.ManyToManyField(Prompt) fields = models.ManyToManyField(Prompt)
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.prompt.api import PromptStageSerializer
return PromptStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.prompt.stage import PromptStageView from passbook.stages.prompt.stage import PromptStageView

View File

@ -4,6 +4,7 @@ from typing import Type
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -12,6 +13,12 @@ class UserDeleteStage(Stage):
"""Deletes the currently pending user without confirmation. """Deletes the currently pending user without confirmation.
Use with caution.""" Use with caution."""
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.user_delete.api import UserDeleteStageSerializer
return UserDeleteStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.user_delete.stage import UserDeleteStageView from passbook.stages.user_delete.stage import UserDeleteStageView

View File

@ -5,6 +5,7 @@ from django.db import models
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -20,6 +21,12 @@ class UserLoginStage(Stage):
), ),
) )
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.user_login.api import UserLoginStageSerializer
return UserLoginStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.user_login.stage import UserLoginStageView from passbook.stages.user_login.stage import UserLoginStageView

View File

@ -4,6 +4,7 @@ from typing import Type
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -11,6 +12,12 @@ from passbook.flows.models import Stage
class UserLogoutStage(Stage): class UserLogoutStage(Stage):
"""Resets the users current session.""" """Resets the users current session."""
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.user_logout.api import UserLogoutStageSerializer
return UserLogoutStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.user_logout.stage import UserLogoutStageView from passbook.stages.user_logout.stage import UserLogoutStageView

View File

@ -4,6 +4,7 @@ from typing import Type
from django.forms import ModelForm from django.forms import ModelForm
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.flows.models import Stage from passbook.flows.models import Stage
@ -12,6 +13,12 @@ class UserWriteStage(Stage):
"""Writes currently pending data into the pending user, or if no user exists, """Writes currently pending data into the pending user, or if no user exists,
creates a new user with the data.""" creates a new user with the data."""
@property
def serializer(self) -> BaseSerializer:
from passbook.stages.user_write.api import UserWriteStageSerializer
return UserWriteStageSerializer
def type(self) -> Type[View]: def type(self) -> Type[View]:
from passbook.stages.user_write.stage import UserWriteStageView from passbook.stages.user_write.stage import UserWriteStageView

View File

@ -6859,7 +6859,6 @@ definitions:
- field_key - field_key
- label - label
- type - type
- placeholder
type: object type: object
properties: properties:
pk: pk:
@ -6900,7 +6899,6 @@ definitions:
placeholder: placeholder:
title: Placeholder title: Placeholder
type: string type: string
minLength: 1
order: order:
title: Order title: Order
type: integer type: integer