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:
		
							
								
								
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							| @ -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
									
									
									
								
							
							
						
						
									
										97
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							| @ -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": { | ||||||
|  | |||||||
| @ -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}" | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										119
									
								
								passbook/flows/tests/test_transfer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								passbook/flows/tests/test_transfer.py
									
									
									
									
									
										Normal 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()) | ||||||
							
								
								
									
										0
									
								
								passbook/flows/transfer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								passbook/flows/transfer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										59
									
								
								passbook/flows/transfer/common.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								passbook/flows/transfer/common.py
									
									
									
									
									
										Normal 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""" | ||||||
							
								
								
									
										78
									
								
								passbook/flows/transfer/exporter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								passbook/flows/transfer/exporter.py
									
									
									
									
									
										Normal 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) | ||||||
							
								
								
									
										134
									
								
								passbook/flows/transfer/importer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								passbook/flows/transfer/importer.py
									
									
									
									
									
										Normal 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 | ||||||
| @ -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): | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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.""" | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  |  | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L