Compare commits
	
		
			74 Commits
		
	
	
		
			version/0.
			...
			version/0.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 48a04744e0 | |||
| 6446ca8bb2 | |||
| b9991465ee | |||
| 3d8242be06 | |||
| ca3bcc565d | |||
| 432176ea2f | |||
| c1dae0b599 | |||
| e70d3b6286 | |||
| 17e6bc921b | |||
| 46111e7cac | |||
| 3b7e47dbe2 | |||
| fff99f0e3d | |||
| 2e15b24f0a | |||
| 088b9592cd | |||
| b1e4e32b83 | |||
| d91a852eda | |||
| 171c5b9759 | |||
| 64290b2a37 | |||
| 72769b8a0a | |||
| 1018309413 | |||
| 6d0ecd228e | |||
| 40a651e66c | |||
| a390bb7b59 | |||
| 245ec65cbb | |||
| 17eea4a10c | |||
| 862fb0f5d2 | |||
| ec73b53340 | |||
| 9110f7fee3 | |||
| 54cc1fdeef | |||
| 8f42a7f0b4 | |||
| 2c221ea819 | |||
| 93e0441b58 | |||
| 7f1455cb12 | |||
| 59fc223a85 | |||
| 0a6f555c23 | |||
| 6a4233d6fd | |||
| 15fa7e9652 | |||
| f2acc154cd | |||
| d21ec6c9a5 | |||
| 43dd858cd5 | |||
| 34cbf5f702 | |||
| 3c6e94b6a8 | |||
| 1cd149c815 | |||
| 4c6f562805 | |||
| e59c4ec1c7 | |||
| 1169db7530 | |||
| 1453008796 | |||
| 2209b6d603 | |||
| ccbc0384f9 | |||
| a48924c896 | |||
| dc8d8dd2b6 | |||
| afca94ceb8 | |||
| 0b86231a36 | |||
| c0df1f38b8 | |||
| 2b8fed8f4e | |||
| c7322a32a0 | |||
| 64b75cab84 | |||
| f58bc61999 | |||
| fb8ccc0283 | |||
| c38012f147 | |||
| 3676ff21c2 | |||
| 920e705d75 | |||
| de0b137b1e | |||
| d44ac6e2a3 | |||
| 71039a4012 | |||
| 8745ac7932 | |||
| 7f70048423 | |||
| 97dbfc8885 | |||
| 149ea22a93 | |||
| 404ed5406d | |||
| b8656858ec | |||
| 6b0f0e8993 | |||
| aec1ccd88d | |||
| bee5c200b6 | 
| @ -1,5 +1,5 @@ | |||||||
| [bumpversion] | [bumpversion] | ||||||
| current_version = 0.3.0-beta | current_version = 0.6.4-beta | ||||||
| tag = True | tag = True | ||||||
| commit = True | commit = True | ||||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) | parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) | ||||||
| @ -23,5 +23,5 @@ values = | |||||||
|  |  | ||||||
| [bumpversion:file:passbook/__init__.py] | [bumpversion:file:passbook/__init__.py] | ||||||
|  |  | ||||||
| [bumpversion:file:passbook/core/nginx.conf] | [bumpversion:file:docker/nginx.conf] | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| [run] | [run] | ||||||
| source = passbook | source = passbook | ||||||
| omit = | omit = | ||||||
|     env/ |  | ||||||
|     */wsgi.py |     */wsgi.py | ||||||
|     manage.py |     manage.py | ||||||
|     */migrations/* |     */migrations/* | ||||||
|  | |||||||
| @ -2,3 +2,4 @@ env | |||||||
| helm | helm | ||||||
| passbook-ui | passbook-ui | ||||||
| static | static | ||||||
|  | *.env.yml | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ create-base-image: | |||||||
|   before_script: |   before_script: | ||||||
|     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json |     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json | ||||||
|   script: |   script: | ||||||
|     - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/base.Dockerfile --destination docker.beryju.org/passbook/base:latest --destination docker.beryju.org/passbook/base:0.3.0-beta |     - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/base.Dockerfile --destination docker.beryju.org/passbook/base:latest --destination docker.beryju.org/passbook/base:0.6.4-beta | ||||||
|   stage: build-base-image |   stage: build-base-image | ||||||
|   only: |   only: | ||||||
|     refs: |     refs: | ||||||
| @ -41,7 +41,7 @@ build-dev-image: | |||||||
|   before_script: |   before_script: | ||||||
|     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json |     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json | ||||||
|   script: |   script: | ||||||
|     - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dev.Dockerfile --destination docker.beryju.org/passbook/dev:latest --destination docker.beryju.org/passbook/dev:0.3.0-beta |     - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dev.Dockerfile --destination docker.beryju.org/passbook/dev:latest --destination docker.beryju.org/passbook/dev:0.6.4-beta | ||||||
|   stage: build-dev-image |   stage: build-dev-image | ||||||
|   only: |   only: | ||||||
|     refs: |     refs: | ||||||
| @ -63,20 +63,20 @@ migrations: | |||||||
|   services: |   services: | ||||||
|   - postgres:latest |   - postgres:latest | ||||||
|   - redis:latest |   - redis:latest | ||||||
| prospector: | # prospector: | ||||||
|   script: | #   script: | ||||||
|     - prospector | #     - prospector | ||||||
|   stage: test | #   stage: test | ||||||
|   services: | #   services: | ||||||
|   - postgres:latest | #   - postgres:latest | ||||||
|   - redis:latest | #   - redis:latest | ||||||
| pylint: | # pylint: | ||||||
|   script: | #   script: | ||||||
|     - pylint passbook | #     - pylint passbook | ||||||
|   stage: test | #   stage: test | ||||||
|   services: | #   services: | ||||||
|   - postgres:latest | #   - postgres:latest | ||||||
|   - redis:latest | #   - redis:latest | ||||||
| coverage: | coverage: | ||||||
|   script: |   script: | ||||||
|     - coverage run manage.py test |     - coverage run manage.py test | ||||||
| @ -87,15 +87,15 @@ coverage: | |||||||
|   - postgres:latest |   - postgres:latest | ||||||
|   - redis:latest |   - redis:latest | ||||||
|  |  | ||||||
| package-passbook-server: | build-passbook-server: | ||||||
|  |   stage: build | ||||||
|   image: |   image: | ||||||
|     name: gcr.io/kaniko-project/executor:debug |     name: gcr.io/kaniko-project/executor:debug | ||||||
|     entrypoint: [""] |     entrypoint: [""] | ||||||
|   before_script: |   before_script: | ||||||
|     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json |     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json | ||||||
|   script: |   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.3.0-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.6.4-beta | ||||||
|   stage: build |  | ||||||
|   only: |   only: | ||||||
|     - tags |     - tags | ||||||
|     - /^version/.*$/ |     - /^version/.*$/ | ||||||
| @ -107,7 +107,7 @@ build-passbook-static: | |||||||
|   before_script: |   before_script: | ||||||
|     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json |     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json | ||||||
|   script: |   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.3.0-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.6.4-beta | ||||||
|   only: |   only: | ||||||
|     - tags |     - tags | ||||||
|     - /^version/.*$/ |     - /^version/.*$/ | ||||||
| @ -124,7 +124,7 @@ package-helm: | |||||||
|     - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash |     - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash | ||||||
|   script: |   script: | ||||||
|     - helm init --client-only |     - helm init --client-only | ||||||
|     - helm dependency build helm/passbook |     - helm dependency update helm/passbook | ||||||
|     - helm package helm/passbook |     - helm package helm/passbook | ||||||
|   artifacts: |   artifacts: | ||||||
|     paths: |     paths: | ||||||
|  | |||||||
| @ -3,11 +3,9 @@ test-warnings: true | |||||||
| doc-warnings: false | doc-warnings: false | ||||||
|  |  | ||||||
| ignore-paths: | ignore-paths: | ||||||
|   - env |  | ||||||
|   - migrations |   - migrations | ||||||
|   - docs |   - docs | ||||||
|   - node_modules |   - node_modules | ||||||
|   - client-packages |  | ||||||
|  |  | ||||||
| uses: | uses: | ||||||
|  - django |  - django | ||||||
|  | |||||||
| @ -2,9 +2,10 @@ | |||||||
|  |  | ||||||
| disable=redefined-outer-name,arguments-differ,no-self-use,cyclic-import,fixme,locally-disabled,unpacking-non-sequence,too-many-ancestors,too-many-branches,too-few-public-methods | disable=redefined-outer-name,arguments-differ,no-self-use,cyclic-import,fixme,locally-disabled,unpacking-non-sequence,too-many-ancestors,too-many-branches,too-few-public-methods | ||||||
| load-plugins=pylint_django,pylint.extensions.bad_builtin | load-plugins=pylint_django,pylint.extensions.bad_builtin | ||||||
| #,pylint.extensions.docparams |  | ||||||
| extension-pkg-whitelist=lxml | extension-pkg-whitelist=lxml | ||||||
| const-rgx=[a-zA-Z0-9_]{1,40}$ | const-rgx=[a-zA-Z0-9_]{1,40}$ | ||||||
|  | ignored-modules=django-otp | ||||||
|  | jobs=4 | ||||||
|  |  | ||||||
| [SIMILARITIES] | [SIMILARITIES] | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,8 +1,9 @@ | |||||||
| FROM docker.beryju.org/passbook/base:latest | FROM docker.beryju.org/passbook/base:latest | ||||||
|  |  | ||||||
| COPY ./passbook/ /app/passbook | COPY --chown=passbook:passbook ./passbook/ /app/passbook | ||||||
| COPY ./manage.py /app/ | COPY ./manage.py /app/ | ||||||
|  | COPY ./docker/uwsgi.ini /app/ | ||||||
| USER passbook |  | ||||||
|  |  | ||||||
| WORKDIR /app/ | WORKDIR /app/ | ||||||
|  |  | ||||||
|  | USER passbook | ||||||
|  | |||||||
							
								
								
									
										19
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								Pipfile
									
									
									
									
									
								
							| @ -4,15 +4,11 @@ url = "https://pypi.org/simple" | |||||||
| verify_ssl = true | verify_ssl = true | ||||||
|  |  | ||||||
| [packages] | [packages] | ||||||
| asgiref = "*" |  | ||||||
| beautifulsoup4 = "*" |  | ||||||
| celery = "*" | celery = "*" | ||||||
| channels = "*" |  | ||||||
| cherrypy = "*" | cherrypy = "*" | ||||||
| colorlog = "*" |  | ||||||
| daphne = "*" |  | ||||||
| defusedxml = "*" | defusedxml = "*" | ||||||
| django = "*" | django = "*" | ||||||
|  | kombu = "==4.5.0" | ||||||
| django-cors-middleware = "*" | django-cors-middleware = "*" | ||||||
| django-filters = "*" | django-filters = "*" | ||||||
| django-ipware = "*" | django-ipware = "*" | ||||||
| @ -23,15 +19,13 @@ django-otp = "*" | |||||||
| django-recaptcha = "*" | django-recaptcha = "*" | ||||||
| django-redis = "*" | django-redis = "*" | ||||||
| django-rest-framework = "*" | django-rest-framework = "*" | ||||||
| django-revproxy = "*" |  | ||||||
| djangorestframework = "==3.9.4" |  | ||||||
| drf-yasg = "*" | drf-yasg = "*" | ||||||
| ldap3 = "*" | ldap3 = "*" | ||||||
| lxml = "*" | lxml = "*" | ||||||
| markdown = "*" | markdown = "*" | ||||||
| oauthlib = "*" | oauthlib = "*" | ||||||
| packaging = "*" | packaging = "*" | ||||||
| psycopg2 = "*" | psycopg2-binary = "*" | ||||||
| pycryptodome = "*" | pycryptodome = "*" | ||||||
| pyyaml = "*" | pyyaml = "*" | ||||||
| qrcode = "*" | qrcode = "*" | ||||||
| @ -40,22 +34,21 @@ sentry-sdk = "*" | |||||||
| service_identity = "*" | service_identity = "*" | ||||||
| signxml = "*" | signxml = "*" | ||||||
| urllib3 = {extras = ["secure"],version = "*"} | urllib3 = {extras = ["secure"],version = "*"} | ||||||
| websocket_client = "*" |  | ||||||
| structlog = "*" | structlog = "*" | ||||||
|  |  | ||||||
| [requires] | [requires] | ||||||
| python_version = "3.7" | python_version = "3.7" | ||||||
|  |  | ||||||
| [dev-packages] | [dev-packages] | ||||||
|  | astroid = "==2.2.5" | ||||||
| coverage = "*" | coverage = "*" | ||||||
| isort = "*" | isort = "*" | ||||||
| pylint = "*" | pylint = "==2.3.1" | ||||||
| pylint-django = "*" | pylint-django = "==2.0.10" | ||||||
| prospector = "*" | prospector = "==1.1.7" | ||||||
| django-debug-toolbar = "*" | django-debug-toolbar = "*" | ||||||
| bumpversion = "*" | bumpversion = "*" | ||||||
| unittest-xml-reporting = "*" | unittest-xml-reporting = "*" | ||||||
| autopep8 = "*" | autopep8 = "*" | ||||||
| bandit = "*" | bandit = "*" | ||||||
| twine = "*" |  | ||||||
| colorama = "*" | colorama = "*" | ||||||
|  | |||||||
							
								
								
									
										510
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										510
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							| @ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|     "_meta": { |     "_meta": { | ||||||
|         "hash": { |         "hash": { | ||||||
|             "sha256": "f8694b0ee03f99560e853fd24e9cd7ac987c757cd50249398346e42cdd98cbbb" |             "sha256": "53d7190ea62f504dc1a36eae952a273e0b2d9f313f23031099d039c3146235b7" | ||||||
|         }, |         }, | ||||||
|         "pipfile-spec": 6, |         "pipfile-spec": 6, | ||||||
|         "requires": { |         "requires": { | ||||||
| @ -18,62 +18,24 @@ | |||||||
|     "default": { |     "default": { | ||||||
|         "amqp": { |         "amqp": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:19a917e260178b8d410122712bac69cb3e6db010d68f6101e7307508aded5e68", |                 "sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8", | ||||||
|                 "sha256:19d851b879a471fcfdcf01df9936cff924f422baa77653289f7095dedd5fb26a" |                 "sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d" | ||||||
|             ], |             ], | ||||||
|             "version": "==2.5.1" |             "version": "==2.5.2" | ||||||
|         }, |  | ||||||
|         "asgiref": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:a4ce726e6ef49cca13642ff49588530ebabcc47c669c7a95af37ea5a74b9b823", |  | ||||||
|                 "sha256:f62b1c88ebf5fe95db202a372982970edcf375c1513d7e70717df0750f5c2b98" |  | ||||||
|             ], |  | ||||||
|             "index": "pypi", |  | ||||||
|             "version": "==3.2.2" |  | ||||||
|         }, |         }, | ||||||
|         "asn1crypto": { |         "asn1crypto": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", |                 "sha256:0b199f211ae690df3db4fd6c1c4ff976497fb1da689193e368eedbadc53d9292", | ||||||
|                 "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49" |                 "sha256:bca90060bd995c3f62c4433168eab407e44bdbdb567b3f3a396a676c1a4c4a3f" | ||||||
|             ], |             ], | ||||||
|             "version": "==0.24.0" |             "version": "==1.0.1" | ||||||
|         }, |         }, | ||||||
|         "attrs": { |         "attrs": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", |                 "sha256:ec20e7a4825331c1b5ebf261d111e16fa9612c1f7a5e1f884f12bd53a664dfd2", | ||||||
|                 "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" |                 "sha256:f913492e1663d3c36f502e5e9ba6cd13cf19d7fab50aa13239e420fef95e1396" | ||||||
|             ], |             ], | ||||||
|             "version": "==19.1.0" |             "version": "==19.2.0" | ||||||
|         }, |  | ||||||
|         "autobahn": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:27688cbddd5545fc2ee2614ec8fa65119f1a2122606ce2ef7756392c33e3ec0f", |  | ||||||
|                 "sha256:a24826ad0bcc35d32cb4576a092fa744e8b6738bd6320d2de857ad8a71df0bec" |  | ||||||
|             ], |  | ||||||
|             "version": "==19.9.3" |  | ||||||
|         }, |  | ||||||
|         "automat": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:cbd78b83fa2d81fe2a4d23d258e1661dd7493c9a50ee2f1a5b2cac61c1793b0e", |  | ||||||
|                 "sha256:fdccab66b68498af9ecfa1fa43693abe546014dd25cf28543cbe9d1334916a58" |  | ||||||
|             ], |  | ||||||
|             "version": "==0.7.0" |  | ||||||
|         }, |  | ||||||
|         "backports.functools-lru-cache": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a", |  | ||||||
|                 "sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd" |  | ||||||
|             ], |  | ||||||
|             "version": "==1.5" |  | ||||||
|         }, |  | ||||||
|         "beautifulsoup4": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:05668158c7b85b791c5abde53e50265e16f98ad601c402ba44d70f96c4159612", |  | ||||||
|                 "sha256:25288c9e176f354bf277c0a10aa96c782a6a18a17122dba2e8cec4a97e03343b", |  | ||||||
|                 "sha256:f040590be10520f2ea4c2ae8c3dae441c7cfff5308ec9d58a0ec0c1b8f81d469" |  | ||||||
|             ], |  | ||||||
|             "index": "pypi", |  | ||||||
|             "version": "==4.8.0" |  | ||||||
|         }, |         }, | ||||||
|         "billiard": { |         "billiard": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -92,10 +54,10 @@ | |||||||
|         }, |         }, | ||||||
|         "certifi": { |         "certifi": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", |                 "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", | ||||||
|                 "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" |                 "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" | ||||||
|             ], |             ], | ||||||
|             "version": "==2019.6.16" |             "version": "==2019.9.11" | ||||||
|         }, |         }, | ||||||
|         "cffi": { |         "cffi": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -130,14 +92,6 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==1.12.3" |             "version": "==1.12.3" | ||||||
|         }, |         }, | ||||||
|         "channels": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:9191a85800673b790d1d74666fb7676f430600b71b662581e97dd69c9aedd29a", |  | ||||||
|                 "sha256:af7cdba9efb3f55b939917d1b15defb5d40259936013e60660e5e9aff98db4c5" |  | ||||||
|             ], |  | ||||||
|             "index": "pypi", |  | ||||||
|             "version": "==2.2.0" |  | ||||||
|         }, |  | ||||||
|         "chardet": { |         "chardet": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", |                 "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", | ||||||
| @ -147,33 +101,18 @@ | |||||||
|         }, |         }, | ||||||
|         "cheroot": { |         "cheroot": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:427e7e3ce51ad5a6e5cf953252b5782d5dfbeb544c09910634971bc06df6621b", |                 "sha256:6168371ab9aaf574ac5f75675f244bbfebf990202bf75048065e9d675b9ae719", | ||||||
|                 "sha256:74d733c55178812253d855990f7ad7b31ab4ee8dab80e4803bd5e52299c50395" |                 "sha256:8cc7c28961db2e13d0cac6b234a589a314c1844f7bbf54e67888ac9a2e25ac59" | ||||||
|             ], |             ], | ||||||
|             "version": "==6.5.8" |             "version": "==7.0.0" | ||||||
|         }, |         }, | ||||||
|         "cherrypy": { |         "cherrypy": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:16fc226a280cd772ede7c309d3964002196784ac6615d8bface52be12ff51230", |                 "sha256:033368d25fcc6bca143e7efe9adbfd3a6d91cc0d90c37a649261935f116aafab", | ||||||
|                 "sha256:488ea5e639885c75330686c1d7d3dfbd002f784c027a3fe5b374b41926b8cba3" |                 "sha256:683e687e7c7b1ba31ef86a113b1eafd0407269fed175bf488d3c839d37d1cc60" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==18.2.0" |             "version": "==18.3.0" | ||||||
|         }, |  | ||||||
|         "colorlog": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:3cf31b25cbc8f86ec01fef582ef3b840950dea414084ed19ab922c8b493f9b42", |  | ||||||
|                 "sha256:450f52ea2a2b6ebb308f034ea9a9b15cea51e65650593dca1da3eb792e4e4981" |  | ||||||
|             ], |  | ||||||
|             "index": "pypi", |  | ||||||
|             "version": "==4.0.2" |  | ||||||
|         }, |  | ||||||
|         "constantly": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35", |  | ||||||
|                 "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d" |  | ||||||
|             ], |  | ||||||
|             "version": "==15.1.0" |  | ||||||
|         }, |         }, | ||||||
|         "coreapi": { |         "coreapi": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -210,14 +149,6 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==2.7" |             "version": "==2.7" | ||||||
|         }, |         }, | ||||||
|         "daphne": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:2329b7a74b5559f7ea012879c10ba945c3a53df7d8d2b5932a904e3b4c9abcc2", |  | ||||||
|                 "sha256:3cae286a995ae5b127d7de84916f0480cb5be19f81125b6a150b8326250dadd5" |  | ||||||
|             ], |  | ||||||
|             "index": "pypi", |  | ||||||
|             "version": "==2.3.0" |  | ||||||
|         }, |  | ||||||
|         "defusedxml": { |         "defusedxml": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", |                 "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", | ||||||
| @ -228,11 +159,11 @@ | |||||||
|         }, |         }, | ||||||
|         "django": { |         "django": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:148a4a2d1a85b23883b0a4e99ab7718f518a83675e4485e44dc0c1d36988c5fa", |                 "sha256:4025317ca01f75fc79250ff7262a06d8ba97cd4f82e93394b2a0a6a4a925caeb", | ||||||
|                 "sha256:deb70aa038e59b58593673b15e9a711d1e5ccd941b5973b30750d5d026abfd56" |                 "sha256:a8ca1033acac9f33995eb2209a6bf18a4681c3e5269a878e9a7e0b7384ed1ca3" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==2.2.5" |             "version": "==2.2.6" | ||||||
|         }, |         }, | ||||||
|         "django-cors-middleware": { |         "django-cors-middleware": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -280,11 +211,11 @@ | |||||||
|         }, |         }, | ||||||
|         "django-otp": { |         "django-otp": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:246b11ee38ec1cea2e2312311a830740d1a8d0384ba15e7b70e03f851d790157", |                 "sha256:79c8253be97246df86540d551dc705e8fe6ca76af8e8c77f78314cd1b513c2cf", | ||||||
|                 "sha256:cefbf5e7295498c767752d77828ce3f56cdb0373915e56fe4f87d99604742394" |                 "sha256:c5bf3916dca5d53cb377aa6dea40aa785c164013fbf750384137362dfa278cf5" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==0.7.0" |             "version": "==0.7.2" | ||||||
|         }, |         }, | ||||||
|         "django-recaptcha": { |         "django-recaptcha": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -309,29 +240,20 @@ | |||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==0.1.0" |             "version": "==0.1.0" | ||||||
|         }, |         }, | ||||||
|         "django-revproxy": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:0b539736e438aad3cd8b34563125783678f65bcb847970c95d8e9820e6dc88b3", |  | ||||||
|                 "sha256:b2c6244aaf53fbbecb79084bf507761754b36895c0f6d01349066e9a355e8455" |  | ||||||
|             ], |  | ||||||
|             "index": "pypi", |  | ||||||
|             "version": "==0.9.15" |  | ||||||
|         }, |  | ||||||
|         "djangorestframework": { |         "djangorestframework": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:376f4b50340a46c15ae15ddd0c853085f4e66058f97e4dbe7d43ed62f5e60651", |                 "sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8", | ||||||
|                 "sha256:c12869cfd83c33d579b17b3cb28a2ae7322a53c3ce85580c2a2ebe4e3f56c4fb" |                 "sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "version": "==3.10.3" | ||||||
|             "version": "==3.9.4" |  | ||||||
|         }, |         }, | ||||||
|         "drf-yasg": { |         "drf-yasg": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:68fded2ffdf46e03f33e766184b7d8f1e1a5236f94acfd0c4ba932a57b812566", |                 "sha256:4cfec631880ae527a91ec7cd3241aea2f82189f59e2f089119aa687761afb227", | ||||||
|                 "sha256:fcef74709ead2b365410be3d12afbfd0a6e49d1efe615a15a929da7e950bb83c" |                 "sha256:504cce09035cf1bace63b84d9d778b772f86bb37d8a71ed6f723346362e633b2" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==1.16.1" |             "version": "==1.17.0" | ||||||
|         }, |         }, | ||||||
|         "eight": { |         "eight": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -346,13 +268,6 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==0.16.0" |             "version": "==0.16.0" | ||||||
|         }, |         }, | ||||||
|         "hyperlink": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:4288e34705da077fada1111a24a0aa08bb1e76699c9ce49876af722441845654", |  | ||||||
|                 "sha256:ab4a308feb039b04f855a020a6eda3b18ca5a68e6d8f8c899cbe9e653721d04f" |  | ||||||
|             ], |  | ||||||
|             "version": "==19.0.0" |  | ||||||
|         }, |  | ||||||
|         "idna": { |         "idna": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", |                 "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", | ||||||
| @ -360,20 +275,6 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==2.8" |             "version": "==2.8" | ||||||
|         }, |         }, | ||||||
|         "importlib-metadata": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:0c505102757e7fa28b9f0958d8bc81301159dea16e2649858c92edc158b78a83", |  | ||||||
|                 "sha256:9a9f75ce32e78170905888acbf2376a81d3f21ecb3bb4867050413411d3ca7a9" |  | ||||||
|             ], |  | ||||||
|             "version": "==0.21" |  | ||||||
|         }, |  | ||||||
|         "incremental": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:717e12246dddf231a349175f48d74d93e2897244939173b01974ab6661406b9f", |  | ||||||
|                 "sha256:7b751696aaf36eebfab537e458929e194460051ccad279c72b755a167eebd4b3" |  | ||||||
|             ], |  | ||||||
|             "version": "==17.5.0" |  | ||||||
|         }, |  | ||||||
|         "inflection": { |         "inflection": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca" |                 "sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca" | ||||||
| @ -395,17 +296,18 @@ | |||||||
|         }, |         }, | ||||||
|         "jinja2": { |         "jinja2": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", |                 "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", | ||||||
|                 "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" |                 "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" | ||||||
|             ], |             ], | ||||||
|             "version": "==2.10.1" |             "version": "==2.10.3" | ||||||
|         }, |         }, | ||||||
|         "kombu": { |         "kombu": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:55274dc75eb3c3994538b0973a0fadddb236b698a4bc135b8aa4981e0a710b8f", |                 "sha256:389ba09e03b15b55b1a7371a441c894fd8121d174f5583bbbca032b9ea8c9edd", | ||||||
|                 "sha256:e5f0312dfb9011bebbf528ccaf118a6c2b5c3b8244451f08381fb23e7715809b" |                 "sha256:7b92303af381ef02fad6899fd5f5a9a96031d781356cd8e505fa54ae5ddee181" | ||||||
|             ], |             ], | ||||||
|             "version": "==4.6.4" |             "index": "pypi", | ||||||
|  |             "version": "==4.5.0" | ||||||
|         }, |         }, | ||||||
|         "ldap3": { |         "ldap3": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -501,11 +403,11 @@ | |||||||
|         }, |         }, | ||||||
|         "packaging": { |         "packaging": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", |                 "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", | ||||||
|                 "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" |                 "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==19.1" |             "version": "==19.2" | ||||||
|         }, |         }, | ||||||
|         "portend": { |         "portend": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -514,19 +416,36 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==2.5" |             "version": "==2.5" | ||||||
|         }, |         }, | ||||||
|         "psycopg2": { |         "psycopg2-binary": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:128d0fa910ada0157bba1cb74a9c5f92bb8a1dca77cf91a31eb274d1f889e001", |                 "sha256:080c72714784989474f97be9ab0ddf7b2ad2984527e77f2909fcd04d4df53809", | ||||||
|                 "sha256:227fd46cf9b7255f07687e5bde454d7d67ae39ca77e170097cdef8ebfc30c323", |                 "sha256:110457be80b63ff4915febb06faa7be002b93a76e5ba19bf3f27636a2ef58598", | ||||||
|                 "sha256:2315e7f104681d498ccf6fd70b0dba5bce65d60ac92171492bfe228e21dcc242", |                 "sha256:171352a03b22fc099f15103959b52ee77d9a27e028895d7e5fde127aa8e3bac5", | ||||||
|                 "sha256:4b5417dcd2999db0f5a891d54717cfaee33acc64f4772c4bc574d4ff95ed9d80", |                 "sha256:19d013e7b0817087517a4b3cab39c084d78898369e5c46258aab7be4f233d6a1", | ||||||
|                 "sha256:640113ddc943522aaf71294e3f2d24013b0edd659b7820621492c9ebd3a2fb0b", |                 "sha256:249b6b21ae4eb0f7b8423b330aa80fab5f821b9ffc3f7561a5e2fd6bb142cf5d", | ||||||
|                 "sha256:897a6e838319b4bf648a574afb6cabcb17d0488f8c7195100d48d872419f4457", |                 "sha256:2ac0731d2d84b05c7bb39e85b7e123c3a0acd4cda631d8d542802c88deb9e87e", | ||||||
|                 "sha256:8dceca81409898c870e011c71179454962dec152a1a6b86a347f4be74b16d864", |                 "sha256:2b6d561193f0dc3f50acfb22dd52ea8c8dfbc64bcafe3938b5f209cc17cb6f00", | ||||||
|                 "sha256:b1b8e41da09a0c3ef0b3d4bb72da0dde2abebe583c1e8462973233fd5ad0235f", |                 "sha256:2bd23e242e954214944481124755cbefe7c2cf563b1a54cd8d196d502f2578bf", | ||||||
|                 "sha256:cb407fccc12fc29dc331f2b934913405fa49b9b75af4f3a72d0f50f57ad2ca23", |                 "sha256:3e1239242ca60b3725e65ab2f13765fc199b03af9eaf1b5572f0e97bdcee5b43", | ||||||
|                 "sha256:d3a27550a8185e53b244ad7e79e307594b92fede8617d80200a8cce1fba2c60f", |                 "sha256:3eb70bb697abbe86b1d2b1316370c02ba320bfd1e9e35cf3b9566a855ea8e4e5", | ||||||
|                 "sha256:f0e6b697a975d9d3ccd04135316c947dd82d841067c7800ccf622a8717e98df1" |                 "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" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==2.8.3" |             "version": "==2.8.3" | ||||||
| @ -618,13 +537,6 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==3.9.0" |             "version": "==3.9.0" | ||||||
|         }, |         }, | ||||||
|         "pyhamcrest": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:6b672c02fdf7470df9674ab82263841ce8333fb143f32f021f6cb26f0e512420", |  | ||||||
|                 "sha256:8ffaa0a53da57e89de14ced7185ac746227a8894dbd5a3c718bf05ddbd1d56cd" |  | ||||||
|             ], |  | ||||||
|             "version": "==1.9.0" |  | ||||||
|         }, |  | ||||||
|         "pyjwkest": { |         "pyjwkest": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:5560fd5ba08655f29ff6ad1df1e15dc05abc9d976fcbcec8d2b5167f49b70222" |                 "sha256:5560fd5ba08655f29ff6ad1df1e15dc05abc9d976fcbcec8d2b5167f49b70222" | ||||||
| @ -647,10 +559,10 @@ | |||||||
|         }, |         }, | ||||||
|         "pytz": { |         "pytz": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", |                 "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", | ||||||
|                 "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" |                 "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" | ||||||
|             ], |             ], | ||||||
|             "version": "==2019.2" |             "version": "==2019.3" | ||||||
|         }, |         }, | ||||||
|         "pyyaml": { |         "pyyaml": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -710,35 +622,35 @@ | |||||||
|         }, |         }, | ||||||
|         "ruamel.yaml.clib": { |         "ruamel.yaml.clib": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:0bbe19d3e099f8ba384e1846e6b54f245f58aeec8700edbbf9abb87afa54fd82", |                 "sha256:1e77424825caba5553bbade750cec2277ef130647d685c2b38f68bc03453bac6", | ||||||
|                 "sha256:2f38024592613f3a8772bbc2904be027d9abf463518ba145f2d0c8e6da27009f", |                 "sha256:392b7c371312abf27fb549ec2d5e0092f7ef6e6c9f767bfb13e83cb903aca0fd", | ||||||
|                 "sha256:44449b3764a3f75815eea8ae5930b98e8326be64a90b0f782747318f861abfe0", |                 "sha256:4d55386129291b96483edcb93b381470f7cd69f97585829b048a3d758d31210a", | ||||||
|                 "sha256:5710be9a357801c31c1eaa37b9bc92d38176d785af5b2f0c9751385c5dc9659a", |                 "sha256:550168c02d8de52ee58c3d8a8193d5a8a9491a5e7b2462d27ac5bf63717574c9", | ||||||
|                 "sha256:5a089acb6833ed5f412e24cbe3e665683064c1429824d2819137b5ade54435c3", |                 "sha256:57933a6986a3036257ad7bf283529e7c19c2810ff24c86f4a0cfeb49d2099919", | ||||||
|                 "sha256:6143386ddd61599ea081c012a69a16e5bdd7b3c6c231bd039534365a48940f30", |                 "sha256:615b0396a7fad02d1f9a0dcf9f01202bf9caefee6265198f252c865f4227fcc6", | ||||||
|                 "sha256:6726aaf851f5f9e4cbdd3e1e414bc700bdd39220e8bc386415fd41c87b1b53c2", |                 "sha256:77556a7aa190be9a2bd83b7ee075d3df5f3c5016d395613671487e79b082d784", | ||||||
|                 "sha256:68fbc3b5d94d145a391452f886ae5fca240cb7e3ab6bd66e1a721507cdaac28a", |                 "sha256:7aee724e1ff424757b5bd8f6c5bbdb033a570b2b4683b17ace4dbe61a99a657b", | ||||||
|                 "sha256:75ebddf99ba9e0b48f32b5bdcf9e5a2b84c017da9e0db7bf11995fa414aa09cd", |                 "sha256:8073c8b92b06b572e4057b583c3d01674ceaf32167801fe545a087d7a1e8bf52", | ||||||
|                 "sha256:79948a6712baa686773a43906728e20932c923f7b2a91be7347993be2d745e55", |                 "sha256:9c6d040d0396c28d3eaaa6cb20152cb3b2f15adf35a0304f4f40a3cf9f1d2448", | ||||||
|                 "sha256:8a2dd8e8b08d369558cade05731172c4b5e2f4c5097762c6b352bd28fd9f9dc4", |                 "sha256:a0ff786d2a7dbe55f9544b3f6ebbcc495d7e730df92a08434604f6f470b899c5", | ||||||
|                 "sha256:c747acdb5e8c242ab2280df6f0c239e62838af4bee647031d96b3db2f9cefc04", |                 "sha256:b1b7fcee6aedcdc7e62c3a73f238b3d080c7ba6650cd808bce8d7761ec484070", | ||||||
|                 "sha256:cadc8eecd27414dca30366b2535cb5e3f3b47b4e2d6be7a0b13e4e52e459ff9f", |                 "sha256:b66832ea8077d9b3f6e311c4a53d06273db5dc2db6e8a908550f3c14d67e718c", | ||||||
|                 "sha256:cee86ecc893a6a8ecaa7c6a9c2d06f75f614176210d78a5f155f8e78d6989509", |                 "sha256:d0d3ac228c9bbab08134b4004d748cf9f8743504875b3603b3afbb97e3472947", | ||||||
|                 "sha256:e59af39e895aff28ee5f55515983cab3466d1a029c91c04db29da1c0f09cf333", |                 "sha256:d10e9dd744cf85c219bf747c75194b624cc7a94f0c80ead624b06bfa9f61d3bc", | ||||||
|                 "sha256:eee7ecd2eee648884fae6c51ae50c814acdcc5d6340dc96c970158aebcd25ac6", |                 "sha256:ea4362548ee0cbc266949d8a441238d9ad3600ca9910c3fe4e82ee3a50706973", | ||||||
|                 "sha256:ef8d4522d231cb9b29f6cdd0edc8faac9d9715c60dc7becbd6eb82c915a98e5b", |                 "sha256:ed5b3698a2bb241b7f5cbbe277eaa7fe48b07a58784fba4f75224fd066d253ad", | ||||||
|                 "sha256:f504d45230cc9abf2810623b924ae048b224a90adb01f97db4e766cfdda8e6eb" |                 "sha256:f9dcc1ae73f36e8059589b601e8e4776b9976effd76c21ad6a855a74318efd6e" | ||||||
|             ], |             ], | ||||||
|             "markers": "platform_python_implementation == 'CPython' and python_version < '3.8'", |             "markers": "platform_python_implementation == 'CPython' and python_version < '3.8'", | ||||||
|             "version": "==0.1.2" |             "version": "==0.2.0" | ||||||
|         }, |         }, | ||||||
|         "sentry-sdk": { |         "sentry-sdk": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:528f936118679e9a52dacb96bfefe20acb5d63e0797856c64a582cc3c2bc1f9e", |                 "sha256:15e51e74b924180c98bcd636cb4634945b0a99a124d50b433c3a9dc6a582e8db", | ||||||
|                 "sha256:b4edcb1296fee107439345d0f8b23432b8732b7e28407f928367d0a4a36301a9" |                 "sha256:1d6a2ee908ec6d8f96c27d78bc39e203df4d586d287c233140af7d8d1aca108a" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==0.11.2" |             "version": "==0.12.3" | ||||||
|         }, |         }, | ||||||
|         "service-identity": { |         "service-identity": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -763,13 +675,6 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==1.12.0" |             "version": "==1.12.0" | ||||||
|         }, |         }, | ||||||
|         "soupsieve": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:8662843366b8d8779dec4e2f921bebec9afd856a5ff2e82cd419acc5054a1a92", |  | ||||||
|                 "sha256:a5a6166b4767725fd52ae55fee8c8b6137d9a51e9f1edea461a062a759160118" |  | ||||||
|             ], |  | ||||||
|             "version": "==1.9.3" |  | ||||||
|         }, |  | ||||||
|         "sqlparse": { |         "sqlparse": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", |                 "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", | ||||||
| @ -792,37 +697,6 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==1.14.1" |             "version": "==1.14.1" | ||||||
|         }, |         }, | ||||||
|         "twisted": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:02214ef6f125804969aedd55daccea57060b98dae6a2aa0a4cb60c4d0acb8a2c", |  | ||||||
|                 "sha256:15b51047ab116ee61d791cf9fe6f037f35e909a6d344ccb437d1691627c4d8a1", |  | ||||||
|                 "sha256:17704d98d58c9c52d97e88570732e4c094a93fe5df937d01b759bab593345eec", |  | ||||||
|                 "sha256:222e0cfd60b0c867dd303bce6355a3ffac46574079dff11ae7a1775235ad12c8", |  | ||||||
|                 "sha256:23090c9fcec01ce4e102912a39eb4645b2bf916abe459804f87853d977ced6e3", |  | ||||||
|                 "sha256:5102fc2bf0d870c1e217aa09ed7a48b633cc579950a31ecae9cecc556ebffdf2", |  | ||||||
|                 "sha256:6bc71d5a2320576a3ac7f2dac7802c290fcf9f1972c59f9ef5c5b85b8bac1e1e", |  | ||||||
|                 "sha256:6c7703b62de08fd5873d60e6ed30478cdb39e3a37b1ead3a5d2fed10deb6e112", |  | ||||||
|                 "sha256:6ca398abd58730070e9bc34e8a01d1198438b2ff130e95492090a2fec5fb683b", |  | ||||||
|                 "sha256:98840f28c44894f44dc597747b4cddc740197dc6f6f18ba4dd810422094e35cb", |  | ||||||
|                 "sha256:998e3baf509c7cf7973b8174c1050ac10f6a8bc1aaf0178ad6a7c422c75a0c68", |  | ||||||
|                 "sha256:a5f2de00c6630c8f5ad32fca64fc4c853536c21e9ea8d0d2ae54804ef5836b9c", |  | ||||||
|                 "sha256:aad65a24b27253eb94f2749131a872487b093c599c5873c03d90a65cc9b8a2fc", |  | ||||||
|                 "sha256:ab788465701f553f764f4442d22b850f39a6a6abd4861e70c05b4c27119c9b50", |  | ||||||
|                 "sha256:c7244e24fcb72f838be57d3e117ad7df135ff5af4c9d4c565417d671cd1e68c9", |  | ||||||
|                 "sha256:d5db93026568f60cacdc0615fcd21d46f694a6bfad0ef3ff53cde2b4bb85a39d", |  | ||||||
|                 "sha256:da92426002703b02d8fccff3acfea2d8baf76a9052e8c55ea76d0407eeaa06ce", |  | ||||||
|                 "sha256:f4f0af14d288140ecb00861a3bd1e0b94ffdc63057cc1abe8b9dc84f6b6dcf18", |  | ||||||
|                 "sha256:f985f31e3244d18610816b55becf8fbf445c8e30fe0731500cadaf19f296baf0" |  | ||||||
|             ], |  | ||||||
|             "version": "==19.7.0" |  | ||||||
|         }, |  | ||||||
|         "txaio": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:67e360ac73b12c52058219bb5f8b3ed4105d2636707a36a7cdafb56fe06db7fe", |  | ||||||
|                 "sha256:b6b235d432cc58ffe111b43e337db71a5caa5d3eaa88f0eacf60b431c7626ef5" |  | ||||||
|             ], |  | ||||||
|             "version": "==18.8.1" |  | ||||||
|         }, |  | ||||||
|         "uritemplate": { |         "uritemplate": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:01c69f4fe8ed503b2951bef85d996a9d22434d2431584b5b107b2981ff416fbd", |                 "sha256:01c69f4fe8ed503b2951bef85d996a9d22434d2431584b5b107b2981ff416fbd", | ||||||
| @ -836,11 +710,11 @@ | |||||||
|                 "secure" |                 "secure" | ||||||
|             ], |             ], | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", |                 "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", | ||||||
|                 "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" |                 "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==1.25.3" |             "version": "==1.25.6" | ||||||
|         }, |         }, | ||||||
|         "vine": { |         "vine": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -849,61 +723,12 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==1.3.0" |             "version": "==1.3.0" | ||||||
|         }, |         }, | ||||||
|         "websocket-client": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:1151d5fb3a62dc129164292e1227655e4bbc5dd5340a5165dfae61128ec50aa9", |  | ||||||
|                 "sha256:1fd5520878b68b84b5748bb30e592b10d0a91529d5383f74f4964e72b297fd3a" |  | ||||||
|             ], |  | ||||||
|             "index": "pypi", |  | ||||||
|             "version": "==0.56.0" |  | ||||||
|         }, |  | ||||||
|         "zc.lockfile": { |         "zc.lockfile": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b", |                 "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b", | ||||||
|                 "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f" |                 "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f" | ||||||
|             ], |             ], | ||||||
|             "version": "==2.0" |             "version": "==2.0" | ||||||
|         }, |  | ||||||
|         "zipp": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", |  | ||||||
|                 "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" |  | ||||||
|             ], |  | ||||||
|             "version": "==0.6.0" |  | ||||||
|         }, |  | ||||||
|         "zope.interface": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c", |  | ||||||
|                 "sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b", |  | ||||||
|                 "sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02", |  | ||||||
|                 "sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f", |  | ||||||
|                 "sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5", |  | ||||||
|                 "sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375", |  | ||||||
|                 "sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487", |  | ||||||
|                 "sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2", |  | ||||||
|                 "sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0", |  | ||||||
|                 "sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b", |  | ||||||
|                 "sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63", |  | ||||||
|                 "sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39", |  | ||||||
|                 "sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745", |  | ||||||
|                 "sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc", |  | ||||||
|                 "sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2", |  | ||||||
|                 "sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa", |  | ||||||
|                 "sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1", |  | ||||||
|                 "sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc", |  | ||||||
|                 "sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98", |  | ||||||
|                 "sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97", |  | ||||||
|                 "sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab", |  | ||||||
|                 "sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127", |  | ||||||
|                 "sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d", |  | ||||||
|                 "sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe", |  | ||||||
|                 "sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891", |  | ||||||
|                 "sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1", |  | ||||||
|                 "sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b", |  | ||||||
|                 "sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966", |  | ||||||
|                 "sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317" |  | ||||||
|             ], |  | ||||||
|             "version": "==4.6.0" |  | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     "develop": { |     "develop": { | ||||||
| @ -912,6 +737,7 @@ | |||||||
|                 "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4", |                 "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4", | ||||||
|                 "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4" |                 "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4" | ||||||
|             ], |             ], | ||||||
|  |             "index": "pypi", | ||||||
|             "version": "==2.2.5" |             "version": "==2.2.5" | ||||||
|         }, |         }, | ||||||
|         "autopep8": { |         "autopep8": { | ||||||
| @ -929,13 +755,6 @@ | |||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==1.6.2" |             "version": "==1.6.2" | ||||||
|         }, |         }, | ||||||
|         "bleach": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", |  | ||||||
|                 "sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa" |  | ||||||
|             ], |  | ||||||
|             "version": "==3.1.0" |  | ||||||
|         }, |  | ||||||
|         "bumpversion": { |         "bumpversion": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e", |                 "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e", | ||||||
| @ -944,20 +763,6 @@ | |||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==0.5.3" |             "version": "==0.5.3" | ||||||
|         }, |         }, | ||||||
|         "certifi": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", |  | ||||||
|                 "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" |  | ||||||
|             ], |  | ||||||
|             "version": "==2019.6.16" |  | ||||||
|         }, |  | ||||||
|         "chardet": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", |  | ||||||
|                 "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" |  | ||||||
|             ], |  | ||||||
|             "version": "==3.0.4" |  | ||||||
|         }, |  | ||||||
|         "colorama": { |         "colorama": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", |                 "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", | ||||||
| @ -1006,11 +811,11 @@ | |||||||
|         }, |         }, | ||||||
|         "django": { |         "django": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:148a4a2d1a85b23883b0a4e99ab7718f518a83675e4485e44dc0c1d36988c5fa", |                 "sha256:4025317ca01f75fc79250ff7262a06d8ba97cd4f82e93394b2a0a6a4a925caeb", | ||||||
|                 "sha256:deb70aa038e59b58593673b15e9a711d1e5ccd941b5973b30750d5d026abfd56" |                 "sha256:a8ca1033acac9f33995eb2209a6bf18a4681c3e5269a878e9a7e0b7384ed1ca3" | ||||||
|             ], |             ], | ||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==2.2.5" |             "version": "==2.2.6" | ||||||
|         }, |         }, | ||||||
|         "django-debug-toolbar": { |         "django-debug-toolbar": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -1020,14 +825,6 @@ | |||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==2.0" |             "version": "==2.0" | ||||||
|         }, |         }, | ||||||
|         "docutils": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", |  | ||||||
|                 "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", |  | ||||||
|                 "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" |  | ||||||
|             ], |  | ||||||
|             "version": "==0.15.2" |  | ||||||
|         }, |  | ||||||
|         "dodgy": { |         "dodgy": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:65e13cf878d7aff129f1461c13cb5fd1bb6dfe66bb5327e09379c3877763280c" |                 "sha256:65e13cf878d7aff129f1461c13cb5fd1bb6dfe66bb5327e09379c3877763280c" | ||||||
| @ -1036,24 +833,17 @@ | |||||||
|         }, |         }, | ||||||
|         "gitdb2": { |         "gitdb2": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:83361131a1836661a155172932a13c08bda2db3674e4caa32368aa6eb02f38c2", |                 "sha256:1b6df1433567a51a4a9c1a5a0de977aa351a405cc56d7d35f3388bad1f630350", | ||||||
|                 "sha256:e3a0141c5f2a3f635c7209d56c496ebe1ad35da82fe4d3ec4aaa36278d70648a" |                 "sha256:96bbb507d765a7f51eb802554a9cfe194a174582f772e0d89f4e87288c288b7b" | ||||||
|             ], |             ], | ||||||
|             "version": "==2.0.5" |             "version": "==2.0.6" | ||||||
|         }, |         }, | ||||||
|         "gitpython": { |         "gitpython": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:947cc75913e7b6da108458136607e2ee0e40c20be1e12d4284e7c6c12956c276", |                 "sha256:631263cc670aa56ce3d3c414cf0fe2e840f2e913514b138ea28d88a477bbcd21", | ||||||
|                 "sha256:d2f4945f8260f6981d724f5957bc076398ada55cb5d25aaee10108bcdc894100" |                 "sha256:6e97b9f0954807f30c2dd8e3165731ed6c477a1b365f194b69d81d7940a08332" | ||||||
|             ], |             ], | ||||||
|             "version": "==3.0.2" |             "version": "==3.0.3" | ||||||
|         }, |  | ||||||
|         "idna": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", |  | ||||||
|                 "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" |  | ||||||
|             ], |  | ||||||
|             "version": "==2.8" |  | ||||||
|         }, |         }, | ||||||
|         "isort": { |         "isort": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -1107,13 +897,6 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==0.4.1" |             "version": "==0.4.1" | ||||||
|         }, |         }, | ||||||
|         "pkginfo": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb", |  | ||||||
|                 "sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32" |  | ||||||
|             ], |  | ||||||
|             "version": "==1.5.0.1" |  | ||||||
|         }, |  | ||||||
|         "prospector": { |         "prospector": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:aba551e53dc1a5a432afa67385eaa81d7b4cf4c162dc1a4d0ee00b3a0712ad90" |                 "sha256:aba551e53dc1a5a432afa67385eaa81d7b4cf4c162dc1a4d0ee00b3a0712ad90" | ||||||
| @ -1142,13 +925,6 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==1.6.0" |             "version": "==1.6.0" | ||||||
|         }, |         }, | ||||||
|         "pygments": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", |  | ||||||
|                 "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" |  | ||||||
|             ], |  | ||||||
|             "version": "==2.4.2" |  | ||||||
|         }, |  | ||||||
|         "pylint": { |         "pylint": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09", |                 "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09", | ||||||
| @ -1179,16 +955,17 @@ | |||||||
|         }, |         }, | ||||||
|         "pylint-plugin-utils": { |         "pylint-plugin-utils": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:8d9e31d5ea8b7b0003e1f0f136b44a5235896a32e47c5bc2ef1143e9f6ba0b74" |                 "sha256:2f30510e1c46edf268d3a195b2849bd98a1b9433229bb2ba63b8d776e1fc4d0a", | ||||||
|  |                 "sha256:57625dcca20140f43731311cd8fd879318bf45a8b0fd17020717a8781714a25a" | ||||||
|             ], |             ], | ||||||
|             "version": "==0.5" |             "version": "==0.6" | ||||||
|         }, |         }, | ||||||
|         "pytz": { |         "pytz": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", |                 "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", | ||||||
|                 "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" |                 "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" | ||||||
|             ], |             ], | ||||||
|             "version": "==2019.2" |             "version": "==2019.3" | ||||||
|         }, |         }, | ||||||
|         "pyyaml": { |         "pyyaml": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -1209,27 +986,6 @@ | |||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==5.1.2" |             "version": "==5.1.2" | ||||||
|         }, |         }, | ||||||
|         "readme-renderer": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f", |  | ||||||
|                 "sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d" |  | ||||||
|             ], |  | ||||||
|             "version": "==24.0" |  | ||||||
|         }, |  | ||||||
|         "requests": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", |  | ||||||
|                 "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" |  | ||||||
|             ], |  | ||||||
|             "version": "==2.22.0" |  | ||||||
|         }, |  | ||||||
|         "requests-toolbelt": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", |  | ||||||
|                 "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0" |  | ||||||
|             ], |  | ||||||
|             "version": "==0.9.1" |  | ||||||
|         }, |  | ||||||
|         "requirements-detector": { |         "requirements-detector": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:9fbc4b24e8b7c3663aff32e3eba34596848c6b91bd425079b386973bd8d08931" |                 "sha256:9fbc4b24e8b7c3663aff32e3eba34596848c6b91bd425079b386973bd8d08931" | ||||||
| @ -1258,9 +1014,10 @@ | |||||||
|         }, |         }, | ||||||
|         "snowballstemmer": { |         "snowballstemmer": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:713e53b79cbcf97bc5245a06080a33d54a77e7cce2f789c835a143bcdb5c033e" |                 "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", | ||||||
|  |                 "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" | ||||||
|             ], |             ], | ||||||
|             "version": "==1.9.1" |             "version": "==2.0.0" | ||||||
|         }, |         }, | ||||||
|         "sqlparse": { |         "sqlparse": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
| @ -1276,21 +1033,6 @@ | |||||||
|             ], |             ], | ||||||
|             "version": "==1.31.0" |             "version": "==1.31.0" | ||||||
|         }, |         }, | ||||||
|         "tqdm": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:1be3e4e3198f2d0e47b928e9d9a8ec1b63525db29095cec1467f4c5a4ea8ebf9", |  | ||||||
|                 "sha256:7e39a30e3d34a7a6539378e39d7490326253b7ee354878a92255656dc4284457" |  | ||||||
|             ], |  | ||||||
|             "version": "==4.35.0" |  | ||||||
|         }, |  | ||||||
|         "twine": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:b2cec0dc1ac55bd74280d257f43763cf0cf928bdcd0de0fd70be70aa1195e3b0", |  | ||||||
|                 "sha256:e37d5a73d77b095b85314dde807bfb85b580b5b9d137f5b21332f4636990d97a" |  | ||||||
|             ], |  | ||||||
|             "index": "pypi", |  | ||||||
|             "version": "==1.14.0" |  | ||||||
|         }, |  | ||||||
|         "typed-ast": { |         "typed-ast": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", |                 "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", | ||||||
| @ -1320,24 +1062,6 @@ | |||||||
|             "index": "pypi", |             "index": "pypi", | ||||||
|             "version": "==2.5.1" |             "version": "==2.5.1" | ||||||
|         }, |         }, | ||||||
|         "urllib3": { |  | ||||||
|             "extras": [ |  | ||||||
|                 "secure" |  | ||||||
|             ], |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", |  | ||||||
|                 "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" |  | ||||||
|             ], |  | ||||||
|             "index": "pypi", |  | ||||||
|             "version": "==1.25.3" |  | ||||||
|         }, |  | ||||||
|         "webencodings": { |  | ||||||
|             "hashes": [ |  | ||||||
|                 "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", |  | ||||||
|                 "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" |  | ||||||
|             ], |  | ||||||
|             "version": "==0.5.1" |  | ||||||
|         }, |  | ||||||
|         "wrapt": { |         "wrapt": { | ||||||
|             "hashes": [ |             "hashes": [ | ||||||
|                 "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" |                 "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | # passbook | ||||||
|  |  | ||||||
|  | ## Quick instance | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | export PASSBOOK_DOMAIN=domain.tld | ||||||
|  | docker-compose pull | ||||||
|  | docker-compose up -d | ||||||
|  | docker-compose exec server ./manage.py migrate | ||||||
|  | docker-compose exec server ./manage.py createsuperuser | ||||||
|  | ``` | ||||||
| @ -1,15 +1,18 @@ | |||||||
| FROM python:3.7-alpine | FROM python:3.7-slim-stretch | ||||||
|  |  | ||||||
| COPY ./Pipfile /app/ | COPY ./Pipfile /app/ | ||||||
| COPY ./Pipfile.lock /app/ | COPY ./Pipfile.lock /app/ | ||||||
|  |  | ||||||
| WORKDIR /app/ | WORKDIR /app/ | ||||||
|  |  | ||||||
| RUN apk update && \ | RUN apt-get update && \ | ||||||
|     apk add --no-cache openssl-dev build-base libxml2-dev libxslt-dev libffi-dev gcc musl-dev libgcc zlib-dev postgresql-dev && \ |     apt-get install -y --no-install-recommends build-essential && \ | ||||||
|     pip install pipenv --no-cache-dir && \ |     pip install pipenv uwsgi --no-cache-dir && \ | ||||||
|     pipenv lock -r > requirements.txt && \ |     apt-get remove -y --purge build-essential && \ | ||||||
|  |     apt-get autoremove -y --purge && \ | ||||||
|  |     rm -rf /var/lib/apt/lists/* | ||||||
|  |  | ||||||
|  | RUN pipenv lock -r > requirements.txt && \ | ||||||
|     pipenv --rm && \ |     pipenv --rm && \ | ||||||
|     pip install -r requirements.txt  --no-cache-dir && \ |     pip install -r requirements.txt  --no-cache-dir && \ | ||||||
|     adduser -S passbook && \ |     adduser --system --no-create-home --uid 1000 --group --home /app passbook | ||||||
|     chown -R passbook /app |  | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| FROM docker.beryju.org/passbook/base:latest | FROM docker.beryju.org/passbook/base:latest | ||||||
|  |  | ||||||
| RUN pipenv lock --dev -r > requirements-dev.txt && \ | RUN pipenv lock --dev -r > requirements-dev.txt && \ | ||||||
|  |     pipenv --rm && \ | ||||||
|     pip install -r /app/requirements-dev.txt  --no-cache-dir |     pip install -r /app/requirements-dev.txt  --no-cache-dir | ||||||
|  | |||||||
							
								
								
									
										89
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | |||||||
|  | --- | ||||||
|  | version: '3.2' | ||||||
|  |  | ||||||
|  | services: | ||||||
|  |   postgresql: | ||||||
|  |     image: postgres | ||||||
|  |     volumes: | ||||||
|  |       - database:/var/lib/postgresql/data | ||||||
|  |     networks: | ||||||
|  |       - internal | ||||||
|  |     environment: | ||||||
|  |       - POSTGRES_PASSWORD=${PG_PASS:-thisisnotagoodpassword} | ||||||
|  |       - POSTGRES_USER=passbook | ||||||
|  |       - POSTGRES_DB=passbook | ||||||
|  |     labels: | ||||||
|  |       - traefik.enable=false | ||||||
|  |   redis: | ||||||
|  |     image: redis | ||||||
|  |     networks: | ||||||
|  |       - internal | ||||||
|  |     labels: | ||||||
|  |       - traefik.enable=false | ||||||
|  |   server: | ||||||
|  |     build: | ||||||
|  |       context: . | ||||||
|  |     image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest} | ||||||
|  |     command: | ||||||
|  |       - uwsgi | ||||||
|  |       - uwsgi.ini | ||||||
|  |     environment: | ||||||
|  |       - PASSBOOK_DOMAIN=${PASSBOOK_DOMAIN} | ||||||
|  |       - PASSBOOK_REDIS__HOST=redis | ||||||
|  |       - PASSBOOK_POSTGRESQL__HOST=postgresql | ||||||
|  |       - PASSBOOK_POSTGRESQL__PASSWORD=${PG_PASS:-thisisnotagoodpassword} | ||||||
|  |     ports: | ||||||
|  |       - 8000 | ||||||
|  |     networks: | ||||||
|  |       - internal | ||||||
|  |     labels: | ||||||
|  |       - traefik.port=8000 | ||||||
|  |       - traefik.docker.network=internal | ||||||
|  |       - traefik.frontend.rule=PathPrefix:/ | ||||||
|  |   worker: | ||||||
|  |     image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest} | ||||||
|  |     command: | ||||||
|  |       - celery | ||||||
|  |       - worker | ||||||
|  |       - --autoscale=10,3 | ||||||
|  |       - -E | ||||||
|  |       - -B | ||||||
|  |       - -A=passbook.root.celery | ||||||
|  |     networks: | ||||||
|  |       - internal | ||||||
|  |     labels: | ||||||
|  |       - traefik.enable=false | ||||||
|  |     environment: | ||||||
|  |       - PASSBOOK_DOMAIN=${PASSBOOK_DOMAIN} | ||||||
|  |       - PASSBOOK_REDIS__HOST=redis | ||||||
|  |       - 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.docker.network=internal | ||||||
|  |   traefik: | ||||||
|  |     image: traefik:1.7 | ||||||
|  |     command: --api --docker | ||||||
|  |     volumes: | ||||||
|  |       - /var/run/docker.sock:/var/run/docker.sock:ro | ||||||
|  |     ports: | ||||||
|  |       - "0.0.0.0:80:80" | ||||||
|  |       - "0.0.0.0:443:443" | ||||||
|  |       - "0.0.0.0:8080:8080" | ||||||
|  |     networks: | ||||||
|  |       - internal | ||||||
|  |  | ||||||
|  | volumes: | ||||||
|  |   database: | ||||||
|  |     driver: local | ||||||
|  |  | ||||||
|  | networks: | ||||||
|  |   internal: {} | ||||||
| @ -39,9 +39,9 @@ http { | |||||||
|         gzip on; |         gzip on; | ||||||
|         gzip_types application/javascript image/* text/css; |         gzip_types application/javascript image/* text/css; | ||||||
|         gunzip on; |         gunzip on; | ||||||
|         add_header X-passbook-Version 0.3.0-beta; |         add_header X-passbook-Version 0.6.4-beta; | ||||||
|         add_header Vary X-passbook-Version; |         add_header Vary X-passbook-Version; | ||||||
|         root /static/; |         root /data/; | ||||||
| 
 | 
 | ||||||
|         location /_/healthz { |         location /_/healthz { | ||||||
|             return 204; |             return 204; | ||||||
							
								
								
									
										10
									
								
								docker/uwsgi.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								docker/uwsgi.ini
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | [uwsgi] | ||||||
|  | http = 0.0.0.0:8000 | ||||||
|  | chdir = /app | ||||||
|  | wsgi-file = passbook/root/wsgi.py | ||||||
|  | processes = 2 | ||||||
|  | master = true | ||||||
|  | threads = 2 | ||||||
|  | enable-threads = true | ||||||
|  | uid = passbook | ||||||
|  | gid = passbook | ||||||
| @ -1,6 +1,6 @@ | |||||||
| apiVersion: v1 | apiVersion: v1 | ||||||
| appVersion: "0.3.0-beta" | appVersion: "0.6.4-beta" | ||||||
| description: A Helm chart for passbook. | description: A Helm chart for passbook. | ||||||
| name: passbook | name: passbook | ||||||
| version: "0.3.0-beta" | version: "0.6.4-beta" | ||||||
| icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png | icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								helm/passbook/charts/postgresql-4.2.2.tgz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								helm/passbook/charts/postgresql-4.2.2.tgz
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -1,12 +1,9 @@ | |||||||
| dependencies: | dependencies: | ||||||
| - name: rabbitmq |  | ||||||
|   repository: https://kubernetes-charts.storage.googleapis.com/ |  | ||||||
|   version: 4.3.2 |  | ||||||
| - name: postgresql | - name: postgresql | ||||||
|   repository: https://kubernetes-charts.storage.googleapis.com/ |   repository: https://kubernetes-charts.storage.googleapis.com/ | ||||||
|   version: 3.10.1 |   version: 4.2.2 | ||||||
| - name: redis | - name: redis | ||||||
|   repository: https://kubernetes-charts.storage.googleapis.com/ |   repository: https://kubernetes-charts.storage.googleapis.com/ | ||||||
|   version: 5.1.0 |   version: 9.2.1 | ||||||
| digest: sha256:8bf68bc928a2e3c0f05139635be05fa0840554c7bde4cecd624fac78fb5fa5a3 | digest: sha256:8782e974a1094eaeecf1d68f093ca4fb84977217b2bd38b09790a05ec289aec2 | ||||||
| generated: 2019-03-21T11:06:51.553379+01:00 | generated: "2019-10-02T21:03:25.90491153Z" | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| dependencies: | dependencies: | ||||||
| - name: postgresql | - name: postgresql | ||||||
|   version: 6.3.10 |   version: 4.2.2 | ||||||
|   repository: https://kubernetes-charts.storage.googleapis.com/ |   repository: https://kubernetes-charts.storage.googleapis.com/ | ||||||
| - name: redis | - name: redis | ||||||
|   version: 9.2.1 |   version: 9.2.1 | ||||||
|  | |||||||
| @ -1,62 +0,0 @@ | |||||||
| apiVersion: apps/v1beta2 |  | ||||||
| kind: Deployment |  | ||||||
| metadata: |  | ||||||
|   name: {{ include "passbook.fullname" . }}-appgw |  | ||||||
|   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: |  | ||||||
|   replicas: {{ .Values.replicaCount }} |  | ||||||
|   selector: |  | ||||||
|     matchLabels: |  | ||||||
|       app.kubernetes.io/name: {{ include "passbook.name" . }} |  | ||||||
|       app.kubernetes.io/instance: {{ .Release.Name }} |  | ||||||
|   template: |  | ||||||
|     metadata: |  | ||||||
|       labels: |  | ||||||
|         app.kubernetes.io/name: {{ include "passbook.name" . }} |  | ||||||
|         app.kubernetes.io/instance: {{ .Release.Name }} |  | ||||||
|         passbook.io/component: appgw |  | ||||||
|     spec: |  | ||||||
|       volumes: |  | ||||||
|         - name: config-volume |  | ||||||
|           configMap: |  | ||||||
|             name: {{ include "passbook.fullname" . }}-config |  | ||||||
|       containers: |  | ||||||
|         - name: {{ .Chart.Name }} |  | ||||||
|           image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}" |  | ||||||
|           imagePullPolicy: IfNotPresent |  | ||||||
|           command: |  | ||||||
|             - ./manage.py |  | ||||||
|           args: |  | ||||||
|             - app_gw_web |  | ||||||
|           ports: |  | ||||||
|             - name: http |  | ||||||
|               containerPort: 8000 |  | ||||||
|               protocol: TCP |  | ||||||
|           volumeMounts: |  | ||||||
|             - mountPath: /etc/passbook |  | ||||||
|               name: config-volume |  | ||||||
|           livenessProbe: |  | ||||||
|             httpGet: |  | ||||||
|               path: / |  | ||||||
|               port: http |  | ||||||
|               httpHeaders: |  | ||||||
|                 - name: Host |  | ||||||
|                   value: kubernetes-healthcheck-host |  | ||||||
|           readinessProbe: |  | ||||||
|             httpGet: |  | ||||||
|               path: / |  | ||||||
|               port: http |  | ||||||
|               httpHeaders: |  | ||||||
|                 - name: Host |  | ||||||
|                   value: kubernetes-healthcheck-host |  | ||||||
|           resources: |  | ||||||
|             requests: |  | ||||||
|               cpu: 150m |  | ||||||
|               memory: 300M |  | ||||||
|             limits: |  | ||||||
|               cpu: 500m |  | ||||||
|               memory: 500M |  | ||||||
| @ -1,20 +0,0 @@ | |||||||
| apiVersion: v1 |  | ||||||
| kind: Service |  | ||||||
| metadata: |  | ||||||
|   name: {{ include "passbook.fullname" . }}-appgw |  | ||||||
|   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: |  | ||||||
|   type: {{ .Values.service.type }} |  | ||||||
|   ports: |  | ||||||
|     - port: {{ .Values.service.port }} |  | ||||||
|       targetPort: http |  | ||||||
|       protocol: TCP |  | ||||||
|       name: http |  | ||||||
|   selector: |  | ||||||
|     app.kubernetes.io/name: {{ include "passbook.name" . }} |  | ||||||
|     app.kubernetes.io/instance: {{ .Release.Name }} |  | ||||||
|     passbook.io/component: appgw |  | ||||||
| @ -8,107 +8,9 @@ data: | |||||||
|       host: "{{ .Release.Name }}-postgresql" |       host: "{{ .Release.Name }}-postgresql" | ||||||
|       name: "{{ .Values.postgresql.postgresqlDatabase }}" |       name: "{{ .Values.postgresql.postgresqlDatabase }}" | ||||||
|       user: postgres |       user: postgres | ||||||
|       password: "{{ .Values.postgresql.postgresqlPassword }}" |  | ||||||
|     redis: |     redis: | ||||||
|       host: "{{ .Release.Name }}-redis-master" |       host: "{{ .Release.Name }}-redis-master" | ||||||
|       password: "{{ .Values.redis.password }}" |  | ||||||
|       cache_db: 0 |       cache_db: 0 | ||||||
|       message_queue_db: 1 |       message_queue_db: 1 | ||||||
|  |  | ||||||
|     # Error reporting, sends stacktrace to sentry.beryju.org |  | ||||||
|     error_report_enabled: {{ .Values.config.error_reporting }} |     error_report_enabled: {{ .Values.config.error_reporting }} | ||||||
|  |     domain: ".{{ .Values.ingress.hosts[0] }}" | ||||||
|     {{- if .Values.config.secret_key }} |  | ||||||
|     secret_key: {{ .Values.config.secret_key }} |  | ||||||
|     {{- else }} |  | ||||||
|     secret_key: {{ randAlphaNum 50 }} |  | ||||||
|     {{- end }} |  | ||||||
|  |  | ||||||
|     primary_domain: {{ .Values.primary_domain }} |  | ||||||
|     domains: |  | ||||||
|       {{- range .Values.ingress.hosts }} |  | ||||||
|       - {{ . | quote }} |  | ||||||
|       {{- end }} |  | ||||||
|       - kubernetes-healthcheck-host |  | ||||||
|  |  | ||||||
|     passbook: |  | ||||||
|       sign_up: |  | ||||||
|         # Enables signup, created users are stored in internal Database and created in LDAP if ldap.create_users is true |  | ||||||
|         enabled: true |  | ||||||
|       password_reset: |  | ||||||
|         # Enable password reset, passwords are reset in internal Database and in LDAP if ldap.reset_password is true |  | ||||||
|         enabled: true |  | ||||||
|         # Verification the user has to provide in order to be able to reset passwords. Can be any combination of `email`, `2fa`, `security_questions` |  | ||||||
|         verification: |  | ||||||
|           - email |  | ||||||
|       # Text used in title, on login page and multiple other places |  | ||||||
|       branding: passbook |  | ||||||
|       login: |  | ||||||
|         # Override URL used for logo |  | ||||||
|         logo_url: null |  | ||||||
|         # Override URL used for Background on Login page |  | ||||||
|         bg_url: null |  | ||||||
|         # Optionally add a subtext, placed below logo on the login page |  | ||||||
|         subtext: null |  | ||||||
|       footer: |  | ||||||
|         links: |  | ||||||
|           # Optionally add links to the footer on the login page |  | ||||||
|           #  - name: test |  | ||||||
|           #    href: https://test |  | ||||||
|       # Specify which fields can be used to authenticate. Can be any combination of `username` and `email` |  | ||||||
|       uid_fields: |  | ||||||
|         - username |  | ||||||
|         - email |  | ||||||
|       session: |  | ||||||
|         remember_age: 2592000 # 60 * 60 * 24 * 30, one month |  | ||||||
|     # Provider-specific settings |  | ||||||
|     ldap: |  | ||||||
|       # # Completely enable or disable LDAP provider |  | ||||||
|       # enabled: false |  | ||||||
|       # # AD Domain, used to generate `userPrincipalName` |  | ||||||
|       # domain: corp.contoso.com |  | ||||||
|       # # Base DN in which passbook should look for users |  | ||||||
|       # base_dn: dn=corp,dn=contoso,dn=com |  | ||||||
|       # # LDAP field which is used to set the django username |  | ||||||
|       # username_field: sAMAccountName |  | ||||||
|       # # LDAP server to connect to, can be set to `<domain_name>` |  | ||||||
|       # server: |  | ||||||
|       #   name: corp.contoso.com |  | ||||||
|       #   use_tls: false |  | ||||||
|       # # Bind credentials, used for account creation |  | ||||||
|       # bind: |  | ||||||
|       #   username: Administraotr@corp.contoso.com |  | ||||||
|       #   password: VerySecurePassword! |  | ||||||
|       # Which field from `uid_fields` maps to which LDAP Attribute |  | ||||||
|       login_field_map: |  | ||||||
|         username: sAMAccountName |  | ||||||
|         email: mail # or userPrincipalName |  | ||||||
|       user_attribute_map: |  | ||||||
|         active_directory: |  | ||||||
|           username: "%(sAMAccountName)s" |  | ||||||
|           email: "%(mail)s" |  | ||||||
|           name: "%(displayName)" |  | ||||||
|       # # Create new users in LDAP upon sign-up |  | ||||||
|       # create_users: true |  | ||||||
|       # # Reset LDAP password when user reset their password |  | ||||||
|       # reset_password: true |  | ||||||
|     oauth_client: |  | ||||||
|       # List of python packages with sources types to load. |  | ||||||
|       types: |  | ||||||
|         - passbook.oauth_client.source_types.discord |  | ||||||
|         - passbook.oauth_client.source_types.facebook |  | ||||||
|         - passbook.oauth_client.source_types.github |  | ||||||
|         - passbook.oauth_client.source_types.google |  | ||||||
|         - passbook.oauth_client.source_types.reddit |  | ||||||
|         - passbook.oauth_client.source_types.supervisr |  | ||||||
|         - passbook.oauth_client.source_types.twitter |  | ||||||
|         - passbook.oauth_client.source_types.azure_ad |  | ||||||
|     saml_idp: |  | ||||||
|       signing: true |  | ||||||
|       autosubmit: false |  | ||||||
|       issuer: passbook |  | ||||||
|       assertion_valid_for: 86400 |  | ||||||
|       # List of python packages with provider types to load. |  | ||||||
|       types: |  | ||||||
|         - passbook.saml_idp.processors.generic |  | ||||||
|         - passbook.saml_idp.processors.salesforce |  | ||||||
|  | |||||||
| @ -37,14 +37,9 @@ spec: | |||||||
|             backend: |             backend: | ||||||
|               serviceName: {{ $fullName }}-static |               serviceName: {{ $fullName }}-static | ||||||
|               servicePort: http |               servicePort: http | ||||||
|   {{- end }} |           - path: /robots.txt | ||||||
|   {{- range .Values.ingress.app_gw_hosts }} |  | ||||||
|     - host: {{ . | quote }} |  | ||||||
|       http: |  | ||||||
|         paths: |  | ||||||
|           - path: / |  | ||||||
|             backend: |             backend: | ||||||
|               serviceName: {{ $fullName }}-appgw |               serviceName: {{ $fullName }}-static | ||||||
|               servicePort: http |               servicePort: http | ||||||
|   {{- end }} |   {{- end }} | ||||||
| {{- end }} | {{- end }} | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								helm/passbook/templates/secret.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								helm/passbook/templates/secret.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | apiVersion: v1 | ||||||
|  | kind: Secret | ||||||
|  | type: Opaque | ||||||
|  | metadata: | ||||||
|  |   name: {{ include "passbook.fullname" . }}-secret-key | ||||||
|  | data: | ||||||
|  |   {{- if .Values.config.secret_key }} | ||||||
|  |   secret_key: {{ .Values.config.secret_key | b64enc | quote }} | ||||||
|  |   {{- else }} | ||||||
|  |   secret_key: {{ randAlphaNum 50 | b64enc | quote}} | ||||||
|  |   {{- end }} | ||||||
| @ -8,7 +8,7 @@ metadata: | |||||||
|     app.kubernetes.io/instance: {{ .Release.Name }} |     app.kubernetes.io/instance: {{ .Release.Name }} | ||||||
|     app.kubernetes.io/managed-by: {{ .Release.Service }} |     app.kubernetes.io/managed-by: {{ .Release.Service }} | ||||||
| spec: | spec: | ||||||
|   replicas: {{ .Values.replicaCount }} |   replicas: 2 | ||||||
|   selector: |   selector: | ||||||
|     matchLabels: |     matchLabels: | ||||||
|       app.kubernetes.io/name: {{ include "passbook.name" . }} |       app.kubernetes.io/name: {{ include "passbook.name" . }} | ||||||
| @ -34,21 +34,61 @@ spec: | |||||||
|           volumeMounts: |           volumeMounts: | ||||||
|             - mountPath: /etc/passbook |             - mountPath: /etc/passbook | ||||||
|               name: config-volume |               name: config-volume | ||||||
|  |           envFrom: | ||||||
|  |             - configMapRef: | ||||||
|  |                 name: {{ include "passbook.fullname" . }}-config | ||||||
|  |               prefix: PASSBOOK_ | ||||||
|  |           env: | ||||||
|  |             - name: PASSBOOK_SECRET_KEY | ||||||
|  |               valueFrom: | ||||||
|  |                 secretKeyRef: | ||||||
|  |                   name: {{ include "passbook.fullname" . }}-secret-key | ||||||
|  |                   key: secret_key | ||||||
|  |             - name: PASSBOOK_REDIS__PASSWORD | ||||||
|  |               valueFrom: | ||||||
|  |                 secretKeyRef: | ||||||
|  |                   name: "{{ .Release.Name }}-redis" | ||||||
|  |                   key: redis-password | ||||||
|  |             - name: PASSBOOK_POSTGRESQL__PASSWORD | ||||||
|  |               valueFrom: | ||||||
|  |                 secretKeyRef: | ||||||
|  |                   name: "{{ .Release.Name }}-postgresql" | ||||||
|  |                   key: postgresql-password | ||||||
|       containers: |       containers: | ||||||
|         - name: {{ .Chart.Name }} |         - name: {{ .Chart.Name }} | ||||||
|           image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}" |           image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}" | ||||||
|           imagePullPolicy: IfNotPresent |           imagePullPolicy: IfNotPresent | ||||||
|           command: |           command: | ||||||
|             - ./manage.py |             - uwsgi | ||||||
|           args: |           args: | ||||||
|             - web |             - uwsgi.ini | ||||||
|  |           volumeMounts: | ||||||
|  |             - mountPath: /etc/passbook | ||||||
|  |               name: config-volume | ||||||
|  |           envFrom: | ||||||
|  |             - configMapRef: | ||||||
|  |                 name: {{ include "passbook.fullname" . }}-config | ||||||
|  |               prefix: PASSBOOK_ | ||||||
|  |           env: | ||||||
|  |             - name: PASSBOOK_SECRET_KEY | ||||||
|  |               valueFrom: | ||||||
|  |                 secretKeyRef: | ||||||
|  |                   name: {{ include "passbook.fullname" . }}-secret-key | ||||||
|  |                   key: secret_key | ||||||
|  |             - name: PASSBOOK_REDIS__PASSWORD | ||||||
|  |               valueFrom: | ||||||
|  |                 secretKeyRef: | ||||||
|  |                   name: "{{ .Release.Name }}-redis" | ||||||
|  |                   key: redis-password | ||||||
|  |             - name: PASSBOOK_POSTGRESQL__PASSWORD | ||||||
|  |               valueFrom: | ||||||
|  |                 secretKeyRef: | ||||||
|  |                   name: "{{ .Release.Name }}-postgresql" | ||||||
|  |                   key: postgresql-password | ||||||
|           ports: |           ports: | ||||||
|             - name: http |             - name: http | ||||||
|               containerPort: 8000 |               containerPort: 8000 | ||||||
|               protocol: TCP |               protocol: TCP | ||||||
|           volumeMounts: |  | ||||||
|             - mountPath: /etc/passbook |  | ||||||
|               name: config-volume |  | ||||||
|           livenessProbe: |           livenessProbe: | ||||||
|             httpGet: |             httpGet: | ||||||
|               path: / |               path: / | ||||||
| @ -65,8 +105,8 @@ spec: | |||||||
|                   value: kubernetes-healthcheck-host |                   value: kubernetes-healthcheck-host | ||||||
|           resources: |           resources: | ||||||
|             requests: |             requests: | ||||||
|               cpu: 50m |               cpu: 100m | ||||||
|               memory: 150M |               memory: 200M | ||||||
|             limits: |             limits: | ||||||
|               cpu: 200m |               cpu: 300m | ||||||
|               memory: 300M |               memory: 350M | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ metadata: | |||||||
|     app.kubernetes.io/instance: {{ .Release.Name }} |     app.kubernetes.io/instance: {{ .Release.Name }} | ||||||
|     app.kubernetes.io/managed-by: {{ .Release.Service }} |     app.kubernetes.io/managed-by: {{ .Release.Service }} | ||||||
| spec: | spec: | ||||||
|   replicas: {{ .Values.replicaCount }} |   replicas: 1 | ||||||
|   selector: |   selector: | ||||||
|     matchLabels: |     matchLabels: | ||||||
|       app.kubernetes.io/name: {{ include "passbook.name" . }} |       app.kubernetes.io/name: {{ include "passbook.name" . }} | ||||||
| @ -29,16 +29,36 @@ spec: | |||||||
|           image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}" |           image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}" | ||||||
|           imagePullPolicy: IfNotPresent |           imagePullPolicy: IfNotPresent | ||||||
|           command: |           command: | ||||||
|             - ./manage.py |             - celery | ||||||
|           args: |           args: | ||||||
|             - worker |             - worker | ||||||
|           ports: |             - --autoscale=10,3 | ||||||
|             - name: http |             - -E | ||||||
|               containerPort: 8000 |             - -B | ||||||
|               protocol: TCP |             - -A=passbook.root.celery | ||||||
|           volumeMounts: |           volumeMounts: | ||||||
|             - mountPath: /etc/passbook |             - mountPath: /etc/passbook | ||||||
|               name: config-volume |               name: config-volume | ||||||
|  |           envFrom: | ||||||
|  |             - configMapRef: | ||||||
|  |                 name: {{ include "passbook.fullname" . }}-config | ||||||
|  |               prefix: PASSBOOK_ | ||||||
|  |           env: | ||||||
|  |             - name: PASSBOOK_SECRET_KEY | ||||||
|  |               valueFrom: | ||||||
|  |                 secretKeyRef: | ||||||
|  |                   name: {{ include "passbook.fullname" . }}-secret-key | ||||||
|  |                   key: secret_key | ||||||
|  |             - name: PASSBOOK_REDIS__PASSWORD | ||||||
|  |               valueFrom: | ||||||
|  |                 secretKeyRef: | ||||||
|  |                   name: "{{ .Release.Name }}-redis" | ||||||
|  |                   key: redis-password | ||||||
|  |             - name: PASSBOOK_POSTGRESQL__PASSWORD | ||||||
|  |               valueFrom: | ||||||
|  |                 secretKeyRef: | ||||||
|  |                   name: "{{ .Release.Name }}-postgresql" | ||||||
|  |                   key: postgresql-password | ||||||
|           resources: |           resources: | ||||||
|             requests: |             requests: | ||||||
|               cpu: 150m |               cpu: 150m | ||||||
|  | |||||||
| @ -1,11 +1,8 @@ | |||||||
| # Default values for passbook. | # Default values for passbook. | ||||||
| # This is a YAML-formatted file. | # This is a YAML-formatted file. | ||||||
| # Declare variables to be passed into your templates. | # Declare variables to be passed into your templates. | ||||||
|  |  | ||||||
| replicaCount: 1 |  | ||||||
|  |  | ||||||
| image: | image: | ||||||
|   tag: 0.3.0-beta |   tag: 0.6.4-beta | ||||||
|  |  | ||||||
| nameOverride: "" | nameOverride: "" | ||||||
|  |  | ||||||
| @ -19,11 +16,13 @@ config: | |||||||
|  |  | ||||||
| postgresql: | postgresql: | ||||||
|   postgresqlDatabase: passbook |   postgresqlDatabase: passbook | ||||||
|   postgresqlPassword: foo |  | ||||||
|  |  | ||||||
| rabbitmq: | redis: | ||||||
|   rabbitmq: |   cluster: | ||||||
|     password: foo |     enabled: false | ||||||
|  |   master: | ||||||
|  |     persistence: | ||||||
|  |       enabled: false | ||||||
|  |  | ||||||
| service: | service: | ||||||
|   type: ClusterIP |   type: ClusterIP | ||||||
| @ -37,28 +36,7 @@ ingress: | |||||||
|   path: / |   path: / | ||||||
|   hosts: |   hosts: | ||||||
|     - passbook.k8s.local |     - passbook.k8s.local | ||||||
|   app_gw_hosts: |  | ||||||
|     - '*.passbook.k8s.local' |  | ||||||
|   defaultHost: passbook.k8s.local |  | ||||||
|   tls: [] |   tls: [] | ||||||
|   #  - secretName: chart-example-tls |   #  - secretName: chart-example-tls | ||||||
|   #    hosts: |   #    hosts: | ||||||
|   #      - passbook.k8s.local |   #      - passbook.k8s.local | ||||||
|  |  | ||||||
| resources: {} |  | ||||||
|   # We usually recommend not to specify default resources and to leave this as a conscious |  | ||||||
|   # choice for the user. This also increases chances charts run on environments with little |  | ||||||
|   # resources, such as Minikube. If you do want to specify resources, uncomment the following |  | ||||||
|   # lines, adjust them as necessary, and remove the curly braces after 'resources:'. |  | ||||||
|   # limits: |  | ||||||
|   #  cpu: 100m |  | ||||||
|   #  memory: 128Mi |  | ||||||
|   # requests: |  | ||||||
|   #  cpu: 100m |  | ||||||
|   #  memory: 128Mi |  | ||||||
|  |  | ||||||
| nodeSelector: {} |  | ||||||
|  |  | ||||||
| tolerations: [] |  | ||||||
|  |  | ||||||
| affinity: {} |  | ||||||
|  | |||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook""" | """passbook""" | ||||||
| __version__ = '0.3.0-beta' | __version__ = '0.6.4-beta' | ||||||
|  | |||||||
| @ -179,8 +179,8 @@ | |||||||
|                     <span class="card-pf-aggregate-status-notification"> |                     <span class="card-pf-aggregate-status-notification"> | ||||||
|                         <a href="#"> |                         <a href="#"> | ||||||
|                             {% if worker_count < 1%} |                             {% if worker_count < 1%} | ||||||
|                             <span class="pficon-error-circle-o" data-toggle="tooltip" data-placement="right" |                             <span class="pficon-warning-triangle-o" data-toggle="tooltip" data-placement="right" | ||||||
|                                 title="{% trans 'No workers connected. Policies will not work and you may expect other issues.' %}"></span> {{ worker_count }} |                                 title="{% trans 'No workers connected.' %}"></span> {{ worker_count }} | ||||||
|                             {% else %} |                             {% else %} | ||||||
|                             <span class="pficon pficon-ok"></span>{{ worker_count }} |                             <span class="pficon pficon-ok"></span>{{ worker_count }} | ||||||
|                             {% endif %} |                             {% endif %} | ||||||
|  | |||||||
| @ -7,6 +7,10 @@ | |||||||
| <div class="container"> | <div class="container"> | ||||||
|     <h1><span class="pficon-users"></span> {% trans "Users" %}</h1> |     <h1><span class="pficon-users"></span> {% trans "Users" %}</h1> | ||||||
|     <hr> |     <hr> | ||||||
|  |     <a href="{% url 'passbook_admin:user-create' %}?back={{ request.get_full_path }}" class="btn btn-primary"> | ||||||
|  |         {% trans 'Create...' %} | ||||||
|  |     </a> | ||||||
|  |     <hr> | ||||||
|     <table class="table table-striped table-bordered"> |     <table class="table table-striped table-bordered"> | ||||||
|         <thead> |         <thead> | ||||||
|             <tr> |             <tr> | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ from structlog import get_logger | |||||||
| from passbook.lib.utils.template import render_to_string | from passbook.lib.utils.template import render_to_string | ||||||
|  |  | ||||||
| register = template.Library() | register = template.Library() | ||||||
| LOGGER = get_logger(__name__) | LOGGER = get_logger() | ||||||
|  |  | ||||||
| @register.simple_tag() | @register.simple_tag() | ||||||
| def get_links(model_instance): | def get_links(model_instance): | ||||||
|  | |||||||
| @ -61,6 +61,7 @@ urlpatterns = [ | |||||||
|     # Users |     # Users | ||||||
|     path('users/', users.UserListView.as_view(), |     path('users/', users.UserListView.as_view(), | ||||||
|          name='users'), |          name='users'), | ||||||
|  |     path('users/create/', users.UserCreateView.as_view(), name='user-create'), | ||||||
|     path('users/<int:pk>/update/', |     path('users/<int:pk>/update/', | ||||||
|          users.UserUpdateView.as_view(), name='user-update'), |          users.UserUpdateView.as_view(), name='user-update'), | ||||||
|     path('users/<int:pk>/delete/', |     path('users/<int:pk>/delete/', | ||||||
|  | |||||||
| @ -3,8 +3,8 @@ from django.core.cache import cache | |||||||
| from django.shortcuts import redirect, reverse | from django.shortcuts import redirect, reverse | ||||||
| from django.views.generic import TemplateView | from django.views.generic import TemplateView | ||||||
|  |  | ||||||
|  | from passbook import __version__ | ||||||
| from passbook.admin.mixins import AdminRequiredMixin | from passbook.admin.mixins import AdminRequiredMixin | ||||||
| from passbook.core import __version__ |  | ||||||
| from passbook.core.models import (Application, Factor, Invitation, Policy, | from passbook.core.models import (Application, Factor, Invitation, Policy, | ||||||
|                                   Provider, Source, User) |                                   Provider, Source, User) | ||||||
| from passbook.root.celery import CELERY_APP | from passbook.root.celery import CELERY_APP | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ from passbook.admin.forms.policies import PolicyTestForm | |||||||
| from passbook.admin.mixins import AdminRequiredMixin | from passbook.admin.mixins import AdminRequiredMixin | ||||||
| from passbook.core.models import Policy | from passbook.core.models import Policy | ||||||
| from passbook.lib.utils.reflection import path_to_class | from passbook.lib.utils.reflection import path_to_class | ||||||
| from passbook.policy.engine import PolicyEngine | from passbook.policies.engine import PolicyEngine | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyListView(AdminRequiredMixin, ListView): | class PolicyListView(AdminRequiredMixin, ListView): | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404, redirect | |||||||
| from django.urls import reverse, reverse_lazy | from django.urls import reverse, reverse_lazy | ||||||
| from django.utils.translation import ugettext as _ | from django.utils.translation import ugettext as _ | ||||||
| from django.views import View | from django.views import View | ||||||
| from django.views.generic import DeleteView, ListView, UpdateView | from django.views.generic import CreateView, DeleteView, ListView, UpdateView | ||||||
|  |  | ||||||
| from passbook.admin.forms.users import UserForm | from passbook.admin.forms.users import UserForm | ||||||
| from passbook.admin.mixins import AdminRequiredMixin | from passbook.admin.mixins import AdminRequiredMixin | ||||||
| @ -19,6 +19,17 @@ class UserListView(AdminRequiredMixin, ListView): | |||||||
|     template_name = 'administration/user/list.html' |     template_name = 'administration/user/list.html' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView): | ||||||
|  |     """Create user""" | ||||||
|  |  | ||||||
|  |     model = User | ||||||
|  |     form_class = UserForm | ||||||
|  |  | ||||||
|  |     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, AdminRequiredMixin, UpdateView): | ||||||
|     """Update user""" |     """Update user""" | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								passbook/app_gw/.DS_Store
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								passbook/app_gw/.DS_Store
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -1,16 +0,0 @@ | |||||||
| """passbook Application Security Gateway app""" |  | ||||||
| from importlib import import_module |  | ||||||
|  |  | ||||||
| from django.apps import AppConfig |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PassbookApplicationApplicationGatewayConfig(AppConfig): |  | ||||||
|     """passbook app_gw app""" |  | ||||||
|  |  | ||||||
|     name = 'passbook.app_gw' |  | ||||||
|     label = 'passbook_app_gw' |  | ||||||
|     verbose_name = 'passbook Application Security Gateway' |  | ||||||
|     mountpoint = 'app_gw/' |  | ||||||
|  |  | ||||||
|     def ready(self): |  | ||||||
|         import_module('passbook.app_gw.signals') |  | ||||||
| @ -1,13 +0,0 @@ | |||||||
| """ |  | ||||||
| ASGI entrypoint. Configures Django and then runs the application |  | ||||||
| defined in the ASGI_APPLICATION setting. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import os |  | ||||||
|  |  | ||||||
| import django |  | ||||||
| from channels.routing import get_default_application |  | ||||||
|  |  | ||||||
| os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings") |  | ||||||
| django.setup() |  | ||||||
| application = get_default_application() |  | ||||||
| @ -1,29 +0,0 @@ | |||||||
| """passbook app_gw webserver management command""" |  | ||||||
|  |  | ||||||
| from daphne.cli import CommandLineInterface |  | ||||||
| from django.core.management.base import BaseCommand |  | ||||||
| from django.utils import autoreload |  | ||||||
| from structlog import get_logger |  | ||||||
|  |  | ||||||
| from passbook.lib.config import CONFIG |  | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Command(BaseCommand): |  | ||||||
|     """Run Daphne Webserver for app_gw""" |  | ||||||
|  |  | ||||||
|     def handle(self, *args, **options): |  | ||||||
|         """passbook daphne server""" |  | ||||||
|         autoreload.run_with_reloader(self.daphne_server) |  | ||||||
|  |  | ||||||
|     def daphne_server(self): |  | ||||||
|         """Run daphne server within autoreload""" |  | ||||||
|         autoreload.raise_last_exception() |  | ||||||
|         CommandLineInterface().run([ |  | ||||||
|             '-p', str(CONFIG.y('app_gw.port', 8000)), |  | ||||||
|             '-b', CONFIG.y('app_gw.listen', '0.0.0.0'),  # nosec |  | ||||||
|             '--access-log', '/dev/null', |  | ||||||
|             '--application-close-timeout', '500', |  | ||||||
|             'passbook.app_gw.asgi:application' |  | ||||||
|         ]) |  | ||||||
| @ -1,33 +0,0 @@ | |||||||
| """passbook app_gw middleware""" |  | ||||||
| from django.views.generic import RedirectView |  | ||||||
|  |  | ||||||
| from passbook.app_gw.proxy.handler import RequestHandler |  | ||||||
| from passbook.lib.config import CONFIG |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ApplicationGatewayMiddleware: |  | ||||||
|     """Check if request should be proxied or handeled normally""" |  | ||||||
|  |  | ||||||
|     _app_gw_cache = {} |  | ||||||
|  |  | ||||||
|     def __init__(self, get_response): |  | ||||||
|         self.get_response = get_response |  | ||||||
|  |  | ||||||
|     def __call__(self, request): |  | ||||||
|         # Rudimentary cache |  | ||||||
|         host_header = request.META.get('HTTP_HOST') |  | ||||||
|         if host_header not in self._app_gw_cache: |  | ||||||
|             self._app_gw_cache[host_header] = RequestHandler.find_app_gw_for_request(request) |  | ||||||
|         if self._app_gw_cache[host_header]: |  | ||||||
|             return self.dispatch(request, self._app_gw_cache[host_header]) |  | ||||||
|         return self.get_response(request) |  | ||||||
|  |  | ||||||
|     def dispatch(self, request, app_gw): |  | ||||||
|         """Build proxied request and pass to upstream""" |  | ||||||
|         handler = RequestHandler(app_gw, request) |  | ||||||
|  |  | ||||||
|         if not handler.check_permission(): |  | ||||||
|             to_url = 'https://%s/?next=%s' % (CONFIG.y('domains')[0], request.get_full_path()) |  | ||||||
|             return RedirectView.as_view(url=to_url)(request) |  | ||||||
|  |  | ||||||
|         return handler.get_response() |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								passbook/app_gw/migrations/.DS_Store
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								passbook/app_gw/migrations/.DS_Store
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -1,18 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-03-21 15:21 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_app_gw', '0001_initial'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name='rewriterule', |  | ||||||
|             name='conditions', |  | ||||||
|             field=models.ManyToManyField(blank=True, to='passbook_core.Policy'), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| # Generated by Django 2.2 on 2019-04-11 13:14 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_app_gw', '0002_auto_20190321_1521'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name='applicationgatewayprovider', |  | ||||||
|             name='authentication_header', |  | ||||||
|             field=models.TextField(blank=True, default='X-Remote-User'), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,8 +0,0 @@ | |||||||
| """Exception classes""" |  | ||||||
|  |  | ||||||
| class ReverseProxyException(Exception): |  | ||||||
|     """Base for revproxy exception""" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class InvalidUpstream(ReverseProxyException): |  | ||||||
|     """Invalid upstream set""" |  | ||||||
| @ -1,233 +0,0 @@ | |||||||
| """passbook app_gw request handler""" |  | ||||||
| import mimetypes |  | ||||||
| from random import SystemRandom |  | ||||||
| from urllib.parse import urlparse |  | ||||||
|  |  | ||||||
| import certifi |  | ||||||
| import urllib3 |  | ||||||
| from django.core.cache import cache |  | ||||||
| from django.utils.http import urlencode |  | ||||||
| from structlog import get_logger |  | ||||||
|  |  | ||||||
| from passbook.app_gw.models import ApplicationGatewayProvider |  | ||||||
| from passbook.app_gw.proxy.exceptions import InvalidUpstream |  | ||||||
| from passbook.app_gw.proxy.response import get_django_response |  | ||||||
| from passbook.app_gw.proxy.rewrite import Rewriter |  | ||||||
| from passbook.app_gw.proxy.utils import encode_items, normalize_request_headers |  | ||||||
| from passbook.core.models import Application |  | ||||||
| from passbook.policy.engine import PolicyEngine |  | ||||||
|  |  | ||||||
| SESSION_UPSTREAM_KEY = 'passbook_app_gw_upstream' |  | ||||||
| IGNORED_HOSTNAMES_KEY = 'passbook_app_gw_ignored' |  | ||||||
| LOGGER = get_logger(__name__) |  | ||||||
| QUOTE_SAFE = r'<.;>\(}*+|~=-$/_:^@)[{]&\'!,"`' |  | ||||||
| ERRORS_MESSAGES = { |  | ||||||
|     'upstream-no-scheme': ("Upstream URL scheme must be either " |  | ||||||
|                            "'http' or 'https' (%s).") |  | ||||||
| } |  | ||||||
| HTTP_NO_VERIFY = urllib3.PoolManager() |  | ||||||
| HTTP = urllib3.PoolManager( |  | ||||||
|     cert_reqs='CERT_REQUIRED', |  | ||||||
|     ca_certs=certifi.where()) |  | ||||||
| IGNORED_HOSTS = cache.get(IGNORED_HOSTNAMES_KEY, []) |  | ||||||
| POLICY_CACHE = {} |  | ||||||
|  |  | ||||||
| class RequestHandler: |  | ||||||
|     """Forward requests""" |  | ||||||
|  |  | ||||||
|     _parsed_url = None |  | ||||||
|     _request_headers = None |  | ||||||
|  |  | ||||||
|     def __init__(self, app_gw, request): |  | ||||||
|         self.app_gw = app_gw |  | ||||||
|         self.request = request |  | ||||||
|         if self.app_gw.pk not in POLICY_CACHE: |  | ||||||
|             POLICY_CACHE[self.app_gw.pk] = self.app_gw.application.policies.all() |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def find_app_gw_for_request(request): |  | ||||||
|         """Check if a request should be proxied or forwarded to passbook""" |  | ||||||
|         # Check if hostname is in cached list of ignored hostnames |  | ||||||
|         # This saves us having to query the database on each request |  | ||||||
|         host_header = request.META.get('HTTP_HOST') |  | ||||||
|         if host_header in IGNORED_HOSTS: |  | ||||||
|             # LOGGER.debug("%s is ignored", host_header) |  | ||||||
|             return False |  | ||||||
|         # Look through all ApplicationGatewayProviders and check hostnames |  | ||||||
|         matches = ApplicationGatewayProvider.objects.filter( |  | ||||||
|             server_name__contains=[host_header], |  | ||||||
|             enabled=True) |  | ||||||
|         if not matches.exists(): |  | ||||||
|             # Mo matching Providers found, add host header to ignored list |  | ||||||
|             IGNORED_HOSTS.append(host_header) |  | ||||||
|             cache.set(IGNORED_HOSTNAMES_KEY, IGNORED_HOSTS) |  | ||||||
|             # LOGGER.debug("Ignoring %s", host_header) |  | ||||||
|             return False |  | ||||||
|         # At this point we're certain there's a matching ApplicationGateway |  | ||||||
|         if len(matches) > 1: |  | ||||||
|             # This should never happen |  | ||||||
|             raise ValueError |  | ||||||
|         app_gw = matches.first() |  | ||||||
|         try: |  | ||||||
|             # Check if ApplicationGateway is associated with application |  | ||||||
|             getattr(app_gw, 'application') |  | ||||||
|             if app_gw: |  | ||||||
|                 return app_gw |  | ||||||
|         except Application.DoesNotExist: |  | ||||||
|             pass |  | ||||||
|             # LOGGER.debug("ApplicationGateway not associated with Application") |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     def _get_upstream(self): |  | ||||||
|         """Choose random upstream and save in session""" |  | ||||||
|         if SESSION_UPSTREAM_KEY not in self.request.session: |  | ||||||
|             self.request.session[SESSION_UPSTREAM_KEY] = {} |  | ||||||
|         if self.app_gw.pk not in self.request.session[SESSION_UPSTREAM_KEY]: |  | ||||||
|             upstream_index = int(SystemRandom().random() * len(self.app_gw.upstream)) |  | ||||||
|             self.request.session[SESSION_UPSTREAM_KEY][self.app_gw.pk] = upstream_index |  | ||||||
|         return self.app_gw.upstream[self.request.session[SESSION_UPSTREAM_KEY][self.app_gw.pk]] |  | ||||||
|  |  | ||||||
|     def get_upstream(self): |  | ||||||
|         """Get upstream as parsed url""" |  | ||||||
|         upstream = self._get_upstream() |  | ||||||
|  |  | ||||||
|         self._parsed_url = urlparse(upstream) |  | ||||||
|  |  | ||||||
|         if self._parsed_url.scheme not in ('http', 'https'): |  | ||||||
|             raise InvalidUpstream(ERRORS_MESSAGES['upstream-no-scheme'] % |  | ||||||
|                                   upstream) |  | ||||||
|  |  | ||||||
|         return upstream |  | ||||||
|  |  | ||||||
|     def _format_path_to_redirect(self): |  | ||||||
|         # LOGGER.debug("Path before: %s", self.request.get_full_path()) |  | ||||||
|         rewriter = Rewriter(self.app_gw, self.request) |  | ||||||
|         after = rewriter.build() |  | ||||||
|         # LOGGER.debug("Path after: %s", after) |  | ||||||
|         return after |  | ||||||
|  |  | ||||||
|     def get_proxy_request_headers(self): |  | ||||||
|         """Get normalized headers for the upstream |  | ||||||
|         Gets all headers from the original request and normalizes them. |  | ||||||
|         Normalization occurs by removing the prefix ``HTTP_`` and |  | ||||||
|         replacing and ``_`` by ``-``. Example: ``HTTP_ACCEPT_ENCODING`` |  | ||||||
|         becames ``Accept-Encoding``. |  | ||||||
|         .. versionadded:: 0.9.1 |  | ||||||
|         :param request:  The original HTTPRequest instance |  | ||||||
|         :returns:  Normalized headers for the upstream |  | ||||||
|         """ |  | ||||||
|         return normalize_request_headers(self.request) |  | ||||||
|  |  | ||||||
|     def get_request_headers(self): |  | ||||||
|         """Return request headers that will be sent to upstream. |  | ||||||
|         The header REMOTE_USER is set to the current user |  | ||||||
|         if AuthenticationMiddleware is enabled and |  | ||||||
|         the view's add_remote_user property is True. |  | ||||||
|         .. versionadded:: 0.9.8 |  | ||||||
|         """ |  | ||||||
|         request_headers = self.get_proxy_request_headers() |  | ||||||
|         if not self.app_gw.authentication_header: |  | ||||||
|             return request_headers |  | ||||||
|         request_headers[self.app_gw.authentication_header] = self.request.user.get_username() |  | ||||||
|         # LOGGER.debug("%s set", self.app_gw.authentication_header) |  | ||||||
|  |  | ||||||
|         return request_headers |  | ||||||
|  |  | ||||||
|     def check_permission(self): |  | ||||||
|         """Check if user is authenticated and has permission to access app""" |  | ||||||
|         if not hasattr(self.request, 'user'): |  | ||||||
|             return False |  | ||||||
|         if not self.request.user.is_authenticated: |  | ||||||
|             return False |  | ||||||
|         policy_engine = PolicyEngine(POLICY_CACHE[self.app_gw.pk]) |  | ||||||
|         policy_engine.for_user(self.request.user).with_request(self.request).build() |  | ||||||
|         passing, _messages = policy_engine.result |  | ||||||
|  |  | ||||||
|         return passing |  | ||||||
|  |  | ||||||
|     def get_encoded_query_params(self): |  | ||||||
|         """Return encoded query params to be used in proxied request""" |  | ||||||
|         get_data = encode_items(self.request.GET.lists()) |  | ||||||
|         return urlencode(get_data) |  | ||||||
|  |  | ||||||
|     def _created_proxy_response(self, path): |  | ||||||
|         request_payload = self.request.body |  | ||||||
|  |  | ||||||
|         # LOGGER.debug("Request headers: %s", self._request_headers) |  | ||||||
|  |  | ||||||
|         request_url = self.get_upstream() + path |  | ||||||
|         # LOGGER.debug("Request URL: %s", request_url) |  | ||||||
|  |  | ||||||
|         if self.request.GET: |  | ||||||
|             request_url += '?' + self.get_encoded_query_params() |  | ||||||
|             # LOGGER.debug("Request URL: %s", request_url) |  | ||||||
|  |  | ||||||
|         http = HTTP |  | ||||||
|         if not self.app_gw.upstream_ssl_verification: |  | ||||||
|             http = HTTP_NO_VERIFY |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             proxy_response = http.urlopen(self.request.method, |  | ||||||
|                                           request_url, |  | ||||||
|                                           redirect=False, |  | ||||||
|                                           retries=None, |  | ||||||
|                                           headers=self._request_headers, |  | ||||||
|                                           body=request_payload, |  | ||||||
|                                           decode_content=False, |  | ||||||
|                                           preload_content=False) |  | ||||||
|             # LOGGER.debug("Proxy response header: %s", |  | ||||||
|             #              proxy_response.getheaders()) |  | ||||||
|         except urllib3.exceptions.HTTPError as error: |  | ||||||
|             LOGGER.exception(error) |  | ||||||
|             raise |  | ||||||
|  |  | ||||||
|         return proxy_response |  | ||||||
|  |  | ||||||
|     def _replace_host_on_redirect_location(self, proxy_response): |  | ||||||
|         location = proxy_response.headers.get('Location') |  | ||||||
|         if location: |  | ||||||
|             if self.request.is_secure(): |  | ||||||
|                 scheme = 'https://' |  | ||||||
|             else: |  | ||||||
|                 scheme = 'http://' |  | ||||||
|             request_host = scheme + self.request.META.get('HTTP_HOST') |  | ||||||
|  |  | ||||||
|             upstream_host_http = 'http://' + self._parsed_url.netloc |  | ||||||
|             upstream_host_https = 'https://' + self._parsed_url.netloc |  | ||||||
|  |  | ||||||
|             location = location.replace(upstream_host_http, request_host) |  | ||||||
|             location = location.replace(upstream_host_https, request_host) |  | ||||||
|             proxy_response.headers['Location'] = location |  | ||||||
|             # LOGGER.debug("Proxy response LOCATION: %s", |  | ||||||
|             #              proxy_response.headers['Location']) |  | ||||||
|  |  | ||||||
|     def _set_content_type(self, proxy_response): |  | ||||||
|         content_type = proxy_response.headers.get('Content-Type') |  | ||||||
|         if not content_type: |  | ||||||
|             content_type = (mimetypes.guess_type(self.request.path) |  | ||||||
|                             [0] or self.app_gw.default_content_type) |  | ||||||
|             proxy_response.headers['Content-Type'] = content_type |  | ||||||
|             # LOGGER.debug("Proxy response CONTENT-TYPE: %s", |  | ||||||
|             #              proxy_response.headers['Content-Type']) |  | ||||||
|  |  | ||||||
|     def get_response(self): |  | ||||||
|         """Pass request to upstream and return response""" |  | ||||||
|         self._request_headers = self.get_request_headers() |  | ||||||
|  |  | ||||||
|         path = self._format_path_to_redirect() |  | ||||||
|         proxy_response = self._created_proxy_response(path) |  | ||||||
|  |  | ||||||
|         self._replace_host_on_redirect_location(proxy_response) |  | ||||||
|         self._set_content_type(proxy_response) |  | ||||||
|         response = get_django_response(proxy_response, strict_cookies=False) |  | ||||||
|  |  | ||||||
|         # If response has a 'Location' header, we rewrite that location as well |  | ||||||
|         if 'Location' in response: |  | ||||||
|             LOGGER.debug("Rewriting Location header") |  | ||||||
|             for server_name in self.app_gw.server_name: |  | ||||||
|                 response['Location'] = response['Location'].replace( |  | ||||||
|                     self._parsed_url.hostname, server_name) |  | ||||||
|             LOGGER.debug(response['Location']) |  | ||||||
|  |  | ||||||
|         # LOGGER.debug("RESPONSE RETURNED: %s", response) |  | ||||||
|         return response |  | ||||||
| @ -1,62 +0,0 @@ | |||||||
| """response functions from django-revproxy""" |  | ||||||
| from django.http import HttpResponse, StreamingHttpResponse |  | ||||||
| from structlog import get_logger |  | ||||||
|  |  | ||||||
| from passbook.app_gw.proxy.utils import (cookie_from_string, |  | ||||||
|                                          set_response_headers, should_stream) |  | ||||||
|  |  | ||||||
| #: Default number of bytes that are going to be read in a file lecture |  | ||||||
| DEFAULT_AMT = 2 ** 16 |  | ||||||
|  |  | ||||||
| logger = get_logger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_django_response(proxy_response, strict_cookies=False): |  | ||||||
|     """This method is used to create an appropriate response based on the |  | ||||||
|     Content-Length of the proxy_response. If the content is bigger than |  | ||||||
|     MIN_STREAMING_LENGTH, which is found on utils.py, |  | ||||||
|     than django.http.StreamingHttpResponse will be created, |  | ||||||
|     else a django.http.HTTPResponse will be created instead |  | ||||||
|  |  | ||||||
|     :param proxy_response: An Instance of urllib3.response.HTTPResponse that |  | ||||||
|                            will create an appropriate response |  | ||||||
|     :param strict_cookies: Whether to only accept RFC-compliant cookies |  | ||||||
|     :returns: Returns an appropriate response based on the proxy_response |  | ||||||
|               content-length |  | ||||||
|     """ |  | ||||||
|     status = proxy_response.status |  | ||||||
|     headers = proxy_response.headers |  | ||||||
|  |  | ||||||
|     logger.debug('Proxy response headers: %s', headers) |  | ||||||
|  |  | ||||||
|     content_type = headers.get('Content-Type') |  | ||||||
|  |  | ||||||
|     logger.debug('Content-Type: %s', content_type) |  | ||||||
|  |  | ||||||
|     if should_stream(proxy_response): |  | ||||||
|         logger.info('Content-Length is bigger than %s', DEFAULT_AMT) |  | ||||||
|         response = StreamingHttpResponse(proxy_response.stream(DEFAULT_AMT), |  | ||||||
|                                          status=status, |  | ||||||
|                                          content_type=content_type) |  | ||||||
|     else: |  | ||||||
|         content = proxy_response.data or b'' |  | ||||||
|         response = HttpResponse(content, status=status, |  | ||||||
|                                 content_type=content_type) |  | ||||||
|  |  | ||||||
|     logger.info('Normalizing response headers') |  | ||||||
|     set_response_headers(response, headers) |  | ||||||
|  |  | ||||||
|     logger.debug('Response headers: %s', getattr(response, '_headers')) |  | ||||||
|  |  | ||||||
|     cookies = proxy_response.headers.getlist('set-cookie') |  | ||||||
|     logger.info('Checking for invalid cookies') |  | ||||||
|     for cookie_string in cookies: |  | ||||||
|         cookie_dict = cookie_from_string(cookie_string, |  | ||||||
|                                          strict_cookies=strict_cookies) |  | ||||||
|         # if cookie is invalid cookie_dict will be None |  | ||||||
|         if cookie_dict: |  | ||||||
|             response.set_cookie(**cookie_dict) |  | ||||||
|  |  | ||||||
|     logger.debug('Response cookies: %s', response.cookies) |  | ||||||
|  |  | ||||||
|     return response |  | ||||||
| @ -1,42 +0,0 @@ | |||||||
| """passbook app_gw rewriter""" |  | ||||||
|  |  | ||||||
| from passbook.app_gw.models import RewriteRule |  | ||||||
|  |  | ||||||
| RULE_CACHE = {} |  | ||||||
|  |  | ||||||
| class Context: |  | ||||||
|     """Empty class which we dynamically add attributes to""" |  | ||||||
|  |  | ||||||
| class Rewriter: |  | ||||||
|     """Apply rewrites""" |  | ||||||
|  |  | ||||||
|     __application = None |  | ||||||
|     __request = None |  | ||||||
|  |  | ||||||
|     def __init__(self, application, request): |  | ||||||
|         self.__application = application |  | ||||||
|         self.__request = request |  | ||||||
|         if self.__application.pk not in RULE_CACHE: |  | ||||||
|             RULE_CACHE[self.__application.pk] = RewriteRule.objects.filter( |  | ||||||
|                 provider__in=[self.__application]) |  | ||||||
|  |  | ||||||
|     def __build_context(self, matches): |  | ||||||
|         """Build object with .0, .1, etc as groups and give access to request""" |  | ||||||
|         context = Context() |  | ||||||
|         for index, group_match in enumerate(matches.groups()): |  | ||||||
|             setattr(context, "g%d" % (index + 1), group_match) |  | ||||||
|         setattr(context, 'request', self.__request) |  | ||||||
|         return context |  | ||||||
|  |  | ||||||
|     def build(self): |  | ||||||
|         """Run all rules over path and return final path""" |  | ||||||
|         path = self.__request.get_full_path() |  | ||||||
|         for rule in RULE_CACHE[self.__application.pk]: |  | ||||||
|             matches = rule.compiled_matcher.search(path) |  | ||||||
|             if not matches: |  | ||||||
|                 continue |  | ||||||
|             replace_context = self.__build_context(matches) |  | ||||||
|             path = rule.replacement.format(context=replace_context) |  | ||||||
|             if rule.halt: |  | ||||||
|                 return path |  | ||||||
|         return path |  | ||||||
| @ -1,226 +0,0 @@ | |||||||
| """Utils from django-revproxy, slightly adjusted""" |  | ||||||
| import re |  | ||||||
| from wsgiref.util import is_hop_by_hop |  | ||||||
|  |  | ||||||
| from structlog import get_logger |  | ||||||
|  |  | ||||||
| try: |  | ||||||
|     from http.cookies import SimpleCookie |  | ||||||
|     COOKIE_PREFIX = '' |  | ||||||
| except ImportError: |  | ||||||
|     from Cookie import SimpleCookie |  | ||||||
|     COOKIE_PREFIX = 'Set-Cookie: ' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #: List containing string constant that are used to represent headers that can |  | ||||||
| #: be ignored in the required_header function |  | ||||||
| IGNORE_HEADERS = ( |  | ||||||
|     'HTTP_ACCEPT_ENCODING',  # We want content to be uncompressed so |  | ||||||
|                              # we remove the Accept-Encoding from |  | ||||||
|                              # original request |  | ||||||
|     'HTTP_HOST', |  | ||||||
|     'HTTP_REMOTE_USER', |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Default from HTTP RFC 2616 |  | ||||||
| #   See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 |  | ||||||
| #: Variable that represent the default charset used |  | ||||||
| DEFAULT_CHARSET = 'latin-1' |  | ||||||
|  |  | ||||||
| #: List containing string constants that represents possible html content type |  | ||||||
| HTML_CONTENT_TYPES = ( |  | ||||||
|     'text/html', |  | ||||||
|     'application/xhtml+xml' |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| #: Variable used to represent a minimal content size required for response |  | ||||||
| #: to be turned into stream |  | ||||||
| MIN_STREAMING_LENGTH = 4 * 1024  # 4KB |  | ||||||
|  |  | ||||||
| #: Regex used to find charset in a html content type |  | ||||||
| _get_charset_re = re.compile(r';\s*charset=(?P<charset>[^\s;]+)', re.I) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def is_html_content_type(content_type): |  | ||||||
|     """Function used to verify if the parameter is a proper html content type |  | ||||||
|  |  | ||||||
|     :param content_type: String variable that represent a content-type |  | ||||||
|     :returns:  A boolean value stating if the content_type is a valid html |  | ||||||
|                content type |  | ||||||
|     """ |  | ||||||
|     for html_content_type in HTML_CONTENT_TYPES: |  | ||||||
|         if content_type.startswith(html_content_type): |  | ||||||
|             return True |  | ||||||
|  |  | ||||||
|     return False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def should_stream(proxy_response): |  | ||||||
|     """Function to verify if the proxy_response must be converted into |  | ||||||
|     a stream.This will be done by checking the proxy_response content-length |  | ||||||
|     and verify if its length is bigger than one stipulated |  | ||||||
|     by MIN_STREAMING_LENGTH. |  | ||||||
|  |  | ||||||
|     :param proxy_response: An Instance of urllib3.response.HTTPResponse |  | ||||||
|     :returns: A boolean stating if the proxy_response should |  | ||||||
|               be treated as a stream |  | ||||||
|     """ |  | ||||||
|     content_type = proxy_response.headers.get('Content-Type') |  | ||||||
|  |  | ||||||
|     if is_html_content_type(content_type): |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     try: |  | ||||||
|         content_length = int(proxy_response.headers.get('Content-Length', 0)) |  | ||||||
|     except ValueError: |  | ||||||
|         content_length = 0 |  | ||||||
|  |  | ||||||
|     if not content_length or content_length > MIN_STREAMING_LENGTH: |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     return False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_charset(content_type): |  | ||||||
|     """Function used to retrieve the charset from a content-type.If there is no |  | ||||||
|     charset in the content type then the charset defined on DEFAULT_CHARSET |  | ||||||
|     will be returned |  | ||||||
|  |  | ||||||
|     :param  content_type:   A string containing a Content-Type header |  | ||||||
|     :returns:               A string containing the charset |  | ||||||
|     """ |  | ||||||
|     if not content_type: |  | ||||||
|         return DEFAULT_CHARSET |  | ||||||
|  |  | ||||||
|     matched = _get_charset_re.search(content_type) |  | ||||||
|     if matched: |  | ||||||
|         # Extract the charset and strip its double quotes |  | ||||||
|         return matched.group('charset').replace('"', '') |  | ||||||
|     return DEFAULT_CHARSET |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def required_header(header): |  | ||||||
|     """Function that verify if the header parameter is a essential header |  | ||||||
|  |  | ||||||
|     :param header:  A string represented a header |  | ||||||
|     :returns:       A boolean value that represent if the header is required |  | ||||||
|     """ |  | ||||||
|     if header in IGNORE_HEADERS: |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     if header.startswith('HTTP_') or header == 'CONTENT_TYPE': |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     return False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def set_response_headers(response, response_headers): |  | ||||||
|     """Set response's header""" |  | ||||||
|     for header, value in response_headers.items(): |  | ||||||
|         if is_hop_by_hop(header) or header.lower() == 'set-cookie': |  | ||||||
|             continue |  | ||||||
|  |  | ||||||
|         response[header.title()] = value |  | ||||||
|  |  | ||||||
|     logger.debug('Response headers: %s', getattr(response, '_headers')) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def normalize_request_headers(request): |  | ||||||
|     """Function used to transform header, replacing 'HTTP\\_' to '' |  | ||||||
|     and replace '_' to '-' |  | ||||||
|  |  | ||||||
|     :param request:  A HttpRequest that will be transformed |  | ||||||
|     :returns:        A dictionary with the normalized headers |  | ||||||
|     """ |  | ||||||
|     norm_headers = {} |  | ||||||
|     for header, value in request.META.items(): |  | ||||||
|         if required_header(header): |  | ||||||
|             norm_header = header.replace('HTTP_', '').title().replace('_', '-') |  | ||||||
|             norm_headers[norm_header] = value |  | ||||||
|  |  | ||||||
|     return norm_headers |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def encode_items(items): |  | ||||||
|     """Function that encode all elements in the list of items passed as |  | ||||||
|     a parameter |  | ||||||
|  |  | ||||||
|     :param items:  A list of tuple |  | ||||||
|     :returns:      A list of tuple with all items encoded in 'utf-8' |  | ||||||
|     """ |  | ||||||
|     encoded = [] |  | ||||||
|     for key, values in items: |  | ||||||
|         for value in values: |  | ||||||
|             encoded.append((key.encode('utf-8'), value.encode('utf-8'))) |  | ||||||
|     return encoded |  | ||||||
|  |  | ||||||
|  |  | ||||||
| logger = get_logger() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def cookie_from_string(cookie_string, strict_cookies=False): |  | ||||||
|     """Parser for HTTP header set-cookie |  | ||||||
|     The return from this function will be used as parameters for |  | ||||||
|     django's response.set_cookie method. Because set_cookie doesn't |  | ||||||
|     have parameter comment, this cookie attribute will be ignored. |  | ||||||
|  |  | ||||||
|     :param  cookie_string: A string representing a valid cookie |  | ||||||
|     :param  strict_cookies: Whether to only accept RFC-compliant cookies |  | ||||||
|     :returns: A dictionary containing the cookie_string attributes |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     if strict_cookies: |  | ||||||
|  |  | ||||||
|         cookies = SimpleCookie(COOKIE_PREFIX + cookie_string) |  | ||||||
|         if not cookies.keys(): |  | ||||||
|             return None |  | ||||||
|         cookie_name, = cookies.keys() |  | ||||||
|         cookie_dict = {k: v for k, v in cookies[cookie_name].items() |  | ||||||
|                        if v and k != 'comment'} |  | ||||||
|         cookie_dict['key'] = cookie_name |  | ||||||
|         cookie_dict['value'] = cookies[cookie_name].value |  | ||||||
|         return cookie_dict |  | ||||||
|     valid_attrs = ('path', 'domain', 'comment', 'expires', |  | ||||||
|                    'max_age', 'httponly', 'secure') |  | ||||||
|  |  | ||||||
|     cookie_dict = {} |  | ||||||
|  |  | ||||||
|     cookie_parts = cookie_string.split(';') |  | ||||||
|     try: |  | ||||||
|         cookie_dict['key'], cookie_dict['value'] = \ |  | ||||||
|             cookie_parts[0].split('=', 1) |  | ||||||
|         cookie_dict['value'] = cookie_dict['value'].replace('"', '') |  | ||||||
|     except ValueError: |  | ||||||
|         logger.warning('Invalid cookie: `%s`', cookie_string) |  | ||||||
|         return None |  | ||||||
|  |  | ||||||
|     if cookie_dict['value'].startswith('='): |  | ||||||
|         logger.warning('Invalid cookie: `%s`', cookie_string) |  | ||||||
|         return None |  | ||||||
|  |  | ||||||
|     for part in cookie_parts[1:]: |  | ||||||
|         if '=' in part: |  | ||||||
|             attr, value = part.split('=', 1) |  | ||||||
|             value = value.strip() |  | ||||||
|         else: |  | ||||||
|             attr = part |  | ||||||
|             value = '' |  | ||||||
|  |  | ||||||
|         attr = attr.strip().lower() |  | ||||||
|         if not attr: |  | ||||||
|             continue |  | ||||||
|  |  | ||||||
|         if attr in valid_attrs: |  | ||||||
|             if attr in ('httponly', 'secure'): |  | ||||||
|                 cookie_dict[attr] = True |  | ||||||
|             elif attr in 'comment': |  | ||||||
|                 # ignoring comment attr as explained in the |  | ||||||
|                 # function docstring |  | ||||||
|                 continue |  | ||||||
|             else: |  | ||||||
|                 cookie_dict[attr] = value |  | ||||||
|         else: |  | ||||||
|             logger.warning('Unknown cookie attribute %s', attr) |  | ||||||
|  |  | ||||||
|         return cookie_dict |  | ||||||
| @ -1,5 +0,0 @@ | |||||||
| """Application Security Gateway settings""" |  | ||||||
| INSTALLED_APPS = [ |  | ||||||
|     'channels' |  | ||||||
| ] |  | ||||||
| ASGI_APPLICATION = "passbook.app_gw.websocket.routing.application" |  | ||||||
| @ -1,19 +0,0 @@ | |||||||
| """passbook app_gw cache clean signals""" |  | ||||||
|  |  | ||||||
| from django.core.cache import cache |  | ||||||
| from django.db.models.signals import post_save |  | ||||||
| from django.dispatch import receiver |  | ||||||
| from structlog import get_logger |  | ||||||
|  |  | ||||||
| from passbook.app_gw.models import ApplicationGatewayProvider |  | ||||||
| from passbook.app_gw.proxy.handler import IGNORED_HOSTNAMES_KEY |  | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) |  | ||||||
|  |  | ||||||
| @receiver(post_save) |  | ||||||
| # pylint: disable=unused-argument |  | ||||||
| def invalidate_app_gw_cache(sender, instance, **kwargs): |  | ||||||
|     """Invalidate Policy cache when app_gw is updated""" |  | ||||||
|     if isinstance(instance, ApplicationGatewayProvider): |  | ||||||
|         LOGGER.debug("Invalidating cache for ignored hostnames") |  | ||||||
|         cache.delete(IGNORED_HOSTNAMES_KEY) |  | ||||||
| @ -1,2 +0,0 @@ | |||||||
| """passbook app_gw urls""" |  | ||||||
| urlpatterns = [] |  | ||||||
| @ -1,83 +0,0 @@ | |||||||
| """websocket proxy consumer""" |  | ||||||
| import threading |  | ||||||
| from ssl import CERT_NONE |  | ||||||
|  |  | ||||||
| import websocket |  | ||||||
| from channels.generic.websocket import WebsocketConsumer |  | ||||||
| from structlog import get_logger |  | ||||||
|  |  | ||||||
| from passbook.app_gw.models import ApplicationGatewayProvider |  | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) |  | ||||||
|  |  | ||||||
| class ProxyConsumer(WebsocketConsumer): |  | ||||||
|     """Proxy websocket connection to upstream""" |  | ||||||
|  |  | ||||||
|     _headers_dict = {} |  | ||||||
|     _app_gw = None |  | ||||||
|     _client = None |  | ||||||
|     _thread = None |  | ||||||
|  |  | ||||||
|     def _fix_headers(self, input_dict): |  | ||||||
|         """Fix headers from bytestrings to normal strings""" |  | ||||||
|         return { |  | ||||||
|             key.decode('utf-8'): value.decode('utf-8') |  | ||||||
|             for key, value in dict(input_dict).items() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     def connect(self): |  | ||||||
|         """Extract host header, lookup in database and proxy connection""" |  | ||||||
|         self._headers_dict = self._fix_headers(dict(self.scope.get('headers'))) |  | ||||||
|         host = self._headers_dict.pop('host') |  | ||||||
|         query_string = self.scope.get('query_string').decode('utf-8') |  | ||||||
|         matches = ApplicationGatewayProvider.objects.filter( |  | ||||||
|             server_name__contains=[host], |  | ||||||
|             enabled=True) |  | ||||||
|         if matches.exists(): |  | ||||||
|             self._app_gw = matches.first() |  | ||||||
|             # TODO: Get upstream that starts with wss or |  | ||||||
|             upstream = self._app_gw.upstream[0].replace('http', 'ws') + self.scope.get('path') |  | ||||||
|             if query_string: |  | ||||||
|                 upstream += '?' + query_string |  | ||||||
|             sslopt = {} |  | ||||||
|             if not self._app_gw.upstream_ssl_verification: |  | ||||||
|                 sslopt = {"cert_reqs": CERT_NONE} |  | ||||||
|             self._client = websocket.WebSocketApp( |  | ||||||
|                 url=upstream, |  | ||||||
|                 subprotocols=self.scope.get('subprotocols'), |  | ||||||
|                 header=self._headers_dict, |  | ||||||
|                 on_message=self._client_on_message_handler(), |  | ||||||
|                 on_error=self._client_on_error_handler(), |  | ||||||
|                 on_close=self._client_on_close_handler(), |  | ||||||
|                 on_open=self._client_on_open_handler()) |  | ||||||
|             LOGGER.debug("Accepting connection for %s", host) |  | ||||||
|             self._thread = threading.Thread(target=lambda: self._client.run_forever(sslopt=sslopt)) |  | ||||||
|             self._thread.start() |  | ||||||
|  |  | ||||||
|     def _client_on_open_handler(self): |  | ||||||
|         return lambda ws: self.accept(self._client.sock.handshake_response.subprotocol) |  | ||||||
|  |  | ||||||
|     def _client_on_message_handler(self): |  | ||||||
|         # pylint: disable=unused-argument,invalid-name |  | ||||||
|         def message_handler(ws, message): |  | ||||||
|             if isinstance(message, str): |  | ||||||
|                 self.send(text_data=message) |  | ||||||
|             else: |  | ||||||
|                 self.send(bytes_data=message) |  | ||||||
|         return message_handler |  | ||||||
|  |  | ||||||
|     def _client_on_error_handler(self): |  | ||||||
|         return lambda ws, error: print(error) |  | ||||||
|  |  | ||||||
|     def _client_on_close_handler(self): |  | ||||||
|         return lambda ws: self.disconnect(0) |  | ||||||
|  |  | ||||||
|     def disconnect(self, code): |  | ||||||
|         self._client.close() |  | ||||||
|  |  | ||||||
|     def receive(self, text_data=None, bytes_data=None): |  | ||||||
|         if text_data: |  | ||||||
|             opcode = websocket.ABNF.OPCODE_TEXT |  | ||||||
|         if bytes_data: |  | ||||||
|             opcode = websocket.ABNF.OPCODE_BINARY |  | ||||||
|         self._client.send(text_data or bytes_data, opcode) |  | ||||||
| @ -1,17 +0,0 @@ | |||||||
| """app_gw websocket proxy""" |  | ||||||
| from channels.auth import AuthMiddlewareStack |  | ||||||
| from channels.routing import ProtocolTypeRouter, URLRouter |  | ||||||
| from django.conf.urls import url |  | ||||||
|  |  | ||||||
| from passbook.app_gw.websocket.consumer import ProxyConsumer |  | ||||||
|  |  | ||||||
| websocket_urlpatterns = [ |  | ||||||
|     url(r'^(.*)$', ProxyConsumer), |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| application = ProtocolTypeRouter({ |  | ||||||
|     # (http->django views is added by default) |  | ||||||
|     'websocket': AuthMiddlewareStack( |  | ||||||
|         URLRouter(websocket_urlpatterns) |  | ||||||
|     ), |  | ||||||
| }) |  | ||||||
| @ -1,7 +1,8 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-16 09:13 | # Generated by Django 2.2.6 on 2019-10-07 14:07 | ||||||
|  |  | ||||||
| import uuid | import uuid | ||||||
|  |  | ||||||
|  | import django.contrib.postgres.fields.jsonb | ||||||
| import django.db.models.deletion | import django.db.models.deletion | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.db import migrations, models | from django.db import migrations, models | ||||||
| @ -23,7 +24,7 @@ class Migration(migrations.Migration): | |||||||
|                 ('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])), |                 ('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])), | ||||||
|                 ('date', models.DateTimeField(auto_now_add=True)), |                 ('date', models.DateTimeField(auto_now_add=True)), | ||||||
|                 ('app', models.TextField()), |                 ('app', models.TextField()), | ||||||
|                 ('_context', models.TextField()), |                 ('context', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)), | ||||||
|                 ('request_ip', models.GenericIPAddressField()), |                 ('request_ip', models.GenericIPAddressField()), | ||||||
|                 ('created', models.DateTimeField(auto_now_add=True)), |                 ('created', models.DateTimeField(auto_now_add=True)), | ||||||
|                 ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), |                 ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), | ||||||
| @ -33,19 +34,4 @@ class Migration(migrations.Migration): | |||||||
|                 'verbose_name_plural': 'Audit Entries', |                 'verbose_name_plural': 'Audit Entries', | ||||||
|             }, |             }, | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='LoginAttempt', |  | ||||||
|             fields=[ |  | ||||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |  | ||||||
|                 ('created', models.DateField(auto_now_add=True)), |  | ||||||
|                 ('last_updated', models.DateTimeField(auto_now=True)), |  | ||||||
|                 ('target_uid', models.CharField(max_length=254)), |  | ||||||
|                 ('request_ip', models.GenericIPAddressField()), |  | ||||||
|                 ('attempts', models.IntegerField(default=1)), |  | ||||||
|             ], |  | ||||||
|         ), |  | ||||||
|         migrations.AlterUniqueTogether( |  | ||||||
|             name='loginattempt', |  | ||||||
|             unique_together={('target_uid', 'request_ip', 'created')}, |  | ||||||
|         ), |  | ||||||
|     ] |     ] | ||||||
|  | |||||||
| @ -1,18 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-21 12:01 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_audit', '0001_initial'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name='loginattempt', |  | ||||||
|             name='created', |  | ||||||
|             field=models.DateTimeField(auto_now_add=True), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,23 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-21 12:40 |  | ||||||
|  |  | ||||||
| import django.contrib.postgres.fields.jsonb |  | ||||||
| from django.db import migrations |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_audit', '0002_auto_20190221_1201'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.RemoveField( |  | ||||||
|             model_name='auditentry', |  | ||||||
|             name='_context', |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='auditentry', |  | ||||||
|             name='context', |  | ||||||
|             field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,16 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-03-08 14:53 |  | ||||||
|  |  | ||||||
| from django.db import migrations |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_audit', '0003_auto_20190221_1240'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.DeleteModel( |  | ||||||
|             name='LoginAttempt', |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -10,7 +10,7 @@ from structlog import get_logger | |||||||
|  |  | ||||||
| from passbook.lib.models import UUIDModel | from passbook.lib.models import UUIDModel | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) | LOGGER = get_logger() | ||||||
|  |  | ||||||
| class AuditEntry(UUIDModel): | class AuditEntry(UUIDModel): | ||||||
|     """An individual audit log entry""" |     """An individual audit log entry""" | ||||||
|  | |||||||
| @ -1,10 +0,0 @@ | |||||||
| """passbook captcha app""" |  | ||||||
| from django.apps import AppConfig |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PassbookCaptchaFactorConfig(AppConfig): |  | ||||||
|     """passbook captcha app""" |  | ||||||
|  |  | ||||||
|     name = 'passbook.captcha_factor' |  | ||||||
|     label = 'passbook_captcha_factor' |  | ||||||
|     verbose_name = 'passbook Captcha' |  | ||||||
| @ -1,2 +0,0 @@ | |||||||
| """passbook core""" |  | ||||||
| __version__ = '0.2.6-beta' |  | ||||||
|  | |||||||
| @ -1,12 +1,6 @@ | |||||||
| """passbook core app config""" | """passbook core app config""" | ||||||
| from importlib import import_module |  | ||||||
|  |  | ||||||
| from django.apps import AppConfig | from django.apps import AppConfig | ||||||
| from structlog import get_logger |  | ||||||
|  |  | ||||||
| from passbook.lib.config import CONFIG |  | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) |  | ||||||
|  |  | ||||||
| class PassbookCoreConfig(AppConfig): | class PassbookCoreConfig(AppConfig): | ||||||
|     """passbook core app config""" |     """passbook core app config""" | ||||||
| @ -15,13 +9,3 @@ class PassbookCoreConfig(AppConfig): | |||||||
|     label = 'passbook_core' |     label = 'passbook_core' | ||||||
|     verbose_name = 'passbook Core' |     verbose_name = 'passbook Core' | ||||||
|     mountpoint = '' |     mountpoint = '' | ||||||
|  |  | ||||||
|     def ready(self): |  | ||||||
|         import_module('passbook.policy.engine') |  | ||||||
|         factors_to_load = CONFIG.y('passbook.factors', []) |  | ||||||
|         for factors_to_load in factors_to_load: |  | ||||||
|             try: |  | ||||||
|                 import_module(factors_to_load) |  | ||||||
|                 LOGGER.info("Loaded %s", factors_to_load) |  | ||||||
|             except ImportError as exc: |  | ||||||
|                 LOGGER.debug(exc) |  | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ class ApplicationForm(forms.ModelForm): | |||||||
|  |  | ||||||
|         model = Application |         model = Application | ||||||
|         fields = ['name', 'slug', 'launch_url', 'icon_url', |         fields = ['name', 'slug', 'launch_url', 'icon_url', | ||||||
|                   'policies', 'provider', 'skip_authorization'] |                   'provider', 'policies', 'skip_authorization'] | ||||||
|         widgets = { |         widgets = { | ||||||
|             'name': forms.TextInput(), |             'name': forms.TextInput(), | ||||||
|             'launch_url': forms.TextInput(), |             'launch_url': forms.TextInput(), | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ from passbook.core.models import User | |||||||
| from passbook.lib.config import CONFIG | from passbook.lib.config import CONFIG | ||||||
| from passbook.lib.utils.ui import human_list | from passbook.lib.utils.ui import human_list | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) | LOGGER = get_logger() | ||||||
|  |  | ||||||
| class LoginForm(forms.Form): | class LoginForm(forms.Form): | ||||||
|     """Allow users to login""" |     """Allow users to login""" | ||||||
| @ -81,13 +81,3 @@ class SignUpForm(forms.Form): | |||||||
|         if password != password_repeat: |         if password != password_repeat: | ||||||
|             raise ValidationError(_("Passwords don't match")) |             raise ValidationError(_("Passwords don't match")) | ||||||
|         return self.cleaned_data.get('password_repeat') |         return self.cleaned_data.get('password_repeat') | ||||||
|  |  | ||||||
|  |  | ||||||
| class PasswordFactorForm(forms.Form): |  | ||||||
|     """Password authentication form""" |  | ||||||
|  |  | ||||||
|     password = forms.CharField(widget=forms.PasswordInput(attrs={ |  | ||||||
|         'placeholder': _('Password'), |  | ||||||
|         'autofocus': 'autofocus', |  | ||||||
|         'autocomplete': 'current-password' |  | ||||||
|         })) |  | ||||||
|  | |||||||
| @ -3,40 +3,8 @@ | |||||||
| from django import forms | from django import forms | ||||||
| from django.utils.translation import gettext as _ | from django.utils.translation import gettext as _ | ||||||
|  |  | ||||||
| from passbook.core.models import (DebugPolicy, FieldMatcherPolicy, | from passbook.core.models import DebugPolicy | ||||||
|                                   GroupMembershipPolicy, PasswordPolicy, | from passbook.policies.forms import GENERAL_FIELDS | ||||||
|                                   SSOLoginPolicy, WebhookPolicy) |  | ||||||
|  |  | ||||||
| GENERAL_FIELDS = ['name', 'action', 'negate', 'order', 'timeout'] |  | ||||||
|  |  | ||||||
| class FieldMatcherPolicyForm(forms.ModelForm): |  | ||||||
|     """FieldMatcherPolicy Form""" |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         model = FieldMatcherPolicy |  | ||||||
|         fields = GENERAL_FIELDS + ['user_field', 'match_action', 'value', ] |  | ||||||
|         widgets = { |  | ||||||
|             'name': forms.TextInput(), |  | ||||||
|             'value': forms.TextInput(), |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WebhookPolicyForm(forms.ModelForm): |  | ||||||
|     """WebhookPolicyForm Form""" |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         model = WebhookPolicy |  | ||||||
|         fields = GENERAL_FIELDS + ['url', 'method', 'json_body', 'json_headers', |  | ||||||
|                                    'result_jsonpath', 'result_json_value', ] |  | ||||||
|         widgets = { |  | ||||||
|             'name': forms.TextInput(), |  | ||||||
|             'json_body': forms.TextInput(), |  | ||||||
|             'json_headers': forms.TextInput(), |  | ||||||
|             'result_jsonpath': forms.TextInput(), |  | ||||||
|             'result_json_value': forms.TextInput(), |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DebugPolicyForm(forms.ModelForm): | class DebugPolicyForm(forms.ModelForm): | ||||||
| @ -52,49 +20,3 @@ class DebugPolicyForm(forms.ModelForm): | |||||||
|         labels = { |         labels = { | ||||||
|             'result': _('Allow user') |             'result': _('Allow user') | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
| class GroupMembershipPolicyForm(forms.ModelForm): |  | ||||||
|     """GroupMembershipPolicy Form""" |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         model = GroupMembershipPolicy |  | ||||||
|         fields = GENERAL_FIELDS + ['group', ] |  | ||||||
|         widgets = { |  | ||||||
|             'name': forms.TextInput(), |  | ||||||
|             'order': forms.NumberInput(), |  | ||||||
|         } |  | ||||||
|  |  | ||||||
| class SSOLoginPolicyForm(forms.ModelForm): |  | ||||||
|     """Edit SSOLoginPolicy instances""" |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         model = SSOLoginPolicy |  | ||||||
|         fields = GENERAL_FIELDS |  | ||||||
|         widgets = { |  | ||||||
|             'name': forms.TextInput(), |  | ||||||
|             'order': forms.NumberInput(), |  | ||||||
|         } |  | ||||||
|  |  | ||||||
| class PasswordPolicyForm(forms.ModelForm): |  | ||||||
|     """PasswordPolicy Form""" |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         model = PasswordPolicy |  | ||||||
|         fields = GENERAL_FIELDS + ['amount_uppercase', 'amount_lowercase', |  | ||||||
|                                    'amount_symbols', 'length_min', 'symbol_charset', |  | ||||||
|                                    'error_message'] |  | ||||||
|         widgets = { |  | ||||||
|             'name': forms.TextInput(), |  | ||||||
|             'symbol_charset': forms.TextInput(), |  | ||||||
|             'error_message': forms.TextInput(), |  | ||||||
|         } |  | ||||||
|         labels = { |  | ||||||
|             'amount_uppercase': _('Minimum amount of Uppercase Characters'), |  | ||||||
|             'amount_lowercase': _('Minimum amount of Lowercase Characters'), |  | ||||||
|             'amount_symbols': _('Minimum amount of Symbols Characters'), |  | ||||||
|             'length_min': _('Minimum Length'), |  | ||||||
|         } |  | ||||||
|  | |||||||
| @ -1,45 +0,0 @@ | |||||||
| """passbook import_users management command""" |  | ||||||
| from csv import DictReader |  | ||||||
|  |  | ||||||
| from django.core.management.base import BaseCommand |  | ||||||
| from django.core.validators import EmailValidator, ValidationError |  | ||||||
| from structlog import get_logger |  | ||||||
|  |  | ||||||
| from passbook.core.models import User |  | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) |  | ||||||
|  |  | ||||||
| class Command(BaseCommand): |  | ||||||
|     """Import users from CSV file""" |  | ||||||
|  |  | ||||||
|     def add_arguments(self, parser): |  | ||||||
|         # Positional arguments |  | ||||||
|         parser.add_argument('file', nargs='+', type=str) |  | ||||||
|  |  | ||||||
|     def handle(self, *args, **options): |  | ||||||
|         """Create Users from CSV file""" |  | ||||||
|         for file in options.get('file'): |  | ||||||
|             with open(file, 'r') as _file: |  | ||||||
|                 reader = DictReader(_file) |  | ||||||
|                 for user in reader: |  | ||||||
|                     LOGGER.debug('User %s', user.get('username')) |  | ||||||
|                     try: |  | ||||||
|                         # only import users with valid email addresses |  | ||||||
|                         if user.get('email'): |  | ||||||
|                             validator = EmailValidator() |  | ||||||
|                             validator(user.get('email')) |  | ||||||
|                         # use combination of username and email to check for existing user |  | ||||||
|                         if User.objects.filter( |  | ||||||
|                                 username=user.get('username'), |  | ||||||
|                                 email=user.get('email')).exists(): |  | ||||||
|                             LOGGER.debug('User %s exists already, skipping', user.get('username')) |  | ||||||
|                         # Create user |  | ||||||
|                         User.objects.create( |  | ||||||
|                             username=user.get('username'), |  | ||||||
|                             email=user.get('email'), |  | ||||||
|                             name=user.get('name'), |  | ||||||
|                             password=user.get('password')) |  | ||||||
|                         LOGGER.debug('Created User %s', user.get('username')) |  | ||||||
|                     except ValidationError as exc: |  | ||||||
|                         LOGGER.warning('User %s caused %r, skipping', user.get('username'), exc) |  | ||||||
|                         continue |  | ||||||
| @ -1,35 +0,0 @@ | |||||||
| """passbook Webserver management command""" |  | ||||||
|  |  | ||||||
| import cherrypy |  | ||||||
| from django.conf import settings |  | ||||||
| from django.core.management.base import BaseCommand |  | ||||||
| from structlog import get_logger |  | ||||||
|  |  | ||||||
| from passbook.lib.config import CONFIG |  | ||||||
| from passbook.root.wsgi import application |  | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Command(BaseCommand): |  | ||||||
|     """Run CherryPy webserver""" |  | ||||||
|  |  | ||||||
|     def handle(self, *args, **options): |  | ||||||
|         """passbook cherrypy server""" |  | ||||||
|         cherrypy.config.update(CONFIG.y('web')) |  | ||||||
|         cherrypy.tree.graft(application, '/') |  | ||||||
|         # Mount NullObject to serve static files |  | ||||||
|         cherrypy.tree.mount(None, settings.STATIC_URL, config={ |  | ||||||
|             '/': { |  | ||||||
|                 'tools.staticdir.on': True, |  | ||||||
|                 'tools.staticdir.dir': settings.STATIC_ROOT, |  | ||||||
|                 'tools.expires.on': True, |  | ||||||
|                 'tools.expires.secs': 86400, |  | ||||||
|                 'tools.gzip.on': True, |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
|         cherrypy.engine.start() |  | ||||||
|         for file in CONFIG.loaded_file: |  | ||||||
|             cherrypy.engine.autoreload.files.add(file) |  | ||||||
|             LOGGER.info("Added '%s' to autoreload triggers", file) |  | ||||||
|         cherrypy.engine.block() |  | ||||||
| @ -1,22 +0,0 @@ | |||||||
| """passbook Worker management command""" |  | ||||||
|  |  | ||||||
| from django.core.management.base import BaseCommand |  | ||||||
| from django.utils import autoreload |  | ||||||
| from structlog import get_logger |  | ||||||
|  |  | ||||||
| from passbook.root.celery import CELERY_APP |  | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Command(BaseCommand): |  | ||||||
|     """Run Celery Worker""" |  | ||||||
|  |  | ||||||
|     def handle(self, *args, **options): |  | ||||||
|         """celery worker""" |  | ||||||
|         autoreload.run_with_reloader(self.celery_worker) |  | ||||||
|  |  | ||||||
|     def celery_worker(self): |  | ||||||
|         """Run celery worker within autoreload""" |  | ||||||
|         autoreload.raise_last_exception() |  | ||||||
|         CELERY_APP.worker_main(['worker', '--autoscale=10,3', '-E', '-B']) |  | ||||||
| @ -1,21 +1,24 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-16 09:10 | # Generated by Django 2.2.6 on 2019-10-07 14:06 | ||||||
|  |  | ||||||
| import uuid | import uuid | ||||||
|  |  | ||||||
| import django.contrib.auth.models | import django.contrib.auth.models | ||||||
| import django.contrib.auth.validators | import django.contrib.auth.validators | ||||||
|  | import django.contrib.postgres.fields.jsonb | ||||||
| import django.db.models.deletion | import django.db.models.deletion | ||||||
| import django.utils.timezone | import django.utils.timezone | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from django.db import migrations, models | from django.db import migrations, models | ||||||
|  |  | ||||||
|  | import passbook.core.models | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|     initial = True |     initial = True | ||||||
|  |  | ||||||
|     dependencies = [ |     dependencies = [ | ||||||
|         ('auth', '0009_alter_user_last_name_max_length'), |         ('auth', '0011_update_proxy_permissions'), | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
|     operations = [ |     operations = [ | ||||||
| @ -34,6 +37,8 @@ class Migration(migrations.Migration): | |||||||
|                 ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), |                 ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), | ||||||
|                 ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), |                 ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), | ||||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False)), |                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False)), | ||||||
|  |                 ('name', models.TextField()), | ||||||
|  |                 ('password_change_date', models.DateTimeField(auto_now_add=True)), | ||||||
|             ], |             ], | ||||||
|             options={ |             options={ | ||||||
|                 'verbose_name': 'user', |                 'verbose_name': 'user', | ||||||
| @ -44,39 +49,17 @@ class Migration(migrations.Migration): | |||||||
|                 ('objects', django.contrib.auth.models.UserManager()), |                 ('objects', django.contrib.auth.models.UserManager()), | ||||||
|             ], |             ], | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='Group', |  | ||||||
|             fields=[ |  | ||||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), |  | ||||||
|                 ('name', models.CharField(max_length=80, verbose_name='name')), |  | ||||||
|                 ('extra_data', models.TextField(blank=True)), |  | ||||||
|                 ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group')), |  | ||||||
|             ], |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='Invitation', |  | ||||||
|             fields=[ |  | ||||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), |  | ||||||
|                 ('expires', models.DateTimeField(blank=True, default=None, null=True)), |  | ||||||
|                 ('fixed_username', models.TextField(blank=True, default=None)), |  | ||||||
|                 ('fixed_email', models.TextField(blank=True, default=None)), |  | ||||||
|                 ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 'verbose_name': 'Invitation', |  | ||||||
|                 'verbose_name_plural': 'Invitations', |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='Policy', |             name='Policy', | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('created', models.DateField(auto_now_add=True)), |                 ('created', models.DateTimeField(auto_now_add=True)), | ||||||
|                 ('last_updated', models.DateTimeField(auto_now=True)), |                 ('last_updated', models.DateTimeField(auto_now=True)), | ||||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), |                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||||
|                 ('name', models.TextField(blank=True, null=True)), |                 ('name', models.TextField(blank=True, null=True)), | ||||||
|                 ('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)), |                 ('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)), | ||||||
|                 ('negate', models.BooleanField(default=False)), |                 ('negate', models.BooleanField(default=False)), | ||||||
|                 ('order', models.IntegerField(default=0)), |                 ('order', models.IntegerField(default=0)), | ||||||
|  |                 ('timeout', models.IntegerField(default=30)), | ||||||
|             ], |             ], | ||||||
|             options={ |             options={ | ||||||
|                 'abstract': False, |                 'abstract': False, | ||||||
| @ -85,28 +68,136 @@ class Migration(migrations.Migration): | |||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='PolicyModel', |             name='PolicyModel', | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('created', models.DateField(auto_now_add=True)), |                 ('created', models.DateTimeField(auto_now_add=True)), | ||||||
|                 ('last_updated', models.DateTimeField(auto_now=True)), |                 ('last_updated', models.DateTimeField(auto_now=True)), | ||||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), |                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||||
|  |                 ('policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')), | ||||||
|             ], |             ], | ||||||
|             options={ |             options={ | ||||||
|                 'abstract': False, |                 'abstract': False, | ||||||
|             }, |             }, | ||||||
|         ), |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='PropertyMapping', | ||||||
|  |             fields=[ | ||||||
|  |                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||||
|  |                 ('name', models.TextField()), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Property Mapping', | ||||||
|  |                 'verbose_name_plural': 'Property Mappings', | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='DebugPolicy', | ||||||
|  |             fields=[ | ||||||
|  |                 ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')), | ||||||
|  |                 ('result', models.BooleanField(default=False)), | ||||||
|  |                 ('wait_min', models.IntegerField(default=5)), | ||||||
|  |                 ('wait_max', models.IntegerField(default=30)), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Debug Policy', | ||||||
|  |                 'verbose_name_plural': 'Debug Policies', | ||||||
|  |             }, | ||||||
|  |             bases=('passbook_core.policy',), | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Factor', | ||||||
|  |             fields=[ | ||||||
|  |                 ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')), | ||||||
|  |                 ('name', models.TextField()), | ||||||
|  |                 ('slug', models.SlugField(unique=True)), | ||||||
|  |                 ('order', models.IntegerField()), | ||||||
|  |                 ('enabled', models.BooleanField(default=True)), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'abstract': False, | ||||||
|  |             }, | ||||||
|  |             bases=('passbook_core.policymodel',), | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Source', | ||||||
|  |             fields=[ | ||||||
|  |                 ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')), | ||||||
|  |                 ('name', models.TextField()), | ||||||
|  |                 ('slug', models.SlugField()), | ||||||
|  |                 ('enabled', models.BooleanField(default=True)), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'abstract': False, | ||||||
|  |             }, | ||||||
|  |             bases=('passbook_core.policymodel',), | ||||||
|  |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='Provider', |             name='Provider', | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||||
|  |                 ('property_mappings', models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping')), | ||||||
|             ], |             ], | ||||||
|         ), |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Nonce', | ||||||
|  |             fields=[ | ||||||
|  |                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||||
|  |                 ('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)), | ||||||
|  |                 ('expiring', models.BooleanField(default=True)), | ||||||
|  |                 ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Nonce', | ||||||
|  |                 'verbose_name_plural': 'Nonces', | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Invitation', | ||||||
|  |             fields=[ | ||||||
|  |                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||||
|  |                 ('expires', models.DateTimeField(blank=True, default=None, null=True)), | ||||||
|  |                 ('fixed_username', models.TextField(blank=True, default=None)), | ||||||
|  |                 ('fixed_email', models.TextField(blank=True, default=None)), | ||||||
|  |                 ('needs_confirmation', models.BooleanField(default=True)), | ||||||
|  |                 ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Invitation', | ||||||
|  |                 'verbose_name_plural': 'Invitations', | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Group', | ||||||
|  |             fields=[ | ||||||
|  |                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||||
|  |                 ('name', models.CharField(max_length=80, verbose_name='name')), | ||||||
|  |                 ('tags', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)), | ||||||
|  |                 ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group')), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'unique_together': {('name', 'parent')}, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='user', | ||||||
|  |             name='groups', | ||||||
|  |             field=models.ManyToManyField(to='passbook_core.Group'), | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='user', | ||||||
|  |             name='user_permissions', | ||||||
|  |             field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), | ||||||
|  |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='UserSourceConnection', |             name='UserSourceConnection', | ||||||
|             fields=[ |             fields=[ | ||||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||||
|                 ('created', models.DateField(auto_now_add=True)), |                 ('created', models.DateTimeField(auto_now_add=True)), | ||||||
|                 ('last_updated', models.DateTimeField(auto_now=True)), |                 ('last_updated', models.DateTimeField(auto_now=True)), | ||||||
|                 ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |                 ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||||||
|  |                 ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source')), | ||||||
|             ], |             ], | ||||||
|  |             options={ | ||||||
|  |                 'unique_together': {('user', 'source')}, | ||||||
|  |             }, | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |         migrations.CreateModel( | ||||||
|             name='Application', |             name='Application', | ||||||
| @ -124,131 +215,9 @@ class Migration(migrations.Migration): | |||||||
|             }, |             }, | ||||||
|             bases=('passbook_core.policymodel',), |             bases=('passbook_core.policymodel',), | ||||||
|         ), |         ), | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='DebugPolicy', |  | ||||||
|             fields=[ |  | ||||||
|                 ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')), |  | ||||||
|                 ('result', models.BooleanField(default=False)), |  | ||||||
|                 ('wait_min', models.IntegerField(default=5)), |  | ||||||
|                 ('wait_max', models.IntegerField(default=30)), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 'verbose_name': 'Debug Policy', |  | ||||||
|                 'verbose_name_plural': 'Debug Policys', |  | ||||||
|             }, |  | ||||||
|             bases=('passbook_core.policy',), |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='Factor', |  | ||||||
|             fields=[ |  | ||||||
|                 ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')), |  | ||||||
|                 ('name', models.TextField()), |  | ||||||
|                 ('slug', models.SlugField(unique=True)), |  | ||||||
|                 ('order', models.IntegerField()), |  | ||||||
|                 ('type', models.TextField(unique=True)), |  | ||||||
|                 ('enabled', models.BooleanField(default=True)), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 'abstract': False, |  | ||||||
|             }, |  | ||||||
|             bases=('passbook_core.policymodel',), |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='FieldMatcherPolicy', |  | ||||||
|             fields=[ |  | ||||||
|                 ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')), |  | ||||||
|                 ('user_field', models.TextField(choices=[('username', 'Username'), ('first_name', 'First Name'), ('last_name', 'Last Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')])), |  | ||||||
|                 ('match_action', models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('endswith', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50)), |  | ||||||
|                 ('value', models.TextField()), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 'verbose_name': 'Field matcher Policy', |  | ||||||
|                 'verbose_name_plural': 'Field matcher Policys', |  | ||||||
|             }, |  | ||||||
|             bases=('passbook_core.policy',), |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='PasswordPolicyPolicy', |  | ||||||
|             fields=[ |  | ||||||
|                 ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')), |  | ||||||
|                 ('amount_uppercase', models.IntegerField(default=0)), |  | ||||||
|                 ('amount_lowercase', models.IntegerField(default=0)), |  | ||||||
|                 ('amount_symbols', models.IntegerField(default=0)), |  | ||||||
|                 ('length_min', models.IntegerField(default=0)), |  | ||||||
|                 ('symbol_charset', models.TextField(default='!\\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ')), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 'verbose_name': 'Password Policy Policy', |  | ||||||
|                 'verbose_name_plural': 'Password Policy Policys', |  | ||||||
|             }, |  | ||||||
|             bases=('passbook_core.policy',), |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='Source', |  | ||||||
|             fields=[ |  | ||||||
|                 ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')), |  | ||||||
|                 ('name', models.TextField()), |  | ||||||
|                 ('slug', models.SlugField()), |  | ||||||
|                 ('enabled', models.BooleanField(default=True)), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 'abstract': False, |  | ||||||
|             }, |  | ||||||
|             bases=('passbook_core.policymodel',), |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='WebhookPolicy', |  | ||||||
|             fields=[ |  | ||||||
|                 ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')), |  | ||||||
|                 ('url', models.URLField()), |  | ||||||
|                 ('method', models.CharField(choices=[('GET', 'GET'), ('POST', 'POST'), ('PATCH', 'PATCH'), ('DELETE', 'DELETE'), ('PUT', 'PUT')], max_length=10)), |  | ||||||
|                 ('json_body', models.TextField()), |  | ||||||
|                 ('json_headers', models.TextField()), |  | ||||||
|                 ('result_jsonpath', models.TextField()), |  | ||||||
|                 ('result_json_value', models.TextField()), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 'verbose_name': 'Webhook Policy', |  | ||||||
|                 'verbose_name_plural': 'Webhook Policys', |  | ||||||
|             }, |  | ||||||
|             bases=('passbook_core.policy',), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='policymodel', |  | ||||||
|             name='policies', |  | ||||||
|             field=models.ManyToManyField(blank=True, to='passbook_core.Policy'), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='user', |  | ||||||
|             name='groups', |  | ||||||
|             field=models.ManyToManyField(to='passbook_core.Group'), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='user', |  | ||||||
|             name='user_permissions', |  | ||||||
|             field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='usersourceconnection', |  | ||||||
|             name='source', |  | ||||||
|             field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source'), |  | ||||||
|         ), |  | ||||||
|         migrations.AlterUniqueTogether( |  | ||||||
|             name='group', |  | ||||||
|             unique_together={('name', 'parent')}, |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='user', |  | ||||||
|             name='applications', |  | ||||||
|             field=models.ManyToManyField(to='passbook_core.Application'), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |         migrations.AddField( | ||||||
|             model_name='user', |             model_name='user', | ||||||
|             name='sources', |             name='sources', | ||||||
|             field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'), |             field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'), | ||||||
|         ), |         ), | ||||||
|         migrations.AlterUniqueTogether( |  | ||||||
|             name='usersourceconnection', |  | ||||||
|             unique_together={('user', 'source')}, |  | ||||||
|         ), |  | ||||||
|     ] |     ] | ||||||
|  | |||||||
| @ -1,29 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-16 10:02 |  | ||||||
|  |  | ||||||
| from django.db import migrations |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0001_initial'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AlterModelOptions( |  | ||||||
|             name='debugpolicy', |  | ||||||
|             options={'verbose_name': 'Debug Policy', 'verbose_name_plural': 'Debug Policies'}, |  | ||||||
|         ), |  | ||||||
|         migrations.AlterModelOptions( |  | ||||||
|             name='fieldmatcherpolicy', |  | ||||||
|             options={'verbose_name': 'Field matcher Policy', 'verbose_name_plural': 'Field matcher Policies'}, |  | ||||||
|         ), |  | ||||||
|         migrations.AlterModelOptions( |  | ||||||
|             name='passwordpolicypolicy', |  | ||||||
|             options={'verbose_name': 'Password Policy Policy', 'verbose_name_plural': 'Password Policy Policies'}, |  | ||||||
|         ), |  | ||||||
|         migrations.AlterModelOptions( |  | ||||||
|             name='webhookpolicy', |  | ||||||
|             options={'verbose_name': 'Webhook Policy', 'verbose_name_plural': 'Webhook Policies'}, |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,4 +1,4 @@ | |||||||
| # Generated by Django 2.2 on 2019-04-18 09:09 | # Generated by Django 2.2.6 on 2019-10-10 11:48 | ||||||
| 
 | 
 | ||||||
| from django.db import migrations, models | from django.db import migrations, models | ||||||
| 
 | 
 | ||||||
| @ -6,13 +6,13 @@ from django.db import migrations, models | |||||||
| class Migration(migrations.Migration): | class Migration(migrations.Migration): | ||||||
| 
 | 
 | ||||||
|     dependencies = [ |     dependencies = [ | ||||||
|         ('passbook_saml_idp', '0002_samlpropertymapping'), |         ('passbook_core', '0001_initial'), | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|     operations = [ |     operations = [ | ||||||
|         migrations.AddField( |         migrations.AddField( | ||||||
|             model_name='samlprovider', |             model_name='nonce', | ||||||
|             name='audience', |             name='description', | ||||||
|             field=models.TextField(blank=True, default=''), |             field=models.TextField(blank=True, default=''), | ||||||
|         ), |         ), | ||||||
|     ] |     ] | ||||||
| @ -1,17 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-16 10:04 |  | ||||||
|  |  | ||||||
| from django.db import migrations |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0002_auto_20190216_1002'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.RenameModel( |  | ||||||
|             old_name='PasswordPolicyPolicy', |  | ||||||
|             new_name='PasswordPolicy', |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,17 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-16 10:13 |  | ||||||
|  |  | ||||||
| from django.db import migrations |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0003_auto_20190216_1004'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AlterModelOptions( |  | ||||||
|             name='passwordpolicy', |  | ||||||
|             options={'verbose_name': 'Password Policy', 'verbose_name_plural': 'Password Policies'}, |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,28 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-21 12:01 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0004_auto_20190216_1013'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name='policy', |  | ||||||
|             name='created', |  | ||||||
|             field=models.DateTimeField(auto_now_add=True), |  | ||||||
|         ), |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name='policymodel', |  | ||||||
|             name='created', |  | ||||||
|             field=models.DateTimeField(auto_now_add=True), |  | ||||||
|         ), |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name='usersourceconnection', |  | ||||||
|             name='created', |  | ||||||
|             field=models.DateTimeField(auto_now_add=True), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,19 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-21 12:32 |  | ||||||
|  |  | ||||||
| import django.contrib.postgres.fields.jsonb |  | ||||||
| from django.db import migrations |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0005_auto_20190221_1201'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='factor', |  | ||||||
|             name='arguments', |  | ||||||
|             field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,19 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-21 12:33 |  | ||||||
|  |  | ||||||
| import django.contrib.postgres.fields.jsonb |  | ||||||
| from django.db import migrations |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0006_factor_arguments'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name='factor', |  | ||||||
|             name='arguments', |  | ||||||
|             field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-21 15:16 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0007_auto_20190221_1233'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name='fieldmatcherpolicy', |  | ||||||
|             name='match_action', |  | ||||||
|             field=models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('contains', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,44 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-24 09:50 |  | ||||||
|  |  | ||||||
| import django.contrib.postgres.fields |  | ||||||
| import django.db.models.deletion |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0008_auto_20190221_1516'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='DummyFactor', |  | ||||||
|             fields=[ |  | ||||||
|                 ('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 'abstract': False, |  | ||||||
|             }, |  | ||||||
|             bases=('passbook_core.factor',), |  | ||||||
|         ), |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='PasswordFactor', |  | ||||||
|             fields=[ |  | ||||||
|                 ('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')), |  | ||||||
|                 ('backends', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 'abstract': False, |  | ||||||
|             }, |  | ||||||
|             bases=('passbook_core.factor',), |  | ||||||
|         ), |  | ||||||
|         migrations.RemoveField( |  | ||||||
|             model_name='factor', |  | ||||||
|             name='arguments', |  | ||||||
|         ), |  | ||||||
|         migrations.RemoveField( |  | ||||||
|             model_name='factor', |  | ||||||
|             name='type', |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,21 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-24 10:16 |  | ||||||
|  |  | ||||||
| from django.db import migrations |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0009_auto_20190224_0950'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AlterModelOptions( |  | ||||||
|             name='dummyfactor', |  | ||||||
|             options={'verbose_name': 'Dummy Factor', 'verbose_name_plural': 'Dummy Factors'}, |  | ||||||
|         ), |  | ||||||
|         migrations.AlterModelOptions( |  | ||||||
|             name='passwordfactor', |  | ||||||
|             options={'verbose_name': 'Password Factor', 'verbose_name_plural': 'Password Factors'}, |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,25 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-25 14:38 |  | ||||||
|  |  | ||||||
| import django.utils.timezone |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0010_auto_20190224_1016'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='passwordfactor', |  | ||||||
|             name='password_policies', |  | ||||||
|             field=models.ManyToManyField(blank=True, to='passbook_core.Policy'), |  | ||||||
|         ), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='user', |  | ||||||
|             name='password_change_date', |  | ||||||
|             field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), |  | ||||||
|             preserve_default=False, |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,31 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-25 19:12 |  | ||||||
|  |  | ||||||
| import uuid |  | ||||||
|  |  | ||||||
| import django.db.models.deletion |  | ||||||
| from django.conf import settings |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
| import passbook.core.models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0011_auto_20190225_1438'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='Nonce', |  | ||||||
|             fields=[ |  | ||||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), |  | ||||||
|                 ('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)), |  | ||||||
|                 ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 'verbose_name': 'Nonce', |  | ||||||
|                 'verbose_name_plural': 'Nonces', |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-25 19:57 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0012_nonce'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='invitation', |  | ||||||
|             name='needs_confirmation', |  | ||||||
|             field=models.BooleanField(default=True), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,19 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-26 14:28 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0014_auto_20190226_0850'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='passwordpolicy', |  | ||||||
|             name='error_message', |  | ||||||
|             field=models.TextField(default=''), |  | ||||||
|             preserve_default=False, |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,38 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-02-27 13:55 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def migrate_names(apps, schema_editor): |  | ||||||
|     """migrate first_name and last_name to name""" |  | ||||||
|     User = apps.get_model("passbook_core", "User") |  | ||||||
|     for user in User.objects.all(): |  | ||||||
|         user.name = '%s %s' % (user.first_name, user.last_name) |  | ||||||
|         user.save() |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0015_passwordpolicy_error_message'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='user', |  | ||||||
|             name='name', |  | ||||||
|             field=models.TextField(default=''), |  | ||||||
|             preserve_default=False, |  | ||||||
|         ), |  | ||||||
|         migrations.RunPython(migrate_names), |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name='user', |  | ||||||
|             name='name', |  | ||||||
|             field=models.TextField(), |  | ||||||
|             preserve_default=False, |  | ||||||
|         ), |  | ||||||
|         migrations.AlterField( |  | ||||||
|             model_name='fieldmatcherpolicy', |  | ||||||
|             name='user_field', |  | ||||||
|             field=models.TextField(choices=[('username', 'Username'), ('name', 'Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')]), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,26 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-03-08 10:40 |  | ||||||
|  |  | ||||||
| import uuid |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0016_auto_20190227_1355'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.CreateModel( |  | ||||||
|             name='PropertyMapping', |  | ||||||
|             fields=[ |  | ||||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), |  | ||||||
|                 ('name', models.TextField()), |  | ||||||
|             ], |  | ||||||
|             options={ |  | ||||||
|                 'verbose_name': 'Property Mapping', |  | ||||||
|                 'verbose_name_plural': 'Property Mappings', |  | ||||||
|             }, |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-03-08 10:50 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0017_propertymapping'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='provider', |  | ||||||
|             name='property_mappings', |  | ||||||
|             field=models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping'), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,25 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-03-10 16:15 |  | ||||||
|  |  | ||||||
| import django.contrib.postgres.fields.hstore |  | ||||||
| from django.contrib.postgres.operations import HStoreExtension |  | ||||||
| from django.db import migrations |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0018_provider_property_mappings'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.RemoveField( |  | ||||||
|             model_name='group', |  | ||||||
|             name='extra_data', |  | ||||||
|         ), |  | ||||||
|         HStoreExtension(), |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='group', |  | ||||||
|             name='tags', |  | ||||||
|             field=django.contrib.postgres.fields.hstore.HStoreField(default=dict), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-03-21 12:03 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0020_groupmembershippolicy'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='policy', |  | ||||||
|             name='timeout', |  | ||||||
|             field=models.IntegerField(default=30), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,18 +0,0 @@ | |||||||
| # Generated by Django 2.1.7 on 2019-04-04 19:42 |  | ||||||
|  |  | ||||||
| from django.db import migrations, models |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0021_policy_timeout'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.AddField( |  | ||||||
|             model_name='nonce', |  | ||||||
|             name='expiring', |  | ||||||
|             field=models.BooleanField(default=True), |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,17 +0,0 @@ | |||||||
| # Generated by Django 2.2 on 2019-04-13 15:51 |  | ||||||
|  |  | ||||||
| from django.db import migrations |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Migration(migrations.Migration): |  | ||||||
|  |  | ||||||
|     dependencies = [ |  | ||||||
|         ('passbook_core', '0022_nonce_expiring'), |  | ||||||
|     ] |  | ||||||
|  |  | ||||||
|     operations = [ |  | ||||||
|         migrations.RemoveField( |  | ||||||
|             model_name='user', |  | ||||||
|             name='applications', |  | ||||||
|         ), |  | ||||||
|     ] |  | ||||||
| @ -1,13 +1,12 @@ | |||||||
| """passbook core models""" | """passbook core models""" | ||||||
| import re |  | ||||||
| from datetime import timedelta | from datetime import timedelta | ||||||
| from random import SystemRandom | from random import SystemRandom | ||||||
| from time import sleep | from time import sleep | ||||||
| from typing import List | from typing import Optional | ||||||
| from uuid import uuid4 | from uuid import uuid4 | ||||||
|  |  | ||||||
| from django.contrib.auth.models import AbstractUser | from django.contrib.auth.models import AbstractUser | ||||||
| from django.contrib.postgres.fields import ArrayField, HStoreField | from django.contrib.postgres.fields import JSONField | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.urls import reverse_lazy | from django.urls import reverse_lazy | ||||||
| from django.utils.timezone import now | from django.utils.timezone import now | ||||||
| @ -17,38 +16,26 @@ from structlog import get_logger | |||||||
|  |  | ||||||
| from passbook.core.signals import password_changed | from passbook.core.signals import password_changed | ||||||
| from passbook.lib.models import CreatedUpdatedModel, UUIDModel | from passbook.lib.models import CreatedUpdatedModel, UUIDModel | ||||||
|  | from passbook.policies.exceptions import PolicyException | ||||||
|  | from passbook.policies.struct import PolicyRequest, PolicyResult | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) | LOGGER = get_logger() | ||||||
|  |  | ||||||
|  |  | ||||||
| def default_nonce_duration(): | def default_nonce_duration(): | ||||||
|     """Default duration a Nonce is valid""" |     """Default duration a Nonce is valid""" | ||||||
|     return now() + timedelta(hours=4) |     return now() + timedelta(hours=4) | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyResult: |  | ||||||
|     """Small data-class to hold policy results""" |  | ||||||
|  |  | ||||||
|     passing: bool = False |  | ||||||
|     messages: List[str] = [] |  | ||||||
|  |  | ||||||
|     def __init__(self, passing: bool, *messages: str): |  | ||||||
|         self.passing = passing |  | ||||||
|         self.messages = messages |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return f"<PolicyResult passing={self.passing}>" |  | ||||||
|  |  | ||||||
| class Group(UUIDModel): | class Group(UUIDModel): | ||||||
|     """Custom Group model which supports a basic hierarchy""" |     """Custom Group model which supports a basic hierarchy""" | ||||||
|  |  | ||||||
|     name = models.CharField(_('name'), max_length=80) |     name = models.CharField(_('name'), max_length=80) | ||||||
|     parent = models.ForeignKey('Group', blank=True, null=True, |     parent = models.ForeignKey('Group', blank=True, null=True, | ||||||
|                                on_delete=models.SET_NULL, related_name='children') |                                on_delete=models.SET_NULL, related_name='children') | ||||||
|     tags = HStoreField(default=dict) |     tags = JSONField(default=dict, blank=True) | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return "Group %s" % self.name |         return f"Group {self.name}" | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
| @ -70,6 +57,7 @@ class User(AbstractUser): | |||||||
|         self.password_change_date = now() |         self.password_change_date = now() | ||||||
|         return super().set_password(password) |         return super().set_password(password) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Provider(models.Model): | class Provider(models.Model): | ||||||
|     """Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application""" |     """Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application""" | ||||||
|  |  | ||||||
| @ -83,11 +71,26 @@ class Provider(models.Model): | |||||||
|             return getattr(self, 'name') |             return getattr(self, 'name') | ||||||
|         return super().__str__() |         return super().__str__() | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyModel(UUIDModel, CreatedUpdatedModel): | class PolicyModel(UUIDModel, CreatedUpdatedModel): | ||||||
|     """Base model which can have policies applied to it""" |     """Base model which can have policies applied to it""" | ||||||
|  |  | ||||||
|     policies = models.ManyToManyField('Policy', blank=True) |     policies = models.ManyToManyField('Policy', blank=True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserSettings: | ||||||
|  |     """Dataclass for Factor and Source's user_settings""" | ||||||
|  |  | ||||||
|  |     name: str | ||||||
|  |     icon: str | ||||||
|  |     view_name: str | ||||||
|  |  | ||||||
|  |     def __init__(self, name: str, icon: str, view_name: str): | ||||||
|  |         self.name = name | ||||||
|  |         self.icon = icon | ||||||
|  |         self.view_name = view_name | ||||||
|  |  | ||||||
|  |  | ||||||
| class Factor(PolicyModel): | class Factor(PolicyModel): | ||||||
|     """Authentication factor, multiple instances of the same Factor can be used""" |     """Authentication factor, multiple instances of the same Factor can be used""" | ||||||
|  |  | ||||||
| @ -100,55 +103,14 @@ class Factor(PolicyModel): | |||||||
|     type = '' |     type = '' | ||||||
|     form = '' |     form = '' | ||||||
|  |  | ||||||
|     def has_user_settings(self): |     def user_settings(self) -> Optional[UserSettings]: | ||||||
|         """Entrypoint to integrate with User settings. Can either return False if no |         """Entrypoint to integrate with User settings. Can either return None if no | ||||||
|         user settings are available, or a tuple or string, string, string where the first string |         user settings are available, or an instanace of UserSettings.""" | ||||||
|         is the name the item has, the second string is the icon and the third is the view-name.""" |         return None | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return "Factor %s" % self.slug |         return f"Factor {self.slug}" | ||||||
|  |  | ||||||
| class PasswordFactor(Factor): |  | ||||||
|     """Password-based Django-backend Authentication Factor""" |  | ||||||
|  |  | ||||||
|     backends = ArrayField(models.TextField()) |  | ||||||
|     password_policies = models.ManyToManyField('Policy', blank=True) |  | ||||||
|  |  | ||||||
|     type = 'passbook.core.auth.factors.password.PasswordFactor' |  | ||||||
|     form = 'passbook.core.forms.factors.PasswordFactorForm' |  | ||||||
|  |  | ||||||
|     def has_user_settings(self): |  | ||||||
|         return _('Change Password'), 'pficon-key', 'passbook_core:user-change-password' |  | ||||||
|  |  | ||||||
|     def password_passes(self, user: User) -> bool: |  | ||||||
|         """Return true if user's password passes, otherwise False or raise Exception""" |  | ||||||
|         for policy in self.policies.all(): |  | ||||||
|             if not policy.passes(user): |  | ||||||
|                 return False |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Password Factor %s" % self.slug |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         verbose_name = _('Password Factor') |  | ||||||
|         verbose_name_plural = _('Password Factors') |  | ||||||
|  |  | ||||||
| class DummyFactor(Factor): |  | ||||||
|     """Dummy factor, mostly used to debug""" |  | ||||||
|  |  | ||||||
|     type = 'passbook.core.auth.factors.dummy.DummyFactor' |  | ||||||
|     form = 'passbook.core.forms.factors.DummyFactorForm' |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return "Dummy Factor %s" % self.slug |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         verbose_name = _('Dummy Factor') |  | ||||||
|         verbose_name_plural = _('Dummy Factors') |  | ||||||
|  |  | ||||||
| class Application(PolicyModel): | class Application(PolicyModel): | ||||||
|     """Every Application which uses passbook for authentication/identification/authorization |     """Every Application which uses passbook for authentication/identification/authorization | ||||||
| @ -174,6 +136,7 @@ class Application(PolicyModel): | |||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return self.name |         return self.name | ||||||
|  |  | ||||||
|  |  | ||||||
| class Source(PolicyModel): | class Source(PolicyModel): | ||||||
|     """Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server""" |     """Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server""" | ||||||
|  |  | ||||||
| @ -200,15 +163,15 @@ class Source(PolicyModel): | |||||||
|         """Return additional Info, such as a callback URL. Show in the administration interface.""" |         """Return additional Info, such as a callback URL. Show in the administration interface.""" | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     def has_user_settings(self): |     def user_settings(self) -> Optional[UserSettings]: | ||||||
|         """Entrypoint to integrate with User settings. Can either return False if no |         """Entrypoint to integrate with User settings. Can either return None if no | ||||||
|         user settings are available, or a tuple or string, string, string where the first string |         user settings are available, or an instanace of UserSettings.""" | ||||||
|         is the name the item has, the second string is the icon and the third is the view-name.""" |         return None | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return self.name |         return self.name | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserSourceConnection(CreatedUpdatedModel): | class UserSourceConnection(CreatedUpdatedModel): | ||||||
|     """Connection between User and Source.""" |     """Connection between User and Source.""" | ||||||
|  |  | ||||||
| @ -219,6 +182,7 @@ class UserSourceConnection(CreatedUpdatedModel): | |||||||
|  |  | ||||||
|         unique_together = (('user', 'source'),) |         unique_together = (('user', 'source'),) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Policy(UUIDModel, CreatedUpdatedModel): | class Policy(UUIDModel, CreatedUpdatedModel): | ||||||
|     """Policies which specify if a user is authorized to use an Application. Can be overridden by |     """Policies which specify if a user is authorized to use an Application. Can be overridden by | ||||||
|     other types to add other fields, more logic, etc.""" |     other types to add other fields, more logic, etc.""" | ||||||
| @ -241,151 +205,12 @@ class Policy(UUIDModel, CreatedUpdatedModel): | |||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         if self.name: |         if self.name: | ||||||
|             return self.name |             return self.name | ||||||
|         return "%s action %s" % (self.name, self.action) |         return f"{self.name} action {self.action}" | ||||||
|  |  | ||||||
|     def passes(self, user: User) -> PolicyResult: |     def passes(self, request: PolicyRequest) -> PolicyResult: | ||||||
|         """Check if user instance passes this policy""" |         """Check if user instance passes this policy""" | ||||||
|         raise NotImplementedError() |         raise PolicyException() | ||||||
|  |  | ||||||
| class FieldMatcherPolicy(Policy): |  | ||||||
|     """Policy which checks if a field of the User model matches/doesn't match a |  | ||||||
|     certain pattern""" |  | ||||||
|  |  | ||||||
|     MATCH_STARTSWITH = 'startswith' |  | ||||||
|     MATCH_ENDSWITH = 'endswith' |  | ||||||
|     MATCH_CONTAINS = 'contains' |  | ||||||
|     MATCH_REGEXP = 'regexp' |  | ||||||
|     MATCH_EXACT = 'exact' |  | ||||||
|  |  | ||||||
|     MATCHES = ( |  | ||||||
|         (MATCH_STARTSWITH, _('Starts with')), |  | ||||||
|         (MATCH_ENDSWITH, _('Ends with')), |  | ||||||
|         (MATCH_CONTAINS, _('Contains')), |  | ||||||
|         (MATCH_REGEXP, _('Regexp')), |  | ||||||
|         (MATCH_EXACT, _('Exact')), |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     USER_FIELDS = ( |  | ||||||
|         ('username', _('Username'),), |  | ||||||
|         ('name', _('Name'),), |  | ||||||
|         ('email', _('E-Mail'),), |  | ||||||
|         ('is_staff', _('Is staff'),), |  | ||||||
|         ('is_active', _('Is active'),), |  | ||||||
|         ('data_joined', _('Date joined'),), |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     user_field = models.TextField(choices=USER_FIELDS) |  | ||||||
|     match_action = models.CharField(max_length=50, choices=MATCHES) |  | ||||||
|     value = models.TextField() |  | ||||||
|  |  | ||||||
|     form = 'passbook.core.forms.policies.FieldMatcherPolicyForm' |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         description = "%s, user.%s %s '%s'" % (self.name, self.user_field, |  | ||||||
|                                                self.match_action, self.value) |  | ||||||
|         if self.name: |  | ||||||
|             description = "%s: %s" % (self.name, description) |  | ||||||
|         return description |  | ||||||
|  |  | ||||||
|     def passes(self, user: User) -> PolicyResult: |  | ||||||
|         """Check if user instance passes this role""" |  | ||||||
|         if not hasattr(user, self.user_field): |  | ||||||
|             raise ValueError("Field does not exist") |  | ||||||
|         user_field_value = getattr(user, self.user_field, None) |  | ||||||
|         LOGGER.debug("Checked '%s' %s with '%s'...", |  | ||||||
|                      user_field_value, self.match_action, self.value) |  | ||||||
|         passes = False |  | ||||||
|         if self.match_action == FieldMatcherPolicy.MATCH_STARTSWITH: |  | ||||||
|             passes = user_field_value.startswith(self.value) |  | ||||||
|         if self.match_action == FieldMatcherPolicy.MATCH_ENDSWITH: |  | ||||||
|             passes = user_field_value.endswith(self.value) |  | ||||||
|         if self.match_action == FieldMatcherPolicy.MATCH_CONTAINS: |  | ||||||
|             passes = self.value in user_field_value |  | ||||||
|         if self.match_action == FieldMatcherPolicy.MATCH_REGEXP: |  | ||||||
|             pattern = re.compile(self.value) |  | ||||||
|             passes = bool(pattern.match(user_field_value)) |  | ||||||
|         if self.match_action == FieldMatcherPolicy.MATCH_EXACT: |  | ||||||
|             passes = user_field_value == self.value |  | ||||||
|  |  | ||||||
|         LOGGER.debug("User got '%r'", passes) |  | ||||||
|         return PolicyResult(passes) |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         verbose_name = _('Field matcher Policy') |  | ||||||
|         verbose_name_plural = _('Field matcher Policies') |  | ||||||
|  |  | ||||||
| class PasswordPolicy(Policy): |  | ||||||
|     """Policy to make sure passwords have certain properties""" |  | ||||||
|  |  | ||||||
|     amount_uppercase = models.IntegerField(default=0) |  | ||||||
|     amount_lowercase = models.IntegerField(default=0) |  | ||||||
|     amount_symbols = models.IntegerField(default=0) |  | ||||||
|     length_min = models.IntegerField(default=0) |  | ||||||
|     symbol_charset = models.TextField(default=r"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ") |  | ||||||
|     error_message = models.TextField() |  | ||||||
|  |  | ||||||
|     form = 'passbook.core.forms.policies.PasswordPolicyForm' |  | ||||||
|  |  | ||||||
|     def passes(self, user: User) -> PolicyResult: |  | ||||||
|         # Only check if password is being set |  | ||||||
|         if not hasattr(user, '__password__'): |  | ||||||
|             return PolicyResult(True) |  | ||||||
|         password = getattr(user, '__password__') |  | ||||||
|  |  | ||||||
|         filter_regex = r'' |  | ||||||
|         if self.amount_lowercase > 0: |  | ||||||
|             filter_regex += r'[a-z]{%d,}' % self.amount_lowercase |  | ||||||
|         if self.amount_uppercase > 0: |  | ||||||
|             filter_regex += r'[A-Z]{%d,}' % self.amount_uppercase |  | ||||||
|         if self.amount_symbols > 0: |  | ||||||
|             filter_regex += r'[%s]{%d,}' % (self.symbol_charset, self.amount_symbols) |  | ||||||
|         result = bool(re.compile(filter_regex).match(password)) |  | ||||||
|         LOGGER.debug("User got %r", result) |  | ||||||
|         if not result: |  | ||||||
|             return PolicyResult(result, self.error_message) |  | ||||||
|         return PolicyResult(result) |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         verbose_name = _('Password Policy') |  | ||||||
|         verbose_name_plural = _('Password Policies') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WebhookPolicy(Policy): |  | ||||||
|     """Policy that asks webhook""" |  | ||||||
|  |  | ||||||
|     METHOD_GET = 'GET' |  | ||||||
|     METHOD_POST = 'POST' |  | ||||||
|     METHOD_PATCH = 'PATCH' |  | ||||||
|     METHOD_DELETE = 'DELETE' |  | ||||||
|     METHOD_PUT = 'PUT' |  | ||||||
|  |  | ||||||
|     METHODS = ( |  | ||||||
|         (METHOD_GET, METHOD_GET), |  | ||||||
|         (METHOD_POST, METHOD_POST), |  | ||||||
|         (METHOD_PATCH, METHOD_PATCH), |  | ||||||
|         (METHOD_DELETE, METHOD_DELETE), |  | ||||||
|         (METHOD_PUT, METHOD_PUT), |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     url = models.URLField() |  | ||||||
|     method = models.CharField(max_length=10, choices=METHODS) |  | ||||||
|     json_body = models.TextField() |  | ||||||
|     json_headers = models.TextField() |  | ||||||
|     result_jsonpath = models.TextField() |  | ||||||
|     result_json_value = models.TextField() |  | ||||||
|  |  | ||||||
|     form = 'passbook.core.forms.policies.WebhookPolicyForm' |  | ||||||
|  |  | ||||||
|     def passes(self, user: User) -> PolicyResult: |  | ||||||
|         """Call webhook asynchronously and report back""" |  | ||||||
|         raise NotImplementedError() |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         verbose_name = _('Webhook Policy') |  | ||||||
|         verbose_name_plural = _('Webhook Policies') |  | ||||||
|  |  | ||||||
| class DebugPolicy(Policy): | class DebugPolicy(Policy): | ||||||
|     """Policy used for debugging the PolicyEngine. Returns a fixed result, |     """Policy used for debugging the PolicyEngine. Returns a fixed result, | ||||||
| @ -397,10 +222,10 @@ class DebugPolicy(Policy): | |||||||
|  |  | ||||||
|     form = 'passbook.core.forms.policies.DebugPolicyForm' |     form = 'passbook.core.forms.policies.DebugPolicyForm' | ||||||
|  |  | ||||||
|     def passes(self, user: User) -> PolicyResult: |     def passes(self, request: PolicyRequest) -> PolicyResult: | ||||||
|         """Wait random time then return result""" |         """Wait random time then return result""" | ||||||
|         wait = SystemRandom().randrange(self.wait_min, self.wait_max) |         wait = SystemRandom().randrange(self.wait_min, self.wait_max) | ||||||
|         LOGGER.debug("Policy '%s' waiting for %ds", self.name, wait) |         LOGGER.debug("Policy waiting", policy=self, delay=wait) | ||||||
|         sleep(wait) |         sleep(wait) | ||||||
|         return PolicyResult(self.result, 'Debugging') |         return PolicyResult(self.result, 'Debugging') | ||||||
|  |  | ||||||
| @ -409,35 +234,6 @@ class DebugPolicy(Policy): | |||||||
|         verbose_name = _('Debug Policy') |         verbose_name = _('Debug Policy') | ||||||
|         verbose_name_plural = _('Debug Policies') |         verbose_name_plural = _('Debug Policies') | ||||||
|  |  | ||||||
| class GroupMembershipPolicy(Policy): |  | ||||||
|     """Policy to check if the user is member in a certain group""" |  | ||||||
|  |  | ||||||
|     group = models.ForeignKey('Group', on_delete=models.CASCADE) |  | ||||||
|  |  | ||||||
|     form = 'passbook.core.forms.policies.GroupMembershipPolicyForm' |  | ||||||
|  |  | ||||||
|     def passes(self, user: User) -> PolicyResult: |  | ||||||
|         return PolicyResult(self.group.user_set.filter(pk=user.pk).exists()) |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         verbose_name = _('Group Membership Policy') |  | ||||||
|         verbose_name_plural = _('Group Membership Policies') |  | ||||||
|  |  | ||||||
| class SSOLoginPolicy(Policy): |  | ||||||
|     """Policy that applies to users that have authenticated themselves through SSO""" |  | ||||||
|  |  | ||||||
|     form = 'passbook.core.forms.policies.SSOLoginPolicyForm' |  | ||||||
|  |  | ||||||
|     def passes(self, user) -> PolicyResult: |  | ||||||
|         """Check if user instance passes this policy""" |  | ||||||
|         from passbook.core.auth.view import AuthenticationView |  | ||||||
|         return PolicyResult(user.session.get(AuthenticationView.SESSION_IS_SSO_LOGIN, False)) |  | ||||||
|  |  | ||||||
|     class Meta: |  | ||||||
|  |  | ||||||
|         verbose_name = _('SSO Login Policy') |  | ||||||
|         verbose_name_plural = _('SSO Login Policies') |  | ||||||
|  |  | ||||||
| class Invitation(UUIDModel): | class Invitation(UUIDModel): | ||||||
|     """Single-use invitation link""" |     """Single-use invitation link""" | ||||||
| @ -451,31 +247,39 @@ class Invitation(UUIDModel): | |||||||
|     @property |     @property | ||||||
|     def link(self): |     def link(self): | ||||||
|         """Get link to use invitation""" |         """Get link to use invitation""" | ||||||
|         return reverse_lazy('passbook_core:auth-sign-up') + '?invitation=%s' % self.uuid |         return reverse_lazy('passbook_core:auth-sign-up') + f'?invitation={self.uuid.hex}' | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return "Invitation %s created by %s" % (self.uuid, self.created_by) |         return f"Invitation {self.uuid.hex} created by {self.created_by}" | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         verbose_name = _('Invitation') |         verbose_name = _('Invitation') | ||||||
|         verbose_name_plural = _('Invitations') |         verbose_name_plural = _('Invitations') | ||||||
|  |  | ||||||
|  |  | ||||||
| class Nonce(UUIDModel): | class Nonce(UUIDModel): | ||||||
|     """One-time link for password resets/sign-up-confirmations""" |     """One-time link for password resets/sign-up-confirmations""" | ||||||
|  |  | ||||||
|     expires = models.DateTimeField(default=default_nonce_duration) |     expires = models.DateTimeField(default=default_nonce_duration) | ||||||
|     user = models.ForeignKey('User', on_delete=models.CASCADE) |     user = models.ForeignKey('User', on_delete=models.CASCADE) | ||||||
|     expiring = models.BooleanField(default=True) |     expiring = models.BooleanField(default=True) | ||||||
|  |     description = models.TextField(default='', blank=True) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def is_expired(self) -> bool: | ||||||
|  |         """Check if nonce is expired yet.""" | ||||||
|  |         return now() > self.expires | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return "Nonce %s (expires=%s)" % (self.uuid.hex, self.expires) |         return f"Nonce f{self.uuid.hex} {self.description} (expires={self.expires})" | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         verbose_name = _('Nonce') |         verbose_name = _('Nonce') | ||||||
|         verbose_name_plural = _('Nonces') |         verbose_name_plural = _('Nonces') | ||||||
|  |  | ||||||
|  |  | ||||||
| class PropertyMapping(UUIDModel): | class PropertyMapping(UUIDModel): | ||||||
|     """User-defined key -> x mapping which can be used by providers to expose extra data.""" |     """User-defined key -> x mapping which can be used by providers to expose extra data.""" | ||||||
|  |  | ||||||
| @ -485,7 +289,7 @@ class PropertyMapping(UUIDModel): | |||||||
|     objects = InheritanceManager() |     objects = InheritanceManager() | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return "Property Mapping %s" % self.name |         return f"Property Mapping {self.name}" | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|  | |||||||
| @ -5,37 +5,20 @@ from django.db.models.signals import post_save | |||||||
| from django.dispatch import receiver | from django.dispatch import receiver | ||||||
| from structlog import get_logger | from structlog import get_logger | ||||||
|  |  | ||||||
| from passbook.core.exceptions import PasswordPolicyInvalid | LOGGER = get_logger() | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) |  | ||||||
|  |  | ||||||
| user_signed_up = Signal(providing_args=['request', 'user']) | user_signed_up = Signal(providing_args=['request', 'user']) | ||||||
| invitation_created = Signal(providing_args=['request', 'invitation']) | invitation_created = Signal(providing_args=['request', 'invitation']) | ||||||
| invitation_used = Signal(providing_args=['request', 'invitation', 'user']) | invitation_used = Signal(providing_args=['request', 'invitation', 'user']) | ||||||
| password_changed = Signal(providing_args=['user', 'password']) | password_changed = Signal(providing_args=['user', 'password']) | ||||||
|  |  | ||||||
| @receiver(password_changed) |  | ||||||
| # pylint: disable=unused-argument |  | ||||||
| def password_policy_checker(sender, password, **kwargs): |  | ||||||
|     """Run password through all password policies which are applied to the user""" |  | ||||||
|     from passbook.core.models import PasswordFactor |  | ||||||
|     from passbook.policy.engine import PolicyEngine |  | ||||||
|     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() |  | ||||||
|         passing, messages = policy_engine.result |  | ||||||
|         if not passing: |  | ||||||
|             raise PasswordPolicyInvalid(*messages) |  | ||||||
|  |  | ||||||
| @receiver(post_save) | @receiver(post_save) | ||||||
| # pylint: disable=unused-argument | # pylint: disable=unused-argument | ||||||
| def invalidate_policy_cache(sender, instance, **kwargs): | def invalidate_policy_cache(sender, instance, **_): | ||||||
|     """Invalidate Policy cache when policy is updated""" |     """Invalidate Policy cache when policy is updated""" | ||||||
|     from passbook.core.models import Policy |     from passbook.core.models import Policy | ||||||
|     if isinstance(instance, Policy): |     if isinstance(instance, Policy): | ||||||
|         LOGGER.debug("Invalidating cache for %s", instance.pk) |         LOGGER.debug("Invalidating policy cache", policy=instance) | ||||||
|         keys = cache.keys("%s#*" % instance.pk) |         keys = cache.keys("%s#*" % instance.pk) | ||||||
|         cache.delete_many(keys) |         cache.delete_many(keys) | ||||||
|         LOGGER.debug("Deleted %d keys", len(keys)) |         LOGGER.debug("Deleted %d keys", len(keys)) | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								passbook/core/static/robots.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								passbook/core/static/robots.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | |||||||
|  | User-agent: * | ||||||
|  | Disallow: / | ||||||
| @ -1,28 +1,15 @@ | |||||||
| """passbook core tasks""" | """passbook core tasks""" | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
|  |  | ||||||
| from django.core.mail import EmailMultiAlternatives |  | ||||||
| from django.template.loader import render_to_string |  | ||||||
| from django.utils.html import strip_tags |  | ||||||
| from structlog import get_logger | from structlog import get_logger | ||||||
|  |  | ||||||
| from passbook.core.models import Nonce | from passbook.core.models import Nonce | ||||||
| from passbook.lib.config import CONFIG |  | ||||||
| from passbook.root.celery import CELERY_APP | from passbook.root.celery import CELERY_APP | ||||||
|  |  | ||||||
| LOGGER = get_logger(__name__) | LOGGER = get_logger() | ||||||
|  |  | ||||||
| @CELERY_APP.task() |  | ||||||
| def send_email(to_address, subject, template, context): |  | ||||||
|     """Send Email to user(s)""" |  | ||||||
|     html_content = render_to_string(template, context=context) |  | ||||||
|     text_content = strip_tags(html_content) |  | ||||||
|     msg = EmailMultiAlternatives(subject, text_content, CONFIG.y('email.from'), [to_address]) |  | ||||||
|     msg.attach_alternative(html_content, "text/html") |  | ||||||
|     msg.send() |  | ||||||
|  |  | ||||||
| @CELERY_APP.task() | @CELERY_APP.task() | ||||||
| def clean_nonces(): | def clean_nonces(): | ||||||
|     """Remove expired nonces""" |     """Remove expired nonces""" | ||||||
|     amount, _ = Nonce.objects.filter(expires__lt=datetime.now(), expiring=True).delete() |     amount, _ = Nonce.objects.filter(expires__lt=datetime.now(), expiring=True).delete() | ||||||
|     LOGGER.debug("Deleted expired %d nonces", amount) |     LOGGER.debug("Deleted expired nonces", amount=amount) | ||||||
|  | |||||||
| @ -46,9 +46,6 @@ | |||||||
|   <script src="{% static 'js/passbook.js' %}"></script> |   <script src="{% static 'js/passbook.js' %}"></script> | ||||||
|   {% block scripts %} |   {% block scripts %} | ||||||
|   {% endblock %} |   {% endblock %} | ||||||
|   <div class="modals"> |  | ||||||
|     {% include 'partials/about_modal.html' %} |  | ||||||
|   </div> |  | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
| </html> | </html> | ||||||
|  | |||||||
| @ -46,9 +46,6 @@ | |||||||
|   <script src="{% static 'js/passbook.js' %}"></script> |   <script src="{% static 'js/passbook.js' %}"></script> | ||||||
|   {% block scripts %} |   {% block scripts %} | ||||||
|   {% endblock %} |   {% endblock %} | ||||||
|   <div class="modals"> |  | ||||||
|     {% include 'partials/about_modal.html' %} |  | ||||||
|   </div> |  | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
| </html> | </html> | ||||||
|  | |||||||
| @ -23,37 +23,18 @@ | |||||||
|     </div> |     </div> | ||||||
|     <nav class="collapse navbar-collapse"> |     <nav class="collapse navbar-collapse"> | ||||||
|         <ul class="nav navbar-nav navbar-right navbar-iconic navbar-utility"> |         <ul class="nav navbar-nav navbar-right navbar-iconic navbar-utility"> | ||||||
|             <li class="dropdown"> |             <a href="{% url 'passbook_core:auth-logout' %}" class="btn btn-link nav-item-iconic" aria-haspopup="true" aria-expanded="true"> | ||||||
|                 <button class="btn btn-link dropdown-toggle nav-item-iconic" id="dropdownMenu1" data-toggle="dropdown" |                 <span title="Username" class="fa fa-sign-out"></span> | ||||||
|                     aria-haspopup="true" aria-expanded="true"> |                 <span class="dropdown-title"> | ||||||
|                     <span title="Help" class="fa pficon-help"></span> |                     {% trans 'Logout' %} | ||||||
|                 </button> |                 </span> | ||||||
|                 <ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> |             </a> | ||||||
|                     {% comment %} <li><a href="#0">Help</a></li> {% endcomment %} |             <a href="{% url 'passbook_core:user-settings' %}" class="btn btn-link nav-item-iconic" aria-haspopup="true" aria-expanded="true"> | ||||||
|                     <li><a data-toggle="modal" data-target="#about-modal" href="#0">{% trans 'About' %}</a></li> |  | ||||||
|                 </ul> |  | ||||||
|             </li> |  | ||||||
|             <li class="dropdown"> |  | ||||||
|                 <button class="btn btn-link dropdown-toggle nav-item-iconic" id="dropdownMenu2" data-toggle="dropdown" |  | ||||||
|                     aria-haspopup="true" aria-expanded="true"> |  | ||||||
|                 <span title="Username" class="fa pficon-user"></span> |                 <span title="Username" class="fa pficon-user"></span> | ||||||
|                 <span class="dropdown-title"> |                 <span class="dropdown-title"> | ||||||
|                         {{ user.username }} <span class="caret"></span> |                     {{ user.username }} | ||||||
|                 </span> |                 </span> | ||||||
|                 </button> |             </a> | ||||||
|                 <ul class="dropdown-menu" aria-labelledby="dropdownMenu2"> |  | ||||||
|                     <li> |  | ||||||
|                         <a href="{% url 'passbook_core:user-settings' %}">{% trans 'User Settings' %}</a> |  | ||||||
|                     </li> |  | ||||||
|                     <li> |  | ||||||
|                         <a href="{% url 'passbook_core:user-change-password' %}">{% trans 'Change Password' %}</a> |  | ||||||
|                     </li> |  | ||||||
|                     <li class="divider"></li> |  | ||||||
|                     <li> |  | ||||||
|                         <a href="{% url 'passbook_core:auth-logout' %}">{% trans 'Logout' %}</a> |  | ||||||
|                     </li> |  | ||||||
|                 </ul> |  | ||||||
|             </li> |  | ||||||
|         </ul> |         </ul> | ||||||
|     </nav> |     </nav> | ||||||
| </nav> | </nav> | ||||||
| @ -65,123 +46,82 @@ | |||||||
|                 <span class="list-group-item-value">{% trans 'Overview' %}</span> |                 <span class="list-group-item-value">{% trans 'Overview' %}</span> | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         {% is_active_app 'passbook_admin' as is_admin %} |  | ||||||
|         {% if user.is_superuser %} |         {% if user.is_superuser %} | ||||||
|         <li class="list-group-item {% is_active_app 'passbook_admin' %} secondary-nav-item-pf"> |         <li class="list-group-item {% is_active_url 'passbook_admin:overview' %}"> | ||||||
|             <a> |  | ||||||
|                 <span class="pficon pficon-user" data-toggle="tooltip" title="" |  | ||||||
|                     data-original-title="{% trans 'Administration' %}"></span> |  | ||||||
|                 <span class="list-group-item-value dropdown-title">{% trans 'Administration' %}</span> |  | ||||||
|             </a> |  | ||||||
|             <div id="user-secondary" class="nav-pf-secondary-nav"> |  | ||||||
|                 <div class="nav-item-pf-header"> |  | ||||||
|                     <a href="#0" class="secondary-collapse-toggle-pf" data-toggle="collapse-secondary-nav"></a> |  | ||||||
|                     <span>{% trans 'Administration' %}</span> |  | ||||||
|                 </div> |  | ||||||
|                 <ul class="list-group"> |  | ||||||
|                     <li class="list-group-item {% is_active 'passbook_admin:overview' %}"> |  | ||||||
|             <a href="{% url 'passbook_admin:overview' %}"> |             <a href="{% url 'passbook_admin:overview' %}"> | ||||||
|                             <span class="list-group-item-value"> |                 <span class="fa pficon-build" data-toggle="tooltip" title="{% trans 'System Status' %}"></span> | ||||||
|                                 {% trans 'Overview' %} |                 <span class="list-group-item-value">{% trans 'System Status' %}</span> | ||||||
|                             </span> |  | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         <li |         <li | ||||||
|             class="list-group-item {% is_active 'passbook_admin:applications' 'passbook_admin:application-create' 'passbook_admin:application-update' 'passbook_admin:application-delete' %}"> |             class="list-group-item {% is_active 'passbook_admin:applications' 'passbook_admin:application-create' 'passbook_admin:application-update' 'passbook_admin:application-delete' %}"> | ||||||
|             <a href="{% url 'passbook_admin:applications' %}"> |             <a href="{% url 'passbook_admin:applications' %}"> | ||||||
|                             <span class="list-group-item-value"> |                 <span class="fa pficon-applications" data-toggle="tooltip" title="{% trans 'Applications' %}"></span> | ||||||
|                                 {% trans 'Applications' %} |                 <span class="list-group-item-value">{% trans 'Applications' %}</span> | ||||||
|                             </span> |  | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         <li |         <li | ||||||
|             class="list-group-item {% is_active 'passbook_admin:sources' 'passbook_admin:source-create' 'passbook_admin:source-update' 'passbook_admin:source-delete' %}"> |             class="list-group-item {% is_active 'passbook_admin:sources' 'passbook_admin:source-create' 'passbook_admin:source-update' 'passbook_admin:source-delete' %}"> | ||||||
|             <a href="{% url 'passbook_admin:sources' %}"> |             <a href="{% url 'passbook_admin:sources' %}"> | ||||||
|                             <span class="list-group-item-value"> |                 <span class="fa pficon-resource-pool" data-toggle="tooltip" title="{% trans 'Sources' %}"></span> | ||||||
|                                 {% trans 'Sources' %} |                 <span class="list-group-item-value">{% trans 'Sources' %}</span> | ||||||
|                             </span> |  | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         <li |         <li | ||||||
|             class="list-group-item {% is_active 'passbook_admin:providers' 'passbook_admin:provider-create' 'passbook_admin:provider-update' 'passbook_admin:provider-delete' %}"> |             class="list-group-item {% is_active 'passbook_admin:providers' 'passbook_admin:provider-create' 'passbook_admin:provider-update' 'passbook_admin:provider-delete' %}"> | ||||||
|             <a href="{% url 'passbook_admin:providers' %}"> |             <a href="{% url 'passbook_admin:providers' %}"> | ||||||
|                             <span class="list-group-item-value"> |                 <span class="fa pficon-integration" data-toggle="tooltip" title="{% trans 'Providers' %}"></span> | ||||||
|                                 {% trans 'Providers' %} |                 <span class="list-group-item-value">{% trans 'Providers' %}</span> | ||||||
|                             </span> |  | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         <li |         <li | ||||||
|             class="list-group-item {% is_active 'passbook_admin:property-mappings' 'passbook_admin:property-mapping-create' 'passbook_admin:property-mapping-update' 'passbook_admin:property-mapping-delete' %}"> |             class="list-group-item {% is_active 'passbook_admin:property-mappings' 'passbook_admin:property-mapping-create' 'passbook_admin:property-mapping-update' 'passbook_admin:property-mapping-delete' %}"> | ||||||
|             <a href="{% url 'passbook_admin:property-mappings' %}"> |             <a href="{% url 'passbook_admin:property-mappings' %}"> | ||||||
|                             <span class="list-group-item-value"> |                 <span class="fa fa-table" data-toggle="tooltip" title="{% trans 'Property Mappings' %}"></span> | ||||||
|                                 {% trans 'Property Mappings' %} |                 <span class="list-group-item-value">{% trans 'Property Mappings' %}</span> | ||||||
|                             </span> |  | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         <li |         <li | ||||||
|             class="list-group-item {% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}"> |             class="list-group-item {% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}"> | ||||||
|             <a href="{% url 'passbook_admin:factors' %}"> |             <a href="{% url 'passbook_admin:factors' %}"> | ||||||
|                             <span class="list-group-item-value"> |                 <span class="fa pficon-plugged" data-toggle="tooltip" title="{% trans 'Factors' %}"></span> | ||||||
|                                 {% trans 'Factors' %} |                 <span class="list-group-item-value">{% trans 'Factors' %}</span> | ||||||
|                             </span> |  | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         <li |         <li | ||||||
|             class="list-group-item {% is_active 'passbook_admin:policies' 'passbook_admin:policy-create' 'passbook_admin:policy-update' 'passbook_admin:policy-delete' 'passbook_admin:policy-test' %}"> |             class="list-group-item {% is_active 'passbook_admin:policies' 'passbook_admin:policy-create' 'passbook_admin:policy-update' 'passbook_admin:policy-delete' 'passbook_admin:policy-test' %}"> | ||||||
|             <a href="{% url 'passbook_admin:policies' %}"> |             <a href="{% url 'passbook_admin:policies' %}"> | ||||||
|                             <span class="list-group-item-value"> |                 <span class="fa pficon-infrastructure" data-toggle="tooltip" title="{% trans 'Policies' %}"></span> | ||||||
|                                 {% trans 'Policies' %} |                 <span class="list-group-item-value">{% trans 'Policies' %}</span> | ||||||
|                             </span> |  | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         <li |         <li | ||||||
|             class="list-group-item {% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}"> |             class="list-group-item {% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}"> | ||||||
|             <a href="{% url 'passbook_admin:invitations' %}"> |             <a href="{% url 'passbook_admin:invitations' %}"> | ||||||
|                             <span class="list-group-item-value"> |                 <span class="fa pficon-migration" data-toggle="tooltip" title="{% trans 'Invitations' %}"></span> | ||||||
|                                 {% trans 'Invitations' %} |                 <span class="list-group-item-value">{% trans 'Invitations' %}</span> | ||||||
|                             </span> |  | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         <li |         <li | ||||||
|             class="list-group-item {% is_active 'passbook_admin:users' 'passbook_admin:user-update' 'passbook_admin:user-delete' %}"> |             class="list-group-item {% is_active 'passbook_admin:users' 'passbook_admin:user-update' 'passbook_admin:user-delete' %}"> | ||||||
|             <a href="{% url 'passbook_admin:users' %}"> |             <a href="{% url 'passbook_admin:users' %}"> | ||||||
|                             <span class="list-group-item-value"> |                 <span class="fa pficon-users" data-toggle="tooltip" title="{% trans 'Users' %}"></span> | ||||||
|                                 {% trans 'Users' %} |                 <span class="list-group-item-value">{% trans 'Users' %}</span> | ||||||
|                             </span> |  | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         <li |         <li | ||||||
|             class="list-group-item {% is_active 'passbook_admin:groups' 'passbook_admin:group-update' 'passbook_admin:group-delete' %}"> |             class="list-group-item {% is_active 'passbook_admin:groups' 'passbook_admin:group-update' 'passbook_admin:group-delete' %}"> | ||||||
|             <a href="{% url 'passbook_admin:groups' %}"> |             <a href="{% url 'passbook_admin:groups' %}"> | ||||||
|                             <span class="list-group-item-value"> |                 <span class="fa pficon-users" data-toggle="tooltip" title="{% trans 'Groups' %}"></span> | ||||||
|                                 {% trans 'Groups' %} |                 <span class="list-group-item-value">{% trans 'Groups' %}</span> | ||||||
|                             </span> |  | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|         <li class="list-group-item {% is_active 'passbook_admin:audit-log' %}"> |         <li class="list-group-item {% is_active 'passbook_admin:audit-log' %}"> | ||||||
|             <a href="{% url 'passbook_admin:audit-log' %}"> |             <a href="{% url 'passbook_admin:audit-log' %}"> | ||||||
|                             <span class="list-group-item-value"> |                 <span class="fa pficon-catalog" data-toggle="tooltip" title="{% trans 'Audit Log' %}"></span> | ||||||
|                                 {% trans 'Audit Log' %} |                 <span class="list-group-item-value">{% trans 'Audit Log' %}</span> | ||||||
|                             </span> |  | ||||||
|             </a> |             </a> | ||||||
|         </li> |         </li> | ||||||
|                     <li class="list-group-item {% is_active_app 'admin' %}"> |  | ||||||
|                         <a href="{% url 'admin:index' %}"> |  | ||||||
|                             <span class="list-group-item-value"> |  | ||||||
|                                 {% trans 'Django' %} |  | ||||||
|                             </span> |  | ||||||
|                         </a> |  | ||||||
|                     </li> |  | ||||||
|                     <li class="list-group-item {% is_active 'passbook_admin:debug-request' %}"> |  | ||||||
|                         <a href="{% url 'passbook_admin:debug-request' %}"> |  | ||||||
|                             <span class="list-group-item-value"> |  | ||||||
|                                 {% trans 'Debug' %} |  | ||||||
|                             </span> |  | ||||||
|                         </a> |  | ||||||
|                     </li> |  | ||||||
|                 </ul> |  | ||||||
|             </div> |  | ||||||
|         </li> |  | ||||||
|         {% endif %} |         {% endif %} | ||||||
|     </ul> |     </ul> | ||||||
| </div> | </div> | ||||||
|  | |||||||
| @ -1,36 +0,0 @@ | |||||||
| {% load static %} |  | ||||||
| {% load i18n %} |  | ||||||
| {% load cache %} |  | ||||||
|  |  | ||||||
| {% load utils %} |  | ||||||
|  |  | ||||||
| <div class="modal fade" id="about-modal" tabindex="-1" role="dialog" aria-hidden="true"> |  | ||||||
|     <div class="modal-dialog"> |  | ||||||
|         <div class="modal-content about-modal-pf"> |  | ||||||
|             <div class="modal-header"> |  | ||||||
|                 <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> |  | ||||||
|                     <span class="pficon pficon-close"></span> |  | ||||||
|                 </button> |  | ||||||
|             </div> |  | ||||||
|             <div class="modal-body"> |  | ||||||
|                 <h1>{% trans 'passbook' %}</h1> |  | ||||||
|                 <div class="product-versions-pf"> |  | ||||||
|                     <ul class="list-unstyled"> |  | ||||||
|                         {% app_versions as vers %} |  | ||||||
|                         {% cache 600 versions %} |  | ||||||
|                         {% for app, ver in vers.items %} |  | ||||||
|                         <li><strong>{{ app }}</strong> {{ ver }}</li> |  | ||||||
|                         {% endfor %} |  | ||||||
|                         {% endcache %} |  | ||||||
|                     </ul> |  | ||||||
|                 </div> |  | ||||||
|                 <div class="trademark-pf"> |  | ||||||
|                     Trademark and Copyright Information |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="modal-footer"> |  | ||||||
|                 <img style="max-height:64px;" src="{% static 'img/logo.png' %}" alt=" Symbol"> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|     </div> |  | ||||||
| </div> |  | ||||||
| @ -16,21 +16,25 @@ | |||||||
|                         <i class="fa pficon-edit"></i> {% trans 'Details' %} |                         <i class="fa pficon-edit"></i> {% trans 'Details' %} | ||||||
|                     </a> |                     </a> | ||||||
|                 </li> |                 </li> | ||||||
|                 <li class="nav-divider"></li> |  | ||||||
|                 {% user_factors as uf %} |                 {% user_factors as uf %} | ||||||
|                 {% for name, icon, link in uf %} |                 {% if uf %} | ||||||
|                 <li class="{% is_active link %}"> |                     <li class="nav-divider"></li> | ||||||
|                      <a href="{% url link %}"> |                 {% endif %} | ||||||
|                          <i class="{{ icon }}"></i> {{ name }} |                 {% for user_settings in uf %} | ||||||
|  |                     <li class="{% is_active user_settings.view_name %}"> | ||||||
|  |                         <a href="{% url user_settings.view_name %}"> | ||||||
|  |                             <i class="{{ user_settings.icon }}"></i> {{ user_settings.name }} | ||||||
|                         </a> |                         </a> | ||||||
|                     </li> |                     </li> | ||||||
|                 {% endfor %} |                 {% endfor %} | ||||||
|                 <li class="nav-divider"></li> |  | ||||||
|                 {% user_sources as us %} |                 {% user_sources as us %} | ||||||
|                 {% for name, icon, link in us %} |                 {% if us %} | ||||||
|                 <li class="{% if link == request.get_full_path %} active {% endif %}"> |                     <li class="nav-divider"></li> | ||||||
|                     <a href="{{ link }}"> |                 {% endif %} | ||||||
|                         <i class="{{ icon }}"></i> {{ name }} |                 {% for user_settings in us %} | ||||||
|  |                     <li class="{% if user_settings.view_name == request.get_full_path %} active {% endif %}"> | ||||||
|  |                         <a href="{{ user_settings.view_name }}"> | ||||||
|  |                             <i class="{{ user_settings.icon }}"></i> {{ user_settings.name }} | ||||||
|                         </a> |                         </a> | ||||||
|                     </li> |                     </li> | ||||||
|                 {% endfor %} |                 {% endfor %} | ||||||
|  | |||||||
| @ -1,36 +1,38 @@ | |||||||
| """passbook user settings template tags""" | """passbook user settings template tags""" | ||||||
|  | from typing import List | ||||||
|  |  | ||||||
| from django import template | from django import template | ||||||
|  | from django.template.context import RequestContext | ||||||
|  |  | ||||||
| from passbook.core.models import Factor, Source | from passbook.core.models import Factor, Source, UserSettings | ||||||
| from passbook.policy.engine import PolicyEngine | from passbook.policies.engine import PolicyEngine | ||||||
|  |  | ||||||
| register = template.Library() | register = template.Library() | ||||||
|  |  | ||||||
| @register.simple_tag(takes_context=True) | @register.simple_tag(takes_context=True) | ||||||
| def user_factors(context): | def user_factors(context: RequestContext) -> List[UserSettings]: | ||||||
|     """Return list of all factors which apply to user""" |     """Return list of all factors which apply to user""" | ||||||
|     user = context.get('request').user |     user = context.get('request').user | ||||||
|     _all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses() |     _all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses() | ||||||
|     matching_factors = [] |     matching_factors: List[UserSettings] = [] | ||||||
|     for factor in _all_factors: |     for factor in _all_factors: | ||||||
|         _link = factor.has_user_settings() |         user_settings = factor.user_settings() | ||||||
|         policy_engine = PolicyEngine(factor.policies.all()) |         policy_engine = PolicyEngine(factor.policies.all()) | ||||||
|         policy_engine.for_user(user).with_request(context.get('request')).build() |         policy_engine.for_user(user).with_request(context.get('request')).build() | ||||||
|         if policy_engine.passing and _link: |         if policy_engine.passing and user_settings: | ||||||
|             matching_factors.append(_link) |             matching_factors.append(user_settings) | ||||||
|     return matching_factors |     return matching_factors | ||||||
|  |  | ||||||
| @register.simple_tag(takes_context=True) | @register.simple_tag(takes_context=True) | ||||||
| def user_sources(context): | def user_sources(context: RequestContext) -> List[UserSettings]: | ||||||
|     """Return a list of all sources which are enabled for the user""" |     """Return a list of all sources which are enabled for the user""" | ||||||
|     user = context.get('request').user |     user = context.get('request').user | ||||||
|     _all_sources = Source.objects.filter(enabled=True).select_subclasses() |     _all_sources = Source.objects.filter(enabled=True).select_subclasses() | ||||||
|     matching_sources = [] |     matching_sources: List[UserSettings] = [] | ||||||
|     for factor in _all_sources: |     for factor in _all_sources: | ||||||
|         _link = factor.has_user_settings() |         user_settings = factor.user_settings() | ||||||
|         policy_engine = PolicyEngine(factor.policies.all()) |         policy_engine = PolicyEngine(factor.policies.all()) | ||||||
|         policy_engine.for_user(user).with_request(context.get('request')).build() |         policy_engine.for_user(user).with_request(context.get('request')).build() | ||||||
|         if policy_engine.passing and _link: |         if policy_engine.passing and user_settings: | ||||||
|             matching_sources.append(_link) |             matching_sources.append(user_settings) | ||||||
|     return matching_sources |     return matching_sources | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	