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] | ||||
| current_version = 0.3.0-beta | ||||
| current_version = 0.6.4-beta | ||||
| tag = True | ||||
| commit = True | ||||
| 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/core/nginx.conf] | ||||
| [bumpversion:file:docker/nginx.conf] | ||||
|  | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| [run] | ||||
| source = passbook | ||||
| omit = | ||||
|     env/ | ||||
|     */wsgi.py | ||||
|     manage.py | ||||
|     */migrations/* | ||||
|  | ||||
| @ -2,3 +2,4 @@ env | ||||
| helm | ||||
| passbook-ui | ||||
| static | ||||
| *.env.yml | ||||
|  | ||||
| @ -27,7 +27,7 @@ create-base-image: | ||||
|   before_script: | ||||
|     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json | ||||
|   script: | ||||
|     - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/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 | ||||
|   only: | ||||
|     refs: | ||||
| @ -41,7 +41,7 @@ build-dev-image: | ||||
|   before_script: | ||||
|     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json | ||||
|   script: | ||||
|     - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/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 | ||||
|   only: | ||||
|     refs: | ||||
| @ -63,20 +63,20 @@ migrations: | ||||
|   services: | ||||
|   - postgres:latest | ||||
|   - redis:latest | ||||
| prospector: | ||||
|   script: | ||||
|     - prospector | ||||
|   stage: test | ||||
|   services: | ||||
|   - postgres:latest | ||||
|   - redis:latest | ||||
| pylint: | ||||
|   script: | ||||
|     - pylint passbook | ||||
|   stage: test | ||||
|   services: | ||||
|   - postgres:latest | ||||
|   - redis:latest | ||||
| # prospector: | ||||
| #   script: | ||||
| #     - prospector | ||||
| #   stage: test | ||||
| #   services: | ||||
| #   - postgres:latest | ||||
| #   - redis:latest | ||||
| # pylint: | ||||
| #   script: | ||||
| #     - pylint passbook | ||||
| #   stage: test | ||||
| #   services: | ||||
| #   - postgres:latest | ||||
| #   - redis:latest | ||||
| coverage: | ||||
|   script: | ||||
|     - coverage run manage.py test | ||||
| @ -87,15 +87,15 @@ coverage: | ||||
|   - postgres:latest | ||||
|   - redis:latest | ||||
|  | ||||
| package-passbook-server: | ||||
| build-passbook-server: | ||||
|   stage: build | ||||
|   image: | ||||
|     name: gcr.io/kaniko-project/executor:debug | ||||
|     entrypoint: [""] | ||||
|   before_script: | ||||
|     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json | ||||
|   script: | ||||
|     - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.3.0-beta | ||||
|   stage: build | ||||
|     - /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 | ||||
|   only: | ||||
|     - tags | ||||
|     - /^version/.*$/ | ||||
| @ -107,7 +107,7 @@ build-passbook-static: | ||||
|   before_script: | ||||
|     - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json | ||||
|   script: | ||||
|     - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/static.Dockerfile --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.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: | ||||
|     - tags | ||||
|     - /^version/.*$/ | ||||
| @ -124,7 +124,7 @@ package-helm: | ||||
|     - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash | ||||
|   script: | ||||
|     - helm init --client-only | ||||
|     - helm dependency build helm/passbook | ||||
|     - helm dependency update helm/passbook | ||||
|     - helm package helm/passbook | ||||
|   artifacts: | ||||
|     paths: | ||||
|  | ||||
| @ -3,11 +3,9 @@ test-warnings: true | ||||
| doc-warnings: false | ||||
|  | ||||
| ignore-paths: | ||||
|   - env | ||||
|   - migrations | ||||
|   - docs | ||||
|   - node_modules | ||||
|   - client-packages | ||||
|  | ||||
| uses: | ||||
|  - 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 | ||||
| load-plugins=pylint_django,pylint.extensions.bad_builtin | ||||
| #,pylint.extensions.docparams | ||||
| extension-pkg-whitelist=lxml | ||||
| const-rgx=[a-zA-Z0-9_]{1,40}$ | ||||
| ignored-modules=django-otp | ||||
| jobs=4 | ||||
|  | ||||
| [SIMILARITIES] | ||||
|  | ||||
|  | ||||
| @ -1,8 +1,9 @@ | ||||
| FROM docker.beryju.org/passbook/base:latest | ||||
|  | ||||
| COPY ./passbook/ /app/passbook | ||||
| COPY --chown=passbook:passbook ./passbook/ /app/passbook | ||||
| COPY ./manage.py /app/ | ||||
|  | ||||
| USER passbook | ||||
| COPY ./docker/uwsgi.ini /app/ | ||||
|  | ||||
| WORKDIR /app/ | ||||
|  | ||||
| USER passbook | ||||
|  | ||||
							
								
								
									
										19
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								Pipfile
									
									
									
									
									
								
							| @ -4,15 +4,11 @@ url = "https://pypi.org/simple" | ||||
| verify_ssl = true | ||||
|  | ||||
| [packages] | ||||
| asgiref = "*" | ||||
| beautifulsoup4 = "*" | ||||
| celery = "*" | ||||
| channels = "*" | ||||
| cherrypy = "*" | ||||
| colorlog = "*" | ||||
| daphne = "*" | ||||
| defusedxml = "*" | ||||
| django = "*" | ||||
| kombu = "==4.5.0" | ||||
| django-cors-middleware = "*" | ||||
| django-filters = "*" | ||||
| django-ipware = "*" | ||||
| @ -23,15 +19,13 @@ django-otp = "*" | ||||
| django-recaptcha = "*" | ||||
| django-redis = "*" | ||||
| django-rest-framework = "*" | ||||
| django-revproxy = "*" | ||||
| djangorestframework = "==3.9.4" | ||||
| drf-yasg = "*" | ||||
| ldap3 = "*" | ||||
| lxml = "*" | ||||
| markdown = "*" | ||||
| oauthlib = "*" | ||||
| packaging = "*" | ||||
| psycopg2 = "*" | ||||
| psycopg2-binary = "*" | ||||
| pycryptodome = "*" | ||||
| pyyaml = "*" | ||||
| qrcode = "*" | ||||
| @ -40,22 +34,21 @@ sentry-sdk = "*" | ||||
| service_identity = "*" | ||||
| signxml = "*" | ||||
| urllib3 = {extras = ["secure"],version = "*"} | ||||
| websocket_client = "*" | ||||
| structlog = "*" | ||||
|  | ||||
| [requires] | ||||
| python_version = "3.7" | ||||
|  | ||||
| [dev-packages] | ||||
| astroid = "==2.2.5" | ||||
| coverage = "*" | ||||
| isort = "*" | ||||
| pylint = "*" | ||||
| pylint-django = "*" | ||||
| prospector = "*" | ||||
| pylint = "==2.3.1" | ||||
| pylint-django = "==2.0.10" | ||||
| prospector = "==1.1.7" | ||||
| django-debug-toolbar = "*" | ||||
| bumpversion = "*" | ||||
| unittest-xml-reporting = "*" | ||||
| autopep8 = "*" | ||||
| bandit = "*" | ||||
| twine = "*" | ||||
| colorama = "*" | ||||
|  | ||||
							
								
								
									
										510
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										510
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							| @ -1,7 +1,7 @@ | ||||
| { | ||||
|     "_meta": { | ||||
|         "hash": { | ||||
|             "sha256": "f8694b0ee03f99560e853fd24e9cd7ac987c757cd50249398346e42cdd98cbbb" | ||||
|             "sha256": "53d7190ea62f504dc1a36eae952a273e0b2d9f313f23031099d039c3146235b7" | ||||
|         }, | ||||
|         "pipfile-spec": 6, | ||||
|         "requires": { | ||||
| @ -18,62 +18,24 @@ | ||||
|     "default": { | ||||
|         "amqp": { | ||||
|             "hashes": [ | ||||
|                 "sha256:19a917e260178b8d410122712bac69cb3e6db010d68f6101e7307508aded5e68", | ||||
|                 "sha256:19d851b879a471fcfdcf01df9936cff924f422baa77653289f7095dedd5fb26a" | ||||
|                 "sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8", | ||||
|                 "sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d" | ||||
|             ], | ||||
|             "version": "==2.5.1" | ||||
|         }, | ||||
|         "asgiref": { | ||||
|             "hashes": [ | ||||
|                 "sha256:a4ce726e6ef49cca13642ff49588530ebabcc47c669c7a95af37ea5a74b9b823", | ||||
|                 "sha256:f62b1c88ebf5fe95db202a372982970edcf375c1513d7e70717df0750f5c2b98" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==3.2.2" | ||||
|             "version": "==2.5.2" | ||||
|         }, | ||||
|         "asn1crypto": { | ||||
|             "hashes": [ | ||||
|                 "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", | ||||
|                 "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49" | ||||
|                 "sha256:0b199f211ae690df3db4fd6c1c4ff976497fb1da689193e368eedbadc53d9292", | ||||
|                 "sha256:bca90060bd995c3f62c4433168eab407e44bdbdb567b3f3a396a676c1a4c4a3f" | ||||
|             ], | ||||
|             "version": "==0.24.0" | ||||
|             "version": "==1.0.1" | ||||
|         }, | ||||
|         "attrs": { | ||||
|             "hashes": [ | ||||
|                 "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", | ||||
|                 "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" | ||||
|                 "sha256:ec20e7a4825331c1b5ebf261d111e16fa9612c1f7a5e1f884f12bd53a664dfd2", | ||||
|                 "sha256:f913492e1663d3c36f502e5e9ba6cd13cf19d7fab50aa13239e420fef95e1396" | ||||
|             ], | ||||
|             "version": "==19.1.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" | ||||
|             "version": "==19.2.0" | ||||
|         }, | ||||
|         "billiard": { | ||||
|             "hashes": [ | ||||
| @ -92,10 +54,10 @@ | ||||
|         }, | ||||
|         "certifi": { | ||||
|             "hashes": [ | ||||
|                 "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", | ||||
|                 "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" | ||||
|                 "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", | ||||
|                 "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" | ||||
|             ], | ||||
|             "version": "==2019.6.16" | ||||
|             "version": "==2019.9.11" | ||||
|         }, | ||||
|         "cffi": { | ||||
|             "hashes": [ | ||||
| @ -130,14 +92,6 @@ | ||||
|             ], | ||||
|             "version": "==1.12.3" | ||||
|         }, | ||||
|         "channels": { | ||||
|             "hashes": [ | ||||
|                 "sha256:9191a85800673b790d1d74666fb7676f430600b71b662581e97dd69c9aedd29a", | ||||
|                 "sha256:af7cdba9efb3f55b939917d1b15defb5d40259936013e60660e5e9aff98db4c5" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==2.2.0" | ||||
|         }, | ||||
|         "chardet": { | ||||
|             "hashes": [ | ||||
|                 "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", | ||||
| @ -147,33 +101,18 @@ | ||||
|         }, | ||||
|         "cheroot": { | ||||
|             "hashes": [ | ||||
|                 "sha256:427e7e3ce51ad5a6e5cf953252b5782d5dfbeb544c09910634971bc06df6621b", | ||||
|                 "sha256:74d733c55178812253d855990f7ad7b31ab4ee8dab80e4803bd5e52299c50395" | ||||
|                 "sha256:6168371ab9aaf574ac5f75675f244bbfebf990202bf75048065e9d675b9ae719", | ||||
|                 "sha256:8cc7c28961db2e13d0cac6b234a589a314c1844f7bbf54e67888ac9a2e25ac59" | ||||
|             ], | ||||
|             "version": "==6.5.8" | ||||
|             "version": "==7.0.0" | ||||
|         }, | ||||
|         "cherrypy": { | ||||
|             "hashes": [ | ||||
|                 "sha256:16fc226a280cd772ede7c309d3964002196784ac6615d8bface52be12ff51230", | ||||
|                 "sha256:488ea5e639885c75330686c1d7d3dfbd002f784c027a3fe5b374b41926b8cba3" | ||||
|                 "sha256:033368d25fcc6bca143e7efe9adbfd3a6d91cc0d90c37a649261935f116aafab", | ||||
|                 "sha256:683e687e7c7b1ba31ef86a113b1eafd0407269fed175bf488d3c839d37d1cc60" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==18.2.0" | ||||
|         }, | ||||
|         "colorlog": { | ||||
|             "hashes": [ | ||||
|                 "sha256:3cf31b25cbc8f86ec01fef582ef3b840950dea414084ed19ab922c8b493f9b42", | ||||
|                 "sha256:450f52ea2a2b6ebb308f034ea9a9b15cea51e65650593dca1da3eb792e4e4981" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==4.0.2" | ||||
|         }, | ||||
|         "constantly": { | ||||
|             "hashes": [ | ||||
|                 "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35", | ||||
|                 "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d" | ||||
|             ], | ||||
|             "version": "==15.1.0" | ||||
|             "version": "==18.3.0" | ||||
|         }, | ||||
|         "coreapi": { | ||||
|             "hashes": [ | ||||
| @ -210,14 +149,6 @@ | ||||
|             ], | ||||
|             "version": "==2.7" | ||||
|         }, | ||||
|         "daphne": { | ||||
|             "hashes": [ | ||||
|                 "sha256:2329b7a74b5559f7ea012879c10ba945c3a53df7d8d2b5932a904e3b4c9abcc2", | ||||
|                 "sha256:3cae286a995ae5b127d7de84916f0480cb5be19f81125b6a150b8326250dadd5" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==2.3.0" | ||||
|         }, | ||||
|         "defusedxml": { | ||||
|             "hashes": [ | ||||
|                 "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", | ||||
| @ -228,11 +159,11 @@ | ||||
|         }, | ||||
|         "django": { | ||||
|             "hashes": [ | ||||
|                 "sha256:148a4a2d1a85b23883b0a4e99ab7718f518a83675e4485e44dc0c1d36988c5fa", | ||||
|                 "sha256:deb70aa038e59b58593673b15e9a711d1e5ccd941b5973b30750d5d026abfd56" | ||||
|                 "sha256:4025317ca01f75fc79250ff7262a06d8ba97cd4f82e93394b2a0a6a4a925caeb", | ||||
|                 "sha256:a8ca1033acac9f33995eb2209a6bf18a4681c3e5269a878e9a7e0b7384ed1ca3" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==2.2.5" | ||||
|             "version": "==2.2.6" | ||||
|         }, | ||||
|         "django-cors-middleware": { | ||||
|             "hashes": [ | ||||
| @ -280,11 +211,11 @@ | ||||
|         }, | ||||
|         "django-otp": { | ||||
|             "hashes": [ | ||||
|                 "sha256:246b11ee38ec1cea2e2312311a830740d1a8d0384ba15e7b70e03f851d790157", | ||||
|                 "sha256:cefbf5e7295498c767752d77828ce3f56cdb0373915e56fe4f87d99604742394" | ||||
|                 "sha256:79c8253be97246df86540d551dc705e8fe6ca76af8e8c77f78314cd1b513c2cf", | ||||
|                 "sha256:c5bf3916dca5d53cb377aa6dea40aa785c164013fbf750384137362dfa278cf5" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.7.0" | ||||
|             "version": "==0.7.2" | ||||
|         }, | ||||
|         "django-recaptcha": { | ||||
|             "hashes": [ | ||||
| @ -309,29 +240,20 @@ | ||||
|             "index": "pypi", | ||||
|             "version": "==0.1.0" | ||||
|         }, | ||||
|         "django-revproxy": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0b539736e438aad3cd8b34563125783678f65bcb847970c95d8e9820e6dc88b3", | ||||
|                 "sha256:b2c6244aaf53fbbecb79084bf507761754b36895c0f6d01349066e9a355e8455" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.9.15" | ||||
|         }, | ||||
|         "djangorestframework": { | ||||
|             "hashes": [ | ||||
|                 "sha256:376f4b50340a46c15ae15ddd0c853085f4e66058f97e4dbe7d43ed62f5e60651", | ||||
|                 "sha256:c12869cfd83c33d579b17b3cb28a2ae7322a53c3ce85580c2a2ebe4e3f56c4fb" | ||||
|                 "sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8", | ||||
|                 "sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==3.9.4" | ||||
|             "version": "==3.10.3" | ||||
|         }, | ||||
|         "drf-yasg": { | ||||
|             "hashes": [ | ||||
|                 "sha256:68fded2ffdf46e03f33e766184b7d8f1e1a5236f94acfd0c4ba932a57b812566", | ||||
|                 "sha256:fcef74709ead2b365410be3d12afbfd0a6e49d1efe615a15a929da7e950bb83c" | ||||
|                 "sha256:4cfec631880ae527a91ec7cd3241aea2f82189f59e2f089119aa687761afb227", | ||||
|                 "sha256:504cce09035cf1bace63b84d9d778b772f86bb37d8a71ed6f723346362e633b2" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==1.16.1" | ||||
|             "version": "==1.17.0" | ||||
|         }, | ||||
|         "eight": { | ||||
|             "hashes": [ | ||||
| @ -346,13 +268,6 @@ | ||||
|             ], | ||||
|             "version": "==0.16.0" | ||||
|         }, | ||||
|         "hyperlink": { | ||||
|             "hashes": [ | ||||
|                 "sha256:4288e34705da077fada1111a24a0aa08bb1e76699c9ce49876af722441845654", | ||||
|                 "sha256:ab4a308feb039b04f855a020a6eda3b18ca5a68e6d8f8c899cbe9e653721d04f" | ||||
|             ], | ||||
|             "version": "==19.0.0" | ||||
|         }, | ||||
|         "idna": { | ||||
|             "hashes": [ | ||||
|                 "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", | ||||
| @ -360,20 +275,6 @@ | ||||
|             ], | ||||
|             "version": "==2.8" | ||||
|         }, | ||||
|         "importlib-metadata": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0c505102757e7fa28b9f0958d8bc81301159dea16e2649858c92edc158b78a83", | ||||
|                 "sha256:9a9f75ce32e78170905888acbf2376a81d3f21ecb3bb4867050413411d3ca7a9" | ||||
|             ], | ||||
|             "version": "==0.21" | ||||
|         }, | ||||
|         "incremental": { | ||||
|             "hashes": [ | ||||
|                 "sha256:717e12246dddf231a349175f48d74d93e2897244939173b01974ab6661406b9f", | ||||
|                 "sha256:7b751696aaf36eebfab537e458929e194460051ccad279c72b755a167eebd4b3" | ||||
|             ], | ||||
|             "version": "==17.5.0" | ||||
|         }, | ||||
|         "inflection": { | ||||
|             "hashes": [ | ||||
|                 "sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca" | ||||
| @ -395,17 +296,18 @@ | ||||
|         }, | ||||
|         "jinja2": { | ||||
|             "hashes": [ | ||||
|                 "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", | ||||
|                 "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" | ||||
|                 "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", | ||||
|                 "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" | ||||
|             ], | ||||
|             "version": "==2.10.1" | ||||
|             "version": "==2.10.3" | ||||
|         }, | ||||
|         "kombu": { | ||||
|             "hashes": [ | ||||
|                 "sha256:55274dc75eb3c3994538b0973a0fadddb236b698a4bc135b8aa4981e0a710b8f", | ||||
|                 "sha256:e5f0312dfb9011bebbf528ccaf118a6c2b5c3b8244451f08381fb23e7715809b" | ||||
|                 "sha256:389ba09e03b15b55b1a7371a441c894fd8121d174f5583bbbca032b9ea8c9edd", | ||||
|                 "sha256:7b92303af381ef02fad6899fd5f5a9a96031d781356cd8e505fa54ae5ddee181" | ||||
|             ], | ||||
|             "version": "==4.6.4" | ||||
|             "index": "pypi", | ||||
|             "version": "==4.5.0" | ||||
|         }, | ||||
|         "ldap3": { | ||||
|             "hashes": [ | ||||
| @ -501,11 +403,11 @@ | ||||
|         }, | ||||
|         "packaging": { | ||||
|             "hashes": [ | ||||
|                 "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", | ||||
|                 "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" | ||||
|                 "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", | ||||
|                 "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==19.1" | ||||
|             "version": "==19.2" | ||||
|         }, | ||||
|         "portend": { | ||||
|             "hashes": [ | ||||
| @ -514,19 +416,36 @@ | ||||
|             ], | ||||
|             "version": "==2.5" | ||||
|         }, | ||||
|         "psycopg2": { | ||||
|         "psycopg2-binary": { | ||||
|             "hashes": [ | ||||
|                 "sha256:128d0fa910ada0157bba1cb74a9c5f92bb8a1dca77cf91a31eb274d1f889e001", | ||||
|                 "sha256:227fd46cf9b7255f07687e5bde454d7d67ae39ca77e170097cdef8ebfc30c323", | ||||
|                 "sha256:2315e7f104681d498ccf6fd70b0dba5bce65d60ac92171492bfe228e21dcc242", | ||||
|                 "sha256:4b5417dcd2999db0f5a891d54717cfaee33acc64f4772c4bc574d4ff95ed9d80", | ||||
|                 "sha256:640113ddc943522aaf71294e3f2d24013b0edd659b7820621492c9ebd3a2fb0b", | ||||
|                 "sha256:897a6e838319b4bf648a574afb6cabcb17d0488f8c7195100d48d872419f4457", | ||||
|                 "sha256:8dceca81409898c870e011c71179454962dec152a1a6b86a347f4be74b16d864", | ||||
|                 "sha256:b1b8e41da09a0c3ef0b3d4bb72da0dde2abebe583c1e8462973233fd5ad0235f", | ||||
|                 "sha256:cb407fccc12fc29dc331f2b934913405fa49b9b75af4f3a72d0f50f57ad2ca23", | ||||
|                 "sha256:d3a27550a8185e53b244ad7e79e307594b92fede8617d80200a8cce1fba2c60f", | ||||
|                 "sha256:f0e6b697a975d9d3ccd04135316c947dd82d841067c7800ccf622a8717e98df1" | ||||
|                 "sha256:080c72714784989474f97be9ab0ddf7b2ad2984527e77f2909fcd04d4df53809", | ||||
|                 "sha256:110457be80b63ff4915febb06faa7be002b93a76e5ba19bf3f27636a2ef58598", | ||||
|                 "sha256:171352a03b22fc099f15103959b52ee77d9a27e028895d7e5fde127aa8e3bac5", | ||||
|                 "sha256:19d013e7b0817087517a4b3cab39c084d78898369e5c46258aab7be4f233d6a1", | ||||
|                 "sha256:249b6b21ae4eb0f7b8423b330aa80fab5f821b9ffc3f7561a5e2fd6bb142cf5d", | ||||
|                 "sha256:2ac0731d2d84b05c7bb39e85b7e123c3a0acd4cda631d8d542802c88deb9e87e", | ||||
|                 "sha256:2b6d561193f0dc3f50acfb22dd52ea8c8dfbc64bcafe3938b5f209cc17cb6f00", | ||||
|                 "sha256:2bd23e242e954214944481124755cbefe7c2cf563b1a54cd8d196d502f2578bf", | ||||
|                 "sha256:3e1239242ca60b3725e65ab2f13765fc199b03af9eaf1b5572f0e97bdcee5b43", | ||||
|                 "sha256:3eb70bb697abbe86b1d2b1316370c02ba320bfd1e9e35cf3b9566a855ea8e4e5", | ||||
|                 "sha256:51a2fc7e94b98bd1bb5d4570936f24fc2b0541b63eccadf8fdea266db8ad2f70", | ||||
|                 "sha256:52f1bdafdc764b7447e393ed39bb263eccb12bfda25a4ac06d82e3a9056251f6", | ||||
|                 "sha256:5b3581319a3951f1e866f4f6c5e42023db0fae0284273b82e97dfd32c51985cd", | ||||
|                 "sha256:63c1b66e3b2a3a336288e4bcec499e0dc310cd1dceaed1c46fa7419764c68877", | ||||
|                 "sha256:8123a99f24ecee469e5c1339427bcdb2a33920a18bb5c0d58b7c13f3b0298ba3", | ||||
|                 "sha256:85e699fcabe7f817c0f0a412d4e7c6627e00c412b418da7666ff353f38e30f67", | ||||
|                 "sha256:8dbff4557bbef963697583366400822387cccf794ccb001f1f2307ed21854c68", | ||||
|                 "sha256:908d21d08d6b81f1b7e056bbf40b2f77f8c499ab29e64ec5113052819ef1c89b", | ||||
|                 "sha256:af39d0237b17d0a5a5f638e9dffb34013ce2b1d41441fd30283e42b22d16858a", | ||||
|                 "sha256:af51bb9f055a3f4af0187149a8f60c9d516cf7d5565b3dac53358796a8fb2a5b", | ||||
|                 "sha256:b2ecac57eb49e461e86c092761e6b8e1fd9654dbaaddf71a076dcc869f7014e2", | ||||
|                 "sha256:cd37cc170678a4609becb26b53a2bc1edea65177be70c48dd7b39a1149cabd6e", | ||||
|                 "sha256:d17e3054b17e1a6cb8c1140f76310f6ede811e75b7a9d461922d2c72973f583e", | ||||
|                 "sha256:d305313c5a9695f40c46294d4315ed3a07c7d2b55e48a9010dad7db7a66c8b7f", | ||||
|                 "sha256:dd0ef0eb1f7dd18a3f4187226e226a7284bda6af5671937a221766e6ef1ee88f", | ||||
|                 "sha256:e1adff53b56db9905db48a972fb89370ad5736e0450b96f91bcf99cadd96cfd7", | ||||
|                 "sha256:f0d43828003c82dbc9269de87aa449e9896077a71954fbbb10a614c017e65737", | ||||
|                 "sha256:f78e8b487de4d92640105c1389e5b90be3496b1d75c90a666edd8737cc2dbab7" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==2.8.3" | ||||
| @ -618,13 +537,6 @@ | ||||
|             ], | ||||
|             "version": "==3.9.0" | ||||
|         }, | ||||
|         "pyhamcrest": { | ||||
|             "hashes": [ | ||||
|                 "sha256:6b672c02fdf7470df9674ab82263841ce8333fb143f32f021f6cb26f0e512420", | ||||
|                 "sha256:8ffaa0a53da57e89de14ced7185ac746227a8894dbd5a3c718bf05ddbd1d56cd" | ||||
|             ], | ||||
|             "version": "==1.9.0" | ||||
|         }, | ||||
|         "pyjwkest": { | ||||
|             "hashes": [ | ||||
|                 "sha256:5560fd5ba08655f29ff6ad1df1e15dc05abc9d976fcbcec8d2b5167f49b70222" | ||||
| @ -647,10 +559,10 @@ | ||||
|         }, | ||||
|         "pytz": { | ||||
|             "hashes": [ | ||||
|                 "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", | ||||
|                 "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" | ||||
|                 "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", | ||||
|                 "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" | ||||
|             ], | ||||
|             "version": "==2019.2" | ||||
|             "version": "==2019.3" | ||||
|         }, | ||||
|         "pyyaml": { | ||||
|             "hashes": [ | ||||
| @ -710,35 +622,35 @@ | ||||
|         }, | ||||
|         "ruamel.yaml.clib": { | ||||
|             "hashes": [ | ||||
|                 "sha256:0bbe19d3e099f8ba384e1846e6b54f245f58aeec8700edbbf9abb87afa54fd82", | ||||
|                 "sha256:2f38024592613f3a8772bbc2904be027d9abf463518ba145f2d0c8e6da27009f", | ||||
|                 "sha256:44449b3764a3f75815eea8ae5930b98e8326be64a90b0f782747318f861abfe0", | ||||
|                 "sha256:5710be9a357801c31c1eaa37b9bc92d38176d785af5b2f0c9751385c5dc9659a", | ||||
|                 "sha256:5a089acb6833ed5f412e24cbe3e665683064c1429824d2819137b5ade54435c3", | ||||
|                 "sha256:6143386ddd61599ea081c012a69a16e5bdd7b3c6c231bd039534365a48940f30", | ||||
|                 "sha256:6726aaf851f5f9e4cbdd3e1e414bc700bdd39220e8bc386415fd41c87b1b53c2", | ||||
|                 "sha256:68fbc3b5d94d145a391452f886ae5fca240cb7e3ab6bd66e1a721507cdaac28a", | ||||
|                 "sha256:75ebddf99ba9e0b48f32b5bdcf9e5a2b84c017da9e0db7bf11995fa414aa09cd", | ||||
|                 "sha256:79948a6712baa686773a43906728e20932c923f7b2a91be7347993be2d745e55", | ||||
|                 "sha256:8a2dd8e8b08d369558cade05731172c4b5e2f4c5097762c6b352bd28fd9f9dc4", | ||||
|                 "sha256:c747acdb5e8c242ab2280df6f0c239e62838af4bee647031d96b3db2f9cefc04", | ||||
|                 "sha256:cadc8eecd27414dca30366b2535cb5e3f3b47b4e2d6be7a0b13e4e52e459ff9f", | ||||
|                 "sha256:cee86ecc893a6a8ecaa7c6a9c2d06f75f614176210d78a5f155f8e78d6989509", | ||||
|                 "sha256:e59af39e895aff28ee5f55515983cab3466d1a029c91c04db29da1c0f09cf333", | ||||
|                 "sha256:eee7ecd2eee648884fae6c51ae50c814acdcc5d6340dc96c970158aebcd25ac6", | ||||
|                 "sha256:ef8d4522d231cb9b29f6cdd0edc8faac9d9715c60dc7becbd6eb82c915a98e5b", | ||||
|                 "sha256:f504d45230cc9abf2810623b924ae048b224a90adb01f97db4e766cfdda8e6eb" | ||||
|                 "sha256:1e77424825caba5553bbade750cec2277ef130647d685c2b38f68bc03453bac6", | ||||
|                 "sha256:392b7c371312abf27fb549ec2d5e0092f7ef6e6c9f767bfb13e83cb903aca0fd", | ||||
|                 "sha256:4d55386129291b96483edcb93b381470f7cd69f97585829b048a3d758d31210a", | ||||
|                 "sha256:550168c02d8de52ee58c3d8a8193d5a8a9491a5e7b2462d27ac5bf63717574c9", | ||||
|                 "sha256:57933a6986a3036257ad7bf283529e7c19c2810ff24c86f4a0cfeb49d2099919", | ||||
|                 "sha256:615b0396a7fad02d1f9a0dcf9f01202bf9caefee6265198f252c865f4227fcc6", | ||||
|                 "sha256:77556a7aa190be9a2bd83b7ee075d3df5f3c5016d395613671487e79b082d784", | ||||
|                 "sha256:7aee724e1ff424757b5bd8f6c5bbdb033a570b2b4683b17ace4dbe61a99a657b", | ||||
|                 "sha256:8073c8b92b06b572e4057b583c3d01674ceaf32167801fe545a087d7a1e8bf52", | ||||
|                 "sha256:9c6d040d0396c28d3eaaa6cb20152cb3b2f15adf35a0304f4f40a3cf9f1d2448", | ||||
|                 "sha256:a0ff786d2a7dbe55f9544b3f6ebbcc495d7e730df92a08434604f6f470b899c5", | ||||
|                 "sha256:b1b7fcee6aedcdc7e62c3a73f238b3d080c7ba6650cd808bce8d7761ec484070", | ||||
|                 "sha256:b66832ea8077d9b3f6e311c4a53d06273db5dc2db6e8a908550f3c14d67e718c", | ||||
|                 "sha256:d0d3ac228c9bbab08134b4004d748cf9f8743504875b3603b3afbb97e3472947", | ||||
|                 "sha256:d10e9dd744cf85c219bf747c75194b624cc7a94f0c80ead624b06bfa9f61d3bc", | ||||
|                 "sha256:ea4362548ee0cbc266949d8a441238d9ad3600ca9910c3fe4e82ee3a50706973", | ||||
|                 "sha256:ed5b3698a2bb241b7f5cbbe277eaa7fe48b07a58784fba4f75224fd066d253ad", | ||||
|                 "sha256:f9dcc1ae73f36e8059589b601e8e4776b9976effd76c21ad6a855a74318efd6e" | ||||
|             ], | ||||
|             "markers": "platform_python_implementation == 'CPython' and python_version < '3.8'", | ||||
|             "version": "==0.1.2" | ||||
|             "version": "==0.2.0" | ||||
|         }, | ||||
|         "sentry-sdk": { | ||||
|             "hashes": [ | ||||
|                 "sha256:528f936118679e9a52dacb96bfefe20acb5d63e0797856c64a582cc3c2bc1f9e", | ||||
|                 "sha256:b4edcb1296fee107439345d0f8b23432b8732b7e28407f928367d0a4a36301a9" | ||||
|                 "sha256:15e51e74b924180c98bcd636cb4634945b0a99a124d50b433c3a9dc6a582e8db", | ||||
|                 "sha256:1d6a2ee908ec6d8f96c27d78bc39e203df4d586d287c233140af7d8d1aca108a" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.11.2" | ||||
|             "version": "==0.12.3" | ||||
|         }, | ||||
|         "service-identity": { | ||||
|             "hashes": [ | ||||
| @ -763,13 +675,6 @@ | ||||
|             ], | ||||
|             "version": "==1.12.0" | ||||
|         }, | ||||
|         "soupsieve": { | ||||
|             "hashes": [ | ||||
|                 "sha256:8662843366b8d8779dec4e2f921bebec9afd856a5ff2e82cd419acc5054a1a92", | ||||
|                 "sha256:a5a6166b4767725fd52ae55fee8c8b6137d9a51e9f1edea461a062a759160118" | ||||
|             ], | ||||
|             "version": "==1.9.3" | ||||
|         }, | ||||
|         "sqlparse": { | ||||
|             "hashes": [ | ||||
|                 "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", | ||||
| @ -792,37 +697,6 @@ | ||||
|             ], | ||||
|             "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": { | ||||
|             "hashes": [ | ||||
|                 "sha256:01c69f4fe8ed503b2951bef85d996a9d22434d2431584b5b107b2981ff416fbd", | ||||
| @ -836,11 +710,11 @@ | ||||
|                 "secure" | ||||
|             ], | ||||
|             "hashes": [ | ||||
|                 "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", | ||||
|                 "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" | ||||
|                 "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", | ||||
|                 "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==1.25.3" | ||||
|             "version": "==1.25.6" | ||||
|         }, | ||||
|         "vine": { | ||||
|             "hashes": [ | ||||
| @ -849,61 +723,12 @@ | ||||
|             ], | ||||
|             "version": "==1.3.0" | ||||
|         }, | ||||
|         "websocket-client": { | ||||
|             "hashes": [ | ||||
|                 "sha256:1151d5fb3a62dc129164292e1227655e4bbc5dd5340a5165dfae61128ec50aa9", | ||||
|                 "sha256:1fd5520878b68b84b5748bb30e592b10d0a91529d5383f74f4964e72b297fd3a" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==0.56.0" | ||||
|         }, | ||||
|         "zc.lockfile": { | ||||
|             "hashes": [ | ||||
|                 "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b", | ||||
|                 "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f" | ||||
|             ], | ||||
|             "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": { | ||||
| @ -912,6 +737,7 @@ | ||||
|                 "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4", | ||||
|                 "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==2.2.5" | ||||
|         }, | ||||
|         "autopep8": { | ||||
| @ -929,13 +755,6 @@ | ||||
|             "index": "pypi", | ||||
|             "version": "==1.6.2" | ||||
|         }, | ||||
|         "bleach": { | ||||
|             "hashes": [ | ||||
|                 "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", | ||||
|                 "sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa" | ||||
|             ], | ||||
|             "version": "==3.1.0" | ||||
|         }, | ||||
|         "bumpversion": { | ||||
|             "hashes": [ | ||||
|                 "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e", | ||||
| @ -944,20 +763,6 @@ | ||||
|             "index": "pypi", | ||||
|             "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": { | ||||
|             "hashes": [ | ||||
|                 "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", | ||||
| @ -1006,11 +811,11 @@ | ||||
|         }, | ||||
|         "django": { | ||||
|             "hashes": [ | ||||
|                 "sha256:148a4a2d1a85b23883b0a4e99ab7718f518a83675e4485e44dc0c1d36988c5fa", | ||||
|                 "sha256:deb70aa038e59b58593673b15e9a711d1e5ccd941b5973b30750d5d026abfd56" | ||||
|                 "sha256:4025317ca01f75fc79250ff7262a06d8ba97cd4f82e93394b2a0a6a4a925caeb", | ||||
|                 "sha256:a8ca1033acac9f33995eb2209a6bf18a4681c3e5269a878e9a7e0b7384ed1ca3" | ||||
|             ], | ||||
|             "index": "pypi", | ||||
|             "version": "==2.2.5" | ||||
|             "version": "==2.2.6" | ||||
|         }, | ||||
|         "django-debug-toolbar": { | ||||
|             "hashes": [ | ||||
| @ -1020,14 +825,6 @@ | ||||
|             "index": "pypi", | ||||
|             "version": "==2.0" | ||||
|         }, | ||||
|         "docutils": { | ||||
|             "hashes": [ | ||||
|                 "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", | ||||
|                 "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", | ||||
|                 "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" | ||||
|             ], | ||||
|             "version": "==0.15.2" | ||||
|         }, | ||||
|         "dodgy": { | ||||
|             "hashes": [ | ||||
|                 "sha256:65e13cf878d7aff129f1461c13cb5fd1bb6dfe66bb5327e09379c3877763280c" | ||||
| @ -1036,24 +833,17 @@ | ||||
|         }, | ||||
|         "gitdb2": { | ||||
|             "hashes": [ | ||||
|                 "sha256:83361131a1836661a155172932a13c08bda2db3674e4caa32368aa6eb02f38c2", | ||||
|                 "sha256:e3a0141c5f2a3f635c7209d56c496ebe1ad35da82fe4d3ec4aaa36278d70648a" | ||||
|                 "sha256:1b6df1433567a51a4a9c1a5a0de977aa351a405cc56d7d35f3388bad1f630350", | ||||
|                 "sha256:96bbb507d765a7f51eb802554a9cfe194a174582f772e0d89f4e87288c288b7b" | ||||
|             ], | ||||
|             "version": "==2.0.5" | ||||
|             "version": "==2.0.6" | ||||
|         }, | ||||
|         "gitpython": { | ||||
|             "hashes": [ | ||||
|                 "sha256:947cc75913e7b6da108458136607e2ee0e40c20be1e12d4284e7c6c12956c276", | ||||
|                 "sha256:d2f4945f8260f6981d724f5957bc076398ada55cb5d25aaee10108bcdc894100" | ||||
|                 "sha256:631263cc670aa56ce3d3c414cf0fe2e840f2e913514b138ea28d88a477bbcd21", | ||||
|                 "sha256:6e97b9f0954807f30c2dd8e3165731ed6c477a1b365f194b69d81d7940a08332" | ||||
|             ], | ||||
|             "version": "==3.0.2" | ||||
|         }, | ||||
|         "idna": { | ||||
|             "hashes": [ | ||||
|                 "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", | ||||
|                 "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" | ||||
|             ], | ||||
|             "version": "==2.8" | ||||
|             "version": "==3.0.3" | ||||
|         }, | ||||
|         "isort": { | ||||
|             "hashes": [ | ||||
| @ -1107,13 +897,6 @@ | ||||
|             ], | ||||
|             "version": "==0.4.1" | ||||
|         }, | ||||
|         "pkginfo": { | ||||
|             "hashes": [ | ||||
|                 "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb", | ||||
|                 "sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32" | ||||
|             ], | ||||
|             "version": "==1.5.0.1" | ||||
|         }, | ||||
|         "prospector": { | ||||
|             "hashes": [ | ||||
|                 "sha256:aba551e53dc1a5a432afa67385eaa81d7b4cf4c162dc1a4d0ee00b3a0712ad90" | ||||
| @ -1142,13 +925,6 @@ | ||||
|             ], | ||||
|             "version": "==1.6.0" | ||||
|         }, | ||||
|         "pygments": { | ||||
|             "hashes": [ | ||||
|                 "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", | ||||
|                 "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" | ||||
|             ], | ||||
|             "version": "==2.4.2" | ||||
|         }, | ||||
|         "pylint": { | ||||
|             "hashes": [ | ||||
|                 "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09", | ||||
| @ -1179,16 +955,17 @@ | ||||
|         }, | ||||
|         "pylint-plugin-utils": { | ||||
|             "hashes": [ | ||||
|                 "sha256:8d9e31d5ea8b7b0003e1f0f136b44a5235896a32e47c5bc2ef1143e9f6ba0b74" | ||||
|                 "sha256:2f30510e1c46edf268d3a195b2849bd98a1b9433229bb2ba63b8d776e1fc4d0a", | ||||
|                 "sha256:57625dcca20140f43731311cd8fd879318bf45a8b0fd17020717a8781714a25a" | ||||
|             ], | ||||
|             "version": "==0.5" | ||||
|             "version": "==0.6" | ||||
|         }, | ||||
|         "pytz": { | ||||
|             "hashes": [ | ||||
|                 "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", | ||||
|                 "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" | ||||
|                 "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", | ||||
|                 "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" | ||||
|             ], | ||||
|             "version": "==2019.2" | ||||
|             "version": "==2019.3" | ||||
|         }, | ||||
|         "pyyaml": { | ||||
|             "hashes": [ | ||||
| @ -1209,27 +986,6 @@ | ||||
|             "index": "pypi", | ||||
|             "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": { | ||||
|             "hashes": [ | ||||
|                 "sha256:9fbc4b24e8b7c3663aff32e3eba34596848c6b91bd425079b386973bd8d08931" | ||||
| @ -1258,9 +1014,10 @@ | ||||
|         }, | ||||
|         "snowballstemmer": { | ||||
|             "hashes": [ | ||||
|                 "sha256:713e53b79cbcf97bc5245a06080a33d54a77e7cce2f789c835a143bcdb5c033e" | ||||
|                 "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", | ||||
|                 "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" | ||||
|             ], | ||||
|             "version": "==1.9.1" | ||||
|             "version": "==2.0.0" | ||||
|         }, | ||||
|         "sqlparse": { | ||||
|             "hashes": [ | ||||
| @ -1276,21 +1033,6 @@ | ||||
|             ], | ||||
|             "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": { | ||||
|             "hashes": [ | ||||
|                 "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", | ||||
| @ -1320,24 +1062,6 @@ | ||||
|             "index": "pypi", | ||||
|             "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": { | ||||
|             "hashes": [ | ||||
|                 "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.lock /app/ | ||||
|  | ||||
| WORKDIR /app/ | ||||
|  | ||||
| RUN apk update && \ | ||||
|     apk add --no-cache openssl-dev build-base libxml2-dev libxslt-dev libffi-dev gcc musl-dev libgcc zlib-dev postgresql-dev && \ | ||||
|     pip install pipenv --no-cache-dir && \ | ||||
|     pipenv lock -r > requirements.txt && \ | ||||
| RUN apt-get update && \ | ||||
|     apt-get install -y --no-install-recommends build-essential && \ | ||||
|     pip install pipenv uwsgi --no-cache-dir && \ | ||||
|     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 && \ | ||||
|     pip install -r requirements.txt  --no-cache-dir && \ | ||||
|     adduser -S passbook && \ | ||||
|     chown -R passbook /app | ||||
|     adduser --system --no-create-home --uid 1000 --group --home /app passbook | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| FROM docker.beryju.org/passbook/base:latest | ||||
|  | ||||
| RUN pipenv lock --dev -r > requirements-dev.txt && \ | ||||
|     pipenv --rm && \ | ||||
|     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_types application/javascript image/* text/css; | ||||
|         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; | ||||
|         root /static/; | ||||
|         root /data/; | ||||
| 
 | ||||
|         location /_/healthz { | ||||
|             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 | ||||
| appVersion: "0.3.0-beta" | ||||
| appVersion: "0.6.4-beta" | ||||
| description: A Helm chart for 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 | ||||
|  | ||||
							
								
								
									
										
											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: | ||||
| - name: rabbitmq | ||||
|   repository: https://kubernetes-charts.storage.googleapis.com/ | ||||
|   version: 4.3.2 | ||||
| - name: postgresql | ||||
|   repository: https://kubernetes-charts.storage.googleapis.com/ | ||||
|   version: 3.10.1 | ||||
|   version: 4.2.2 | ||||
| - name: redis | ||||
|   repository: https://kubernetes-charts.storage.googleapis.com/ | ||||
|   version: 5.1.0 | ||||
| digest: sha256:8bf68bc928a2e3c0f05139635be05fa0840554c7bde4cecd624fac78fb5fa5a3 | ||||
| generated: 2019-03-21T11:06:51.553379+01:00 | ||||
|   version: 9.2.1 | ||||
| digest: sha256:8782e974a1094eaeecf1d68f093ca4fb84977217b2bd38b09790a05ec289aec2 | ||||
| generated: "2019-10-02T21:03:25.90491153Z" | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| dependencies: | ||||
| - name: postgresql | ||||
|   version: 6.3.10 | ||||
|   version: 4.2.2 | ||||
|   repository: https://kubernetes-charts.storage.googleapis.com/ | ||||
| - name: redis | ||||
|   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" | ||||
|       name: "{{ .Values.postgresql.postgresqlDatabase }}" | ||||
|       user: postgres | ||||
|       password: "{{ .Values.postgresql.postgresqlPassword }}" | ||||
|     redis: | ||||
|       host: "{{ .Release.Name }}-redis-master" | ||||
|       password: "{{ .Values.redis.password }}" | ||||
|       cache_db: 0 | ||||
|       message_queue_db: 1 | ||||
|  | ||||
|     # Error reporting, sends stacktrace to sentry.beryju.org | ||||
|     error_report_enabled: {{ .Values.config.error_reporting }} | ||||
|  | ||||
|     {{- 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 | ||||
|     domain: ".{{ .Values.ingress.hosts[0] }}" | ||||
|  | ||||
| @ -37,14 +37,9 @@ spec: | ||||
|             backend: | ||||
|               serviceName: {{ $fullName }}-static | ||||
|               servicePort: http | ||||
|   {{- end }} | ||||
|   {{- range .Values.ingress.app_gw_hosts }} | ||||
|     - host: {{ . | quote }} | ||||
|       http: | ||||
|         paths: | ||||
|           - path: / | ||||
|           - path: /robots.txt | ||||
|             backend: | ||||
|               serviceName: {{ $fullName }}-appgw | ||||
|               serviceName: {{ $fullName }}-static | ||||
|               servicePort: http | ||||
|   {{- 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/managed-by: {{ .Release.Service }} | ||||
| spec: | ||||
|   replicas: {{ .Values.replicaCount }} | ||||
|   replicas: 2 | ||||
|   selector: | ||||
|     matchLabels: | ||||
|       app.kubernetes.io/name: {{ include "passbook.name" . }} | ||||
| @ -34,21 +34,61 @@ spec: | ||||
|           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 | ||||
|       containers: | ||||
|         - name: {{ .Chart.Name }} | ||||
|           image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}" | ||||
|           imagePullPolicy: IfNotPresent | ||||
|           command: | ||||
|             - ./manage.py | ||||
|             - uwsgi | ||||
|           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: | ||||
|             - name: http | ||||
|               containerPort: 8000 | ||||
|               protocol: TCP | ||||
|           volumeMounts: | ||||
|             - mountPath: /etc/passbook | ||||
|               name: config-volume | ||||
|           livenessProbe: | ||||
|             httpGet: | ||||
|               path: / | ||||
| @ -65,8 +105,8 @@ spec: | ||||
|                   value: kubernetes-healthcheck-host | ||||
|           resources: | ||||
|             requests: | ||||
|               cpu: 50m | ||||
|               memory: 150M | ||||
|               cpu: 100m | ||||
|               memory: 200M | ||||
|             limits: | ||||
|               cpu: 200m | ||||
|               memory: 300M | ||||
|               cpu: 300m | ||||
|               memory: 350M | ||||
|  | ||||
| @ -8,7 +8,7 @@ metadata: | ||||
|     app.kubernetes.io/instance: {{ .Release.Name }} | ||||
|     app.kubernetes.io/managed-by: {{ .Release.Service }} | ||||
| spec: | ||||
|   replicas: {{ .Values.replicaCount }} | ||||
|   replicas: 1 | ||||
|   selector: | ||||
|     matchLabels: | ||||
|       app.kubernetes.io/name: {{ include "passbook.name" . }} | ||||
| @ -29,16 +29,36 @@ spec: | ||||
|           image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}" | ||||
|           imagePullPolicy: IfNotPresent | ||||
|           command: | ||||
|             - ./manage.py | ||||
|             - celery | ||||
|           args: | ||||
|             - worker | ||||
|           ports: | ||||
|             - name: http | ||||
|               containerPort: 8000 | ||||
|               protocol: TCP | ||||
|             - --autoscale=10,3 | ||||
|             - -E | ||||
|             - -B | ||||
|             - -A=passbook.root.celery | ||||
|           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 | ||||
|           resources: | ||||
|             requests: | ||||
|               cpu: 150m | ||||
|  | ||||
| @ -1,11 +1,8 @@ | ||||
| # Default values for passbook. | ||||
| # This is a YAML-formatted file. | ||||
| # Declare variables to be passed into your templates. | ||||
|  | ||||
| replicaCount: 1 | ||||
|  | ||||
| image: | ||||
|   tag: 0.3.0-beta | ||||
|   tag: 0.6.4-beta | ||||
|  | ||||
| nameOverride: "" | ||||
|  | ||||
| @ -19,11 +16,13 @@ config: | ||||
|  | ||||
| postgresql: | ||||
|   postgresqlDatabase: passbook | ||||
|   postgresqlPassword: foo | ||||
|  | ||||
| rabbitmq: | ||||
|   rabbitmq: | ||||
|     password: foo | ||||
| redis: | ||||
|   cluster: | ||||
|     enabled: false | ||||
|   master: | ||||
|     persistence: | ||||
|       enabled: false | ||||
|  | ||||
| service: | ||||
|   type: ClusterIP | ||||
| @ -37,28 +36,7 @@ ingress: | ||||
|   path: / | ||||
|   hosts: | ||||
|     - passbook.k8s.local | ||||
|   app_gw_hosts: | ||||
|     - '*.passbook.k8s.local' | ||||
|   defaultHost: passbook.k8s.local | ||||
|   tls: [] | ||||
|   #  - secretName: chart-example-tls | ||||
|   #    hosts: | ||||
|   #      - 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""" | ||||
| __version__ = '0.3.0-beta' | ||||
| __version__ = '0.6.4-beta' | ||||
|  | ||||
| @ -179,8 +179,8 @@ | ||||
|                     <span class="card-pf-aggregate-status-notification"> | ||||
|                         <a href="#"> | ||||
|                             {% if worker_count < 1%} | ||||
|                             <span class="pficon-error-circle-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 }} | ||||
|                             <span class="pficon-warning-triangle-o" data-toggle="tooltip" data-placement="right" | ||||
|                                 title="{% trans 'No workers connected.' %}"></span> {{ worker_count }} | ||||
|                             {% else %} | ||||
|                             <span class="pficon pficon-ok"></span>{{ worker_count }} | ||||
|                             {% endif %} | ||||
|  | ||||
| @ -7,6 +7,10 @@ | ||||
| <div class="container"> | ||||
|     <h1><span class="pficon-users"></span> {% trans "Users" %}</h1> | ||||
|     <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"> | ||||
|         <thead> | ||||
|             <tr> | ||||
|  | ||||
| @ -8,7 +8,7 @@ from structlog import get_logger | ||||
| from passbook.lib.utils.template import render_to_string | ||||
|  | ||||
| register = template.Library() | ||||
| LOGGER = get_logger(__name__) | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| @register.simple_tag() | ||||
| def get_links(model_instance): | ||||
|  | ||||
| @ -61,6 +61,7 @@ urlpatterns = [ | ||||
|     # Users | ||||
|     path('users/', users.UserListView.as_view(), | ||||
|          name='users'), | ||||
|     path('users/create/', users.UserCreateView.as_view(), name='user-create'), | ||||
|     path('users/<int:pk>/update/', | ||||
|          users.UserUpdateView.as_view(), name='user-update'), | ||||
|     path('users/<int:pk>/delete/', | ||||
|  | ||||
| @ -3,8 +3,8 @@ from django.core.cache import cache | ||||
| from django.shortcuts import redirect, reverse | ||||
| from django.views.generic import TemplateView | ||||
|  | ||||
| from passbook import __version__ | ||||
| from passbook.admin.mixins import AdminRequiredMixin | ||||
| from passbook.core import __version__ | ||||
| from passbook.core.models import (Application, Factor, Invitation, Policy, | ||||
|                                   Provider, Source, User) | ||||
| 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.core.models import Policy | ||||
| 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): | ||||
|  | ||||
| @ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404, redirect | ||||
| from django.urls import reverse, reverse_lazy | ||||
| from django.utils.translation import ugettext as _ | ||||
| from django.views import View | ||||
| from django.views.generic import DeleteView, ListView, UpdateView | ||||
| from django.views.generic import CreateView, DeleteView, ListView, UpdateView | ||||
|  | ||||
| from passbook.admin.forms.users import UserForm | ||||
| from passbook.admin.mixins import AdminRequiredMixin | ||||
| @ -19,6 +19,17 @@ class UserListView(AdminRequiredMixin, ListView): | ||||
|     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): | ||||
|     """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 django.contrib.postgres.fields.jsonb | ||||
| import django.db.models.deletion | ||||
| from django.conf import settings | ||||
| 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')])), | ||||
|                 ('date', models.DateTimeField(auto_now_add=True)), | ||||
|                 ('app', models.TextField()), | ||||
|                 ('_context', models.TextField()), | ||||
|                 ('context', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)), | ||||
|                 ('request_ip', models.GenericIPAddressField()), | ||||
|                 ('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)), | ||||
| @ -33,19 +34,4 @@ class Migration(migrations.Migration): | ||||
|                 '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 | ||||
|  | ||||
| LOGGER = get_logger(__name__) | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| class AuditEntry(UUIDModel): | ||||
|     """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""" | ||||
| from importlib import import_module | ||||
|  | ||||
| from django.apps import AppConfig | ||||
| from structlog import get_logger | ||||
|  | ||||
| from passbook.lib.config import CONFIG | ||||
|  | ||||
| LOGGER = get_logger(__name__) | ||||
|  | ||||
| class PassbookCoreConfig(AppConfig): | ||||
|     """passbook core app config""" | ||||
| @ -15,13 +9,3 @@ class PassbookCoreConfig(AppConfig): | ||||
|     label = 'passbook_core' | ||||
|     verbose_name = 'passbook Core' | ||||
|     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 | ||||
|         fields = ['name', 'slug', 'launch_url', 'icon_url', | ||||
|                   'policies', 'provider', 'skip_authorization'] | ||||
|                   'provider', 'policies', 'skip_authorization'] | ||||
|         widgets = { | ||||
|             'name': 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.utils.ui import human_list | ||||
|  | ||||
| LOGGER = get_logger(__name__) | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| class LoginForm(forms.Form): | ||||
|     """Allow users to login""" | ||||
| @ -81,13 +81,3 @@ class SignUpForm(forms.Form): | ||||
|         if password != password_repeat: | ||||
|             raise ValidationError(_("Passwords don't match")) | ||||
|         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.utils.translation import gettext as _ | ||||
|  | ||||
| from passbook.core.models import (DebugPolicy, FieldMatcherPolicy, | ||||
|                                   GroupMembershipPolicy, PasswordPolicy, | ||||
|                                   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(), | ||||
|         } | ||||
| from passbook.core.models import DebugPolicy | ||||
| from passbook.policies.forms import GENERAL_FIELDS | ||||
|  | ||||
|  | ||||
| class DebugPolicyForm(forms.ModelForm): | ||||
| @ -52,49 +20,3 @@ class DebugPolicyForm(forms.ModelForm): | ||||
|         labels = { | ||||
|             '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 django.contrib.auth.models | ||||
| import django.contrib.auth.validators | ||||
| import django.contrib.postgres.fields.jsonb | ||||
| import django.db.models.deletion | ||||
| import django.utils.timezone | ||||
| from django.conf import settings | ||||
| from django.db import migrations, models | ||||
|  | ||||
| import passbook.core.models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     initial = True | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('auth', '0009_alter_user_last_name_max_length'), | ||||
|         ('auth', '0011_update_proxy_permissions'), | ||||
|     ] | ||||
|  | ||||
|     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')), | ||||
|                 ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), | ||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False)), | ||||
|                 ('name', models.TextField()), | ||||
|                 ('password_change_date', models.DateTimeField(auto_now_add=True)), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'user', | ||||
| @ -44,39 +49,17 @@ class Migration(migrations.Migration): | ||||
|                 ('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( | ||||
|             name='Policy', | ||||
|             fields=[ | ||||
|                 ('created', models.DateField(auto_now_add=True)), | ||||
|                 ('created', models.DateTimeField(auto_now_add=True)), | ||||
|                 ('last_updated', models.DateTimeField(auto_now=True)), | ||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||
|                 ('name', models.TextField(blank=True, null=True)), | ||||
|                 ('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)), | ||||
|                 ('negate', models.BooleanField(default=False)), | ||||
|                 ('order', models.IntegerField(default=0)), | ||||
|                 ('timeout', models.IntegerField(default=30)), | ||||
|             ], | ||||
|             options={ | ||||
|                 'abstract': False, | ||||
| @ -85,28 +68,136 @@ class Migration(migrations.Migration): | ||||
|         migrations.CreateModel( | ||||
|             name='PolicyModel', | ||||
|             fields=[ | ||||
|                 ('created', models.DateField(auto_now_add=True)), | ||||
|                 ('created', models.DateTimeField(auto_now_add=True)), | ||||
|                 ('last_updated', models.DateTimeField(auto_now=True)), | ||||
|                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||
|                 ('policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')), | ||||
|             ], | ||||
|             options={ | ||||
|                 '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( | ||||
|             name='Provider', | ||||
|             fields=[ | ||||
|                 ('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( | ||||
|             name='UserSourceConnection', | ||||
|             fields=[ | ||||
|                 ('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)), | ||||
|                 ('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( | ||||
|             name='Application', | ||||
| @ -124,131 +215,9 @@ class Migration(migrations.Migration): | ||||
|             }, | ||||
|             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( | ||||
|             model_name='user', | ||||
|             name='sources', | ||||
|             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 | ||||
| 
 | ||||
| @ -6,13 +6,13 @@ from django.db import migrations, models | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         ('passbook_saml_idp', '0002_samlpropertymapping'), | ||||
|         ('passbook_core', '0001_initial'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name='samlprovider', | ||||
|             name='audience', | ||||
|             model_name='nonce', | ||||
|             name='description', | ||||
|             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""" | ||||
| import re | ||||
| from datetime import timedelta | ||||
| from random import SystemRandom | ||||
| from time import sleep | ||||
| from typing import List | ||||
| from typing import Optional | ||||
| from uuid import uuid4 | ||||
|  | ||||
| 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.urls import reverse_lazy | ||||
| from django.utils.timezone import now | ||||
| @ -17,38 +16,26 @@ from structlog import get_logger | ||||
|  | ||||
| from passbook.core.signals import password_changed | ||||
| 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(): | ||||
|     """Default duration a Nonce is valid""" | ||||
|     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): | ||||
|     """Custom Group model which supports a basic hierarchy""" | ||||
|  | ||||
|     name = models.CharField(_('name'), max_length=80) | ||||
|     parent = models.ForeignKey('Group', blank=True, null=True, | ||||
|                                on_delete=models.SET_NULL, related_name='children') | ||||
|     tags = HStoreField(default=dict) | ||||
|     tags = JSONField(default=dict, blank=True) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Group %s" % self.name | ||||
|         return f"Group {self.name}" | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
| @ -70,6 +57,7 @@ class User(AbstractUser): | ||||
|         self.password_change_date = now() | ||||
|         return super().set_password(password) | ||||
|  | ||||
|  | ||||
| class Provider(models.Model): | ||||
|     """Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application""" | ||||
|  | ||||
| @ -83,11 +71,26 @@ class Provider(models.Model): | ||||
|             return getattr(self, 'name') | ||||
|         return super().__str__() | ||||
|  | ||||
|  | ||||
| class PolicyModel(UUIDModel, CreatedUpdatedModel): | ||||
|     """Base model which can have policies applied to it""" | ||||
|  | ||||
|     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): | ||||
|     """Authentication factor, multiple instances of the same Factor can be used""" | ||||
|  | ||||
| @ -100,55 +103,14 @@ class Factor(PolicyModel): | ||||
|     type = '' | ||||
|     form = '' | ||||
|  | ||||
|     def has_user_settings(self): | ||||
|         """Entrypoint to integrate with User settings. Can either return False if no | ||||
|         user settings are available, or a tuple or string, string, string where the first string | ||||
|         is the name the item has, the second string is the icon and the third is the view-name.""" | ||||
|         return False | ||||
|     def user_settings(self) -> Optional[UserSettings]: | ||||
|         """Entrypoint to integrate with User settings. Can either return None if no | ||||
|         user settings are available, or an instanace of UserSettings.""" | ||||
|         return None | ||||
|  | ||||
|     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): | ||||
|     """Every Application which uses passbook for authentication/identification/authorization | ||||
| @ -174,6 +136,7 @@ class Application(PolicyModel): | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|  | ||||
| class Source(PolicyModel): | ||||
|     """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 None | ||||
|  | ||||
|     def has_user_settings(self): | ||||
|         """Entrypoint to integrate with User settings. Can either return False if no | ||||
|         user settings are available, or a tuple or string, string, string where the first string | ||||
|         is the name the item has, the second string is the icon and the third is the view-name.""" | ||||
|         return False | ||||
|     def user_settings(self) -> Optional[UserSettings]: | ||||
|         """Entrypoint to integrate with User settings. Can either return None if no | ||||
|         user settings are available, or an instanace of UserSettings.""" | ||||
|         return None | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|  | ||||
| class UserSourceConnection(CreatedUpdatedModel): | ||||
|     """Connection between User and Source.""" | ||||
|  | ||||
| @ -219,6 +182,7 @@ class UserSourceConnection(CreatedUpdatedModel): | ||||
|  | ||||
|         unique_together = (('user', 'source'),) | ||||
|  | ||||
|  | ||||
| class Policy(UUIDModel, CreatedUpdatedModel): | ||||
|     """Policies which specify if a user is authorized to use an Application. Can be overridden by | ||||
|     other types to add other fields, more logic, etc.""" | ||||
| @ -241,151 +205,12 @@ class Policy(UUIDModel, CreatedUpdatedModel): | ||||
|     def __str__(self): | ||||
|         if 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""" | ||||
|         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): | ||||
|     """Policy used for debugging the PolicyEngine. Returns a fixed result, | ||||
| @ -397,10 +222,10 @@ class DebugPolicy(Policy): | ||||
|  | ||||
|     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 = 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) | ||||
|         return PolicyResult(self.result, 'Debugging') | ||||
|  | ||||
| @ -409,35 +234,6 @@ class DebugPolicy(Policy): | ||||
|         verbose_name = _('Debug Policy') | ||||
|         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): | ||||
|     """Single-use invitation link""" | ||||
| @ -451,31 +247,39 @@ class Invitation(UUIDModel): | ||||
|     @property | ||||
|     def link(self): | ||||
|         """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): | ||||
|         return "Invitation %s created by %s" % (self.uuid, self.created_by) | ||||
|         return f"Invitation {self.uuid.hex} created by {self.created_by}" | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         verbose_name = _('Invitation') | ||||
|         verbose_name_plural = _('Invitations') | ||||
|  | ||||
|  | ||||
| class Nonce(UUIDModel): | ||||
|     """One-time link for password resets/sign-up-confirmations""" | ||||
|  | ||||
|     expires = models.DateTimeField(default=default_nonce_duration) | ||||
|     user = models.ForeignKey('User', on_delete=models.CASCADE) | ||||
|     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): | ||||
|         return "Nonce %s (expires=%s)" % (self.uuid.hex, self.expires) | ||||
|         return f"Nonce f{self.uuid.hex} {self.description} (expires={self.expires})" | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         verbose_name = _('Nonce') | ||||
|         verbose_name_plural = _('Nonces') | ||||
|  | ||||
|  | ||||
| class PropertyMapping(UUIDModel): | ||||
|     """User-defined key -> x mapping which can be used by providers to expose extra data.""" | ||||
|  | ||||
| @ -485,7 +289,7 @@ class PropertyMapping(UUIDModel): | ||||
|     objects = InheritanceManager() | ||||
|  | ||||
|     def __str__(self): | ||||
|         return "Property Mapping %s" % self.name | ||||
|         return f"Property Mapping {self.name}" | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|  | ||||
| @ -5,37 +5,20 @@ from django.db.models.signals import post_save | ||||
| from django.dispatch import receiver | ||||
| from structlog import get_logger | ||||
|  | ||||
| from passbook.core.exceptions import PasswordPolicyInvalid | ||||
|  | ||||
| LOGGER = get_logger(__name__) | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| user_signed_up = Signal(providing_args=['request', 'user']) | ||||
| invitation_created = Signal(providing_args=['request', 'invitation']) | ||||
| invitation_used = Signal(providing_args=['request', 'invitation', 'user']) | ||||
| 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) | ||||
| # pylint: disable=unused-argument | ||||
| def invalidate_policy_cache(sender, instance, **kwargs): | ||||
| def invalidate_policy_cache(sender, instance, **_): | ||||
|     """Invalidate Policy cache when policy is updated""" | ||||
|     from passbook.core.models import 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) | ||||
|         cache.delete_many(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""" | ||||
| 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 passbook.core.models import Nonce | ||||
| from passbook.lib.config import CONFIG | ||||
| from passbook.root.celery import CELERY_APP | ||||
|  | ||||
| LOGGER = get_logger(__name__) | ||||
|  | ||||
| @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() | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| @CELERY_APP.task() | ||||
| def clean_nonces(): | ||||
|     """Remove expired nonces""" | ||||
|     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> | ||||
|   {% block scripts %} | ||||
|   {% endblock %} | ||||
|   <div class="modals"> | ||||
|     {% include 'partials/about_modal.html' %} | ||||
|   </div> | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
|  | ||||
| @ -46,9 +46,6 @@ | ||||
|   <script src="{% static 'js/passbook.js' %}"></script> | ||||
|   {% block scripts %} | ||||
|   {% endblock %} | ||||
|   <div class="modals"> | ||||
|     {% include 'partials/about_modal.html' %} | ||||
|   </div> | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
|  | ||||
| @ -23,37 +23,18 @@ | ||||
|     </div> | ||||
|     <nav class="collapse navbar-collapse"> | ||||
|         <ul class="nav navbar-nav navbar-right navbar-iconic navbar-utility"> | ||||
|             <li class="dropdown"> | ||||
|                 <button class="btn btn-link dropdown-toggle nav-item-iconic" id="dropdownMenu1" data-toggle="dropdown" | ||||
|                     aria-haspopup="true" aria-expanded="true"> | ||||
|                     <span title="Help" class="fa pficon-help"></span> | ||||
|                 </button> | ||||
|                 <ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> | ||||
|                     {% comment %} <li><a href="#0">Help</a></li> {% endcomment %} | ||||
|                     <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"> | ||||
|             <a href="{% url 'passbook_core:auth-logout' %}" class="btn btn-link nav-item-iconic" aria-haspopup="true" aria-expanded="true"> | ||||
|                 <span title="Username" class="fa fa-sign-out"></span> | ||||
|                 <span class="dropdown-title"> | ||||
|                     {% trans 'Logout' %} | ||||
|                 </span> | ||||
|             </a> | ||||
|             <a href="{% url 'passbook_core:user-settings' %}" class="btn btn-link nav-item-iconic" aria-haspopup="true" aria-expanded="true"> | ||||
|                 <span title="Username" class="fa pficon-user"></span> | ||||
|                 <span class="dropdown-title"> | ||||
|                         {{ user.username }} <span class="caret"></span> | ||||
|                     {{ user.username }} | ||||
|                 </span> | ||||
|                 </button> | ||||
|                 <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> | ||||
|             </a> | ||||
|         </ul> | ||||
|     </nav> | ||||
| </nav> | ||||
| @ -65,123 +46,82 @@ | ||||
|                 <span class="list-group-item-value">{% trans 'Overview' %}</span> | ||||
|             </a> | ||||
|         </li> | ||||
|         {% is_active_app 'passbook_admin' as is_admin %} | ||||
|         {% if user.is_superuser %} | ||||
|         <li class="list-group-item {% is_active_app 'passbook_admin' %} secondary-nav-item-pf"> | ||||
|             <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' %}"> | ||||
|         <li class="list-group-item {% is_active_url 'passbook_admin:overview' %}"> | ||||
|             <a href="{% url 'passbook_admin:overview' %}"> | ||||
|                             <span class="list-group-item-value"> | ||||
|                                 {% trans 'Overview' %} | ||||
|                             </span> | ||||
|                 <span class="fa pficon-build" data-toggle="tooltip" title="{% trans 'System Status' %}"></span> | ||||
|                 <span class="list-group-item-value">{% trans 'System Status' %}</span> | ||||
|             </a> | ||||
|         </li> | ||||
|         <li | ||||
|             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' %}"> | ||||
|                             <span class="list-group-item-value"> | ||||
|                                 {% trans 'Applications' %} | ||||
|                             </span> | ||||
|                 <span class="fa pficon-applications" data-toggle="tooltip" title="{% trans 'Applications' %}"></span> | ||||
|                 <span class="list-group-item-value">{% trans 'Applications' %}</span> | ||||
|             </a> | ||||
|         </li> | ||||
|         <li | ||||
|             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' %}"> | ||||
|                             <span class="list-group-item-value"> | ||||
|                                 {% trans 'Sources' %} | ||||
|                             </span> | ||||
|                 <span class="fa pficon-resource-pool" data-toggle="tooltip" title="{% trans 'Sources' %}"></span> | ||||
|                 <span class="list-group-item-value">{% trans 'Sources' %}</span> | ||||
|             </a> | ||||
|         </li> | ||||
|         <li | ||||
|             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' %}"> | ||||
|                             <span class="list-group-item-value"> | ||||
|                                 {% trans 'Providers' %} | ||||
|                             </span> | ||||
|                 <span class="fa pficon-integration" data-toggle="tooltip" title="{% trans 'Providers' %}"></span> | ||||
|                 <span class="list-group-item-value">{% trans 'Providers' %}</span> | ||||
|             </a> | ||||
|         </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' %}"> | ||||
|             <a href="{% url 'passbook_admin:property-mappings' %}"> | ||||
|                             <span class="list-group-item-value"> | ||||
|                                 {% trans 'Property Mappings' %} | ||||
|                             </span> | ||||
|                 <span class="fa fa-table" data-toggle="tooltip" title="{% trans 'Property Mappings' %}"></span> | ||||
|                 <span class="list-group-item-value">{% trans 'Property Mappings' %}</span> | ||||
|             </a> | ||||
|         </li> | ||||
|         <li | ||||
|             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' %}"> | ||||
|                             <span class="list-group-item-value"> | ||||
|                                 {% trans 'Factors' %} | ||||
|                             </span> | ||||
|                 <span class="fa pficon-plugged" data-toggle="tooltip" title="{% trans 'Factors' %}"></span> | ||||
|                 <span class="list-group-item-value">{% trans 'Factors' %}</span> | ||||
|             </a> | ||||
|         </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' %}"> | ||||
|             <a href="{% url 'passbook_admin:policies' %}"> | ||||
|                             <span class="list-group-item-value"> | ||||
|                                 {% trans 'Policies' %} | ||||
|                             </span> | ||||
|                 <span class="fa pficon-infrastructure" data-toggle="tooltip" title="{% trans 'Policies' %}"></span> | ||||
|                 <span class="list-group-item-value">{% trans 'Policies' %}</span> | ||||
|             </a> | ||||
|         </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' %}"> | ||||
|             <a href="{% url 'passbook_admin:invitations' %}"> | ||||
|                             <span class="list-group-item-value"> | ||||
|                                 {% trans 'Invitations' %} | ||||
|                             </span> | ||||
|                 <span class="fa pficon-migration" data-toggle="tooltip" title="{% trans 'Invitations' %}"></span> | ||||
|                 <span class="list-group-item-value">{% trans 'Invitations' %}</span> | ||||
|             </a> | ||||
|         </li> | ||||
|         <li | ||||
|             class="list-group-item {% is_active 'passbook_admin:users' 'passbook_admin:user-update' 'passbook_admin:user-delete' %}"> | ||||
|             <a href="{% url 'passbook_admin:users' %}"> | ||||
|                             <span class="list-group-item-value"> | ||||
|                                 {% trans 'Users' %} | ||||
|                             </span> | ||||
|                 <span class="fa pficon-users" data-toggle="tooltip" title="{% trans 'Users' %}"></span> | ||||
|                 <span class="list-group-item-value">{% trans 'Users' %}</span> | ||||
|             </a> | ||||
|         </li> | ||||
|         <li | ||||
|             class="list-group-item {% is_active 'passbook_admin:groups' 'passbook_admin:group-update' 'passbook_admin:group-delete' %}"> | ||||
|             <a href="{% url 'passbook_admin:groups' %}"> | ||||
|                             <span class="list-group-item-value"> | ||||
|                                 {% trans 'Groups' %} | ||||
|                             </span> | ||||
|                 <span class="fa pficon-users" data-toggle="tooltip" title="{% trans 'Groups' %}"></span> | ||||
|                 <span class="list-group-item-value">{% trans 'Groups' %}</span> | ||||
|             </a> | ||||
|         </li> | ||||
|         <li class="list-group-item {% is_active 'passbook_admin:audit-log' %}"> | ||||
|             <a href="{% url 'passbook_admin:audit-log' %}"> | ||||
|                             <span class="list-group-item-value"> | ||||
|                                 {% trans 'Audit Log' %} | ||||
|                             </span> | ||||
|                 <span class="fa pficon-catalog" data-toggle="tooltip" title="{% trans 'Audit Log' %}"></span> | ||||
|                 <span class="list-group-item-value">{% trans 'Audit Log' %}</span> | ||||
|             </a> | ||||
|         </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 %} | ||||
|     </ul> | ||||
| </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' %} | ||||
|                     </a> | ||||
|                 </li> | ||||
|                 <li class="nav-divider"></li> | ||||
|                 {% user_factors as uf %} | ||||
|                 {% for name, icon, link in uf %} | ||||
|                 <li class="{% is_active link %}"> | ||||
|                      <a href="{% url link %}"> | ||||
|                          <i class="{{ icon }}"></i> {{ name }} | ||||
|                 {% if uf %} | ||||
|                     <li class="nav-divider"></li> | ||||
|                 {% endif %} | ||||
|                 {% 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> | ||||
|                     </li> | ||||
|                 {% endfor %} | ||||
|                 <li class="nav-divider"></li> | ||||
|                 {% user_sources as us %} | ||||
|                 {% for name, icon, link in us %} | ||||
|                 <li class="{% if link == request.get_full_path %} active {% endif %}"> | ||||
|                     <a href="{{ link }}"> | ||||
|                         <i class="{{ icon }}"></i> {{ name }} | ||||
|                 {% if us %} | ||||
|                     <li class="nav-divider"></li> | ||||
|                 {% endif %} | ||||
|                 {% 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> | ||||
|                     </li> | ||||
|                 {% endfor %} | ||||
|  | ||||
| @ -1,36 +1,38 @@ | ||||
| """passbook user settings template tags""" | ||||
| from typing import List | ||||
|  | ||||
| from django import template | ||||
| from django.template.context import RequestContext | ||||
|  | ||||
| from passbook.core.models import Factor, Source | ||||
| from passbook.policy.engine import PolicyEngine | ||||
| from passbook.core.models import Factor, Source, UserSettings | ||||
| from passbook.policies.engine import PolicyEngine | ||||
|  | ||||
| register = template.Library() | ||||
|  | ||||
| @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""" | ||||
|     user = context.get('request').user | ||||
|     _all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses() | ||||
|     matching_factors = [] | ||||
|     matching_factors: List[UserSettings] = [] | ||||
|     for factor in _all_factors: | ||||
|         _link = factor.has_user_settings() | ||||
|         user_settings = factor.user_settings() | ||||
|         policy_engine = PolicyEngine(factor.policies.all()) | ||||
|         policy_engine.for_user(user).with_request(context.get('request')).build() | ||||
|         if policy_engine.passing and _link: | ||||
|             matching_factors.append(_link) | ||||
|         if policy_engine.passing and user_settings: | ||||
|             matching_factors.append(user_settings) | ||||
|     return matching_factors | ||||
|  | ||||
| @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""" | ||||
|     user = context.get('request').user | ||||
|     _all_sources = Source.objects.filter(enabled=True).select_subclasses() | ||||
|     matching_sources = [] | ||||
|     matching_sources: List[UserSettings] = [] | ||||
|     for factor in _all_sources: | ||||
|         _link = factor.has_user_settings() | ||||
|         user_settings = factor.user_settings() | ||||
|         policy_engine = PolicyEngine(factor.policies.all()) | ||||
|         policy_engine.for_user(user).with_request(context.get('request')).build() | ||||
|         if policy_engine.passing and _link: | ||||
|             matching_sources.append(_link) | ||||
|         if policy_engine.passing and user_settings: | ||||
|             matching_sources.append(user_settings) | ||||
|     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
	