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