Compare commits
	
		
			64 Commits
		
	
	
		
			version/0.
			...
			version/0.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 23146de2bf | |||
| e24f4fe3a8 | |||
| 8e6b69f96f | |||
| 979bea17ed | |||
| 30dba285d9 | |||
| 99fadf2e55 | |||
| b606e3d0cb | |||
| be642bc874 | |||
| 49a347b32f | |||
| 089b48aad1 | |||
| 2997cb83b1 | |||
| 08f0aca894 | |||
| 80ea7c40b7 | |||
| 019a0cb14d | |||
| 97290755e7 | |||
| 7f150c96b4 | |||
| 73558f30d1 | |||
| dfcfd87644 | |||
| 2c0f0a68a8 | |||
| 3d73aac3ab | |||
| e4fbcd3735 | |||
| 44c0eb37cf | |||
| adc3dcc2c4 | |||
| bac8227371 | |||
| 73d4d9dfe0 | |||
| afdac5f3f8 | |||
| dabce36667 | |||
| 3bd56ce522 | |||
| 540419d5c1 | |||
| 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.4-beta
 | 
			
		||||
tag = True
 | 
			
		||||
commit = True
 | 
			
		||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
 | 
			
		||||
@ -23,5 +23,3 @@ values =
 | 
			
		||||
 | 
			
		||||
[bumpversion:file:passbook/__init__.py]
 | 
			
		||||
 | 
			
		||||
[bumpversion:file:docker/nginx.conf]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -192,3 +192,7 @@ pip-selfcheck.json
 | 
			
		||||
/static/
 | 
			
		||||
local.env.yml
 | 
			
		||||
.vscode/
 | 
			
		||||
 | 
			
		||||
### Helm ###
 | 
			
		||||
# Chart dependencies
 | 
			
		||||
