Compare commits
40 Commits
version/0.
...
version/0.
Author | SHA1 | Date | |
---|---|---|---|
48a04744e0 | |||
6446ca8bb2 | |||
b9991465ee | |||
3d8242be06 | |||
ca3bcc565d | |||
432176ea2f | |||
c1dae0b599 | |||
e70d3b6286 | |||
17e6bc921b | |||
46111e7cac | |||
3b7e47dbe2 | |||
fff99f0e3d | |||
2e15b24f0a | |||
088b9592cd | |||
b1e4e32b83 | |||
d91a852eda | |||
171c5b9759 | |||
64290b2a37 | |||
72769b8a0a | |||
1018309413 | |||
6d0ecd228e | |||
40a651e66c | |||
a390bb7b59 | |||
245ec65cbb | |||
17eea4a10c | |||
862fb0f5d2 | |||
ec73b53340 | |||
9110f7fee3 | |||
54cc1fdeef | |||
8f42a7f0b4 | |||
2c221ea819 | |||
93e0441b58 | |||
7f1455cb12 | |||
59fc223a85 | |||
0a6f555c23 | |||
6a4233d6fd | |||
15fa7e9652 | |||
f2acc154cd | |||
d21ec6c9a5 | |||
43dd858cd5 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.5.0-beta
|
||||
current_version = 0.6.4-beta
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
||||
|
@ -27,7 +27,7 @@ create-base-image:
|
||||
before_script:
|
||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/base.Dockerfile --destination docker.beryju.org/passbook/base:latest --destination docker.beryju.org/passbook/base:0.5.0-beta
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/base.Dockerfile --destination docker.beryju.org/passbook/base:latest --destination docker.beryju.org/passbook/base:0.6.4-beta
|
||||
stage: build-base-image
|
||||
only:
|
||||
refs:
|
||||
@ -41,7 +41,7 @@ build-dev-image:
|
||||
before_script:
|
||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dev.Dockerfile --destination docker.beryju.org/passbook/dev:latest --destination docker.beryju.org/passbook/dev:0.5.0-beta
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dev.Dockerfile --destination docker.beryju.org/passbook/dev:latest --destination docker.beryju.org/passbook/dev:0.6.4-beta
|
||||
stage: build-dev-image
|
||||
only:
|
||||
refs:
|
||||
@ -87,15 +87,15 @@ coverage:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
|
||||
package-passbook-server:
|
||||
build-passbook-server:
|
||||
stage: build
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:debug
|
||||
entrypoint: [""]
|
||||
before_script:
|
||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.5.0-beta
|
||||
stage: build
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.6.4-beta
|
||||
only:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
@ -107,7 +107,7 @@ build-passbook-static:
|
||||
before_script:
|
||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/static.Dockerfile --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.5.0-beta
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/static.Dockerfile --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.6.4-beta
|
||||
only:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
|
@ -1,9 +1,9 @@
|
||||
FROM docker.beryju.org/passbook/base:test
|
||||
FROM docker.beryju.org/passbook/base:latest
|
||||
|
||||
COPY ./passbook/ /app/passbook
|
||||
COPY --chown=passbook:passbook ./passbook/ /app/passbook
|
||||
COPY ./manage.py /app/
|
||||
COPY ./docker/uwsgi.ini /app/
|
||||
|
||||
USER passbook
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
USER passbook
|
||||
|
2
Pipfile
2
Pipfile
@ -8,6 +8,7 @@ celery = "*"
|
||||
cherrypy = "*"
|
||||
defusedxml = "*"
|
||||
django = "*"
|
||||
kombu = "==4.5.0"
|
||||
django-cors-middleware = "*"
|
||||
django-filters = "*"
|
||||
django-ipware = "*"
|
||||
@ -18,7 +19,6 @@ django-otp = "*"
|
||||
django-recaptcha = "*"
|
||||
django-redis = "*"
|
||||
django-rest-framework = "*"
|
||||
djangorestframework = "==3.9.4"
|
||||
drf-yasg = "*"
|
||||
ldap3 = "*"
|
||||
lxml = "*"
|
||||
|
60
Pipfile.lock
generated
60
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "d03d1e494d28a90b39edd1d489afdb5e39ec09bceb18daa2a54b2cc7de61d83c"
|
||||
"sha256": "53d7190ea62f504dc1a36eae952a273e0b2d9f313f23031099d039c3146235b7"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@ -18,17 +18,17 @@
|
||||
"default": {
|
||||
"amqp": {
|
||||
"hashes": [
|
||||
"sha256:19a917e260178b8d410122712bac69cb3e6db010d68f6101e7307508aded5e68",
|
||||
"sha256:19d851b879a471fcfdcf01df9936cff924f422baa77653289f7095dedd5fb26a"
|
||||
"sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8",
|
||||
"sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d"
|
||||
],
|
||||
"version": "==2.5.1"
|
||||
"version": "==2.5.2"
|
||||
},
|
||||
"asn1crypto": {
|
||||
"hashes": [
|
||||
"sha256:d02bf8ea1b964a5ff04ac7891fe3a39150045d1e5e4fe99273ba677d11b92a04",
|
||||
"sha256:f822954b90c4c44f002e2cd46d636ab630f1fe4df22c816a82b66505c404eb2a"
|
||||
"sha256:0b199f211ae690df3db4fd6c1c4ff976497fb1da689193e368eedbadc53d9292",
|
||||
"sha256:bca90060bd995c3f62c4433168eab407e44bdbdb567b3f3a396a676c1a4c4a3f"
|
||||
],
|
||||
"version": "==1.0.0"
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
@ -242,11 +242,10 @@
|
||||
},
|
||||
"djangorestframework": {
|
||||
"hashes": [
|
||||
"sha256:376f4b50340a46c15ae15ddd0c853085f4e66058f97e4dbe7d43ed62f5e60651",
|
||||
"sha256:c12869cfd83c33d579b17b3cb28a2ae7322a53c3ce85580c2a2ebe4e3f56c4fb"
|
||||
"sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8",
|
||||
"sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.9.4"
|
||||
"version": "==3.10.3"
|
||||
},
|
||||
"drf-yasg": {
|
||||
"hashes": [
|
||||
@ -276,13 +275,6 @@
|
||||
],
|
||||
"version": "==2.8"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26",
|
||||
"sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"
|
||||
],
|
||||
"version": "==0.23"
|
||||
},
|
||||
"inflection": {
|
||||
"hashes": [
|
||||
"sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca"
|
||||
@ -304,17 +296,18 @@
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
|
||||
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
|
||||
"sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f",
|
||||
"sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"
|
||||
],
|
||||
"version": "==2.10.1"
|
||||
"version": "==2.10.3"
|
||||
},
|
||||
"kombu": {
|
||||
"hashes": [
|
||||
"sha256:31edb84947996fdda065b6560c128d5673bb913ff34aa19e7b84755217a24deb",
|
||||
"sha256:c9078124ce2616b29cf6607f0ac3db894c59154252dee6392cdbbe15e5c4b566"
|
||||
"sha256:389ba09e03b15b55b1a7371a441c894fd8121d174f5583bbbca032b9ea8c9edd",
|
||||
"sha256:7b92303af381ef02fad6899fd5f5a9a96031d781356cd8e505fa54ae5ddee181"
|
||||
],
|
||||
"version": "==4.6.5"
|
||||
"index": "pypi",
|
||||
"version": "==4.5.0"
|
||||
},
|
||||
"ldap3": {
|
||||
"hashes": [
|
||||
@ -566,10 +559,10 @@
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32",
|
||||
"sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"
|
||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
||||
],
|
||||
"version": "==2019.2"
|
||||
"version": "==2019.3"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
@ -736,13 +729,6 @@
|
||||
"sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f"
|
||||
],
|
||||
"version": "==2.0"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
|
||||
"sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
|
||||
],
|
||||
"version": "==0.6.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
@ -976,10 +962,10 @@
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32",
|
||||
"sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"
|
||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
||||
],
|
||||
"version": "==2019.2"
|
||||
"version": "==2019.3"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
|
@ -3,7 +3,9 @@
|
||||
## 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
|
||||
```
|
||||
|
@ -15,5 +15,4 @@ RUN apt-get update && \
|
||||
RUN pipenv lock -r > requirements.txt && \
|
||||
pipenv --rm && \
|
||||
pip install -r requirements.txt --no-cache-dir && \
|
||||
adduser --system --no-create-home passbook && \
|
||||
chown -R passbook /app
|
||||
adduser --system --no-create-home --uid 1000 --group --home /app passbook
|
||||
|
@ -20,28 +20,15 @@ services:
|
||||
- internal
|
||||
labels:
|
||||
- traefik.enable=false
|
||||
database-migrate:
|
||||
build:
|
||||
context: .
|
||||
image: docker.beryju.org/passbook/server:${TAG:-test}
|
||||
command:
|
||||
- ./manage.py
|
||||
- migrate
|
||||
networks:
|
||||
- internal
|
||||
restart: 'no'
|
||||
environment:
|
||||
- PASSBOOK_REDIS__HOST=redis
|
||||
- PASSBOOK_POSTGRESQL__HOST=postgresql
|
||||
- PASSBOOK_POSTGRESQL__PASSWORD=${PG_PASS:-thisisnotagoodpassword}
|
||||
server:
|
||||
build:
|
||||
context: .
|
||||
image: docker.beryju.org/passbook/server:${TAG:-test}
|
||||
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}
|
||||
@ -54,15 +41,20 @@ services:
|
||||
- traefik.docker.network=internal
|
||||
- traefik.frontend.rule=PathPrefix:/
|
||||
worker:
|
||||
image: docker.beryju.org/passbook/server:${TAG:-test}
|
||||
image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest}
|
||||
command:
|
||||
- ./manage.py
|
||||
- celery
|
||||
- worker
|
||||
- --autoscale=10,3
|
||||
- -E
|
||||
- -B
|
||||
- -A=passbook.root.celery
|
||||
networks:
|
||||
- internal
|
||||
labels:
|
||||
- traefik.enable=false
|
||||
environment:
|
||||
- PASSBOOK_DOMAIN=${PASSBOOK_DOMAIN}
|
||||
- PASSBOOK_REDIS__HOST=redis
|
||||
- PASSBOOK_POSTGRESQL__HOST=postgresql
|
||||
- PASSBOOK_POSTGRESQL__PASSWORD=${PG_PASS:-thisisnotagoodpassword}
|
||||
@ -70,7 +62,7 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: static.Dockerfile
|
||||
image: docker.beryju.org/passbook/static:${TAG:-test}
|
||||
image: docker.beryju.org/passbook/static:latest
|
||||
networks:
|
||||
- internal
|
||||
labels:
|
||||
|
@ -39,7 +39,7 @@ http {
|
||||
gzip on;
|
||||
gzip_types application/javascript image/* text/css;
|
||||
gunzip on;
|
||||
add_header X-passbook-Version 0.5.0-beta;
|
||||
add_header X-passbook-Version 0.6.4-beta;
|
||||
add_header Vary X-passbook-Version;
|
||||
root /data/;
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
http = 0.0.0.0:8000
|
||||
chdir = /app
|
||||
wsgi-file = passbook/root/wsgi.py
|
||||
processes = 4
|
||||
processes = 2
|
||||
master = true
|
||||
threads = 2
|
||||
enable-threads = true
|
||||
uid = passbook
|
||||
|
@ -1,6 +1,6 @@
|
||||
apiVersion: v1
|
||||
appVersion: "0.5.0-beta"
|
||||
appVersion: "0.6.4-beta"
|
||||
description: A Helm chart for passbook.
|
||||
name: passbook
|
||||
version: "0.5.0-beta"
|
||||
version: "0.6.4-beta"
|
||||
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png
|
||||
|
@ -12,101 +12,5 @@ data:
|
||||
host: "{{ .Release.Name }}-redis-master"
|
||||
cache_db: 0
|
||||
message_queue_db: 1
|
||||
|
||||
# Error reporting, sends stacktrace to sentry.beryju.org
|
||||
error_report_enabled: {{ .Values.config.error_reporting }}
|
||||
|
||||
{{- if .Values.config.secret_key }}
|
||||
secret_key: {{ .Values.config.secret_key }}
|
||||
{{- else }}
|
||||
secret_key: {{ randAlphaNum 50 }}
|
||||
{{- end }}
|
||||
|
||||
primary_domain: {{ .Values.primary_domain }}
|
||||
domains:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
- kubernetes-healthcheck-host
|
||||
|
||||
passbook:
|
||||
sign_up:
|
||||
# Enables signup, created users are stored in internal Database and created in LDAP if ldap.create_users is true
|
||||
enabled: true
|
||||
password_reset:
|
||||
# Enable password reset, passwords are reset in internal Database and in LDAP if ldap.reset_password is true
|
||||
enabled: true
|
||||
# Verification the user has to provide in order to be able to reset passwords. Can be any combination of `email`, `2fa`, `security_questions`
|
||||
verification:
|
||||
- email
|
||||
# Text used in title, on login page and multiple other places
|
||||
branding: passbook
|
||||
login:
|
||||
# Override URL used for logo
|
||||
logo_url: null
|
||||
# Override URL used for Background on Login page
|
||||
bg_url: null
|
||||
# Optionally add a subtext, placed below logo on the login page
|
||||
subtext: null
|
||||
footer:
|
||||
links:
|
||||
# Optionally add links to the footer on the login page
|
||||
# - name: test
|
||||
# href: https://test
|
||||
# Specify which fields can be used to authenticate. Can be any combination of `username` and `email`
|
||||
uid_fields:
|
||||
- username
|
||||
- email
|
||||
session:
|
||||
remember_age: 2592000 # 60 * 60 * 24 * 30, one month
|
||||
# Provider-specific settings
|
||||
ldap:
|
||||
# # Completely enable or disable LDAP provider
|
||||
# enabled: false
|
||||
# # AD Domain, used to generate `userPrincipalName`
|
||||
# domain: corp.contoso.com
|
||||
# # Base DN in which passbook should look for users
|
||||
# base_dn: dn=corp,dn=contoso,dn=com
|
||||
# # LDAP field which is used to set the django username
|
||||
# username_field: sAMAccountName
|
||||
# # LDAP server to connect to, can be set to `<domain_name>`
|
||||
# server:
|
||||
# name: corp.contoso.com
|
||||
# use_tls: false
|
||||
# # Bind credentials, used for account creation
|
||||
# bind:
|
||||
# username: Administraotr@corp.contoso.com
|
||||
# password: VerySecurePassword!
|
||||
# Which field from `uid_fields` maps to which LDAP Attribute
|
||||
login_field_map:
|
||||
username: sAMAccountName
|
||||
email: mail # or userPrincipalName
|
||||
user_attribute_map:
|
||||
active_directory:
|
||||
username: "%(sAMAccountName)s"
|
||||
email: "%(mail)s"
|
||||
name: "%(displayName)"
|
||||
# # Create new users in LDAP upon sign-up
|
||||
# create_users: true
|
||||
# # Reset LDAP password when user reset their password
|
||||
# reset_password: true
|
||||
oauth_client:
|
||||
# List of python packages with sources types to load.
|
||||
types:
|
||||
- passbook.oauth_client.source_types.discord
|
||||
- passbook.oauth_client.source_types.facebook
|
||||
- passbook.oauth_client.source_types.github
|
||||
- passbook.oauth_client.source_types.google
|
||||
- passbook.oauth_client.source_types.reddit
|
||||
- passbook.oauth_client.source_types.supervisr
|
||||
- passbook.oauth_client.source_types.twitter
|
||||
- passbook.oauth_client.source_types.azure_ad
|
||||
saml_idp:
|
||||
signing: true
|
||||
autosubmit: false
|
||||
issuer: passbook
|
||||
assertion_valid_for: 86400
|
||||
# List of python packages with provider types to load.
|
||||
types:
|
||||
- passbook.saml_idp.processors.generic
|
||||
- passbook.saml_idp.processors.salesforce
|
||||
domain: ".{{ .Values.ingress.hosts[0] }}"
|
||||
|
11
helm/passbook/templates/secret.yaml
Normal file
11
helm/passbook/templates/secret.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: Opaque
|
||||
metadata:
|
||||
name: {{ include "passbook.fullname" . }}-secret-key
|
||||
data:
|
||||
{{- if .Values.config.secret_key }}
|
||||
secret_key: {{ .Values.config.secret_key | b64enc | quote }}
|
||||
{{- else }}
|
||||
secret_key: {{ randAlphaNum 50 | b64enc | quote}}
|
||||
{{- end }}
|
@ -8,7 +8,7 @@ metadata:
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: {{ include "passbook.name" . }}
|
||||
@ -31,11 +31,19 @@ spec:
|
||||
- ./manage.py
|
||||
args:
|
||||
- migrate
|
||||
volumeMounts:
|
||||
- mountPath: /etc/passbook
|
||||
name: config-volume
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "passbook.fullname" . }}-config
|
||||
prefix: PASSBOOK_
|
||||
env:
|
||||
- name: PASSBOOK_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "passbook.fullname" . }}-secret-key
|
||||
key: secret_key
|
||||
- name: PASSBOOK_REDIS__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
@ -46,9 +54,6 @@ spec:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-postgresql"
|
||||
key: postgresql-password
|
||||
volumeMounts:
|
||||
- mountPath: /etc/passbook
|
||||
name: config-volume
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
||||
@ -57,11 +62,19 @@ spec:
|
||||
- uwsgi
|
||||
args:
|
||||
- uwsgi.ini
|
||||
volumeMounts:
|
||||
- mountPath: /etc/passbook
|
||||
name: config-volume
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "passbook.fullname" . }}-config
|
||||
prefix: PASSBOOK_
|
||||
env:
|
||||
- name: PASSBOOK_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "passbook.fullname" . }}-secret-key
|
||||
key: secret_key
|
||||
- name: PASSBOOK_REDIS__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
@ -76,9 +89,6 @@ spec:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- mountPath: /etc/passbook
|
||||
name: config-volume
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
@ -95,8 +105,8 @@ spec:
|
||||
value: kubernetes-healthcheck-host
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 150M
|
||||
cpu: 100m
|
||||
memory: 200M
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 300M
|
||||
cpu: 300m
|
||||
memory: 350M
|
||||
|
@ -8,7 +8,7 @@ metadata:
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: {{ include "passbook.name" . }}
|
||||
@ -29,14 +29,26 @@ spec:
|
||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- ./manage.py
|
||||
- celery
|
||||
args:
|
||||
- worker
|
||||
- --autoscale=10,3
|
||||
- -E
|
||||
- -B
|
||||
- -A=passbook.root.celery
|
||||
volumeMounts:
|
||||
- mountPath: /etc/passbook
|
||||
name: config-volume
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "passbook.fullname" . }}-config
|
||||
prefix: PASSBOOK_
|
||||
env:
|
||||
- name: PASSBOOK_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "passbook.fullname" . }}-secret-key
|
||||
key: secret_key
|
||||
- name: PASSBOOK_REDIS__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
@ -47,13 +59,6 @@ spec:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-postgresql"
|
||||
key: postgresql-password
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- mountPath: /etc/passbook
|
||||
name: config-volume
|
||||
resources:
|
||||
requests:
|
||||
cpu: 150m
|
||||
|
@ -1,11 +1,8 @@
|
||||
# Default values for passbook.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
tag: 0.5.0-beta
|
||||
tag: 0.6.4-beta
|
||||
|
||||
nameOverride: ""
|
||||
|
||||
@ -19,7 +16,13 @@ config:
|
||||
|
||||
postgresql:
|
||||
postgresqlDatabase: passbook
|
||||
postgresqlPassword: foo
|
||||
|
||||
redis:
|
||||
cluster:
|
||||
enabled: false
|
||||
master:
|
||||
persistence:
|
||||
enabled: false
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
|
@ -1,2 +1,2 @@
|
||||
"""passbook"""
|
||||
__version__ = '0.5.0-beta'
|
||||
__version__ = '0.6.4-beta'
|
||||
|
@ -7,6 +7,10 @@
|
||||
<div class="container">
|
||||
<h1><span class="pficon-users"></span> {% trans "Users" %}</h1>
|
||||
<hr>
|
||||
<a href="{% url 'passbook_admin:user-create' %}?back={{ request.get_full_path }}" class="btn btn-primary">
|
||||
{% trans 'Create...' %}
|
||||
</a>
|
||||
<hr>
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -61,6 +61,7 @@ urlpatterns = [
|
||||
# Users
|
||||
path('users/', users.UserListView.as_view(),
|
||||
name='users'),
|
||||
path('users/create/', users.UserCreateView.as_view(), name='user-create'),
|
||||
path('users/<int:pk>/update/',
|
||||
users.UserUpdateView.as_view(), name='user-update'),
|
||||
path('users/<int:pk>/delete/',
|
||||
|
@ -3,8 +3,8 @@ from django.core.cache import cache
|
||||
from django.shortcuts import redirect, reverse
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from passbook import __version__
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core import __version__
|
||||
from passbook.core.models import (Application, Factor, Invitation, Policy,
|
||||
Provider, Source, User)
|
||||
from passbook.root.celery import CELERY_APP
|
||||
|
@ -12,7 +12,7 @@ from passbook.admin.forms.policies import PolicyTestForm
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.models import Policy
|
||||
from passbook.lib.utils.reflection import path_to_class
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
from passbook.policies.engine import PolicyEngine
|
||||
|
||||
|
||||
class PolicyListView(AdminRequiredMixin, ListView):
|
||||
|
@ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views import View
|
||||
from django.views.generic import DeleteView, ListView, UpdateView
|
||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||
|
||||
from passbook.admin.forms.users import UserForm
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
@ -19,6 +19,17 @@ class UserListView(AdminRequiredMixin, ListView):
|
||||
template_name = 'administration/user/list.html'
|
||||
|
||||
|
||||
class UserCreateView(SuccessMessageMixin, AdminRequiredMixin, CreateView):
|
||||
"""Create user"""
|
||||
|
||||
model = User
|
||||
form_class = UserForm
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:users')
|
||||
success_message = _('Successfully created User')
|
||||
|
||||
|
||||
class UserUpdateView(SuccessMessageMixin, AdminRequiredMixin, UpdateView):
|
||||
"""Update user"""
|
||||
|
||||
|
BIN
passbook/app_gw/.DS_Store
vendored
BIN
passbook/app_gw/.DS_Store
vendored
Binary file not shown.
@ -1,11 +0,0 @@
|
||||
"""passbook Application Security Gateway app"""
|
||||
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/'
|
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,7 +1,8 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-16 09:13
|
||||
# Generated by Django 2.2.6 on 2019-10-07 14:07
|
||||
|
||||
import uuid
|
||||
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
@ -23,7 +24,7 @@ class Migration(migrations.Migration):
|
||||
('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])),
|
||||
('date', models.DateTimeField(auto_now_add=True)),
|
||||
('app', models.TextField()),
|
||||
('_context', models.TextField()),
|
||||
('context', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
|
||||
('request_ip', models.GenericIPAddressField()),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
@ -33,19 +34,4 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'Audit Entries',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LoginAttempt',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('target_uid', models.CharField(max_length=254)),
|
||||
('request_ip', models.GenericIPAddressField()),
|
||||
('attempts', models.IntegerField(default=1)),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='loginattempt',
|
||||
unique_together={('target_uid', 'request_ip', 'created')},
|
||||
),
|
||||
]
|
||||
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-21 12:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_audit', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='loginattempt',
|
||||
name='created',
|
||||
field=models.DateTimeField(auto_now_add=True),
|
||||
),
|
||||
]
|
@ -1,23 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-21 12:40
|
||||
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_audit', '0002_auto_20190221_1201'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='auditentry',
|
||||
name='_context',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='auditentry',
|
||||
name='context',
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
|
||||
),
|
||||
]
|
@ -1,16 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-03-08 14:53
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_audit', '0003_auto_20190221_1240'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='LoginAttempt',
|
||||
),
|
||||
]
|
@ -1,10 +0,0 @@
|
||||
"""passbook captcha app"""
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PassbookCaptchaFactorConfig(AppConfig):
|
||||
"""passbook captcha app"""
|
||||
|
||||
name = 'passbook.captcha_factor'
|
||||
label = 'passbook_captcha_factor'
|
||||
verbose_name = 'passbook Captcha'
|
@ -1,2 +0,0 @@
|
||||
"""passbook core"""
|
||||
__version__ = '0.2.6-beta'
|
||||
|
@ -1,12 +1,6 @@
|
||||
"""passbook core app config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
class PassbookCoreConfig(AppConfig):
|
||||
"""passbook core app config"""
|
||||
@ -15,13 +9,3 @@ class PassbookCoreConfig(AppConfig):
|
||||
label = 'passbook_core'
|
||||
verbose_name = 'passbook Core'
|
||||
mountpoint = ''
|
||||
|
||||
def ready(self):
|
||||
import_module('passbook.policy.engine')
|
||||
factors_to_load = CONFIG.y('passbook.factors', [])
|
||||
for factors_to_load in factors_to_load:
|
||||
try:
|
||||
import_module(factors_to_load)
|
||||
LOGGER.info("Loaded factor", factor_class=factors_to_load)
|
||||
except ImportError as exc:
|
||||
LOGGER.debug(exc)
|
||||
|
@ -16,7 +16,7 @@ class ApplicationForm(forms.ModelForm):
|
||||
|
||||
model = Application
|
||||
fields = ['name', 'slug', 'launch_url', 'icon_url',
|
||||
'policies', 'provider', 'skip_authorization']
|
||||
'provider', 'policies', 'skip_authorization']
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'launch_url': forms.TextInput(),
|
||||
|
@ -81,13 +81,3 @@ class SignUpForm(forms.Form):
|
||||
if password != password_repeat:
|
||||
raise ValidationError(_("Passwords don't match"))
|
||||
return self.cleaned_data.get('password_repeat')
|
||||
|
||||
|
||||
class PasswordFactorForm(forms.Form):
|
||||
"""Password authentication form"""
|
||||
|
||||
password = forms.CharField(widget=forms.PasswordInput(attrs={
|
||||
'placeholder': _('Password'),
|
||||
'autofocus': 'autofocus',
|
||||
'autocomplete': 'current-password'
|
||||
}))
|
||||
|
@ -3,40 +3,8 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from passbook.core.models import (DebugPolicy, FieldMatcherPolicy,
|
||||
GroupMembershipPolicy, PasswordPolicy,
|
||||
SSOLoginPolicy, WebhookPolicy)
|
||||
|
||||
GENERAL_FIELDS = ['name', 'action', 'negate', 'order', 'timeout']
|
||||
|
||||
class FieldMatcherPolicyForm(forms.ModelForm):
|
||||
"""FieldMatcherPolicy Form"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = FieldMatcherPolicy
|
||||
fields = GENERAL_FIELDS + ['user_field', 'match_action', 'value', ]
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'value': forms.TextInput(),
|
||||
}
|
||||
|
||||
|
||||
class WebhookPolicyForm(forms.ModelForm):
|
||||
"""WebhookPolicyForm Form"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = WebhookPolicy
|
||||
fields = GENERAL_FIELDS + ['url', 'method', 'json_body', 'json_headers',
|
||||
'result_jsonpath', 'result_json_value', ]
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'json_body': forms.TextInput(),
|
||||
'json_headers': forms.TextInput(),
|
||||
'result_jsonpath': forms.TextInput(),
|
||||
'result_json_value': forms.TextInput(),
|
||||
}
|
||||
from passbook.core.models import DebugPolicy
|
||||
from passbook.policies.forms import GENERAL_FIELDS
|
||||
|
||||
|
||||
class DebugPolicyForm(forms.ModelForm):
|
||||
@ -52,49 +20,3 @@ class DebugPolicyForm(forms.ModelForm):
|
||||
labels = {
|
||||
'result': _('Allow user')
|
||||
}
|
||||
|
||||
|
||||
class GroupMembershipPolicyForm(forms.ModelForm):
|
||||
"""GroupMembershipPolicy Form"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = GroupMembershipPolicy
|
||||
fields = GENERAL_FIELDS + ['group', ]
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'order': forms.NumberInput(),
|
||||
}
|
||||
|
||||
class SSOLoginPolicyForm(forms.ModelForm):
|
||||
"""Edit SSOLoginPolicy instances"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = SSOLoginPolicy
|
||||
fields = GENERAL_FIELDS
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'order': forms.NumberInput(),
|
||||
}
|
||||
|
||||
class PasswordPolicyForm(forms.ModelForm):
|
||||
"""PasswordPolicy Form"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = PasswordPolicy
|
||||
fields = GENERAL_FIELDS + ['amount_uppercase', 'amount_lowercase',
|
||||
'amount_symbols', 'length_min', 'symbol_charset',
|
||||
'error_message']
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'symbol_charset': forms.TextInput(),
|
||||
'error_message': forms.TextInput(),
|
||||
}
|
||||
labels = {
|
||||
'amount_uppercase': _('Minimum amount of Uppercase Characters'),
|
||||
'amount_lowercase': _('Minimum amount of Lowercase Characters'),
|
||||
'amount_symbols': _('Minimum amount of Symbols Characters'),
|
||||
'length_min': _('Minimum Length'),
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
"""passbook import_users management command"""
|
||||
from csv import DictReader
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.validators import EmailValidator, ValidationError
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import User
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Run Celery Worker"""
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""celery worker"""
|
||||
autoreload.run_with_reloader(self.celery_worker)
|
||||
|
||||
def celery_worker(self):
|
||||
"""Run celery worker within autoreload"""
|
||||
autoreload.raise_last_exception()
|
||||
CELERY_APP.worker_main(['worker', '--autoscale=10,3', '-E', '-B'])
|
@ -1,21 +1,24 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-16 09:10
|
||||
# Generated by Django 2.2.6 on 2019-10-07 14:06
|
||||
|
||||
import uuid
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import passbook.core.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0009_alter_user_last_name_max_length'),
|
||||
('auth', '0011_update_proxy_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@ -34,6 +37,8 @@ class Migration(migrations.Migration):
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
|
||||
('name', models.TextField()),
|
||||
('password_change_date', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user',
|
||||
@ -44,39 +49,17 @@ class Migration(migrations.Migration):
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Group',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=80, verbose_name='name')),
|
||||
('extra_data', models.TextField(blank=True)),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Invitation',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('expires', models.DateTimeField(blank=True, default=None, null=True)),
|
||||
('fixed_username', models.TextField(blank=True, default=None)),
|
||||
('fixed_email', models.TextField(blank=True, default=None)),
|
||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Invitation',
|
||||
'verbose_name_plural': 'Invitations',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Policy',
|
||||
fields=[
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.TextField(blank=True, null=True)),
|
||||
('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)),
|
||||
('negate', models.BooleanField(default=False)),
|
||||
('order', models.IntegerField(default=0)),
|
||||
('timeout', models.IntegerField(default=30)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
@ -85,28 +68,136 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='PolicyModel',
|
||||
fields=[
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PropertyMapping',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.TextField()),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Property Mapping',
|
||||
'verbose_name_plural': 'Property Mappings',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DebugPolicy',
|
||||
fields=[
|
||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
||||
('result', models.BooleanField(default=False)),
|
||||
('wait_min', models.IntegerField(default=5)),
|
||||
('wait_max', models.IntegerField(default=30)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Debug Policy',
|
||||
'verbose_name_plural': 'Debug Policies',
|
||||
},
|
||||
bases=('passbook_core.policy',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Factor',
|
||||
fields=[
|
||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
||||
('name', models.TextField()),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('order', models.IntegerField()),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('passbook_core.policymodel',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Source',
|
||||
fields=[
|
||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
||||
('name', models.TextField()),
|
||||
('slug', models.SlugField()),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('passbook_core.policymodel',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Provider',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('property_mappings', models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Nonce',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)),
|
||||
('expiring', models.BooleanField(default=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Nonce',
|
||||
'verbose_name_plural': 'Nonces',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Invitation',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('expires', models.DateTimeField(blank=True, default=None, null=True)),
|
||||
('fixed_username', models.TextField(blank=True, default=None)),
|
||||
('fixed_email', models.TextField(blank=True, default=None)),
|
||||
('needs_confirmation', models.BooleanField(default=True)),
|
||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Invitation',
|
||||
'verbose_name_plural': 'Invitations',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Group',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=80, verbose_name='name')),
|
||||
('tags', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('name', 'parent')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='groups',
|
||||
field=models.ManyToManyField(to='passbook_core.Group'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='user_permissions',
|
||||
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserSourceConnection',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('user', 'source')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Application',
|
||||
@ -124,131 +215,9 @@ class Migration(migrations.Migration):
|
||||
},
|
||||
bases=('passbook_core.policymodel',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DebugPolicy',
|
||||
fields=[
|
||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
||||
('result', models.BooleanField(default=False)),
|
||||
('wait_min', models.IntegerField(default=5)),
|
||||
('wait_max', models.IntegerField(default=30)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Debug Policy',
|
||||
'verbose_name_plural': 'Debug Policys',
|
||||
},
|
||||
bases=('passbook_core.policy',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Factor',
|
||||
fields=[
|
||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
||||
('name', models.TextField()),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('order', models.IntegerField()),
|
||||
('type', models.TextField(unique=True)),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('passbook_core.policymodel',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FieldMatcherPolicy',
|
||||
fields=[
|
||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
||||
('user_field', models.TextField(choices=[('username', 'Username'), ('first_name', 'First Name'), ('last_name', 'Last Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')])),
|
||||
('match_action', models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('endswith', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50)),
|
||||
('value', models.TextField()),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Field matcher Policy',
|
||||
'verbose_name_plural': 'Field matcher Policys',
|
||||
},
|
||||
bases=('passbook_core.policy',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PasswordPolicyPolicy',
|
||||
fields=[
|
||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
||||
('amount_uppercase', models.IntegerField(default=0)),
|
||||
('amount_lowercase', models.IntegerField(default=0)),
|
||||
('amount_symbols', models.IntegerField(default=0)),
|
||||
('length_min', models.IntegerField(default=0)),
|
||||
('symbol_charset', models.TextField(default='!\\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Password Policy Policy',
|
||||
'verbose_name_plural': 'Password Policy Policys',
|
||||
},
|
||||
bases=('passbook_core.policy',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Source',
|
||||
fields=[
|
||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
||||
('name', models.TextField()),
|
||||
('slug', models.SlugField()),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('passbook_core.policymodel',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WebhookPolicy',
|
||||
fields=[
|
||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
||||
('url', models.URLField()),
|
||||
('method', models.CharField(choices=[('GET', 'GET'), ('POST', 'POST'), ('PATCH', 'PATCH'), ('DELETE', 'DELETE'), ('PUT', 'PUT')], max_length=10)),
|
||||
('json_body', models.TextField()),
|
||||
('json_headers', models.TextField()),
|
||||
('result_jsonpath', models.TextField()),
|
||||
('result_json_value', models.TextField()),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Webhook Policy',
|
||||
'verbose_name_plural': 'Webhook Policys',
|
||||
},
|
||||
bases=('passbook_core.policy',),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='policymodel',
|
||||
name='policies',
|
||||
field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='groups',
|
||||
field=models.ManyToManyField(to='passbook_core.Group'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='user_permissions',
|
||||
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='usersourceconnection',
|
||||
name='source',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='group',
|
||||
unique_together={('name', 'parent')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='applications',
|
||||
field=models.ManyToManyField(to='passbook_core.Application'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='sources',
|
||||
field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='usersourceconnection',
|
||||
unique_together={('user', 'source')},
|
||||
),
|
||||
]
|
||||
|
@ -1,29 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-16 10:02
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='debugpolicy',
|
||||
options={'verbose_name': 'Debug Policy', 'verbose_name_plural': 'Debug Policies'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='fieldmatcherpolicy',
|
||||
options={'verbose_name': 'Field matcher Policy', 'verbose_name_plural': 'Field matcher Policies'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='passwordpolicypolicy',
|
||||
options={'verbose_name': 'Password Policy Policy', 'verbose_name_plural': 'Password Policy Policies'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='webhookpolicy',
|
||||
options={'verbose_name': 'Webhook Policy', 'verbose_name_plural': 'Webhook Policies'},
|
||||
),
|
||||
]
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 2.2 on 2019-04-18 09:09
|
||||
# Generated by Django 2.2.6 on 2019-10-10 11:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_saml_idp', '0002_samlpropertymapping'),
|
||||
('passbook_core', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='samlprovider',
|
||||
name='audience',
|
||||
model_name='nonce',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, default=''),
|
||||
),
|
||||
]
|
@ -1,17 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-16 10:04
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0002_auto_20190216_1002'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='PasswordPolicyPolicy',
|
||||
new_name='PasswordPolicy',
|
||||
),
|
||||
]
|
@ -1,17 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-16 10:13
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0003_auto_20190216_1004'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='passwordpolicy',
|
||||
options={'verbose_name': 'Password Policy', 'verbose_name_plural': 'Password Policies'},
|
||||
),
|
||||
]
|
@ -1,28 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-21 12:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0004_auto_20190216_1013'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='policy',
|
||||
name='created',
|
||||
field=models.DateTimeField(auto_now_add=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='policymodel',
|
||||
name='created',
|
||||
field=models.DateTimeField(auto_now_add=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='usersourceconnection',
|
||||
name='created',
|
||||
field=models.DateTimeField(auto_now_add=True),
|
||||
),
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-21 12:32
|
||||
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0005_auto_20190221_1201'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='factor',
|
||||
name='arguments',
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(default=dict),
|
||||
),
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-21 12:33
|
||||
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0006_factor_arguments'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='factor',
|
||||
name='arguments',
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-21 15:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0007_auto_20190221_1233'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='fieldmatcherpolicy',
|
||||
name='match_action',
|
||||
field=models.CharField(choices=[('startswith', 'Starts with'), ('endswith', 'Ends with'), ('contains', 'Contains'), ('regexp', 'Regexp'), ('exact', 'Exact')], max_length=50),
|
||||
),
|
||||
]
|
@ -1,44 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-24 09:50
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0008_auto_20190221_1516'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DummyFactor',
|
||||
fields=[
|
||||
('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('passbook_core.factor',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PasswordFactor',
|
||||
fields=[
|
||||
('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')),
|
||||
('backends', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('passbook_core.factor',),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='factor',
|
||||
name='arguments',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='factor',
|
||||
name='type',
|
||||
),
|
||||
]
|
@ -1,21 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-24 10:16
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0009_auto_20190224_0950'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='dummyfactor',
|
||||
options={'verbose_name': 'Dummy Factor', 'verbose_name_plural': 'Dummy Factors'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='passwordfactor',
|
||||
options={'verbose_name': 'Password Factor', 'verbose_name_plural': 'Password Factors'},
|
||||
),
|
||||
]
|
@ -1,25 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-25 14:38
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0010_auto_20190224_1016'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='passwordfactor',
|
||||
name='password_policies',
|
||||
field=models.ManyToManyField(blank=True, to='passbook_core.Policy'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='password_change_date',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -1,31 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-25 19:12
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import passbook.core.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0011_auto_20190225_1438'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Nonce',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Nonce',
|
||||
'verbose_name_plural': 'Nonces',
|
||||
},
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-25 19:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0012_nonce'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='invitation',
|
||||
name='needs_confirmation',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-26 14:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0014_auto_20190226_0850'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='passwordpolicy',
|
||||
name='error_message',
|
||||
field=models.TextField(default=''),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -1,38 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-27 13:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_names(apps, schema_editor):
|
||||
"""migrate first_name and last_name to name"""
|
||||
User = apps.get_model("passbook_core", "User")
|
||||
for user in User.objects.all():
|
||||
user.name = '%s %s' % (user.first_name, user.last_name)
|
||||
user.save()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0015_passwordpolicy_error_message'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='name',
|
||||
field=models.TextField(default=''),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.RunPython(migrate_names),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='name',
|
||||
field=models.TextField(),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='fieldmatcherpolicy',
|
||||
name='user_field',
|
||||
field=models.TextField(choices=[('username', 'Username'), ('name', 'Name'), ('email', 'E-Mail'), ('is_staff', 'Is staff'), ('is_active', 'Is active'), ('data_joined', 'Date joined')]),
|
||||
),
|
||||
]
|
@ -1,26 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-03-08 10:40
|
||||
|
||||
import uuid
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0016_auto_20190227_1355'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PropertyMapping',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.TextField()),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Property Mapping',
|
||||
'verbose_name_plural': 'Property Mappings',
|
||||
},
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-03-08 10:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0017_propertymapping'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='provider',
|
||||
name='property_mappings',
|
||||
field=models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping'),
|
||||
),
|
||||
]
|
@ -1,25 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-03-10 16:15
|
||||
|
||||
import django.contrib.postgres.fields.hstore
|
||||
from django.contrib.postgres.operations import HStoreExtension
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0018_provider_property_mappings'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='group',
|
||||
name='extra_data',
|
||||
),
|
||||
HStoreExtension(),
|
||||
migrations.AddField(
|
||||
model_name='group',
|
||||
name='tags',
|
||||
field=django.contrib.postgres.fields.hstore.HStoreField(default=dict),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-03-21 12:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0020_groupmembershippolicy'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='policy',
|
||||
name='timeout',
|
||||
field=models.IntegerField(default=30),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-04-04 19:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0021_policy_timeout'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='nonce',
|
||||
name='expiring',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
@ -1,17 +0,0 @@
|
||||
# Generated by Django 2.2 on 2019-04-13 15:51
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0022_nonce_expiring'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='applications',
|
||||
),
|
||||
]
|
@ -1,12 +1,12 @@
|
||||
"""passbook core models"""
|
||||
import re
|
||||
from datetime import timedelta
|
||||
from random import SystemRandom
|
||||
from time import sleep
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.contrib.postgres.fields import ArrayField, HStoreField
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.db import models
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.timezone import now
|
||||
@ -16,8 +16,8 @@ from structlog import get_logger
|
||||
|
||||
from passbook.core.signals import password_changed
|
||||
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
|
||||
from passbook.policy.exceptions import PolicyException
|
||||
from passbook.policy.struct import PolicyRequest, PolicyResult
|
||||
from passbook.policies.exceptions import PolicyException
|
||||
from passbook.policies.struct import PolicyRequest, PolicyResult
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -32,10 +32,10 @@ class Group(UUIDModel):
|
||||
name = models.CharField(_('name'), max_length=80)
|
||||
parent = models.ForeignKey('Group', blank=True, null=True,
|
||||
on_delete=models.SET_NULL, related_name='children')
|
||||
tags = HStoreField(default=dict)
|
||||
tags = JSONField(default=dict, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return "Group %s" % self.name
|
||||
return f"Group {self.name}"
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -57,6 +57,7 @@ class User(AbstractUser):
|
||||
self.password_change_date = now()
|
||||
return super().set_password(password)
|
||||
|
||||
|
||||
class Provider(models.Model):
|
||||
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
|
||||
|
||||
@ -70,11 +71,26 @@ class Provider(models.Model):
|
||||
return getattr(self, 'name')
|
||||
return super().__str__()
|
||||
|
||||
|
||||
class PolicyModel(UUIDModel, CreatedUpdatedModel):
|
||||
"""Base model which can have policies applied to it"""
|
||||
|
||||
policies = models.ManyToManyField('Policy', blank=True)
|
||||
|
||||
|
||||
class UserSettings:
|
||||
"""Dataclass for Factor and Source's user_settings"""
|
||||
|
||||
name: str
|
||||
icon: str
|
||||
view_name: str
|
||||
|
||||
def __init__(self, name: str, icon: str, view_name: str):
|
||||
self.name = name
|
||||
self.icon = icon
|
||||
self.view_name = view_name
|
||||
|
||||
|
||||
class Factor(PolicyModel):
|
||||
"""Authentication factor, multiple instances of the same Factor can be used"""
|
||||
|
||||
@ -87,55 +103,14 @@ class Factor(PolicyModel):
|
||||
type = ''
|
||||
form = ''
|
||||
|
||||
def has_user_settings(self):
|
||||
"""Entrypoint to integrate with User settings. Can either return False if no
|
||||
user settings are available, or a tuple or string, string, string where the first string
|
||||
is the name the item has, the second string is the icon and the third is the view-name."""
|
||||
return False
|
||||
def user_settings(self) -> Optional[UserSettings]:
|
||||
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||
user settings are available, or an instanace of UserSettings."""
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return "Factor %s" % self.slug
|
||||
return f"Factor {self.slug}"
|
||||
|
||||
class PasswordFactor(Factor):
|
||||
"""Password-based Django-backend Authentication Factor"""
|
||||
|
||||
backends = ArrayField(models.TextField())
|
||||
password_policies = models.ManyToManyField('Policy', blank=True)
|
||||
|
||||
type = 'passbook.core.auth.factors.password.PasswordFactor'
|
||||
form = 'passbook.core.forms.factors.PasswordFactorForm'
|
||||
|
||||
def has_user_settings(self):
|
||||
return _('Change Password'), 'pficon-key', 'passbook_core:user-change-password'
|
||||
|
||||
def password_passes(self, user: User) -> bool:
|
||||
"""Return true if user's password passes, otherwise False or raise Exception"""
|
||||
for policy in self.policies.all():
|
||||
if not policy.passes(user):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
return "Password Factor %s" % self.slug
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Password Factor')
|
||||
verbose_name_plural = _('Password Factors')
|
||||
|
||||
class DummyFactor(Factor):
|
||||
"""Dummy factor, mostly used to debug"""
|
||||
|
||||
type = 'passbook.core.auth.factors.dummy.DummyFactor'
|
||||
form = 'passbook.core.forms.factors.DummyFactorForm'
|
||||
|
||||
def __str__(self):
|
||||
return "Dummy Factor %s" % self.slug
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Dummy Factor')
|
||||
verbose_name_plural = _('Dummy Factors')
|
||||
|
||||
class Application(PolicyModel):
|
||||
"""Every Application which uses passbook for authentication/identification/authorization
|
||||
@ -161,6 +136,7 @@ class Application(PolicyModel):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Source(PolicyModel):
|
||||
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
|
||||
|
||||
@ -187,15 +163,15 @@ class Source(PolicyModel):
|
||||
"""Return additional Info, such as a callback URL. Show in the administration interface."""
|
||||
return None
|
||||
|
||||
def has_user_settings(self):
|
||||
"""Entrypoint to integrate with User settings. Can either return False if no
|
||||
user settings are available, or a tuple or string, string, string where the first string
|
||||
is the name the item has, the second string is the icon and the third is the view-name."""
|
||||
return False
|
||||
def user_settings(self) -> Optional[UserSettings]:
|
||||
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||
user settings are available, or an instanace of UserSettings."""
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class UserSourceConnection(CreatedUpdatedModel):
|
||||
"""Connection between User and Source."""
|
||||
|
||||
@ -206,6 +182,7 @@ class UserSourceConnection(CreatedUpdatedModel):
|
||||
|
||||
unique_together = (('user', 'source'),)
|
||||
|
||||
|
||||
class Policy(UUIDModel, CreatedUpdatedModel):
|
||||
"""Policies which specify if a user is authorized to use an Application. Can be overridden by
|
||||
other types to add other fields, more logic, etc."""
|
||||
@ -228,148 +205,12 @@ class Policy(UUIDModel, CreatedUpdatedModel):
|
||||
def __str__(self):
|
||||
if self.name:
|
||||
return self.name
|
||||
return "%s action %s" % (self.name, self.action)
|
||||
return f"{self.name} action {self.action}"
|
||||
|
||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||
"""Check if user instance passes this policy"""
|
||||
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, request: PolicyRequest) -> PolicyResult:
|
||||
"""Check if user instance passes this role"""
|
||||
if not hasattr(request.user, self.user_field):
|
||||
raise ValueError("Field does not exist")
|
||||
user_field_value = getattr(request.user, self.user_field, None)
|
||||
LOGGER.debug("Checking field", value=user_field_value,
|
||||
action=self.match_action, should_be=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
|
||||
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, request: PolicyRequest) -> PolicyResult:
|
||||
# Only check if password is being set
|
||||
if not hasattr(request.user, '__password__'):
|
||||
return PolicyResult(True)
|
||||
password = getattr(request.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))
|
||||
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, request: PolicyRequest) -> PolicyResult:
|
||||
"""Call webhook asynchronously and report back"""
|
||||
raise NotImplementedError()
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Webhook Policy')
|
||||
verbose_name_plural = _('Webhook Policies')
|
||||
|
||||
class DebugPolicy(Policy):
|
||||
"""Policy used for debugging the PolicyEngine. Returns a fixed result,
|
||||
@ -393,36 +234,6 @@ class DebugPolicy(Policy):
|
||||
verbose_name = _('Debug Policy')
|
||||
verbose_name_plural = _('Debug Policies')
|
||||
|
||||
class GroupMembershipPolicy(Policy):
|
||||
"""Policy to check if the user is member in a certain group"""
|
||||
|
||||
group = models.ForeignKey('Group', on_delete=models.CASCADE)
|
||||
|
||||
form = 'passbook.core.forms.policies.GroupMembershipPolicyForm'
|
||||
|
||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||
return PolicyResult(self.group.user_set.filter(pk=request.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, request: PolicyRequest) -> PolicyResult:
|
||||
"""Check if user instance passes this policy"""
|
||||
from passbook.core.auth.view import AuthenticationView
|
||||
is_sso_login = request.user.session.get(AuthenticationView.SESSION_IS_SSO_LOGIN, False)
|
||||
return PolicyResult(is_sso_login)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('SSO Login Policy')
|
||||
verbose_name_plural = _('SSO Login Policies')
|
||||
|
||||
class Invitation(UUIDModel):
|
||||
"""Single-use invitation link"""
|
||||
@ -436,31 +247,39 @@ class Invitation(UUIDModel):
|
||||
@property
|
||||
def link(self):
|
||||
"""Get link to use invitation"""
|
||||
return reverse_lazy('passbook_core:auth-sign-up') + '?invitation=%s' % self.uuid
|
||||
return reverse_lazy('passbook_core:auth-sign-up') + f'?invitation={self.uuid.hex}'
|
||||
|
||||
def __str__(self):
|
||||
return "Invitation %s created by %s" % (self.uuid, self.created_by)
|
||||
return f"Invitation {self.uuid.hex} created by {self.created_by}"
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Invitation')
|
||||
verbose_name_plural = _('Invitations')
|
||||
|
||||
|
||||
class Nonce(UUIDModel):
|
||||
"""One-time link for password resets/sign-up-confirmations"""
|
||||
|
||||
expires = models.DateTimeField(default=default_nonce_duration)
|
||||
user = models.ForeignKey('User', on_delete=models.CASCADE)
|
||||
expiring = models.BooleanField(default=True)
|
||||
description = models.TextField(default='', blank=True)
|
||||
|
||||
@property
|
||||
def is_expired(self) -> bool:
|
||||
"""Check if nonce is expired yet."""
|
||||
return now() > self.expires
|
||||
|
||||
def __str__(self):
|
||||
return "Nonce %s (expires=%s)" % (self.uuid.hex, self.expires)
|
||||
return f"Nonce f{self.uuid.hex} {self.description} (expires={self.expires})"
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Nonce')
|
||||
verbose_name_plural = _('Nonces')
|
||||
|
||||
|
||||
class PropertyMapping(UUIDModel):
|
||||
"""User-defined key -> x mapping which can be used by providers to expose extra data."""
|
||||
|
||||
@ -470,7 +289,7 @@ class PropertyMapping(UUIDModel):
|
||||
objects = InheritanceManager()
|
||||
|
||||
def __str__(self):
|
||||
return "Property Mapping %s" % self.name
|
||||
return f"Property Mapping {self.name}"
|
||||
|
||||
class Meta:
|
||||
|
||||
|
@ -5,8 +5,6 @@ from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.exceptions import PasswordPolicyInvalid
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
user_signed_up = Signal(providing_args=['request', 'user'])
|
||||
@ -14,24 +12,9 @@ invitation_created = Signal(providing_args=['request', 'invitation'])
|
||||
invitation_used = Signal(providing_args=['request', 'invitation', 'user'])
|
||||
password_changed = Signal(providing_args=['user', 'password'])
|
||||
|
||||
@receiver(password_changed)
|
||||
# pylint: disable=unused-argument
|
||||
def password_policy_checker(sender, password, **kwargs):
|
||||
"""Run password through all password policies which are applied to the user"""
|
||||
from passbook.core.models import PasswordFactor
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
setattr(sender, '__password__', password)
|
||||
_all_factors = PasswordFactor.objects.filter(enabled=True).order_by('order')
|
||||
for factor in _all_factors:
|
||||
policy_engine = PolicyEngine(factor.password_policies.all().select_subclasses())
|
||||
policy_engine.for_user(sender).build()
|
||||
passing, messages = policy_engine.result
|
||||
if not passing:
|
||||
raise PasswordPolicyInvalid(*messages)
|
||||
|
||||
@receiver(post_save)
|
||||
# pylint: disable=unused-argument
|
||||
def invalidate_policy_cache(sender, instance, **kwargs):
|
||||
def invalidate_policy_cache(sender, instance, **_):
|
||||
"""Invalidate Policy cache when policy is updated"""
|
||||
from passbook.core.models import Policy
|
||||
if isinstance(instance, Policy):
|
||||
|
@ -1,28 +1,15 @@
|
||||
"""passbook core tasks"""
|
||||
from datetime import datetime
|
||||
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import Nonce
|
||||
from passbook.lib.config import CONFIG
|
||||
from passbook.root.celery import CELERY_APP
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@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()
|
||||
def clean_nonces():
|
||||
"""Remove expired nonces"""
|
||||
amount, _ = Nonce.objects.filter(expires__lt=datetime.now(), expiring=True).delete()
|
||||
LOGGER.debug("Deleted expired %d nonces", amount)
|
||||
LOGGER.debug("Deleted expired nonces", amount=amount)
|
||||
|
@ -46,9 +46,6 @@
|
||||
<script src="{% static 'js/passbook.js' %}"></script>
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
<div class="modals">
|
||||
{% include 'partials/about_modal.html' %}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -46,9 +46,6 @@
|
||||
<script src="{% static 'js/passbook.js' %}"></script>
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
<div class="modals">
|
||||
{% include 'partials/about_modal.html' %}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -23,37 +23,18 @@
|
||||
</div>
|
||||
<nav class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav navbar-right navbar-iconic navbar-utility">
|
||||
<li class="dropdown">
|
||||
<button class="btn btn-link dropdown-toggle nav-item-iconic" id="dropdownMenu1" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="true">
|
||||
<span title="Help" class="fa pficon-help"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
{% comment %} <li><a href="#0">Help</a></li> {% endcomment %}
|
||||
<li><a data-toggle="modal" data-target="#about-modal" href="#0">{% trans 'About' %}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<button class="btn btn-link dropdown-toggle nav-item-iconic" id="dropdownMenu2" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="true">
|
||||
<span title="Username" class="fa pficon-user"></span>
|
||||
<span class="dropdown-title">
|
||||
{{ user.username }} <span class="caret"></span>
|
||||
</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
|
||||
<li>
|
||||
<a href="{% url 'passbook_core:user-settings' %}">{% trans 'User Settings' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'passbook_core:user-change-password' %}">{% trans 'Change Password' %}</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<a href="{% url 'passbook_core:auth-logout' %}">{% trans 'Logout' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<a href="{% url 'passbook_core:auth-logout' %}" class="btn btn-link nav-item-iconic" aria-haspopup="true" aria-expanded="true">
|
||||
<span title="Username" class="fa fa-sign-out"></span>
|
||||
<span class="dropdown-title">
|
||||
{% trans 'Logout' %}
|
||||
</span>
|
||||
</a>
|
||||
<a href="{% url 'passbook_core:user-settings' %}" class="btn btn-link nav-item-iconic" aria-haspopup="true" aria-expanded="true">
|
||||
<span title="Username" class="fa pficon-user"></span>
|
||||
<span class="dropdown-title">
|
||||
{{ user.username }}
|
||||
</span>
|
||||
</a>
|
||||
</ul>
|
||||
</nav>
|
||||
</nav>
|
||||
@ -65,122 +46,81 @@
|
||||
<span class="list-group-item-value">{% trans 'Overview' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% is_active_app 'passbook_admin' as is_admin %}
|
||||
{% if user.is_superuser %}
|
||||
<li class="list-group-item {% is_active_app 'passbook_admin' %} secondary-nav-item-pf">
|
||||
<a>
|
||||
<span class="pficon pficon-user" data-toggle="tooltip" title=""
|
||||
data-original-title="{% trans 'Administration' %}"></span>
|
||||
<span class="list-group-item-value dropdown-title">{% trans 'Administration' %}</span>
|
||||
<li class="list-group-item {% is_active_url 'passbook_admin:overview' %}">
|
||||
<a href="{% url 'passbook_admin:overview' %}">
|
||||
<span class="fa pficon-build" data-toggle="tooltip" title="{% trans 'System Status' %}"></span>
|
||||
<span class="list-group-item-value">{% trans 'System Status' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:applications' 'passbook_admin:application-create' 'passbook_admin:application-update' 'passbook_admin:application-delete' %}">
|
||||
<a href="{% url 'passbook_admin:applications' %}">
|
||||
<span class="fa pficon-applications" data-toggle="tooltip" title="{% trans 'Applications' %}"></span>
|
||||
<span class="list-group-item-value">{% trans 'Applications' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:sources' 'passbook_admin:source-create' 'passbook_admin:source-update' 'passbook_admin:source-delete' %}">
|
||||
<a href="{% url 'passbook_admin:sources' %}">
|
||||
<span class="fa pficon-resource-pool" data-toggle="tooltip" title="{% trans 'Sources' %}"></span>
|
||||
<span class="list-group-item-value">{% trans 'Sources' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:providers' 'passbook_admin:provider-create' 'passbook_admin:provider-update' 'passbook_admin:provider-delete' %}">
|
||||
<a href="{% url 'passbook_admin:providers' %}">
|
||||
<span class="fa pficon-integration" data-toggle="tooltip" title="{% trans 'Providers' %}"></span>
|
||||
<span class="list-group-item-value">{% trans 'Providers' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:property-mappings' 'passbook_admin:property-mapping-create' 'passbook_admin:property-mapping-update' 'passbook_admin:property-mapping-delete' %}">
|
||||
<a href="{% url 'passbook_admin:property-mappings' %}">
|
||||
<span class="fa fa-table" data-toggle="tooltip" title="{% trans 'Property Mappings' %}"></span>
|
||||
<span class="list-group-item-value">{% trans 'Property Mappings' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}">
|
||||
<a href="{% url 'passbook_admin:factors' %}">
|
||||
<span class="fa pficon-plugged" data-toggle="tooltip" title="{% trans 'Factors' %}"></span>
|
||||
<span class="list-group-item-value">{% trans 'Factors' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:policies' 'passbook_admin:policy-create' 'passbook_admin:policy-update' 'passbook_admin:policy-delete' 'passbook_admin:policy-test' %}">
|
||||
<a href="{% url 'passbook_admin:policies' %}">
|
||||
<span class="fa pficon-infrastructure" data-toggle="tooltip" title="{% trans 'Policies' %}"></span>
|
||||
<span class="list-group-item-value">{% trans 'Policies' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}">
|
||||
<a href="{% url 'passbook_admin:invitations' %}">
|
||||
<span class="fa pficon-migration" data-toggle="tooltip" title="{% trans 'Invitations' %}"></span>
|
||||
<span class="list-group-item-value">{% trans 'Invitations' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:users' 'passbook_admin:user-update' 'passbook_admin:user-delete' %}">
|
||||
<a href="{% url 'passbook_admin:users' %}">
|
||||
<span class="fa pficon-users" data-toggle="tooltip" title="{% trans 'Users' %}"></span>
|
||||
<span class="list-group-item-value">{% trans 'Users' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:groups' 'passbook_admin:group-update' 'passbook_admin:group-delete' %}">
|
||||
<a href="{% url 'passbook_admin:groups' %}">
|
||||
<span class="fa pficon-users" data-toggle="tooltip" title="{% trans 'Groups' %}"></span>
|
||||
<span class="list-group-item-value">{% trans 'Groups' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item {% is_active 'passbook_admin:audit-log' %}">
|
||||
<a href="{% url 'passbook_admin:audit-log' %}">
|
||||
<span class="fa pficon-catalog" data-toggle="tooltip" title="{% trans 'Audit Log' %}"></span>
|
||||
<span class="list-group-item-value">{% trans 'Audit Log' %}</span>
|
||||
</a>
|
||||
<div id="user-secondary" class="nav-pf-secondary-nav">
|
||||
<div class="nav-item-pf-header">
|
||||
<a href="#0" class="secondary-collapse-toggle-pf" data-toggle="collapse-secondary-nav"></a>
|
||||
<span>{% trans 'Administration' %}</span>
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item {% is_active 'passbook_admin:overview' %}">
|
||||
<a href="{% url 'passbook_admin:overview' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Overview' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:applications' 'passbook_admin:application-create' 'passbook_admin:application-update' 'passbook_admin:application-delete' %}">
|
||||
<a href="{% url 'passbook_admin:applications' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Applications' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:sources' 'passbook_admin:source-create' 'passbook_admin:source-update' 'passbook_admin:source-delete' %}">
|
||||
<a href="{% url 'passbook_admin:sources' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Sources' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:providers' 'passbook_admin:provider-create' 'passbook_admin:provider-update' 'passbook_admin:provider-delete' %}">
|
||||
<a href="{% url 'passbook_admin:providers' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Providers' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:property-mappings' 'passbook_admin:property-mapping-create' 'passbook_admin:property-mapping-update' 'passbook_admin:property-mapping-delete' %}">
|
||||
<a href="{% url 'passbook_admin:property-mappings' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Property Mappings' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}">
|
||||
<a href="{% url 'passbook_admin:factors' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Factors' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:policies' 'passbook_admin:policy-create' 'passbook_admin:policy-update' 'passbook_admin:policy-delete' 'passbook_admin:policy-test' %}">
|
||||
<a href="{% url 'passbook_admin:policies' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Policies' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}">
|
||||
<a href="{% url 'passbook_admin:invitations' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Invitations' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:users' 'passbook_admin:user-update' 'passbook_admin:user-delete' %}">
|
||||
<a href="{% url 'passbook_admin:users' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Users' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="list-group-item {% is_active 'passbook_admin:groups' 'passbook_admin:group-update' 'passbook_admin:group-delete' %}">
|
||||
<a href="{% url 'passbook_admin:groups' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Groups' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item {% is_active 'passbook_admin:audit-log' %}">
|
||||
<a href="{% url 'passbook_admin:audit-log' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Audit Log' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item {% is_active_app 'admin' %}">
|
||||
<a href="{% url 'admin:index' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Django' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item {% is_active 'passbook_admin:debug-request' %}">
|
||||
<a href="{% url 'passbook_admin:debug-request' %}">
|
||||
<span class="list-group-item-value">
|
||||
{% trans 'Debug' %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
@ -1,36 +0,0 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load cache %}
|
||||
|
||||
{% load utils %}
|
||||
|
||||
<div class="modal fade" id="about-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content about-modal-pf">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
|
||||
<span class="pficon pficon-close"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h1>{% trans 'passbook' %}</h1>
|
||||
<div class="product-versions-pf">
|
||||
<ul class="list-unstyled">
|
||||
{% app_versions as vers %}
|
||||
{% cache 600 versions %}
|
||||
{% for app, ver in vers.items %}
|
||||
<li><strong>{{ app }}</strong> {{ ver }}</li>
|
||||
{% endfor %}
|
||||
{% endcache %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="trademark-pf">
|
||||
Trademark and Copyright Information
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<img style="max-height:64px;" src="{% static 'img/logo.png' %}" alt=" Symbol">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -16,23 +16,27 @@
|
||||
<i class="fa pficon-edit"></i> {% trans 'Details' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-divider"></li>
|
||||
{% user_factors as uf %}
|
||||
{% for name, icon, link in uf %}
|
||||
<li class="{% is_active link %}">
|
||||
<a href="{% url link %}">
|
||||
<i class="{{ icon }}"></i> {{ name }}
|
||||
</a>
|
||||
</li>
|
||||
{% if uf %}
|
||||
<li class="nav-divider"></li>
|
||||
{% endif %}
|
||||
{% for user_settings in uf %}
|
||||
<li class="{% is_active user_settings.view_name %}">
|
||||
<a href="{% url user_settings.view_name %}">
|
||||
<i class="{{ user_settings.icon }}"></i> {{ user_settings.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li class="nav-divider"></li>
|
||||
{% user_sources as us %}
|
||||
{% for name, icon, link in us %}
|
||||
<li class="{% if link == request.get_full_path %} active {% endif %}">
|
||||
<a href="{{ link }}">
|
||||
<i class="{{ icon }}"></i> {{ name }}
|
||||
</a>
|
||||
</li>
|
||||
{% if us %}
|
||||
<li class="nav-divider"></li>
|
||||
{% endif %}
|
||||
{% for user_settings in us %}
|
||||
<li class="{% if user_settings.view_name == request.get_full_path %} active {% endif %}">
|
||||
<a href="{{ user_settings.view_name }}">
|
||||
<i class="{{ user_settings.icon }}"></i> {{ user_settings.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -1,36 +1,38 @@
|
||||
"""passbook user settings template tags"""
|
||||
from typing import List
|
||||
|
||||
from django import template
|
||||
from django.template.context import RequestContext
|
||||
|
||||
from passbook.core.models import Factor, Source
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
from passbook.core.models import Factor, Source, UserSettings
|
||||
from passbook.policies.engine import PolicyEngine
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def user_factors(context):
|
||||
def user_factors(context: RequestContext) -> List[UserSettings]:
|
||||
"""Return list of all factors which apply to user"""
|
||||
user = context.get('request').user
|
||||
_all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses()
|
||||
matching_factors = []
|
||||
matching_factors: List[UserSettings] = []
|
||||
for factor in _all_factors:
|
||||
_link = factor.has_user_settings()
|
||||
user_settings = factor.user_settings()
|
||||
policy_engine = PolicyEngine(factor.policies.all())
|
||||
policy_engine.for_user(user).with_request(context.get('request')).build()
|
||||
if policy_engine.passing and _link:
|
||||
matching_factors.append(_link)
|
||||
if policy_engine.passing and user_settings:
|
||||
matching_factors.append(user_settings)
|
||||
return matching_factors
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def user_sources(context):
|
||||
def user_sources(context: RequestContext) -> List[UserSettings]:
|
||||
"""Return a list of all sources which are enabled for the user"""
|
||||
user = context.get('request').user
|
||||
_all_sources = Source.objects.filter(enabled=True).select_subclasses()
|
||||
matching_sources = []
|
||||
matching_sources: List[UserSettings] = []
|
||||
for factor in _all_sources:
|
||||
_link = factor.has_user_settings()
|
||||
user_settings = factor.user_settings()
|
||||
policy_engine = PolicyEngine(factor.policies.all())
|
||||
policy_engine.for_user(user).with_request(context.get('request')).build()
|
||||
if policy_engine.passing and _link:
|
||||
matching_sources.append(_link)
|
||||
if policy_engine.passing and user_settings:
|
||||
matching_sources.append(user_settings)
|
||||
return matching_sources
|
||||
|
@ -2,8 +2,8 @@
|
||||
from django.urls import path
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.auth import view
|
||||
from passbook.core.views import authentication, overview, user
|
||||
from passbook.factors import view
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
"""passbook access helper classes"""
|
||||
from typing import List, Tuple
|
||||
|
||||
from django.contrib import messages
|
||||
from django.http import HttpRequest
|
||||
from django.utils.translation import gettext as _
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import Application
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
from passbook.core.models import Application, Provider, User
|
||||
from passbook.policies.engine import PolicyEngine
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -13,9 +16,9 @@ class AccessMixin:
|
||||
Provider functions to check application access, etc"""
|
||||
|
||||
# request is set by view but since this Mixin has no base class
|
||||
request = None
|
||||
request: HttpRequest = None
|
||||
|
||||
def provider_to_application(self, provider):
|
||||
def provider_to_application(self, provider: Provider) -> Application:
|
||||
"""Lookup application assigned to provider, throw error if no application assigned"""
|
||||
try:
|
||||
return provider.application
|
||||
@ -25,9 +28,9 @@ class AccessMixin:
|
||||
}))
|
||||
raise exc
|
||||
|
||||
def user_has_access(self, application, user):
|
||||
def user_has_access(self, application: Application, user: User) -> Tuple[bool, List[str]]:
|
||||
"""Check if user has access to application."""
|
||||
LOGGER.debug("Checking permissions of %s on application %s...", user, application)
|
||||
LOGGER.debug("Checking permissions", user=user, application=application)
|
||||
policy_engine = PolicyEngine(application.policies.all())
|
||||
policy_engine.for_user(user).with_request(self.request).build()
|
||||
return policy_engine.result
|
||||
|
@ -12,12 +12,11 @@ from django.views import View
|
||||
from django.views.generic import FormView
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.auth.view import AuthenticationView, _redirect_with_qs
|
||||
from passbook.core.exceptions import PasswordPolicyInvalid
|
||||
from passbook.core.forms.authentication import LoginForm, SignUpForm
|
||||
from passbook.core.models import Invitation, Nonce, Source, User
|
||||
from passbook.core.signals import invitation_used, user_signed_up
|
||||
from passbook.core.tasks import send_email
|
||||
from passbook.factors.password.exceptions import PasswordPolicyInvalid
|
||||
from passbook.factors.view import AuthenticationView, _redirect_with_qs
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
LOGGER = get_logger()
|
||||
@ -97,7 +96,7 @@ class SignUpView(UserPassesTestMixin, FormView):
|
||||
template_name = 'login/form.html'
|
||||
form_class = SignUpForm
|
||||
success_url = '.'
|
||||
# Invitation insatnce, if invitation link was used
|
||||
# Invitation instance, if invitation link was used
|
||||
_invitation = None
|
||||
# Instance of newly created user
|
||||
_user = None
|
||||
@ -152,23 +151,23 @@ class SignUpView(UserPassesTestMixin, FormView):
|
||||
for error in exc.messages:
|
||||
errors.append(error)
|
||||
return self.form_invalid(form)
|
||||
needs_confirmation = True
|
||||
if self._invitation and not self._invitation.needs_confirmation:
|
||||
needs_confirmation = False
|
||||
if needs_confirmation:
|
||||
nonce = Nonce.objects.create(user=self._user)
|
||||
LOGGER.debug(str(nonce.uuid))
|
||||
# Send email to user
|
||||
send_email.delay(self._user.email, _('Confirm your account.'),
|
||||
'email/account_confirm.html', {
|
||||
'url': self.request.build_absolute_uri(
|
||||
reverse('passbook_core:auth-sign-up-confirm', kwargs={
|
||||
'nonce': nonce.uuid
|
||||
})
|
||||
)
|
||||
})
|
||||
self._user.is_active = False
|
||||
self._user.save()
|
||||
# needs_confirmation = True
|
||||
# if self._invitation and not self._invitation.needs_confirmation:
|
||||
# needs_confirmation = False
|
||||
# if needs_confirmation:
|
||||
# nonce = Nonce.objects.create(user=self._user)
|
||||
# LOGGER.debug(str(nonce.uuid))
|
||||
# # Send email to user
|
||||
# send_email.delay(self._user.email, _('Confirm your account.'),
|
||||
# 'email/account_confirm.html', {
|
||||
# 'url': self.request.build_absolute_uri(
|
||||
# reverse('passbook_core:auth-sign-up-confirm', kwargs={
|
||||
# 'nonce': nonce.uuid
|
||||
# })
|
||||
# )
|
||||
# })
|
||||
# self._user.is_active = False
|
||||
# self._user.save()
|
||||
self.consume_invitation()
|
||||
messages.success(self.request, _("Successfully signed up!"))
|
||||
LOGGER.debug("Successfully signed up %s",
|
||||
|
@ -4,7 +4,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from passbook.core.models import Application
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
from passbook.policies.engine import PolicyEngine
|
||||
|
||||
|
||||
class OverviewView(LoginRequiredMixin, TemplateView):
|
||||
|
@ -9,8 +9,8 @@ from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import DeleteView, FormView, UpdateView
|
||||
|
||||
from passbook.core.exceptions import PasswordPolicyInvalid
|
||||
from passbook.core.forms.users import PasswordChangeForm, UserDetailForm
|
||||
from passbook.factors.password.exceptions import PasswordPolicyInvalid
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
|
||||
|
@ -1,22 +1,27 @@
|
||||
"""passbook multi-factor authentication engine"""
|
||||
from django.forms import ModelForm
|
||||
from django.http import HttpRequest
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from passbook.core.models import User
|
||||
from passbook.factors.view import AuthenticationView
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
|
||||
class AuthenticationFactor(TemplateView):
|
||||
"""Abstract Authentication factor, inherits TemplateView but can be combined with FormView"""
|
||||
|
||||
form = None
|
||||
required = True
|
||||
authenticator = None
|
||||
pending_user = None
|
||||
request = None
|
||||
form: ModelForm = None
|
||||
required: bool = True
|
||||
authenticator: AuthenticationView
|
||||
pending_user: User
|
||||
request: HttpRequest = None
|
||||
template_name = 'login/form_with_user.html'
|
||||
|
||||
def __init__(self, authenticator):
|
||||
def __init__(self, authenticator: AuthenticationView):
|
||||
self.authenticator = authenticator
|
||||
self.pending_user = None
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['config'] = CONFIG.y('passbook')
|
5
passbook/factors/captcha/admin.py
Normal file
5
passbook/factors/captcha/admin.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""captcha factor admin"""
|
||||
|
||||
from passbook.lib.admin import admin_autoregister
|
||||
|
||||
admin_autoregister('passbook_factors_captcha')
|
10
passbook/factors/captcha/apps.py
Normal file
10
passbook/factors/captcha/apps.py
Normal file
@ -0,0 +1,10 @@
|
||||
"""passbook captcha app"""
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PassbookFactorCaptchaConfig(AppConfig):
|
||||
"""passbook captcha app"""
|
||||
|
||||
name = 'passbook.factors.captcha'
|
||||
label = 'passbook_factors_captcha'
|
||||
verbose_name = 'passbook Factors.Captcha'
|
@ -2,8 +2,8 @@
|
||||
|
||||
from django.views.generic import FormView
|
||||
|
||||
from passbook.captcha_factor.forms import CaptchaForm
|
||||
from passbook.core.auth.factor import AuthenticationFactor
|
||||
from passbook.factors.base import AuthenticationFactor
|
||||
from passbook.factors.captcha.forms import CaptchaForm
|
||||
|
||||
|
||||
class CaptchaFactor(FormView, AuthenticationFactor):
|
||||
@ -16,7 +16,7 @@ class CaptchaFactor(FormView, AuthenticationFactor):
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = CaptchaForm(**self.get_form_kwargs())
|
||||
form.fields['captcha'].public_key = '6Lfi1w8TAAAAAELH-YiWp0OFItmMzvjGmw2xkvUN'
|
||||
form.fields['captcha'].private_key = '6Lfi1w8TAAAAAMQI3f86tGMvd1QkcqqVQyBWI23D'
|
||||
form.fields['captcha'].public_key = self.authenticator.current_factor.public_key
|
||||
form.fields['captcha'].private_key = self.authenticator.current_factor.private_key
|
||||
form.fields['captcha'].widget.attrs["data-sitekey"] = form.fields['captcha'].public_key
|
||||
return form
|
@ -1,9 +1,11 @@
|
||||
"""passbook captcha factor forms"""
|
||||
from captcha.fields import ReCaptchaField
|
||||
from django import forms
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from passbook.captcha_factor.models import CaptchaFactor
|
||||
from passbook.core.forms.factors import GENERAL_FIELDS
|
||||
from passbook.factors.captcha.models import CaptchaFactor
|
||||
from passbook.factors.forms import GENERAL_FIELDS
|
||||
|
||||
|
||||
class CaptchaForm(forms.Form):
|
||||
@ -21,6 +23,7 @@ class CaptchaFactorForm(forms.ModelForm):
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'order': forms.NumberInput(),
|
||||
'policies': FilteredSelectMultiple(_('policies'), False),
|
||||
'public_key': forms.TextInput(),
|
||||
'private_key': forms.TextInput(),
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-24 21:35
|
||||
# Generated by Django 2.2.6 on 2019-10-07 14:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
@ -9,7 +9,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0010_auto_20190224_1016'),
|
||||
('passbook_core', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
@ -11,11 +11,11 @@ class CaptchaFactor(Factor):
|
||||
public_key = models.TextField()
|
||||
private_key = models.TextField()
|
||||
|
||||
type = 'passbook.captcha_factor.factor.CaptchaFactor'
|
||||
form = 'passbook.captcha_factor.forms.CaptchaFactorForm'
|
||||
type = 'passbook.factors.captcha.factor.CaptchaFactor'
|
||||
form = 'passbook.factors.captcha.forms.CaptchaFactorForm'
|
||||
|
||||
def __str__(self):
|
||||
return "Captcha Factor %s" % self.slug
|
||||
return f"Captcha Factor {self.slug}"
|
||||
|
||||
class Meta:
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""passbook captcha_facot settings"""
|
||||
"""passbook captcha_factor settings"""
|
||||
# https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do
|
||||
RECAPTCHA_PUBLIC_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
|
||||
RECAPTCHA_PRIVATE_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'
|
||||
|
||||
NOCAPTCHA = True
|
||||
INSTALLED_APPS = [
|
||||
'captcha'
|
5
passbook/factors/dummy/admin.py
Normal file
5
passbook/factors/dummy/admin.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""dummy factor admin"""
|
||||
|
||||
from passbook.lib.admin import admin_autoregister
|
||||
|
||||
admin_autoregister('passbook_factors_dummy')
|
11
passbook/factors/dummy/apps.py
Normal file
11
passbook/factors/dummy/apps.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""passbook dummy factor config"""
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PassbookFactorDummyConfig(AppConfig):
|
||||
"""passbook dummy factor config"""
|
||||
|
||||
name = 'passbook.factors.dummy'
|
||||
label = 'passbook_factors_dummy'
|
||||
verbose_name = 'passbook Factors.Dummy'
|
@ -1,14 +1,12 @@
|
||||
"""passbook multi-factor authentication engine"""
|
||||
from structlog import get_logger
|
||||
from django.http import HttpRequest
|
||||
|
||||
from passbook.core.auth.factor import AuthenticationFactor
|
||||
|
||||
LOGGER = get_logger()
|
||||
from passbook.factors.base import AuthenticationFactor
|
||||
|
||||
|
||||
class DummyFactor(AuthenticationFactor):
|
||||
"""Dummy factor for testing with multiple factors"""
|
||||
|
||||
def post(self, request):
|
||||
def post(self, request: HttpRequest):
|
||||
"""Just redirect to next factor"""
|
||||
return self.authenticator.user_ok()
|
21
passbook/factors/dummy/forms.py
Normal file
21
passbook/factors/dummy/forms.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""passbook administration forms"""
|
||||
from django import forms
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from passbook.factors.dummy.models import DummyFactor
|
||||
from passbook.factors.forms import GENERAL_FIELDS
|
||||
|
||||
|
||||
class DummyFactorForm(forms.ModelForm):
|
||||
"""Form to create/edit Dummy Factor"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = DummyFactor
|
||||
fields = GENERAL_FIELDS
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'order': forms.NumberInput(),
|
||||
'policies': FilteredSelectMultiple(_('policies'), False)
|
||||
}
|
27
passbook/factors/dummy/migrations/0001_initial.py
Normal file
27
passbook/factors/dummy/migrations/0001_initial.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Generated by Django 2.2.6 on 2019-10-07 14:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0001_initial'),
|
||||
]
|
||||
|
||||
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={
|
||||
'verbose_name': 'Dummy Factor',
|
||||
'verbose_name_plural': 'Dummy Factors',
|
||||
},
|
||||
bases=('passbook_core.factor',),
|
||||
),
|
||||
]
|
19
passbook/factors/dummy/models.py
Normal file
19
passbook/factors/dummy/models.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""dummy factor models"""
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from passbook.core.models import Factor
|
||||
|
||||
|
||||
class DummyFactor(Factor):
|
||||
"""Dummy factor, mostly used to debug"""
|
||||
|
||||
type = 'passbook.factors.dummy.factor.DummyFactor'
|
||||
form = 'passbook.factors.dummy.forms.DummyFactorForm'
|
||||
|
||||
def __str__(self):
|
||||
return f"Dummy Factor {self.slug}"
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Dummy Factor')
|
||||
verbose_name_plural = _('Dummy Factors')
|
5
passbook/factors/email/admin.py
Normal file
5
passbook/factors/email/admin.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""email factor admin"""
|
||||
|
||||
from passbook.lib.admin import admin_autoregister
|
||||
|
||||
admin_autoregister('passbook_factors_email')
|
15
passbook/factors/email/apps.py
Normal file
15
passbook/factors/email/apps.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""passbook email factor config"""
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PassbookFactorEmailConfig(AppConfig):
|
||||
"""passbook email factor config"""
|
||||
|
||||
name = 'passbook.factors.email'
|
||||
label = 'passbook_factors_email'
|
||||
verbose_name = 'passbook Factors.Email'
|
||||
|
||||
def ready(self):
|
||||
import_module('passbook.factors.email.tasks')
|
45
passbook/factors/email/factor.py
Normal file
45
passbook/factors/email/factor.py
Normal file
@ -0,0 +1,45 @@
|
||||
"""passbook multi-factor authentication engine"""
|
||||
from django.contrib import messages
|
||||
from django.http import HttpRequest
|
||||
from django.shortcuts import redirect, reverse
|
||||
from django.utils.translation import gettext as _
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import Nonce
|
||||
from passbook.factors.base import AuthenticationFactor
|
||||
from passbook.factors.email.tasks import send_mails
|
||||
from passbook.factors.email.utils import TemplateEmailMessage
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class EmailFactorView(AuthenticationFactor):
|
||||
"""Dummy factor for testing with multiple factors"""
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['show_password_forget_notice'] = CONFIG.y('passbook.password_reset.enabled')
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
nonce = Nonce.objects.create(user=self.pending_user)
|
||||
LOGGER.debug("DEBUG %s", str(nonce.uuid))
|
||||
# Send mail to user
|
||||
message = TemplateEmailMessage(
|
||||
subject=_('Forgotten password'),
|
||||
template_name='email/account_password_reset.html',
|
||||
template_context={
|
||||
'url': self.request.build_absolute_uri(
|
||||
reverse('passbook_core:auth-password-reset',
|
||||
kwargs={
|
||||
'nonce': nonce.uuid
|
||||
})
|
||||
)})
|
||||
send_mails(self.authenticator.current_factor, message)
|
||||
self.authenticator.cleanup()
|
||||
messages.success(request, _('Check your E-Mails for a password reset link.'))
|
||||
return redirect('passbook_core:auth-login')
|
||||
|
||||
def post(self, request: HttpRequest):
|
||||
"""Just redirect to next factor"""
|
||||
return self.authenticator.user_ok()
|
43
passbook/factors/email/forms.py
Normal file
43
passbook/factors/email/forms.py
Normal file
@ -0,0 +1,43 @@
|
||||
"""passbook administration forms"""
|
||||
from django import forms
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from passbook.factors.email.models import EmailFactor
|
||||
from passbook.factors.forms import GENERAL_FIELDS
|
||||
|
||||
|
||||
class EmailFactorForm(forms.ModelForm):
|
||||
"""Form to create/edit Dummy Factor"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EmailFactor
|
||||
fields = GENERAL_FIELDS + [
|
||||
'host',
|
||||
'port',
|
||||
'username',
|
||||
'password',
|
||||
'use_tls',
|
||||
'use_ssl',
|
||||
'timeout',
|
||||
'from_address',
|
||||
'ssl_keyfile',
|
||||
'ssl_certfile',
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'order': forms.NumberInput(),
|
||||
'policies': FilteredSelectMultiple(_('policies'), False),
|
||||
'host': forms.TextInput(),
|
||||
'username': forms.TextInput(),
|
||||
'password': forms.TextInput(),
|
||||
'ssl_keyfile': forms.TextInput(),
|
||||
'ssl_certfile': forms.TextInput(),
|
||||
}
|
||||
labels = {
|
||||
'use_tls': _('Use TLS'),
|
||||
'use_ssl': _('Use SSL'),
|
||||
'ssl_keyfile': _('SSL Keyfile (optional)'),
|
||||
'ssl_certfile': _('SSL Certfile (optional)'),
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user