Compare commits
84 Commits
version/0.
...
version/0.
Author | SHA1 | Date | |
---|---|---|---|
34ed0b3594 | |||
f008a3e20c | |||
9de950220f | |||
567c90b4c6 | |||
ae19236366 | |||
f9babe7089 | |||
78c74cd469 | |||
32abb27e61 | |||
8478b03892 | |||
e972f2b289 | |||
22c4fb1414 | |||
0154def916 | |||
fc69b6851d | |||
44a3c7fa5f | |||
4e6653e299 | |||
c782585287 | |||
7718b3b3b8 | |||
8ff9e72972 | |||
ef6ef68a39 | |||
48a04744e0 | |||
6446ca8bb2 | |||
b9991465ee | |||
3d8242be06 | |||
ca3bcc565d | |||
432176ea2f | |||
c1dae0b599 | |||
e70d3b6286 | |||
17e6bc921b | |||
46111e7cac | |||
3b7e47dbe2 | |||
fff99f0e3d | |||
2e15b24f0a | |||
088b9592cd | |||
b1e4e32b83 | |||
d91a852eda | |||
171c5b9759 | |||
64290b2a37 | |||
72769b8a0a | |||
1018309413 | |||
6d0ecd228e | |||
40a651e66c | |||
a390bb7b59 | |||
245ec65cbb | |||
17eea4a10c | |||
862fb0f5d2 | |||
ec73b53340 | |||
9110f7fee3 | |||
54cc1fdeef | |||
8f42a7f0b4 | |||
2c221ea819 | |||
93e0441b58 | |||
7f1455cb12 | |||
59fc223a85 | |||
0a6f555c23 | |||
6a4233d6fd | |||
15fa7e9652 | |||
f2acc154cd | |||
d21ec6c9a5 | |||
43dd858cd5 | |||
34cbf5f702 | |||
3c6e94b6a8 | |||
1cd149c815 | |||
4c6f562805 | |||
e59c4ec1c7 | |||
1169db7530 | |||
1453008796 | |||
2209b6d603 | |||
ccbc0384f9 | |||
a48924c896 | |||
dc8d8dd2b6 | |||
afca94ceb8 | |||
0b86231a36 | |||
c0df1f38b8 | |||
2b8fed8f4e | |||
c7322a32a0 | |||
64b75cab84 | |||
f58bc61999 | |||
fb8ccc0283 | |||
c38012f147 | |||
3676ff21c2 | |||
920e705d75 | |||
de0b137b1e | |||
d44ac6e2a3 | |||
71039a4012 |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.4.0-beta
|
current_version = 0.6.6-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.4.0-beta
|
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/base.Dockerfile --destination docker.beryju.org/passbook/base:latest
|
||||||
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.4.0-beta
|
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dev.Dockerfile --destination docker.beryju.org/passbook/dev:latest
|
||||||
stage: build-dev-image
|
stage: build-dev-image
|
||||||
only:
|
only:
|
||||||
refs:
|
refs:
|
||||||
@ -70,13 +70,13 @@ migrations:
|
|||||||
# 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.4.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.6-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.4.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.6-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,7 +2,8 @@ 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
|
|
||||||
|
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
|
USER passbook
|
||||||
|
18
Pipfile
18
Pipfile
@ -4,15 +4,11 @@ url = "https://pypi.org/simple"
|
|||||||
verify_ssl = true
|
verify_ssl = true
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
asgiref = "*"
|
|
||||||
beautifulsoup4 = "*"
|
|
||||||
celery = "*"
|
celery = "*"
|
||||||
channels = "*"
|
|
||||||
cherrypy = "*"
|
cherrypy = "*"
|
||||||
colorlog = "*"
|
|
||||||
daphne = "*"
|
|
||||||
defusedxml = "*"
|
defusedxml = "*"
|
||||||
django = "*"
|
django = "*"
|
||||||
|
kombu = "==4.5.0"
|
||||||
django-cors-middleware = "*"
|
django-cors-middleware = "*"
|
||||||
django-filters = "*"
|
django-filters = "*"
|
||||||
django-ipware = "*"
|
django-ipware = "*"
|
||||||
@ -23,15 +19,13 @@ django-otp = "*"
|
|||||||
django-recaptcha = "*"
|
django-recaptcha = "*"
|
||||||
django-redis = "*"
|
django-redis = "*"
|
||||||
django-rest-framework = "*"
|
django-rest-framework = "*"
|
||||||
django-revproxy = "*"
|
|
||||||
djangorestframework = "==3.9.4"
|
|
||||||
drf-yasg = "*"
|
drf-yasg = "*"
|
||||||
ldap3 = "*"
|
ldap3 = "*"
|
||||||
lxml = "*"
|
lxml = "*"
|
||||||
markdown = "*"
|
markdown = "*"
|
||||||
oauthlib = "*"
|
oauthlib = "*"
|
||||||
packaging = "*"
|
packaging = "*"
|
||||||
psycopg2 = "*"
|
psycopg2-binary = "*"
|
||||||
pycryptodome = "*"
|
pycryptodome = "*"
|
||||||
pyyaml = "*"
|
pyyaml = "*"
|
||||||
qrcode = "*"
|
qrcode = "*"
|
||||||
@ -40,20 +34,18 @@ sentry-sdk = "*"
|
|||||||
service_identity = "*"
|
service_identity = "*"
|
||||||
signxml = "*"
|
signxml = "*"
|
||||||
urllib3 = {extras = ["secure"],version = "*"}
|
urllib3 = {extras = ["secure"],version = "*"}
|
||||||
websocket_client = "*"
|
|
||||||
structlog = "*"
|
structlog = "*"
|
||||||
uwsgi = "*"
|
pyuwsgi = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.7"
|
python_version = "3.7"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
astroid = "==2.2.5"
|
|
||||||
coverage = "*"
|
coverage = "*"
|
||||||
isort = "*"
|
isort = "*"
|
||||||
pylint = "==2.3.1"
|
pylint = "==2.3.1"
|
||||||
pylint-django = "==2.0.10"
|
pylint-django = "*"
|
||||||
prospector = "==1.1.7"
|
prospector = "*"
|
||||||
django-debug-toolbar = "*"
|
django-debug-toolbar = "*"
|
||||||
bumpversion = "*"
|
bumpversion = "*"
|
||||||
unittest-xml-reporting = "*"
|
unittest-xml-reporting = "*"
|
||||||
|
555
Pipfile.lock
generated
555
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "cd82871d9aca8cfd548a6a62856196b2211524f12fbd416dfe5218aad9471e44"
|
"sha256": "94b3d5140f0c31dac1fc77af75a0df30ae4fb0571bf6b7fcd722487c63dc1872"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -18,62 +18,24 @@
|
|||||||
"default": {
|
"default": {
|
||||||
"amqp": {
|
"amqp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:19a917e260178b8d410122712bac69cb3e6db010d68f6101e7307508aded5e68",
|
"sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8",
|
||||||
"sha256:19d851b879a471fcfdcf01df9936cff924f422baa77653289f7095dedd5fb26a"
|
"sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d"
|
||||||
],
|
],
|
||||||
"version": "==2.5.1"
|
"version": "==2.5.2"
|
||||||
},
|
|
||||||
"asgiref": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:a4ce726e6ef49cca13642ff49588530ebabcc47c669c7a95af37ea5a74b9b823",
|
|
||||||
"sha256:f62b1c88ebf5fe95db202a372982970edcf375c1513d7e70717df0750f5c2b98"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==3.2.2"
|
|
||||||
},
|
},
|
||||||
"asn1crypto": {
|
"asn1crypto": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
|
"sha256:0b199f211ae690df3db4fd6c1c4ff976497fb1da689193e368eedbadc53d9292",
|
||||||
"sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
|
"sha256:bca90060bd995c3f62c4433168eab407e44bdbdb567b3f3a396a676c1a4c4a3f"
|
||||||
],
|
],
|
||||||
"version": "==0.24.0"
|
"version": "==1.0.1"
|
||||||
},
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
|
"sha256:ec20e7a4825331c1b5ebf261d111e16fa9612c1f7a5e1f884f12bd53a664dfd2",
|
||||||
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
|
"sha256:f913492e1663d3c36f502e5e9ba6cd13cf19d7fab50aa13239e420fef95e1396"
|
||||||
],
|
],
|
||||||
"version": "==19.1.0"
|
"version": "==19.2.0"
|
||||||
},
|
|
||||||
"autobahn": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:27688cbddd5545fc2ee2614ec8fa65119f1a2122606ce2ef7756392c33e3ec0f",
|
|
||||||
"sha256:a24826ad0bcc35d32cb4576a092fa744e8b6738bd6320d2de857ad8a71df0bec"
|
|
||||||
],
|
|
||||||
"version": "==19.9.3"
|
|
||||||
},
|
|
||||||
"automat": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:cbd78b83fa2d81fe2a4d23d258e1661dd7493c9a50ee2f1a5b2cac61c1793b0e",
|
|
||||||
"sha256:fdccab66b68498af9ecfa1fa43693abe546014dd25cf28543cbe9d1334916a58"
|
|
||||||
],
|
|
||||||
"version": "==0.7.0"
|
|
||||||
},
|
|
||||||
"backports.functools-lru-cache": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a",
|
|
||||||
"sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd"
|
|
||||||
],
|
|
||||||
"version": "==1.5"
|
|
||||||
},
|
|
||||||
"beautifulsoup4": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:05668158c7b85b791c5abde53e50265e16f98ad601c402ba44d70f96c4159612",
|
|
||||||
"sha256:25288c9e176f354bf277c0a10aa96c782a6a18a17122dba2e8cec4a97e03343b",
|
|
||||||
"sha256:f040590be10520f2ea4c2ae8c3dae441c7cfff5308ec9d58a0ec0c1b8f81d469"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==4.8.0"
|
|
||||||
},
|
},
|
||||||
"billiard": {
|
"billiard": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -92,10 +54,10 @@
|
|||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
|
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50",
|
||||||
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
|
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"
|
||||||
],
|
],
|
||||||
"version": "==2019.6.16"
|
"version": "==2019.9.11"
|
||||||
},
|
},
|
||||||
"cffi": {
|
"cffi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -130,14 +92,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.12.3"
|
"version": "==1.12.3"
|
||||||
},
|
},
|
||||||
"channels": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:9191a85800673b790d1d74666fb7676f430600b71b662581e97dd69c9aedd29a",
|
|
||||||
"sha256:af7cdba9efb3f55b939917d1b15defb5d40259936013e60660e5e9aff98db4c5"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==2.2.0"
|
|
||||||
},
|
|
||||||
"chardet": {
|
"chardet": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||||
@ -147,33 +101,18 @@
|
|||||||
},
|
},
|
||||||
"cheroot": {
|
"cheroot": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:427e7e3ce51ad5a6e5cf953252b5782d5dfbeb544c09910634971bc06df6621b",
|
"sha256:3ff64073efa35b39d5e107410f5c79664dc8c6c5990651e970740c80ab8878a8",
|
||||||
"sha256:74d733c55178812253d855990f7ad7b31ab4ee8dab80e4803bd5e52299c50395"
|
"sha256:d523a1525258730026aa35b86c8c47c8d0e3892fb89f0f39157d4b32a50edf05"
|
||||||
],
|
],
|
||||||
"version": "==6.5.8"
|
"version": "==8.1.0"
|
||||||
},
|
},
|
||||||
"cherrypy": {
|
"cherrypy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:16fc226a280cd772ede7c309d3964002196784ac6615d8bface52be12ff51230",
|
"sha256:033368d25fcc6bca143e7efe9adbfd3a6d91cc0d90c37a649261935f116aafab",
|
||||||
"sha256:488ea5e639885c75330686c1d7d3dfbd002f784c027a3fe5b374b41926b8cba3"
|
"sha256:683e687e7c7b1ba31ef86a113b1eafd0407269fed175bf488d3c839d37d1cc60"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==18.2.0"
|
"version": "==18.3.0"
|
||||||
},
|
|
||||||
"colorlog": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3cf31b25cbc8f86ec01fef582ef3b840950dea414084ed19ab922c8b493f9b42",
|
|
||||||
"sha256:450f52ea2a2b6ebb308f034ea9a9b15cea51e65650593dca1da3eb792e4e4981"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==4.0.2"
|
|
||||||
},
|
|
||||||
"constantly": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35",
|
|
||||||
"sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d"
|
|
||||||
],
|
|
||||||
"version": "==15.1.0"
|
|
||||||
},
|
},
|
||||||
"coreapi": {
|
"coreapi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -210,14 +149,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==2.7"
|
"version": "==2.7"
|
||||||
},
|
},
|
||||||
"daphne": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:2329b7a74b5559f7ea012879c10ba945c3a53df7d8d2b5932a904e3b4c9abcc2",
|
|
||||||
"sha256:3cae286a995ae5b127d7de84916f0480cb5be19f81125b6a150b8326250dadd5"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==2.3.0"
|
|
||||||
},
|
|
||||||
"defusedxml": {
|
"defusedxml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
|
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
|
||||||
@ -228,11 +159,11 @@
|
|||||||
},
|
},
|
||||||
"django": {
|
"django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:148a4a2d1a85b23883b0a4e99ab7718f518a83675e4485e44dc0c1d36988c5fa",
|
"sha256:4025317ca01f75fc79250ff7262a06d8ba97cd4f82e93394b2a0a6a4a925caeb",
|
||||||
"sha256:deb70aa038e59b58593673b15e9a711d1e5ccd941b5973b30750d5d026abfd56"
|
"sha256:a8ca1033acac9f33995eb2209a6bf18a4681c3e5269a878e9a7e0b7384ed1ca3"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.2.5"
|
"version": "==2.2.6"
|
||||||
},
|
},
|
||||||
"django-cors-middleware": {
|
"django-cors-middleware": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -280,11 +211,11 @@
|
|||||||
},
|
},
|
||||||
"django-otp": {
|
"django-otp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:246b11ee38ec1cea2e2312311a830740d1a8d0384ba15e7b70e03f851d790157",
|
"sha256:79c8253be97246df86540d551dc705e8fe6ca76af8e8c77f78314cd1b513c2cf",
|
||||||
"sha256:cefbf5e7295498c767752d77828ce3f56cdb0373915e56fe4f87d99604742394"
|
"sha256:c5bf3916dca5d53cb377aa6dea40aa785c164013fbf750384137362dfa278cf5"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.7.0"
|
"version": "==0.7.2"
|
||||||
},
|
},
|
||||||
"django-recaptcha": {
|
"django-recaptcha": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -309,29 +240,20 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.1.0"
|
"version": "==0.1.0"
|
||||||
},
|
},
|
||||||
"django-revproxy": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0b539736e438aad3cd8b34563125783678f65bcb847970c95d8e9820e6dc88b3",
|
|
||||||
"sha256:b2c6244aaf53fbbecb79084bf507761754b36895c0f6d01349066e9a355e8455"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==0.9.15"
|
|
||||||
},
|
|
||||||
"djangorestframework": {
|
"djangorestframework": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:376f4b50340a46c15ae15ddd0c853085f4e66058f97e4dbe7d43ed62f5e60651",
|
"sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8",
|
||||||
"sha256:c12869cfd83c33d579b17b3cb28a2ae7322a53c3ce85580c2a2ebe4e3f56c4fb"
|
"sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"version": "==3.10.3"
|
||||||
"version": "==3.9.4"
|
|
||||||
},
|
},
|
||||||
"drf-yasg": {
|
"drf-yasg": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:68fded2ffdf46e03f33e766184b7d8f1e1a5236f94acfd0c4ba932a57b812566",
|
"sha256:4cfec631880ae527a91ec7cd3241aea2f82189f59e2f089119aa687761afb227",
|
||||||
"sha256:fcef74709ead2b365410be3d12afbfd0a6e49d1efe615a15a929da7e950bb83c"
|
"sha256:504cce09035cf1bace63b84d9d778b772f86bb37d8a71ed6f723346362e633b2"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.16.1"
|
"version": "==1.17.0"
|
||||||
},
|
},
|
||||||
"eight": {
|
"eight": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -346,13 +268,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==0.16.0"
|
"version": "==0.16.0"
|
||||||
},
|
},
|
||||||
"hyperlink": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:4288e34705da077fada1111a24a0aa08bb1e76699c9ce49876af722441845654",
|
|
||||||
"sha256:ab4a308feb039b04f855a020a6eda3b18ca5a68e6d8f8c899cbe9e653721d04f"
|
|
||||||
],
|
|
||||||
"version": "==19.0.0"
|
|
||||||
},
|
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||||
@ -360,20 +275,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==2.8"
|
"version": "==2.8"
|
||||||
},
|
},
|
||||||
"importlib-metadata": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0c505102757e7fa28b9f0958d8bc81301159dea16e2649858c92edc158b78a83",
|
|
||||||
"sha256:9a9f75ce32e78170905888acbf2376a81d3f21ecb3bb4867050413411d3ca7a9"
|
|
||||||
],
|
|
||||||
"version": "==0.21"
|
|
||||||
},
|
|
||||||
"incremental": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:717e12246dddf231a349175f48d74d93e2897244939173b01974ab6661406b9f",
|
|
||||||
"sha256:7b751696aaf36eebfab537e458929e194460051ccad279c72b755a167eebd4b3"
|
|
||||||
],
|
|
||||||
"version": "==17.5.0"
|
|
||||||
},
|
|
||||||
"inflection": {
|
"inflection": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca"
|
"sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca"
|
||||||
@ -395,17 +296,18 @@
|
|||||||
},
|
},
|
||||||
"jinja2": {
|
"jinja2": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
|
"sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f",
|
||||||
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
|
"sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"
|
||||||
],
|
],
|
||||||
"version": "==2.10.1"
|
"version": "==2.10.3"
|
||||||
},
|
},
|
||||||
"kombu": {
|
"kombu": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:55274dc75eb3c3994538b0973a0fadddb236b698a4bc135b8aa4981e0a710b8f",
|
"sha256:389ba09e03b15b55b1a7371a441c894fd8121d174f5583bbbca032b9ea8c9edd",
|
||||||
"sha256:e5f0312dfb9011bebbf528ccaf118a6c2b5c3b8244451f08381fb23e7715809b"
|
"sha256:7b92303af381ef02fad6899fd5f5a9a96031d781356cd8e505fa54ae5ddee181"
|
||||||
],
|
],
|
||||||
"version": "==4.6.4"
|
"index": "pypi",
|
||||||
|
"version": "==4.5.0"
|
||||||
},
|
},
|
||||||
"ldap3": {
|
"ldap3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -501,11 +403,11 @@
|
|||||||
},
|
},
|
||||||
"packaging": {
|
"packaging": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9",
|
"sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47",
|
||||||
"sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe"
|
"sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==19.1"
|
"version": "==19.2"
|
||||||
},
|
},
|
||||||
"portend": {
|
"portend": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -514,19 +416,36 @@
|
|||||||
],
|
],
|
||||||
"version": "==2.5"
|
"version": "==2.5"
|
||||||
},
|
},
|
||||||
"psycopg2": {
|
"psycopg2-binary": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:128d0fa910ada0157bba1cb74a9c5f92bb8a1dca77cf91a31eb274d1f889e001",
|
"sha256:080c72714784989474f97be9ab0ddf7b2ad2984527e77f2909fcd04d4df53809",
|
||||||
"sha256:227fd46cf9b7255f07687e5bde454d7d67ae39ca77e170097cdef8ebfc30c323",
|
"sha256:110457be80b63ff4915febb06faa7be002b93a76e5ba19bf3f27636a2ef58598",
|
||||||
"sha256:2315e7f104681d498ccf6fd70b0dba5bce65d60ac92171492bfe228e21dcc242",
|
"sha256:171352a03b22fc099f15103959b52ee77d9a27e028895d7e5fde127aa8e3bac5",
|
||||||
"sha256:4b5417dcd2999db0f5a891d54717cfaee33acc64f4772c4bc574d4ff95ed9d80",
|
"sha256:19d013e7b0817087517a4b3cab39c084d78898369e5c46258aab7be4f233d6a1",
|
||||||
"sha256:640113ddc943522aaf71294e3f2d24013b0edd659b7820621492c9ebd3a2fb0b",
|
"sha256:249b6b21ae4eb0f7b8423b330aa80fab5f821b9ffc3f7561a5e2fd6bb142cf5d",
|
||||||
"sha256:897a6e838319b4bf648a574afb6cabcb17d0488f8c7195100d48d872419f4457",
|
"sha256:2ac0731d2d84b05c7bb39e85b7e123c3a0acd4cda631d8d542802c88deb9e87e",
|
||||||
"sha256:8dceca81409898c870e011c71179454962dec152a1a6b86a347f4be74b16d864",
|
"sha256:2b6d561193f0dc3f50acfb22dd52ea8c8dfbc64bcafe3938b5f209cc17cb6f00",
|
||||||
"sha256:b1b8e41da09a0c3ef0b3d4bb72da0dde2abebe583c1e8462973233fd5ad0235f",
|
"sha256:2bd23e242e954214944481124755cbefe7c2cf563b1a54cd8d196d502f2578bf",
|
||||||
"sha256:cb407fccc12fc29dc331f2b934913405fa49b9b75af4f3a72d0f50f57ad2ca23",
|
"sha256:3e1239242ca60b3725e65ab2f13765fc199b03af9eaf1b5572f0e97bdcee5b43",
|
||||||
"sha256:d3a27550a8185e53b244ad7e79e307594b92fede8617d80200a8cce1fba2c60f",
|
"sha256:3eb70bb697abbe86b1d2b1316370c02ba320bfd1e9e35cf3b9566a855ea8e4e5",
|
||||||
"sha256:f0e6b697a975d9d3ccd04135316c947dd82d841067c7800ccf622a8717e98df1"
|
"sha256:51a2fc7e94b98bd1bb5d4570936f24fc2b0541b63eccadf8fdea266db8ad2f70",
|
||||||
|
"sha256:52f1bdafdc764b7447e393ed39bb263eccb12bfda25a4ac06d82e3a9056251f6",
|
||||||
|
"sha256:5b3581319a3951f1e866f4f6c5e42023db0fae0284273b82e97dfd32c51985cd",
|
||||||
|
"sha256:63c1b66e3b2a3a336288e4bcec499e0dc310cd1dceaed1c46fa7419764c68877",
|
||||||
|
"sha256:8123a99f24ecee469e5c1339427bcdb2a33920a18bb5c0d58b7c13f3b0298ba3",
|
||||||
|
"sha256:85e699fcabe7f817c0f0a412d4e7c6627e00c412b418da7666ff353f38e30f67",
|
||||||
|
"sha256:8dbff4557bbef963697583366400822387cccf794ccb001f1f2307ed21854c68",
|
||||||
|
"sha256:908d21d08d6b81f1b7e056bbf40b2f77f8c499ab29e64ec5113052819ef1c89b",
|
||||||
|
"sha256:af39d0237b17d0a5a5f638e9dffb34013ce2b1d41441fd30283e42b22d16858a",
|
||||||
|
"sha256:af51bb9f055a3f4af0187149a8f60c9d516cf7d5565b3dac53358796a8fb2a5b",
|
||||||
|
"sha256:b2ecac57eb49e461e86c092761e6b8e1fd9654dbaaddf71a076dcc869f7014e2",
|
||||||
|
"sha256:cd37cc170678a4609becb26b53a2bc1edea65177be70c48dd7b39a1149cabd6e",
|
||||||
|
"sha256:d17e3054b17e1a6cb8c1140f76310f6ede811e75b7a9d461922d2c72973f583e",
|
||||||
|
"sha256:d305313c5a9695f40c46294d4315ed3a07c7d2b55e48a9010dad7db7a66c8b7f",
|
||||||
|
"sha256:dd0ef0eb1f7dd18a3f4187226e226a7284bda6af5671937a221766e6ef1ee88f",
|
||||||
|
"sha256:e1adff53b56db9905db48a972fb89370ad5736e0450b96f91bcf99cadd96cfd7",
|
||||||
|
"sha256:f0d43828003c82dbc9269de87aa449e9896077a71954fbbb10a614c017e65737",
|
||||||
|
"sha256:f78e8b487de4d92640105c1389e5b90be3496b1d75c90a666edd8737cc2dbab7"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.8.3"
|
"version": "==2.8.3"
|
||||||
@ -540,10 +459,10 @@
|
|||||||
},
|
},
|
||||||
"pyasn1-modules": {
|
"pyasn1-modules": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:43c17a83c155229839cc5c6b868e8d0c6041dba149789b6d6e28801c64821722",
|
"sha256:0c35a52e00b672f832e5846826f1fb7507907f7d52fba6faa9e3c4cbe874fe4b",
|
||||||
"sha256:e30199a9d221f1b26c885ff3d87fd08694dbbe18ed0e8e405a2a7126d30ce4c0"
|
"sha256:b6ada4f840fe51abf5a6bd545b45bf537bea62221fa0dde2e8a553ed9f06a4e3"
|
||||||
],
|
],
|
||||||
"version": "==0.2.6"
|
"version": "==0.2.7"
|
||||||
},
|
},
|
||||||
"pycparser": {
|
"pycparser": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -618,13 +537,6 @@
|
|||||||
],
|
],
|
||||||
"version": "==3.9.0"
|
"version": "==3.9.0"
|
||||||
},
|
},
|
||||||
"pyhamcrest": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:6b672c02fdf7470df9674ab82263841ce8333fb143f32f021f6cb26f0e512420",
|
|
||||||
"sha256:8ffaa0a53da57e89de14ced7185ac746227a8894dbd5a3c718bf05ddbd1d56cd"
|
|
||||||
],
|
|
||||||
"version": "==1.9.0"
|
|
||||||
},
|
|
||||||
"pyjwkest": {
|
"pyjwkest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5560fd5ba08655f29ff6ad1df1e15dc05abc9d976fcbcec8d2b5167f49b70222"
|
"sha256:5560fd5ba08655f29ff6ad1df1e15dc05abc9d976fcbcec8d2b5167f49b70222"
|
||||||
@ -647,10 +559,40 @@
|
|||||||
},
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32",
|
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||||
"sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"
|
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
||||||
],
|
],
|
||||||
"version": "==2019.2"
|
"version": "==2019.3"
|
||||||
|
},
|
||||||
|
"pyuwsgi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:15a4626740753b0d0dfeeac7d367f9b2e89ab6af16c195927e60f75359fc1bbc",
|
||||||
|
"sha256:24c40c3b889eb9f283d43feffbc0f7c7fc024e914451425156ddb68af3df1e71",
|
||||||
|
"sha256:393737bd43a7e38f0a4a1601a37a69c4bf893635b37665ff958170fdb604fdb7",
|
||||||
|
"sha256:5a08308f87e639573c1efaa5966a6d04410cd45a73c4586a932fe3ee4b56369d",
|
||||||
|
"sha256:5f4b36c0dbb9931c4da8008aa423158be596e3b4a23cec95a958631603a94e45",
|
||||||
|
"sha256:7c31794f71bbd0ccf542cab6bddf38aa69e84e31ae0f9657a2e18ebdc150c01a",
|
||||||
|
"sha256:802ec6dad4b6707b934370926ec1866603abe31ba03c472f56149001b3533ba1",
|
||||||
|
"sha256:814d73d4569add69a6c19bb4a27cd5adb72b196e5e080caed17dbda740402072",
|
||||||
|
"sha256:829299cd117cf8abe837796bf587e61ce6bfe18423a3a1c510c21e9825789c2c",
|
||||||
|
"sha256:85f2210ceae5f48b7d8fad2240d831f4b890cac85cd98ca82683ac6aa481dfc8",
|
||||||
|
"sha256:861c94442b28cd64af033e88e0f63c66dbd5609f67952dc18694098b47a43f3a",
|
||||||
|
"sha256:957bc6316ffc8463795d56d9953d58e7f32aa5aad1c5ac80bc45c69f3299961e",
|
||||||
|
"sha256:9760c3f56fb5f15852d163429096600906478e9ed2c189a52f2bb21d8a2a986c",
|
||||||
|
"sha256:a4b24703ea818196d0be1dc64b3b57b79c67e8dee0cfa207a4216220912035a7",
|
||||||
|
"sha256:ad7f4968c1ddbf139a306d9b075360d959cc554d994ba5e1f512af9a40e62357",
|
||||||
|
"sha256:b1127d34b90f74faf1707718c57a4193ac028b9f4aec0238638983132297d456",
|
||||||
|
"sha256:bcb04d6ec644b3e08d03c64851e06edd7110489261e50627a4bcadf66ff6920e",
|
||||||
|
"sha256:bebfebb9ee83d7cf37668bf54275b677b7ae283e84a944f9f3ac6a4b66f95d4b",
|
||||||
|
"sha256:c29892dafc65a8b6eb95823fa4bac7754ca3fd1c28ab8d2a973289531b340a27",
|
||||||
|
"sha256:cb296b50b51ba022b0090b28d032ff1dd395a6db03672b65a39e83532edad527",
|
||||||
|
"sha256:ce777ebdf49ce736fc04abf555b5c41ab3f130127543a689dcf8d4871cd18fe4",
|
||||||
|
"sha256:d8b4bf930b6a19bc9ee982b9163d948c87501ad91b71516924e8ed25fe85d2ee",
|
||||||
|
"sha256:e2a420f2c4d35f3ec0b7e752a80d7bd385e2c5a64f67c05f2d2d74230e3114b6",
|
||||||
|
"sha256:fed899ce96f4f2b4d1b9f338dd145a4040ee1d8a5152213af0dd8d4a4d36e9fe"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.0.18.post0"
|
||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -710,35 +652,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 +705,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",
|
||||||
@ -777,6 +712,14 @@
|
|||||||
],
|
],
|
||||||
"version": "==0.3.0"
|
"version": "==0.3.0"
|
||||||
},
|
},
|
||||||
|
"structlog": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:5feae03167620824d3ae3e8915ea8589fc28d1ad6f3edf3cc90ed7c7cb33fab5",
|
||||||
|
"sha256:db441b81c65b0f104a7ce5d86c5432be099956b98b8a2c8be0b3fb3a7a0b1536"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==19.1.0"
|
||||||
|
},
|
||||||
"tempora": {
|
"tempora": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:cb60b1d2b1664104e307f8e5269d7f4acdb077c82e35cd57246ae14a3427d2d6",
|
"sha256:cb60b1d2b1664104e307f8e5269d7f4acdb077c82e35cd57246ae14a3427d2d6",
|
||||||
@ -784,37 +727,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",
|
||||||
@ -828,11 +740,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": [
|
||||||
@ -841,61 +753,12 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.3.0"
|
"version": "==1.3.0"
|
||||||
},
|
},
|
||||||
"websocket-client": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:1151d5fb3a62dc129164292e1227655e4bbc5dd5340a5165dfae61128ec50aa9",
|
|
||||||
"sha256:1fd5520878b68b84b5748bb30e592b10d0a91529d5383f74f4964e72b297fd3a"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==0.56.0"
|
|
||||||
},
|
|
||||||
"zc.lockfile": {
|
"zc.lockfile": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b",
|
"sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b",
|
||||||
"sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f"
|
"sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f"
|
||||||
],
|
],
|
||||||
"version": "==2.0"
|
"version": "==2.0"
|
||||||
},
|
|
||||||
"zipp": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
|
|
||||||
"sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
|
|
||||||
],
|
|
||||||
"version": "==0.6.0"
|
|
||||||
},
|
|
||||||
"zope.interface": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:086707e0f413ff8800d9c4bc26e174f7ee4c9c8b0302fbad68d083071822316c",
|
|
||||||
"sha256:1157b1ec2a1f5bf45668421e3955c60c610e31913cc695b407a574efdbae1f7b",
|
|
||||||
"sha256:11ebddf765bff3bbe8dbce10c86884d87f90ed66ee410a7e6c392086e2c63d02",
|
|
||||||
"sha256:14b242d53f6f35c2d07aa2c0e13ccb710392bcd203e1b82a1828d216f6f6b11f",
|
|
||||||
"sha256:1b3d0dcabc7c90b470e59e38a9acaa361be43b3a6ea644c0063951964717f0e5",
|
|
||||||
"sha256:20a12ab46a7e72b89ce0671e7d7a6c3c1ca2c2766ac98112f78c5bddaa6e4375",
|
|
||||||
"sha256:298f82c0ab1b182bd1f34f347ea97dde0fffb9ecf850ecf7f8904b8442a07487",
|
|
||||||
"sha256:2f6175722da6f23dbfc76c26c241b67b020e1e83ec7fe93c9e5d3dd18667ada2",
|
|
||||||
"sha256:3b877de633a0f6d81b600624ff9137312d8b1d0f517064dfc39999352ab659f0",
|
|
||||||
"sha256:4265681e77f5ac5bac0905812b828c9fe1ce80c6f3e3f8574acfb5643aeabc5b",
|
|
||||||
"sha256:550695c4e7313555549aa1cdb978dc9413d61307531f123558e438871a883d63",
|
|
||||||
"sha256:5f4d42baed3a14c290a078e2696c5f565501abde1b2f3f1a1c0a94fbf6fbcc39",
|
|
||||||
"sha256:62dd71dbed8cc6a18379700701d959307823b3b2451bdc018594c48956ace745",
|
|
||||||
"sha256:7040547e5b882349c0a2cc9b50674b1745db551f330746af434aad4f09fba2cc",
|
|
||||||
"sha256:7e099fde2cce8b29434684f82977db4e24f0efa8b0508179fce1602d103296a2",
|
|
||||||
"sha256:7e5c9a5012b2b33e87980cee7d1c82412b2ebabcb5862d53413ba1a2cfde23aa",
|
|
||||||
"sha256:81295629128f929e73be4ccfdd943a0906e5fe3cdb0d43ff1e5144d16fbb52b1",
|
|
||||||
"sha256:95cc574b0b83b85be9917d37cd2fad0ce5a0d21b024e1a5804d044aabea636fc",
|
|
||||||
"sha256:968d5c5702da15c5bf8e4a6e4b67a4d92164e334e9c0b6acf080106678230b98",
|
|
||||||
"sha256:9e998ba87df77a85c7bed53240a7257afe51a07ee6bc3445a0bf841886da0b97",
|
|
||||||
"sha256:a0c39e2535a7e9c195af956610dba5a1073071d2d85e9d2e5d789463f63e52ab",
|
|
||||||
"sha256:a15e75d284178afe529a536b0e8b28b7e107ef39626a7809b4ee64ff3abc9127",
|
|
||||||
"sha256:a6a6ff82f5f9b9702478035d8f6fb6903885653bff7ec3a1e011edc9b1a7168d",
|
|
||||||
"sha256:b639f72b95389620c1f881d94739c614d385406ab1d6926a9ffe1c8abbea23fe",
|
|
||||||
"sha256:bad44274b151d46619a7567010f7cde23a908c6faa84b97598fd2f474a0c6891",
|
|
||||||
"sha256:bbcef00d09a30948756c5968863316c949d9cedbc7aabac5e8f0ffbdb632e5f1",
|
|
||||||
"sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b",
|
|
||||||
"sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966",
|
|
||||||
"sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317"
|
|
||||||
],
|
|
||||||
"version": "==4.6.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
@ -921,13 +784,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",
|
||||||
@ -936,19 +792,13 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.5.3"
|
"version": "==0.5.3"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"colorama": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
|
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
|
||||||
"sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
|
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
|
||||||
],
|
],
|
||||||
"version": "==2019.6.16"
|
"index": "pypi",
|
||||||
},
|
"version": "==0.4.1"
|
||||||
"chardet": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
|
||||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
|
||||||
],
|
|
||||||
"version": "==3.0.4"
|
|
||||||
},
|
},
|
||||||
"coverage": {
|
"coverage": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -990,11 +840,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": [
|
||||||
@ -1004,14 +854,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"
|
||||||
@ -1020,24 +862,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": [
|
||||||
@ -1091,13 +926,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"
|
||||||
@ -1126,13 +954,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",
|
||||||
@ -1163,16 +984,17 @@
|
|||||||
},
|
},
|
||||||
"pylint-plugin-utils": {
|
"pylint-plugin-utils": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:8d9e31d5ea8b7b0003e1f0f136b44a5235896a32e47c5bc2ef1143e9f6ba0b74"
|
"sha256:2f30510e1c46edf268d3a195b2849bd98a1b9433229bb2ba63b8d776e1fc4d0a",
|
||||||
|
"sha256:57625dcca20140f43731311cd8fd879318bf45a8b0fd17020717a8781714a25a"
|
||||||
],
|
],
|
||||||
"version": "==0.5"
|
"version": "==0.6"
|
||||||
},
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32",
|
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||||
"sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"
|
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
||||||
],
|
],
|
||||||
"version": "==2019.2"
|
"version": "==2019.3"
|
||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1193,27 +1015,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"
|
||||||
@ -1242,9 +1043,10 @@
|
|||||||
},
|
},
|
||||||
"snowballstemmer": {
|
"snowballstemmer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:713e53b79cbcf97bc5245a06080a33d54a77e7cce2f789c835a143bcdb5c033e"
|
"sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
|
||||||
|
"sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
|
||||||
],
|
],
|
||||||
"version": "==1.9.1"
|
"version": "==2.0.0"
|
||||||
},
|
},
|
||||||
"sqlparse": {
|
"sqlparse": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1260,21 +1062,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",
|
||||||
@ -1304,24 +1091,6 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.5.1"
|
"version": "==2.5.1"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
|
||||||
"extras": [
|
|
||||||
"secure"
|
|
||||||
],
|
|
||||||
"hashes": [
|
|
||||||
"sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
|
|
||||||
"sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==1.25.3"
|
|
||||||
},
|
|
||||||
"webencodings": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
|
|
||||||
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
|
|
||||||
],
|
|
||||||
"version": "==0.5.1"
|
|
||||||
},
|
|
||||||
"wrapt": {
|
"wrapt": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"
|
"sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"
|
||||||
|
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# passbook
|
||||||
|
|
||||||
|
## Quick instance
|
||||||
|
|
||||||
|
```
|
||||||
|
export PASSBOOK_DOMAIN=domain.tld
|
||||||
|
docker-compose pull
|
||||||
|
docker-compose up -d
|
||||||
|
docker-compose exec server ./manage.py migrate
|
||||||
|
docker-compose exec server ./manage.py createsuperuser
|
||||||
|
```
|
@ -1,15 +1,20 @@
|
|||||||
FROM python:3.7-alpine
|
FROM python:3.7-slim-buster as locker
|
||||||
|
|
||||||
COPY ./Pipfile /app/
|
COPY ./Pipfile /app/
|
||||||
COPY ./Pipfile.lock /app/
|
COPY ./Pipfile.lock /app/
|
||||||
|
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
RUN apk update && \
|
RUN pip install pipenv && \
|
||||||
apk add --no-cache openssl-dev build-base libxml2-dev libxslt-dev libffi-dev gcc musl-dev libgcc zlib-dev postgresql-dev && \
|
|
||||||
pip install pipenv --no-cache-dir && \
|
|
||||||
pipenv lock -r > requirements.txt && \
|
pipenv lock -r > requirements.txt && \
|
||||||
pipenv --rm && \
|
pipenv lock -rd > requirements-dev.txt
|
||||||
pip install -r requirements.txt --no-cache-dir && \
|
|
||||||
adduser -S passbook && \
|
FROM python:3.7-slim-buster
|
||||||
chown -R passbook /app
|
|
||||||
|
COPY --from=locker /app/requirements.txt /app/
|
||||||
|
COPY --from=locker /app/requirements-dev.txt /app/
|
||||||
|
|
||||||
|
WORKDIR /app/
|
||||||
|
|
||||||
|
RUN pip install -r requirements.txt --no-cache-dir && \
|
||||||
|
adduser --system --no-create-home --uid 1000 --group --home /app passbook
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
FROM docker.beryju.org/passbook/base:latest
|
FROM docker.beryju.org/passbook/base:latest
|
||||||
|
|
||||||
RUN pipenv lock --dev -r > requirements-dev.txt && \
|
RUN pip install -r /app/requirements-dev.txt --no-cache-dir
|
||||||
pip install -r /app/requirements-dev.txt --no-cache-dir
|
|
||||||
|
90
docker-compose.yml
Normal file
90
docker-compose.yml
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
version: '3.2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgresql:
|
||||||
|
image: postgres
|
||||||
|
volumes:
|
||||||
|
- database:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
environment:
|
||||||
|
- POSTGRES_PASSWORD=${PG_PASS:-thisisnotagoodpassword}
|
||||||
|
- POSTGRES_USER=passbook
|
||||||
|
- POSTGRES_DB=passbook
|
||||||
|
labels:
|
||||||
|
- traefik.enable=false
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
labels:
|
||||||
|
- traefik.enable=false
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest}
|
||||||
|
command:
|
||||||
|
- uwsgi
|
||||||
|
- uwsgi.ini
|
||||||
|
environment:
|
||||||
|
- PASSBOOK_DOMAIN=${PASSBOOK_DOMAIN}
|
||||||
|
- PASSBOOK_REDIS__HOST=redis
|
||||||
|
- PASSBOOK_POSTGRESQL__HOST=postgresql
|
||||||
|
- PASSBOOK_POSTGRESQL__PASSWORD=${PG_PASS:-thisisnotagoodpassword}
|
||||||
|
ports:
|
||||||
|
- 8000
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
labels:
|
||||||
|
- traefik.port=8000
|
||||||
|
- traefik.docker.network=internal
|
||||||
|
- traefik.frontend.rule=PathPrefix:/
|
||||||
|
worker:
|
||||||
|
image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest}
|
||||||
|
command:
|
||||||
|
- celery
|
||||||
|
- worker
|
||||||
|
- --autoscale=10,3
|
||||||
|
- -E
|
||||||
|
- -B
|
||||||
|
- -A=passbook.root.celery
|
||||||
|
- -s=/tmp/celerybeat-schedule
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
labels:
|
||||||
|
- traefik.enable=false
|
||||||
|
environment:
|
||||||
|
- PASSBOOK_DOMAIN=${PASSBOOK_DOMAIN}
|
||||||
|
- PASSBOOK_REDIS__HOST=redis
|
||||||
|
- PASSBOOK_POSTGRESQL__HOST=postgresql
|
||||||
|
- PASSBOOK_POSTGRESQL__PASSWORD=${PG_PASS:-thisisnotagoodpassword}
|
||||||
|
static:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: static.Dockerfile
|
||||||
|
image: docker.beryju.org/passbook/static:latest
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
labels:
|
||||||
|
- traefik.frontend.rule=PathPrefix:/static, /robots.txt
|
||||||
|
- traefik.port=80
|
||||||
|
- traefik.docker.network=internal
|
||||||
|
traefik:
|
||||||
|
image: traefik:1.7
|
||||||
|
command: --api --docker
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
ports:
|
||||||
|
- "0.0.0.0:80:80"
|
||||||
|
- "0.0.0.0:443:443"
|
||||||
|
- "0.0.0.0:8080:8080"
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
database:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
internal: {}
|
@ -39,9 +39,9 @@ http {
|
|||||||
gzip on;
|
gzip on;
|
||||||
gzip_types application/javascript image/* text/css;
|
gzip_types application/javascript image/* text/css;
|
||||||
gunzip on;
|
gunzip on;
|
||||||
add_header X-passbook-Version 0.4.0-beta;
|
add_header X-passbook-Version 0.6.6-beta;
|
||||||
add_header Vary X-passbook-Version;
|
add_header Vary X-passbook-Version;
|
||||||
root /static/;
|
root /data/;
|
||||||
|
|
||||||
location /_/healthz {
|
location /_/healthz {
|
||||||
return 204;
|
return 204;
|
10
docker/uwsgi.ini
Normal file
10
docker/uwsgi.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[uwsgi]
|
||||||
|
http = 0.0.0.0:8000
|
||||||
|
chdir = /app
|
||||||
|
wsgi-file = passbook/root/wsgi.py
|
||||||
|
processes = 2
|
||||||
|
master = true
|
||||||
|
threads = 2
|
||||||
|
enable-threads = true
|
||||||
|
uid = passbook
|
||||||
|
gid = passbook
|
@ -1,6 +1,6 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
appVersion: "0.4.0-beta"
|
appVersion: "0.6.6-beta"
|
||||||
description: A Helm chart for passbook.
|
description: A Helm chart for passbook.
|
||||||
name: passbook
|
name: passbook
|
||||||
version: "0.4.0-beta"
|
version: "0.6.6-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: 5.3.9
|
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,77 +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
|
|
||||||
envFrom:
|
|
||||||
- configMapRef:
|
|
||||||
name: {{ include "p2.fullname" . }}-config
|
|
||||||
prefix: PASSBOOK_
|
|
||||||
env:
|
|
||||||
- name: P2_REDIS__PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: "{{ .Release.Name }}-redis"
|
|
||||||
key: redis-password
|
|
||||||
- name: P2_POSTGRESQL__PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: "{{ .Release.Name }}-postgresql"
|
|
||||||
key: postgresql-password
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: 8000
|
|
||||||
protocol: TCP
|
|
||||||
volumeMounts:
|
|
||||||
- mountPath: /etc/passbook
|
|
||||||
name: config-volume
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
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
|
|
@ -12,101 +12,5 @@ data:
|
|||||||
host: "{{ .Release.Name }}-redis-master"
|
host: "{{ .Release.Name }}-redis-master"
|
||||||
cache_db: 0
|
cache_db: 0
|
||||||
message_queue_db: 1
|
message_queue_db: 1
|
||||||
|
|
||||||
# Error reporting, sends stacktrace to sentry.beryju.org
|
|
||||||
error_report_enabled: {{ .Values.config.error_reporting }}
|
error_report_enabled: {{ .Values.config.error_reporting }}
|
||||||
|
domain: ".{{ .Values.ingress.hosts[0] }}"
|
||||||
{{- if .Values.config.secret_key }}
|
|
||||||
secret_key: {{ .Values.config.secret_key }}
|
|
||||||
{{- else }}
|
|
||||||
secret_key: {{ randAlphaNum 50 }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
primary_domain: {{ .Values.primary_domain }}
|
|
||||||
domains:
|
|
||||||
{{- range .Values.ingress.hosts }}
|
|
||||||
- {{ . | quote }}
|
|
||||||
{{- end }}
|
|
||||||
- kubernetes-healthcheck-host
|
|
||||||
|
|
||||||
passbook:
|
|
||||||
sign_up:
|
|
||||||
# Enables signup, created users are stored in internal Database and created in LDAP if ldap.create_users is true
|
|
||||||
enabled: true
|
|
||||||
password_reset:
|
|
||||||
# Enable password reset, passwords are reset in internal Database and in LDAP if ldap.reset_password is true
|
|
||||||
enabled: true
|
|
||||||
# Verification the user has to provide in order to be able to reset passwords. Can be any combination of `email`, `2fa`, `security_questions`
|
|
||||||
verification:
|
|
||||||
- email
|
|
||||||
# Text used in title, on login page and multiple other places
|
|
||||||
branding: passbook
|
|
||||||
login:
|
|
||||||
# Override URL used for logo
|
|
||||||
logo_url: null
|
|
||||||
# Override URL used for Background on Login page
|
|
||||||
bg_url: null
|
|
||||||
# Optionally add a subtext, placed below logo on the login page
|
|
||||||
subtext: null
|
|
||||||
footer:
|
|
||||||
links:
|
|
||||||
# Optionally add links to the footer on the login page
|
|
||||||
# - name: test
|
|
||||||
# href: https://test
|
|
||||||
# Specify which fields can be used to authenticate. Can be any combination of `username` and `email`
|
|
||||||
uid_fields:
|
|
||||||
- username
|
|
||||||
- email
|
|
||||||
session:
|
|
||||||
remember_age: 2592000 # 60 * 60 * 24 * 30, one month
|
|
||||||
# Provider-specific settings
|
|
||||||
ldap:
|
|
||||||
# # Completely enable or disable LDAP provider
|
|
||||||
# enabled: false
|
|
||||||
# # AD Domain, used to generate `userPrincipalName`
|
|
||||||
# domain: corp.contoso.com
|
|
||||||
# # Base DN in which passbook should look for users
|
|
||||||
# base_dn: dn=corp,dn=contoso,dn=com
|
|
||||||
# # LDAP field which is used to set the django username
|
|
||||||
# username_field: sAMAccountName
|
|
||||||
# # LDAP server to connect to, can be set to `<domain_name>`
|
|
||||||
# server:
|
|
||||||
# name: corp.contoso.com
|
|
||||||
# use_tls: false
|
|
||||||
# # Bind credentials, used for account creation
|
|
||||||
# bind:
|
|
||||||
# username: Administraotr@corp.contoso.com
|
|
||||||
# password: VerySecurePassword!
|
|
||||||
# Which field from `uid_fields` maps to which LDAP Attribute
|
|
||||||
login_field_map:
|
|
||||||
username: sAMAccountName
|
|
||||||
email: mail # or userPrincipalName
|
|
||||||
user_attribute_map:
|
|
||||||
active_directory:
|
|
||||||
username: "%(sAMAccountName)s"
|
|
||||||
email: "%(mail)s"
|
|
||||||
name: "%(displayName)"
|
|
||||||
# # Create new users in LDAP upon sign-up
|
|
||||||
# create_users: true
|
|
||||||
# # Reset LDAP password when user reset their password
|
|
||||||
# reset_password: true
|
|
||||||
oauth_client:
|
|
||||||
# List of python packages with sources types to load.
|
|
||||||
types:
|
|
||||||
- passbook.oauth_client.source_types.discord
|
|
||||||
- passbook.oauth_client.source_types.facebook
|
|
||||||
- passbook.oauth_client.source_types.github
|
|
||||||
- passbook.oauth_client.source_types.google
|
|
||||||
- passbook.oauth_client.source_types.reddit
|
|
||||||
- passbook.oauth_client.source_types.supervisr
|
|
||||||
- passbook.oauth_client.source_types.twitter
|
|
||||||
- passbook.oauth_client.source_types.azure_ad
|
|
||||||
saml_idp:
|
|
||||||
signing: true
|
|
||||||
autosubmit: false
|
|
||||||
issuer: passbook
|
|
||||||
assertion_valid_for: 86400
|
|
||||||
# List of python packages with provider types to load.
|
|
||||||
types:
|
|
||||||
- passbook.saml_idp.processors.generic
|
|
||||||
- passbook.saml_idp.processors.salesforce
|
|
||||||
|
@ -37,14 +37,9 @@ spec:
|
|||||||
backend:
|
backend:
|
||||||
serviceName: {{ $fullName }}-static
|
serviceName: {{ $fullName }}-static
|
||||||
servicePort: http
|
servicePort: http
|
||||||
{{- end }}
|
- path: /robots.txt
|
||||||
{{- range .Values.ingress.app_gw_hosts }}
|
|
||||||
- host: {{ . | quote }}
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
backend:
|
backend:
|
||||||
serviceName: {{ $fullName }}-appgw
|
serviceName: {{ $fullName }}-static
|
||||||
servicePort: http
|
servicePort: http
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
11
helm/passbook/templates/secret.yaml
Normal file
11
helm/passbook/templates/secret.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
type: Opaque
|
||||||
|
metadata:
|
||||||
|
name: {{ include "passbook.fullname" . }}-secret-key
|
||||||
|
data:
|
||||||
|
{{- if .Values.config.secret_key }}
|
||||||
|
secret_key: {{ .Values.config.secret_key | b64enc | quote }}
|
||||||
|
{{- else }}
|
||||||
|
secret_key: {{ randAlphaNum 50 | b64enc | quote}}
|
||||||
|
{{- end }}
|
@ -8,7 +8,7 @@ metadata:
|
|||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
spec:
|
spec:
|
||||||
replicas: {{ .Values.replicaCount }}
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: {{ include "passbook.name" . }}
|
app.kubernetes.io/name: {{ include "passbook.name" . }}
|
||||||
@ -31,24 +31,29 @@ spec:
|
|||||||
- ./manage.py
|
- ./manage.py
|
||||||
args:
|
args:
|
||||||
- migrate
|
- migrate
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /etc/passbook
|
||||||
|
name: config-volume
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: {{ include "p2.fullname" . }}-config
|
name: {{ include "passbook.fullname" . }}-config
|
||||||
prefix: PASSBOOK_
|
prefix: PASSBOOK_
|
||||||
env:
|
env:
|
||||||
- name: P2_REDIS__PASSWORD
|
- name: PASSBOOK_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "passbook.fullname" . }}-secret-key
|
||||||
|
key: secret_key
|
||||||
|
- name: PASSBOOK_REDIS__PASSWORD
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: "{{ .Release.Name }}-redis"
|
name: "{{ .Release.Name }}-redis"
|
||||||
key: redis-password
|
key: redis-password
|
||||||
- name: P2_POSTGRESQL__PASSWORD
|
- name: PASSBOOK_POSTGRESQL__PASSWORD
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: "{{ .Release.Name }}-postgresql"
|
name: "{{ .Release.Name }}-postgresql"
|
||||||
key: postgresql-password
|
key: postgresql-password
|
||||||
volumeMounts:
|
|
||||||
- mountPath: /etc/passbook
|
|
||||||
name: config-volume
|
|
||||||
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 }}"
|
||||||
@ -56,25 +61,26 @@ spec:
|
|||||||
command:
|
command:
|
||||||
- uwsgi
|
- uwsgi
|
||||||
args:
|
args:
|
||||||
- --http 0.0.0.0:8000
|
- uwsgi.ini
|
||||||
- --wsgi-file passbook/root/wsgi.py
|
volumeMounts:
|
||||||
- --master
|
- mountPath: /etc/passbook
|
||||||
- --processes 24
|
name: config-volume
|
||||||
- --threads 2
|
|
||||||
- --offload-threads 4
|
|
||||||
- --stats 0.0.0.0:8001
|
|
||||||
- --stats-http
|
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: {{ include "p2.fullname" . }}-config
|
name: {{ include "passbook.fullname" . }}-config
|
||||||
prefix: PASSBOOK_
|
prefix: PASSBOOK_
|
||||||
env:
|
env:
|
||||||
- name: P2_REDIS__PASSWORD
|
- name: PASSBOOK_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "passbook.fullname" . }}-secret-key
|
||||||
|
key: secret_key
|
||||||
|
- name: PASSBOOK_REDIS__PASSWORD
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: "{{ .Release.Name }}-redis"
|
name: "{{ .Release.Name }}-redis"
|
||||||
key: redis-password
|
key: redis-password
|
||||||
- name: P2_POSTGRESQL__PASSWORD
|
- name: PASSBOOK_POSTGRESQL__PASSWORD
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: "{{ .Release.Name }}-postgresql"
|
name: "{{ .Release.Name }}-postgresql"
|
||||||
@ -83,9 +89,6 @@ spec:
|
|||||||
- 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: /
|
||||||
@ -102,8 +105,8 @@ spec:
|
|||||||
value: kubernetes-healthcheck-host
|
value: kubernetes-healthcheck-host
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 50m
|
cpu: 100m
|
||||||
memory: 150M
|
memory: 200M
|
||||||
limits:
|
limits:
|
||||||
cpu: 200m
|
cpu: 300m
|
||||||
memory: 300M
|
memory: 350M
|
||||||
|
@ -8,7 +8,7 @@ metadata:
|
|||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
spec:
|
spec:
|
||||||
replicas: {{ .Values.replicaCount }}
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: {{ include "passbook.name" . }}
|
app.kubernetes.io/name: {{ include "passbook.name" . }}
|
||||||
@ -29,31 +29,37 @@ spec:
|
|||||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command:
|
command:
|
||||||
- ./manage.py
|
- celery
|
||||||
args:
|
args:
|
||||||
- worker
|
- worker
|
||||||
|
- --autoscale=10,3
|
||||||
|
- -E
|
||||||
|
- -B
|
||||||
|
- -A=passbook.root.celery
|
||||||
|
- -s=/tmp/celerybeat-schedule
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /etc/passbook
|
||||||
|
name: config-volume
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: {{ include "p2.fullname" . }}-config
|
name: {{ include "passbook.fullname" . }}-config
|
||||||
prefix: PASSBOOK_
|
prefix: PASSBOOK_
|
||||||
env:
|
env:
|
||||||
- name: P2_REDIS__PASSWORD
|
- name: PASSBOOK_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "passbook.fullname" . }}-secret-key
|
||||||
|
key: secret_key
|
||||||
|
- name: PASSBOOK_REDIS__PASSWORD
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: "{{ .Release.Name }}-redis"
|
name: "{{ .Release.Name }}-redis"
|
||||||
key: redis-password
|
key: redis-password
|
||||||
- name: P2_POSTGRESQL__PASSWORD
|
- name: PASSBOOK_POSTGRESQL__PASSWORD
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: "{{ .Release.Name }}-postgresql"
|
name: "{{ .Release.Name }}-postgresql"
|
||||||
key: postgresql-password
|
key: postgresql-password
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: 8000
|
|
||||||
protocol: TCP
|
|
||||||
volumeMounts:
|
|
||||||
- mountPath: /etc/passbook
|
|
||||||
name: config-volume
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 150m
|
cpu: 150m
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
# Default values for passbook.
|
# Default values for passbook.
|
||||||
# This is a YAML-formatted file.
|
# This is a YAML-formatted file.
|
||||||
# Declare variables to be passed into your templates.
|
# Declare variables to be passed into your templates.
|
||||||
|
|
||||||
replicaCount: 1
|
|
||||||
|
|
||||||
image:
|
image:
|
||||||
tag: 0.4.0-beta
|
tag: 0.6.6-beta
|
||||||
|
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
|
|
||||||
@ -19,11 +16,13 @@ config:
|
|||||||
|
|
||||||
postgresql:
|
postgresql:
|
||||||
postgresqlDatabase: passbook
|
postgresqlDatabase: passbook
|
||||||
postgresqlPassword: foo
|
|
||||||
|
|
||||||
rabbitmq:
|
redis:
|
||||||
rabbitmq:
|
cluster:
|
||||||
password: foo
|
enabled: false
|
||||||
|
master:
|
||||||
|
persistence:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
service:
|
service:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
@ -37,28 +36,7 @@ ingress:
|
|||||||
path: /
|
path: /
|
||||||
hosts:
|
hosts:
|
||||||
- passbook.k8s.local
|
- passbook.k8s.local
|
||||||
app_gw_hosts:
|
|
||||||
- '*.passbook.k8s.local'
|
|
||||||
defaultHost: passbook.k8s.local
|
|
||||||
tls: []
|
tls: []
|
||||||
# - secretName: chart-example-tls
|
# - secretName: chart-example-tls
|
||||||
# hosts:
|
# hosts:
|
||||||
# - passbook.k8s.local
|
# - passbook.k8s.local
|
||||||
|
|
||||||
resources: {}
|
|
||||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
|
||||||
# choice for the user. This also increases chances charts run on environments with little
|
|
||||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
|
||||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
|
||||||
# limits:
|
|
||||||
# cpu: 100m
|
|
||||||
# memory: 128Mi
|
|
||||||
# requests:
|
|
||||||
# cpu: 100m
|
|
||||||
# memory: 128Mi
|
|
||||||
|
|
||||||
nodeSelector: {}
|
|
||||||
|
|
||||||
tolerations: []
|
|
||||||
|
|
||||||
affinity: {}
|
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
"""passbook"""
|
"""passbook"""
|
||||||
__version__ = '0.4.0-beta'
|
__version__ = '0.6.6-beta'
|
||||||
|
@ -11,7 +11,7 @@ class UserForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
fields = ['username', 'name', 'email', 'is_staff', 'is_active']
|
fields = ['username', 'name', 'email', 'is_staff', 'is_active', 'attributes']
|
||||||
widgets = {
|
widgets = {
|
||||||
'name': forms.TextInput
|
'name': forms.TextInput
|
||||||
}
|
}
|
||||||
|
@ -179,8 +179,8 @@
|
|||||||
<span class="card-pf-aggregate-status-notification">
|
<span class="card-pf-aggregate-status-notification">
|
||||||
<a href="#">
|
<a href="#">
|
||||||
{% if worker_count < 1%}
|
{% if worker_count < 1%}
|
||||||
<span class="pficon-error-circle-o" data-toggle="tooltip" data-placement="right"
|
<span class="pficon-warning-triangle-o" data-toggle="tooltip" data-placement="right"
|
||||||
title="{% trans 'No workers connected. Policies will not work and you may expect other issues.' %}"></span> {{ worker_count }}
|
title="{% trans 'No workers connected.' %}"></span> {{ worker_count }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="pficon pficon-ok"></span>{{ worker_count }}
|
<span class="pficon pficon-ok"></span>{{ worker_count }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h1><span class="pficon-users"></span> {% trans "Users" %}</h1>
|
<h1><span class="pficon-users"></span> {% trans "Users" %}</h1>
|
||||||
<hr>
|
<hr>
|
||||||
|
<a href="{% url 'passbook_admin:user-create' %}?back={{ request.get_full_path }}" class="btn btn-primary">
|
||||||
|
{% trans 'Create...' %}
|
||||||
|
</a>
|
||||||
|
<hr>
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -8,7 +8,7 @@ from structlog import get_logger
|
|||||||
from passbook.lib.utils.template import render_to_string
|
from passbook.lib.utils.template import render_to_string
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
LOGGER = get_logger(__name__)
|
LOGGER = get_logger()
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def get_links(model_instance):
|
def get_links(model_instance):
|
||||||
|
@ -61,6 +61,7 @@ urlpatterns = [
|
|||||||
# Users
|
# Users
|
||||||
path('users/', users.UserListView.as_view(),
|
path('users/', users.UserListView.as_view(),
|
||||||
name='users'),
|
name='users'),
|
||||||
|
path('users/create/', users.UserCreateView.as_view(), name='user-create'),
|
||||||
path('users/<int:pk>/update/',
|
path('users/<int:pk>/update/',
|
||||||
users.UserUpdateView.as_view(), name='user-update'),
|
users.UserUpdateView.as_view(), name='user-update'),
|
||||||
path('users/<int:pk>/delete/',
|
path('users/<int:pk>/delete/',
|
||||||
|
@ -3,8 +3,8 @@ from django.core.cache import cache
|
|||||||
from django.shortcuts import redirect, reverse
|
from django.shortcuts import redirect, reverse
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
from passbook import __version__
|
||||||
from passbook.admin.mixins import AdminRequiredMixin
|
from passbook.admin.mixins import AdminRequiredMixin
|
||||||
from passbook.core import __version__
|
|
||||||
from passbook.core.models import (Application, Factor, Invitation, Policy,
|
from passbook.core.models import (Application, Factor, Invitation, Policy,
|
||||||
Provider, Source, User)
|
Provider, Source, User)
|
||||||
from passbook.root.celery import CELERY_APP
|
from passbook.root.celery import CELERY_APP
|
||||||
|
@ -12,7 +12,7 @@ from passbook.admin.forms.policies import PolicyTestForm
|
|||||||
from passbook.admin.mixins import AdminRequiredMixin
|
from passbook.admin.mixins import AdminRequiredMixin
|
||||||
from passbook.core.models import Policy
|
from passbook.core.models import Policy
|
||||||
from passbook.lib.utils.reflection import path_to_class
|
from passbook.lib.utils.reflection import path_to_class
|
||||||
from passbook.policy.engine import PolicyEngine
|
from passbook.policies.engine import PolicyEngine
|
||||||
|
|
||||||
|
|
||||||
class PolicyListView(AdminRequiredMixin, ListView):
|
class PolicyListView(AdminRequiredMixin, ListView):
|
||||||
|
@ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404, redirect
|
|||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic import DeleteView, ListView, UpdateView
|
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||||
|
|
||||||
from passbook.admin.forms.users import UserForm
|
from passbook.admin.forms.users import UserForm
|
||||||
from passbook.admin.mixins import AdminRequiredMixin
|
from passbook.admin.mixins import AdminRequiredMixin
|
||||||
@ -19,6 +19,17 @@ class UserListView(AdminRequiredMixin, ListView):
|
|||||||
template_name = 'administration/user/list.html'
|
template_name = 'administration/user/list.html'
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||||
|
"""Create user"""
|
||||||
|
|
||||||
|
model = User
|
||||||
|
form_class = UserForm
|
||||||
|
|
||||||
|
template_name = 'generic/create.html'
|
||||||
|
success_url = reverse_lazy('passbook_admin:users')
|
||||||
|
success_message = _('Successfully created User')
|
||||||
|
|
||||||
|
|
||||||
class UserUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
class UserUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||||
"""Update user"""
|
"""Update user"""
|
||||||
|
|
||||||
|
BIN
passbook/app_gw/.DS_Store
vendored
BIN
passbook/app_gw/.DS_Store
vendored
Binary file not shown.
@ -1,16 +0,0 @@
|
|||||||
"""passbook Application Security Gateway app"""
|
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class PassbookApplicationApplicationGatewayConfig(AppConfig):
|
|
||||||
"""passbook app_gw app"""
|
|
||||||
|
|
||||||
name = 'passbook.app_gw'
|
|
||||||
label = 'passbook_app_gw'
|
|
||||||
verbose_name = 'passbook Application Security Gateway'
|
|
||||||
mountpoint = 'app_gw/'
|
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
import_module('passbook.app_gw.signals')
|
|
@ -1,13 +0,0 @@
|
|||||||
"""
|
|
||||||
ASGI entrypoint. Configures Django and then runs the application
|
|
||||||
defined in the ASGI_APPLICATION setting.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import django
|
|
||||||
from channels.routing import get_default_application
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings")
|
|
||||||
django.setup()
|
|
||||||
application = get_default_application()
|
|
@ -1,29 +0,0 @@
|
|||||||
"""passbook app_gw webserver management command"""
|
|
||||||
|
|
||||||
from daphne.cli import CommandLineInterface
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.utils import autoreload
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.lib.config import CONFIG
|
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""Run Daphne Webserver for app_gw"""
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
"""passbook daphne server"""
|
|
||||||
autoreload.run_with_reloader(self.daphne_server)
|
|
||||||
|
|
||||||
def daphne_server(self):
|
|
||||||
"""Run daphne server within autoreload"""
|
|
||||||
autoreload.raise_last_exception()
|
|
||||||
CommandLineInterface().run([
|
|
||||||
'-p', str(CONFIG.y('app_gw.port', 8000)),
|
|
||||||
'-b', CONFIG.y('app_gw.listen', '0.0.0.0'), # nosec
|
|
||||||
'--access-log', '/dev/null',
|
|
||||||
'--application-close-timeout', '500',
|
|
||||||
'passbook.app_gw.asgi:application'
|
|
||||||
])
|
|
@ -1,33 +0,0 @@
|
|||||||
"""passbook app_gw middleware"""
|
|
||||||
from django.views.generic import RedirectView
|
|
||||||
|
|
||||||
from passbook.app_gw.proxy.handler import RequestHandler
|
|
||||||
from passbook.lib.config import CONFIG
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationGatewayMiddleware:
|
|
||||||
"""Check if request should be proxied or handeled normally"""
|
|
||||||
|
|
||||||
_app_gw_cache = {}
|
|
||||||
|
|
||||||
def __init__(self, get_response):
|
|
||||||
self.get_response = get_response
|
|
||||||
|
|
||||||
def __call__(self, request):
|
|
||||||
# Rudimentary cache
|
|
||||||
host_header = request.META.get('HTTP_HOST')
|
|
||||||
if host_header not in self._app_gw_cache:
|
|
||||||
self._app_gw_cache[host_header] = RequestHandler.find_app_gw_for_request(request)
|
|
||||||
if self._app_gw_cache[host_header]:
|
|
||||||
return self.dispatch(request, self._app_gw_cache[host_header])
|
|
||||||
return self.get_response(request)
|
|
||||||
|
|
||||||
def dispatch(self, request, app_gw):
|
|
||||||
"""Build proxied request and pass to upstream"""
|
|
||||||
handler = RequestHandler(app_gw, request)
|
|
||||||
|
|
||||||
if not handler.check_permission():
|
|
||||||
to_url = 'https://%s/?next=%s' % (CONFIG.y('domains')[0], request.get_full_path())
|
|
||||||
return RedirectView.as_view(url=to_url)(request)
|
|
||||||
|
|
||||||
return handler.get_response()
|
|
BIN
passbook/app_gw/migrations/.DS_Store
vendored
BIN
passbook/app_gw/migrations/.DS_Store
vendored
Binary file not shown.
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-03-21 15:21
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_app_gw', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='rewriterule',
|
|
||||||
name='conditions',
|
|
||||||
field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 2.2 on 2019-04-11 13:14
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_app_gw', '0002_auto_20190321_1521'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='applicationgatewayprovider',
|
|
||||||
name='authentication_header',
|
|
||||||
field=models.TextField(blank=True, default='X-Remote-User'),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,8 +0,0 @@
|
|||||||
"""Exception classes"""
|
|
||||||
|
|
||||||
class ReverseProxyException(Exception):
|
|
||||||
"""Base for revproxy exception"""
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidUpstream(ReverseProxyException):
|
|
||||||
"""Invalid upstream set"""
|
|
@ -1,233 +0,0 @@
|
|||||||
"""passbook app_gw request handler"""
|
|
||||||
import mimetypes
|
|
||||||
from random import SystemRandom
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
import certifi
|
|
||||||
import urllib3
|
|
||||||
from django.core.cache import cache
|
|
||||||
from django.utils.http import urlencode
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.app_gw.models import ApplicationGatewayProvider
|
|
||||||
from passbook.app_gw.proxy.exceptions import InvalidUpstream
|
|
||||||
from passbook.app_gw.proxy.response import get_django_response
|
|
||||||
from passbook.app_gw.proxy.rewrite import Rewriter
|
|
||||||
from passbook.app_gw.proxy.utils import encode_items, normalize_request_headers
|
|
||||||
from passbook.core.models import Application
|
|
||||||
from passbook.policy.engine import PolicyEngine
|
|
||||||
|
|
||||||
SESSION_UPSTREAM_KEY = 'passbook_app_gw_upstream'
|
|
||||||
IGNORED_HOSTNAMES_KEY = 'passbook_app_gw_ignored'
|
|
||||||
LOGGER = get_logger(__name__)
|
|
||||||
QUOTE_SAFE = r'<.;>\(}*+|~=-$/_:^@)[{]&\'!,"`'
|
|
||||||
ERRORS_MESSAGES = {
|
|
||||||
'upstream-no-scheme': ("Upstream URL scheme must be either "
|
|
||||||
"'http' or 'https' (%s).")
|
|
||||||
}
|
|
||||||
HTTP_NO_VERIFY = urllib3.PoolManager()
|
|
||||||
HTTP = urllib3.PoolManager(
|
|
||||||
cert_reqs='CERT_REQUIRED',
|
|
||||||
ca_certs=certifi.where())
|
|
||||||
IGNORED_HOSTS = cache.get(IGNORED_HOSTNAMES_KEY, [])
|
|
||||||
POLICY_CACHE = {}
|
|
||||||
|
|
||||||
class RequestHandler:
|
|
||||||
"""Forward requests"""
|
|
||||||
|
|
||||||
_parsed_url = None
|
|
||||||
_request_headers = None
|
|
||||||
|
|
||||||
def __init__(self, app_gw, request):
|
|
||||||
self.app_gw = app_gw
|
|
||||||
self.request = request
|
|
||||||
if self.app_gw.pk not in POLICY_CACHE:
|
|
||||||
POLICY_CACHE[self.app_gw.pk] = self.app_gw.application.policies.all()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def find_app_gw_for_request(request):
|
|
||||||
"""Check if a request should be proxied or forwarded to passbook"""
|
|
||||||
# Check if hostname is in cached list of ignored hostnames
|
|
||||||
# This saves us having to query the database on each request
|
|
||||||
host_header = request.META.get('HTTP_HOST')
|
|
||||||
if host_header in IGNORED_HOSTS:
|
|
||||||
# LOGGER.debug("%s is ignored", host_header)
|
|
||||||
return False
|
|
||||||
# Look through all ApplicationGatewayProviders and check hostnames
|
|
||||||
matches = ApplicationGatewayProvider.objects.filter(
|
|
||||||
server_name__contains=[host_header],
|
|
||||||
enabled=True)
|
|
||||||
if not matches.exists():
|
|
||||||
# Mo matching Providers found, add host header to ignored list
|
|
||||||
IGNORED_HOSTS.append(host_header)
|
|
||||||
cache.set(IGNORED_HOSTNAMES_KEY, IGNORED_HOSTS)
|
|
||||||
# LOGGER.debug("Ignoring %s", host_header)
|
|
||||||
return False
|
|
||||||
# At this point we're certain there's a matching ApplicationGateway
|
|
||||||
if len(matches) > 1:
|
|
||||||
# This should never happen
|
|
||||||
raise ValueError
|
|
||||||
app_gw = matches.first()
|
|
||||||
try:
|
|
||||||
# Check if ApplicationGateway is associated with application
|
|
||||||
getattr(app_gw, 'application')
|
|
||||||
if app_gw:
|
|
||||||
return app_gw
|
|
||||||
except Application.DoesNotExist:
|
|
||||||
pass
|
|
||||||
# LOGGER.debug("ApplicationGateway not associated with Application")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _get_upstream(self):
|
|
||||||
"""Choose random upstream and save in session"""
|
|
||||||
if SESSION_UPSTREAM_KEY not in self.request.session:
|
|
||||||
self.request.session[SESSION_UPSTREAM_KEY] = {}
|
|
||||||
if self.app_gw.pk not in self.request.session[SESSION_UPSTREAM_KEY]:
|
|
||||||
upstream_index = int(SystemRandom().random() * len(self.app_gw.upstream))
|
|
||||||
self.request.session[SESSION_UPSTREAM_KEY][self.app_gw.pk] = upstream_index
|
|
||||||
return self.app_gw.upstream[self.request.session[SESSION_UPSTREAM_KEY][self.app_gw.pk]]
|
|
||||||
|
|
||||||
def get_upstream(self):
|
|
||||||
"""Get upstream as parsed url"""
|
|
||||||
upstream = self._get_upstream()
|
|
||||||
|
|
||||||
self._parsed_url = urlparse(upstream)
|
|
||||||
|
|
||||||
if self._parsed_url.scheme not in ('http', 'https'):
|
|
||||||
raise InvalidUpstream(ERRORS_MESSAGES['upstream-no-scheme'] %
|
|
||||||
upstream)
|
|
||||||
|
|
||||||
return upstream
|
|
||||||
|
|
||||||
def _format_path_to_redirect(self):
|
|
||||||
# LOGGER.debug("Path before: %s", self.request.get_full_path())
|
|
||||||
rewriter = Rewriter(self.app_gw, self.request)
|
|
||||||
after = rewriter.build()
|
|
||||||
# LOGGER.debug("Path after: %s", after)
|
|
||||||
return after
|
|
||||||
|
|
||||||
def get_proxy_request_headers(self):
|
|
||||||
"""Get normalized headers for the upstream
|
|
||||||
Gets all headers from the original request and normalizes them.
|
|
||||||
Normalization occurs by removing the prefix ``HTTP_`` and
|
|
||||||
replacing and ``_`` by ``-``. Example: ``HTTP_ACCEPT_ENCODING``
|
|
||||||
becames ``Accept-Encoding``.
|
|
||||||
.. versionadded:: 0.9.1
|
|
||||||
:param request: The original HTTPRequest instance
|
|
||||||
:returns: Normalized headers for the upstream
|
|
||||||
"""
|
|
||||||
return normalize_request_headers(self.request)
|
|
||||||
|
|
||||||
def get_request_headers(self):
|
|
||||||
"""Return request headers that will be sent to upstream.
|
|
||||||
The header REMOTE_USER is set to the current user
|
|
||||||
if AuthenticationMiddleware is enabled and
|
|
||||||
the view's add_remote_user property is True.
|
|
||||||
.. versionadded:: 0.9.8
|
|
||||||
"""
|
|
||||||
request_headers = self.get_proxy_request_headers()
|
|
||||||
if not self.app_gw.authentication_header:
|
|
||||||
return request_headers
|
|
||||||
request_headers[self.app_gw.authentication_header] = self.request.user.get_username()
|
|
||||||
# LOGGER.debug("%s set", self.app_gw.authentication_header)
|
|
||||||
|
|
||||||
return request_headers
|
|
||||||
|
|
||||||
def check_permission(self):
|
|
||||||
"""Check if user is authenticated and has permission to access app"""
|
|
||||||
if not hasattr(self.request, 'user'):
|
|
||||||
return False
|
|
||||||
if not self.request.user.is_authenticated:
|
|
||||||
return False
|
|
||||||
policy_engine = PolicyEngine(POLICY_CACHE[self.app_gw.pk])
|
|
||||||
policy_engine.for_user(self.request.user).with_request(self.request).build()
|
|
||||||
passing, _messages = policy_engine.result
|
|
||||||
|
|
||||||
return passing
|
|
||||||
|
|
||||||
def get_encoded_query_params(self):
|
|
||||||
"""Return encoded query params to be used in proxied request"""
|
|
||||||
get_data = encode_items(self.request.GET.lists())
|
|
||||||
return urlencode(get_data)
|
|
||||||
|
|
||||||
def _created_proxy_response(self, path):
|
|
||||||
request_payload = self.request.body
|
|
||||||
|
|
||||||
# LOGGER.debug("Request headers: %s", self._request_headers)
|
|
||||||
|
|
||||||
request_url = self.get_upstream() + path
|
|
||||||
# LOGGER.debug("Request URL: %s", request_url)
|
|
||||||
|
|
||||||
if self.request.GET:
|
|
||||||
request_url += '?' + self.get_encoded_query_params()
|
|
||||||
# LOGGER.debug("Request URL: %s", request_url)
|
|
||||||
|
|
||||||
http = HTTP
|
|
||||||
if not self.app_gw.upstream_ssl_verification:
|
|
||||||
http = HTTP_NO_VERIFY
|
|
||||||
|
|
||||||
try:
|
|
||||||
proxy_response = http.urlopen(self.request.method,
|
|
||||||
request_url,
|
|
||||||
redirect=False,
|
|
||||||
retries=None,
|
|
||||||
headers=self._request_headers,
|
|
||||||
body=request_payload,
|
|
||||||
decode_content=False,
|
|
||||||
preload_content=False)
|
|
||||||
# LOGGER.debug("Proxy response header: %s",
|
|
||||||
# proxy_response.getheaders())
|
|
||||||
except urllib3.exceptions.HTTPError as error:
|
|
||||||
LOGGER.exception(error)
|
|
||||||
raise
|
|
||||||
|
|
||||||
return proxy_response
|
|
||||||
|
|
||||||
def _replace_host_on_redirect_location(self, proxy_response):
|
|
||||||
location = proxy_response.headers.get('Location')
|
|
||||||
if location:
|
|
||||||
if self.request.is_secure():
|
|
||||||
scheme = 'https://'
|
|
||||||
else:
|
|
||||||
scheme = 'http://'
|
|
||||||
request_host = scheme + self.request.META.get('HTTP_HOST')
|
|
||||||
|
|
||||||
upstream_host_http = 'http://' + self._parsed_url.netloc
|
|
||||||
upstream_host_https = 'https://' + self._parsed_url.netloc
|
|
||||||
|
|
||||||
location = location.replace(upstream_host_http, request_host)
|
|
||||||
location = location.replace(upstream_host_https, request_host)
|
|
||||||
proxy_response.headers['Location'] = location
|
|
||||||
# LOGGER.debug("Proxy response LOCATION: %s",
|
|
||||||
# proxy_response.headers['Location'])
|
|
||||||
|
|
||||||
def _set_content_type(self, proxy_response):
|
|
||||||
content_type = proxy_response.headers.get('Content-Type')
|
|
||||||
if not content_type:
|
|
||||||
content_type = (mimetypes.guess_type(self.request.path)
|
|
||||||
[0] or self.app_gw.default_content_type)
|
|
||||||
proxy_response.headers['Content-Type'] = content_type
|
|
||||||
# LOGGER.debug("Proxy response CONTENT-TYPE: %s",
|
|
||||||
# proxy_response.headers['Content-Type'])
|
|
||||||
|
|
||||||
def get_response(self):
|
|
||||||
"""Pass request to upstream and return response"""
|
|
||||||
self._request_headers = self.get_request_headers()
|
|
||||||
|
|
||||||
path = self._format_path_to_redirect()
|
|
||||||
proxy_response = self._created_proxy_response(path)
|
|
||||||
|
|
||||||
self._replace_host_on_redirect_location(proxy_response)
|
|
||||||
self._set_content_type(proxy_response)
|
|
||||||
response = get_django_response(proxy_response, strict_cookies=False)
|
|
||||||
|
|
||||||
# If response has a 'Location' header, we rewrite that location as well
|
|
||||||
if 'Location' in response:
|
|
||||||
LOGGER.debug("Rewriting Location header")
|
|
||||||
for server_name in self.app_gw.server_name:
|
|
||||||
response['Location'] = response['Location'].replace(
|
|
||||||
self._parsed_url.hostname, server_name)
|
|
||||||
LOGGER.debug(response['Location'])
|
|
||||||
|
|
||||||
# LOGGER.debug("RESPONSE RETURNED: %s", response)
|
|
||||||
return response
|
|
@ -1,62 +0,0 @@
|
|||||||
"""response functions from django-revproxy"""
|
|
||||||
from django.http import HttpResponse, StreamingHttpResponse
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.app_gw.proxy.utils import (cookie_from_string,
|
|
||||||
set_response_headers, should_stream)
|
|
||||||
|
|
||||||
#: Default number of bytes that are going to be read in a file lecture
|
|
||||||
DEFAULT_AMT = 2 ** 16
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def get_django_response(proxy_response, strict_cookies=False):
|
|
||||||
"""This method is used to create an appropriate response based on the
|
|
||||||
Content-Length of the proxy_response. If the content is bigger than
|
|
||||||
MIN_STREAMING_LENGTH, which is found on utils.py,
|
|
||||||
than django.http.StreamingHttpResponse will be created,
|
|
||||||
else a django.http.HTTPResponse will be created instead
|
|
||||||
|
|
||||||
:param proxy_response: An Instance of urllib3.response.HTTPResponse that
|
|
||||||
will create an appropriate response
|
|
||||||
:param strict_cookies: Whether to only accept RFC-compliant cookies
|
|
||||||
:returns: Returns an appropriate response based on the proxy_response
|
|
||||||
content-length
|
|
||||||
"""
|
|
||||||
status = proxy_response.status
|
|
||||||
headers = proxy_response.headers
|
|
||||||
|
|
||||||
logger.debug('Proxy response headers: %s', headers)
|
|
||||||
|
|
||||||
content_type = headers.get('Content-Type')
|
|
||||||
|
|
||||||
logger.debug('Content-Type: %s', content_type)
|
|
||||||
|
|
||||||
if should_stream(proxy_response):
|
|
||||||
logger.info('Content-Length is bigger than %s', DEFAULT_AMT)
|
|
||||||
response = StreamingHttpResponse(proxy_response.stream(DEFAULT_AMT),
|
|
||||||
status=status,
|
|
||||||
content_type=content_type)
|
|
||||||
else:
|
|
||||||
content = proxy_response.data or b''
|
|
||||||
response = HttpResponse(content, status=status,
|
|
||||||
content_type=content_type)
|
|
||||||
|
|
||||||
logger.info('Normalizing response headers')
|
|
||||||
set_response_headers(response, headers)
|
|
||||||
|
|
||||||
logger.debug('Response headers: %s', getattr(response, '_headers'))
|
|
||||||
|
|
||||||
cookies = proxy_response.headers.getlist('set-cookie')
|
|
||||||
logger.info('Checking for invalid cookies')
|
|
||||||
for cookie_string in cookies:
|
|
||||||
cookie_dict = cookie_from_string(cookie_string,
|
|
||||||
strict_cookies=strict_cookies)
|
|
||||||
# if cookie is invalid cookie_dict will be None
|
|
||||||
if cookie_dict:
|
|
||||||
response.set_cookie(**cookie_dict)
|
|
||||||
|
|
||||||
logger.debug('Response cookies: %s', response.cookies)
|
|
||||||
|
|
||||||
return response
|
|
@ -1,42 +0,0 @@
|
|||||||
"""passbook app_gw rewriter"""
|
|
||||||
|
|
||||||
from passbook.app_gw.models import RewriteRule
|
|
||||||
|
|
||||||
RULE_CACHE = {}
|
|
||||||
|
|
||||||
class Context:
|
|
||||||
"""Empty class which we dynamically add attributes to"""
|
|
||||||
|
|
||||||
class Rewriter:
|
|
||||||
"""Apply rewrites"""
|
|
||||||
|
|
||||||
__application = None
|
|
||||||
__request = None
|
|
||||||
|
|
||||||
def __init__(self, application, request):
|
|
||||||
self.__application = application
|
|
||||||
self.__request = request
|
|
||||||
if self.__application.pk not in RULE_CACHE:
|
|
||||||
RULE_CACHE[self.__application.pk] = RewriteRule.objects.filter(
|
|
||||||
provider__in=[self.__application])
|
|
||||||
|
|
||||||
def __build_context(self, matches):
|
|
||||||
"""Build object with .0, .1, etc as groups and give access to request"""
|
|
||||||
context = Context()
|
|
||||||
for index, group_match in enumerate(matches.groups()):
|
|
||||||
setattr(context, "g%d" % (index + 1), group_match)
|
|
||||||
setattr(context, 'request', self.__request)
|
|
||||||
return context
|
|
||||||
|
|
||||||
def build(self):
|
|
||||||
"""Run all rules over path and return final path"""
|
|
||||||
path = self.__request.get_full_path()
|
|
||||||
for rule in RULE_CACHE[self.__application.pk]:
|
|
||||||
matches = rule.compiled_matcher.search(path)
|
|
||||||
if not matches:
|
|
||||||
continue
|
|
||||||
replace_context = self.__build_context(matches)
|
|
||||||
path = rule.replacement.format(context=replace_context)
|
|
||||||
if rule.halt:
|
|
||||||
return path
|
|
||||||
return path
|
|
@ -1,226 +0,0 @@
|
|||||||
"""Utils from django-revproxy, slightly adjusted"""
|
|
||||||
import re
|
|
||||||
from wsgiref.util import is_hop_by_hop
|
|
||||||
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
try:
|
|
||||||
from http.cookies import SimpleCookie
|
|
||||||
COOKIE_PREFIX = ''
|
|
||||||
except ImportError:
|
|
||||||
from Cookie import SimpleCookie
|
|
||||||
COOKIE_PREFIX = 'Set-Cookie: '
|
|
||||||
|
|
||||||
|
|
||||||
#: List containing string constant that are used to represent headers that can
|
|
||||||
#: be ignored in the required_header function
|
|
||||||
IGNORE_HEADERS = (
|
|
||||||
'HTTP_ACCEPT_ENCODING', # We want content to be uncompressed so
|
|
||||||
# we remove the Accept-Encoding from
|
|
||||||
# original request
|
|
||||||
'HTTP_HOST',
|
|
||||||
'HTTP_REMOTE_USER',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Default from HTTP RFC 2616
|
|
||||||
# See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1
|
|
||||||
#: Variable that represent the default charset used
|
|
||||||
DEFAULT_CHARSET = 'latin-1'
|
|
||||||
|
|
||||||
#: List containing string constants that represents possible html content type
|
|
||||||
HTML_CONTENT_TYPES = (
|
|
||||||
'text/html',
|
|
||||||
'application/xhtml+xml'
|
|
||||||
)
|
|
||||||
|
|
||||||
#: Variable used to represent a minimal content size required for response
|
|
||||||
#: to be turned into stream
|
|
||||||
MIN_STREAMING_LENGTH = 4 * 1024 # 4KB
|
|
||||||
|
|
||||||
#: Regex used to find charset in a html content type
|
|
||||||
_get_charset_re = re.compile(r';\s*charset=(?P<charset>[^\s;]+)', re.I)
|
|
||||||
|
|
||||||
|
|
||||||
def is_html_content_type(content_type):
|
|
||||||
"""Function used to verify if the parameter is a proper html content type
|
|
||||||
|
|
||||||
:param content_type: String variable that represent a content-type
|
|
||||||
:returns: A boolean value stating if the content_type is a valid html
|
|
||||||
content type
|
|
||||||
"""
|
|
||||||
for html_content_type in HTML_CONTENT_TYPES:
|
|
||||||
if content_type.startswith(html_content_type):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def should_stream(proxy_response):
|
|
||||||
"""Function to verify if the proxy_response must be converted into
|
|
||||||
a stream.This will be done by checking the proxy_response content-length
|
|
||||||
and verify if its length is bigger than one stipulated
|
|
||||||
by MIN_STREAMING_LENGTH.
|
|
||||||
|
|
||||||
:param proxy_response: An Instance of urllib3.response.HTTPResponse
|
|
||||||
:returns: A boolean stating if the proxy_response should
|
|
||||||
be treated as a stream
|
|
||||||
"""
|
|
||||||
content_type = proxy_response.headers.get('Content-Type')
|
|
||||||
|
|
||||||
if is_html_content_type(content_type):
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
content_length = int(proxy_response.headers.get('Content-Length', 0))
|
|
||||||
except ValueError:
|
|
||||||
content_length = 0
|
|
||||||
|
|
||||||
if not content_length or content_length > MIN_STREAMING_LENGTH:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_charset(content_type):
|
|
||||||
"""Function used to retrieve the charset from a content-type.If there is no
|
|
||||||
charset in the content type then the charset defined on DEFAULT_CHARSET
|
|
||||||
will be returned
|
|
||||||
|
|
||||||
:param content_type: A string containing a Content-Type header
|
|
||||||
:returns: A string containing the charset
|
|
||||||
"""
|
|
||||||
if not content_type:
|
|
||||||
return DEFAULT_CHARSET
|
|
||||||
|
|
||||||
matched = _get_charset_re.search(content_type)
|
|
||||||
if matched:
|
|
||||||
# Extract the charset and strip its double quotes
|
|
||||||
return matched.group('charset').replace('"', '')
|
|
||||||
return DEFAULT_CHARSET
|
|
||||||
|
|
||||||
|
|
||||||
def required_header(header):
|
|
||||||
"""Function that verify if the header parameter is a essential header
|
|
||||||
|
|
||||||
:param header: A string represented a header
|
|
||||||
:returns: A boolean value that represent if the header is required
|
|
||||||
"""
|
|
||||||
if header in IGNORE_HEADERS:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if header.startswith('HTTP_') or header == 'CONTENT_TYPE':
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def set_response_headers(response, response_headers):
|
|
||||||
"""Set response's header"""
|
|
||||||
for header, value in response_headers.items():
|
|
||||||
if is_hop_by_hop(header) or header.lower() == 'set-cookie':
|
|
||||||
continue
|
|
||||||
|
|
||||||
response[header.title()] = value
|
|
||||||
|
|
||||||
logger.debug('Response headers: %s', getattr(response, '_headers'))
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_request_headers(request):
|
|
||||||
"""Function used to transform header, replacing 'HTTP\\_' to ''
|
|
||||||
and replace '_' to '-'
|
|
||||||
|
|
||||||
:param request: A HttpRequest that will be transformed
|
|
||||||
:returns: A dictionary with the normalized headers
|
|
||||||
"""
|
|
||||||
norm_headers = {}
|
|
||||||
for header, value in request.META.items():
|
|
||||||
if required_header(header):
|
|
||||||
norm_header = header.replace('HTTP_', '').title().replace('_', '-')
|
|
||||||
norm_headers[norm_header] = value
|
|
||||||
|
|
||||||
return norm_headers
|
|
||||||
|
|
||||||
|
|
||||||
def encode_items(items):
|
|
||||||
"""Function that encode all elements in the list of items passed as
|
|
||||||
a parameter
|
|
||||||
|
|
||||||
:param items: A list of tuple
|
|
||||||
:returns: A list of tuple with all items encoded in 'utf-8'
|
|
||||||
"""
|
|
||||||
encoded = []
|
|
||||||
for key, values in items:
|
|
||||||
for value in values:
|
|
||||||
encoded.append((key.encode('utf-8'), value.encode('utf-8')))
|
|
||||||
return encoded
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger()
|
|
||||||
|
|
||||||
|
|
||||||
def cookie_from_string(cookie_string, strict_cookies=False):
|
|
||||||
"""Parser for HTTP header set-cookie
|
|
||||||
The return from this function will be used as parameters for
|
|
||||||
django's response.set_cookie method. Because set_cookie doesn't
|
|
||||||
have parameter comment, this cookie attribute will be ignored.
|
|
||||||
|
|
||||||
:param cookie_string: A string representing a valid cookie
|
|
||||||
:param strict_cookies: Whether to only accept RFC-compliant cookies
|
|
||||||
:returns: A dictionary containing the cookie_string attributes
|
|
||||||
"""
|
|
||||||
|
|
||||||
if strict_cookies:
|
|
||||||
|
|
||||||
cookies = SimpleCookie(COOKIE_PREFIX + cookie_string)
|
|
||||||
if not cookies.keys():
|
|
||||||
return None
|
|
||||||
cookie_name, = cookies.keys()
|
|
||||||
cookie_dict = {k: v for k, v in cookies[cookie_name].items()
|
|
||||||
if v and k != 'comment'}
|
|
||||||
cookie_dict['key'] = cookie_name
|
|
||||||
cookie_dict['value'] = cookies[cookie_name].value
|
|
||||||
return cookie_dict
|
|
||||||
valid_attrs = ('path', 'domain', 'comment', 'expires',
|
|
||||||
'max_age', 'httponly', 'secure')
|
|
||||||
|
|
||||||
cookie_dict = {}
|
|
||||||
|
|
||||||
cookie_parts = cookie_string.split(';')
|
|
||||||
try:
|
|
||||||
cookie_dict['key'], cookie_dict['value'] = \
|
|
||||||
cookie_parts[0].split('=', 1)
|
|
||||||
cookie_dict['value'] = cookie_dict['value'].replace('"', '')
|
|
||||||
except ValueError:
|
|
||||||
logger.warning('Invalid cookie: `%s`', cookie_string)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if cookie_dict['value'].startswith('='):
|
|
||||||
logger.warning('Invalid cookie: `%s`', cookie_string)
|
|
||||||
return None
|
|
||||||
|
|
||||||
for part in cookie_parts[1:]:
|
|
||||||
if '=' in part:
|
|
||||||
attr, value = part.split('=', 1)
|
|
||||||
value = value.strip()
|
|
||||||
else:
|
|
||||||
attr = part
|
|
||||||
value = ''
|
|
||||||
|
|
||||||
attr = attr.strip().lower()
|
|
||||||
if not attr:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if attr in valid_attrs:
|
|
||||||
if attr in ('httponly', 'secure'):
|
|
||||||
cookie_dict[attr] = True
|
|
||||||
elif attr in 'comment':
|
|
||||||
# ignoring comment attr as explained in the
|
|
||||||
# function docstring
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
cookie_dict[attr] = value
|
|
||||||
else:
|
|
||||||
logger.warning('Unknown cookie attribute %s', attr)
|
|
||||||
|
|
||||||
return cookie_dict
|
|
@ -1,5 +0,0 @@
|
|||||||
"""Application Security Gateway settings"""
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'channels'
|
|
||||||
]
|
|
||||||
ASGI_APPLICATION = "passbook.app_gw.websocket.routing.application"
|
|
@ -1,19 +0,0 @@
|
|||||||
"""passbook app_gw cache clean signals"""
|
|
||||||
|
|
||||||
from django.core.cache import cache
|
|
||||||
from django.db.models.signals import post_save
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.app_gw.models import ApplicationGatewayProvider
|
|
||||||
from passbook.app_gw.proxy.handler import IGNORED_HOSTNAMES_KEY
|
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
|
||||||
|
|
||||||
@receiver(post_save)
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def invalidate_app_gw_cache(sender, instance, **kwargs):
|
|
||||||
"""Invalidate Policy cache when app_gw is updated"""
|
|
||||||
if isinstance(instance, ApplicationGatewayProvider):
|
|
||||||
LOGGER.debug("Invalidating cache for ignored hostnames")
|
|
||||||
cache.delete(IGNORED_HOSTNAMES_KEY)
|
|
@ -1,2 +0,0 @@
|
|||||||
"""passbook app_gw urls"""
|
|
||||||
urlpatterns = []
|
|
@ -1,83 +0,0 @@
|
|||||||
"""websocket proxy consumer"""
|
|
||||||
import threading
|
|
||||||
from ssl import CERT_NONE
|
|
||||||
|
|
||||||
import websocket
|
|
||||||
from channels.generic.websocket import WebsocketConsumer
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.app_gw.models import ApplicationGatewayProvider
|
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
|
||||||
|
|
||||||
class ProxyConsumer(WebsocketConsumer):
|
|
||||||
"""Proxy websocket connection to upstream"""
|
|
||||||
|
|
||||||
_headers_dict = {}
|
|
||||||
_app_gw = None
|
|
||||||
_client = None
|
|
||||||
_thread = None
|
|
||||||
|
|
||||||
def _fix_headers(self, input_dict):
|
|
||||||
"""Fix headers from bytestrings to normal strings"""
|
|
||||||
return {
|
|
||||||
key.decode('utf-8'): value.decode('utf-8')
|
|
||||||
for key, value in dict(input_dict).items()
|
|
||||||
}
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
"""Extract host header, lookup in database and proxy connection"""
|
|
||||||
self._headers_dict = self._fix_headers(dict(self.scope.get('headers')))
|
|
||||||
host = self._headers_dict.pop('host')
|
|
||||||
query_string = self.scope.get('query_string').decode('utf-8')
|
|
||||||
matches = ApplicationGatewayProvider.objects.filter(
|
|
||||||
server_name__contains=[host],
|
|
||||||
enabled=True)
|
|
||||||
if matches.exists():
|
|
||||||
self._app_gw = matches.first()
|
|
||||||
# TODO: Get upstream that starts with wss or
|
|
||||||
upstream = self._app_gw.upstream[0].replace('http', 'ws') + self.scope.get('path')
|
|
||||||
if query_string:
|
|
||||||
upstream += '?' + query_string
|
|
||||||
sslopt = {}
|
|
||||||
if not self._app_gw.upstream_ssl_verification:
|
|
||||||
sslopt = {"cert_reqs": CERT_NONE}
|
|
||||||
self._client = websocket.WebSocketApp(
|
|
||||||
url=upstream,
|
|
||||||
subprotocols=self.scope.get('subprotocols'),
|
|
||||||
header=self._headers_dict,
|
|
||||||
on_message=self._client_on_message_handler(),
|
|
||||||
on_error=self._client_on_error_handler(),
|
|
||||||
on_close=self._client_on_close_handler(),
|
|
||||||
on_open=self._client_on_open_handler())
|
|
||||||
LOGGER.debug("Accepting connection for %s", host)
|
|
||||||
self._thread = threading.Thread(target=lambda: self._client.run_forever(sslopt=sslopt))
|
|
||||||
self._thread.start()
|
|
||||||
|
|
||||||
def _client_on_open_handler(self):
|
|
||||||
return lambda ws: self.accept(self._client.sock.handshake_response.subprotocol)
|
|
||||||
|
|
||||||
def _client_on_message_handler(self):
|
|
||||||
# pylint: disable=unused-argument,invalid-name
|
|
||||||
def message_handler(ws, message):
|
|
||||||
if isinstance(message, str):
|
|
||||||
self.send(text_data=message)
|
|
||||||
else:
|
|
||||||
self.send(bytes_data=message)
|
|
||||||
return message_handler
|
|
||||||
|
|
||||||
def _client_on_error_handler(self):
|
|
||||||
return lambda ws, error: print(error)
|
|
||||||
|
|
||||||
def _client_on_close_handler(self):
|
|
||||||
return lambda ws: self.disconnect(0)
|
|
||||||
|
|
||||||
def disconnect(self, code):
|
|
||||||
self._client.close()
|
|
||||||
|
|
||||||
def receive(self, text_data=None, bytes_data=None):
|
|
||||||
if text_data:
|
|
||||||
opcode = websocket.ABNF.OPCODE_TEXT
|
|
||||||
if bytes_data:
|
|
||||||
opcode = websocket.ABNF.OPCODE_BINARY
|
|
||||||
self._client.send(text_data or bytes_data, opcode)
|
|
@ -1,17 +0,0 @@
|
|||||||
"""app_gw websocket proxy"""
|
|
||||||
from channels.auth import AuthMiddlewareStack
|
|
||||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
||||||
from django.conf.urls import url
|
|
||||||
|
|
||||||
from passbook.app_gw.websocket.consumer import ProxyConsumer
|
|
||||||
|
|
||||||
websocket_urlpatterns = [
|
|
||||||
url(r'^(.*)$', ProxyConsumer),
|
|
||||||
]
|
|
||||||
|
|
||||||
application = ProtocolTypeRouter({
|
|
||||||
# (http->django views is added by default)
|
|
||||||
'websocket': AuthMiddlewareStack(
|
|
||||||
URLRouter(websocket_urlpatterns)
|
|
||||||
),
|
|
||||||
})
|
|
@ -1,7 +1,8 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-16 09:13
|
# Generated by Django 2.2.6 on 2019-10-07 14:07
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
@ -23,7 +24,7 @@ class Migration(migrations.Migration):
|
|||||||
('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])),
|
('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])),
|
||||||
('date', models.DateTimeField(auto_now_add=True)),
|
('date', models.DateTimeField(auto_now_add=True)),
|
||||||
('app', models.TextField()),
|
('app', models.TextField()),
|
||||||
('_context', models.TextField()),
|
('context', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
|
||||||
('request_ip', models.GenericIPAddressField()),
|
('request_ip', models.GenericIPAddressField()),
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||||
@ -33,19 +34,4 @@ class Migration(migrations.Migration):
|
|||||||
'verbose_name_plural': 'Audit Entries',
|
'verbose_name_plural': 'Audit Entries',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='LoginAttempt',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created', models.DateField(auto_now_add=True)),
|
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
|
||||||
('target_uid', models.CharField(max_length=254)),
|
|
||||||
('request_ip', models.GenericIPAddressField()),
|
|
||||||
('attempts', models.IntegerField(default=1)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='loginattempt',
|
|
||||||
unique_together={('target_uid', 'request_ip', 'created')},
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-21 12:01
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_audit', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='loginattempt',
|
|
||||||
name='created',
|
|
||||||
field=models.DateTimeField(auto_now_add=True),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-21 12:40
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields.jsonb
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_audit', '0002_auto_20190221_1201'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='auditentry',
|
|
||||||
name='_context',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='auditentry',
|
|
||||||
name='context',
|
|
||||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,16 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-03-08 14:53
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_audit', '0003_auto_20190221_1240'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='LoginAttempt',
|
|
||||||
),
|
|
||||||
]
|
|
@ -10,7 +10,7 @@ from structlog import get_logger
|
|||||||
|
|
||||||
from passbook.lib.models import UUIDModel
|
from passbook.lib.models import UUIDModel
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
LOGGER = get_logger()
|
||||||
|
|
||||||
class AuditEntry(UUIDModel):
|
class AuditEntry(UUIDModel):
|
||||||
"""An individual audit log entry"""
|
"""An individual audit log entry"""
|
||||||
@ -60,7 +60,8 @@ class AuditEntry(UUIDModel):
|
|||||||
# User 255.255.255.255 as fallback if IP cannot be determined
|
# User 255.255.255.255 as fallback if IP cannot be determined
|
||||||
request_ip=client_ip or '255.255.255.255',
|
request_ip=client_ip or '255.255.255.255',
|
||||||
context=kwargs)
|
context=kwargs)
|
||||||
LOGGER.debug("Logged %s from %s (%s)", action, user, client_ip)
|
LOGGER.debug("Created Audit entry", action=action,
|
||||||
|
user=user, from_ip=client_ip, context=kwargs)
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
"""passbook captcha app"""
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class PassbookCaptchaFactorConfig(AppConfig):
|
|
||||||
"""passbook captcha app"""
|
|
||||||
|
|
||||||
name = 'passbook.captcha_factor'
|
|
||||||
label = 'passbook_captcha_factor'
|
|
||||||
verbose_name = 'passbook Captcha'
|
|
@ -1,2 +0,0 @@
|
|||||||
"""passbook core"""
|
|
||||||
__version__ = '0.2.6-beta'
|
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
"""passbook core app config"""
|
"""passbook core app config"""
|
||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.lib.config import CONFIG
|
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
|
||||||
|
|
||||||
class PassbookCoreConfig(AppConfig):
|
class PassbookCoreConfig(AppConfig):
|
||||||
"""passbook core app config"""
|
"""passbook core app config"""
|
||||||
@ -15,13 +9,3 @@ class PassbookCoreConfig(AppConfig):
|
|||||||
label = 'passbook_core'
|
label = 'passbook_core'
|
||||||
verbose_name = 'passbook Core'
|
verbose_name = 'passbook Core'
|
||||||
mountpoint = ''
|
mountpoint = ''
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
import_module('passbook.policy.engine')
|
|
||||||
factors_to_load = CONFIG.y('passbook.factors', [])
|
|
||||||
for factors_to_load in factors_to_load:
|
|
||||||
try:
|
|
||||||
import_module(factors_to_load)
|
|
||||||
LOGGER.info("Loaded %s", factors_to_load)
|
|
||||||
except ImportError as exc:
|
|
||||||
LOGGER.debug(exc)
|
|
||||||
|
@ -16,7 +16,7 @@ class ApplicationForm(forms.ModelForm):
|
|||||||
|
|
||||||
model = Application
|
model = Application
|
||||||
fields = ['name', 'slug', 'launch_url', 'icon_url',
|
fields = ['name', 'slug', 'launch_url', 'icon_url',
|
||||||
'policies', 'provider', 'skip_authorization']
|
'provider', 'policies', 'skip_authorization']
|
||||||
widgets = {
|
widgets = {
|
||||||
'name': forms.TextInput(),
|
'name': forms.TextInput(),
|
||||||
'launch_url': forms.TextInput(),
|
'launch_url': forms.TextInput(),
|
||||||
|
@ -9,7 +9,7 @@ from passbook.core.models import User
|
|||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
from passbook.lib.utils.ui import human_list
|
from passbook.lib.utils.ui import human_list
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
LOGGER = get_logger()
|
||||||
|
|
||||||
class LoginForm(forms.Form):
|
class LoginForm(forms.Form):
|
||||||
"""Allow users to login"""
|
"""Allow users to login"""
|
||||||
@ -81,13 +81,3 @@ class SignUpForm(forms.Form):
|
|||||||
if password != password_repeat:
|
if password != password_repeat:
|
||||||
raise ValidationError(_("Passwords don't match"))
|
raise ValidationError(_("Passwords don't match"))
|
||||||
return self.cleaned_data.get('password_repeat')
|
return self.cleaned_data.get('password_repeat')
|
||||||
|
|
||||||
|
|
||||||
class PasswordFactorForm(forms.Form):
|
|
||||||
"""Password authentication form"""
|
|
||||||
|
|
||||||
password = forms.CharField(widget=forms.PasswordInput(attrs={
|
|
||||||
'placeholder': _('Password'),
|
|
||||||
'autofocus': 'autofocus',
|
|
||||||
'autocomplete': 'current-password'
|
|
||||||
}))
|
|
||||||
|
@ -26,7 +26,7 @@ class GroupForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Group
|
model = Group
|
||||||
fields = ['name', 'parent', 'members', 'tags']
|
fields = ['name', 'parent', 'members', 'attributes']
|
||||||
widgets = {
|
widgets = {
|
||||||
'name': forms.TextInput(),
|
'name': forms.TextInput(),
|
||||||
}
|
}
|
||||||
|
@ -3,40 +3,8 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from passbook.core.models import (DebugPolicy, FieldMatcherPolicy,
|
from passbook.core.models import DebugPolicy
|
||||||
GroupMembershipPolicy, PasswordPolicy,
|
from passbook.policies.forms import GENERAL_FIELDS
|
||||||
SSOLoginPolicy, WebhookPolicy)
|
|
||||||
|
|
||||||
GENERAL_FIELDS = ['name', 'action', 'negate', 'order', 'timeout']
|
|
||||||
|
|
||||||
class FieldMatcherPolicyForm(forms.ModelForm):
|
|
||||||
"""FieldMatcherPolicy Form"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = FieldMatcherPolicy
|
|
||||||
fields = GENERAL_FIELDS + ['user_field', 'match_action', 'value', ]
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(),
|
|
||||||
'value': forms.TextInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class WebhookPolicyForm(forms.ModelForm):
|
|
||||||
"""WebhookPolicyForm Form"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = WebhookPolicy
|
|
||||||
fields = GENERAL_FIELDS + ['url', 'method', 'json_body', 'json_headers',
|
|
||||||
'result_jsonpath', 'result_json_value', ]
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(),
|
|
||||||
'json_body': forms.TextInput(),
|
|
||||||
'json_headers': forms.TextInput(),
|
|
||||||
'result_jsonpath': forms.TextInput(),
|
|
||||||
'result_json_value': forms.TextInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DebugPolicyForm(forms.ModelForm):
|
class DebugPolicyForm(forms.ModelForm):
|
||||||
@ -52,49 +20,3 @@ class DebugPolicyForm(forms.ModelForm):
|
|||||||
labels = {
|
labels = {
|
||||||
'result': _('Allow user')
|
'result': _('Allow user')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class GroupMembershipPolicyForm(forms.ModelForm):
|
|
||||||
"""GroupMembershipPolicy Form"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = GroupMembershipPolicy
|
|
||||||
fields = GENERAL_FIELDS + ['group', ]
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(),
|
|
||||||
'order': forms.NumberInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
class SSOLoginPolicyForm(forms.ModelForm):
|
|
||||||
"""Edit SSOLoginPolicy instances"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = SSOLoginPolicy
|
|
||||||
fields = GENERAL_FIELDS
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(),
|
|
||||||
'order': forms.NumberInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
class PasswordPolicyForm(forms.ModelForm):
|
|
||||||
"""PasswordPolicy Form"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
model = PasswordPolicy
|
|
||||||
fields = GENERAL_FIELDS + ['amount_uppercase', 'amount_lowercase',
|
|
||||||
'amount_symbols', 'length_min', 'symbol_charset',
|
|
||||||
'error_message']
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(),
|
|
||||||
'symbol_charset': forms.TextInput(),
|
|
||||||
'error_message': forms.TextInput(),
|
|
||||||
}
|
|
||||||
labels = {
|
|
||||||
'amount_uppercase': _('Minimum amount of Uppercase Characters'),
|
|
||||||
'amount_lowercase': _('Minimum amount of Lowercase Characters'),
|
|
||||||
'amount_symbols': _('Minimum amount of Symbols Characters'),
|
|
||||||
'length_min': _('Minimum Length'),
|
|
||||||
}
|
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
"""passbook import_users management command"""
|
|
||||||
from csv import DictReader
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.core.validators import EmailValidator, ValidationError
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.core.models import User
|
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""Import users from CSV file"""
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
# Positional arguments
|
|
||||||
parser.add_argument('file', nargs='+', type=str)
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
"""Create Users from CSV file"""
|
|
||||||
for file in options.get('file'):
|
|
||||||
with open(file, 'r') as _file:
|
|
||||||
reader = DictReader(_file)
|
|
||||||
for user in reader:
|
|
||||||
LOGGER.debug('User %s', user.get('username'))
|
|
||||||
try:
|
|
||||||
# only import users with valid email addresses
|
|
||||||
if user.get('email'):
|
|
||||||
validator = EmailValidator()
|
|
||||||
validator(user.get('email'))
|
|
||||||
# use combination of username and email to check for existing user
|
|
||||||
if User.objects.filter(
|
|
||||||
username=user.get('username'),
|
|
||||||
email=user.get('email')).exists():
|
|
||||||
LOGGER.debug('User %s exists already, skipping', user.get('username'))
|
|
||||||
# Create user
|
|
||||||
User.objects.create(
|
|
||||||
username=user.get('username'),
|
|
||||||
email=user.get('email'),
|
|
||||||
name=user.get('name'),
|
|
||||||
password=user.get('password'))
|
|
||||||
LOGGER.debug('Created User %s', user.get('username'))
|
|
||||||
except ValidationError as exc:
|
|
||||||
LOGGER.warning('User %s caused %r, skipping', user.get('username'), exc)
|
|
||||||
continue
|
|
@ -1,35 +0,0 @@
|
|||||||
"""passbook Webserver management command"""
|
|
||||||
|
|
||||||
import cherrypy
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.lib.config import CONFIG
|
|
||||||
from passbook.root.wsgi import application
|
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""Run CherryPy webserver"""
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
"""passbook cherrypy server"""
|
|
||||||
cherrypy.config.update(CONFIG.y('web'))
|
|
||||||
cherrypy.tree.graft(application, '/')
|
|
||||||
# Mount NullObject to serve static files
|
|
||||||
cherrypy.tree.mount(None, settings.STATIC_URL, config={
|
|
||||||
'/': {
|
|
||||||
'tools.staticdir.on': True,
|
|
||||||
'tools.staticdir.dir': settings.STATIC_ROOT,
|
|
||||||
'tools.expires.on': True,
|
|
||||||
'tools.expires.secs': 86400,
|
|
||||||
'tools.gzip.on': True,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
cherrypy.engine.start()
|
|
||||||
for file in CONFIG.loaded_file:
|
|
||||||
cherrypy.engine.autoreload.files.add(file)
|
|
||||||
LOGGER.info("Added '%s' to autoreload triggers", file)
|
|
||||||
cherrypy.engine.block()
|
|
@ -1,22 +0,0 @@
|
|||||||
"""passbook Worker management command"""
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.utils import autoreload
|
|
||||||
from structlog import get_logger
|
|
||||||
|
|
||||||
from passbook.root.celery import CELERY_APP
|
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""Run Celery Worker"""
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
"""celery worker"""
|
|
||||||
autoreload.run_with_reloader(self.celery_worker)
|
|
||||||
|
|
||||||
def celery_worker(self):
|
|
||||||
"""Run celery worker within autoreload"""
|
|
||||||
autoreload.raise_last_exception()
|
|
||||||
CELERY_APP.worker_main(['worker', '--autoscale=10,3', '-E', '-B'])
|
|
@ -1,21 +1,24 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-16 09:10
|
# Generated by Django 2.2.6 on 2019-10-07 14:06
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
import django.contrib.auth.validators
|
import django.contrib.auth.validators
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import passbook.core.models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('auth', '0009_alter_user_last_name_max_length'),
|
('auth', '0011_update_proxy_permissions'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
@ -34,6 +37,8 @@ class Migration(migrations.Migration):
|
|||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
|
||||||
|
('name', models.TextField()),
|
||||||
|
('password_change_date', models.DateTimeField(auto_now_add=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'user',
|
'verbose_name': 'user',
|
||||||
@ -44,39 +49,17 @@ class Migration(migrations.Migration):
|
|||||||
('objects', django.contrib.auth.models.UserManager()),
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='Group',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=80, verbose_name='name')),
|
|
||||||
('extra_data', models.TextField(blank=True)),
|
|
||||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Invitation',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('expires', models.DateTimeField(blank=True, default=None, null=True)),
|
|
||||||
('fixed_username', models.TextField(blank=True, default=None)),
|
|
||||||
('fixed_email', models.TextField(blank=True, default=None)),
|
|
||||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Invitation',
|
|
||||||
'verbose_name_plural': 'Invitations',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Policy',
|
name='Policy',
|
||||||
fields=[
|
fields=[
|
||||||
('created', models.DateField(auto_now_add=True)),
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
('last_updated', models.DateTimeField(auto_now=True)),
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
('name', models.TextField(blank=True, null=True)),
|
('name', models.TextField(blank=True, null=True)),
|
||||||
('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)),
|
('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)),
|
||||||
('negate', models.BooleanField(default=False)),
|
('negate', models.BooleanField(default=False)),
|
||||||
('order', models.IntegerField(default=0)),
|
('order', models.IntegerField(default=0)),
|
||||||
|
('timeout', models.IntegerField(default=30)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
@ -85,28 +68,136 @@ class Migration(migrations.Migration):
|
|||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='PolicyModel',
|
name='PolicyModel',
|
||||||
fields=[
|
fields=[
|
||||||
('created', models.DateField(auto_now_add=True)),
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
('last_updated', models.DateTimeField(auto_now=True)),
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PropertyMapping',
|
||||||
|
fields=[
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('name', models.TextField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Property Mapping',
|
||||||
|
'verbose_name_plural': 'Property Mappings',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DebugPolicy',
|
||||||
|
fields=[
|
||||||
|
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
||||||
|
('result', models.BooleanField(default=False)),
|
||||||
|
('wait_min', models.IntegerField(default=5)),
|
||||||
|
('wait_max', models.IntegerField(default=30)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Debug Policy',
|
||||||
|
'verbose_name_plural': 'Debug Policies',
|
||||||
|
},
|
||||||
|
bases=('passbook_core.policy',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Factor',
|
||||||
|
fields=[
|
||||||
|
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
||||||
|
('name', models.TextField()),
|
||||||
|
('slug', models.SlugField(unique=True)),
|
||||||
|
('order', models.IntegerField()),
|
||||||
|
('enabled', models.BooleanField(default=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('passbook_core.policymodel',),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Source',
|
||||||
|
fields=[
|
||||||
|
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
||||||
|
('name', models.TextField()),
|
||||||
|
('slug', models.SlugField()),
|
||||||
|
('enabled', models.BooleanField(default=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('passbook_core.policymodel',),
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Provider',
|
name='Provider',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('property_mappings', models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Nonce',
|
||||||
|
fields=[
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)),
|
||||||
|
('expiring', models.BooleanField(default=True)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Nonce',
|
||||||
|
'verbose_name_plural': 'Nonces',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Invitation',
|
||||||
|
fields=[
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('expires', models.DateTimeField(blank=True, default=None, null=True)),
|
||||||
|
('fixed_username', models.TextField(blank=True, default=None)),
|
||||||
|
('fixed_email', models.TextField(blank=True, default=None)),
|
||||||
|
('needs_confirmation', models.BooleanField(default=True)),
|
||||||
|
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Invitation',
|
||||||
|
'verbose_name_plural': 'Invitations',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Group',
|
||||||
|
fields=[
|
||||||
|
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=80, verbose_name='name')),
|
||||||
|
('tags', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
|
||||||
|
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('name', 'parent')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='groups',
|
||||||
|
field=models.ManyToManyField(to='passbook_core.Group'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='user_permissions',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UserSourceConnection',
|
name='UserSourceConnection',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('created', models.DateField(auto_now_add=True)),
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
('last_updated', models.DateTimeField(auto_now=True)),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('user', 'source')},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Application',
|
name='Application',
|
||||||
@ -124,131 +215,9 @@ class Migration(migrations.Migration):
|
|||||||
},
|
},
|
||||||
bases=('passbook_core.policymodel',),
|
bases=('passbook_core.policymodel',),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='DebugPolicy',
|
|
||||||
fields=[
|
|
||||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
|
||||||
('result', models.BooleanField(default=False)),
|
|
||||||
('wait_min', models.IntegerField(default=5)),
|
|
||||||
('wait_max', models.IntegerField(default=30)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Debug Policy',
|
|
||||||
'verbose_name_plural': 'Debug Policys',
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policy',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Factor',
|
|
||||||
fields=[
|
|
||||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
|
||||||
('name', models.TextField()),
|
|
||||||
('slug', models.SlugField(unique=True)),
|
|
||||||
('order', models.IntegerField()),
|
|
||||||
('type', models.TextField(unique=True)),
|
|
||||||
('enabled', models.BooleanField(default=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policymodel',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='FieldMatcherPolicy',
|
|
||||||
fields=[
|
|
||||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
|
||||||
('user_field', models.TextField(choices=[('username', 'Username'), ('first_name', 'First Name'), ('last_name', 'Last Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')])),
|
|
||||||
('match_action', models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('endswith', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50)),
|
|
||||||
('value', models.TextField()),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Field matcher Policy',
|
|
||||||
'verbose_name_plural': 'Field matcher Policys',
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policy',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PasswordPolicyPolicy',
|
|
||||||
fields=[
|
|
||||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
|
||||||
('amount_uppercase', models.IntegerField(default=0)),
|
|
||||||
('amount_lowercase', models.IntegerField(default=0)),
|
|
||||||
('amount_symbols', models.IntegerField(default=0)),
|
|
||||||
('length_min', models.IntegerField(default=0)),
|
|
||||||
('symbol_charset', models.TextField(default='!\\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Password Policy Policy',
|
|
||||||
'verbose_name_plural': 'Password Policy Policys',
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policy',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Source',
|
|
||||||
fields=[
|
|
||||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
|
||||||
('name', models.TextField()),
|
|
||||||
('slug', models.SlugField()),
|
|
||||||
('enabled', models.BooleanField(default=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policymodel',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='WebhookPolicy',
|
|
||||||
fields=[
|
|
||||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
|
||||||
('url', models.URLField()),
|
|
||||||
('method', models.CharField(choices=[('GET', 'GET'), ('POST', 'POST'), ('PATCH', 'PATCH'), ('DELETE', 'DELETE'), ('PUT', 'PUT')], max_length=10)),
|
|
||||||
('json_body', models.TextField()),
|
|
||||||
('json_headers', models.TextField()),
|
|
||||||
('result_jsonpath', models.TextField()),
|
|
||||||
('result_json_value', models.TextField()),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Webhook Policy',
|
|
||||||
'verbose_name_plural': 'Webhook Policys',
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policy',),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='policymodel',
|
|
||||||
name='policies',
|
|
||||||
field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='groups',
|
|
||||||
field=models.ManyToManyField(to='passbook_core.Group'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='user_permissions',
|
|
||||||
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='usersourceconnection',
|
|
||||||
name='source',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source'),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='group',
|
|
||||||
unique_together={('name', 'parent')},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='applications',
|
|
||||||
field=models.ManyToManyField(to='passbook_core.Application'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='user',
|
model_name='user',
|
||||||
name='sources',
|
name='sources',
|
||||||
field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'),
|
field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'),
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='usersourceconnection',
|
|
||||||
unique_together={('user', 'source')},
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-16 10:02
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='debugpolicy',
|
|
||||||
options={'verbose_name': 'Debug Policy', 'verbose_name_plural': 'Debug Policies'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='fieldmatcherpolicy',
|
|
||||||
options={'verbose_name': 'Field matcher Policy', 'verbose_name_plural': 'Field matcher Policies'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='passwordpolicypolicy',
|
|
||||||
options={'verbose_name': 'Password Policy Policy', 'verbose_name_plural': 'Password Policy Policies'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='webhookpolicy',
|
|
||||||
options={'verbose_name': 'Webhook Policy', 'verbose_name_plural': 'Webhook Policies'},
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 2.2 on 2019-04-18 09:09
|
# Generated by Django 2.2.6 on 2019-10-10 11:48
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
@ -6,13 +6,13 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_saml_idp', '0002_samlpropertymapping'),
|
('passbook_core', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='samlprovider',
|
model_name='nonce',
|
||||||
name='audience',
|
name='description',
|
||||||
field=models.TextField(blank=True, default=''),
|
field=models.TextField(blank=True, default=''),
|
||||||
),
|
),
|
||||||
]
|
]
|
@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-16 10:04
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0002_auto_20190216_1002'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameModel(
|
|
||||||
old_name='PasswordPolicyPolicy',
|
|
||||||
new_name='PasswordPolicy',
|
|
||||||
),
|
|
||||||
]
|
|
29
passbook/core/migrations/0003_auto_20191011_0914.py
Normal file
29
passbook/core/migrations/0003_auto_20191011_0914.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 2.2.6 on 2019-10-11 09:14
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields.jsonb
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('passbook_core', '0002_nonce_description'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='group',
|
||||||
|
old_name='tags',
|
||||||
|
new_name='attributes',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='source',
|
||||||
|
name='property_mappings',
|
||||||
|
field=models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='attributes',
|
||||||
|
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
|
||||||
|
),
|
||||||
|
]
|
@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-16 10:13
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0003_auto_20190216_1004'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='passwordpolicy',
|
|
||||||
options={'verbose_name': 'Password Policy', 'verbose_name_plural': 'Password Policies'},
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,28 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-21 12:01
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0004_auto_20190216_1013'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='policy',
|
|
||||||
name='created',
|
|
||||||
field=models.DateTimeField(auto_now_add=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='policymodel',
|
|
||||||
name='created',
|
|
||||||
field=models.DateTimeField(auto_now_add=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='usersourceconnection',
|
|
||||||
name='created',
|
|
||||||
field=models.DateTimeField(auto_now_add=True),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-21 12:32
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields.jsonb
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0005_auto_20190221_1201'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='factor',
|
|
||||||
name='arguments',
|
|
||||||
field=django.contrib.postgres.fields.jsonb.JSONField(default=dict),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-21 12:33
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields.jsonb
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0006_factor_arguments'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='factor',
|
|
||||||
name='arguments',
|
|
||||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-21 15:16
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0007_auto_20190221_1233'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='fieldmatcherpolicy',
|
|
||||||
name='match_action',
|
|
||||||
field=models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('contains', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,44 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-24 09:50
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0008_auto_20190221_1516'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='DummyFactor',
|
|
||||||
fields=[
|
|
||||||
('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('passbook_core.factor',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PasswordFactor',
|
|
||||||
fields=[
|
|
||||||
('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')),
|
|
||||||
('backends', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('passbook_core.factor',),
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='factor',
|
|
||||||
name='arguments',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='factor',
|
|
||||||
name='type',
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,21 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-24 10:16
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0009_auto_20190224_0950'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='dummyfactor',
|
|
||||||
options={'verbose_name': 'Dummy Factor', 'verbose_name_plural': 'Dummy Factors'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='passwordfactor',
|
|
||||||
options={'verbose_name': 'Password Factor', 'verbose_name_plural': 'Password Factors'},
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,25 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-25 14:38
|
|
||||||
|
|
||||||
import django.utils.timezone
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0010_auto_20190224_1016'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='passwordfactor',
|
|
||||||
name='password_policies',
|
|
||||||
field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='password_change_date',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,31 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-25 19:12
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
import passbook.core.models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0011_auto_20190225_1438'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Nonce',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Nonce',
|
|
||||||
'verbose_name_plural': 'Nonces',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-25 19:57
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0012_nonce'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='invitation',
|
|
||||||
name='needs_confirmation',
|
|
||||||
field=models.BooleanField(default=True),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-26 14:28
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0014_auto_20190226_0850'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='passwordpolicy',
|
|
||||||
name='error_message',
|
|
||||||
field=models.TextField(default=''),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,38 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-02-27 13:55
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_names(apps, schema_editor):
|
|
||||||
"""migrate first_name and last_name to name"""
|
|
||||||
User = apps.get_model("passbook_core", "User")
|
|
||||||
for user in User.objects.all():
|
|
||||||
user.name = '%s %s' % (user.first_name, user.last_name)
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0015_passwordpolicy_error_message'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='name',
|
|
||||||
field=models.TextField(default=''),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.RunPython(migrate_names),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='user',
|
|
||||||
name='name',
|
|
||||||
field=models.TextField(),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='fieldmatcherpolicy',
|
|
||||||
name='user_field',
|
|
||||||
field=models.TextField(choices=[('username', 'Username'), ('name', 'Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')]),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,26 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-03-08 10:40
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0016_auto_20190227_1355'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PropertyMapping',
|
|
||||||
fields=[
|
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('name', models.TextField()),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Property Mapping',
|
|
||||||
'verbose_name_plural': 'Property Mappings',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-03-08 10:50
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0017_propertymapping'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='provider',
|
|
||||||
name='property_mappings',
|
|
||||||
field=models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping'),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,25 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-03-10 16:15
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields.hstore
|
|
||||||
from django.contrib.postgres.operations import HStoreExtension
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0018_provider_property_mappings'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='group',
|
|
||||||
name='extra_data',
|
|
||||||
),
|
|
||||||
HStoreExtension(),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='group',
|
|
||||||
name='tags',
|
|
||||||
field=django.contrib.postgres.fields.hstore.HStoreField(default=dict),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-03-21 12:03
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0020_groupmembershippolicy'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='policy',
|
|
||||||
name='timeout',
|
|
||||||
field=models.IntegerField(default=30),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 2.1.7 on 2019-04-04 19:42
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0021_policy_timeout'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='nonce',
|
|
||||||
name='expiring',
|
|
||||||
field=models.BooleanField(default=True),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 2.2 on 2019-04-13 15:51
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('passbook_core', '0022_nonce_expiring'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='user',
|
|
||||||
name='applications',
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,13 +1,12 @@
|
|||||||
"""passbook core models"""
|
"""passbook core models"""
|
||||||
import re
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import List
|
from typing import Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.contrib.postgres.fields import ArrayField, HStoreField
|
from django.contrib.postgres.fields import JSONField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
@ -17,38 +16,26 @@ from structlog import get_logger
|
|||||||
|
|
||||||
from passbook.core.signals import password_changed
|
from passbook.core.signals import password_changed
|
||||||
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
|
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
|
||||||
|
from passbook.policies.exceptions import PolicyException
|
||||||
|
from passbook.policies.struct import PolicyRequest, PolicyResult
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
def default_nonce_duration():
|
def default_nonce_duration():
|
||||||
"""Default duration a Nonce is valid"""
|
"""Default duration a Nonce is valid"""
|
||||||
return now() + timedelta(hours=4)
|
return now() + timedelta(hours=4)
|
||||||
|
|
||||||
|
|
||||||
class PolicyResult:
|
|
||||||
"""Small data-class to hold policy results"""
|
|
||||||
|
|
||||||
passing: bool = False
|
|
||||||
messages: List[str] = []
|
|
||||||
|
|
||||||
def __init__(self, passing: bool, *messages: str):
|
|
||||||
self.passing = passing
|
|
||||||
self.messages = messages
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"<PolicyResult passing={self.passing}>"
|
|
||||||
|
|
||||||
class Group(UUIDModel):
|
class Group(UUIDModel):
|
||||||
"""Custom Group model which supports a basic hierarchy"""
|
"""Custom Group model which supports a basic hierarchy"""
|
||||||
|
|
||||||
name = models.CharField(_('name'), max_length=80)
|
name = models.CharField(_('name'), max_length=80)
|
||||||
parent = models.ForeignKey('Group', blank=True, null=True,
|
parent = models.ForeignKey('Group', blank=True, null=True,
|
||||||
on_delete=models.SET_NULL, related_name='children')
|
on_delete=models.SET_NULL, related_name='children')
|
||||||
tags = HStoreField(default=dict)
|
attributes = 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:
|
||||||
|
|
||||||
@ -64,12 +51,15 @@ class User(AbstractUser):
|
|||||||
groups = models.ManyToManyField('Group')
|
groups = models.ManyToManyField('Group')
|
||||||
password_change_date = models.DateTimeField(auto_now_add=True)
|
password_change_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
attributes = JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
def set_password(self, password):
|
def set_password(self, password):
|
||||||
if self.pk:
|
if self.pk:
|
||||||
password_changed.send(sender=self, user=self, password=password)
|
password_changed.send(sender=self, user=self, password=password)
|
||||||
self.password_change_date = now()
|
self.password_change_date = now()
|
||||||
return super().set_password(password)
|
return super().set_password(password)
|
||||||
|
|
||||||
|
|
||||||
class Provider(models.Model):
|
class Provider(models.Model):
|
||||||
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
|
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
|
||||||
|
|
||||||
@ -83,11 +73,26 @@ class Provider(models.Model):
|
|||||||
return getattr(self, 'name')
|
return getattr(self, 'name')
|
||||||
return super().__str__()
|
return super().__str__()
|
||||||
|
|
||||||
|
|
||||||
class PolicyModel(UUIDModel, CreatedUpdatedModel):
|
class PolicyModel(UUIDModel, CreatedUpdatedModel):
|
||||||
"""Base model which can have policies applied to it"""
|
"""Base model which can have policies applied to it"""
|
||||||
|
|
||||||
policies = models.ManyToManyField('Policy', blank=True)
|
policies = models.ManyToManyField('Policy', blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class UserSettings:
|
||||||
|
"""Dataclass for Factor and Source's user_settings"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
icon: str
|
||||||
|
view_name: str
|
||||||
|
|
||||||
|
def __init__(self, name: str, icon: str, view_name: str):
|
||||||
|
self.name = name
|
||||||
|
self.icon = icon
|
||||||
|
self.view_name = view_name
|
||||||
|
|
||||||
|
|
||||||
class Factor(PolicyModel):
|
class Factor(PolicyModel):
|
||||||
"""Authentication factor, multiple instances of the same Factor can be used"""
|
"""Authentication factor, multiple instances of the same Factor can be used"""
|
||||||
|
|
||||||
@ -100,55 +105,14 @@ class Factor(PolicyModel):
|
|||||||
type = ''
|
type = ''
|
||||||
form = ''
|
form = ''
|
||||||
|
|
||||||
def has_user_settings(self):
|
def user_settings(self) -> Optional[UserSettings]:
|
||||||
"""Entrypoint to integrate with User settings. Can either return False if no
|
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||||
user settings are available, or a tuple or string, string, string where the first string
|
user settings are available, or an instanace of UserSettings."""
|
||||||
is the name the item has, the second string is the icon and the third is the view-name."""
|
return None
|
||||||
return False
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Factor %s" % self.slug
|
return f"Factor {self.slug}"
|
||||||
|
|
||||||
class PasswordFactor(Factor):
|
|
||||||
"""Password-based Django-backend Authentication Factor"""
|
|
||||||
|
|
||||||
backends = ArrayField(models.TextField())
|
|
||||||
password_policies = models.ManyToManyField('Policy', blank=True)
|
|
||||||
|
|
||||||
type = 'passbook.core.auth.factors.password.PasswordFactor'
|
|
||||||
form = 'passbook.core.forms.factors.PasswordFactorForm'
|
|
||||||
|
|
||||||
def has_user_settings(self):
|
|
||||||
return _('Change Password'), 'pficon-key', 'passbook_core:user-change-password'
|
|
||||||
|
|
||||||
def password_passes(self, user: User) -> bool:
|
|
||||||
"""Return true if user's password passes, otherwise False or raise Exception"""
|
|
||||||
for policy in self.policies.all():
|
|
||||||
if not policy.passes(user):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Password Factor %s" % self.slug
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _('Password Factor')
|
|
||||||
verbose_name_plural = _('Password Factors')
|
|
||||||
|
|
||||||
class DummyFactor(Factor):
|
|
||||||
"""Dummy factor, mostly used to debug"""
|
|
||||||
|
|
||||||
type = 'passbook.core.auth.factors.dummy.DummyFactor'
|
|
||||||
form = 'passbook.core.forms.factors.DummyFactorForm'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Dummy Factor %s" % self.slug
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _('Dummy Factor')
|
|
||||||
verbose_name_plural = _('Dummy Factors')
|
|
||||||
|
|
||||||
class Application(PolicyModel):
|
class Application(PolicyModel):
|
||||||
"""Every Application which uses passbook for authentication/identification/authorization
|
"""Every Application which uses passbook for authentication/identification/authorization
|
||||||
@ -174,12 +138,14 @@ 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"""
|
||||||
|
|
||||||
name = models.TextField()
|
name = models.TextField()
|
||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
enabled = models.BooleanField(default=True)
|
enabled = models.BooleanField(default=True)
|
||||||
|
property_mappings = models.ManyToManyField('PropertyMapping', default=None, blank=True)
|
||||||
|
|
||||||
form = '' # ModelForm-based class ued to create/edit instance
|
form = '' # ModelForm-based class ued to create/edit instance
|
||||||
|
|
||||||
@ -200,15 +166,15 @@ class Source(PolicyModel):
|
|||||||
"""Return additional Info, such as a callback URL. Show in the administration interface."""
|
"""Return additional Info, such as a callback URL. Show in the administration interface."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def has_user_settings(self):
|
def user_settings(self) -> Optional[UserSettings]:
|
||||||
"""Entrypoint to integrate with User settings. Can either return False if no
|
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||||
user settings are available, or a tuple or string, string, string where the first string
|
user settings are available, or an instanace of UserSettings."""
|
||||||
is the name the item has, the second string is the icon and the third is the view-name."""
|
return None
|
||||||
return False
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class UserSourceConnection(CreatedUpdatedModel):
|
class UserSourceConnection(CreatedUpdatedModel):
|
||||||
"""Connection between User and Source."""
|
"""Connection between User and Source."""
|
||||||
|
|
||||||
@ -219,6 +185,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 +208,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 +225,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 +237,6 @@ class DebugPolicy(Policy):
|
|||||||
verbose_name = _('Debug Policy')
|
verbose_name = _('Debug Policy')
|
||||||
verbose_name_plural = _('Debug Policies')
|
verbose_name_plural = _('Debug Policies')
|
||||||
|
|
||||||
class GroupMembershipPolicy(Policy):
|
|
||||||
"""Policy to check if the user is member in a certain group"""
|
|
||||||
|
|
||||||
group = models.ForeignKey('Group', on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
form = 'passbook.core.forms.policies.GroupMembershipPolicyForm'
|
|
||||||
|
|
||||||
def passes(self, user: User) -> PolicyResult:
|
|
||||||
return PolicyResult(self.group.user_set.filter(pk=user.pk).exists())
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _('Group Membership Policy')
|
|
||||||
verbose_name_plural = _('Group Membership Policies')
|
|
||||||
|
|
||||||
class SSOLoginPolicy(Policy):
|
|
||||||
"""Policy that applies to users that have authenticated themselves through SSO"""
|
|
||||||
|
|
||||||
form = 'passbook.core.forms.policies.SSOLoginPolicyForm'
|
|
||||||
|
|
||||||
def passes(self, user) -> PolicyResult:
|
|
||||||
"""Check if user instance passes this policy"""
|
|
||||||
from passbook.core.auth.view import AuthenticationView
|
|
||||||
return PolicyResult(user.session.get(AuthenticationView.SESSION_IS_SSO_LOGIN, False))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
verbose_name = _('SSO Login Policy')
|
|
||||||
verbose_name_plural = _('SSO Login Policies')
|
|
||||||
|
|
||||||
class Invitation(UUIDModel):
|
class Invitation(UUIDModel):
|
||||||
"""Single-use invitation link"""
|
"""Single-use invitation link"""
|
||||||
@ -451,31 +250,39 @@ class Invitation(UUIDModel):
|
|||||||
@property
|
@property
|
||||||
def link(self):
|
def link(self):
|
||||||
"""Get link to use invitation"""
|
"""Get link to use invitation"""
|
||||||
return reverse_lazy('passbook_core:auth-sign-up') + '?invitation=%s' % self.uuid
|
return reverse_lazy('passbook_core:auth-sign-up') + f'?invitation={self.uuid.hex}'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Invitation %s created by %s" % (self.uuid, self.created_by)
|
return f"Invitation {self.uuid.hex} created by {self.created_by}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
verbose_name = _('Invitation')
|
verbose_name = _('Invitation')
|
||||||
verbose_name_plural = _('Invitations')
|
verbose_name_plural = _('Invitations')
|
||||||
|
|
||||||
|
|
||||||
class Nonce(UUIDModel):
|
class Nonce(UUIDModel):
|
||||||
"""One-time link for password resets/sign-up-confirmations"""
|
"""One-time link for password resets/sign-up-confirmations"""
|
||||||
|
|
||||||
expires = models.DateTimeField(default=default_nonce_duration)
|
expires = models.DateTimeField(default=default_nonce_duration)
|
||||||
user = models.ForeignKey('User', on_delete=models.CASCADE)
|
user = models.ForeignKey('User', on_delete=models.CASCADE)
|
||||||
expiring = models.BooleanField(default=True)
|
expiring = models.BooleanField(default=True)
|
||||||
|
description = models.TextField(default='', blank=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_expired(self) -> bool:
|
||||||
|
"""Check if nonce is expired yet."""
|
||||||
|
return now() > self.expires
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Nonce %s (expires=%s)" % (self.uuid.hex, self.expires)
|
return f"Nonce f{self.uuid.hex} {self.description} (expires={self.expires})"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
verbose_name = _('Nonce')
|
verbose_name = _('Nonce')
|
||||||
verbose_name_plural = _('Nonces')
|
verbose_name_plural = _('Nonces')
|
||||||
|
|
||||||
|
|
||||||
class PropertyMapping(UUIDModel):
|
class PropertyMapping(UUIDModel):
|
||||||
"""User-defined key -> x mapping which can be used by providers to expose extra data."""
|
"""User-defined key -> x mapping which can be used by providers to expose extra data."""
|
||||||
|
|
||||||
@ -485,7 +292,7 @@ class PropertyMapping(UUIDModel):
|
|||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Property Mapping %s" % self.name
|
return f"Property Mapping {self.name}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
|
@ -5,37 +5,20 @@ from django.db.models.signals import post_save
|
|||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.exceptions import PasswordPolicyInvalid
|
LOGGER = get_logger()
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
|
||||||
|
|
||||||
user_signed_up = Signal(providing_args=['request', 'user'])
|
user_signed_up = Signal(providing_args=['request', 'user'])
|
||||||
invitation_created = Signal(providing_args=['request', 'invitation'])
|
invitation_created = Signal(providing_args=['request', 'invitation'])
|
||||||
invitation_used = Signal(providing_args=['request', 'invitation', 'user'])
|
invitation_used = Signal(providing_args=['request', 'invitation', 'user'])
|
||||||
password_changed = Signal(providing_args=['user', 'password'])
|
password_changed = Signal(providing_args=['user', 'password'])
|
||||||
|
|
||||||
@receiver(password_changed)
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def password_policy_checker(sender, password, **kwargs):
|
|
||||||
"""Run password through all password policies which are applied to the user"""
|
|
||||||
from passbook.core.models import PasswordFactor
|
|
||||||
from passbook.policy.engine import PolicyEngine
|
|
||||||
setattr(sender, '__password__', password)
|
|
||||||
_all_factors = PasswordFactor.objects.filter(enabled=True).order_by('order')
|
|
||||||
for factor in _all_factors:
|
|
||||||
policy_engine = PolicyEngine(factor.password_policies.all().select_subclasses())
|
|
||||||
policy_engine.for_user(sender).build()
|
|
||||||
passing, messages = policy_engine.result
|
|
||||||
if not passing:
|
|
||||||
raise PasswordPolicyInvalid(*messages)
|
|
||||||
|
|
||||||
@receiver(post_save)
|
@receiver(post_save)
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def invalidate_policy_cache(sender, instance, **kwargs):
|
def invalidate_policy_cache(sender, instance, **_):
|
||||||
"""Invalidate Policy cache when policy is updated"""
|
"""Invalidate Policy cache when policy is updated"""
|
||||||
from passbook.core.models import Policy
|
from passbook.core.models import Policy
|
||||||
if isinstance(instance, Policy):
|
if isinstance(instance, Policy):
|
||||||
LOGGER.debug("Invalidating cache for %s", instance.pk)
|
LOGGER.debug("Invalidating policy cache", policy=instance)
|
||||||
keys = cache.keys("%s#*" % instance.pk)
|
keys = cache.keys("%s#*" % instance.pk)
|
||||||
cache.delete_many(keys)
|
cache.delete_many(keys)
|
||||||
LOGGER.debug("Deleted %d keys", len(keys))
|
LOGGER.debug("Deleted %d keys", len(keys))
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
.navbar-brand-name {
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
.dynamic-array-widget .array-item {
|
.dynamic-array-widget .array-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -23,7 +27,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Selector */
|
/* Selector */
|
||||||
|
|
||||||
.selector {
|
.selector {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
2
passbook/core/static/robots.txt
Normal file
2
passbook/core/static/robots.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
@ -1,28 +1,14 @@
|
|||||||
"""passbook core tasks"""
|
"""passbook core tasks"""
|
||||||
from datetime import datetime
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from django.core.mail import EmailMultiAlternatives
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.utils.html import strip_tags
|
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Nonce
|
from passbook.core.models import Nonce
|
||||||
from passbook.lib.config import CONFIG
|
|
||||||
from passbook.root.celery import CELERY_APP
|
from passbook.root.celery import CELERY_APP
|
||||||
|
|
||||||
LOGGER = get_logger(__name__)
|
LOGGER = get_logger()
|
||||||
|
|
||||||
@CELERY_APP.task()
|
|
||||||
def send_email(to_address, subject, template, context):
|
|
||||||
"""Send Email to user(s)"""
|
|
||||||
html_content = render_to_string(template, context=context)
|
|
||||||
text_content = strip_tags(html_content)
|
|
||||||
msg = EmailMultiAlternatives(subject, text_content, CONFIG.y('email.from'), [to_address])
|
|
||||||
msg.attach_alternative(html_content, "text/html")
|
|
||||||
msg.send()
|
|
||||||
|
|
||||||
@CELERY_APP.task()
|
@CELERY_APP.task()
|
||||||
def clean_nonces():
|
def clean_nonces():
|
||||||
"""Remove expired nonces"""
|
"""Remove expired nonces"""
|
||||||
amount, _ = Nonce.objects.filter(expires__lt=datetime.now(), expiring=True).delete()
|
amount, _ = Nonce.objects.filter(expires__lt=now(), expiring=True).delete()
|
||||||
LOGGER.debug("Deleted expired %d nonces", amount)
|
LOGGER.debug("Deleted expired nonces", amount=amount)
|
||||||
|
@ -46,9 +46,6 @@
|
|||||||
<script src="{% static 'js/passbook.js' %}"></script>
|
<script src="{% static 'js/passbook.js' %}"></script>
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<div class="modals">
|
|
||||||
{% include 'partials/about_modal.html' %}
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -46,9 +46,6 @@
|
|||||||
<script src="{% static 'js/passbook.js' %}"></script>
|
<script src="{% static 'js/passbook.js' %}"></script>
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<div class="modals">
|
|
||||||
{% include 'partials/about_modal.html' %}
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user