Compare commits
35 Commits
version/0.
...
version/0.
Author | SHA1 | Date | |
---|---|---|---|
ed1fcc3930 | |||
c22ddc5394 | |||
0544864a3f | |||
0b9fc9e444 | |||
e862b97005 | |||
cffe09b02e | |||
846a86fb62 | |||
463c130351 | |||
ffca957838 | |||
543e949a48 | |||
feb80049aa | |||
5c59c8ccb6 | |||
1fadd82c65 | |||
7e7736126d | |||
5e0915afce | |||
bf6c9e8c4a | |||
3353aa0298 | |||
d4cb1a98c7 | |||
13f4ea0b8b | |||
261d57ad7b | |||
4086252979 | |||
8bdf12cff1 | |||
65a065c4ee | |||
a691ee529c | |||
f1c4a62612 | |||
358e39ced0 | |||
48c3f68cfc | |||
1849a7c383 | |||
37111fd07b | |||
143a575369 | |||
344a8817c3 | |||
3afb0d4f6d | |||
c9714893bb | |||
3185a86b22 | |||
a53f7a49ac |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.6.8-beta
|
||||
current_version = 0.7.0-beta
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
||||
|
@ -21,6 +21,7 @@ exclude_lines =
|
||||
def __str__
|
||||
def __repr__
|
||||
if self\.debug
|
||||
if TYPE_CHECKING
|
||||
|
||||
# Don't complain if tests don't hit defensive assertion code:
|
||||
raise AssertionError
|
||||
|
@ -80,9 +80,9 @@ pylint:
|
||||
- redis:latest
|
||||
coverage:
|
||||
script:
|
||||
- coverage run manage.py test
|
||||
- coverage run --concurrency=multiprocessing manage.py test
|
||||
- coverage combine
|
||||
- coverage report
|
||||
- coverage html
|
||||
stage: test
|
||||
services:
|
||||
- postgres:latest
|
||||
@ -96,7 +96,7 @@ build-passbook-server:
|
||||
before_script:
|
||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.6.8-beta
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.7.0-beta
|
||||
only:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
@ -108,7 +108,7 @@ build-passbook-static:
|
||||
before_script:
|
||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/static.Dockerfile --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.6.8-beta
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/static.Dockerfile --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.7.0-beta
|
||||
only:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
@ -136,12 +136,17 @@ package-helm:
|
||||
- /^version/.*$/
|
||||
|
||||
notify-sentry:
|
||||
image: alpine
|
||||
image: getsentry/sentry-cli
|
||||
stage: post-release
|
||||
variables:
|
||||
SENTRY_URL: https://sentry.beryju.org
|
||||
SENTRY_ORG: beryjuorg
|
||||
SENTRY_PROJECT: passbook
|
||||
before_script:
|
||||
- apk add curl
|
||||
script:
|
||||
- "curl $SENTRY_RELEASE -X POST -H 'Content-Type: application/json' -d '{\"version\": \"passbook@0.6.8-beta\"}'"
|
||||
- sentry-cli releases new passbook@0.7.0-beta
|
||||
- sentry-cli releases set-commits --auto passbook@0.7.0-beta
|
||||
only:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
|
8
Pipfile
8
Pipfile
@ -10,7 +10,7 @@ defusedxml = "*"
|
||||
django = "*"
|
||||
kombu = "==4.5.0"
|
||||
django-cors-middleware = "*"
|
||||
django-filters = "*"
|
||||
django-filter = "*"
|
||||
django-ipware = "*"
|
||||
django-model-utils = "*"
|
||||
django-oauth-toolkit = "*"
|
||||
@ -19,6 +19,7 @@ django-otp = "*"
|
||||
django-recaptcha = "*"
|
||||
django-redis = "*"
|
||||
django-rest-framework = "*"
|
||||
djangorestframework-guardian = "*"
|
||||
drf-yasg = "*"
|
||||
ldap3 = "*"
|
||||
lxml = "*"
|
||||
@ -36,6 +37,11 @@ signxml = "*"
|
||||
urllib3 = {extras = ["secure"],version = "*"}
|
||||
structlog = "*"
|
||||
pyuwsgi = "*"
|
||||
django-guardian = "*"
|
||||
django-dbbackup = "*"
|
||||
boto3 = "*"
|
||||
django-storages = "*"
|
||||
swagger-spec-validator = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
|
382
Pipfile.lock
generated
382
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "94b3d5140f0c31dac1fc77af75a0df30ae4fb0571bf6b7fcd722487c63dc1872"
|
||||
"sha256": "045f299c421b29003cbc657397d79fcb1c16a0bce9991aaeadc76f55339f7f09"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@ -25,17 +25,17 @@
|
||||
},
|
||||
"asn1crypto": {
|
||||
"hashes": [
|
||||
"sha256:0b199f211ae690df3db4fd6c1c4ff976497fb1da689193e368eedbadc53d9292",
|
||||
"sha256:bca90060bd995c3f62c4433168eab407e44bdbdb567b3f3a396a676c1a4c4a3f"
|
||||
"sha256:7bb1cc02a5620b3d72da4ba070bda2f44f0e61b44dee910a302eddff802b6fb5",
|
||||
"sha256:87620880a477123e01177a1f73d0f327210b43a3cdbd714efcd2fa49a8d7b384"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:ec20e7a4825331c1b5ebf261d111e16fa9612c1f7a5e1f884f12bd53a664dfd2",
|
||||
"sha256:f913492e1663d3c36f502e5e9ba6cd13cf19d7fab50aa13239e420fef95e1396"
|
||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||
],
|
||||
"version": "==19.2.0"
|
||||
"version": "==19.3.0"
|
||||
},
|
||||
"billiard": {
|
||||
"hashes": [
|
||||
@ -44,6 +44,21 @@
|
||||
],
|
||||
"version": "==3.6.1.0"
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:7fc97cb2c9cdff905e950750c8e8b23b872a84696158a28852355dc4b712ba3a",
|
||||
"sha256:818c56a317c176142dbf1dca3f5b4366c80460c6cc3c4efe22f0bde736571283"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.10.2"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:8223485841ef4731a5d4943a733295ba69d0005c4ae64c468308cc07f6960d39",
|
||||
"sha256:f8e12dc6e536ea512f0ad25b74e7eecdf5d9e09ae92b5de236b535bee7804d5b"
|
||||
],
|
||||
"version": "==1.13.2"
|
||||
},
|
||||
"celery": {
|
||||
"hashes": [
|
||||
"sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9",
|
||||
@ -61,36 +76,40 @@
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774",
|
||||
"sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d",
|
||||
"sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90",
|
||||
"sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b",
|
||||
"sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63",
|
||||
"sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45",
|
||||
"sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25",
|
||||
"sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3",
|
||||
"sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b",
|
||||
"sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647",
|
||||
"sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016",
|
||||
"sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4",
|
||||
"sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb",
|
||||
"sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753",
|
||||
"sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7",
|
||||
"sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9",
|
||||
"sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f",
|
||||
"sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8",
|
||||
"sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f",
|
||||
"sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc",
|
||||
"sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42",
|
||||
"sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3",
|
||||
"sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909",
|
||||
"sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45",
|
||||
"sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d",
|
||||
"sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512",
|
||||
"sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff",
|
||||
"sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201"
|
||||
"sha256:00d890313797d9fe4420506613384b43099ad7d2b905c0752dbcc3a6f14d80fa",
|
||||
"sha256:0cf9e550ac6c5e57b713437e2f4ac2d7fd0cd10336525a27224f5fc1ec2ee59a",
|
||||
"sha256:0ea23c9c0cdd6778146a50d867d6405693ac3b80a68829966c98dd5e1bbae400",
|
||||
"sha256:193697c2918ecdb3865acf6557cddf5076bb39f1f654975e087b67efdff83365",
|
||||
"sha256:1ae14b542bf3b35e5229439c35653d2ef7d8316c1fffb980f9b7647e544baa98",
|
||||
"sha256:1e389e069450609c6ffa37f21f40cce36f9be7643bbe5051ab1de99d5a779526",
|
||||
"sha256:263242b6ace7f9cd4ea401428d2d45066b49a700852334fd55311bde36dcda14",
|
||||
"sha256:33142ae9807665fa6511cfa9857132b2c3ee6ddffb012b3f0933fc11e1e830d5",
|
||||
"sha256:364f8404034ae1b232335d8c7f7b57deac566f148f7222cef78cf8ae28ef764e",
|
||||
"sha256:47368f69fe6529f8f49a5d146ddee713fc9057e31d61e8b6dc86a6a5e38cecc1",
|
||||
"sha256:4895640844f17bec32943995dc8c96989226974dfeb9dd121cc45d36e0d0c434",
|
||||
"sha256:558b3afef987cf4b17abd849e7bedf64ee12b28175d564d05b628a0f9355599b",
|
||||
"sha256:5ba86e1d80d458b338bda676fd9f9d68cb4e7a03819632969cf6d46b01a26730",
|
||||
"sha256:63424daa6955e6b4c70dc2755897f5be1d719eabe71b2625948b222775ed5c43",
|
||||
"sha256:6381a7d8b1ebd0bc27c3bc85bc1bfadbb6e6f756b4d4db0aa1425c3719ba26b4",
|
||||
"sha256:6381ab708158c4e1639da1f2a7679a9bbe3e5a776fc6d1fd808076f0e3145331",
|
||||
"sha256:6fd58366747debfa5e6163ada468a90788411f10c92597d3b0a912d07e580c36",
|
||||
"sha256:728ec653964655d65408949b07f9b2219df78badd601d6c49e28d604efe40599",
|
||||
"sha256:7cfcfda59ef1f95b9f729c56fe8a4041899f96b72685d36ef16a3440a0f85da8",
|
||||
"sha256:819f8d5197c2684524637f940445c06e003c4a541f9983fd30d6deaa2a5487d8",
|
||||
"sha256:825ecffd9574557590e3225560a8a9d751f6ffe4a49e3c40918c9969b93395fa",
|
||||
"sha256:8a2bcae2258d00fcfc96a9bde4a6177bc4274fe033f79311c5dd3d3148c26518",
|
||||
"sha256:9009e917d8f5ef780c2626e29b6bc126f4cb2a4d43ca67aa2b40f2a5d6385e78",
|
||||
"sha256:9c77564a51d4d914ed5af096cd9843d90c45b784b511723bd46a8a9d09cf16fc",
|
||||
"sha256:a19089fa74ed19c4fe96502a291cfdb89223a9705b1d73b3005df4256976142e",
|
||||
"sha256:a40ed527bffa2b7ebe07acc5a3f782da072e262ca994b4f2085100b5a444bbb2",
|
||||
"sha256:b8f09f21544b9899defb09afbdaeb200e6a87a2b8e604892940044cf94444644",
|
||||
"sha256:bb75ba21d5716abc41af16eac1145ab2e471deedde1f22c6f99bd9f995504df0",
|
||||
"sha256:e22a00c0c81ffcecaf07c2bfb3672fa372c50e2bd1024ffee0da191c1b27fc71",
|
||||
"sha256:e55b5a746fb77f10c83e8af081979351722f6ea48facea79d470b3731c7b2891",
|
||||
"sha256:ec2fa3ee81707a5232bf2dfbd6623fdb278e070d596effc7e2d788f2ada71a05",
|
||||
"sha256:fd82eb4694be712fcae03c717ca2e0fc720657ac226b80bbb597e971fc6928c2"
|
||||
],
|
||||
"version": "==1.12.3"
|
||||
"version": "==1.13.1"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@ -101,10 +120,10 @@
|
||||
},
|
||||
"cheroot": {
|
||||
"hashes": [
|
||||
"sha256:3ff64073efa35b39d5e107410f5c79664dc8c6c5990651e970740c80ab8878a8",
|
||||
"sha256:d523a1525258730026aa35b86c8c47c8d0e3892fb89f0f39157d4b32a50edf05"
|
||||
"sha256:5b525b3e4a755adf78070ab54c1821fb860d4255a9317dba2b88eb2df2441cff",
|
||||
"sha256:5d73fff8f99e6a35325a22f61e6a942cb7d378ac9ce3621f08b560720dfe4a7f"
|
||||
],
|
||||
"version": "==8.1.0"
|
||||
"version": "==8.2.1"
|
||||
},
|
||||
"cherrypy": {
|
||||
"hashes": [
|
||||
@ -130,24 +149,29 @@
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c",
|
||||
"sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643",
|
||||
"sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216",
|
||||
"sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799",
|
||||
"sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a",
|
||||
"sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9",
|
||||
"sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc",
|
||||
"sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8",
|
||||
"sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53",
|
||||
"sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1",
|
||||
"sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609",
|
||||
"sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292",
|
||||
"sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e",
|
||||
"sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6",
|
||||
"sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed",
|
||||
"sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d"
|
||||
"sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c",
|
||||
"sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595",
|
||||
"sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad",
|
||||
"sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651",
|
||||
"sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2",
|
||||
"sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff",
|
||||
"sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d",
|
||||
"sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42",
|
||||
"sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d",
|
||||
"sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e",
|
||||
"sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912",
|
||||
"sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793",
|
||||
"sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13",
|
||||
"sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7",
|
||||
"sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0",
|
||||
"sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879",
|
||||
"sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f",
|
||||
"sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9",
|
||||
"sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2",
|
||||
"sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf",
|
||||
"sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"
|
||||
],
|
||||
"version": "==2.7"
|
||||
"version": "==2.8"
|
||||
},
|
||||
"defusedxml": {
|
||||
"hashes": [
|
||||
@ -173,12 +197,28 @@
|
||||
"index": "pypi",
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"django-filters": {
|
||||
"django-dbbackup": {
|
||||
"hashes": [
|
||||
"sha256:1a9799a41106dc53ed894e952a24e8dee9b4fb37f010f22d178c09c90c61d711"
|
||||
"sha256:9470e5d8bdaee4feb878b1b66c59eb9b27a131cccd648bf7cbfe70930acd4fc0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.2.1"
|
||||
"version": "==3.2.0"
|
||||
},
|
||||
"django-filter": {
|
||||
"hashes": [
|
||||
"sha256:558c727bce3ffa89c4a7a0b13bc8976745d63e5fd576b3a9a851650ef11c401b",
|
||||
"sha256:c3deb57f0dd7ff94d7dce52a047516822013e2b441bed472b722a317658cfd14"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"django-guardian": {
|
||||
"hashes": [
|
||||
"sha256:8cf4efd67a863eb32beafd4335a38ffb083630f8ab2045212d27f8f9c3abe5a6",
|
||||
"sha256:e638c9a23eeac534bb68b133975539ed8782f733ab6f35c0b23b4c39cd06b1bb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"django-ipware": {
|
||||
"hashes": [
|
||||
@ -211,11 +251,11 @@
|
||||
},
|
||||
"django-otp": {
|
||||
"hashes": [
|
||||
"sha256:79c8253be97246df86540d551dc705e8fe6ca76af8e8c77f78314cd1b513c2cf",
|
||||
"sha256:c5bf3916dca5d53cb377aa6dea40aa785c164013fbf750384137362dfa278cf5"
|
||||
"sha256:0009211222388d8ba4a4840b6de21ff24461fd4aad6c6c194926e3091ac65f06",
|
||||
"sha256:a9d39b35f7aa8eee82d6d9769d8004ec538e7d7c2f5a1c5e5525cda90d0e9b69"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.2"
|
||||
"version": "==0.7.3"
|
||||
},
|
||||
"django-recaptcha": {
|
||||
"hashes": [
|
||||
@ -240,6 +280,14 @@
|
||||
"index": "pypi",
|
||||
"version": "==0.1.0"
|
||||
},
|
||||
"django-storages": {
|
||||
"hashes": [
|
||||
"sha256:87287b7ad2e789cd603373439994e1ac6f94d9dc2e5f8173d2a87aa3ed458bd9",
|
||||
"sha256:f3b3def96493d3ccde37b864cea376472baf6e8a596504b209278801c510b807"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.7.2"
|
||||
},
|
||||
"djangorestframework": {
|
||||
"hashes": [
|
||||
"sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8",
|
||||
@ -247,6 +295,22 @@
|
||||
],
|
||||
"version": "==3.10.3"
|
||||
},
|
||||
"djangorestframework-guardian": {
|
||||
"hashes": [
|
||||
"sha256:1883756452d9bfcc2a51fb4e039a6837a8f6697c756447aa83af085749b59330",
|
||||
"sha256:3bd3dd6ea58e1bceca5048faf6f8b1a93bb5dcff30ba5eb91b9a0e190a48a0c7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.3.0"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0",
|
||||
"sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827",
|
||||
"sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"
|
||||
],
|
||||
"version": "==0.15.2"
|
||||
},
|
||||
"drf-yasg": {
|
||||
"hashes": [
|
||||
"sha256:4cfec631880ae527a91ec7cd3241aea2f82189f59e2f089119aa687761afb227",
|
||||
@ -275,6 +339,13 @@
|
||||
],
|
||||
"version": "==2.8"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
|
||||
"sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
|
||||
],
|
||||
"version": "==0.23"
|
||||
},
|
||||
"inflection": {
|
||||
"hashes": [
|
||||
"sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca"
|
||||
@ -301,6 +372,20 @@
|
||||
],
|
||||
"version": "==2.10.3"
|
||||
},
|
||||
"jmespath": {
|
||||
"hashes": [
|
||||
"sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6",
|
||||
"sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c"
|
||||
],
|
||||
"version": "==0.9.4"
|
||||
},
|
||||
"jsonschema": {
|
||||
"hashes": [
|
||||
"sha256:2fa0684276b6333ff3c0b1b27081f4b2305f0a36cf702a23db50edb141893c3f",
|
||||
"sha256:94c0a13b4a0616458b42529091624e66700a17f847453e52279e35509a5b7631"
|
||||
],
|
||||
"version": "==3.1.1"
|
||||
},
|
||||
"kombu": {
|
||||
"hashes": [
|
||||
"sha256:389ba09e03b15b55b1a7371a441c894fd8121d174f5583bbbca032b9ea8c9edd",
|
||||
@ -322,6 +407,7 @@
|
||||
"sha256:02ca7bf899da57084041bb0f6095333e4d239948ad3169443f454add9f4e9cb4",
|
||||
"sha256:096b82c5e0ea27ce9138bcbb205313343ee66a6e132f25c5ed67e2c8d960a1bc",
|
||||
"sha256:0a920ff98cf1aac310470c644bc23b326402d3ef667ddafecb024e1713d485f1",
|
||||
"sha256:1409b14bf83a7d729f92e2a7fbfe7ec929d4883ca071b06e95c539ceedb6497c",
|
||||
"sha256:17cae1730a782858a6e2758fd20dd0ef7567916c47757b694a06ffafdec20046",
|
||||
"sha256:17e3950add54c882e032527795c625929613adbd2ce5162b94667334458b5a36",
|
||||
"sha256:1f4f214337f6ee5825bf90a65d04d70aab05526c08191ab888cb5149501923c5",
|
||||
@ -332,11 +418,14 @@
|
||||
"sha256:760c12276fee05c36f95f8040180abc7fbebb9e5011447a97cdc289b5d6ab6fc",
|
||||
"sha256:796685d3969815a633827c818863ee199440696b0961e200b011d79b9394bbe7",
|
||||
"sha256:891fe897b49abb7db470c55664b198b1095e4943b9f82b7dcab317a19116cd38",
|
||||
"sha256:9277562f175d2334744ad297568677056861070399cec56ff06abbe2564d1232",
|
||||
"sha256:a471628e20f03dcdfde00770eeaf9c77811f0c331c8805219ca7b87ac17576c5",
|
||||
"sha256:a63b4fd3e2cabdcc9d918ed280bdde3e8e9641e04f3c59a2a3109644a07b9832",
|
||||
"sha256:ae88588d687bd476be588010cbbe551e9c2872b816f2da8f01f6f1fda74e1ef0",
|
||||
"sha256:b0b84408d4eabc6de9dd1e1e0bc63e7731e890c0b378a62443e5741cfd0ae90a",
|
||||
"sha256:be78485e5d5f3684e875dab60f40cddace2f5b2a8f7fede412358ab3214c3a6f",
|
||||
"sha256:c27eaed872185f047bb7f7da2d21a7d8913457678c9a100a50db6da890bc28b9",
|
||||
"sha256:c7fccd08b14aa437fe096c71c645c0f9be0655a9b1a4b7cffc77bcb23b3d61d2",
|
||||
"sha256:c81cb40bff373ab7a7446d6bbca0190bccc5be3448b47b51d729e37799bb5692",
|
||||
"sha256:d11874b3c33ee441059464711cd365b89fa1a9cf19ae75b0c189b01fbf735b84",
|
||||
"sha256:e9c028b5897901361d81a4718d1db217b716424a0283afe9d6735fe0caf70f79",
|
||||
@ -418,37 +507,39 @@
|
||||
},
|
||||
"psycopg2-binary": {
|
||||
"hashes": [
|
||||
"sha256:080c72714784989474f97be9ab0ddf7b2ad2984527e77f2909fcd04d4df53809",
|
||||
"sha256:110457be80b63ff4915febb06faa7be002b93a76e5ba19bf3f27636a2ef58598",
|
||||
"sha256:171352a03b22fc099f15103959b52ee77d9a27e028895d7e5fde127aa8e3bac5",
|
||||
"sha256:19d013e7b0817087517a4b3cab39c084d78898369e5c46258aab7be4f233d6a1",
|
||||
"sha256:249b6b21ae4eb0f7b8423b330aa80fab5f821b9ffc3f7561a5e2fd6bb142cf5d",
|
||||
"sha256:2ac0731d2d84b05c7bb39e85b7e123c3a0acd4cda631d8d542802c88deb9e87e",
|
||||
"sha256:2b6d561193f0dc3f50acfb22dd52ea8c8dfbc64bcafe3938b5f209cc17cb6f00",
|
||||
"sha256:2bd23e242e954214944481124755cbefe7c2cf563b1a54cd8d196d502f2578bf",
|
||||
"sha256:3e1239242ca60b3725e65ab2f13765fc199b03af9eaf1b5572f0e97bdcee5b43",
|
||||
"sha256:3eb70bb697abbe86b1d2b1316370c02ba320bfd1e9e35cf3b9566a855ea8e4e5",
|
||||
"sha256:51a2fc7e94b98bd1bb5d4570936f24fc2b0541b63eccadf8fdea266db8ad2f70",
|
||||
"sha256:52f1bdafdc764b7447e393ed39bb263eccb12bfda25a4ac06d82e3a9056251f6",
|
||||
"sha256:5b3581319a3951f1e866f4f6c5e42023db0fae0284273b82e97dfd32c51985cd",
|
||||
"sha256:63c1b66e3b2a3a336288e4bcec499e0dc310cd1dceaed1c46fa7419764c68877",
|
||||
"sha256:8123a99f24ecee469e5c1339427bcdb2a33920a18bb5c0d58b7c13f3b0298ba3",
|
||||
"sha256:85e699fcabe7f817c0f0a412d4e7c6627e00c412b418da7666ff353f38e30f67",
|
||||
"sha256:8dbff4557bbef963697583366400822387cccf794ccb001f1f2307ed21854c68",
|
||||
"sha256:908d21d08d6b81f1b7e056bbf40b2f77f8c499ab29e64ec5113052819ef1c89b",
|
||||
"sha256:af39d0237b17d0a5a5f638e9dffb34013ce2b1d41441fd30283e42b22d16858a",
|
||||
"sha256:af51bb9f055a3f4af0187149a8f60c9d516cf7d5565b3dac53358796a8fb2a5b",
|
||||
"sha256:b2ecac57eb49e461e86c092761e6b8e1fd9654dbaaddf71a076dcc869f7014e2",
|
||||
"sha256:cd37cc170678a4609becb26b53a2bc1edea65177be70c48dd7b39a1149cabd6e",
|
||||
"sha256:d17e3054b17e1a6cb8c1140f76310f6ede811e75b7a9d461922d2c72973f583e",
|
||||
"sha256:d305313c5a9695f40c46294d4315ed3a07c7d2b55e48a9010dad7db7a66c8b7f",
|
||||
"sha256:dd0ef0eb1f7dd18a3f4187226e226a7284bda6af5671937a221766e6ef1ee88f",
|
||||
"sha256:e1adff53b56db9905db48a972fb89370ad5736e0450b96f91bcf99cadd96cfd7",
|
||||
"sha256:f0d43828003c82dbc9269de87aa449e9896077a71954fbbb10a614c017e65737",
|
||||
"sha256:f78e8b487de4d92640105c1389e5b90be3496b1d75c90a666edd8737cc2dbab7"
|
||||
"sha256:040234f8a4a8dfd692662a8308d78f63f31a97e1c42d2480e5e6810c48966a29",
|
||||
"sha256:086f7e89ec85a6704db51f68f0dcae432eff9300809723a6e8782c41c2f48e03",
|
||||
"sha256:18ca813fdb17bc1db73fe61b196b05dd1ca2165b884dd5ec5568877cabf9b039",
|
||||
"sha256:19dc39616850342a2a6db70559af55b22955f86667b5f652f40c0e99253d9881",
|
||||
"sha256:2166e770cb98f02ed5ee2b0b569d40db26788e0bf2ec3ae1a0d864ea6f1d8309",
|
||||
"sha256:3a2522b1d9178575acee4adf8fd9f979f9c0449b00b4164bb63c3475ea6528ed",
|
||||
"sha256:3aa773580f85a28ffdf6f862e59cb5a3cc7ef6885121f2de3fca8d6ada4dbf3b",
|
||||
"sha256:3b5deaa3ee7180585a296af33e14c9b18c218d148e735c7accf78130765a47e3",
|
||||
"sha256:407af6d7e46593415f216c7f56ba087a9a42bd6dc2ecb86028760aa45b802bd7",
|
||||
"sha256:4c3c09fb674401f630626310bcaf6cd6285daf0d5e4c26d6e55ca26a2734e39b",
|
||||
"sha256:4c6717962247445b4f9e21c962ea61d2e884fc17df5ddf5e35863b016f8a1f03",
|
||||
"sha256:50446fae5681fc99f87e505d4e77c9407e683ab60c555ec302f9ac9bffa61103",
|
||||
"sha256:5057669b6a66aa9ca118a2a860159f0ee3acf837eda937bdd2a64f3431361a2d",
|
||||
"sha256:5dd90c5438b4f935c9d01fcbad3620253da89d19c1f5fca9158646407ed7df35",
|
||||
"sha256:659c815b5b8e2a55193ede2795c1e2349b8011497310bb936da7d4745652823b",
|
||||
"sha256:69b13fdf12878b10dc6003acc8d0abf3ad93e79813fd5f3812497c1c9fb9be49",
|
||||
"sha256:7a1cb80e35e1ccea3e11a48afe65d38744a0e0bde88795cc56a4d05b6e4f9d70",
|
||||
"sha256:7e6e3c52e6732c219c07bd97fff6c088f8df4dae3b79752ee3a817e6f32e177e",
|
||||
"sha256:7f42a8490c4fe854325504ce7a6e4796b207960dabb2cbafe3c3959cb00d1d7e",
|
||||
"sha256:84156313f258eafff716b2961644a4483a9be44a5d43551d554844d15d4d224e",
|
||||
"sha256:8578d6b8192e4c805e85f187bc530d0f52ba86c39172e61cd51f68fddd648103",
|
||||
"sha256:890167d5091279a27e2505ff0e1fb273f8c48c41d35c5b92adbf4af80e6b2ed6",
|
||||
"sha256:9aadff9032e967865f9778485571e93908d27dab21d0fdfdec0ca779bb6f8ad9",
|
||||
"sha256:9f24f383a298a0c0f9b3113b982e21751a8ecde6615494a3f1470eb4a9d70e9e",
|
||||
"sha256:a73021b44813b5c84eda4a3af5826dd72356a900bac9bd9dd1f0f81ee1c22c2f",
|
||||
"sha256:afd96845e12638d2c44d213d4810a08f4dc4a563f9a98204b7428e567014b1cd",
|
||||
"sha256:b73ddf033d8cd4cc9dfed6324b1ad2a89ba52c410ef6877998422fcb9c23e3a8",
|
||||
"sha256:dbc5cd56fff1a6152ca59445178652756f4e509f672e49ccdf3d79c1043113a4",
|
||||
"sha256:eac8a3499754790187bb00574ab980df13e754777d346f85e0ff6df929bcd964",
|
||||
"sha256:eaed1c65f461a959284649e37b5051224f4db6ebdc84e40b5e65f2986f101a08"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.8.3"
|
||||
"version": "==2.8.4"
|
||||
},
|
||||
"pyasn1": {
|
||||
"hashes": [
|
||||
@ -557,6 +648,20 @@
|
||||
],
|
||||
"version": "==2.4.2"
|
||||
},
|
||||
"pyrsistent": {
|
||||
"hashes": [
|
||||
"sha256:eb6545dbeb1aa69ab1fb4809bfbf5a8705e44d92ef8fc7c2361682a47c46c778"
|
||||
],
|
||||
"version": "==0.15.5"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
|
||||
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
|
||||
],
|
||||
"markers": "python_version >= '2.7'",
|
||||
"version": "==2.8.0"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||
@ -579,6 +684,7 @@
|
||||
"sha256:861c94442b28cd64af033e88e0f63c66dbd5609f67952dc18694098b47a43f3a",
|
||||
"sha256:957bc6316ffc8463795d56d9953d58e7f32aa5aad1c5ac80bc45c69f3299961e",
|
||||
"sha256:9760c3f56fb5f15852d163429096600906478e9ed2c189a52f2bb21d8a2a986c",
|
||||
"sha256:9fdfb98a2992de01e8efad2aeed22c825e36db628b144b2d6b93d81fb549f811",
|
||||
"sha256:a4b24703ea818196d0be1dc64b3b57b79c67e8dee0cfa207a4216220912035a7",
|
||||
"sha256:ad7f4968c1ddbf139a306d9b075360d959cc554d994ba5e1f512af9a40e62357",
|
||||
"sha256:b1127d34b90f74faf1707718c57a4193ac028b9f4aec0238638983132297d456",
|
||||
@ -589,6 +695,7 @@
|
||||
"sha256:ce777ebdf49ce736fc04abf555b5c41ab3f130127543a689dcf8d4871cd18fe4",
|
||||
"sha256:d8b4bf930b6a19bc9ee982b9163d948c87501ad91b71516924e8ed25fe85d2ee",
|
||||
"sha256:e2a420f2c4d35f3ec0b7e752a80d7bd385e2c5a64f67c05f2d2d74230e3114b6",
|
||||
"sha256:ef5eb630f541af6b69378d58594be90a0922fa6d6a50a9248c25b9502585f6bf",
|
||||
"sha256:fed899ce96f4f2b4d1b9f338dd145a4040ee1d8a5152213af0dd8d4a4d36e9fe"
|
||||
],
|
||||
"index": "pypi",
|
||||
@ -623,10 +730,10 @@
|
||||
},
|
||||
"redis": {
|
||||
"hashes": [
|
||||
"sha256:98a22fb750c9b9bb46e75e945dc3f61d0ab30d06117cbb21ff9cd1d315fedd3b",
|
||||
"sha256:c504251769031b0dd7dd5cf786050a6050197c6de0d37778c80c08cb04ae8275"
|
||||
"sha256:3613daad9ce5951e426f460deddd5caf469e08a3af633e9578fc77d362becf62",
|
||||
"sha256:8d0fc278d3f5e1249967cba2eb4a5632d19e45ce5c09442b8422d15ee2c22cc2"
|
||||
],
|
||||
"version": "==3.3.8"
|
||||
"version": "==3.3.11"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
@ -674,13 +781,20 @@
|
||||
"markers": "platform_python_implementation == 'CPython' and python_version < '3.8'",
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"s3transfer": {
|
||||
"hashes": [
|
||||
"sha256:6efc926738a3cd576c2a79725fed9afde92378aa5c6a957e3af010cb019fac9d",
|
||||
"sha256:b780f2411b824cb541dbcd2c713d0cb61c7d1bcadae204cdddda2b35cef493ba"
|
||||
],
|
||||
"version": "==0.2.1"
|
||||
},
|
||||
"sentry-sdk": {
|
||||
"hashes": [
|
||||
"sha256:15e51e74b924180c98bcd636cb4634945b0a99a124d50b433c3a9dc6a582e8db",
|
||||
"sha256:1d6a2ee908ec6d8f96c27d78bc39e203df4d586d287c233140af7d8d1aca108a"
|
||||
"sha256:cf4b0f8401f4d146e6b8c5579b24397273126c9a0576fa7eb9581ad27b330f13",
|
||||
"sha256:f6e850f304382d87c5c52c01db8c0004d2ced6a0b073df2f2257168cf31b31aa"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.12.3"
|
||||
"version": "==0.13.1"
|
||||
},
|
||||
"service-identity": {
|
||||
"hashes": [
|
||||
@ -714,11 +828,19 @@
|
||||
},
|
||||
"structlog": {
|
||||
"hashes": [
|
||||
"sha256:5feae03167620824d3ae3e8915ea8589fc28d1ad6f3edf3cc90ed7c7cb33fab5",
|
||||
"sha256:db441b81c65b0f104a7ce5d86c5432be099956b98b8a2c8be0b3fb3a7a0b1536"
|
||||
"sha256:4287058cf4ce1a59bc5dea290d6386d37f29a37529c9a51cdf7387e51710152b",
|
||||
"sha256:6640e6690fc31d5949bc614c1a630464d3aaa625284aeb7c6e486c3010d73e12"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==19.1.0"
|
||||
"version": "==19.2.0"
|
||||
},
|
||||
"swagger-spec-validator": {
|
||||
"hashes": [
|
||||
"sha256:57e29feb3aa921a9fb98bd70af148746b27c77d3207266f5571cebcce211e685",
|
||||
"sha256:62ef22eca3f429d93fddda5d793d2a1a9057d3732e7a14606e641805326ae4a6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4.3"
|
||||
},
|
||||
"tempora": {
|
||||
"hashes": [
|
||||
@ -744,6 +866,7 @@
|
||||
"sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": null,
|
||||
"version": "==1.25.6"
|
||||
},
|
||||
"vine": {
|
||||
@ -759,6 +882,13 @@
|
||||
"sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f"
|
||||
],
|
||||
"version": "==2.0"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
|
||||
"sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
|
||||
],
|
||||
"version": "==0.6.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
@ -869,10 +999,10 @@
|
||||
},
|
||||
"gitpython": {
|
||||
"hashes": [
|
||||
"sha256:631263cc670aa56ce3d3c414cf0fe2e840f2e913514b138ea28d88a477bbcd21",
|
||||
"sha256:6e97b9f0954807f30c2dd8e3165731ed6c477a1b365f194b69d81d7940a08332"
|
||||
"sha256:3237caca1139d0a7aa072f6735f5fd2520de52195e0fa1d8b83a9b212a2498b2",
|
||||
"sha256:a7d6bef0775f66ba47f25911d285bcd692ce9053837ff48a120c2b8cf3a71389"
|
||||
],
|
||||
"version": "==3.0.3"
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"isort": {
|
||||
"hashes": [
|
||||
@ -884,26 +1014,29 @@
|
||||
},
|
||||
"lazy-object-proxy": {
|
||||
"hashes": [
|
||||
"sha256:02b260c8deb80db09325b99edf62ae344ce9bc64d68b7a634410b8e9a568edbf",
|
||||
"sha256:18f9c401083a4ba6e162355873f906315332ea7035803d0fd8166051e3d402e3",
|
||||
"sha256:1f2c6209a8917c525c1e2b55a716135ca4658a3042b5122d4e3413a4030c26ce",
|
||||
"sha256:2f06d97f0ca0f414f6b707c974aaf8829c2292c1c497642f63824119d770226f",
|
||||
"sha256:616c94f8176808f4018b39f9638080ed86f96b55370b5a9463b2ee5c926f6c5f",
|
||||
"sha256:63b91e30ef47ef68a30f0c3c278fbfe9822319c15f34b7538a829515b84ca2a0",
|
||||
"sha256:77b454f03860b844f758c5d5c6e5f18d27de899a3db367f4af06bec2e6013a8e",
|
||||
"sha256:83fe27ba321e4cfac466178606147d3c0aa18e8087507caec78ed5a966a64905",
|
||||
"sha256:84742532d39f72df959d237912344d8a1764c2d03fe58beba96a87bfa11a76d8",
|
||||
"sha256:874ebf3caaf55a020aeb08acead813baf5a305927a71ce88c9377970fe7ad3c2",
|
||||
"sha256:9f5caf2c7436d44f3cec97c2fa7791f8a675170badbfa86e1992ca1b84c37009",
|
||||
"sha256:a0c8758d01fcdfe7ae8e4b4017b13552efa7f1197dd7358dc9da0576f9d0328a",
|
||||
"sha256:a4def978d9d28cda2d960c279318d46b327632686d82b4917516c36d4c274512",
|
||||
"sha256:ad4f4be843dace866af5fc142509e9b9817ca0c59342fdb176ab6ad552c927f5",
|
||||
"sha256:ae33dd198f772f714420c5ab698ff05ff900150486c648d29951e9c70694338e",
|
||||
"sha256:b4a2b782b8a8c5522ad35c93e04d60e2ba7f7dcb9271ec8e8c3e08239be6c7b4",
|
||||
"sha256:c462eb33f6abca3b34cdedbe84d761f31a60b814e173b98ede3c81bb48967c4f",
|
||||
"sha256:fd135b8d35dfdcdb984828c84d695937e58cc5f49e1c854eb311c4d6aa03f4f1"
|
||||
"sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d",
|
||||
"sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449",
|
||||
"sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08",
|
||||
"sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a",
|
||||
"sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50",
|
||||
"sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd",
|
||||
"sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239",
|
||||
"sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb",
|
||||
"sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea",
|
||||
"sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e",
|
||||
"sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156",
|
||||
"sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142",
|
||||
"sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442",
|
||||
"sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62",
|
||||
"sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db",
|
||||
"sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531",
|
||||
"sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383",
|
||||
"sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a",
|
||||
"sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357",
|
||||
"sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
|
||||
"sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
|
||||
],
|
||||
"version": "==1.4.2"
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
@ -1064,20 +1197,25 @@
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161",
|
||||
"sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e",
|
||||
"sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e",
|
||||
"sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0",
|
||||
"sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c",
|
||||
"sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47",
|
||||
"sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631",
|
||||
"sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4",
|
||||
"sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34",
|
||||
"sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b",
|
||||
"sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2",
|
||||
"sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e",
|
||||
"sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a",
|
||||
"sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233",
|
||||
"sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1",
|
||||
"sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36",
|
||||
"sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d",
|
||||
"sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a",
|
||||
"sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66",
|
||||
"sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"
|
||||
],
|
||||
"markers": "implementation_name == 'cpython'",
|
||||
|
@ -16,5 +16,8 @@ COPY --from=locker /app/requirements-dev.txt /app/
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
RUN pip install -r requirements.txt --no-cache-dir && \
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends postgresql-client-11 && \
|
||||
rm -rf /var/lib/apt/ && \
|
||||
pip install -r requirements.txt --no-cache-dir && \
|
||||
adduser --system --no-create-home --uid 1000 --group --home /app passbook
|
||||
|
@ -39,7 +39,7 @@ http {
|
||||
gzip on;
|
||||
gzip_types application/javascript image/* text/css;
|
||||
gunzip on;
|
||||
add_header X-passbook-Version 0.6.8-beta;
|
||||
add_header X-passbook-Version 0.7.0-beta;
|
||||
add_header Vary X-passbook-Version;
|
||||
root /data/;
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
[uwsgi]
|
||||
http = 0.0.0.0:8000
|
||||
chdir = /app
|
||||
wsgi-file = passbook/root/wsgi.py
|
||||
processes = 2
|
||||
master = true
|
||||
@ -8,3 +7,4 @@ threads = 2
|
||||
enable-threads = true
|
||||
uid = passbook
|
||||
gid = passbook
|
||||
disable-logging=True
|
||||
|
11
hack/up.sh
Executable file
11
hack/up.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
# macos specific setting, for some reason
|
||||
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
|
||||
export DEBUG=false
|
||||
|
||||
export POSTGRES_USER=postgres
|
||||
|
||||
# ./manage.py generate_swagger > storhappy-ui/swagger.json
|
||||
|
||||
uwsgi docker/uwsgi.ini
|
@ -1,6 +1,6 @@
|
||||
apiVersion: v1
|
||||
appVersion: "0.6.8-beta"
|
||||
appVersion: "0.7.0-beta"
|
||||
description: A Helm chart for passbook.
|
||||
name: passbook
|
||||
version: "0.6.8-beta"
|
||||
version: "0.7.0-beta"
|
||||
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png
|
||||
|
@ -1,6 +1,6 @@
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
version: 4.2.2
|
||||
version: 6.3.13
|
||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||
- name: redis
|
||||
version: 9.2.1
|
||||
|
@ -13,4 +13,4 @@ data:
|
||||
cache_db: 0
|
||||
message_queue_db: 1
|
||||
error_report_enabled: {{ .Values.config.error_reporting }}
|
||||
domain: ".{{ .Values.ingress.hosts[0] }}"
|
||||
domain: ".{{ index .Values.ingress.hosts 0 }}"
|
||||
|
@ -2,7 +2,7 @@
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
image:
|
||||
tag: 0.6.8-beta
|
||||
tag: 0.7.0-beta
|
||||
|
||||
nameOverride: ""
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
"""passbook"""
|
||||
__version__ = '0.6.8-beta'
|
||||
__version__ = '0.7.0-beta'
|
||||
|
@ -1,6 +0,0 @@
|
||||
"""Versioned Admin API Urls"""
|
||||
from django.conf.urls import include, url
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/', include('passbook.admin.api.v1.urls', namespace='v1')),
|
||||
]
|
@ -1,36 +0,0 @@
|
||||
"""passbook admin gorup API"""
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.serializers import ModelSerializer, Serializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.core.models import Group
|
||||
|
||||
|
||||
class RecursiveField(Serializer):
|
||||
"""Recursive field for manytomanyfield"""
|
||||
|
||||
def to_representation(self, value):
|
||||
serializer = self.parent.parent.__class__(value, context=self.context)
|
||||
return serializer.data
|
||||
|
||||
def create(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def update(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
class GroupSerializer(ModelSerializer):
|
||||
"""Group Serializer"""
|
||||
|
||||
children = RecursiveField(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = '__all__'
|
||||
|
||||
class GroupViewSet(ModelViewSet):
|
||||
"""Group Viewset"""
|
||||
|
||||
permission_classes = [IsAdminUser]
|
||||
serializer_class = GroupSerializer
|
||||
queryset = Group.objects.filter(parent__isnull=True)
|
@ -1,33 +0,0 @@
|
||||
"""passbook admin API URLs"""
|
||||
from django.urls import path
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
from rest_framework import permissions
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from passbook.admin.api.v1.applications import ApplicationViewSet
|
||||
from passbook.admin.api.v1.groups import GroupViewSet
|
||||
from passbook.admin.api.v1.users import UserViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register('applications', ApplicationViewSet)
|
||||
router.register('groups', GroupViewSet)
|
||||
router.register('users', UserViewSet)
|
||||
|
||||
SchemaView = get_schema_view(
|
||||
openapi.Info(
|
||||
title="passbook Administration API",
|
||||
default_version='v1',
|
||||
description="Internal passbook API for Administration Interface",
|
||||
contact=openapi.Contact(email="contact@snippets.local"),
|
||||
license=openapi.License(name="MIT License"),
|
||||
),
|
||||
public=True,
|
||||
permission_classes=(permissions.IsAdminUser,),
|
||||
)
|
||||
|
||||
urlpatterns = router.urls + [
|
||||
path('swagger.yml', SchemaView.without_ui(cache_timeout=0), name='schema-json'),
|
||||
path('swagger/', SchemaView.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
]
|
||||
app_name = 'passbook.admin'
|
@ -2,5 +2,6 @@
|
||||
# from django import forms
|
||||
|
||||
SOURCE_FORM_FIELDS = ['name', 'slug', 'enabled', 'policies']
|
||||
SOURCE_SERIALIZER_FIELDS = ['pk', 'name', 'slug', 'enabled', 'policies']
|
||||
|
||||
# class SourceForm(forms.Form)
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""passbook URL Configuration"""
|
||||
from django.urls import include, path
|
||||
from django.urls import path
|
||||
|
||||
from passbook.admin.views import (applications, audit, debug, factors, groups,
|
||||
invitations, overview, policy,
|
||||
@ -74,11 +74,9 @@ urlpatterns = [
|
||||
path('group/<uuid:pk>/update/', groups.GroupUpdateView.as_view(), name='group-update'),
|
||||
path('group/<uuid:pk>/delete/', groups.GroupDeleteView.as_view(), name='group-delete'),
|
||||
# Audit Log
|
||||
path('audit/', audit.AuditEntryListView.as_view(), name='audit-log'),
|
||||
path('audit/', audit.EventListView.as_view(), name='audit-log'),
|
||||
# Groups
|
||||
path('groups/', groups.GroupListView.as_view(), name='groups'),
|
||||
# API
|
||||
path('api/', include('passbook.admin.api.urls')),
|
||||
# Debug
|
||||
path('debug/request/', debug.DebugRequestView.as_view(), name='debug-request'),
|
||||
]
|
||||
|
@ -1,19 +1,24 @@
|
||||
"""passbook Application administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||
from django.views.generic import DeleteView, ListView, UpdateView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.forms.applications import ApplicationForm
|
||||
from passbook.core.models import Application
|
||||
from passbook.lib.views import CreateAssignPermView
|
||||
|
||||
|
||||
class ApplicationListView(AdminRequiredMixin, ListView):
|
||||
class ApplicationListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all applications"""
|
||||
|
||||
model = Application
|
||||
permission_required = 'passbook_core.view_application'
|
||||
ordering = 'name'
|
||||
paginate_by = 40
|
||||
template_name = 'administration/application/list.html'
|
||||
@ -22,10 +27,18 @@ class ApplicationListView(AdminRequiredMixin, ListView):
|
||||
return super().get_queryset().select_subclasses()
|
||||
|
||||
|
||||
class ApplicationCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
class ApplicationCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
"""Create new Application"""
|
||||
|
||||
model = Application
|
||||
form_class = ApplicationForm
|
||||
permission_required = 'passbook_core.add_application'
|
||||
permissions = [
|
||||
'passbook_core.view_application',
|
||||
'passbook_core.change_application',
|
||||
'passbook_core.delete_application',
|
||||
]
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:applications')
|
||||
@ -36,21 +49,25 @@ class ApplicationCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ApplicationUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
class ApplicationUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
"""Update application"""
|
||||
|
||||
model = Application
|
||||
form_class = ApplicationForm
|
||||
permission_required = 'passbook_core.change_application'
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:applications')
|
||||
success_message = _('Successfully updated Application')
|
||||
|
||||
|
||||
class ApplicationDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
|
||||
class ApplicationDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
"""Delete application"""
|
||||
|
||||
model = Application
|
||||
permission_required = 'passbook_core.delete_application'
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:applications')
|
||||
|
@ -1,16 +1,18 @@
|
||||
"""passbook AuditEntry administration"""
|
||||
"""passbook Event administration"""
|
||||
from django.views.generic import ListView
|
||||
from guardian.mixins import PermissionListMixin
|
||||
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.audit.models import AuditEntry
|
||||
from passbook.audit.models import Event
|
||||
|
||||
|
||||
class AuditEntryListView(AdminRequiredMixin, ListView):
|
||||
class EventListView(PermissionListMixin, ListView):
|
||||
"""Show list of all invitations"""
|
||||
|
||||
model = AuditEntry
|
||||
model = Event
|
||||
template_name = 'administration/audit/list.html'
|
||||
permission_required = 'passbook_audit.view_event'
|
||||
ordering = '-created'
|
||||
paginate_by = 10
|
||||
|
||||
def get_queryset(self):
|
||||
return AuditEntry.objects.all().order_by('-created')
|
||||
return Event.objects.all().order_by('-created')
|
||||
|
@ -1,11 +1,9 @@
|
||||
"""passbook administration debug views"""
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
|
||||
|
||||
class DebugRequestView(AdminRequiredMixin, TemplateView):
|
||||
class DebugRequestView(LoginRequiredMixin, TemplateView):
|
||||
"""Show debug info about request"""
|
||||
|
||||
template_name = 'administration/debug/request.html'
|
||||
|
@ -1,14 +1,18 @@
|
||||
"""passbook Factor administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import Http404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||
from django.views.generic import DeleteView, ListView, UpdateView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.models import Factor
|
||||
from passbook.lib.utils.reflection import path_to_class
|
||||
from passbook.lib.views import CreateAssignPermView
|
||||
|
||||
|
||||
def all_subclasses(cls):
|
||||
@ -16,11 +20,13 @@ def all_subclasses(cls):
|
||||
return set(cls.__subclasses__()).union(
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)])
|
||||
|
||||
class FactorListView(AdminRequiredMixin, ListView):
|
||||
|
||||
class FactorListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all factors"""
|
||||
|
||||
model = Factor
|
||||
template_name = 'administration/factor/list.html'
|
||||
permission_required = 'passbook_core.view_factor'
|
||||
ordering = 'order'
|
||||
paginate_by = 40
|
||||
|
||||
@ -32,10 +38,20 @@ class FactorListView(AdminRequiredMixin, ListView):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_subclasses()
|
||||
|
||||
class FactorCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
|
||||
class FactorCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
"""Create new Factor"""
|
||||
|
||||
model = Factor
|
||||
template_name = 'generic/create.html'
|
||||
permission_required = 'passbook_core.add_factor'
|
||||
permissions = [
|
||||
'passbook_core.view_factor',
|
||||
'passbook_core.change_factor',
|
||||
'passbook_core.delete_factor',
|
||||
]
|
||||
|
||||
success_url = reverse_lazy('passbook_admin:factors')
|
||||
success_message = _('Successfully created Factor')
|
||||
|
||||
@ -53,10 +69,13 @@ class FactorCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
raise Http404
|
||||
return path_to_class(model.form)
|
||||
|
||||
class FactorUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
|
||||
class FactorUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
"""Update factor"""
|
||||
|
||||
model = Factor
|
||||
permission_required = 'passbook_core.update_application'
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:factors')
|
||||
success_message = _('Successfully updated Factor')
|
||||
@ -69,11 +88,14 @@ class FactorUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
def get_object(self, queryset=None):
|
||||
return Factor.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
|
||||
class FactorDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
|
||||
|
||||
class FactorDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
"""Delete factor"""
|
||||
|
||||
model = Factor
|
||||
template_name = 'generic/delete.html'
|
||||
permission_required = 'passbook_core.delete_factor'
|
||||
success_url = reverse_lazy('passbook_admin:factors')
|
||||
success_message = _('Successfully deleted Factor')
|
||||
|
||||
|
@ -1,29 +1,41 @@
|
||||
"""passbook Group administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||
from django.views.generic import DeleteView, ListView, UpdateView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.forms.groups import GroupForm
|
||||
from passbook.core.models import Group
|
||||
from passbook.lib.views import CreateAssignPermView
|
||||
|
||||
|
||||
class GroupListView(AdminRequiredMixin, ListView):
|
||||
class GroupListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all groups"""
|
||||
|
||||
model = Group
|
||||
permission_required = 'passbook_core.view_group'
|
||||
ordering = 'name'
|
||||
paginate_by = 40
|
||||
template_name = 'administration/group/list.html'
|
||||
|
||||
|
||||
class GroupCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
class GroupCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
"""Create new Group"""
|
||||
|
||||
model = Group
|
||||
form_class = GroupForm
|
||||
|
||||
permission_required = 'passbook_core.add_group'
|
||||
permissions = [
|
||||
'passbook_core.view_group',
|
||||
'passbook_core.change_group',
|
||||
'passbook_core.delete_group',
|
||||
]
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:groups')
|
||||
success_message = _('Successfully created Group')
|
||||
@ -33,18 +45,20 @@ class GroupCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class GroupUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
class GroupUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
"""Update group"""
|
||||
|
||||
model = Group
|
||||
form_class = GroupForm
|
||||
permission_required = 'passbook_core.change_group'
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:groups')
|
||||
success_message = _('Successfully updated Group')
|
||||
|
||||
|
||||
class GroupDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
|
||||
class GroupDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView):
|
||||
"""Delete group"""
|
||||
|
||||
model = Group
|
||||
|
@ -1,33 +1,44 @@
|
||||
"""passbook Invitation administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import CreateView, DeleteView, ListView
|
||||
from django.views.generic import DeleteView, ListView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.forms.invitations import InvitationForm
|
||||
from passbook.core.models import Invitation
|
||||
from passbook.core.signals import invitation_created
|
||||
from passbook.lib.views import CreateAssignPermView
|
||||
|
||||
|
||||
class InvitationListView(AdminRequiredMixin, ListView):
|
||||
class InvitationListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all invitations"""
|
||||
|
||||
model = Invitation
|
||||
ordering = 'expires'
|
||||
paginate_by = 40
|
||||
permission_required = 'passbook_core.view_invitation'
|
||||
template_name = 'administration/invitation/list.html'
|
||||
|
||||
|
||||
class InvitationCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
class InvitationCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
"""Create new Invitation"""
|
||||
|
||||
model = Invitation
|
||||
form_class = InvitationForm
|
||||
permission_required = 'passbook_core.add_invitation'
|
||||
permissions = [
|
||||
'passbook_core.view_invitation',
|
||||
'passbook_core.change_invitation',
|
||||
'passbook_core.delete_invitation',
|
||||
]
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:invitations')
|
||||
success_message = _('Successfully created Invitation')
|
||||
form_class = InvitationForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['type'] = 'Invitation'
|
||||
@ -43,10 +54,14 @@ class InvitationCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
invitation=obj)
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
class InvitationDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
|
||||
|
||||
class InvitationDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
"""Delete invitation"""
|
||||
|
||||
model = Invitation
|
||||
permission_required = 'passbook_core.delete_invitation'
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:invitations')
|
||||
success_message = _('Successfully deleted Invitation')
|
||||
|
@ -1,26 +1,29 @@
|
||||
"""passbook Policy administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import Http404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import (CreateView, DeleteView, FormView, ListView,
|
||||
UpdateView)
|
||||
from django.views.generic import DeleteView, FormView, ListView, UpdateView
|
||||
from django.views.generic.detail import DetailView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
|
||||
from passbook.admin.forms.policies import PolicyTestForm
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.models import Policy
|
||||
from passbook.lib.utils.reflection import path_to_class
|
||||
from passbook.lib.views import CreateAssignPermView
|
||||
from passbook.policies.engine import PolicyEngine
|
||||
|
||||
|
||||
class PolicyListView(AdminRequiredMixin, ListView):
|
||||
class PolicyListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all policies"""
|
||||
|
||||
model = Policy
|
||||
ordering = 'name'
|
||||
paginate_by = 40
|
||||
permission_required = 'passbook_core.view_policy'
|
||||
|
||||
template_name = 'administration/policy/list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@ -32,9 +35,18 @@ class PolicyListView(AdminRequiredMixin, ListView):
|
||||
return super().get_queryset().order_by('order').select_subclasses()
|
||||
|
||||
|
||||
class PolicyCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
class PolicyCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
"""Create new Policy"""
|
||||
|
||||
model = Policy
|
||||
permission_required = 'passbook_core.add_policy'
|
||||
permissions = [
|
||||
'passbook_core.view_policy',
|
||||
'passbook_core.change_policy',
|
||||
'passbook_core.delete_policy',
|
||||
]
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:policies')
|
||||
success_message = _('Successfully created Policy')
|
||||
@ -48,10 +60,13 @@ class PolicyCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
return path_to_class(model.form)
|
||||
|
||||
|
||||
class PolicyUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
class PolicyUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
"""Update policy"""
|
||||
|
||||
model = Policy
|
||||
permission_required = 'passbook_core.change_policy'
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:policies')
|
||||
success_message = _('Successfully updated Policy')
|
||||
@ -65,10 +80,13 @@ class PolicyUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
|
||||
|
||||
class PolicyDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
|
||||
class PolicyDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
"""Delete policy"""
|
||||
|
||||
model = Policy
|
||||
permission_required = 'passbook_core.delete_policy'
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:policies')
|
||||
success_message = _('Successfully deleted Policy')
|
||||
@ -81,11 +99,12 @@ class PolicyDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
class PolicyTestView(AdminRequiredMixin, DetailView, FormView):
|
||||
class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, FormView):
|
||||
"""View to test policy(s)"""
|
||||
|
||||
model = Policy
|
||||
form_class = PolicyTestForm
|
||||
permission_required = 'passbook_core.view_policy'
|
||||
template_name = 'administration/policy/test.html'
|
||||
object = None
|
||||
|
||||
@ -103,8 +122,9 @@ class PolicyTestView(AdminRequiredMixin, DetailView, FormView):
|
||||
def form_valid(self, form):
|
||||
policy = self.get_object()
|
||||
user = form.cleaned_data.get('user')
|
||||
policy_engine = PolicyEngine([policy])
|
||||
policy_engine.for_user(user).with_request(self.request).build()
|
||||
policy_engine = PolicyEngine([policy], user, self.request)
|
||||
policy_engine.use_cache = False
|
||||
policy_engine.build()
|
||||
result = policy_engine.passing
|
||||
if result:
|
||||
messages.success(self.request, _('User successfully passed policy.'))
|
||||
|
@ -1,14 +1,18 @@
|
||||
"""passbook PropertyMapping administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import Http404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||
from django.views.generic import DeleteView, ListView, UpdateView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.models import PropertyMapping
|
||||
from passbook.lib.utils.reflection import path_to_class
|
||||
from passbook.lib.views import CreateAssignPermView
|
||||
|
||||
|
||||
def all_subclasses(cls):
|
||||
@ -17,10 +21,11 @@ def all_subclasses(cls):
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)])
|
||||
|
||||
|
||||
class PropertyMappingListView(AdminRequiredMixin, ListView):
|
||||
class PropertyMappingListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all property_mappings"""
|
||||
|
||||
model = PropertyMapping
|
||||
permission_required = 'passbook_core.view_propertymapping'
|
||||
template_name = 'administration/property_mapping/list.html'
|
||||
ordering = 'name'
|
||||
paginate_by = 40
|
||||
@ -34,9 +39,18 @@ class PropertyMappingListView(AdminRequiredMixin, ListView):
|
||||
return super().get_queryset().select_subclasses()
|
||||
|
||||
|
||||
class PropertyMappingCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
class PropertyMappingCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
"""Create new PropertyMapping"""
|
||||
|
||||
model = PropertyMapping
|
||||
permission_required = 'passbook_core.add_propertymapping'
|
||||
permissions = [
|
||||
'passbook_core.view_propertymapping',
|
||||
'passbook_core.change_propertymapping',
|
||||
'passbook_core.delete_propertymapping',
|
||||
]
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:property-mappings')
|
||||
success_message = _('Successfully created Property Mapping')
|
||||
@ -58,10 +72,13 @@ class PropertyMappingCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateV
|
||||
return path_to_class(model.form)
|
||||
|
||||
|
||||
class PropertyMappingUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
class PropertyMappingUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
"""Update property_mapping"""
|
||||
|
||||
model = PropertyMapping
|
||||
permission_required = 'passbook_core.change_propertymapping'
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:property-mappings')
|
||||
success_message = _('Successfully updated Property Mapping')
|
||||
@ -75,10 +92,13 @@ class PropertyMappingUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateV
|
||||
return PropertyMapping.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
|
||||
|
||||
class PropertyMappingDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
|
||||
class PropertyMappingDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
"""Delete property_mapping"""
|
||||
|
||||
model = PropertyMapping
|
||||
permission_required = 'passbook_core.delete_propertymapping'
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:property-mappings')
|
||||
success_message = _('Successfully deleted Property Mapping')
|
||||
|
@ -1,21 +1,25 @@
|
||||
"""passbook Provider administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import Http404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||
from django.views.generic import DeleteView, ListView, UpdateView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.models import Provider
|
||||
from passbook.lib.utils.reflection import path_to_class
|
||||
from passbook.lib.views import CreateAssignPermView
|
||||
|
||||
|
||||
class ProviderListView(AdminRequiredMixin, ListView):
|
||||
class ProviderListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all providers"""
|
||||
|
||||
model = Provider
|
||||
paginate_by = 40
|
||||
permission_required = 'passbook_core.add_provider'
|
||||
template_name = 'administration/provider/list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@ -27,9 +31,18 @@ class ProviderListView(AdminRequiredMixin, ListView):
|
||||
return super().get_queryset().select_subclasses()
|
||||
|
||||
|
||||
class ProviderCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
class ProviderCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
"""Create new Provider"""
|
||||
|
||||
model = Provider
|
||||
permission_required = 'passbook_core.add_provider'
|
||||
permissions = [
|
||||
'passbook_core.view_provider',
|
||||
'passbook_core.change_provider',
|
||||
'passbook_core.delete_provider',
|
||||
]
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:providers')
|
||||
success_message = _('Successfully created Provider')
|
||||
@ -43,10 +56,13 @@ class ProviderCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
return path_to_class(model.form)
|
||||
|
||||
|
||||
class ProviderUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
class ProviderUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
"""Update provider"""
|
||||
|
||||
model = Provider
|
||||
permission_required = 'passbook_core.change_provider'
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:providers')
|
||||
success_message = _('Successfully updated Provider')
|
||||
@ -60,10 +76,13 @@ class ProviderUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
return Provider.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
|
||||
|
||||
class ProviderDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
|
||||
class ProviderDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
"""Delete provider"""
|
||||
|
||||
model = Provider
|
||||
permission_required = 'passbook_core.delete_provider'
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:providers')
|
||||
success_message = _('Successfully deleted Provider')
|
||||
|
@ -1,14 +1,18 @@
|
||||
"""passbook Source administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import Http404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||
from django.views.generic import DeleteView, ListView, UpdateView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.models import Source
|
||||
from passbook.lib.utils.reflection import path_to_class
|
||||
from passbook.lib.views import CreateAssignPermView
|
||||
|
||||
|
||||
def all_subclasses(cls):
|
||||
@ -16,10 +20,11 @@ def all_subclasses(cls):
|
||||
return set(cls.__subclasses__()).union(
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)])
|
||||
|
||||
class SourceListView(AdminRequiredMixin, ListView):
|
||||
class SourceListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all sources"""
|
||||
|
||||
model = Source
|
||||
permission_required = 'passbook_core.view_source'
|
||||
ordering = 'name'
|
||||
paginate_by = 40
|
||||
template_name = 'administration/source/list.html'
|
||||
@ -33,9 +38,18 @@ class SourceListView(AdminRequiredMixin, ListView):
|
||||
return super().get_queryset().select_subclasses()
|
||||
|
||||
|
||||
class SourceCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
class SourceCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
"""Create new Source"""
|
||||
|
||||
model = Source
|
||||
permission_required = 'passbook_core.add_source'
|
||||
permissions = [
|
||||
'passbook_core.view_source',
|
||||
'passbook_core.change_source',
|
||||
'passbook_core.delete_source',
|
||||
]
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:sources')
|
||||
success_message = _('Successfully created Source')
|
||||
@ -48,10 +62,13 @@ class SourceCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
return path_to_class(model.form)
|
||||
|
||||
|
||||
class SourceUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
class SourceUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
"""Update source"""
|
||||
|
||||
model = Source
|
||||
permission_required = 'passbook_core.change_source'
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:sources')
|
||||
success_message = _('Successfully updated Source')
|
||||
@ -65,10 +82,13 @@ class SourceUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
return Source.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
|
||||
|
||||
class SourceDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
|
||||
class SourceDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
"""Delete source"""
|
||||
|
||||
model = Source
|
||||
permission_required = 'passbook_core.delete_source'
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:sources')
|
||||
success_message = _('Successfully deleted Source')
|
||||
|
@ -1,42 +1,55 @@
|
||||
"""passbook User administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views import View
|
||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||
from django.views.generic import DeleteView, DetailView, ListView, UpdateView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
|
||||
from passbook.admin.forms.users import UserForm
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.models import Nonce, User
|
||||
from passbook.lib.views import CreateAssignPermView
|
||||
|
||||
|
||||
class UserListView(AdminRequiredMixin, ListView):
|
||||
class UserListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all users"""
|
||||
|
||||
model = User
|
||||
permission_required = 'passbook_core.view_user'
|
||||
ordering = 'username'
|
||||
paginate_by = 40
|
||||
template_name = 'administration/user/list.html'
|
||||
|
||||
|
||||
class UserCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
class UserCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
"""Create user"""
|
||||
|
||||
model = User
|
||||
form_class = UserForm
|
||||
permission_required = 'passbook_core.add_user'
|
||||
permissions = [
|
||||
'passbook_core.view_user',
|
||||
'passbook_core.change_user',
|
||||
'passbook_core.delete_user',
|
||||
]
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:users')
|
||||
success_message = _('Successfully created User')
|
||||
|
||||
|
||||
class UserUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
class UserUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
"""Update user"""
|
||||
|
||||
model = User
|
||||
form_class = UserForm
|
||||
permission_required = 'passbook_core.change_user'
|
||||
|
||||
context_object_name = 'object' # By default the object's name
|
||||
# is user which is used by other checks
|
||||
@ -45,10 +58,13 @@ class UserUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
success_message = _('Successfully updated User')
|
||||
|
||||
|
||||
class UserDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
|
||||
class UserDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
"""Delete user"""
|
||||
|
||||
model = User
|
||||
permission_required = 'passbook_core.delete_user'
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:users')
|
||||
success_message = _('Successfully deleted User')
|
||||
@ -58,14 +74,16 @@ class UserDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView):
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
class UserPasswordResetView(AdminRequiredMixin, View):
|
||||
class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
"""Get Password reset link for user"""
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def get(self, request, pk):
|
||||
model = User
|
||||
permission_required = 'passbook_core.reset_user_password'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Create nonce for user and return link"""
|
||||
user = get_object_or_404(User, pk=pk)
|
||||
nonce = Nonce.objects.create(user=user)
|
||||
super().get(request, *args, **kwargs)
|
||||
nonce = Nonce.objects.create(user=self.object)
|
||||
link = request.build_absolute_uri(reverse(
|
||||
'passbook_core:auth-password-reset', kwargs={'nonce': nonce.uuid}))
|
||||
messages.success(request, _('Password reset link: <pre>%(link)s</pre>' % {'link': link}))
|
||||
|
31
passbook/api/permissions.py
Normal file
31
passbook/api/permissions.py
Normal file
@ -0,0 +1,31 @@
|
||||
"""permission classes for django restframework"""
|
||||
from rest_framework.permissions import BasePermission, DjangoObjectPermissions
|
||||
|
||||
from passbook.core.models import PolicyModel
|
||||
from passbook.policies.engine import PolicyEngine
|
||||
|
||||
|
||||
class CustomObjectPermissions(DjangoObjectPermissions):
|
||||
"""Similar to `DjangoObjectPermissions`, but adding 'view' permissions."""
|
||||
|
||||
perms_map = {
|
||||
'GET': ['%(app_label)s.view_%(model_name)s'],
|
||||
'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
|
||||
'HEAD': ['%(app_label)s.view_%(model_name)s'],
|
||||
'POST': ['%(app_label)s.add_%(model_name)s'],
|
||||
'PUT': ['%(app_label)s.change_%(model_name)s'],
|
||||
'PATCH': ['%(app_label)s.change_%(model_name)s'],
|
||||
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
|
||||
}
|
||||
|
||||
|
||||
class PolicyPermissions(BasePermission):
|
||||
"""Permission checker based on PolicyEngine"""
|
||||
|
||||
policy_engine: PolicyEngine
|
||||
|
||||
def has_object_permission(self, request, view, obj: PolicyModel) -> bool:
|
||||
# if not obj.po
|
||||
self.policy_engine = PolicyEngine(obj.policies, request.user, request)
|
||||
self.policy_engine.request.obj = obj
|
||||
return self.policy_engine.build().passing
|
@ -2,7 +2,9 @@
|
||||
from django.urls import include, path
|
||||
|
||||
from passbook.api.v1.urls import urlpatterns as v1_urls
|
||||
from passbook.api.v2.urls import urlpatterns as v2_urls
|
||||
|
||||
urlpatterns = [
|
||||
path('v1/', include(v1_urls))
|
||||
path('v1/', include(v1_urls)),
|
||||
path('v2/', include(v2_urls)),
|
||||
]
|
||||
|
103
passbook/api/v2/urls.py
Normal file
103
passbook/api/v2/urls.py
Normal file
@ -0,0 +1,103 @@
|
||||
"""api v2 urls"""
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
from rest_framework import routers
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.api.permissions import CustomObjectPermissions
|
||||
from passbook.audit.api.events import EventViewSet
|
||||
from passbook.core.api.applications import ApplicationViewSet
|
||||
from passbook.core.api.factors import FactorViewSet
|
||||
from passbook.core.api.groups import GroupViewSet
|
||||
from passbook.core.api.invitations import InvitationViewSet
|
||||
from passbook.core.api.policies import PolicyViewSet
|
||||
from passbook.core.api.propertymappings import PropertyMappingViewSet
|
||||
from passbook.core.api.providers import ProviderViewSet
|
||||
from passbook.core.api.sources import SourceViewSet
|
||||
from passbook.core.api.users import UserViewSet
|
||||
from passbook.factors.captcha.api import CaptchaFactorViewSet
|
||||
from passbook.factors.dummy.api import DummyFactorViewSet
|
||||
from passbook.factors.email.api import EmailFactorViewSet
|
||||
from passbook.factors.otp.api import OTPFactorViewSet
|
||||
from passbook.factors.password.api import PasswordFactorViewSet
|
||||
from passbook.lib.utils.reflection import get_apps
|
||||
from passbook.policies.expiry.api import PasswordExpiryPolicyViewSet
|
||||
from passbook.policies.group.api import GroupMembershipPolicyViewSet
|
||||
from passbook.policies.hibp.api import HaveIBeenPwendPolicyViewSet
|
||||
from passbook.policies.matcher.api import FieldMatcherPolicyViewSet
|
||||
from passbook.policies.password.api import PasswordPolicyViewSet
|
||||
from passbook.policies.reputation.api import ReputationPolicyViewSet
|
||||
from passbook.policies.sso.api import SSOLoginPolicyViewSet
|
||||
from passbook.policies.webhook.api import WebhookPolicyViewSet
|
||||
from passbook.providers.app_gw.api import ApplicationGatewayProviderViewSet
|
||||
from passbook.providers.oauth.api import OAuth2ProviderViewSet
|
||||
from passbook.providers.oidc.api import OpenIDProviderViewSet
|
||||
from passbook.providers.saml.api import (SAMLPropertyMappingViewSet,
|
||||
SAMLProviderViewSet)
|
||||
from passbook.sources.ldap.api import (LDAPPropertyMappingViewSet,
|
||||
LDAPSourceViewSet)
|
||||
from passbook.sources.oauth.api import OAuthSourceViewSet
|
||||
|
||||
LOGGER = get_logger()
|
||||
router = routers.DefaultRouter()
|
||||
|
||||
for _passbook_app in get_apps():
|
||||
if hasattr(_passbook_app, 'api_mountpoint'):
|
||||
for prefix, viewset in _passbook_app.api_mountpoint:
|
||||
router.register(prefix, viewset)
|
||||
LOGGER.debug("Mounted API URLs", app_name=_passbook_app.name)
|
||||
|
||||
router.register('core/applications', ApplicationViewSet)
|
||||
router.register('core/invitations', InvitationViewSet)
|
||||
router.register('core/groups', GroupViewSet)
|
||||
router.register('core/users', UserViewSet)
|
||||
router.register('audit/events', EventViewSet)
|
||||
router.register('sources/all', SourceViewSet)
|
||||
router.register('sources/ldap', LDAPSourceViewSet)
|
||||
router.register('sources/oauth', OAuthSourceViewSet)
|
||||
router.register('policies/all', PolicyViewSet)
|
||||
router.register('policies/passwordexpiry', PasswordExpiryPolicyViewSet)
|
||||
router.register('policies/groupmembership', GroupMembershipPolicyViewSet)
|
||||
router.register('policies/haveibeenpwned', HaveIBeenPwendPolicyViewSet)
|
||||
router.register('policies/fieldmatcher', FieldMatcherPolicyViewSet)
|
||||
router.register('policies/password', PasswordPolicyViewSet)
|
||||
router.register('policies/reputation', ReputationPolicyViewSet)
|
||||
router.register('policies/ssologin', SSOLoginPolicyViewSet)
|
||||
router.register('policies/webhook', WebhookPolicyViewSet)
|
||||
router.register('providers/all', ProviderViewSet)
|
||||
router.register('providers/applicationgateway', ApplicationGatewayProviderViewSet)
|
||||
router.register('providers/oauth', OAuth2ProviderViewSet)
|
||||
router.register('providers/openid', OpenIDProviderViewSet)
|
||||
router.register('providers/saml', SAMLProviderViewSet)
|
||||
router.register('propertymappings/all', PropertyMappingViewSet)
|
||||
router.register('propertymappings/ldap', LDAPPropertyMappingViewSet)
|
||||
router.register('propertymappings/saml', SAMLPropertyMappingViewSet)
|
||||
router.register('factors/all', FactorViewSet)
|
||||
router.register('factors/captcha', CaptchaFactorViewSet)
|
||||
router.register('factors/dummy', DummyFactorViewSet)
|
||||
router.register('factors/email', EmailFactorViewSet)
|
||||
router.register('factors/otp', OTPFactorViewSet)
|
||||
router.register('factors/password', PasswordFactorViewSet)
|
||||
|
||||
info = openapi.Info(
|
||||
title="passbook API",
|
||||
default_version='v2',
|
||||
# description="Test description",
|
||||
# terms_of_service="https://www.google.com/policies/terms/",
|
||||
contact=openapi.Contact(email="hello@beryju.org"),
|
||||
license=openapi.License(name="MIT License"),
|
||||
)
|
||||
SchemaView = get_schema_view(
|
||||
info,
|
||||
public=True,
|
||||
permission_classes=(CustomObjectPermissions,),
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^swagger(?P<format>\.json|\.yaml)$',
|
||||
SchemaView.without_ui(cache_timeout=0), name='schema-json'),
|
||||
path('swagger/', SchemaView.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
path('redoc/', SchemaView.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||
] + router.urls
|
21
passbook/audit/api/events.py
Normal file
21
passbook/audit/api/events.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Audit API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
|
||||
from passbook.audit.models import Event
|
||||
|
||||
|
||||
class EventSerializer(ModelSerializer):
|
||||
"""Event Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Event
|
||||
fields = ['pk', 'user', 'action', 'date', 'app', 'context', 'request_ip', 'created', ]
|
||||
|
||||
|
||||
class EventViewSet(ReadOnlyModelViewSet):
|
||||
"""Event Read-Only Viewset"""
|
||||
|
||||
queryset = Event.objects.all()
|
||||
serializer_class = EventSerializer
|
19
passbook/audit/migrations/0002_auto_20191028_0829.py
Normal file
19
passbook/audit/migrations/0002_auto_20191028_0829.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.6 on 2019-10-28 08:29
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('passbook_audit', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='AuditEntry',
|
||||
new_name='Event',
|
||||
),
|
||||
]
|
@ -12,8 +12,8 @@ from passbook.lib.models import UUIDModel
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
class AuditEntry(UUIDModel):
|
||||
"""An individual audit log entry"""
|
||||
class Event(UUIDModel):
|
||||
"""An individual audit log event"""
|
||||
|
||||
ACTION_LOGIN = 'login'
|
||||
ACTION_LOGIN_FAILED = 'login_failed'
|
||||
@ -46,7 +46,7 @@ class AuditEntry(UUIDModel):
|
||||
|
||||
@staticmethod
|
||||
def create(action, request, **kwargs):
|
||||
"""Create AuditEntry from arguments"""
|
||||
"""Create Event from arguments"""
|
||||
client_ip, _ = get_client_ip(request)
|
||||
if not hasattr(request, 'user'):
|
||||
user = None
|
||||
@ -54,7 +54,7 @@ class AuditEntry(UUIDModel):
|
||||
user = request.user
|
||||
if isinstance(user, AnonymousUser):
|
||||
user = kwargs.get('user', None)
|
||||
entry = AuditEntry.objects.create(
|
||||
entry = Event.objects.create(
|
||||
action=action,
|
||||
user=user,
|
||||
# User 255.255.255.255 as fallback if IP cannot be determined
|
||||
|
@ -2,7 +2,7 @@
|
||||
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||
from django.dispatch import receiver
|
||||
|
||||
from passbook.audit.models import AuditEntry
|
||||
from passbook.audit.models import Event
|
||||
from passbook.core.signals import (invitation_created, invitation_used,
|
||||
user_signed_up)
|
||||
|
||||
@ -10,26 +10,26 @@ from passbook.core.signals import (invitation_created, invitation_used,
|
||||
@receiver(user_logged_in)
|
||||
def on_user_logged_in(sender, request, user, **kwargs):
|
||||
"""Log successful login"""
|
||||
AuditEntry.create(AuditEntry.ACTION_LOGIN, request)
|
||||
Event.create(Event.ACTION_LOGIN, request)
|
||||
|
||||
@receiver(user_logged_out)
|
||||
def on_user_logged_out(sender, request, user, **kwargs):
|
||||
"""Log successfully logout"""
|
||||
AuditEntry.create(AuditEntry.ACTION_LOGOUT, request)
|
||||
Event.create(Event.ACTION_LOGOUT, request)
|
||||
|
||||
@receiver(user_signed_up)
|
||||
def on_user_signed_up(sender, request, user, **kwargs):
|
||||
"""Log successfully signed up"""
|
||||
AuditEntry.create(AuditEntry.ACTION_SIGN_UP, request)
|
||||
Event.create(Event.ACTION_SIGN_UP, request)
|
||||
|
||||
@receiver(invitation_created)
|
||||
def on_invitation_created(sender, request, invitation, **kwargs):
|
||||
"""Log Invitation creation"""
|
||||
AuditEntry.create(AuditEntry.ACTION_INVITE_CREATED, request,
|
||||
invitation_uuid=invitation.uuid.hex)
|
||||
Event.create(Event.ACTION_INVITE_CREATED, request,
|
||||
invitation_uuid=invitation.uuid.hex)
|
||||
|
||||
@receiver(invitation_used)
|
||||
def on_invitation_used(sender, request, invitation, **kwargs):
|
||||
"""Log Invitation usage"""
|
||||
AuditEntry.create(AuditEntry.ACTION_INVITE_USED, request,
|
||||
invitation_uuid=invitation.uuid.hex)
|
||||
Event.create(Event.ACTION_INVITE_USED, request,
|
||||
invitation_uuid=invitation.uuid.hex)
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""passbook admin application API"""
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
"""Application API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
@ -10,13 +9,14 @@ class ApplicationSerializer(ModelSerializer):
|
||||
"""Application Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Application
|
||||
fields = '__all__'
|
||||
fields = ['pk', 'name', 'slug', 'launch_url', 'icon_url',
|
||||
'provider', 'policies', 'skip_authorization']
|
||||
|
||||
|
||||
class ApplicationViewSet(ModelViewSet):
|
||||
"""Application Viewset"""
|
||||
|
||||
permission_classes = [IsAdminUser]
|
||||
serializer_class = ApplicationSerializer
|
||||
queryset = Application.objects.all()
|
||||
serializer_class = ApplicationSerializer
|
30
passbook/core/api/factors.py
Normal file
30
passbook/core/api/factors.py
Normal file
@ -0,0 +1,30 @@
|
||||
"""Factor API Views"""
|
||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
|
||||
from passbook.core.models import Factor
|
||||
|
||||
|
||||
class FactorSerializer(ModelSerializer):
|
||||
"""Factor Serializer"""
|
||||
|
||||
__type__ = SerializerMethodField(method_name='get_type')
|
||||
|
||||
def get_type(self, obj):
|
||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace('factor', '')
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Factor
|
||||
fields = ['pk', 'name', 'slug', 'order', 'enabled', '__type__']
|
||||
|
||||
|
||||
class FactorViewSet(ReadOnlyModelViewSet):
|
||||
"""Factor Viewset"""
|
||||
|
||||
queryset = Factor.objects.all()
|
||||
serializer_class = FactorSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return Factor.objects.select_subclasses()
|
21
passbook/core/api/groups.py
Normal file
21
passbook/core/api/groups.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Groups API Viewset"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.core.models import Group
|
||||
|
||||
|
||||
class GroupSerializer(ModelSerializer):
|
||||
"""Group Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Group
|
||||
fields = ['pk', 'name', 'parent', 'user_set', 'attributes']
|
||||
|
||||
|
||||
class GroupViewSet(ModelViewSet):
|
||||
"""Group Viewset"""
|
||||
|
||||
queryset = Group.objects.all()
|
||||
serializer_class = GroupSerializer
|
21
passbook/core/api/invitations.py
Normal file
21
passbook/core/api/invitations.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Invitation API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.core.models import Invitation
|
||||
|
||||
|
||||
class InvitationSerializer(ModelSerializer):
|
||||
"""Invitation Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Invitation
|
||||
fields = ['pk', 'expires', 'fixed_username', 'fixed_email', 'needs_confirmation']
|
||||
|
||||
|
||||
class InvitationViewSet(ModelViewSet):
|
||||
"""Invitation Viewset"""
|
||||
|
||||
queryset = Invitation.objects.all()
|
||||
serializer_class = InvitationSerializer
|
31
passbook/core/api/policies.py
Normal file
31
passbook/core/api/policies.py
Normal file
@ -0,0 +1,31 @@
|
||||
"""Policy API Views"""
|
||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
|
||||
from passbook.core.models import Policy
|
||||
from passbook.policies.forms import GENERAL_FIELDS
|
||||
|
||||
|
||||
class PolicySerializer(ModelSerializer):
|
||||
"""Policy Serializer"""
|
||||
|
||||
__type__ = SerializerMethodField(method_name='get_type')
|
||||
|
||||
def get_type(self, obj):
|
||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace('policy', '')
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Policy
|
||||
fields = ['pk'] + GENERAL_FIELDS + ['__type__']
|
||||
|
||||
|
||||
class PolicyViewSet(ReadOnlyModelViewSet):
|
||||
"""Policy Viewset"""
|
||||
|
||||
queryset = Policy.objects.all()
|
||||
serializer_class = PolicySerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return Policy.objects.select_subclasses()
|
30
passbook/core/api/propertymappings.py
Normal file
30
passbook/core/api/propertymappings.py
Normal file
@ -0,0 +1,30 @@
|
||||
"""PropertyMapping API Views"""
|
||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
|
||||
from passbook.core.models import PropertyMapping
|
||||
|
||||
|
||||
class PropertyMappingSerializer(ModelSerializer):
|
||||
"""PropertyMapping Serializer"""
|
||||
|
||||
__type__ = SerializerMethodField(method_name='get_type')
|
||||
|
||||
def get_type(self, obj):
|
||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace('propertymapping', '')
|
||||
|
||||
class Meta:
|
||||
|
||||
model = PropertyMapping
|
||||
fields = ['pk', 'name', '__type__']
|
||||
|
||||
|
||||
class PropertyMappingViewSet(ReadOnlyModelViewSet):
|
||||
"""PropertyMapping Viewset"""
|
||||
|
||||
queryset = PropertyMapping.objects.all()
|
||||
serializer_class = PropertyMappingSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return PropertyMapping.objects.select_subclasses()
|
30
passbook/core/api/providers.py
Normal file
30
passbook/core/api/providers.py
Normal file
@ -0,0 +1,30 @@
|
||||
"""Provider API Views"""
|
||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
|
||||
from passbook.core.models import Provider
|
||||
|
||||
|
||||
class ProviderSerializer(ModelSerializer):
|
||||
"""Provider Serializer"""
|
||||
|
||||
__type__ = SerializerMethodField(method_name='get_type')
|
||||
|
||||
def get_type(self, obj):
|
||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace('provider', '')
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Provider
|
||||
fields = ['pk', 'property_mappings', '__type__']
|
||||
|
||||
|
||||
class ProviderViewSet(ReadOnlyModelViewSet):
|
||||
"""Provider Viewset"""
|
||||
|
||||
queryset = Provider.objects.all()
|
||||
serializer_class = ProviderSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return Provider.objects.select_subclasses()
|
31
passbook/core/api/sources.py
Normal file
31
passbook/core/api/sources.py
Normal file
@ -0,0 +1,31 @@
|
||||
"""Source API Views"""
|
||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
|
||||
from passbook.admin.forms.source import SOURCE_SERIALIZER_FIELDS
|
||||
from passbook.core.models import Source
|
||||
|
||||
|
||||
class SourceSerializer(ModelSerializer):
|
||||
"""Source Serializer"""
|
||||
|
||||
__type__ = SerializerMethodField(method_name='get_type')
|
||||
|
||||
def get_type(self, obj):
|
||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace('source', '')
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Source
|
||||
fields = SOURCE_SERIALIZER_FIELDS + ['__type__']
|
||||
|
||||
|
||||
class SourceViewSet(ReadOnlyModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = Source.objects.all()
|
||||
serializer_class = SourceSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return Source.objects.select_subclasses()
|
@ -1,5 +1,4 @@
|
||||
"""passbook admin user API"""
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
"""User API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
@ -10,14 +9,13 @@ class UserSerializer(ModelSerializer):
|
||||
"""User Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = User
|
||||
fields = ['is_superuser', 'username', 'name', 'email', 'date_joined',
|
||||
'uuid']
|
||||
fields = ['pk', 'username', 'name', 'email']
|
||||
|
||||
|
||||
class UserViewSet(ModelViewSet):
|
||||
"""User Viewset"""
|
||||
|
||||
permission_classes = [IsAdminUser]
|
||||
serializer_class = UserSerializer
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserSerializer
|
17
passbook/core/migrations/0002_auto_20191010_1058.py
Normal file
17
passbook/core/migrations/0002_auto_20191010_1058.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 2.2.6 on 2019-10-10 10:58
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='user',
|
||||
options={'permissions': (('reset_user_password', 'Reset Password'),)},
|
||||
),
|
||||
]
|
14
passbook/core/migrations/0003_merge_20191010_1541.py
Normal file
14
passbook/core/migrations/0003_merge_20191010_1541.py
Normal file
@ -0,0 +1,14 @@
|
||||
# Generated by Django 2.2.6 on 2019-10-10 15:41
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0002_auto_20191010_1058'),
|
||||
('passbook_core', '0002_nonce_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
17
passbook/core/migrations/0004_remove_policy_action.py
Normal file
17
passbook/core/migrations/0004_remove_policy_action.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 2.2.6 on 2019-10-14 11:56
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0003_auto_20191011_0914'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='policy',
|
||||
name='action',
|
||||
),
|
||||
]
|
14
passbook/core/migrations/0005_merge_20191025_2022.py
Normal file
14
passbook/core/migrations/0005_merge_20191025_2022.py
Normal file
@ -0,0 +1,14 @@
|
||||
# Generated by Django 2.2.6 on 2019-10-25 20:22
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0004_remove_policy_action'),
|
||||
('passbook_core', '0003_merge_20191010_1541'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
@ -11,6 +11,7 @@ from django.db import models
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext as _
|
||||
from guardian.mixins import GuardianUserMixin
|
||||
from model_utils.managers import InheritanceManager
|
||||
from structlog import get_logger
|
||||
|
||||
@ -41,7 +42,7 @@ class Group(UUIDModel):
|
||||
|
||||
unique_together = (('name', 'parent',),)
|
||||
|
||||
class User(AbstractUser):
|
||||
class User(GuardianUserMixin, AbstractUser):
|
||||
"""Custom User model to allow easier adding o f user-based settings"""
|
||||
|
||||
uuid = models.UUIDField(default=uuid4, editable=False)
|
||||
@ -59,6 +60,11 @@ class User(AbstractUser):
|
||||
self.password_change_date = now()
|
||||
return super().set_password(password)
|
||||
|
||||
class Meta:
|
||||
|
||||
permissions = (
|
||||
('reset_user_password', 'Reset Password'),
|
||||
)
|
||||
|
||||
class Provider(models.Model):
|
||||
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
|
||||
@ -186,15 +192,7 @@ class Policy(UUIDModel, CreatedUpdatedModel):
|
||||
"""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."""
|
||||
|
||||
ACTION_ALLOW = 'allow'
|
||||
ACTION_DENY = 'deny'
|
||||
ACTIONS = (
|
||||
(ACTION_ALLOW, ACTION_ALLOW),
|
||||
(ACTION_DENY, ACTION_DENY),
|
||||
)
|
||||
|
||||
name = models.TextField(blank=True, null=True)
|
||||
action = models.CharField(max_length=20, choices=ACTIONS)
|
||||
negate = models.BooleanField(default=False)
|
||||
order = models.IntegerField(default=0)
|
||||
timeout = models.IntegerField(default=30)
|
||||
@ -202,9 +200,7 @@ class Policy(UUIDModel, CreatedUpdatedModel):
|
||||
objects = InheritanceManager()
|
||||
|
||||
def __str__(self):
|
||||
if self.name:
|
||||
return self.name
|
||||
return f"{self.name} action {self.action}"
|
||||
return f"Policy {self.name}"
|
||||
|
||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||
"""Check if user instance passes this policy"""
|
||||
|
@ -11,4 +11,4 @@ LOGGER = get_logger()
|
||||
def clean_nonces():
|
||||
"""Remove expired nonces"""
|
||||
amount, _ = Nonce.objects.filter(expires__lt=now(), expiring=True).delete()
|
||||
LOGGER.debug("Deleted expired nonces", amount=amount)
|
||||
LOGGER.debug('Deleted expired nonces', amount=amount)
|
||||
|
@ -17,8 +17,8 @@ def user_factors(context: RequestContext) -> List[UserSettings]:
|
||||
matching_factors: List[UserSettings] = []
|
||||
for factor in _all_factors:
|
||||
user_settings = factor.user_settings()
|
||||
policy_engine = PolicyEngine(factor.policies.all())
|
||||
policy_engine.for_user(user).with_request(context.get('request')).build()
|
||||
policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request'))
|
||||
policy_engine.build()
|
||||
if policy_engine.passing and user_settings:
|
||||
matching_factors.append(user_settings)
|
||||
return matching_factors
|
||||
@ -31,8 +31,8 @@ def user_sources(context: RequestContext) -> List[UserSettings]:
|
||||
matching_sources: List[UserSettings] = []
|
||||
for factor in _all_sources:
|
||||
user_settings = factor.user_settings()
|
||||
policy_engine = PolicyEngine(factor.policies.all())
|
||||
policy_engine.for_user(user).with_request(context.get('request')).build()
|
||||
policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request'))
|
||||
policy_engine.build()
|
||||
if policy_engine.passing and user_settings:
|
||||
matching_sources.append(user_settings)
|
||||
return matching_sources
|
||||
|
@ -31,6 +31,6 @@ class AccessMixin:
|
||||
def user_has_access(self, application: Application, user: User) -> Tuple[bool, List[str]]:
|
||||
"""Check if user has access to application."""
|
||||
LOGGER.debug("Checking permissions", user=user, application=application)
|
||||
policy_engine = PolicyEngine(application.policies.all())
|
||||
policy_engine.for_user(user).with_request(self.request).build()
|
||||
policy_engine = PolicyEngine(application.policies.all(), user, self.request)
|
||||
policy_engine.build()
|
||||
return policy_engine.result
|
||||
|
@ -16,8 +16,7 @@ class OverviewView(LoginRequiredMixin, TemplateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['applications'] = []
|
||||
for application in Application.objects.all():
|
||||
engine = PolicyEngine(application.policies.all())
|
||||
engine.for_user(self.request.user).with_request(self.request)
|
||||
engine = PolicyEngine(application.policies.all(), self.request.user, self.request)
|
||||
engine.build()
|
||||
if engine.passing:
|
||||
kwargs['applications'].append(application)
|
||||
|
21
passbook/factors/captcha/api.py
Normal file
21
passbook/factors/captcha/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""CaptchaFactor API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.factors.captcha.models import CaptchaFactor
|
||||
|
||||
|
||||
class CaptchaFactorSerializer(ModelSerializer):
|
||||
"""CaptchaFactor Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = CaptchaFactor
|
||||
fields = ['pk', 'name', 'slug', 'order', 'enabled', 'public_key', 'private_key']
|
||||
|
||||
|
||||
class CaptchaFactorViewSet(ModelViewSet):
|
||||
"""CaptchaFactor Viewset"""
|
||||
|
||||
queryset = CaptchaFactor.objects.all()
|
||||
serializer_class = CaptchaFactorSerializer
|
21
passbook/factors/dummy/api.py
Normal file
21
passbook/factors/dummy/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""DummyFactor API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.factors.dummy.models import DummyFactor
|
||||
|
||||
|
||||
class DummyFactorSerializer(ModelSerializer):
|
||||
"""DummyFactor Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = DummyFactor
|
||||
fields = ['pk', 'name', 'slug', 'order', 'enabled', '__type__']
|
||||
|
||||
|
||||
class DummyFactorViewSet(ModelViewSet):
|
||||
"""DummyFactor Viewset"""
|
||||
|
||||
queryset = DummyFactor.objects.all()
|
||||
serializer_class = DummyFactorSerializer
|
33
passbook/factors/email/api.py
Normal file
33
passbook/factors/email/api.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""EmailFactor API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.factors.email.models import EmailFactor
|
||||
|
||||
|
||||
class EmailFactorSerializer(ModelSerializer):
|
||||
"""EmailFactor Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EmailFactor
|
||||
fields = ['pk', 'name', 'slug', 'order', 'enabled', 'host',
|
||||
'port',
|
||||
'username',
|
||||
'password',
|
||||
'use_tls',
|
||||
'use_ssl',
|
||||
'timeout',
|
||||
'from_address',
|
||||
'ssl_keyfile',
|
||||
'ssl_certfile', ]
|
||||
extra_kwargs = {
|
||||
'password': {'write_only': True}
|
||||
}
|
||||
|
||||
|
||||
class EmailFactorViewSet(ModelViewSet):
|
||||
"""EmailFactor Viewset"""
|
||||
|
||||
queryset = EmailFactor.objects.all()
|
||||
serializer_class = EmailFactorSerializer
|
21
passbook/factors/otp/api.py
Normal file
21
passbook/factors/otp/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""OTPFactor API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.factors.otp.models import OTPFactor
|
||||
|
||||
|
||||
class OTPFactorSerializer(ModelSerializer):
|
||||
"""OTPFactor Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = OTPFactor
|
||||
fields = ['pk', 'name', 'slug', 'order', 'enabled', 'enforced']
|
||||
|
||||
|
||||
class OTPFactorViewSet(ModelViewSet):
|
||||
"""OTPFactor Viewset"""
|
||||
|
||||
queryset = OTPFactor.objects.all()
|
||||
serializer_class = OTPFactorSerializer
|
22
passbook/factors/password/api.py
Normal file
22
passbook/factors/password/api.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""PasswordFactor API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.factors.password.models import PasswordFactor
|
||||
|
||||
|
||||
class PasswordFactorSerializer(ModelSerializer):
|
||||
"""PasswordFactor Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = PasswordFactor
|
||||
fields = ['pk', 'name', 'slug', 'order', 'enabled',
|
||||
'backends', 'password_policies', 'reset_factors']
|
||||
|
||||
|
||||
class PasswordFactorViewSet(ModelViewSet):
|
||||
"""PasswordFactor Viewset"""
|
||||
|
||||
queryset = PasswordFactor.objects.all()
|
||||
serializer_class = PasswordFactorSerializer
|
@ -13,8 +13,8 @@ def password_policy_checker(sender, password, **_):
|
||||
setattr(sender, '__password__', password)
|
||||
_all_factors = PasswordFactor.objects.filter(enabled=True).order_by('order')
|
||||
for factor in _all_factors:
|
||||
policy_engine = PolicyEngine(factor.password_policies.all().select_subclasses())
|
||||
policy_engine.for_user(sender).build()
|
||||
policy_engine = PolicyEngine(factor.password_policies.all().select_subclasses(), sender)
|
||||
policy_engine.build()
|
||||
passing, messages = policy_engine.result
|
||||
if not passing:
|
||||
raise PasswordPolicyInvalid(*messages)
|
||||
|
@ -67,8 +67,8 @@ class AuthenticationView(UserPassesTestMixin, View):
|
||||
for factor in _all_factors:
|
||||
LOGGER.debug("Checking if factor applies to user",
|
||||
factor=factor, user=self.pending_user)
|
||||
policy_engine = PolicyEngine(factor.policies.all())
|
||||
policy_engine.for_user(self.pending_user).with_request(self.request).build()
|
||||
policy_engine = PolicyEngine(factor.policies.all(), self.pending_user, self.request)
|
||||
policy_engine.build()
|
||||
if policy_engine.passing:
|
||||
pending_factors.append((factor.uuid.hex, factor.type))
|
||||
LOGGER.debug("Factor applies", factor=factor, user=self.pending_user)
|
||||
|
@ -4,6 +4,7 @@ from django.apps import apps
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.sites import AlreadyRegistered
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from guardian.admin import GuardedModelAdmin
|
||||
|
||||
from passbook.core.models import User
|
||||
|
||||
@ -13,10 +14,9 @@ def admin_autoregister(app):
|
||||
app_models = apps.get_app_config(app).get_models()
|
||||
for model in app_models:
|
||||
try:
|
||||
admin.site.register(model)
|
||||
admin.site.register(model, GuardedModelAdmin)
|
||||
except AlreadyRegistered:
|
||||
pass
|
||||
|
||||
|
||||
admin.site.register(User, UserAdmin)
|
||||
admin_autoregister('passbook_core')
|
||||
|
@ -11,6 +11,7 @@ def before_send(event, hint):
|
||||
from rest_framework.exceptions import APIException
|
||||
from billiard.exceptions import WorkerLostError
|
||||
from django.core.exceptions import DisallowedHost
|
||||
from botocore.client import ClientError
|
||||
ignored_classes = (
|
||||
OperationalError,
|
||||
ConnectionInterrupted,
|
||||
@ -20,6 +21,8 @@ def before_send(event, hint):
|
||||
WorkerLostError,
|
||||
DisallowedHost,
|
||||
ConnectionResetError,
|
||||
KeyboardInterrupt,
|
||||
ClientError
|
||||
)
|
||||
if 'exc_info' in hint:
|
||||
_exc_type, exc_value, _ = hint['exc_info']
|
||||
|
13
passbook/lib/tasks.py
Normal file
13
passbook/lib/tasks.py
Normal file
@ -0,0 +1,13 @@
|
||||
"""passbook misc tasks"""
|
||||
from django.core import management
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.root.celery import CELERY_APP
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@CELERY_APP.task()
|
||||
def backup_database():
|
||||
"""Backup database"""
|
||||
management.call_command('dbbackup')
|
||||
LOGGER.info('Successfully backed up database.')
|
16
passbook/lib/views.py
Normal file
16
passbook/lib/views.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""passbook helper views"""
|
||||
|
||||
from django.views.generic import CreateView
|
||||
from guardian.shortcuts import assign_perm
|
||||
|
||||
|
||||
class CreateAssignPermView(CreateView):
|
||||
"""Assign permissions to object after creation"""
|
||||
|
||||
permissions = []
|
||||
|
||||
def form_valid(self, form):
|
||||
response = super().form_valid(form)
|
||||
for permission in self.permissions:
|
||||
assign_perm(permission, self.request.user, self.object)
|
||||
return response
|
@ -1,7 +1,7 @@
|
||||
"""passbook policy engine"""
|
||||
from multiprocessing import Pipe
|
||||
from multiprocessing.connection import Connection
|
||||
from typing import List, Tuple
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.http import HttpRequest
|
||||
@ -13,44 +13,37 @@ from passbook.policies.struct import PolicyRequest, PolicyResult
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class PolicyProcessInfo:
|
||||
"""Dataclass to hold all information and communication channels to a process"""
|
||||
|
||||
process: PolicyProcess
|
||||
connection: Connection
|
||||
result: PolicyResult = None
|
||||
result: Optional[PolicyResult]
|
||||
policy: Policy
|
||||
|
||||
def __init__(self, process: PolicyProcess, connection: Connection, policy: Policy):
|
||||
self.process = process
|
||||
self.connection = connection
|
||||
self.policy = policy
|
||||
self.result = None
|
||||
|
||||
class PolicyEngine:
|
||||
"""Orchestrate policy checking, launch tasks and return result"""
|
||||
|
||||
use_cache: bool = True
|
||||
policies: List[Policy] = []
|
||||
__request: HttpRequest
|
||||
__user: User
|
||||
request: PolicyRequest
|
||||
|
||||
__processes: List[PolicyProcessInfo] = []
|
||||
|
||||
def __init__(self, policies, user: User = None, request: HttpRequest = None):
|
||||
def __init__(self, policies, user: User, request: HttpRequest = None):
|
||||
self.policies = policies
|
||||
self.__request = request
|
||||
self.__user = user
|
||||
self.request = PolicyRequest(user)
|
||||
if request:
|
||||
self.request.http_request = request
|
||||
self.__processes = []
|
||||
|
||||
def for_user(self, user: User) -> 'PolicyEngine':
|
||||
"""Check policies for user"""
|
||||
self.__user = user
|
||||
return self
|
||||
|
||||
def with_request(self, request: HttpRequest) -> 'PolicyEngine':
|
||||
"""Set request"""
|
||||
self.__request = request
|
||||
return self
|
||||
|
||||
def _select_subclasses(self) -> List[Policy]:
|
||||
"""Make sure all Policies are their respective classes"""
|
||||
return Policy.objects \
|
||||
@ -60,21 +53,17 @@ class PolicyEngine:
|
||||
|
||||
def build(self) -> 'PolicyEngine':
|
||||
"""Build task group"""
|
||||
if not self.__user:
|
||||
raise ValueError("User not set.")
|
||||
cached_policies = []
|
||||
request = PolicyRequest(self.__user)
|
||||
request.http_request = self.__request
|
||||
for policy in self._select_subclasses():
|
||||
cached_policy = cache.get(cache_key(policy, self.__user), None)
|
||||
if cached_policy:
|
||||
cached_policy = cache.get(cache_key(policy, self.request.user), None)
|
||||
if cached_policy and self.use_cache:
|
||||
LOGGER.debug("Taking result from cache", policy=policy)
|
||||
cached_policies.append(cached_policy)
|
||||
else:
|
||||
LOGGER.debug("Evaluating policy", policy=policy)
|
||||
our_end, task_end = Pipe(False)
|
||||
task = PolicyProcess(policy, request, task_end)
|
||||
LOGGER.debug("Starting Process", for_policy=policy)
|
||||
task = PolicyProcess(policy, self.request, task_end)
|
||||
LOGGER.debug("Starting Process", policy=policy)
|
||||
task.start()
|
||||
self.__processes.append(PolicyProcessInfo(process=task,
|
||||
connection=our_end, policy=policy))
|
||||
@ -91,9 +80,7 @@ class PolicyEngine:
|
||||
"""Get policy-checking result"""
|
||||
messages: List[str] = []
|
||||
for proc_info in self.__processes:
|
||||
# passing = (policy_action == Policy.ACTION_ALLOW and policy_result) or \
|
||||
# (policy_action == Policy.ACTION_DENY and not policy_result)
|
||||
LOGGER.debug("Result", passing=proc_info.result.passing)
|
||||
LOGGER.debug("Result", policy=proc_info.policy, passing=proc_info.result.passing)
|
||||
if proc_info.result.messages:
|
||||
messages += proc_info.result.messages
|
||||
if not proc_info.result.passing:
|
||||
|
21
passbook/policies/expiry/api.py
Normal file
21
passbook/policies/expiry/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Source API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.policies.expiry.models import PasswordExpiryPolicy
|
||||
from passbook.policies.forms import GENERAL_SERIALIZER_FIELDS
|
||||
|
||||
|
||||
class PasswordExpiryPolicySerializer(ModelSerializer):
|
||||
"""Password Expiry Policy Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = PasswordExpiryPolicy
|
||||
fields = GENERAL_SERIALIZER_FIELDS + ['days', 'deny_only']
|
||||
|
||||
|
||||
class PasswordExpiryPolicyViewSet(ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = PasswordExpiryPolicy.objects.all()
|
||||
serializer_class = PasswordExpiryPolicySerializer
|
@ -1,3 +1,4 @@
|
||||
"""General fields"""
|
||||
|
||||
GENERAL_FIELDS = ['name', 'action', 'negate', 'order', 'timeout']
|
||||
GENERAL_FIELDS = ['name', 'negate', 'order', 'timeout']
|
||||
GENERAL_SERIALIZER_FIELDS = ['pk', 'name', 'negate', 'order', 'timeout']
|
||||
|
21
passbook/policies/group/api.py
Normal file
21
passbook/policies/group/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Source API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.policies.forms import GENERAL_SERIALIZER_FIELDS
|
||||
from passbook.policies.group.models import GroupMembershipPolicy
|
||||
|
||||
|
||||
class GroupMembershipPolicySerializer(ModelSerializer):
|
||||
"""Group Membership Policy Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = GroupMembershipPolicy
|
||||
fields = GENERAL_SERIALIZER_FIELDS + ['group']
|
||||
|
||||
|
||||
class GroupMembershipPolicyViewSet(ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = GroupMembershipPolicy.objects.all()
|
||||
serializer_class = GroupMembershipPolicySerializer
|
21
passbook/policies/hibp/api.py
Normal file
21
passbook/policies/hibp/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Source API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.policies.forms import GENERAL_SERIALIZER_FIELDS
|
||||
from passbook.policies.hibp.models import HaveIBeenPwendPolicy
|
||||
|
||||
|
||||
class HaveIBeenPwendPolicySerializer(ModelSerializer):
|
||||
"""Have I Been Pwned Policy Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = HaveIBeenPwendPolicy
|
||||
fields = GENERAL_SERIALIZER_FIELDS + ['allowed_count']
|
||||
|
||||
|
||||
class HaveIBeenPwendPolicyViewSet(ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = HaveIBeenPwendPolicy.objects.all()
|
||||
serializer_class = HaveIBeenPwendPolicySerializer
|
21
passbook/policies/matcher/api.py
Normal file
21
passbook/policies/matcher/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Source API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.policies.forms import GENERAL_SERIALIZER_FIELDS
|
||||
from passbook.policies.matcher.models import FieldMatcherPolicy
|
||||
|
||||
|
||||
class FieldMatcherPolicySerializer(ModelSerializer):
|
||||
"""Field Matcher Policy Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = FieldMatcherPolicy
|
||||
fields = GENERAL_SERIALIZER_FIELDS + ['user_field', 'match_action', 'value', ]
|
||||
|
||||
|
||||
class FieldMatcherPolicyViewSet(ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = FieldMatcherPolicy.objects.all()
|
||||
serializer_class = FieldMatcherPolicySerializer
|
23
passbook/policies/password/api.py
Normal file
23
passbook/policies/password/api.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""Source API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.policies.forms import GENERAL_SERIALIZER_FIELDS
|
||||
from passbook.policies.password.models import PasswordPolicy
|
||||
|
||||
|
||||
class PasswordPolicySerializer(ModelSerializer):
|
||||
"""Password Policy Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = PasswordPolicy
|
||||
fields = GENERAL_SERIALIZER_FIELDS + ['amount_uppercase', 'amount_lowercase',
|
||||
'amount_symbols', 'length_min', 'symbol_charset',
|
||||
'error_message']
|
||||
|
||||
|
||||
class PasswordPolicyViewSet(ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = PasswordPolicy.objects.all()
|
||||
serializer_class = PasswordPolicySerializer
|
@ -40,9 +40,9 @@ class PolicyProcess(Process):
|
||||
policy_result = PolicyResult(False, str(exc))
|
||||
# Invert result if policy.negate is set
|
||||
if self.policy.negate:
|
||||
policy_result = not policy_result
|
||||
policy_result.passing = not policy_result.passing
|
||||
LOGGER.debug("Got result", policy=self.policy, result=policy_result,
|
||||
process="PolicyProcess")
|
||||
process="PolicyProcess", passing=policy_result.passing, user=self.request.user)
|
||||
key = cache_key(self.policy, self.request.user)
|
||||
cache.set(key, policy_result)
|
||||
LOGGER.debug("Cached policy evaluation", key=key)
|
||||
|
21
passbook/policies/reputation/api.py
Normal file
21
passbook/policies/reputation/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Source API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.policies.forms import GENERAL_SERIALIZER_FIELDS
|
||||
from passbook.policies.reputation.models import ReputationPolicy
|
||||
|
||||
|
||||
class ReputationPolicySerializer(ModelSerializer):
|
||||
"""Reputation Policy Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = ReputationPolicy
|
||||
fields = GENERAL_SERIALIZER_FIELDS + ['check_ip', 'check_username', 'threshold']
|
||||
|
||||
|
||||
class ReputationPolicyViewSet(ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = ReputationPolicy.objects.all()
|
||||
serializer_class = ReputationPolicySerializer
|
@ -1,5 +1,6 @@
|
||||
"""passbook reputation request forms"""
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from passbook.core.forms.policies import GENERAL_FIELDS
|
||||
from passbook.policies.reputation.models import ReputationPolicy
|
||||
@ -16,3 +17,6 @@ class ReputationPolicyForm(forms.ModelForm):
|
||||
'name': forms.TextInput(),
|
||||
'value': forms.TextInput(),
|
||||
}
|
||||
labels = {
|
||||
'check_ip': _('Check IP'),
|
||||
}
|
||||
|
21
passbook/policies/sso/api.py
Normal file
21
passbook/policies/sso/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""Source API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.policies.forms import GENERAL_SERIALIZER_FIELDS
|
||||
from passbook.policies.sso.models import SSOLoginPolicy
|
||||
|
||||
|
||||
class SSOLoginPolicySerializer(ModelSerializer):
|
||||
"""SSO Login Policy Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = SSOLoginPolicy
|
||||
fields = GENERAL_SERIALIZER_FIELDS
|
||||
|
||||
|
||||
class SSOLoginPolicyViewSet(ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = SSOLoginPolicy.objects.all()
|
||||
serializer_class = SSOLoginPolicySerializer
|
@ -1,8 +1,9 @@
|
||||
"""policy structs"""
|
||||
"""policy structures"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from django.db.models import Model
|
||||
from django.http import HttpRequest
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -13,6 +14,7 @@ class PolicyRequest:
|
||||
|
||||
user: User
|
||||
http_request: HttpRequest
|
||||
obj: Model
|
||||
|
||||
def __init__(self, user: User):
|
||||
self.user = user
|
||||
|
0
passbook/policies/tests/__init__.py
Normal file
0
passbook/policies/tests/__init__.py
Normal file
59
passbook/policies/tests/test_engine.py
Normal file
59
passbook/policies/tests/test_engine.py
Normal file
@ -0,0 +1,59 @@
|
||||
"""policy engine tests"""
|
||||
from django.core.cache import cache
|
||||
from django.test import TestCase
|
||||
|
||||
from passbook.core.models import DebugPolicy, Policy, User
|
||||
from passbook.policies.engine import PolicyEngine
|
||||
|
||||
|
||||
class PolicyTestEngine(TestCase):
|
||||
"""PolicyEngine tests"""
|
||||
|
||||
def setUp(self):
|
||||
cache.clear()
|
||||
self.user = User.objects.create_user(
|
||||
username="policyuser")
|
||||
self.policy_false = DebugPolicy.objects.create(
|
||||
result=False,
|
||||
wait_min=0,
|
||||
wait_max=1)
|
||||
self.policy_true = DebugPolicy.objects.create(
|
||||
result=True,
|
||||
wait_min=0,
|
||||
wait_max=1)
|
||||
self.policy_negate = DebugPolicy.objects.create(
|
||||
negate=True,
|
||||
result=True,
|
||||
wait_min=0,
|
||||
wait_max=1)
|
||||
self.policy_raises = Policy.objects.create(
|
||||
name='raises')
|
||||
|
||||
def test_engine_empty(self):
|
||||
"""Ensure empty policy list passes"""
|
||||
engine = PolicyEngine([], self.user)
|
||||
self.assertEqual(engine.build().passing, True)
|
||||
|
||||
def test_engine(self):
|
||||
"""Ensure all policies passes (Mix of false and true -> false)"""
|
||||
engine = PolicyEngine(DebugPolicy.objects.filter(negate__exact=False), self.user)
|
||||
self.assertEqual(engine.build().passing, False)
|
||||
|
||||
def test_engine_negate(self):
|
||||
"""Test negate flag"""
|
||||
engine = PolicyEngine(DebugPolicy.objects.filter(negate__exact=True), self.user)
|
||||
self.assertEqual(engine.build().passing, False)
|
||||
|
||||
def test_engine_policy_error(self):
|
||||
"""Test negate flag"""
|
||||
engine = PolicyEngine(Policy.objects.filter(name='raises'), self.user)
|
||||
self.assertEqual(engine.build().passing, False)
|
||||
|
||||
def test_engine_cache(self):
|
||||
"""Ensure empty policy list passes"""
|
||||
engine = PolicyEngine(DebugPolicy.objects.filter(negate__exact=False), self.user)
|
||||
self.assertEqual(len(cache.keys('policy_*')), 0)
|
||||
self.assertEqual(engine.build().passing, False)
|
||||
self.assertEqual(len(cache.keys('policy_*')), 2)
|
||||
self.assertEqual(engine.build().passing, False)
|
||||
self.assertEqual(len(cache.keys('policy_*')), 2)
|
22
passbook/policies/webhook/api.py
Normal file
22
passbook/policies/webhook/api.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""Source API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.policies.forms import GENERAL_SERIALIZER_FIELDS
|
||||
from passbook.policies.webhook.models import WebhookPolicy
|
||||
|
||||
|
||||
class WebhookPolicySerializer(ModelSerializer):
|
||||
"""Webhook Policy Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = WebhookPolicy
|
||||
fields = GENERAL_SERIALIZER_FIELDS + ['url', 'method', 'json_body', 'json_headers',
|
||||
'result_jsonpath', 'result_json_value', ]
|
||||
|
||||
|
||||
class WebhookPolicyViewSet(ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = WebhookPolicy.objects.all()
|
||||
serializer_class = WebhookPolicySerializer
|
21
passbook/providers/app_gw/api.py
Normal file
21
passbook/providers/app_gw/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""ApplicationGatewayProvider API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.providers.app_gw.models import ApplicationGatewayProvider
|
||||
|
||||
|
||||
class ApplicationGatewayProviderSerializer(ModelSerializer):
|
||||
"""ApplicationGatewayProvider Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ApplicationGatewayProvider
|
||||
fields = ['pk', 'server_name', 'upstream', 'enabled', 'authentication_header',
|
||||
'default_content_type', 'upstream_ssl_verification', 'property_mappings']
|
||||
|
||||
class ApplicationGatewayProviderViewSet(ModelViewSet):
|
||||
"""ApplicationGatewayProvider Viewset"""
|
||||
|
||||
queryset = ApplicationGatewayProvider.objects.all()
|
||||
serializer_class = ApplicationGatewayProviderSerializer
|
21
passbook/providers/oauth/api.py
Normal file
21
passbook/providers/oauth/api.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""OAuth2Provider API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.providers.oauth.models import OAuth2Provider
|
||||
|
||||
|
||||
class OAuth2ProviderSerializer(ModelSerializer):
|
||||
"""OAuth2Provider Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = OAuth2Provider
|
||||
fields = ['pk', 'name', 'redirect_uris', 'client_type',
|
||||
'authorization_grant_type', 'client_id', 'client_secret', ]
|
||||
|
||||
class OAuth2ProviderViewSet(ModelViewSet):
|
||||
"""OAuth2Provider Viewset"""
|
||||
|
||||
queryset = OAuth2Provider.objects.all()
|
||||
serializer_class = OAuth2ProviderSerializer
|
@ -8,7 +8,7 @@ from django.utils.translation import ugettext as _
|
||||
from oauth2_provider.views.base import AuthorizationView
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.audit.models import AuditEntry
|
||||
from passbook.audit.models import Event
|
||||
from passbook.core.models import Application
|
||||
from passbook.core.views.access import AccessMixin
|
||||
from passbook.core.views.utils import LoadingView, PermissionDeniedView
|
||||
@ -77,8 +77,8 @@ class PassbookAuthorizationView(AccessMixin, AuthorizationView):
|
||||
|
||||
def form_valid(self, form):
|
||||
# User has clicked on "Authorize"
|
||||
AuditEntry.create(
|
||||
action=AuditEntry.ACTION_AUTHORIZE_APPLICATION,
|
||||
Event.create(
|
||||
action=Event.ACTION_AUTHORIZE_APPLICATION,
|
||||
request=self.request,
|
||||
app=str(self._application))
|
||||
LOGGER.debug('user %s authorized %s', self.request.user, self._application)
|
||||
|
22
passbook/providers/oidc/api.py
Normal file
22
passbook/providers/oidc/api.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""OpenIDProvider API Views"""
|
||||
from oidc_provider.models import Client
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
# from passbook.providers.oidc.models import OpenIDProvider
|
||||
|
||||
|
||||
class OpenIDProviderSerializer(ModelSerializer):
|
||||
"""OpenIDProvider Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Client
|
||||
fields = ['pk', 'name', 'client_type', 'client_id', 'client_secret', 'response_types',
|
||||
'jwt_alg', 'reuse_consent', 'require_consent', '_redirect_uris', '_scope']
|
||||
|
||||
class OpenIDProviderViewSet(ModelViewSet):
|
||||
"""OpenIDProvider Viewset"""
|
||||
|
||||
queryset = Client.objects.all()
|
||||
serializer_class = OpenIDProviderSerializer
|
@ -3,7 +3,7 @@ from django.contrib import messages
|
||||
from django.shortcuts import redirect
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.audit.models import AuditEntry
|
||||
from passbook.audit.models import Event
|
||||
from passbook.core.models import Application
|
||||
from passbook.policies.engine import PolicyEngine
|
||||
|
||||
@ -18,8 +18,8 @@ def check_permissions(request, user, client):
|
||||
except Application.DoesNotExist:
|
||||
return redirect('passbook_providers_oauth:oauth2-permission-denied')
|
||||
LOGGER.debug("Checking permissions for application", user=user, application=application)
|
||||
policy_engine = PolicyEngine(application.policies.all())
|
||||
policy_engine.for_user(user).with_request(request).build()
|
||||
policy_engine = PolicyEngine(application.policies.all(), user, request)
|
||||
policy_engine.build()
|
||||
|
||||
# Check permissions
|
||||
passing, policy_messages = policy_engine.result
|
||||
@ -28,8 +28,8 @@ def check_permissions(request, user, client):
|
||||
messages.error(request, policy_message)
|
||||
return redirect('passbook_providers_oauth:oauth2-permission-denied')
|
||||
|
||||
AuditEntry.create(
|
||||
action=AuditEntry.ACTION_AUTHORIZE_APPLICATION,
|
||||
Event.create(
|
||||
action=Event.ACTION_AUTHORIZE_APPLICATION,
|
||||
request=request,
|
||||
app=application.name,
|
||||
skipped_authorization=False)
|
||||
|
38
passbook/providers/saml/api.py
Normal file
38
passbook/providers/saml/api.py
Normal file
@ -0,0 +1,38 @@
|
||||
"""SAMLProvider API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.providers.saml.models import SAMLPropertyMapping, SAMLProvider
|
||||
|
||||
|
||||
class SAMLProviderSerializer(ModelSerializer):
|
||||
"""SAMLProvider Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = SAMLProvider
|
||||
fields = ['pk', 'name', 'property_mappings', 'acs_url', 'audience', 'processor_path',
|
||||
'issuer', 'assertion_valid_for', 'signing', 'signing_cert', 'signing_key', ]
|
||||
|
||||
|
||||
class SAMLProviderViewSet(ModelViewSet):
|
||||
"""SAMLProvider Viewset"""
|
||||
|
||||
queryset = SAMLProvider.objects.all()
|
||||
serializer_class = SAMLProviderSerializer
|
||||
|
||||
|
||||
class SAMLPropertyMappingSerializer(ModelSerializer):
|
||||
"""SAMLPropertyMapping Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = SAMLPropertyMapping
|
||||
fields = ['pk', 'name', 'saml_name', 'friendly_name', 'values']
|
||||
|
||||
|
||||
class SAMLPropertyMappingViewSet(ModelViewSet):
|
||||
"""SAMLPropertyMapping Viewset"""
|
||||
|
||||
queryset = SAMLPropertyMapping.objects.all()
|
||||
serializer_class = SAMLPropertyMappingSerializer
|
@ -13,7 +13,7 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
from signxml.util import strip_pem_header
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.audit.models import AuditEntry
|
||||
from passbook.audit.models import Event
|
||||
from passbook.core.models import Application
|
||||
from passbook.lib.mixins import CSRFExemptMixin
|
||||
from passbook.lib.utils.template import render_to_string
|
||||
@ -59,8 +59,9 @@ class AccessRequiredView(AccessMixin, View):
|
||||
|
||||
def _has_access(self):
|
||||
"""Check if user has access to application"""
|
||||
policy_engine = PolicyEngine(self.provider.application.policies.all())
|
||||
policy_engine.for_user(self.request.user).with_request(self.request).build()
|
||||
policy_engine = PolicyEngine(self.provider.application.policies.all(),
|
||||
self.request.user, self.request)
|
||||
policy_engine.build()
|
||||
return policy_engine.passing
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
@ -122,8 +123,8 @@ class LoginProcessView(AccessRequiredView):
|
||||
if self.provider.application.skip_authorization:
|
||||
ctx = self.provider.processor.generate_response()
|
||||
# Log Application Authorization
|
||||
AuditEntry.create(
|
||||
action=AuditEntry.ACTION_AUTHORIZE_APPLICATION,
|
||||
Event.create(
|
||||
action=Event.ACTION_AUTHORIZE_APPLICATION,
|
||||
request=request,
|
||||
app=self.provider.application.name,
|
||||
skipped_authorization=True)
|
||||
@ -144,8 +145,8 @@ class LoginProcessView(AccessRequiredView):
|
||||
# Check if user has access
|
||||
if request.POST.get('ACSUrl', None):
|
||||
# User accepted request
|
||||
AuditEntry.create(
|
||||
action=AuditEntry.ACTION_AUTHORIZE_APPLICATION,
|
||||
Event.create(
|
||||
action=Event.ACTION_AUTHORIZE_APPLICATION,
|
||||
request=request,
|
||||
app=self.provider.application.name,
|
||||
skipped_authorization=False)
|
||||
|
31
passbook/recovery/tests.py
Normal file
31
passbook/recovery/tests.py
Normal file
@ -0,0 +1,31 @@
|
||||
"""recovery tests"""
|
||||
from io import StringIO
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase
|
||||
|
||||
from passbook.core.models import Nonce, User
|
||||
|
||||
|
||||
class TestRecovery(TestCase):
|
||||
"""recovery tests"""
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username='recovery-test-user')
|
||||
|
||||
def test_create_key(self):
|
||||
"""Test creation of a new key"""
|
||||
out = StringIO()
|
||||
self.assertEqual(len(Nonce.objects.all()), 0)
|
||||
call_command('create_recovery_key', '1', self.user.username, stdout=out)
|
||||
self.assertIn('https://localhost/recovery/use-nonce/', out.getvalue())
|
||||
self.assertEqual(len(Nonce.objects.all()), 1)
|
||||
|
||||
def test_recovery_view(self):
|
||||
"""Test recovery view"""
|
||||
call_command('create_recovery_key', '1', self.user.username)
|
||||
nonce = Nonce.objects.first()
|
||||
self.client.get(reverse('passbook_recovery:use-nonce', kwargs={'uuid': str(nonce.uuid)}))
|
||||
self.assertEqual(int(self.client.session['_auth_user_id']), nonce.user.pk)
|
@ -24,6 +24,8 @@ from passbook import __version__
|
||||
from passbook.lib.config import CONFIG
|
||||
from passbook.lib.sentry import before_send
|
||||
|
||||
LOGGER = structlog.get_logger()
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
STATIC_ROOT = BASE_DIR + '/static'
|
||||
@ -53,10 +55,10 @@ LANGUAGE_COOKIE_NAME = 'passbook_language'
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'guardian.backends.ObjectPermissionBackend',
|
||||
]
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
@ -65,8 +67,10 @@ INSTALLED_APPS = [
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.postgres',
|
||||
# 'rest_framework',
|
||||
# 'drf_yasg',
|
||||
'rest_framework',
|
||||
'drf_yasg',
|
||||
'guardian',
|
||||
|
||||
'passbook.core.apps.PassbookCoreConfig',
|
||||
'passbook.admin.apps.PassbookAdminConfig',
|
||||
'passbook.api.apps.PassbookAPIConfig',
|
||||
@ -98,12 +102,35 @@ INSTALLED_APPS = [
|
||||
'passbook.policies.webhook.apps.PassbookPoliciesWebhookConfig',
|
||||
]
|
||||
|
||||
GUARDIAN_MONKEY_PATCH = False
|
||||
|
||||
SWAGGER_SETTINGS = {
|
||||
'DEFAULT_INFO': 'passbook.api.v2.urls.info',
|
||||
# 'SECURITY_DEFINITIONS': {
|
||||
# 'JWT': {
|
||||
# 'type': 'apiKey',
|
||||
# 'name': 'Authorization',
|
||||
# 'in': 'header'
|
||||
# }
|
||||
# }
|
||||
}
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
# Use Django's standard `django.contrib.auth` permissions,
|
||||
# or allow read-only access for unauthenticated users.
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
|
||||
]
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||
'PAGE_SIZE': 100,
|
||||
'DEFAULT_FILTER_BACKENDS': [
|
||||
'django_filters.rest_framework.DjangoFilterBackend',
|
||||
'rest_framework.filters.OrderingFilter',
|
||||
'rest_framework.filters.SearchFilter',
|
||||
],
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
# 'rest_framework.permissions.IsAuthenticated',
|
||||
'passbook.api.permissions.CustomObjectPermissions',
|
||||
),
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
# 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
|
||||
),
|
||||
}
|
||||
|
||||
CACHES = {
|
||||
@ -118,7 +145,7 @@ CACHES = {
|
||||
}
|
||||
DJANGO_REDIS_IGNORE_EXCEPTIONS = True
|
||||
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
SESSION_CACHE_ALIAS = "default"
|
||||
|
||||
MIDDLEWARE = [
|
||||
@ -213,7 +240,24 @@ CELERY_BROKER_URL = (f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.ho
|
||||
CELERY_RESULT_BACKEND = (f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}"
|
||||
f":6379/{CONFIG.y('redis.message_queue_db')}")
|
||||
|
||||
# Database backup
|
||||
if CONFIG.y('postgresql.backup'):
|
||||
INSTALLED_APPS += ['dbbackup']
|
||||
DBBACKUP_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
||||
|
||||
AWS_ACCESS_KEY_ID = CONFIG.y('postgresql.backup.access_key')
|
||||
AWS_SECRET_ACCESS_KEY = CONFIG.y('postgresql.backup.secret_key')
|
||||
AWS_STORAGE_BUCKET_NAME = CONFIG.y('postgresql.backup.bucket')
|
||||
AWS_S3_ENDPOINT_URL = CONFIG.y('postgresql.backup.host')
|
||||
AWS_DEFAULT_ACL = None
|
||||
LOGGER.info('Database backup is configured', host=CONFIG.y('postgresql.backup.host'))
|
||||
# Add automatic task to backup
|
||||
CELERY_BEAT_SCHEDULE['db_backup'] = {
|
||||
'task': 'passbook.lib.tasks.backup_database',
|
||||
'schedule': crontab(minute=0, hour=0) # Run every day, midnight
|
||||
}
|
||||
|
||||
# Sentry integration
|
||||
if not DEBUG:
|
||||
sentry_init(
|
||||
dsn="https://33cdbcb23f8b436dbe0ee06847410b67@sentry.beryju.org/3",
|
||||
@ -269,7 +313,7 @@ with CONFIG.cd('log'):
|
||||
'formatters': {
|
||||
"plain": {
|
||||
"()": structlog.stdlib.ProcessorFormatter,
|
||||
"processor": structlog.processors.JSONRenderer(),
|
||||
"processor": structlog.processors.JSONRenderer(sort_keys=True),
|
||||
"foreign_pre_chain": LOG_PRE_CHAIN,
|
||||
},
|
||||
"colored": {
|
||||
|
@ -14,14 +14,12 @@ from structlog import get_logger
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings")
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class WSGILogger:
|
||||
""" This is the generalized WSGI middleware for any style request logging. """
|
||||
|
||||
def __init__(self, application):
|
||||
self.application = application
|
||||
self.logger = get_logger('passbook.wsgi')
|
||||
|
||||
def __healthcheck(self, start_response):
|
||||
start_response('204 OK', [])
|
||||
@ -32,7 +30,7 @@ class WSGILogger:
|
||||
status_codes = []
|
||||
content_lengths = []
|
||||
|
||||
if environ.get('HTTP_HOST').startswith('kubernetes-healthcheck-host'):
|
||||
if environ.get('HTTP_HOST', '').startswith('kubernetes-healthcheck-host'):
|
||||
# Don't log kubernetes health/readiness requests
|
||||
return self.__healthcheck(start_response)
|
||||
|
||||
@ -64,13 +62,13 @@ class WSGILogger:
|
||||
query_string = ''
|
||||
if environ.get('QUERY_STRING') != '':
|
||||
query_string = f"?{environ.get('QUERY_STRING')}"
|
||||
LOGGER.info(f"{environ.get('PATH_INFO', '')}{query_string}",
|
||||
host=host,
|
||||
method=environ.get('REQUEST_METHOD', ''),
|
||||
protocol=environ.get('SERVER_PROTOCOL', ''),
|
||||
status=status_code,
|
||||
size=content_length / 1000 if content_length > 0 else '-',
|
||||
runtime=kwargs.get('runtime'))
|
||||
self.logger.info(f"{environ.get('PATH_INFO', '')}{query_string}",
|
||||
host=host,
|
||||
method=environ.get('REQUEST_METHOD', ''),
|
||||
protocol=environ.get('SERVER_PROTOCOL', ''),
|
||||
status=status_code,
|
||||
size=content_length / 1000 if content_length > 0 else '-',
|
||||
runtime=kwargs.get('runtime'))
|
||||
|
||||
|
||||
application = WSGILogger(get_wsgi_application())
|
||||
|
54
passbook/sources/ldap/api.py
Normal file
54
passbook/sources/ldap/api.py
Normal file
@ -0,0 +1,54 @@
|
||||
"""Source API Views"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.admin.forms.source import SOURCE_SERIALIZER_FIELDS
|
||||
from passbook.sources.ldap.models import LDAPPropertyMapping, LDAPSource
|
||||
|
||||
|
||||
class LDAPSourceSerializer(ModelSerializer):
|
||||
"""LDAP Source Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = LDAPSource
|
||||
fields = SOURCE_SERIALIZER_FIELDS + [
|
||||
'server_uri',
|
||||
'bind_cn',
|
||||
'bind_password',
|
||||
'start_tls',
|
||||
'base_dn',
|
||||
'additional_user_dn',
|
||||
'additional_group_dn',
|
||||
'user_object_filter',
|
||||
'group_object_filter',
|
||||
'user_group_membership_field',
|
||||
'object_uniqueness_field',
|
||||
'sync_groups',
|
||||
'sync_parent_group',
|
||||
'property_mappings',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'bind_password': {'write_only': True}
|
||||
}
|
||||
|
||||
|
||||
class LDAPPropertyMappingSerializer(ModelSerializer):
|
||||
"""LDAP PropertyMapping Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = LDAPPropertyMapping
|
||||
fields = SOURCE_SERIALIZER_FIELDS + ['name', 'ldap_property', 'object_field']
|
||||
|
||||
|
||||
class LDAPSourceViewSet(ModelViewSet):
|
||||
"""LDAP Source Viewset"""
|
||||
|
||||
queryset = LDAPSource.objects.all()
|
||||
serializer_class = LDAPSourceSerializer
|
||||
|
||||
|
||||
class LDAPPropertyMappingViewSet(ModelViewSet):
|
||||
"""LDAP PropertyMapping Viewset"""
|
||||
|
||||
queryset = LDAPPropertyMapping.objects.all()
|
||||
serializer_class = LDAPPropertyMappingSerializer
|
23
passbook/sources/oauth/api.py
Normal file
23
passbook/sources/oauth/api.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""OAuth Source Serializer"""
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from passbook.admin.forms.source import SOURCE_SERIALIZER_FIELDS
|
||||
from passbook.sources.oauth.models import OAuthSource
|
||||
|
||||
|
||||
class OAuthSourceSerializer(ModelSerializer):
|
||||
"""OAuth Source Serializer"""
|
||||
|
||||
class Meta:
|
||||
model = OAuthSource
|
||||
fields = SOURCE_SERIALIZER_FIELDS + ['provider_type', 'request_token_url',
|
||||
'authorization_url', 'access_token_url',
|
||||
'profile_url', 'consumer_key', 'consumer_secret']
|
||||
|
||||
|
||||
class OAuthSourceViewSet(ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = OAuthSource.objects.all()
|
||||
serializer_class = OAuthSourceSerializer
|
Reference in New Issue
Block a user