**/charts/*.tgz
 | 
			
		||||
 | 
			
		||||
@ -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.4-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.4-beta
 | 
			
		||||
  only:
 | 
			
		||||
    - tags
 | 
			
		||||
    - /^version/.*$/
 | 
			
		||||
@ -116,6 +116,18 @@ build-passbook-static:
 | 
			
		||||
  services:
 | 
			
		||||
    - postgres:latest
 | 
			
		||||
    - redis:latest
 | 
			
		||||
# build-passbook-gatekeeper:
 | 
			
		||||
#   stage: build
 | 
			
		||||
#   image:
 | 
			
		||||
#     name: gcr.io/kaniko-project/executor:debug
 | 
			
		||||
#     entrypoint: [""]
 | 
			
		||||
#   before_script:
 | 
			
		||||
#     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
 | 
			
		||||
#   script:
 | 
			
		||||
#     - /kaniko/executor --context $CI_PROJECT_DIR/gatekeeper --dockerfile $CI_PROJECT_DIR/gatekeeper/Dockerfile --destination docker.beryju.org/passbook/gatekeeper:latest --destination docker.beryju.org/passbook/gatekeeper:0.7.4-beta
 | 
			
		||||
#   only:
 | 
			
		||||
#     - tags
 | 
			
		||||
#     - /^version/.*$/
 | 
			
		||||
 | 
			
		||||
package-helm:
 | 
			
		||||
  image: debian:stretch-slim
 | 
			
		||||
@ -136,12 +148,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.4-beta
 | 
			
		||||
    - sentry-cli releases set-commits --auto passbook@0.7.4-beta
 | 
			
		||||
  only:
 | 
			
		||||
    - tags
 | 
			
		||||
    - /^version/.*$/
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								Pipfile
									
									
									
									
									
								
							@ -4,51 +4,56 @@ url = "https://pypi.org/simple"
 | 
			
		||||
verify_ssl = true
 | 
			
		||||
 | 
			
		||||
[packages]
 | 
			
		||||
boto3 = "*"
 | 
			
		||||
celery = "*"
 | 
			
		||||
cherrypy = "*"
 | 
			
		||||
defusedxml = "*"
 | 
			
		||||
django = "*"
 | 
			
		||||
kombu = "==4.5.0"
 | 
			
		||||
django-cors-middleware = "*"
 | 
			
		||||
django-filters = "*"
 | 
			
		||||
django-dbbackup = "*"
 | 
			
		||||
django-filter = "*"
 | 
			
		||||
django-guardian = "*"
 | 
			
		||||
django-ipware = "*"
 | 
			
		||||
django-model-utils = "*"
 | 
			
		||||
django-oauth-toolkit = "*"
 | 
			
		||||
django-oidc-provider = "*"
 | 
			
		||||
django-otp = "*"
 | 
			
		||||
django-prometheus = "*"
 | 
			
		||||
django-recaptcha = "*"
 | 
			
		||||
django-redis = "*"
 | 
			
		||||
django-rest-framework = "*"
 | 
			
		||||
django-storages = "*"
 | 
			
		||||
djangorestframework-guardian = "*"
 | 
			
		||||
drf-yasg = "*"
 | 
			
		||||
kombu = "==4.5.0"
 | 
			
		||||
ldap3 = "*"
 | 
			
		||||
lxml = "*"
 | 
			
		||||
markdown = "*"
 | 
			
		||||
oauthlib = "*"
 | 
			
		||||
packaging = "*"
 | 
			
		||||
psycopg2-binary = "*"
 | 
			
		||||
pycryptodome = "*"
 | 
			
		||||
pyuwsgi = "*"
 | 
			
		||||
pyyaml = "*"
 | 
			
		||||
qrcode = "*"
 | 
			
		||||
requests-oauthlib = "*"
 | 
			
		||||
sentry-sdk = "*"
 | 
			
		||||
service_identity = "*"
 | 
			
		||||
signxml = "*"
 | 
			
		||||
urllib3 = {extras = ["secure"],version = "*"}
 | 
			
		||||
structlog = "*"
 | 
			
		||||
pyuwsgi = "*"
 | 
			
		||||
swagger-spec-validator = "*"
 | 
			
		||||
urllib3 = {extras = ["secure"],version = "*"}
 | 
			
		||||
 | 
			
		||||
[requires]
 | 
			
		||||
python_version = "3.7"
 | 
			
		||||
 | 
			
		||||
[dev-packages]
 | 
			
		||||
coverage = "*"
 | 
			
		||||
isort = "*"
 | 
			
		||||
pylint = "==2.3.1"
 | 
			
		||||
pylint-django = "*"
 | 
			
		||||
prospector = "*"
 | 
			
		||||
django-debug-toolbar = "*"
 | 
			
		||||
bumpversion = "*"
 | 
			
		||||
unittest-xml-reporting = "*"
 | 
			
		||||
autopep8 = "*"
 | 
			
		||||
bandit = "*"
 | 
			
		||||
bumpversion = "*"
 | 
			
		||||
colorama = "*"
 | 
			
		||||
coverage = "*"
 | 
			
		||||
django-debug-toolbar = "*"
 | 
			
		||||
isort = "*"
 | 
			
		||||
prospector = "*"
 | 
			
		||||
pylint = "==2.3.1"
 | 
			
		||||
pylint-django = "*"
 | 
			
		||||
unittest-xml-reporting = "*"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										590
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										590
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
    "_meta": {
 | 
			
		||||
        "hash": {
 | 
			
		||||
            "sha256": "94b3d5140f0c31dac1fc77af75a0df30ae4fb0571bf6b7fcd722487c63dc1872"
 | 
			
		||||
            "sha256": "716683e8e7794821723dcb671c58b3af32c061c52410148e5b5b6c8fc503c935"
 | 
			
		||||
        },
 | 
			
		||||
        "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:228cea7e2b3be79e5393719641854d4000826d7a7baebede903a616b505b8e17",
 | 
			
		||||
                "sha256:ad6d50dd5726a12c6442c23aabec0c7e09ef610834d9fbda010bade6888d7677"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.10.13"
 | 
			
		||||
        },
 | 
			
		||||
        "botocore": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:33ee13a42ee1cc2391a3cd3ce12c84026db20cc76a5700d94fbe07a136d0c354",
 | 
			
		||||
                "sha256:d1c6f01486566521b59fd5d4f6ba0adf526ed0d1807a0c0ba6604e982d014f3d"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.13.13"
 | 
			
		||||
        },
 | 
			
		||||
        "celery": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9",
 | 
			
		||||
@ -61,36 +76,41 @@
 | 
			
		||||
        },
 | 
			
		||||
        "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:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42",
 | 
			
		||||
                "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04",
 | 
			
		||||
                "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5",
 | 
			
		||||
                "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54",
 | 
			
		||||
                "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba",
 | 
			
		||||
                "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57",
 | 
			
		||||
                "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396",
 | 
			
		||||
                "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12",
 | 
			
		||||
                "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97",
 | 
			
		||||
                "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43",
 | 
			
		||||
                "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db",
 | 
			
		||||
                "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3",
 | 
			
		||||
                "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b",
 | 
			
		||||
                "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579",
 | 
			
		||||
                "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346",
 | 
			
		||||
                "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159",
 | 
			
		||||
                "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652",
 | 
			
		||||
                "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e",
 | 
			
		||||
                "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a",
 | 
			
		||||
                "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506",
 | 
			
		||||
                "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f",
 | 
			
		||||
                "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d",
 | 
			
		||||
                "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c",
 | 
			
		||||
                "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20",
 | 
			
		||||
                "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858",
 | 
			
		||||
                "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc",
 | 
			
		||||
                "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a",
 | 
			
		||||
                "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3",
 | 
			
		||||
                "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e",
 | 
			
		||||
                "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410",
 | 
			
		||||
                "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25",
 | 
			
		||||
                "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b",
 | 
			
		||||
                "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.12.3"
 | 
			
		||||
            "version": "==1.13.2"
 | 
			
		||||
        },
 | 
			
		||||
        "chardet": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -99,21 +119,6 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.0.4"
 | 
			
		||||
        },
 | 
			
		||||
        "cheroot": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3ff64073efa35b39d5e107410f5c79664dc8c6c5990651e970740c80ab8878a8",
 | 
			
		||||
                "sha256:d523a1525258730026aa35b86c8c47c8d0e3892fb89f0f39157d4b32a50edf05"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==8.1.0"
 | 
			
		||||
        },
 | 
			
		||||
        "cherrypy": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:033368d25fcc6bca143e7efe9adbfd3a6d91cc0d90c37a649261935f116aafab",
 | 
			
		||||
                "sha256:683e687e7c7b1ba31ef86a113b1eafd0407269fed175bf488d3c839d37d1cc60"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==18.3.0"
 | 
			
		||||
        },
 | 
			
		||||
        "coreapi": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb",
 | 
			
		||||
@ -130,24 +135,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": [
 | 
			
		||||
@ -159,11 +169,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4025317ca01f75fc79250ff7262a06d8ba97cd4f82e93394b2a0a6a4a925caeb",
 | 
			
		||||
                "sha256:a8ca1033acac9f33995eb2209a6bf18a4681c3e5269a878e9a7e0b7384ed1ca3"
 | 
			
		||||
                "sha256:16040e1288c6c9f68c6da2fe75ebde83c0a158f6f5d54f4c5177b0c1478c5b86",
 | 
			
		||||
                "sha256:89c2007ca4fa5b351a51a279eccff298520783b713bf28efb89dfb81c80ea49b"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.2.6"
 | 
			
		||||
            "version": "==2.2.7"
 | 
			
		||||
        },
 | 
			
		||||
        "django-cors-middleware": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -173,12 +183,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 +237,19 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django-otp": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:79c8253be97246df86540d551dc705e8fe6ca76af8e8c77f78314cd1b513c2cf",
 | 
			
		||||
                "sha256:c5bf3916dca5d53cb377aa6dea40aa785c164013fbf750384137362dfa278cf5"
 | 
			
		||||
                "sha256:0009211222388d8ba4a4840b6de21ff24461fd4aad6c6c194926e3091ac65f06",
 | 
			
		||||
                "sha256:a9d39b35f7aa8eee82d6d9769d8004ec538e7d7c2f5a1c5e5525cda90d0e9b69"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.7.2"
 | 
			
		||||
            "version": "==0.7.3"
 | 
			
		||||
        },
 | 
			
		||||
        "django-prometheus": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:60f331788f9846891e9ea8d7ccd2928b1042e2e99c8d673f97e2b85f5bc20112",
 | 
			
		||||
                "sha256:bb2d4f8acd681fa5787df77e7482391017f0090c70473bccd2aa7cad327800ad"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.1.0"
 | 
			
		||||
        },
 | 
			
		||||
        "django-recaptcha": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -240,6 +274,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 +289,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 +333,13 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.8"
 | 
			
		||||
        },
 | 
			
		||||
        "importlib-metadata": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
 | 
			
		||||
                "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.23"
 | 
			
		||||
        },
 | 
			
		||||
        "inflection": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca"
 | 
			
		||||
@ -287,13 +352,6 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.1.0"
 | 
			
		||||
        },
 | 
			
		||||
        "jaraco.functools": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:35ba944f52b1a7beee8843a5aa6752d1d5b79893eeb7770ea98be6b637bf9345",
 | 
			
		||||
                "sha256:e9e377644cee5f6f9128b4dab1631fca74981236e95a255f80e4292bcd2b5284"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.0"
 | 
			
		||||
        },
 | 
			
		||||
        "jinja2": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f",
 | 
			
		||||
@ -301,6 +359,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 +394,7 @@
 | 
			
		||||
                "sha256:02ca7bf899da57084041bb0f6095333e4d239948ad3169443f454add9f4e9cb4",
 | 
			
		||||
                "sha256:096b82c5e0ea27ce9138bcbb205313343ee66a6e132f25c5ed67e2c8d960a1bc",
 | 
			
		||||
                "sha256:0a920ff98cf1aac310470c644bc23b326402d3ef667ddafecb024e1713d485f1",
 | 
			
		||||
                "sha256:1409b14bf83a7d729f92e2a7fbfe7ec929d4883ca071b06e95c539ceedb6497c",
 | 
			
		||||
                "sha256:17cae1730a782858a6e2758fd20dd0ef7567916c47757b694a06ffafdec20046",
 | 
			
		||||
                "sha256:17e3950add54c882e032527795c625929613adbd2ce5162b94667334458b5a36",
 | 
			
		||||
                "sha256:1f4f214337f6ee5825bf90a65d04d70aab05526c08191ab888cb5149501923c5",
 | 
			
		||||
@ -332,11 +405,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",
 | 
			
		||||
@ -345,14 +421,6 @@
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==4.4.1"
 | 
			
		||||
        },
 | 
			
		||||
        "markdown": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a",
 | 
			
		||||
                "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==3.1.1"
 | 
			
		||||
        },
 | 
			
		||||
        "markupsafe": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
 | 
			
		||||
@ -409,46 +477,47 @@
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==19.2"
 | 
			
		||||
        },
 | 
			
		||||
        "portend": {
 | 
			
		||||
        "prometheus-client": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:19dc27bfb3c72471bd30a235a4d5fbefef8a7e31cab367744b5d87a205e7bfd9",
 | 
			
		||||
                "sha256:d2dca12e585ce29fc357b31ce424a27c16e2d485029252bbf8ddcc9696207976"
 | 
			
		||||
                "sha256:71cd24a2b3eb335cb800c7159f423df1bd4dcd5171b234be15e3f31ec9f622da"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.5"
 | 
			
		||||
            "version": "==0.7.1"
 | 
			
		||||
        },
 | 
			
		||||
        "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": [
 | 
			
		||||
@ -472,70 +541,78 @@
 | 
			
		||||
        },
 | 
			
		||||
        "pycryptodome": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:023c294367d7189ae224fb61bc8d49a2347704087c1c78dbd5ab114dd5b97761",
 | 
			
		||||
                "sha256:0f29e1238ad3b6b6e2acd7ea1d8e8b382978a56503f2c48b67d5dc144d143cb0",
 | 
			
		||||
                "sha256:18f376698e3ddcb1d3b312512ca78c9eed132e68ac6d0bf2e72452dfe213e96f",
 | 
			
		||||
                "sha256:1de815b847982f909dc2e5e2ca641b85cde80d95cc7e6a359c03d4b42cd21568",
 | 
			
		||||
                "sha256:1ff619b8e4050799ca5ca0ffdf8eb0dbccba6997997866755f37e6aa7dde23fe",
 | 
			
		||||
                "sha256:233a04bb7bdd4b07e14d61d5166150942d872802daa4f049d49a453fe0659e94",
 | 
			
		||||
                "sha256:33c07e1e36ec84524b49f99f11804d5e4d2188c643e84d914cb1e0a277ed3c79",
 | 
			
		||||
                "sha256:3701822a085dbebf678bfbdfbd6ebd92ffa80d5a544c9979984bf16a67c9790b",
 | 
			
		||||
                "sha256:3f8e6851c0a45429f9b86c1597d3b831b0cff140b3e170a891fce55ef8dac2bb",
 | 
			
		||||
                "sha256:4f6cdddf1fe72e7f173e9734aa19b94cbd046b61a8559d650ff222e36021d5c1",
 | 
			
		||||
                "sha256:52d20b22c5b1fc952b4c686b99a6c55c3b0b0a673bec30570f156a72198f66ff",
 | 
			
		||||
                "sha256:5452b534fecf8bf57cf9106d00877f5f4ab7264e7a5e1f5ea8d15b04517d1255",
 | 
			
		||||
                "sha256:5a7a9a4a7f8f0990fa97fee71c7f7e0c412925c515cfc6d4996961e92c9be8e5",
 | 
			
		||||
                "sha256:600bf9dd5fbed0feee83950e2a8baacaa1f38b56c237fff270d31e47f8da9e52",
 | 
			
		||||
                "sha256:6840c9881e528224ebf72b3f73b3d11baf399e265106c9f4d9bae4f09615a93a",
 | 
			
		||||
                "sha256:71b041d43fe13004abc36ca720ac64ea489ee8a3407a25116481d0faf9d62494",
 | 
			
		||||
                "sha256:7252498b427c421e306473ed344e58235eedd95c15fec2e1b33d333aefa1ea10",
 | 
			
		||||
                "sha256:8d2135c941d38f241e0e62dbdfc1ca5d9240527e61316126797f50b6f3e49825",
 | 
			
		||||
                "sha256:a0962aea03933b99cf391c3e10dfef32f77915d5553464264cfbc6711f31d254",
 | 
			
		||||
                "sha256:a117047a220b3911d425affcd1cbc97a1af7ea7eb5d985d9964d42b4f0558489",
 | 
			
		||||
                "sha256:a35a5c588248ba00eb976a8554211e584a55de286783bc69b12bdd7954052b4a",
 | 
			
		||||
                "sha256:c1a4f3f651471b9bf60b0d98fa8a994b8a73ff8ab4edc691e23243c853aaff9f",
 | 
			
		||||
                "sha256:c419943306756ddd1a1997120bb073733bc223365909c68185106d5521cbc0ef",
 | 
			
		||||
                "sha256:c453ad968b67d66448543420ec39770c30bd16d986058255f058ab87c4f6cc1f",
 | 
			
		||||
                "sha256:d2d78644655629c7d1b9bf28e479d29facc0949d9ff095103ca9c2314b329ee0",
 | 
			
		||||
                "sha256:d7be60dc2126ee350ac7191549f5ab05c2dd76a5d5a3022249f395a401c6ea37",
 | 
			
		||||
                "sha256:dbeb08ad850056747aa7d5f33273b7ce0b9a77910604a1be7b7a6f2ef076213f",
 | 
			
		||||
                "sha256:f02382dc1bf91fb7123f2a3851fb1b526c871fa9359f387f2bcc847efc74ae52"
 | 
			
		||||
                "sha256:0aa49f3fa110f8dc090bad1671a768cc17d3d3bd01566641ffc0d10d0fec8d49",
 | 
			
		||||
                "sha256:0fafd3c4fb76c6992f34bf2d074f582f388e3b8062b8ba5d65b020634cc221e6",
 | 
			
		||||
                "sha256:17eb9bd5d30a71b0c8a832e3e9cd2b7723f99907c38dc5dd23e59e8c368a70e2",
 | 
			
		||||
                "sha256:2776255d5c748782f095ec422d42da2eadd8392ac9de7da23db4aed4231272bd",
 | 
			
		||||
                "sha256:3500826dc3b9a8fdb762bebe551106081a6bdecd4181a3d1bd0206e48bba8974",
 | 
			
		||||
                "sha256:3aa0d30326dcdef24c632d5c03b8e4d379c6ae0645082b27dd69ea816bb97ecb",
 | 
			
		||||
                "sha256:3c7769bdadcc4809508e71997008912cc6d94fd7b5b1f3ef121683ebcac71d81",
 | 
			
		||||
                "sha256:3e8c97a38dac6dafd180b4696a522b1581dd1a8e0ea60763458be547bac97361",
 | 
			
		||||
                "sha256:5aca5125a46e458b308b5571ce8fe36d2229f161aa7db27b3ecacded70c6aa8b",
 | 
			
		||||
                "sha256:62beb75f0688f406946312bfef8923d8ab23f5b8013acded931413625299d317",
 | 
			
		||||
                "sha256:7725643de3c884a9945a086670787dce637037f32c5c2df7fd602bd5967f3486",
 | 
			
		||||
                "sha256:872191a02a0c2a3b98dc75c62b32912b220a8ae5ff6ac9e39868f903f55dd6a4",
 | 
			
		||||
                "sha256:8c501e80960d12328d49e1d409daf426f29364a37c602f257c99509999654650",
 | 
			
		||||
                "sha256:9512638bfef8ffc94c62751965a4733c3792104dc84771ba54ce0f80f49134df",
 | 
			
		||||
                "sha256:962043051afa7a5ab071b0d8996dc00e564327a18566d3e574a39cb6e097b462",
 | 
			
		||||
                "sha256:9db72b18b30902a83fa57b0d7dae4ce24f85186695e3bea0d423f1ec7c5b3fbe",
 | 
			
		||||
                "sha256:9ffd4f0bfb5949dfa0e5cedef836364f18da0deb2fba04671607fb3b59b29112",
 | 
			
		||||
                "sha256:a26819f693cf5fc0a2373a3e4b91c38e359cad9f00020a885b667c77f28738d5",
 | 
			
		||||
                "sha256:a3efc575a53511c48361d933e12e07c2eb940db1afda0995285176c372ab7352",
 | 
			
		||||
                "sha256:ababd6685b9d94729a851a0615482156afdacbeaabeea60f67961db0e975b1af",
 | 
			
		||||
                "sha256:b0e9c8c270cd3f8c73b53139f0708f257189a00bbc898be6d3f03995e5f7edc2",
 | 
			
		||||
                "sha256:b74173b13c221ee96b608212b9adc2c459a73d3632f04490df42e4f07e7041e6",
 | 
			
		||||
                "sha256:bed297f75ba19cefe2d10beb4959f4f8cb62c2560a3998ad87479485098ee939",
 | 
			
		||||
                "sha256:c639f09e8ce8ad5af9884233f952ade4b73a11b7d41d3b9bb7d4e64d9e1df164",
 | 
			
		||||
                "sha256:c7bc308be67288af1cd44668d59e36356f0ce518337899079ddb0235bd55db79",
 | 
			
		||||
                "sha256:cca152dcebc318833ba70499190ce17ee81b525404e2a7548c77f52b439306a7",
 | 
			
		||||
                "sha256:d5261d22bc3a54db26f11dabcda14bbaab72080977e083d795b4b1d1b510c774",
 | 
			
		||||
                "sha256:d81111e3da7fc9eee825ba7d8a68b3c1464f41110ef98a7280e0c7fb82c91e73",
 | 
			
		||||
                "sha256:d95fafa899abb9f82e55ff43f423e100784312b43932514f2c05d41cbb20323e",
 | 
			
		||||
                "sha256:de411a64d4105d4424441833bd25943208e58c846abf981bba5bbeeba88a49c3",
 | 
			
		||||
                "sha256:e02c7b3d05b88ff1a236e49a252b2bf8444d3a1d04a056784af766c0909eba36",
 | 
			
		||||
                "sha256:fbafe9b01b717e0bfbc83cd740ff5bf5cdd3f208815be470ea203942b899bbdf"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==3.9.0"
 | 
			
		||||
            "version": "==3.9.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pycryptodomex": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:020928b2831b2047288c9143f41c6690eb669d60761c7ca8c5ca743a2c51517c",
 | 
			
		||||
                "sha256:0ce1950ba6544eca4d6fd7386e2502d4bd871fcbd5e5b977604f48ea37b29fc6",
 | 
			
		||||
                "sha256:0d5b1159a24a56fd3359b7b1aa1e4331c394033eababb2972bb923d6767968db",
 | 
			
		||||
                "sha256:11453e8628cdccbcb08e04405298d659c0c0458cf9bf23eaaa3c201f8d635389",
 | 
			
		||||
                "sha256:22e050089f60e70b97909fe62612ee9589f0be1c928c2aa637f2534eddf61632",
 | 
			
		||||
                "sha256:27317f1e8e496a2f208b1c40da425d5fe760b494f95c847bb7c3074c95a8edcb",
 | 
			
		||||
                "sha256:32e2fe1d0c5fada45b22b647f88367b210dfea40a5cc849b142b4e9fa497c488",
 | 
			
		||||
                "sha256:3a998b390a80fd0d22c7d9fbaf49a9a11772ef90495a8baecdea2e6d09929937",
 | 
			
		||||
                "sha256:46dda35fbed5426794ab64d483d6257dc43f52e78ba934563492df7cb89f7de6",
 | 
			
		||||
                "sha256:4846ca0f2363bdb934c556667b056331d4aabd48f20924b0c5583a49d764d3fc",
 | 
			
		||||
                "sha256:550f5e6f07b091f986023f871fa8a2bde9875ccae51d4bd07b31fa9855fe994f",
 | 
			
		||||
                "sha256:561905b459de41c3ad19912cdcd88c8e24295d01e97b7b2a63d4188c8e4e0dbc",
 | 
			
		||||
                "sha256:5745ca86a4e88a775b7cace28b947a86661d5cc09ecc1c8d97293a7d20c1bb79",
 | 
			
		||||
                "sha256:5c2a3bb28dde992f97d856937e973dda0462bf3acb7d0009308a81159a35323b",
 | 
			
		||||
                "sha256:73a8acc8ff7f09d482e481757d92a250f803e66e0f248019df90a69e61840180",
 | 
			
		||||
                "sha256:8601613ebc329b853e466f581ad1156638989926e0dcdf52952542a89883836c",
 | 
			
		||||
                "sha256:8b604f4fa1de456d6d19771b01c2823675a75a2c60e51a6b738f71fdfe865370",
 | 
			
		||||
                "sha256:96f8622cb8061f4aca95e52cc835659f024bc2e237ee6a9d01117873b7490b98",
 | 
			
		||||
                "sha256:a01c99532c5f7ab96274b5c9f3e135315b79b55ba5c8233fc4d029e0369e94df",
 | 
			
		||||
                "sha256:c63040e0313e27b62b0f4295f41adecf96cde7ff4d49f653b81b1958cb1180bf",
 | 
			
		||||
                "sha256:c812cb9f3af63da8eaa251e7e48f8b38c4e40974d2bdae2f0ca7a7a12549727a",
 | 
			
		||||
                "sha256:cb9e8ef672b7a961f90e0a497718e0f052f76324f216840a4ec30248e4d19f20",
 | 
			
		||||
                "sha256:ce8edda46374c344de87089f9887ad4dd317bb4a22f91f1844202eaf14b08de0",
 | 
			
		||||
                "sha256:de58de0d5f2fb9253707ee718e1378f2194fdd394cdbed1b6464ab44642f5217",
 | 
			
		||||
                "sha256:e0100f9b93d0119d846a33e6cb5001ee208519b81c6acf76da614b71de75885b",
 | 
			
		||||
                "sha256:e530b77bdff5c2bf3065e6a088e1602ad193b43e285bac196d4b8820308ec6bb",
 | 
			
		||||
                "sha256:f048069aa7b530f1c5e84d55c2b28ca7a7272bb3b8d28829d454a94bec6529a8",
 | 
			
		||||
                "sha256:f6a9271c842e93c349b6007676a62d03dca712c9f4dff66c3270d50504ca9014"
 | 
			
		||||
                "sha256:0713fc29cddb14f977887ccf3199d1a00d0b040e8c35785df20d107ad59efabc",
 | 
			
		||||
                "sha256:110651378be063d5e0e653d107a14b511bd45c355968a32270f5b1bf8c093056",
 | 
			
		||||
                "sha256:158428c0f337984cb3611484d9f61faea973aec624c8f88c5809ab88adab0884",
 | 
			
		||||
                "sha256:17625d9f9442d3567b2532795c9232ed80cc1d6c91064ad48c802f3bff2b937d",
 | 
			
		||||
                "sha256:179125d0b2bcbf5cf9ddf9fb74fe13e30d19fb1c2691cac43b8b37d74df9ddf6",
 | 
			
		||||
                "sha256:227e660ee3835284fc6195163c467f8d21a1de51d0aa85d32157a1fc4bf16b9a",
 | 
			
		||||
                "sha256:2f651173c4bb8de6a96493e5cc03b2838eedf4bb1cbfbe2b354e40a2f2f245fc",
 | 
			
		||||
                "sha256:33b0e5c9ca02c099ec537138e8ffee1e4d054e49d69258062d89ddbd9f660000",
 | 
			
		||||
                "sha256:42bead6e7dbca9328a6601ff41d25554606847d92b0fd198ca3f6c971c662c07",
 | 
			
		||||
                "sha256:478cce6245e8ff8cda8f733ef1a1161ee6bf5aaa45312e1ace6c7b80fbc1e01f",
 | 
			
		||||
                "sha256:4ce38cb16b6f41c4b579e3e9a9d66c36ba24192cc0518ce09313c25ae44d2d74",
 | 
			
		||||
                "sha256:4e0bc594c61bd1db86c0060a5eb351c22a6c4c154315a52af1c8cd24c4e6a8a3",
 | 
			
		||||
                "sha256:4e1e616d12f79f256109de14aebcee1bf7e0a78d00b3de6c9a0cf2eb2a80785c",
 | 
			
		||||
                "sha256:5e4b459ccd6bfe55cc6b030b8983040bc8956f5757b621ae32dd0a26b0f85a91",
 | 
			
		||||
                "sha256:61a586b0cb85bc8c60af4ddcae24928a3476c944cb37eb7b9066965bc1d4b4d8",
 | 
			
		||||
                "sha256:643bea8898e875e54177c546f2ac704317937230379a9d295ece844c79e00cdb",
 | 
			
		||||
                "sha256:7403d7addaaa4649777ce487832ef8421222960a10d7a95b0f2c9efd217a93e6",
 | 
			
		||||
                "sha256:7f36378a699f201aea3e431a3c217c16e63abbe84ddb8d9bd0af9b28e3f826aa",
 | 
			
		||||
                "sha256:9146a6cf9eeb4683cfffabc7093fd1063076185d790680596f7a2dfb40f6b4b9",
 | 
			
		||||
                "sha256:9829d8aa2fb52646eda9041b785e9c6825fc1f1054f2254046fb7628800acb8e",
 | 
			
		||||
                "sha256:aa18ad3da8da74cbd119a6c5460079c7357ba8775b2edbc5a78722fc1e52f881",
 | 
			
		||||
                "sha256:ad39a8d3be6c5aad42b1ef839c49a50185618b26d5f1b555b1edd4d9d700e3b9",
 | 
			
		||||
                "sha256:b10bb3c640d7666993d5b0aec0e5334131386eddbd200aabcc123fe07c2b8928",
 | 
			
		||||
                "sha256:b17b2f5f65dffdeddf06bb82eb73a6aa55766322c3c45bc5032f9e3259adfdb0",
 | 
			
		||||
                "sha256:ba5bce9e1fc21160c27015a705e80f49901f1c42aa8bf96ed1d650ce4b5311bd",
 | 
			
		||||
                "sha256:c2b867277ef5a996b2198bec149abaeaeddbe57a77a4f6840882be382af72297",
 | 
			
		||||
                "sha256:c43d5d7516b0dc8436aef6bf9ebb9fbeaebcbbc4cb1b6a23be4a5f843c2614e3",
 | 
			
		||||
                "sha256:ce65a7dc9162a6e676f336e45f6602297981afa82f8e7ccc690667316c6b449b",
 | 
			
		||||
                "sha256:d9a38c3a85dd3dc6cae43eac94b73485fd7e5a1daf74bb510d7220a8b18482d2",
 | 
			
		||||
                "sha256:de39d7c456147755e5610177bd50cb7c89f74477d608b5ac055fed4e7c4c35c1",
 | 
			
		||||
                "sha256:ea368b7b4f36c5524d7b47aa583db604085958b92ff6580075230c8d7c88cdbe",
 | 
			
		||||
                "sha256:ff75fa26b7f8e1eaeba9edfc50b1d21bca913e743ce993a189b07bf483bedda0"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.9.0"
 | 
			
		||||
            "version": "==3.9.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pyjwkest": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -552,10 +629,24 @@
 | 
			
		||||
        },
 | 
			
		||||
        "pyparsing": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80",
 | 
			
		||||
                "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"
 | 
			
		||||
                "sha256:4acadc9a2b96c19fe00932a38ca63e601180c39a189a696abce1eaab641447e1",
 | 
			
		||||
                "sha256:61b5ed888beab19ddccab3478910e2076a6b5a0295dffc43021890e136edf764"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.4.2"
 | 
			
		||||
            "version": "==2.4.4"
 | 
			
		||||
        },
 | 
			
		||||
        "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": [
 | 
			
		||||
@ -579,6 +670,7 @@
 | 
			
		||||
                "sha256:861c94442b28cd64af033e88e0f63c66dbd5609f67952dc18694098b47a43f3a",
 | 
			
		||||
                "sha256:957bc6316ffc8463795d56d9953d58e7f32aa5aad1c5ac80bc45c69f3299961e",
 | 
			
		||||
                "sha256:9760c3f56fb5f15852d163429096600906478e9ed2c189a52f2bb21d8a2a986c",
 | 
			
		||||
                "sha256:9fdfb98a2992de01e8efad2aeed22c825e36db628b144b2d6b93d81fb549f811",
 | 
			
		||||
                "sha256:a4b24703ea818196d0be1dc64b3b57b79c67e8dee0cfa207a4216220912035a7",
 | 
			
		||||
                "sha256:ad7f4968c1ddbf139a306d9b075360d959cc554d994ba5e1f512af9a40e62357",
 | 
			
		||||
                "sha256:b1127d34b90f74faf1707718c57a4193ac028b9f4aec0238638983132297d456",
 | 
			
		||||
@ -589,6 +681,7 @@
 | 
			
		||||
                "sha256:ce777ebdf49ce736fc04abf555b5c41ab3f130127543a689dcf8d4871cd18fe4",
 | 
			
		||||
                "sha256:d8b4bf930b6a19bc9ee982b9163d948c87501ad91b71516924e8ed25fe85d2ee",
 | 
			
		||||
                "sha256:e2a420f2c4d35f3ec0b7e752a80d7bd385e2c5a64f67c05f2d2d74230e3114b6",
 | 
			
		||||
                "sha256:ef5eb630f541af6b69378d58594be90a0922fa6d6a50a9248c25b9502585f6bf",
 | 
			
		||||
                "sha256:fed899ce96f4f2b4d1b9f338dd145a4040ee1d8a5152213af0dd8d4a4d36e9fe"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
@ -623,10 +716,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "redis": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:98a22fb750c9b9bb46e75e945dc3f61d0ab30d06117cbb21ff9cd1d315fedd3b",
 | 
			
		||||
                "sha256:c504251769031b0dd7dd5cf786050a6050197c6de0d37778c80c08cb04ae8275"
 | 
			
		||||
                "sha256:3613daad9ce5951e426f460deddd5caf469e08a3af633e9578fc77d362becf62",
 | 
			
		||||
                "sha256:8d0fc278d3f5e1249967cba2eb4a5632d19e45ce5c09442b8422d15ee2c22cc2"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.3.8"
 | 
			
		||||
            "version": "==3.3.11"
 | 
			
		||||
        },
 | 
			
		||||
        "requests": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -637,11 +730,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "requests-oauthlib": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:bd6533330e8748e94bf0b214775fed487d309b8b8fe823dc45641ebcd9a32f57",
 | 
			
		||||
                "sha256:d3ed0c8f2e3bbc6b344fa63d6f933745ab394469da38db16bdddb461c7e25140"
 | 
			
		||||
                "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
 | 
			
		||||
                "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.2.0"
 | 
			
		||||
            "version": "==1.3.0"
 | 
			
		||||
        },
 | 
			
		||||
        "ruamel.yaml": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -674,13 +767,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:09e1e8f00f22ea580348f83bbbd880adf40b29f1dec494a8e4b33e22f77184fb",
 | 
			
		||||
                "sha256:ff1fa7fb85703ae9414c8b427ee73f8363232767c9cd19158f08f6e4f0b58fc7"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.12.3"
 | 
			
		||||
            "version": "==0.13.2"
 | 
			
		||||
        },
 | 
			
		||||
        "service-identity": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -700,10 +800,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "six": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
 | 
			
		||||
                "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
 | 
			
		||||
                "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
 | 
			
		||||
                "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.12.0"
 | 
			
		||||
            "version": "==1.13.0"
 | 
			
		||||
        },
 | 
			
		||||
        "sqlparse": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -714,18 +814,19 @@
 | 
			
		||||
        },
 | 
			
		||||
        "structlog": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:5feae03167620824d3ae3e8915ea8589fc28d1ad6f3edf3cc90ed7c7cb33fab5",
 | 
			
		||||
                "sha256:db441b81c65b0f104a7ce5d86c5432be099956b98b8a2c8be0b3fb3a7a0b1536"
 | 
			
		||||
                "sha256:4287058cf4ce1a59bc5dea290d6386d37f29a37529c9a51cdf7387e51710152b",
 | 
			
		||||
                "sha256:6640e6690fc31d5949bc614c1a630464d3aaa625284aeb7c6e486c3010d73e12"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==19.1.0"
 | 
			
		||||
            "version": "==19.2.0"
 | 
			
		||||
        },
 | 
			
		||||
        "tempora": {
 | 
			
		||||
        "swagger-spec-validator": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:cb60b1d2b1664104e307f8e5269d7f4acdb077c82e35cd57246ae14a3427d2d6",
 | 
			
		||||
                "sha256:d28a03d2f64ee81aec6e6bff374127ef306fe00c1b7e27c7ff1618344221a699"
 | 
			
		||||
                "sha256:57e29feb3aa921a9fb98bd70af148746b27c77d3207266f5571cebcce211e685",
 | 
			
		||||
                "sha256:62ef22eca3f429d93fddda5d793d2a1a9057d3732e7a14606e641805326ae4a6"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.14.1"
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.4.3"
 | 
			
		||||
        },
 | 
			
		||||
        "uritemplate": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -744,6 +845,7 @@
 | 
			
		||||
                "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "markers": null,
 | 
			
		||||
            "version": "==1.25.6"
 | 
			
		||||
        },
 | 
			
		||||
        "vine": {
 | 
			
		||||
@ -753,12 +855,12 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.3.0"
 | 
			
		||||
        },
 | 
			
		||||
        "zc.lockfile": {
 | 
			
		||||
        "zipp": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b",
 | 
			
		||||
                "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f"
 | 
			
		||||
                "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
 | 
			
		||||
                "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.0"
 | 
			
		||||
            "version": "==0.6.0"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "develop": {
 | 
			
		||||
@ -840,11 +942,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4025317ca01f75fc79250ff7262a06d8ba97cd4f82e93394b2a0a6a4a925caeb",
 | 
			
		||||
                "sha256:a8ca1033acac9f33995eb2209a6bf18a4681c3e5269a878e9a7e0b7384ed1ca3"
 | 
			
		||||
                "sha256:16040e1288c6c9f68c6da2fe75ebde83c0a158f6f5d54f4c5177b0c1478c5b86",
 | 
			
		||||
                "sha256:89c2007ca4fa5b351a51a279eccff298520783b713bf28efb89dfb81c80ea49b"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.2.6"
 | 
			
		||||
            "version": "==2.2.7"
 | 
			
		||||
        },
 | 
			
		||||
        "django-debug-toolbar": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -869,10 +971,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "gitpython": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:631263cc670aa56ce3d3c414cf0fe2e840f2e913514b138ea28d88a477bbcd21",
 | 
			
		||||
                "sha256:6e97b9f0954807f30c2dd8e3165731ed6c477a1b365f194b69d81d7940a08332"
 | 
			
		||||
                "sha256:3237caca1139d0a7aa072f6735f5fd2520de52195e0fa1d8b83a9b212a2498b2",
 | 
			
		||||
                "sha256:a7d6bef0775f66ba47f25911d285bcd692ce9053837ff48a120c2b8cf3a71389"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.0.3"
 | 
			
		||||
            "version": "==3.0.4"
 | 
			
		||||
        },
 | 
			
		||||
        "isort": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -884,26 +986,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": [
 | 
			
		||||
@ -1029,10 +1134,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "six": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
 | 
			
		||||
                "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
 | 
			
		||||
                "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
 | 
			
		||||
                "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.12.0"
 | 
			
		||||
            "version": "==1.13.0"
 | 
			
		||||
        },
 | 
			
		||||
        "smmap2": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1064,20 +1169,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'",
 | 
			
		||||
@ -1085,11 +1195,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "unittest-xml-reporting": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:140982e4b58e4052d9ecb775525b246a96bfc1fc26097806e05ea06e9166dd6c",
 | 
			
		||||
                "sha256:d1fbc7a1b6c6680ccfe75b5e9701e5431c646970de049e687b4bb35ba4325d72"
 | 
			
		||||
                "sha256:358bbdaf24a26d904cc1c26ef3078bca7fc81541e0a54c8961693cc96a6f35e0",
 | 
			
		||||
                "sha256:9d28ddf6524cf0ff9293f61bd12e792de298f8561a5c945acea63fb437789e0e"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.5.1"
 | 
			
		||||
            "version": "==2.5.2"
 | 
			
		||||
        },
 | 
			
		||||
        "wrapt": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,8 @@
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
export PASSBOOK_DOMAIN=domain.tld
 | 
			
		||||
# Optionally enable Error-reporting
 | 
			
		||||
# export PASSBOOK_ERROR_REPORTING=true
 | 
			
		||||
docker-compose pull
 | 
			
		||||
docker-compose up -d
 | 
			
		||||
docker-compose exec server ./manage.py migrate
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -21,8 +21,6 @@ services:
 | 
			
		||||
    labels:
 | 
			
		||||
      - traefik.enable=false
 | 
			
		||||
  server:
 | 
			
		||||
    build:
 | 
			
		||||
      context: .
 | 
			
		||||
    image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest}
 | 
			
		||||
    command:
 | 
			
		||||
      - uwsgi
 | 
			
		||||
@ -30,6 +28,7 @@ services:
 | 
			
		||||
    environment:
 | 
			
		||||
      - PASSBOOK_DOMAIN=${PASSBOOK_DOMAIN}
 | 
			
		||||
      - PASSBOOK_REDIS__HOST=redis
 | 
			
		||||
      - PASSBOOK_ERROR_REPORTING=${PASSBOOK_ERROR_REPORTING:-false}
 | 
			
		||||
      - PASSBOOK_POSTGRESQL__HOST=postgresql
 | 
			
		||||
      - PASSBOOK_POSTGRESQL__PASSWORD=${PG_PASS:-thisisnotagoodpassword}
 | 
			
		||||
    ports:
 | 
			
		||||
@ -57,18 +56,16 @@ services:
 | 
			
		||||
    environment:
 | 
			
		||||
      - PASSBOOK_DOMAIN=${PASSBOOK_DOMAIN}
 | 
			
		||||
      - PASSBOOK_REDIS__HOST=redis
 | 
			
		||||
      - PASSBOOK_ERROR_REPORTING=${PASSBOOK_ERROR_REPORTING:-false}
 | 
			
		||||
      - PASSBOOK_POSTGRESQL__HOST=postgresql
 | 
			
		||||
      - PASSBOOK_POSTGRESQL__PASSWORD=${PG_PASS:-thisisnotagoodpassword}
 | 
			
		||||
  static:
 | 
			
		||||
    build:
 | 
			
		||||
      context: .
 | 
			
		||||
      dockerfile: static.Dockerfile
 | 
			
		||||
    image: docker.beryju.org/passbook/static:latest
 | 
			
		||||
    networks:
 | 
			
		||||
      - internal
 | 
			
		||||
    labels:
 | 
			
		||||
      - traefik.frontend.rule=PathPrefix:/static, /robots.txt
 | 
			
		||||
      - traefik.port=80
 | 
			
		||||
      - traefik.port=8080
 | 
			
		||||
      - traefik.docker.network=internal
 | 
			
		||||
  traefik:
 | 
			
		||||
    image: traefik:1.7
 | 
			
		||||
 | 
			
		||||
@ -1,66 +0,0 @@
 | 
			
		||||
user  nginx;
 | 
			
		||||
worker_processes  1;
 | 
			
		||||
 | 
			
		||||
error_log  stderr warn;
 | 
			
		||||
pid        /var/run/nginx.pid;
 | 
			
		||||
 | 
			
		||||
events {
 | 
			
		||||
    worker_connections  1024;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
http {
 | 
			
		||||
    include       /etc/nginx/mime.types;
 | 
			
		||||
    default_type  application/octet-stream;
 | 
			
		||||
 | 
			
		||||
    log_format json_combined escape=json
 | 
			
		||||
        '{'
 | 
			
		||||
            '"time_local":"$time_local",'
 | 
			
		||||
            '"remote_addr":"$remote_addr",'
 | 
			
		||||
            '"remote_user":"$remote_user",'
 | 
			
		||||
            '"request":"$request",'
 | 
			
		||||
            '"status": "$status",'
 | 
			
		||||
            '"body_bytes_sent":"$body_bytes_sent",'
 | 
			
		||||
            '"request_time":"$request_time",'
 | 
			
		||||
            '"http_referrer":"$http_referer",'
 | 
			
		||||
            '"http_user_agent":"$http_user_agent"'
 | 
			
		||||
        '}';
 | 
			
		||||
 | 
			
		||||
    access_log /dev/stdout json_combined;
 | 
			
		||||
 | 
			
		||||
    sendfile        on;
 | 
			
		||||
    tcp_nopush     on;
 | 
			
		||||
 | 
			
		||||
    keepalive_timeout  65;
 | 
			
		||||
 | 
			
		||||
    server {
 | 
			
		||||
 | 
			
		||||
        server_name _;
 | 
			
		||||
 | 
			
		||||
        gzip on;
 | 
			
		||||
        gzip_types application/javascript image/* text/css;
 | 
			
		||||
        gunzip on;
 | 
			
		||||
        add_header X-passbook-Version 0.6.8-beta;
 | 
			
		||||
        add_header Vary X-passbook-Version;
 | 
			
		||||
        root /data/;
 | 
			
		||||
 | 
			
		||||
        location /_/healthz {
 | 
			
		||||
            return 204;
 | 
			
		||||
        }
 | 
			
		||||
        location ~* \.(jpg|jpeg|png|gif|ico)$ {
 | 
			
		||||
            expires 30d;
 | 
			
		||||
        }
 | 
			
		||||
        location ~* \.(css|js)$ {
 | 
			
		||||
            expires 7d;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    server {
 | 
			
		||||
 | 
			
		||||
        listen 8080;
 | 
			
		||||
 | 
			
		||||
        location = /stub_status {
 | 
			
		||||
            stub_status;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								gatekeeper/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								gatekeeper/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
FROM quay.io/pusher/oauth2_proxy
 | 
			
		||||
 | 
			
		||||
COPY templates /templates
 | 
			
		||||
 | 
			
		||||
ENV OAUTH2_PROXY_EMAIL_DOMAINS=*
 | 
			
		||||
ENV OAUTH2_PROXY_PROVIDER=oidc
 | 
			
		||||
ENV OAUTH2_PROXY_CUSTOM_TEMPLATES_DIR=/templates
 | 
			
		||||
ENV OAUTH2_PROXY_HTTP_ADDRESS=:4180
 | 
			
		||||
							
								
								
									
										18
									
								
								gatekeeper/templates/error.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								gatekeeper/templates/error.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
{{define "error.html"}}
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en" charset="utf-8">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <title>{{.Title}}</title>
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <h2>{{.Title}}</h2>
 | 
			
		||||
    <p>{{.Message}}</p>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <p><a href="{{.ProxyPrefix}}/sign_in">Sign In</a></p>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
{{end}}
 | 
			
		||||
							
								
								
									
										119
									
								
								gatekeeper/templates/sign_in.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								gatekeeper/templates/sign_in.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,119 @@
 | 
			
		||||
{{define "sign_in.html"}}
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en" charset="utf-8">
 | 
			
		||||
<head>
 | 
			
		||||
    <title>Sign In with passbook</title>
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
 | 
			
		||||
    <style>
 | 
			
		||||
        body {
 | 
			
		||||
            font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
 | 
			
		||||
            font-size: 14px;
 | 
			
		||||
            line-height: 1.42857143;
 | 
			
		||||
            color: #333;
 | 
			
		||||
            background: #f0f0f0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .signin {
 | 
			
		||||
            display: block;
 | 
			
		||||
            margin: 20px auto;
 | 
			
		||||
            max-width: 400px;
 | 
			
		||||
            background: #fff;
 | 
			
		||||
            border: 1px solid #ccc;
 | 
			
		||||
            border-radius: 10px;
 | 
			
		||||
            padding: 20px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .center {
 | 
			
		||||
            text-align: center;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .btn {
 | 
			
		||||
            color: #fff;
 | 
			
		||||
            background-color: #428bca;
 | 
			
		||||
            border: 1px solid #357ebd;
 | 
			
		||||
            -webkit-border-radius: 4;
 | 
			
		||||
            -moz-border-radius: 4;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            font-size: 14px;
 | 
			
		||||
            padding: 6px 12px;
 | 
			
		||||
            text-decoration: none;
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .btn:hover {
 | 
			
		||||
            background-color: #3071a9;
 | 
			
		||||
            border-color: #285e8e;
 | 
			
		||||
            text-decoration: none;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        label {
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            max-width: 100%;
 | 
			
		||||
            margin-bottom: 5px;
 | 
			
		||||
            font-weight: 700;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        input {
 | 
			
		||||
            display: block;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 34px;
 | 
			
		||||
            padding: 6px 12px;
 | 
			
		||||
            font-size: 14px;
 | 
			
		||||
            line-height: 1.42857143;
 | 
			
		||||
            color: #555;
 | 
			
		||||
            background-color: #fff;
 | 
			
		||||
            background-image: none;
 | 
			
		||||
            border: 1px solid #ccc;
 | 
			
		||||
            border-radius: 4px;
 | 
			
		||||
            -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
 | 
			
		||||
            box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
 | 
			
		||||
            -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
 | 
			
		||||
            -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
 | 
			
		||||
            transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
 | 
			
		||||
            margin: 0;
 | 
			
		||||
            box-sizing: border-box;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        footer {
 | 
			
		||||
            display: block;
 | 
			
		||||
            font-size: 10px;
 | 
			
		||||
            color: #aaa;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            margin-bottom: 10px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        footer a {
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            height: 25px;
 | 
			
		||||
            line-height: 25px;
 | 
			
		||||
            color: #aaa;
 | 
			
		||||
            text-decoration: underline;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        footer a:hover {
 | 
			
		||||
            color: #aaa;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <div class="signin center">
 | 
			
		||||
        <form method="GET" action="{{.ProxyPrefix}}/start">
 | 
			
		||||
            <input type="hidden" name="rd" value="{{.Redirect}}">
 | 
			
		||||
            <button type="submit" class="btn">Sign in with passbook</button><br />
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
    <script>
 | 
			
		||||
        if (window.location.hash) {
 | 
			
		||||
            (function () {
 | 
			
		||||
                var inputs = document.getElementsByName('rd');
 | 
			
		||||
                for (var i = 0; i < inputs.length; i++) {
 | 
			
		||||
                    inputs[i].value += window.location.hash;
 | 
			
		||||
                }
 | 
			
		||||
            })();
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
{{end}}
 | 
			
		||||
							
								
								
									
										10
									
								
								hack/prometheus/grafana.helm.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								hack/prometheus/grafana.helm.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
ingress:
 | 
			
		||||
  enabled: true
 | 
			
		||||
  hosts:
 | 
			
		||||
    - some.address.tld
 | 
			
		||||
 | 
			
		||||
grafana.ini:
 | 
			
		||||
  auth.anonymous:
 | 
			
		||||
    enabled: true
 | 
			
		||||
    org_name: Main Org.
 | 
			
		||||
    org_role: Viewer
 | 
			
		||||
							
								
								
									
										63
									
								
								hack/prometheus/instance.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								hack/prometheus/instance.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
---
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
kind: ServiceAccount
 | 
			
		||||
metadata:
 | 
			
		||||
    name: prometheus
 | 
			
		||||
---
 | 
			
		||||
apiVersion: rbac.authorization.k8s.io/v1beta1
 | 
			
		||||
kind: ClusterRole
 | 
			
		||||
metadata:
 | 
			
		||||
    name: prometheus
 | 
			
		||||
rules:
 | 
			
		||||
    - apiGroups: [""]
 | 
			
		||||
      resources:
 | 
			
		||||
          - nodes
 | 
			
		||||
          - services
 | 
			
		||||
          - endpoints
 | 
			
		||||
          - pods
 | 
			
		||||
      verbs: ["get", "list", "watch"]
 | 
			
		||||
    - apiGroups: [""]
 | 
			
		||||
      resources:
 | 
			
		||||
          - configmaps
 | 
			
		||||
      verbs: ["get"]
 | 
			
		||||
    - nonResourceURLs: ["/metrics"]
 | 
			
		||||
      verbs: ["get"]
 | 
			
		||||
---
 | 
			
		||||
apiVersion: rbac.authorization.k8s.io/v1beta1
 | 
			
		||||
kind: ClusterRoleBinding
 | 
			
		||||
metadata:
 | 
			
		||||
    name: prometheus
 | 
			
		||||
roleRef:
 | 
			
		||||
    apiGroup: rbac.authorization.k8s.io
 | 
			
		||||
    kind: ClusterRole
 | 
			
		||||
    name: prometheus
 | 
			
		||||
subjects:
 | 
			
		||||
    - kind: ServiceAccount
 | 
			
		||||
      name: prometheus
 | 
			
		||||
      namespace: prod-passbook-ng
 | 
			
		||||
---
 | 
			
		||||
apiVersion: monitoring.coreos.com/v1
 | 
			
		||||
kind: Prometheus
 | 
			
		||||
metadata:
 | 
			
		||||
    name: prometheus
 | 
			
		||||
spec:
 | 
			
		||||
    serviceAccountName: prometheus
 | 
			
		||||
    serviceMonitorSelector:
 | 
			
		||||
        matchLabels:
 | 
			
		||||
            app.kubernetes.io/name: passbook
 | 
			
		||||
    enableAdminAPI: false
 | 
			
		||||
    ruleSelector:
 | 
			
		||||
        matchLabels:
 | 
			
		||||
            app.kubernetes.io/name: passbook
 | 
			
		||||
    storage:
 | 
			
		||||
        volumeClaimTemplate:
 | 
			
		||||
            metadata:
 | 
			
		||||
                labels:
 | 
			
		||||
                    prometheus: k8s
 | 
			
		||||
                name: prometheus-storage
 | 
			
		||||
            spec:
 | 
			
		||||
                accessModes:
 | 
			
		||||
                    - ReadWriteOnce
 | 
			
		||||
                resources:
 | 
			
		||||
                    requests:
 | 
			
		||||
                        storage: 15Gi
 | 
			
		||||
							
								
								
									
										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,9 +1,9 @@
 | 
			
		||||
dependencies:
 | 
			
		||||
- name: postgresql
 | 
			
		||||
  repository: https://kubernetes-charts.storage.googleapis.com/
 | 
			
		||||
  version: 6.3.10
 | 
			
		||||
  version: 6.5.8
 | 
			
		||||
- name: redis
 | 
			
		||||
  repository: https://kubernetes-charts.storage.googleapis.com/
 | 
			
		||||
  version: 9.2.1
 | 
			
		||||
digest: sha256:bdde250e1401dccdd5161e39c807f9e88b05e3e8e72e74df767a1bbb5fc39a4a
 | 
			
		||||
generated: "2019-10-01T10:46:06.542706+02:00"
 | 
			
		||||
  version: 9.5.1
 | 
			
		||||
digest: sha256:f18b5dc8d0be13d584407405c60d10b6b84d25f7fa8aaa3dd0e5385c38f5c516
 | 
			
		||||
generated: "2019-11-07T10:23:07.259176+01:00"
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
appVersion: "0.6.8-beta"
 | 
			
		||||
appVersion: "0.7.4-beta"
 | 
			
		||||
description: A Helm chart for passbook.
 | 
			
		||||
name: passbook
 | 
			
		||||
version: "0.6.8-beta"
 | 
			
		||||
version: "0.7.4-beta"
 | 
			
		||||
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png
 | 
			
		||||
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@ -1,9 +1,9 @@
 | 
			
		||||
dependencies:
 | 
			
		||||
- name: postgresql
 | 
			
		||||
  repository: https://kubernetes-charts.storage.googleapis.com/
 | 
			
		||||
  version: 4.2.2
 | 
			
		||||
  version: 6.5.8
 | 
			
		||||
- name: redis
 | 
			
		||||
  repository: https://kubernetes-charts.storage.googleapis.com/
 | 
			
		||||
  version: 9.2.1
 | 
			
		||||
digest: sha256:8782e974a1094eaeecf1d68f093ca4fb84977217b2bd38b09790a05ec289aec2
 | 
			
		||||
generated: "2019-10-02T21:03:25.90491153Z"
 | 
			
		||||
  version: 9.5.1
 | 
			
		||||
digest: sha256:476834fb82f66bc7242c4a5e7343d0a859d8307cb301256beb0eb749983014e4
 | 
			
		||||
generated: "2019-11-07T10:21:30.902415+01:00"
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
dependencies:
 | 
			
		||||
- name: postgresql
 | 
			
		||||
  version: 4.2.2
 | 
			
		||||
  version: 6.5.8
 | 
			
		||||
  repository: https://kubernetes-charts.storage.googleapis.com/
 | 
			
		||||
- name: redis
 | 
			
		||||
  version: 9.2.1
 | 
			
		||||
  version: 9.5.1
 | 
			
		||||
  repository: https://kubernetes-charts.storage.googleapis.com/
 | 
			
		||||
 | 
			
		||||
@ -12,5 +12,5 @@ data:
 | 
			
		||||
      host: "{{ .Release.Name }}-redis-master"
 | 
			
		||||
      cache_db: 0
 | 
			
		||||
      message_queue_db: 1
 | 
			
		||||
    error_report_enabled: {{ .Values.config.error_reporting }}
 | 
			
		||||
    domain: ".{{ .Values.ingress.hosts[0] }}"
 | 
			
		||||
    error_reporting: {{ .Values.config.error_reporting }}
 | 
			
		||||
    domain: ".{{ index .Values.ingress.hosts 0 }}"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										121
									
								
								helm/passbook/templates/prom-rules.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								helm/passbook/templates/prom-rules.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,121 @@
 | 
			
		||||
{{- if .Values.monitoring.enabled -}}
 | 
			
		||||
---
 | 
			
		||||
apiVersion: monitoring.coreos.com/v1
 | 
			
		||||
kind: PrometheusRule
 | 
			
		||||
metadata:
 | 
			
		||||
  name: {{ include "passbook.fullname" . }}-static-rules
 | 
			
		||||
  labels:
 | 
			
		||||
    app.kubernetes.io/name: {{ include "passbook.name" . }}
 | 
			
		||||
    helm.sh/chart: {{ include "passbook.chart" . }}
 | 
			
		||||
    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
			
		||||
spec:
 | 
			
		||||
  groups:
 | 
			
		||||
  - name: Aggregate request counters
 | 
			
		||||
    rules:
 | 
			
		||||
      - record: job:django_http_requests_before_middlewares_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_before_middlewares_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_requests_unknown_latency_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_unknown_latency_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_ajax_requests_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_ajax_requests_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_responses_before_middlewares_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_before_middlewares_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_requests_unknown_latency_including_middlewares_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_unknown_latency_including_middlewares_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_requests_body_total_bytes:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_body_total_bytes[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_responses_streaming_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_streaming_total[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_responses_body_total_bytes:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_body_total_bytes[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_requests_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_total_by_method[30s])) by (job)
 | 
			
		||||
      - record: job:django_http_requests_total_by_method:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_total_by_method[30s])) by (job,method)
 | 
			
		||||
      - record: job:django_http_requests_total_by_transport:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_total_by_transport[30s])) by (job,transport)
 | 
			
		||||
      - record: job:django_http_requests_total_by_view:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_total_by_view_transport_method[30s])) by (job,view)
 | 
			
		||||
      - record: job:django_http_requests_total_by_view_transport_method:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_requests_total_by_view_transport_method[30s])) by (job,view,transport,method)
 | 
			
		||||
      - record: job:django_http_responses_total_by_templatename:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_total_by_templatename[30s])) by (job,templatename)
 | 
			
		||||
      - record: job:django_http_responses_total_by_status:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_total_by_status[30s])) by (job,status)
 | 
			
		||||
      - record: job:django_http_responses_total_by_status_name_method:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_total_by_status_name_method[30s])) by (job,status,name,method)
 | 
			
		||||
      - record: job:django_http_responses_total_by_charset:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_responses_total_by_charset[30s])) by (job,charset)
 | 
			
		||||
      - record: job:django_http_exceptions_total_by_type:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_exceptions_total_by_type[30s])) by (job,type)
 | 
			
		||||
      - record: job:django_http_exceptions_total_by_view:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_http_exceptions_total_by_view[30s])) by (job,view)
 | 
			
		||||
  - name: Aggregate latency histograms
 | 
			
		||||
    rules:
 | 
			
		||||
      - record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.50, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "50"
 | 
			
		||||
      - record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.95, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "95"
 | 
			
		||||
      - record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.99, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "99"
 | 
			
		||||
      - record: job:django_http_requests_latency_including_middlewares_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.999, sum(rate(django_http_requests_latency_including_middlewares_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "99.9"
 | 
			
		||||
      - record: job:django_http_requests_latency_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.50, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "50"
 | 
			
		||||
      - record: job:django_http_requests_latency_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.95, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "95"
 | 
			
		||||
      - record: job:django_http_requests_latency_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.99, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "99"
 | 
			
		||||
      - record: job:django_http_requests_latency_seconds:quantile_rate30s
 | 
			
		||||
        expr: histogram_quantile(0.999, sum(rate(django_http_requests_latency_seconds_bucket[30s])) by (job, le))
 | 
			
		||||
        labels:
 | 
			
		||||
          quantile: "99.9"
 | 
			
		||||
  - name: Aggregate model operations
 | 
			
		||||
    rules:
 | 
			
		||||
      - record: job:django_model_inserts_total:sum_rate1m
 | 
			
		||||
        expr: sum(rate(django_model_inserts_total[1m])) by (job, model)
 | 
			
		||||
      - record: job:django_model_updates_total:sum_rate1m
 | 
			
		||||
        expr: sum(rate(django_model_updates_total[1m])) by (job, model)
 | 
			
		||||
      - record: job:django_model_deletes_total:sum_rate1m
 | 
			
		||||
        expr: sum(rate(django_model_deletes_total[1m])) by (job, model)
 | 
			
		||||
  - name: Aggregate database operations
 | 
			
		||||
    rules:
 | 
			
		||||
      - record: job:django_db_new_connections_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_db_new_connections_total[30s])) by (alias, vendor)
 | 
			
		||||
      - record: job:django_db_new_connection_errors_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_db_new_connection_errors_total[30s])) by (alias, vendor)
 | 
			
		||||
      - record: job:django_db_execute_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_db_execute_total[30s])) by (alias, vendor)
 | 
			
		||||
      - record: job:django_db_execute_many_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_db_execute_many_total[30s])) by (alias, vendor)
 | 
			
		||||
      - record: job:django_db_errors_total:sum_rate30s
 | 
			
		||||
        expr: sum(rate(django_db_errors_total[30s])) by (alias, vendor, type)
 | 
			
		||||
  - name: Aggregate migrations
 | 
			
		||||
    rules:
 | 
			
		||||
      - record: job:django_migrations_applied_total:max
 | 
			
		||||
        expr: max(django_migrations_applied_total) by (job, connection)
 | 
			
		||||
      - record: job:django_migrations_unapplied_total:max
 | 
			
		||||
        expr: max(django_migrations_unapplied_total) by (job, connection)
 | 
			
		||||
  - name: Alerts
 | 
			
		||||
    rules:
 | 
			
		||||
      - alert: UnappliedMigrations
 | 
			
		||||
        expr: job:django_migrations_unapplied_total:max > 0
 | 
			
		||||
        for: 1m
 | 
			
		||||
        labels:
 | 
			
		||||
          severity: testing
 | 
			
		||||
{{- end }}
 | 
			
		||||
@ -4,6 +4,7 @@ type: Opaque
 | 
			
		||||
metadata:
 | 
			
		||||
  name: {{ include "passbook.fullname" . }}-secret-key
 | 
			
		||||
data:
 | 
			
		||||
  monitoring_username: bW9uaXRvcg== # monitor in base64
 | 
			
		||||
  {{- if .Values.config.secret_key }}
 | 
			
		||||
  secret_key: {{ .Values.config.secret_key | b64enc | quote }}
 | 
			
		||||
  {{- else }}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
apiVersion: apps/v1beta2
 | 
			
		||||
apiVersion: apps/v1
 | 
			
		||||
kind: Deployment
 | 
			
		||||
metadata:
 | 
			
		||||
  name: {{ include "passbook.fullname" . }}-static
 | 
			
		||||
@ -18,10 +18,6 @@ spec:
 | 
			
		||||
        app.kubernetes.io/name: {{ include "passbook.name" . }}
 | 
			
		||||
        app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
        k8s.passbook.io/component: static
 | 
			
		||||
      annotations:
 | 
			
		||||
        prometheus.io/scrape: "true"
 | 
			
		||||
        prometheus.io/port: '9113'
 | 
			
		||||
        field.cattle.io/workloadMetrics: '[{"path":"/metrics","port":9113,"schema":"HTTP"}]'
 | 
			
		||||
    spec:
 | 
			
		||||
      containers:
 | 
			
		||||
        - name: {{ .Chart.Name }}-static
 | 
			
		||||
@ -29,7 +25,7 @@ spec:
 | 
			
		||||
          imagePullPolicy: IfNotPresent
 | 
			
		||||
          ports:
 | 
			
		||||
            - name: http
 | 
			
		||||
              containerPort: 80
 | 
			
		||||
              containerPort: 8080
 | 
			
		||||
              protocol: TCP
 | 
			
		||||
          livenessProbe:
 | 
			
		||||
            initialDelaySeconds: 10
 | 
			
		||||
@ -50,6 +46,3 @@ spec:
 | 
			
		||||
            limits:
 | 
			
		||||
              cpu: 20m
 | 
			
		||||
              memory: 20M
 | 
			
		||||
        - name: {{ .Chart.Name }}-static-prometheus
 | 
			
		||||
          image: nginx/nginx-prometheus-exporter:0.4.1
 | 
			
		||||
          imagePullPolicy: IfNotPresent
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ metadata:
 | 
			
		||||
spec:
 | 
			
		||||
  type: ClusterIP
 | 
			
		||||
  ports:
 | 
			
		||||
    - port: 80
 | 
			
		||||
    - port: 8080
 | 
			
		||||
      targetPort: http
 | 
			
		||||
      protocol: TCP
 | 
			
		||||
      name: http
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								helm/passbook/templates/static-sm.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								helm/passbook/templates/static-sm.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
{{- if .Values.monitoring.enabled -}}
 | 
			
		||||
apiVersion: monitoring.coreos.com/v1
 | 
			
		||||
kind: ServiceMonitor
 | 
			
		||||
metadata:
 | 
			
		||||
  labels:
 | 
			
		||||
    app.kubernetes.io/name: {{ include "passbook.name" . }}
 | 
			
		||||
    helm.sh/chart: {{ include "passbook.chart" . }}
 | 
			
		||||
    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
			
		||||
  name: {{ include "passbook.fullname" . }}-static-monitoring
 | 
			
		||||
spec:
 | 
			
		||||
  endpoints:
 | 
			
		||||
  - port: http
 | 
			
		||||
  selector:
 | 
			
		||||
    matchLabels:
 | 
			
		||||
      k8s.passbook.io/component: static
 | 
			
		||||
{{- end }}
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
apiVersion: apps/v1beta2
 | 
			
		||||
apiVersion: apps/v1
 | 
			
		||||
kind: Deployment
 | 
			
		||||
metadata:
 | 
			
		||||
  name: {{ include "passbook.fullname" . }}-web
 | 
			
		||||
 | 
			
		||||
@ -4,13 +4,14 @@ metadata:
 | 
			
		||||
  name: {{ include "passbook.fullname" . }}-web
 | 
			
		||||
  labels:
 | 
			
		||||
    app.kubernetes.io/name: {{ include "passbook.name" . }}
 | 
			
		||||
    helm.sh/chart: {{ include "passbook.chart" . }}
 | 
			
		||||
    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
			
		||||
    helm.sh/chart: {{ include "passbook.chart" . }}
 | 
			
		||||
    k8s.passbook.io/component: web
 | 
			
		||||
spec:
 | 
			
		||||
  type: {{ .Values.service.type }}
 | 
			
		||||
  type: ClusterIP
 | 
			
		||||
  ports:
 | 
			
		||||
    - port: {{ .Values.service.port }}
 | 
			
		||||
    - port: 80
 | 
			
		||||
      targetPort: http
 | 
			
		||||
      protocol: TCP
 | 
			
		||||
      name: http
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										25
									
								
								helm/passbook/templates/web-sm.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								helm/passbook/templates/web-sm.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
{{- if .Values.monitoring.enabled -}}
 | 
			
		||||
apiVersion: monitoring.coreos.com/v1
 | 
			
		||||
kind: ServiceMonitor
 | 
			
		||||
metadata:
 | 
			
		||||
  labels:
 | 
			
		||||
    app.kubernetes.io/name: {{ include "passbook.name" . }}
 | 
			
		||||
    helm.sh/chart: {{ include "passbook.chart" . }}
 | 
			
		||||
    app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
    app.kubernetes.io/managed-by: {{ .Release.Service }}
 | 
			
		||||
  name: {{ include "passbook.fullname" . }}-web-monitoring
 | 
			
		||||
spec:
 | 
			
		||||
  endpoints:
 | 
			
		||||
  - basicAuth:
 | 
			
		||||
      password:
 | 
			
		||||
        name: {{ include "passbook.fullname" . }}-secret-key
 | 
			
		||||
        key: secret_key
 | 
			
		||||
      username:
 | 
			
		||||
        name: {{ include "passbook.fullname" . }}-secret-key
 | 
			
		||||
        key: monitoring_username
 | 
			
		||||
    port: http
 | 
			
		||||
    interval: 10s
 | 
			
		||||
  selector:
 | 
			
		||||
    matchLabels:
 | 
			
		||||
      k8s.passbook.io/component: web
 | 
			
		||||
{{- end }}
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
apiVersion: apps/v1beta2
 | 
			
		||||
apiVersion: apps/v1
 | 
			
		||||
kind: Deployment
 | 
			
		||||
metadata:
 | 
			
		||||
  name: {{ include "passbook.fullname" . }}-worker
 | 
			
		||||
 | 
			
		||||
@ -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.4-beta
 | 
			
		||||
 | 
			
		||||
nameOverride: ""
 | 
			
		||||
 | 
			
		||||
@ -10,23 +10,14 @@ config:
 | 
			
		||||
  # Optionally specify fixed secret_key, otherwise generated automatically
 | 
			
		||||
  # secret_key: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
 | 
			
		||||
  # Enable error reporting
 | 
			
		||||
  error_reporting: true
 | 
			
		||||
  error_reporting: false
 | 
			
		||||
  email:
 | 
			
		||||
    host: localhost
 | 
			
		||||
 | 
			
		||||
postgresql:
 | 
			
		||||
  postgresqlDatabase: passbook
 | 
			
		||||
 | 
			
		||||
redis:
 | 
			
		||||
  cluster:
 | 
			
		||||
# This Helm chart ships with built-in Prometheus ServiceMonitors and Rules.
 | 
			
		||||
# This requires the CoreOS Prometheus Operator.
 | 
			
		||||
monitoring:
 | 
			
		||||
  enabled: false
 | 
			
		||||
  master:
 | 
			
		||||
    persistence:
 | 
			
		||||
      enabled: false
 | 
			
		||||
 | 
			
		||||
service:
 | 
			
		||||
  type: ClusterIP
 | 
			
		||||
  port: 80
 | 
			
		||||
 | 
			
		||||
ingress:
 | 
			
		||||
  enabled: false
 | 
			
		||||
@ -40,3 +31,14 @@ ingress:
 | 
			
		||||
  #  - secretName: chart-example-tls
 | 
			
		||||
  #    hosts:
 | 
			
		||||
  #      - passbook.k8s.local
 | 
			
		||||
 | 
			
		||||
# These settings configure the packaged PostgreSQL and Redis chart.
 | 
			
		||||
postgresql:
 | 
			
		||||
  postgresqlDatabase: passbook
 | 
			
		||||
 | 
			
		||||
redis:
 | 
			
		||||
  cluster:
 | 
			
		||||
    enabled: false
 | 
			
		||||
  master:
 | 
			
		||||
    persistence:
 | 
			
		||||
      enabled: false
 | 
			
		||||
 | 
			
		||||
@ -1,2 +1,2 @@
 | 
			
		||||
"""passbook"""
 | 
			
		||||
__version__ = '0.6.8-beta'
 | 
			
		||||
__version__ = '0.7.4-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,13 @@ 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'
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:applications')
 | 
			
		||||
@ -36,21 +44,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,15 @@ 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'
 | 
			
		||||
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:factors')
 | 
			
		||||
    success_message = _('Successfully created Factor')
 | 
			
		||||
 | 
			
		||||
@ -53,10 +64,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 +83,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,28 +1,36 @@
 | 
			
		||||
"""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'
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:groups')
 | 
			
		||||
@ -33,18 +41,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,40 @@
 | 
			
		||||
"""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'
 | 
			
		||||
 | 
			
		||||
    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 +50,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,13 @@ 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'
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:policies')
 | 
			
		||||
    success_message = _('Successfully created Policy')
 | 
			
		||||
@ -48,10 +55,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 +75,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 +94,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 +117,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,13 @@ 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'
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:property-mappings')
 | 
			
		||||
    success_message = _('Successfully created Property Mapping')
 | 
			
		||||
@ -58,10 +67,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 +87,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,13 @@ 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'
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:providers')
 | 
			
		||||
    success_message = _('Successfully created Provider')
 | 
			
		||||
@ -43,10 +51,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 +71,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,13 @@ 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'
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:sources')
 | 
			
		||||
    success_message = _('Successfully created Source')
 | 
			
		||||
@ -48,10 +57,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 +77,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,50 @@
 | 
			
		||||
"""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'
 | 
			
		||||
 | 
			
		||||
    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 +53,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 +69,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,
 | 
			
		||||
    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,
 | 
			
		||||
    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']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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')
 | 
			
		||||
 | 
			
		||||
@ -13,8 +13,8 @@ redis:
 | 
			
		||||
 | 
			
		||||
debug: false
 | 
			
		||||
 | 
			
		||||
# Error reporting, sends stacktrace to sentry.services.beryju.org
 | 
			
		||||
error_report_enabled: true
 | 
			
		||||
# Error reporting, sends stacktrace to sentry.beryju.org
 | 
			
		||||
error_reporting: false
 | 
			
		||||
 | 
			
		||||
domain: localhost
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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.')
 | 
			
		||||
							
								
								
									
										23
									
								
								passbook/lib/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								passbook/lib/views.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
"""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 = [
 | 
			
		||||
        '%s.view_%s',
 | 
			
		||||
        '%s.change_%s',
 | 
			
		||||
        '%s.delete_%s',
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        response = super().form_valid(form)
 | 
			
		||||
        for permission in self.permissions:
 | 
			
		||||
            full_permission = permission % (
 | 
			
		||||
                self.object._meta.app_label, self.object._meta.model_name)
 | 
			
		||||
            print(full_permission)
 | 
			
		||||
            assign_perm(full_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)
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user