Compare commits
27 Commits
version/0.
...
version/0.
| Author | SHA1 | Date | |
|---|---|---|---|
| c38012f147 | |||
| 3676ff21c2 | |||
| 920e705d75 | |||
| de0b137b1e | |||
| d44ac6e2a3 | |||
| 71039a4012 | |||
| 8745ac7932 | |||
| 7f70048423 | |||
| 97dbfc8885 | |||
| 149ea22a93 | |||
| 404ed5406d | |||
| b8656858ec | |||
| 6b0f0e8993 | |||
| aec1ccd88d | |||
| bee5c200b6 | |||
| 9d640efc88 | |||
| f0907841dd | |||
| 2bffc12ef9 | |||
| 2ff9ec6522 | |||
| 43a54f5c54 | |||
| 7bff2734aa | |||
| 84768c0ec6 | |||
| f4499a5459 | |||
| b3aede5bba | |||
| 531ea1c039 | |||
| c2c5ff6912 | |||
| 9cddab8fd5 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.2.8-beta
|
||||
current_version = 0.4.1-beta
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -191,3 +191,4 @@ pip-selfcheck.json
|
||||
# End of https://www.gitignore.io/api/python,django
|
||||
/static/
|
||||
local.env.yml
|
||||
.vscode/
|
||||
|
||||
@ -13,9 +13,12 @@ variables:
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
|
||||
before_script:
|
||||
- pip install pipenv
|
||||
# Ensure all dependencies are installed, even those not included in passbook/dev
|
||||
- pip install -r requirements.txt
|
||||
- pip install -r requirements-dev.txt
|
||||
# According to pipenv docs, -d outputs all packages, however it actually does not
|
||||
- pipenv lock -r > requirements-all.txt
|
||||
- pipenv lock -rd >> requirements-all.txt
|
||||
- pip install -r requirements-all.txt
|
||||
|
||||
create-base-image:
|
||||
image:
|
||||
@ -24,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/Dockerfile.base --destination docker.beryju.org/passbook/base:latest --destination docker.beryju.org/passbook/base:0.2.8-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.4.1-beta
|
||||
stage: build-base-image
|
||||
only:
|
||||
refs:
|
||||
@ -38,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/Dockerfile.dev --destination docker.beryju.org/passbook/dev:latest --destination docker.beryju.org/passbook/dev:0.2.8-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.4.1-beta
|
||||
stage: build-dev-image
|
||||
only:
|
||||
refs:
|
||||
@ -60,20 +63,20 @@ migrations:
|
||||
services:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
prospector:
|
||||
script:
|
||||
- prospector
|
||||
stage: test
|
||||
services:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
pylint:
|
||||
script:
|
||||
- pylint passbook
|
||||
stage: test
|
||||
services:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
# prospector:
|
||||
# script:
|
||||
# - prospector
|
||||
# stage: test
|
||||
# services:
|
||||
# - postgres:latest
|
||||
# - redis:latest
|
||||
# pylint:
|
||||
# script:
|
||||
# - pylint passbook
|
||||
# stage: test
|
||||
# services:
|
||||
# - postgres:latest
|
||||
# - redis:latest
|
||||
coverage:
|
||||
script:
|
||||
- coverage run manage.py test
|
||||
@ -91,7 +94,7 @@ package-passbook-server:
|
||||
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.2.8-beta
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.4.1-beta
|
||||
stage: build
|
||||
only:
|
||||
- tags
|
||||
@ -104,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/Dockerfile.static --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.2.8-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.4.1-beta
|
||||
only:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
@ -121,7 +124,7 @@ package-helm:
|
||||
- curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash
|
||||
script:
|
||||
- helm init --client-only
|
||||
- helm dependency build helm/passbook
|
||||
- helm dependency update helm/passbook
|
||||
- helm package helm/passbook
|
||||
artifacts:
|
||||
paths:
|
||||
|
||||
114
.vscode/.ropeproject/config.py
vendored
114
.vscode/.ropeproject/config.py
vendored
@ -1,114 +0,0 @@
|
||||
# The default ``config.py``
|
||||
# flake8: noqa
|
||||
|
||||
|
||||
def set_prefs(prefs):
|
||||
"""This function is called before opening the project"""
|
||||
|
||||
# Specify which files and folders to ignore in the project.
|
||||
# Changes to ignored resources are not added to the history and
|
||||
# VCSs. Also they are not returned in `Project.get_files()`.
|
||||
# Note that ``?`` and ``*`` match all characters but slashes.
|
||||
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
|
||||
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
|
||||
# '.svn': matches 'pkg/.svn' and all of its children
|
||||
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
|
||||
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
|
||||
prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
|
||||
'.hg', '.svn', '_svn', '.git', '.tox']
|
||||
|
||||
# Specifies which files should be considered python files. It is
|
||||
# useful when you have scripts inside your project. Only files
|
||||
# ending with ``.py`` are considered to be python files by
|
||||
# default.
|
||||
# prefs['python_files'] = ['*.py']
|
||||
|
||||
# Custom source folders: By default rope searches the project
|
||||
# for finding source folders (folders that should be searched
|
||||
# for finding modules). You can add paths to that list. Note
|
||||
# that rope guesses project source folders correctly most of the
|
||||
# time; use this if you have any problems.
|
||||
# The folders should be relative to project root and use '/' for
|
||||
# separating folders regardless of the platform rope is running on.
|
||||
# 'src/my_source_folder' for instance.
|
||||
# prefs.add('source_folders', 'src')
|
||||
|
||||
# You can extend python path for looking up modules
|
||||
# prefs.add('python_path', '~/python/')
|
||||
|
||||
# Should rope save object information or not.
|
||||
prefs['save_objectdb'] = True
|
||||
prefs['compress_objectdb'] = False
|
||||
|
||||
# If `True`, rope analyzes each module when it is being saved.
|
||||
prefs['automatic_soa'] = True
|
||||
# The depth of calls to follow in static object analysis
|
||||
prefs['soa_followed_calls'] = 0
|
||||
|
||||
# If `False` when running modules or unit tests "dynamic object
|
||||
# analysis" is turned off. This makes them much faster.
|
||||
prefs['perform_doa'] = True
|
||||
|
||||
# Rope can check the validity of its object DB when running.
|
||||
prefs['validate_objectdb'] = True
|
||||
|
||||
# How many undos to hold?
|
||||
prefs['max_history_items'] = 32
|
||||
|
||||
# Shows whether to save history across sessions.
|
||||
prefs['save_history'] = True
|
||||
prefs['compress_history'] = False
|
||||
|
||||
# Set the number spaces used for indenting. According to
|
||||
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
|
||||
# unit-tests use 4 spaces it is more reliable, too.
|
||||
prefs['indent_size'] = 4
|
||||
|
||||
# Builtin and c-extension modules that are allowed to be imported
|
||||
# and inspected by rope.
|
||||
prefs['extension_modules'] = []
|
||||
|
||||
# Add all standard c-extensions to extension_modules list.
|
||||
prefs['import_dynload_stdmods'] = True
|
||||
|
||||
# If `True` modules with syntax errors are considered to be empty.
|
||||
# The default value is `False`; When `False` syntax errors raise
|
||||
# `rope.base.exceptions.ModuleSyntaxError` exception.
|
||||
prefs['ignore_syntax_errors'] = False
|
||||
|
||||
# If `True`, rope ignores unresolvable imports. Otherwise, they
|
||||
# appear in the importing namespace.
|
||||
prefs['ignore_bad_imports'] = False
|
||||
|
||||
# If `True`, rope will insert new module imports as
|
||||
# `from <package> import <module>` by default.
|
||||
prefs['prefer_module_from_imports'] = False
|
||||
|
||||
# If `True`, rope will transform a comma list of imports into
|
||||
# multiple separate import statements when organizing
|
||||
# imports.
|
||||
prefs['split_imports'] = False
|
||||
|
||||
# If `True`, rope will remove all top-level import statements and
|
||||
# reinsert them at the top of the module when making changes.
|
||||
prefs['pull_imports_to_top'] = True
|
||||
|
||||
# If `True`, rope will sort imports alphabetically by module name instead
|
||||
# of alphabetically by import statement, with from imports after normal
|
||||
# imports.
|
||||
prefs['sort_imports_alphabetically'] = False
|
||||
|
||||
# Location of implementation of
|
||||
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
|
||||
# case, you don't have to change this value, unless you're an rope expert.
|
||||
# Change this value to inject you own implementations of interfaces
|
||||
# listed in module rope.base.oi.type_hinting.providers.interfaces
|
||||
# For example, you can add you own providers for Django Models, or disable
|
||||
# the search type-hinting in a class hierarchy, etc.
|
||||
prefs['type_hinting_factory'] = (
|
||||
'rope.base.oi.type_hinting.factory.default_type_hinting_factory')
|
||||
|
||||
|
||||
def project_opened(project):
|
||||
"""This function is called after opening the project"""
|
||||
# Do whatever you like here!
|
||||
BIN
.vscode/.ropeproject/objectdb
vendored
BIN
.vscode/.ropeproject/objectdb
vendored
Binary file not shown.
14
.vscode/settings.json
vendored
14
.vscode/settings.json
vendored
@ -1,14 +0,0 @@
|
||||
{
|
||||
"python.pythonPath": "env/bin/python",
|
||||
"editor.tabSize": 4,
|
||||
"[html]": {
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[yml]": {
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"cSpell.words": [
|
||||
"SAML",
|
||||
"passbook"
|
||||
]
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
FROM docker.beryju.org/passbook/base:latest
|
||||
|
||||
COPY ./requirements-dev.txt /app/
|
||||
|
||||
RUN pip install -r /app/requirements-dev.txt --no-cache-dir
|
||||
62
Pipfile
Normal file
62
Pipfile
Normal file
@ -0,0 +1,62 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[packages]
|
||||
asgiref = "*"
|
||||
beautifulsoup4 = "*"
|
||||
celery = "*"
|
||||
channels = "*"
|
||||
cherrypy = "*"
|
||||
colorlog = "*"
|
||||
daphne = "*"
|
||||
defusedxml = "*"
|
||||
django = "*"
|
||||
django-cors-middleware = "*"
|
||||
django-filters = "*"
|
||||
django-ipware = "*"
|
||||
django-model-utils = "*"
|
||||
django-oauth-toolkit = "*"
|
||||
django-oidc-provider = "*"
|
||||
django-otp = "*"
|
||||
django-recaptcha = "*"
|
||||
django-redis = "*"
|
||||
django-rest-framework = "*"
|
||||
django-revproxy = "*"
|
||||
djangorestframework = "==3.9.4"
|
||||
drf-yasg = "*"
|
||||
ldap3 = "*"
|
||||
lxml = "*"
|
||||
markdown = "*"
|
||||
oauthlib = "*"
|
||||
packaging = "*"
|
||||
psycopg2 = "*"
|
||||
pycryptodome = "*"
|
||||
pyyaml = "*"
|
||||
qrcode = "*"
|
||||
requests-oauthlib = "*"
|
||||
sentry-sdk = "*"
|
||||
service_identity = "*"
|
||||
signxml = "*"
|
||||
urllib3 = {extras = ["secure"],version = "*"}
|
||||
websocket_client = "*"
|
||||
structlog = "*"
|
||||
uwsgi = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
|
||||
[dev-packages]
|
||||
astroid = "==2.2.5"
|
||||
coverage = "*"
|
||||
isort = "*"
|
||||
pylint = "==2.3.1"
|
||||
pylint-django = "==2.0.10"
|
||||
prospector = "==1.1.7"
|
||||
django-debug-toolbar = "*"
|
||||
bumpversion = "*"
|
||||
unittest-xml-reporting = "*"
|
||||
autopep8 = "*"
|
||||
bandit = "*"
|
||||
colorama = "*"
|
||||
1332
Pipfile.lock
generated
Normal file
1332
Pipfile.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,15 @@
|
||||
FROM python:3.7-alpine
|
||||
|
||||
COPY ./requirements.txt /app/
|
||||
COPY ./Pipfile /app/
|
||||
COPY ./Pipfile.lock /app/
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
RUN apk update && \
|
||||
apk add --no-cache openssl-dev build-base libxml2-dev libxslt-dev libffi-dev gcc musl-dev libgcc zlib-dev postgresql-dev && \
|
||||
pip install -r /app/requirements.txt --no-cache-dir && \
|
||||
pip install pipenv --no-cache-dir && \
|
||||
pipenv lock -r > requirements.txt && \
|
||||
pipenv --rm && \
|
||||
pip install -r requirements.txt --no-cache-dir && \
|
||||
adduser -S passbook && \
|
||||
chown -R passbook /app
|
||||
4
dev.Dockerfile
Normal file
4
dev.Dockerfile
Normal file
@ -0,0 +1,4 @@
|
||||
FROM docker.beryju.org/passbook/base:latest
|
||||
|
||||
RUN pipenv lock --dev -r > requirements-dev.txt && \
|
||||
pip install -r /app/requirements-dev.txt --no-cache-dir
|
||||
9
helm/passbook/Chart.lock
Normal file
9
helm/passbook/Chart.lock
Normal file
@ -0,0 +1,9 @@
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||
version: 6.3.10
|
||||
- name: redis
|
||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||
version: 9.2.1
|
||||
digest: sha256:bdde250e1401dccdd5161e39c807f9e88b05e3e8e72e74df767a1bbb5fc39a4a
|
||||
generated: "2019-10-01T10:46:06.542706+02:00"
|
||||
@ -1,6 +1,6 @@
|
||||
apiVersion: v1
|
||||
appVersion: "0.2.8-beta"
|
||||
appVersion: "0.4.1-beta"
|
||||
description: A Helm chart for passbook.
|
||||
name: passbook
|
||||
version: "0.2.8-beta"
|
||||
version: "0.4.1-beta"
|
||||
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png
|
||||
|
||||
@ -1 +0,0 @@
|
||||
# passbook
|
||||
Binary file not shown.
BIN
helm/passbook/charts/postgresql-4.2.2.tgz
Normal file
BIN
helm/passbook/charts/postgresql-4.2.2.tgz
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
helm/passbook/charts/redis-9.2.1.tgz
Normal file
BIN
helm/passbook/charts/redis-9.2.1.tgz
Normal file
Binary file not shown.
@ -1,98 +0,0 @@
|
||||
---
|
||||
categories:
|
||||
- Authentication
|
||||
- SSO
|
||||
questions:
|
||||
- default: "true"
|
||||
variable: config.error_reporting
|
||||
type: boolean
|
||||
description: "Enable error-reporting to sentry.services.beryju.org"
|
||||
group: "passbook Configuration"
|
||||
label: "Error Reporting"
|
||||
####################################################################
|
||||
### PostgreSQL
|
||||
####################################################################
|
||||
- variable: postgresql.enabled
|
||||
default: true
|
||||
description: "Deploy a database server as part of this deployment, or set to false and configure an external database connection."
|
||||
type: boolean
|
||||
required: true
|
||||
label: Install PostgreSQL
|
||||
show_subquestion_if: true
|
||||
group: "Database Settings"
|
||||
subquestions:
|
||||
- variable: postgresql.postgresqlDatabase
|
||||
default: "passbook"
|
||||
description: "Database name to create"
|
||||
type: string
|
||||
label: PostgreSQL Database
|
||||
- variable: postgresql.postgresqlUsername
|
||||
default: "passbook"
|
||||
description: "Database user to create"
|
||||
type: string
|
||||
label: PostgreSQL User
|
||||
- variable: postgresql.postgresqlPassword
|
||||
default: ""
|
||||
description: "password will be auto-generated if not specified"
|
||||
type: password
|
||||
label: PostgreSQL Password
|
||||
- variable: externalDatabase.host
|
||||
default: ""
|
||||
description: "Host of the external database"
|
||||
type: string
|
||||
label: External Database Host
|
||||
show_if: "postgresql.enabled=false"
|
||||
group: "Database Settings"
|
||||
- variable: externalDatabase.user
|
||||
default: ""
|
||||
description: "Existing username in the external DB"
|
||||
type: string
|
||||
label: External Database username
|
||||
show_if: "postgresql.enabled=false"
|
||||
group: "Database Settings"
|
||||
- variable: externalDatabase.password
|
||||
default: ""
|
||||
description: "External database password"
|
||||
type: password
|
||||
label: External Database password
|
||||
show_if: "postgresql.enabled=false"
|
||||
group: "Database Settings"
|
||||
- variable: externalDatabase.database
|
||||
default: ""
|
||||
description: "Name of the existing database"
|
||||
type: string
|
||||
label: External Database
|
||||
show_if: "postgresql.enabled=false"
|
||||
group: "Database Settings"
|
||||
- variable: externalDatabase.port
|
||||
default: "3306"
|
||||
description: "External database port number"
|
||||
type: string
|
||||
label: External Database Port
|
||||
show_if: "postgresql.enabled=false"
|
||||
group: "Database Settings"
|
||||
- variable: postgresql.persistence.enabled
|
||||
default: false
|
||||
description: "Enable persistent volume for PostgreSQL"
|
||||
type: boolean
|
||||
required: true
|
||||
label: PostgreSQL Persistent Volume Enabled
|
||||
show_if: "postgresql.enabled=true"
|
||||
show_subquestion_if: true
|
||||
group: "Database Settings"
|
||||
subquestions:
|
||||
- variable: postgresql.master.persistence.size
|
||||
default: "8Gi"
|
||||
description: "PostgreSQL Persistent Volume Size"
|
||||
type: string
|
||||
label: PostgreSQL Volume Size
|
||||
- variable: postgresql.master.persistence.storageClass
|
||||
default: ""
|
||||
description: "If undefined or null, uses the default StorageClass. Default to null"
|
||||
type: storageclass
|
||||
label: Default StorageClass for PostgreSQL
|
||||
- variable: postgresql.master.persistence.existingClaim
|
||||
default: ""
|
||||
description: "If not empty, uses the specified existing PVC instead of creating new one"
|
||||
type: string
|
||||
label: Existing Persistent Volume Claim for PostgreSQL
|
||||
@ -1,12 +1,9 @@
|
||||
dependencies:
|
||||
- name: rabbitmq
|
||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||
version: 4.3.2
|
||||
- name: postgresql
|
||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||
version: 3.10.1
|
||||
version: 4.2.2
|
||||
- name: redis
|
||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||
version: 5.1.0
|
||||
digest: sha256:8bf68bc928a2e3c0f05139635be05fa0840554c7bde4cecd624fac78fb5fa5a3
|
||||
generated: 2019-03-21T11:06:51.553379+01:00
|
||||
version: 9.2.1
|
||||
digest: sha256:8782e974a1094eaeecf1d68f093ca4fb84977217b2bd38b09790a05ec289aec2
|
||||
generated: "2019-10-02T21:03:25.90491153Z"
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
dependencies:
|
||||
- name: rabbitmq
|
||||
version: 4.3.2
|
||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||
- name: postgresql
|
||||
version: 3.10.1
|
||||
version: 4.2.2
|
||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||
- name: redis
|
||||
version: 5.1.0
|
||||
version: 9.2.1
|
||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||
|
||||
@ -32,6 +32,21 @@ spec:
|
||||
- ./manage.py
|
||||
args:
|
||||
- app_gw_web
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "passbook.fullname" . }}-config
|
||||
prefix: PASSBOOK_
|
||||
env:
|
||||
- name: PASSBOOK_REDIS__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-redis"
|
||||
key: redis-password
|
||||
- name: PASSBOOK_POSTGRESQL__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-postgresql"
|
||||
key: postgresql-password
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
|
||||
@ -4,41 +4,16 @@ metadata:
|
||||
name: {{ include "passbook.fullname" . }}-config
|
||||
data:
|
||||
config.yml: |
|
||||
# Env for Docker images
|
||||
databases:
|
||||
default:
|
||||
engine: django.db.backends.postgresql
|
||||
name: {{ .Values.postgresql.postgresqlDatabase }}
|
||||
user: postgres
|
||||
password: {{ .Values.postgresql.postgresqlPassword }}
|
||||
host: {{ .Release.Name }}-postgresql
|
||||
port: ''
|
||||
log:
|
||||
level:
|
||||
console: WARNING
|
||||
file: WARNING
|
||||
file: /dev/null
|
||||
syslog:
|
||||
host: 127.0.0.1
|
||||
port: 514
|
||||
email:
|
||||
host: {{ .Values.config.email.host }}
|
||||
port: 25
|
||||
user: ''
|
||||
password: ''
|
||||
use_tls: false
|
||||
use_ssl: false
|
||||
from: passbook <passbook@domain.tld>
|
||||
web:
|
||||
listen: 0.0.0.0
|
||||
port: 8000
|
||||
threads: 30
|
||||
debug: false
|
||||
secure_proxy_header:
|
||||
HTTP_X_FORWARDED_PROTO: https
|
||||
rabbitmq: "user:{{ .Values.rabbitmq.rabbitmq.password }}@{{ .Release.Name }}-rabbitmq"
|
||||
redis: ":{{ .Values.redis.password }}@{{ .Release.Name }}-redis-master/0"
|
||||
# Error reporting, sends stacktrace to sentry.services.beryju.org
|
||||
postgresql:
|
||||
host: "{{ .Release.Name }}-postgresql"
|
||||
name: "{{ .Values.postgresql.postgresqlDatabase }}"
|
||||
user: postgres
|
||||
redis:
|
||||
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 }}
|
||||
@ -49,10 +24,10 @@ data:
|
||||
|
||||
primary_domain: {{ .Values.primary_domain }}
|
||||
domains:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
- kubernetes-healthcheck-host
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
- kubernetes-healthcheck-host
|
||||
|
||||
passbook:
|
||||
sign_up:
|
||||
|
||||
@ -31,6 +31,21 @@ spec:
|
||||
- ./manage.py
|
||||
args:
|
||||
- migrate
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "passbook.fullname" . }}-config
|
||||
prefix: PASSBOOK_
|
||||
env:
|
||||
- name: PASSBOOK_REDIS__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-redis"
|
||||
key: redis-password
|
||||
- name: PASSBOOK_POSTGRESQL__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-postgresql"
|
||||
key: postgresql-password
|
||||
volumeMounts:
|
||||
- mountPath: /etc/passbook
|
||||
name: config-volume
|
||||
@ -39,9 +54,31 @@ spec:
|
||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- ./manage.py
|
||||
- uwsgi
|
||||
args:
|
||||
- web
|
||||
- --http 0.0.0.0:8000
|
||||
- --wsgi-file passbook/root/wsgi.py
|
||||
- --master
|
||||
- --processes 24
|
||||
- --threads 2
|
||||
- --offload-threads 4
|
||||
- --stats 0.0.0.0:8001
|
||||
- --stats-http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "passbook.fullname" . }}-config
|
||||
prefix: PASSBOOK_
|
||||
env:
|
||||
- name: PASSBOOK_REDIS__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-redis"
|
||||
key: redis-password
|
||||
- name: PASSBOOK_POSTGRESQL__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-postgresql"
|
||||
key: postgresql-password
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
|
||||
@ -32,6 +32,21 @@ spec:
|
||||
- ./manage.py
|
||||
args:
|
||||
- worker
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "passbook.fullname" . }}-config
|
||||
prefix: PASSBOOK_
|
||||
env:
|
||||
- name: PASSBOOK_REDIS__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-redis"
|
||||
key: redis-password
|
||||
- name: PASSBOOK_POSTGRESQL__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: "{{ .Release.Name }}-postgresql"
|
||||
key: postgresql-password
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
tag: 0.2.8-beta
|
||||
tag: 0.4.1-beta
|
||||
|
||||
nameOverride: ""
|
||||
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
"""passbook"""
|
||||
__version__ = '0.2.8-beta'
|
||||
__version__ = '0.4.1-beta'
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
"""passbook admin templatetags"""
|
||||
import inspect
|
||||
from logging import getLogger
|
||||
|
||||
from django import template
|
||||
from django.db.models import Model
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.lib.utils.template import render_to_string
|
||||
|
||||
register = template.Library()
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
@register.simple_tag()
|
||||
def get_links(model_instance):
|
||||
|
||||
@ -11,8 +11,8 @@ from django.views.generic.detail import DetailView
|
||||
from passbook.admin.forms.policies import PolicyTestForm
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.models import Policy
|
||||
from passbook.core.policies import PolicyEngine
|
||||
from passbook.lib.utils.reflection import path_to_class
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
|
||||
|
||||
class PolicyListView(AdminRequiredMixin, ListView):
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
"""passbook app_gw webserver management command"""
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
from daphne.cli import CommandLineInterface
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import autoreload
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
@ -27,7 +27,7 @@ class ApplicationGatewayMiddleware:
|
||||
handler = RequestHandler(app_gw, request)
|
||||
|
||||
if not handler.check_permission():
|
||||
to_url = 'https://%s/?next=%s' % (CONFIG.get('domains')[0], request.get_full_path())
|
||||
to_url = 'https://%s/?next=%s' % (CONFIG.y('domains')[0], request.get_full_path())
|
||||
return RedirectView.as_view(url=to_url)(request)
|
||||
|
||||
return handler.get_response()
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
"""passbook app_gw request handler"""
|
||||
import mimetypes
|
||||
from logging import getLogger
|
||||
from random import SystemRandom
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@ -8,6 +7,7 @@ import certifi
|
||||
import urllib3
|
||||
from django.core.cache import cache
|
||||
from django.utils.http import urlencode
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.app_gw.models import ApplicationGatewayProvider
|
||||
from passbook.app_gw.proxy.exceptions import InvalidUpstream
|
||||
@ -15,11 +15,11 @@ from passbook.app_gw.proxy.response import get_django_response
|
||||
from passbook.app_gw.proxy.rewrite import Rewriter
|
||||
from passbook.app_gw.proxy.utils import encode_items, normalize_request_headers
|
||||
from passbook.core.models import Application
|
||||
from passbook.core.policies import PolicyEngine
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
|
||||
SESSION_UPSTREAM_KEY = 'passbook_app_gw_upstream'
|
||||
IGNORED_HOSTNAMES_KEY = 'passbook_app_gw_ignored'
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
QUOTE_SAFE = r'<.;>\(}*+|~=-$/_:^@)[{]&\'!,"`'
|
||||
ERRORS_MESSAGES = {
|
||||
'upstream-no-scheme': ("Upstream URL scheme must be either "
|
||||
@ -204,8 +204,8 @@ class RequestHandler:
|
||||
def _set_content_type(self, proxy_response):
|
||||
content_type = proxy_response.headers.get('Content-Type')
|
||||
if not content_type:
|
||||
content_type = (mimetypes.guess_type(self.request.path)[0] or
|
||||
self.app_gw.default_content_type)
|
||||
content_type = (mimetypes.guess_type(self.request.path)
|
||||
[0] or self.app_gw.default_content_type)
|
||||
proxy_response.headers['Content-Type'] = content_type
|
||||
# LOGGER.debug("Proxy response CONTENT-TYPE: %s",
|
||||
# proxy_response.headers['Content-Type'])
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
"""response functions from django-revproxy"""
|
||||
import logging
|
||||
|
||||
from django.http import HttpResponse, StreamingHttpResponse
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.app_gw.proxy.utils import (cookie_from_string,
|
||||
set_response_headers, should_stream)
|
||||
@ -9,7 +8,7 @@ from passbook.app_gw.proxy.utils import (cookie_from_string,
|
||||
#: Default number of bytes that are going to be read in a file lecture
|
||||
DEFAULT_AMT = 2 ** 16
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def get_django_response(proxy_response, strict_cookies=False):
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
"""Utils from django-revproxy, slightly adjusted"""
|
||||
import logging
|
||||
import re
|
||||
from wsgiref.util import is_hop_by_hop
|
||||
|
||||
from structlog import get_logger
|
||||
|
||||
try:
|
||||
from http.cookies import SimpleCookie
|
||||
COOKIE_PREFIX = ''
|
||||
@ -155,7 +156,7 @@ def encode_items(items):
|
||||
return encoded
|
||||
|
||||
|
||||
logger = logging.getLogger('revproxy.cookies')
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
def cookie_from_string(cookie_string, strict_cookies=False):
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
"""passbook app_gw cache clean signals"""
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.app_gw.models import ApplicationGatewayProvider
|
||||
from passbook.app_gw.proxy.handler import IGNORED_HOSTNAMES_KEY
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
@receiver(post_save)
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
"""websocket proxy consumer"""
|
||||
import threading
|
||||
from logging import getLogger
|
||||
from ssl import CERT_NONE
|
||||
|
||||
import websocket
|
||||
from channels.generic.websocket import WebsocketConsumer
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.app_gw.models import ApplicationGatewayProvider
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class ProxyConsumer(WebsocketConsumer):
|
||||
"""Proxy websocket connection to upstream"""
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
"""passbook audit models"""
|
||||
from logging import getLogger
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
@ -8,10 +6,11 @@ from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext as _
|
||||
from ipware import get_client_ip
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.lib.models import UUIDModel
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class AuditEntry(UUIDModel):
|
||||
"""An individual audit log entry"""
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
"""passbook core app config"""
|
||||
from importlib import import_module
|
||||
from logging import getLogger
|
||||
|
||||
from django.apps import AppConfig
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class PassbookCoreConfig(AppConfig):
|
||||
"""passbook core app config"""
|
||||
@ -17,7 +17,7 @@ class PassbookCoreConfig(AppConfig):
|
||||
mountpoint = ''
|
||||
|
||||
def ready(self):
|
||||
import_module('passbook.core.policies')
|
||||
import_module('passbook.policy.engine')
|
||||
factors_to_load = CONFIG.y('passbook.factors', [])
|
||||
for factors_to_load in factors_to_load:
|
||||
try:
|
||||
|
||||
@ -19,7 +19,7 @@ class AuthenticationFactor(TemplateView):
|
||||
self.authenticator = authenticator
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['config'] = CONFIG.get('passbook')
|
||||
kwargs['config'] = CONFIG.y('passbook')
|
||||
kwargs['is_login'] = True
|
||||
kwargs['title'] = _('Log in to your account')
|
||||
kwargs['primary_action'] = _('Log in')
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
"""passbook multi-factor authentication engine"""
|
||||
from logging import getLogger
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.auth.factor import AuthenticationFactor
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class DummyFactor(AuthenticationFactor):
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
"""passbook multi-factor authentication engine"""
|
||||
from inspect import Signature
|
||||
from logging import getLogger
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import _clean_credentials
|
||||
@ -10,6 +9,7 @@ from django.forms.utils import ErrorList
|
||||
from django.shortcuts import redirect, reverse
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import FormView
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.auth.factor import AuthenticationFactor
|
||||
from passbook.core.auth.view import AuthenticationView
|
||||
@ -19,7 +19,7 @@ from passbook.core.tasks import send_email
|
||||
from passbook.lib.config import CONFIG
|
||||
from passbook.lib.utils.reflection import path_to_class
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
def authenticate(request, backends, **credentials):
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
"""passbook multi-factor authentication engine"""
|
||||
from logging import getLogger
|
||||
from typing import List, Tuple
|
||||
|
||||
from django.contrib.auth import login
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
from django.shortcuts import get_object_or_404, redirect, reverse
|
||||
from django.utils.http import urlencode
|
||||
from django.views.generic import View
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import Factor, User
|
||||
from passbook.core.policies import PolicyEngine
|
||||
from passbook.core.views.utils import PermissionDeniedView
|
||||
from passbook.lib.utils.reflection import class_to_path, path_to_class
|
||||
from passbook.lib.utils.urls import is_url_absolute
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
def _redirect_with_qs(view, get_query_set=None):
|
||||
"""Wrapper to redirect whilst keeping GET Parameters"""
|
||||
@ -31,12 +32,12 @@ class AuthenticationView(UserPassesTestMixin, View):
|
||||
SESSION_USER_BACKEND = 'passbook_user_backend'
|
||||
SESSION_IS_SSO_LOGIN = 'passbook_sso_login'
|
||||
|
||||
pending_user = None
|
||||
pending_factors = []
|
||||
pending_user: User
|
||||
pending_factors: List[Tuple[str, str]] = []
|
||||
|
||||
_current_factor_class = None
|
||||
_current_factor_class: Factor
|
||||
|
||||
current_factor = None
|
||||
current_factor: Factor
|
||||
|
||||
# Allow only not authenticated users to login
|
||||
def test_func(self):
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
"""passbook core authentication forms"""
|
||||
from logging import getLogger
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import User
|
||||
from passbook.lib.config import CONFIG
|
||||
from passbook.lib.utils.ui import human_list
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class LoginForm(forms.Form):
|
||||
"""Allow users to login"""
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
"""passbook import_users management command"""
|
||||
from csv import DictReader
|
||||
from logging import getLogger
|
||||
|
||||
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 = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Import users from CSV file"""
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
"""passbook Webserver management command"""
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
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 = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@ -17,7 +16,7 @@ class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""passbook cherrypy server"""
|
||||
cherrypy.config.update(CONFIG.get('web'))
|
||||
cherrypy.config.update(CONFIG.y('web'))
|
||||
cherrypy.tree.graft(application, '/')
|
||||
# Mount NullObject to serve static files
|
||||
cherrypy.tree.mount(None, settings.STATIC_URL, config={
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
"""passbook Worker management command"""
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
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 = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
"""passbook core models"""
|
||||
import re
|
||||
from datetime import timedelta
|
||||
from logging import getLogger
|
||||
from random import SystemRandom
|
||||
from time import sleep
|
||||
from typing import Tuple, Union
|
||||
from typing import List
|
||||
from uuid import uuid4
|
||||
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
@ -14,17 +13,33 @@ from django.urls import reverse_lazy
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext as _
|
||||
from model_utils.managers import InheritanceManager
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.policy.exceptions import PolicyException
|
||||
from passbook.core.signals import password_changed
|
||||
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
def default_nonce_duration():
|
||||
"""Default duration a Nonce is valid"""
|
||||
return now() + timedelta(hours=4)
|
||||
|
||||
|
||||
class PolicyResult:
|
||||
"""Small data-class to hold policy results"""
|
||||
|
||||
passing: bool = False
|
||||
messages: List[str] = []
|
||||
|
||||
def __init__(self, passing: bool, *messages: str):
|
||||
self.passing = passing
|
||||
self.messages = messages
|
||||
|
||||
def __str__(self):
|
||||
return f"<PolicyResult passing={self.passing}>"
|
||||
|
||||
class Group(UUIDModel):
|
||||
"""Custom Group model which supports a basic hierarchy"""
|
||||
|
||||
@ -229,9 +244,9 @@ class Policy(UUIDModel, CreatedUpdatedModel):
|
||||
return self.name
|
||||
return "%s action %s" % (self.name, self.action)
|
||||
|
||||
def passes(self, user: User) -> Union[bool, Tuple[bool, str]]:
|
||||
def passes(self, user: User) -> PolicyResult:
|
||||
"""Check if user instance passes this policy"""
|
||||
raise NotImplementedError()
|
||||
raise PolicyException()
|
||||
|
||||
class FieldMatcherPolicy(Policy):
|
||||
"""Policy which checks if a field of the User model matches/doesn't match a
|
||||
@ -273,7 +288,7 @@ class FieldMatcherPolicy(Policy):
|
||||
description = "%s: %s" % (self.name, description)
|
||||
return description
|
||||
|
||||
def passes(self, user: User) -> Union[bool, Tuple[bool, str]]:
|
||||
def passes(self, user: User) -> PolicyResult:
|
||||
"""Check if user instance passes this role"""
|
||||
if not hasattr(user, self.user_field):
|
||||
raise ValueError("Field does not exist")
|
||||
@ -294,7 +309,7 @@ class FieldMatcherPolicy(Policy):
|
||||
passes = user_field_value == self.value
|
||||
|
||||
LOGGER.debug("User got '%r'", passes)
|
||||
return passes
|
||||
return PolicyResult(passes)
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -313,10 +328,10 @@ class PasswordPolicy(Policy):
|
||||
|
||||
form = 'passbook.core.forms.policies.PasswordPolicyForm'
|
||||
|
||||
def passes(self, user: User) -> Union[bool, Tuple[bool, str]]:
|
||||
def passes(self, user: User) -> PolicyResult:
|
||||
# Only check if password is being set
|
||||
if not hasattr(user, '__password__'):
|
||||
return True
|
||||
return PolicyResult(True)
|
||||
password = getattr(user, '__password__')
|
||||
|
||||
filter_regex = r''
|
||||
@ -329,8 +344,8 @@ class PasswordPolicy(Policy):
|
||||
result = bool(re.compile(filter_regex).match(password))
|
||||
LOGGER.debug("User got %r", result)
|
||||
if not result:
|
||||
return result, self.error_message
|
||||
return result
|
||||
return PolicyResult(result, self.error_message)
|
||||
return PolicyResult(result)
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -364,7 +379,7 @@ class WebhookPolicy(Policy):
|
||||
|
||||
form = 'passbook.core.forms.policies.WebhookPolicyForm'
|
||||
|
||||
def passes(self, user: User):
|
||||
def passes(self, user: User) -> PolicyResult:
|
||||
"""Call webhook asynchronously and report back"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@ -383,12 +398,12 @@ class DebugPolicy(Policy):
|
||||
|
||||
form = 'passbook.core.forms.policies.DebugPolicyForm'
|
||||
|
||||
def passes(self, user: User):
|
||||
def passes(self, user: User) -> PolicyResult:
|
||||
"""Wait random time then return result"""
|
||||
wait = SystemRandom().randrange(self.wait_min, self.wait_max)
|
||||
LOGGER.debug("Policy '%s' waiting for %ds", self.name, wait)
|
||||
sleep(wait)
|
||||
return self.result, 'Debugging'
|
||||
return PolicyResult(self.result, 'Debugging')
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -402,8 +417,8 @@ class GroupMembershipPolicy(Policy):
|
||||
|
||||
form = 'passbook.core.forms.policies.GroupMembershipPolicyForm'
|
||||
|
||||
def passes(self, user: User) -> Union[bool, Tuple[bool, str]]:
|
||||
return self.group.user_set.filter(pk=user.pk).exists()
|
||||
def passes(self, user: User) -> PolicyResult:
|
||||
return PolicyResult(self.group.user_set.filter(pk=user.pk).exists())
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -415,10 +430,10 @@ class SSOLoginPolicy(Policy):
|
||||
|
||||
form = 'passbook.core.forms.policies.SSOLoginPolicyForm'
|
||||
|
||||
def passes(self, user):
|
||||
def passes(self, user) -> PolicyResult:
|
||||
"""Check if user instance passes this policy"""
|
||||
from passbook.core.auth.view import AuthenticationView
|
||||
return user.session.get(AuthenticationView.SESSION_IS_SSO_LOGIN, False), ""
|
||||
return PolicyResult(user.session.get(AuthenticationView.SESSION_IS_SSO_LOGIN, False))
|
||||
|
||||
class Meta:
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ http {
|
||||
gzip on;
|
||||
gzip_types application/javascript image/* text/css;
|
||||
gunzip on;
|
||||
add_header X-passbook-Version 0.2.8-beta;
|
||||
add_header X-passbook-Version 0.4.1-beta;
|
||||
add_header Vary X-passbook-Version;
|
||||
root /static/;
|
||||
|
||||
|
||||
@ -1,134 +0,0 @@
|
||||
"""passbook core policy engine"""
|
||||
from logging import getLogger
|
||||
|
||||
from amqp.exceptions import UnexpectedFrame
|
||||
from celery import group
|
||||
from celery.exceptions import TimeoutError as CeleryTimeoutError
|
||||
from django.core.cache import cache
|
||||
from ipware import get_client_ip
|
||||
|
||||
from passbook.core.models import Policy, User
|
||||
from passbook.root.celery import CELERY_APP
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
|
||||
def _cache_key(policy, user):
|
||||
return "policy_%s#%s" % (policy.uuid, user.pk)
|
||||
|
||||
@CELERY_APP.task()
|
||||
def _policy_engine_task(user_pk, policy_pk, **kwargs):
|
||||
"""Task wrapper to run policy checking"""
|
||||
if not user_pk:
|
||||
raise ValueError()
|
||||
policy_obj = Policy.objects.filter(pk=policy_pk).select_subclasses().first()
|
||||
user_obj = User.objects.get(pk=user_pk)
|
||||
for key, value in kwargs.items():
|
||||
setattr(user_obj, key, value)
|
||||
LOGGER.debug("Running policy `%s`#%s for user %s...", policy_obj.name,
|
||||
policy_obj.pk.hex, user_obj)
|
||||
policy_result = policy_obj.passes(user_obj)
|
||||
# Handle policy result correctly if result, message or just result
|
||||
message = None
|
||||
if isinstance(policy_result, (tuple, list)):
|
||||
policy_result, message = policy_result
|
||||
# Invert result if policy.negate is set
|
||||
if policy_obj.negate:
|
||||
policy_result = not policy_result
|
||||
LOGGER.debug("Policy %r#%s got %s", policy_obj.name, policy_obj.pk.hex, policy_result)
|
||||
cache_key = _cache_key(policy_obj, user_obj)
|
||||
cache.set(cache_key, (policy_obj.action, policy_result, message))
|
||||
LOGGER.debug("Cached entry as %s", cache_key)
|
||||
return policy_obj.action, policy_result, message
|
||||
|
||||
class PolicyEngine:
|
||||
"""Orchestrate policy checking, launch tasks and return result"""
|
||||
|
||||
__group = None
|
||||
__cached = None
|
||||
|
||||
policies = None
|
||||
__get_timeout = 0
|
||||
__request = None
|
||||
__user = None
|
||||
|
||||
def __init__(self, policies):
|
||||
self.policies = policies
|
||||
self.__request = None
|
||||
self.__user = None
|
||||
|
||||
def for_user(self, user):
|
||||
"""Check policies for user"""
|
||||
self.__user = user
|
||||
return self
|
||||
|
||||
def with_request(self, request):
|
||||
"""Set request"""
|
||||
self.__request = request
|
||||
return self
|
||||
|
||||
def build(self):
|
||||
"""Build task group"""
|
||||
if not self.__user:
|
||||
raise ValueError("User not set.")
|
||||
signatures = []
|
||||
cached_policies = []
|
||||
kwargs = {
|
||||
'__password__': getattr(self.__user, '__password__', None),
|
||||
'session': dict(getattr(self.__request, 'session', {}).items()),
|
||||
}
|
||||
if self.__request:
|
||||
kwargs['remote_ip'], _ = get_client_ip(self.__request)
|
||||
if not kwargs['remote_ip']:
|
||||
kwargs['remote_ip'] = '255.255.255.255'
|
||||
for policy in self.policies:
|
||||
cached_policy = cache.get(_cache_key(policy, self.__user), None)
|
||||
if cached_policy:
|
||||
LOGGER.debug("Taking result from cache for %s", policy.pk.hex)
|
||||
cached_policies.append(cached_policy)
|
||||
else:
|
||||
LOGGER.debug("Evaluating policy %s", policy.pk.hex)
|
||||
signatures.append(_policy_engine_task.signature(
|
||||
args=(self.__user.pk, policy.pk.hex),
|
||||
kwargs=kwargs,
|
||||
time_limit=policy.timeout))
|
||||
self.__get_timeout += policy.timeout
|
||||
LOGGER.debug("Set total policy timeout to %r", self.__get_timeout)
|
||||
# If all policies are cached, we have an empty list here.
|
||||
if signatures:
|
||||
self.__group = group(signatures)()
|
||||
self.__get_timeout += 3
|
||||
self.__get_timeout = (self.__get_timeout / len(self.policies)) * 1.5
|
||||
self.__cached = cached_policies
|
||||
return self
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
"""Get policy-checking result"""
|
||||
messages = []
|
||||
result = []
|
||||
try:
|
||||
if self.__group:
|
||||
# ValueError can be thrown from _policy_engine_task when user is None
|
||||
result += self.__group.get(timeout=self.__get_timeout)
|
||||
result += self.__cached
|
||||
except ValueError as exc:
|
||||
# ValueError can be thrown from _policy_engine_task when user is None
|
||||
return False, [str(exc)]
|
||||
except UnexpectedFrame as exc:
|
||||
return False, [str(exc)]
|
||||
except CeleryTimeoutError as exc:
|
||||
return False, [str(exc)]
|
||||
for policy_action, policy_result, policy_message in result:
|
||||
passing = (policy_action == Policy.ACTION_ALLOW and policy_result) or \
|
||||
(policy_action == Policy.ACTION_DENY and not policy_result)
|
||||
LOGGER.debug('Action=%s, Result=%r => %r', policy_action, policy_result, passing)
|
||||
if policy_message:
|
||||
messages.append(policy_message)
|
||||
if not passing:
|
||||
return False, messages
|
||||
return True, messages
|
||||
|
||||
@property
|
||||
def passing(self):
|
||||
"""Only get true/false if user passes"""
|
||||
return self.result[0]
|
||||
@ -1,14 +1,13 @@
|
||||
"""passbook core signals"""
|
||||
from logging import getLogger
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.core.signals import Signal
|
||||
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 = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
user_signed_up = Signal(providing_args=['request', 'user'])
|
||||
invitation_created = Signal(providing_args=['request', 'invitation'])
|
||||
@ -20,7 +19,7 @@ password_changed = Signal(providing_args=['user', 'password'])
|
||||
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.core.policies import PolicyEngine
|
||||
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:
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
"""passbook core tasks"""
|
||||
from datetime import datetime
|
||||
from logging import getLogger
|
||||
|
||||
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 = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
@CELERY_APP.task()
|
||||
def send_email(to_address, subject, template, context):
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
from django import template
|
||||
|
||||
from passbook.core.models import Factor, Source
|
||||
from passbook.core.policies import PolicyEngine
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@ -77,7 +77,7 @@ class TestFactorAuthentication(TestCase):
|
||||
request.user = AnonymousUser()
|
||||
middleware = SessionMiddleware()
|
||||
middleware.process_request(request)
|
||||
request.session.save()
|
||||
request.session.save() # pylint: disable=no-member
|
||||
request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk
|
||||
|
||||
response = AuthenticationView.as_view()(request)
|
||||
@ -93,7 +93,7 @@ class TestFactorAuthentication(TestCase):
|
||||
request.user = AnonymousUser()
|
||||
middleware = SessionMiddleware()
|
||||
middleware.process_request(request)
|
||||
request.session.save()
|
||||
request.session.save() # pylint: disable=no-member
|
||||
request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk
|
||||
|
||||
response = AuthenticationView.as_view()(request)
|
||||
@ -111,7 +111,7 @@ class TestFactorAuthentication(TestCase):
|
||||
request.user = AnonymousUser()
|
||||
middleware = SessionMiddleware()
|
||||
middleware.process_request(request)
|
||||
request.session.save()
|
||||
request.session.save() # pylint: disable=no-member
|
||||
request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk
|
||||
|
||||
response = AuthenticationView.as_view()(request)
|
||||
@ -127,7 +127,7 @@ class TestFactorAuthentication(TestCase):
|
||||
middleware.process_request(request)
|
||||
for key, value in session_copy:
|
||||
request.session[key] = value
|
||||
request.session.save()
|
||||
request.session.save() # pylint: disable=no-member
|
||||
response = AuthenticationView.as_view()(request)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, reverse('passbook_core:overview'))
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
"""passbook URL Configuration"""
|
||||
from logging import getLogger
|
||||
|
||||
from django.urls import path
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.auth import view
|
||||
from passbook.core.views import authentication, overview, user
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
urlpatterns = [
|
||||
# Authentication views
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
"""passbook access helper classes"""
|
||||
from logging import getLogger
|
||||
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import gettext as _
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import Application
|
||||
from passbook.core.policies import PolicyEngine
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class AccessMixin:
|
||||
"""Mixin class for usage in Authorization views.
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
"""passbook core authentication views"""
|
||||
from logging import getLogger
|
||||
from typing import Dict
|
||||
|
||||
from django.contrib import messages
|
||||
@ -11,6 +10,7 @@ from django.shortcuts import get_object_or_404, redirect, reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
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
|
||||
@ -20,7 +20,7 @@ from passbook.core.signals import invitation_used, user_signed_up
|
||||
from passbook.core.tasks import send_email
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class LoginView(UserPassesTestMixin, FormView):
|
||||
@ -40,7 +40,7 @@ class LoginView(UserPassesTestMixin, FormView):
|
||||
return redirect(reverse('passbook_core:overview'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['config'] = CONFIG.get('passbook')
|
||||
kwargs['config'] = CONFIG.y('passbook')
|
||||
kwargs['is_login'] = True
|
||||
kwargs['title'] = _('Log in to your account')
|
||||
kwargs['primary_action'] = _('Log in')
|
||||
@ -135,7 +135,7 @@ class SignUpView(UserPassesTestMixin, FormView):
|
||||
return super().get_initial()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['config'] = CONFIG.get('passbook')
|
||||
kwargs['config'] = CONFIG.y('passbook')
|
||||
kwargs['is_login'] = True
|
||||
kwargs['title'] = _('Sign Up')
|
||||
kwargs['primary_action'] = _('Sign up')
|
||||
|
||||
@ -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.core.policies import PolicyEngine
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
|
||||
|
||||
class OverviewView(LoginRequiredMixin, TemplateView):
|
||||
|
||||
@ -66,7 +66,7 @@ class UserChangePasswordView(LoginRequiredMixin, FormView):
|
||||
return redirect('passbook_core:overview')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['config'] = CONFIG.get('passbook')
|
||||
kwargs['config'] = CONFIG.y('passbook')
|
||||
kwargs['is_login'] = True
|
||||
kwargs['title'] = _('Change Password')
|
||||
kwargs['primary_action'] = _('Change')
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
"""passbook HIBP Models"""
|
||||
from hashlib import sha1
|
||||
from logging import getLogger
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext as _
|
||||
from requests import get
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import Policy, User
|
||||
from passbook.core.models import Policy, PolicyResult, User
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class HaveIBeenPwendPolicy(Policy):
|
||||
"""Check if password is on HaveIBeenPwned's list by upload the first
|
||||
@ -18,13 +18,13 @@ class HaveIBeenPwendPolicy(Policy):
|
||||
|
||||
form = 'passbook.hibp_policy.forms.HaveIBeenPwnedPolicyForm'
|
||||
|
||||
def passes(self, user: User) -> bool:
|
||||
def passes(self, user: User) -> PolicyResult:
|
||||
"""Check if password is in HIBP DB. Hashes given Password with SHA1, uses the first 5
|
||||
characters of Password in request and checks if full hash is in response. Returns 0
|
||||
if Password is not in result otherwise the count of how many times it was used."""
|
||||
# Only check if password is being set
|
||||
if not hasattr(user, '__password__'):
|
||||
return True
|
||||
return PolicyResult(True)
|
||||
password = getattr(user, '__password__')
|
||||
pw_hash = sha1(password.encode('utf-8')).hexdigest() # nosec
|
||||
url = 'https://api.pwnedpasswords.com/range/%s' % pw_hash[:5]
|
||||
@ -36,8 +36,9 @@ class HaveIBeenPwendPolicy(Policy):
|
||||
final_count = int(count)
|
||||
LOGGER.debug("Got count %d for hash %s", final_count, pw_hash[:5])
|
||||
if final_count > self.allowed_count:
|
||||
return False, _("Password exists on %(count)d online lists." % {'count': final_count})
|
||||
return True
|
||||
message = _("Password exists on %(count)d online lists." % {'count': final_count})
|
||||
return PolicyResult(False, message)
|
||||
return PolicyResult(True)
|
||||
|
||||
class Meta:
|
||||
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
"""passbook LDAP Authentication Backend"""
|
||||
from logging import getLogger
|
||||
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.ldap.ldap_connector import LDAPConnector
|
||||
from passbook.ldap.models import LDAPSource
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class LDAPBackend(ModelBackend):
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
"""Wrapper for ldap3 to easily manage user"""
|
||||
from logging import getLogger
|
||||
from time import time
|
||||
|
||||
import ldap3
|
||||
import ldap3.core.exceptions
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import User
|
||||
from passbook.ldap.models import LDAPSource
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
USERNAME_FIELD = CONFIG.y('ldap.username_field', 'sAMAccountName')
|
||||
LOGIN_FIELD = CONFIG.y('ldap.login_field', 'userPrincipalName')
|
||||
@ -166,7 +166,7 @@ class LDAPConnector:
|
||||
if not self._source.enabled:
|
||||
return None
|
||||
# FIXME: Adapt user_uid
|
||||
# email = filters.pop(CONFIG.get('passport').get('ldap').get, '')
|
||||
# email = filters.pop(CONFIG.y('passport').get('ldap').get, '')
|
||||
email = filters.pop('email')
|
||||
user_dn = self.lookup(self.generate_filter(**{LOGIN_FIELD: email}))
|
||||
if not user_dn:
|
||||
|
||||
@ -1,37 +1,40 @@
|
||||
"""passbook lib config loader"""
|
||||
"""passbook core config loader"""
|
||||
import os
|
||||
from collections.abc import Mapping
|
||||
from contextlib import contextmanager
|
||||
from glob import glob
|
||||
from logging import getLogger
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import yaml
|
||||
from django.conf import ImproperlyConfigured
|
||||
from django.utils.autoreload import autoreload_started
|
||||
from structlog import get_logger
|
||||
|
||||
SEARCH_PATHS = [
|
||||
'passbook/lib/default.yml',
|
||||
'/etc/passbook/config.yml',
|
||||
'.',
|
||||
'',
|
||||
] + glob('/etc/passbook/config.d/*.yml', recursive=True)
|
||||
LOGGER = getLogger(__name__)
|
||||
ENVIRONMENT = os.getenv('PASSBOOK_ENV', 'local')
|
||||
LOGGER = get_logger()
|
||||
ENV_PREFIX = 'PASSBOOK'
|
||||
ENVIRONMENT = os.getenv(f'{ENV_PREFIX}_ENV', 'local')
|
||||
|
||||
|
||||
class ConfigLoader:
|
||||
"""Search through SEARCH_PATHS and load configuration"""
|
||||
"""Search through SEARCH_PATHS and load configuration. Environment variables starting with
|
||||
`ENV_PREFIX` are also applied.
|
||||
|
||||
A variable like PASSBOOK_POSTGRESQL__HOST would translate to postgresql.host"""
|
||||
|
||||
loaded_file = []
|
||||
|
||||
__config = {}
|
||||
__context_default = None
|
||||
__sub_dicts = []
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
base_dir = os.path.realpath(os.path.join(
|
||||
os.path.dirname(__file__), '../..'))
|
||||
base_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '../..'))
|
||||
for path in SEARCH_PATHS:
|
||||
# Check if path is relative, and if so join with base_dir
|
||||
if not os.path.isabs(path):
|
||||
@ -41,21 +44,13 @@ class ConfigLoader:
|
||||
self.update_from_file(path)
|
||||
elif os.path.isdir(path) and os.path.exists(path):
|
||||
# Path is an existing dir, so we try to read the env config from it
|
||||
env_paths = [os.path.join(path, ENVIRONMENT+'.yml'),
|
||||
os.path.join(path, ENVIRONMENT+'.env.yml')]
|
||||
env_paths = [os.path.join(path, ENVIRONMENT + '.yml'),
|
||||
os.path.join(path, ENVIRONMENT + '.env.yml')]
|
||||
for env_file in env_paths:
|
||||
if os.path.isfile(env_file) and os.path.exists(env_file):
|
||||
# Update config with env file
|
||||
self.update_from_file(env_file)
|
||||
self.handle_secret_key()
|
||||
|
||||
def handle_secret_key(self):
|
||||
"""Handle `secret_key_file`"""
|
||||
if 'secret_key_file' in self.__config:
|
||||
secret_key_file = self.__config.get('secret_key_file')
|
||||
if os.path.isfile(secret_key_file) and os.path.exists(secret_key_file):
|
||||
with open(secret_key_file) as file:
|
||||
self.__config['secret_key'] = file.read().replace('\n', '')
|
||||
self.update_from_env()
|
||||
|
||||
def update(self, root, updatee):
|
||||
"""Recursively update dictionary"""
|
||||
@ -63,16 +58,25 @@ class ConfigLoader:
|
||||
if isinstance(value, Mapping):
|
||||
root[key] = self.update(root.get(key, {}), value)
|
||||
else:
|
||||
if isinstance(value, str):
|
||||
value = self.parse_uri(value)
|
||||
root[key] = value
|
||||
return root
|
||||
|
||||
def parse_uri(self, value):
|
||||
"""Parse string values which start with a URI"""
|
||||
url = urlparse(value)
|
||||
if url.scheme == 'env':
|
||||
value = os.getenv(url.netloc, url.query)
|
||||
return value
|
||||
|
||||
def update_from_file(self, path: str):
|
||||
"""Update config from file contents"""
|
||||
try:
|
||||
with open(path) as file:
|
||||
try:
|
||||
self.update(self.__config, yaml.safe_load(file))
|
||||
LOGGER.debug("Loaded %s", path)
|
||||
LOGGER.debug("Loaded config", file=path)
|
||||
self.loaded_file.append(path)
|
||||
except yaml.YAMLError as exc:
|
||||
raise ImproperlyConfigured from exc
|
||||
@ -83,12 +87,26 @@ class ConfigLoader:
|
||||
"""Update config from dict"""
|
||||
self.__config.update(update)
|
||||
|
||||
@contextmanager
|
||||
def default(self, value: Any):
|
||||
"""Contextmanage that sets default"""
|
||||
self.__context_default = value
|
||||
yield
|
||||
self.__context_default = None
|
||||
def update_from_env(self):
|
||||
"""Check environment variables"""
|
||||
outer = {}
|
||||
idx = 0
|
||||
for key, value in os.environ.items():
|
||||
if not key.startswith(ENV_PREFIX):
|
||||
continue
|
||||
relative_key = key.replace(f"{ENV_PREFIX}_", '').replace('__', '.').lower()
|
||||
# Recursively convert path from a.b.c into outer[a][b][c]
|
||||
current_obj = outer
|
||||
dot_parts = relative_key.split('.')
|
||||
for dot_part in dot_parts[:-1]:
|
||||
if dot_part not in current_obj:
|
||||
current_obj[dot_part] = {}
|
||||
current_obj = current_obj[dot_part]
|
||||
current_obj[dot_parts[-1]] = value
|
||||
idx += 1
|
||||
if idx > 0:
|
||||
LOGGER.debug("Loaded environment variables", count=idx)
|
||||
self.update(self.__config, outer)
|
||||
|
||||
@contextmanager
|
||||
# pylint: disable=invalid-name
|
||||
@ -98,15 +116,6 @@ class ConfigLoader:
|
||||
yield
|
||||
self.__sub_dicts.pop()
|
||||
|
||||
def get(self, key: str, default=None) -> Any:
|
||||
"""Get value from loaded config file"""
|
||||
if default is None:
|
||||
default = self.__context_default
|
||||
config_copy = self.raw
|
||||
for sub in self.__sub_dicts:
|
||||
config_copy = config_copy.get(sub, None)
|
||||
return config_copy.get(key, default)
|
||||
|
||||
@property
|
||||
def raw(self) -> dict:
|
||||
"""Get raw config dictionary"""
|
||||
@ -115,8 +124,6 @@ class ConfigLoader:
|
||||
# pylint: disable=invalid-name
|
||||
def y(self, path: str, default=None, sep='.') -> Any:
|
||||
"""Access attribute by using yaml path"""
|
||||
if default is None:
|
||||
default = self.__context_default
|
||||
# Walk sub_dicts before parsing path
|
||||
root = self.raw
|
||||
for sub in self.__sub_dicts:
|
||||
@ -129,11 +136,14 @@ class ConfigLoader:
|
||||
return default
|
||||
return root
|
||||
|
||||
def y_bool(self, path: str, default=False) -> bool:
|
||||
"""Wrapper for y that converts value into boolean"""
|
||||
return str(self.y(path, default)).lower() == 'true'
|
||||
|
||||
|
||||
CONFIG = ConfigLoader()
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def signal_handler(sender, **kwargs):
|
||||
def signal_handler(sender, **_):
|
||||
"""Add all loaded config files to autoreload watcher"""
|
||||
for path in CONFIG.loaded_file:
|
||||
sender.watch_file(path)
|
||||
|
||||
@ -32,8 +32,7 @@ def reauth_required(view_function):
|
||||
|
||||
if RE_AUTH_KEY not in request.session:
|
||||
# Timestamp not in session, force user to reauth
|
||||
return redirect(reverse('account-reauth') + '?' +
|
||||
urlencode({'next': request.path}))
|
||||
return redirect(reverse('account-reauth') + '?' + urlencode({'next': request.path}))
|
||||
|
||||
if RE_AUTH_KEY in request.session and \
|
||||
request.session[RE_AUTH_KEY] >= (now - RE_AUTH_MARGAIN) and \
|
||||
|
||||
@ -1,46 +1,23 @@
|
||||
# This is the default configuration file
|
||||
databases:
|
||||
default:
|
||||
engine: 'django.db.backends.postgresql'
|
||||
name: passbook
|
||||
user: passbook
|
||||
password: 'EK-5jnKfjrGRm<77'
|
||||
host: localhost
|
||||
log:
|
||||
level:
|
||||
console: DEBUG
|
||||
file: DEBUG
|
||||
file: /dev/null
|
||||
syslog:
|
||||
host: 127.0.0.1
|
||||
port: 514
|
||||
email:
|
||||
postgresql:
|
||||
host: localhost
|
||||
name: passbook
|
||||
user: passbook
|
||||
password: 'env://POSTGRES_PASSWORD'
|
||||
|
||||
redis:
|
||||
host: localhost
|
||||
port: 25
|
||||
user: ''
|
||||
password: ''
|
||||
use_tls: false
|
||||
use_ssl: false
|
||||
from: passbook <passbook@domain.tld>
|
||||
web:
|
||||
server.socket_host: 0.0.0.0
|
||||
server.socket_port: 8000
|
||||
server.thread_pool: 20
|
||||
log.screen: false
|
||||
log.access_file: ''
|
||||
log.error_file: ''
|
||||
cache_db: 0
|
||||
message_queue_db: 1
|
||||
|
||||
debug: false
|
||||
secure_proxy_header:
|
||||
HTTP_X_FORWARDED_PROTO: https
|
||||
rabbitmq: guest:guest@localhost/passbook
|
||||
redis: localhost/0
|
||||
|
||||
# Error reporting, sends stacktrace to sentry.services.beryju.org
|
||||
error_report_enabled: true
|
||||
secret_key: 9$@r!d^1^jrn#fk#1#@ks#9&i$^s#1)_13%$rwjrhd=e8jfi_s
|
||||
|
||||
domains:
|
||||
- passbook.local
|
||||
- passbook.local
|
||||
primary_domain: 'localhost'
|
||||
|
||||
passbook:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"""passbook sentry integration"""
|
||||
from logging import getLogger
|
||||
from structlog import get_logger
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
def before_send(event, hint):
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
"""passbook lib navbar Templatetag"""
|
||||
from logging import getLogger
|
||||
|
||||
from django import template
|
||||
from django.urls import reverse
|
||||
from structlog import get_logger
|
||||
|
||||
register = template.Library()
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
"""passbook Core Reflection templatetags Templatetag"""
|
||||
from logging import getLogger
|
||||
|
||||
from django import template
|
||||
from structlog import get_logger
|
||||
|
||||
register = template.Library()
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
def get_key_unique(context):
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
from django.template import Context, Template, loader
|
||||
|
||||
|
||||
def render_from_string(template: str, ctx: Context) -> str:
|
||||
def render_from_string(tmpl: str, ctx: Context) -> str:
|
||||
"""Render template from string to string"""
|
||||
template = Template(template)
|
||||
template = Template(tmpl)
|
||||
return template.render(ctx)
|
||||
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
"""passbook oauth_client config"""
|
||||
from importlib import import_module
|
||||
from logging import getLogger
|
||||
|
||||
from django.apps import AppConfig
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class PassbookOAuthClientConfig(AppConfig):
|
||||
"""passbook oauth_client config"""
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
"""OAuth Clients"""
|
||||
|
||||
import json
|
||||
from logging import getLogger
|
||||
from urllib.parse import parse_qs, urlencode
|
||||
|
||||
from django.conf import settings
|
||||
@ -10,8 +9,9 @@ from django.utils.encoding import force_text
|
||||
from requests import Session
|
||||
from requests.exceptions import RequestException
|
||||
from requests_oauthlib import OAuth1
|
||||
from structlog import get_logger
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class BaseOAuthClient:
|
||||
@ -120,9 +120,9 @@ class OAuthClient(BaseOAuthClient):
|
||||
"Parse token and secret from raw token response."
|
||||
if raw_token is None:
|
||||
return (None, None)
|
||||
qs = parse_qs(raw_token)
|
||||
token = qs.get('oauth_token', [None])[0]
|
||||
secret = qs.get('oauth_token_secret', [None])[0]
|
||||
query_string = parse_qs(raw_token)
|
||||
token = query_string.get('oauth_token', [None])[0]
|
||||
secret = query_string.get('oauth_token_secret', [None])[0]
|
||||
return (token, secret)
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
@ -217,8 +217,7 @@ class OAuth2Client(BaseOAuthClient):
|
||||
try:
|
||||
token_data = json.loads(raw_token)
|
||||
except ValueError:
|
||||
qs = parse_qs(raw_token)
|
||||
token = qs.get('access_token', [None])[0]
|
||||
token = parse_qs(raw_token).get('access_token', [None])[0]
|
||||
else:
|
||||
token = token_data.get('access_token', None)
|
||||
return (token, None)
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
"""AzureAD OAuth2 Views"""
|
||||
import json
|
||||
import uuid
|
||||
from logging import getLogger
|
||||
|
||||
from requests.exceptions import RequestException
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.oauth_client.clients import OAuth2Client
|
||||
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
||||
from passbook.oauth_client.utils import user_get_or_create
|
||||
from passbook.oauth_client.views.core import OAuthCallback
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class AzureADOAuth2Client(OAuth2Client):
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
"""Discord OAuth Views"""
|
||||
import json
|
||||
from logging import getLogger
|
||||
|
||||
from requests.exceptions import RequestException
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.oauth_client.clients import OAuth2Client
|
||||
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
||||
from passbook.oauth_client.utils import user_get_or_create
|
||||
from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
@MANAGER.source(kind=RequestKind.redirect, name='Discord')
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
"""Source type manager"""
|
||||
from enum import Enum
|
||||
from logging import getLogger
|
||||
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class RequestKind(Enum):
|
||||
"""Enum of OAuth Request types"""
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
"""Reddit OAuth Views"""
|
||||
import json
|
||||
from logging import getLogger
|
||||
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from requests.exceptions import RequestException
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.oauth_client.clients import OAuth2Client
|
||||
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
||||
from passbook.oauth_client.utils import user_get_or_create
|
||||
from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
@MANAGER.source(kind=RequestKind.redirect, name='reddit')
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
"""Supervisr OAuth2 Views"""
|
||||
|
||||
import json
|
||||
from logging import getLogger
|
||||
|
||||
from requests.exceptions import RequestException
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.oauth_client.clients import OAuth2Client
|
||||
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
||||
from passbook.oauth_client.utils import user_get_or_create
|
||||
from passbook.oauth_client.views.core import OAuthCallback
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class SupervisrOAuth2Client(OAuth2Client):
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
"""Twitter OAuth Views"""
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
from requests.exceptions import RequestException
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.oauth_client.clients import OAuthClient
|
||||
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
||||
from passbook.oauth_client.utils import user_get_or_create
|
||||
from passbook.oauth_client.views.core import OAuthCallback
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class TwitterOAuthClient(OAuthClient):
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
"""Core OAauth Views"""
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import authenticate
|
||||
@ -11,13 +9,14 @@ from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import RedirectView, View
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.auth.view import AuthenticationView, _redirect_with_qs
|
||||
from passbook.lib.utils.reflection import app
|
||||
from passbook.oauth_client.clients import get_client
|
||||
from passbook.oauth_client.models import OAuthSource, UserOAuthSourceConnection
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
"""passbook OAuth2 Views"""
|
||||
from logging import getLogger
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.contrib import messages
|
||||
@ -7,6 +6,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import get_object_or_404, redirect, reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from oauth2_provider.views.base import AuthorizationView
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.audit.models import AuditEntry
|
||||
from passbook.core.models import Application
|
||||
@ -14,7 +14,7 @@ from passbook.core.views.access import AccessMixin
|
||||
from passbook.core.views.utils import LoadingView, PermissionDeniedView
|
||||
from passbook.oauth_provider.models import OAuth2Provider
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class PassbookAuthorizationLoadingView(LoginRequiredMixin, LoadingView):
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
"""passbook auth oidc provider app config"""
|
||||
from logging import getLogger
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.db.utils import InternalError, OperationalError, ProgrammingError
|
||||
from django.urls import include, path
|
||||
from structlog import get_logger
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class PassbookOIDCProviderConfig(AppConfig):
|
||||
"""passbook auth oidc provider app config"""
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
"""OIDC Permission checking"""
|
||||
from logging import getLogger
|
||||
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import Application
|
||||
from passbook.core.policies import PolicyEngine
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
def check_permissions(request, user, client):
|
||||
"""Check permissions, used for
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
"""OTP Factor logic"""
|
||||
from logging import getLogger
|
||||
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import FormView
|
||||
from django_otp import match_token, user_has_device
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.auth.factor import AuthenticationFactor
|
||||
from passbook.otp.forms import OTPVerifyForm
|
||||
from passbook.otp.views import OTP_SETTING_UP_KEY, EnableView
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class OTPFactor(FormView, AuthenticationFactor):
|
||||
"""OTP Factor View"""
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
"""passbook OTP Views"""
|
||||
from base64 import b32encode
|
||||
from binascii import unhexlify
|
||||
from logging import getLogger
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
@ -15,6 +14,7 @@ from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
|
||||
from django_otp.plugins.otp_totp.models import TOTPDevice
|
||||
from qrcode import make
|
||||
from qrcode.image.svg import SvgPathImage
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.lib.boilerplate import NeverCacheMixin
|
||||
from passbook.lib.config import CONFIG
|
||||
@ -23,7 +23,7 @@ from passbook.otp.utils import otpauth_url
|
||||
|
||||
OTP_SESSION_KEY = 'passbook_otp_key'
|
||||
OTP_SETTING_UP_KEY = 'passbook_otp_setup'
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class UserSettingsView(LoginRequiredMixin, TemplateView):
|
||||
"""View for user settings to control OTP"""
|
||||
@ -75,7 +75,7 @@ class EnableView(LoginRequiredMixin, FormView):
|
||||
|
||||
# TODO: Check if OTP Factor exists and applies to user
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['config'] = CONFIG.get('passbook')
|
||||
kwargs['config'] = CONFIG.y('passbook')
|
||||
kwargs['is_login'] = True
|
||||
kwargs['title'] = _('Configue OTP')
|
||||
kwargs['primary_action'] = _('Setup')
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
"""passbook password_expiry_policy Models"""
|
||||
from datetime import timedelta
|
||||
from logging import getLogger
|
||||
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext as _
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import Policy, User
|
||||
from passbook.core.models import Policy, PolicyResult, User
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class PasswordExpiryPolicy(Policy):
|
||||
@ -20,7 +20,7 @@ class PasswordExpiryPolicy(Policy):
|
||||
|
||||
form = 'passbook.password_expiry_policy.forms.PasswordExpiryPolicyForm'
|
||||
|
||||
def passes(self, user: User) -> bool:
|
||||
def passes(self, user: User) -> PolicyResult:
|
||||
"""If password change date is more than x days in the past, call set_unusable_password
|
||||
and show a notice"""
|
||||
actual_days = (now() - user.password_change_date).days
|
||||
@ -29,12 +29,13 @@ class PasswordExpiryPolicy(Policy):
|
||||
if not self.deny_only:
|
||||
user.set_unusable_password()
|
||||
user.save()
|
||||
return False, _(('Password expired %(days)d days ago. '
|
||||
'Please update your password.') % {
|
||||
'days': days_since_expiry
|
||||
})
|
||||
return False, _('Password has expired.')
|
||||
return True
|
||||
message = _(('Password expired %(days)d days ago. '
|
||||
'Please update your password.') % {
|
||||
'days': days_since_expiry
|
||||
})
|
||||
return PolicyResult(False, message)
|
||||
return PolicyResult(False, _('Password has expired.'))
|
||||
return PolicyResult(True)
|
||||
|
||||
class Meta:
|
||||
|
||||
|
||||
0
passbook/policy/__init__.py
Normal file
0
passbook/policy/__init__.py
Normal file
100
passbook/policy/engine.py
Normal file
100
passbook/policy/engine.py
Normal file
@ -0,0 +1,100 @@
|
||||
"""passbook policy engine"""
|
||||
from multiprocessing import Pipe
|
||||
from multiprocessing.connection import Connection
|
||||
from typing import List, Tuple
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.http import HttpRequest
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import Policy, PolicyResult, User
|
||||
from passbook.policy.task import PolicyTask
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
def _cache_key(policy, user):
|
||||
return "policy_%s#%s" % (policy.uuid, user.pk)
|
||||
|
||||
class PolicyEngine:
|
||||
"""Orchestrate policy checking, launch tasks and return result"""
|
||||
|
||||
# __group = None
|
||||
# __cached = None
|
||||
|
||||
policies: List[Policy] = []
|
||||
__request: HttpRequest
|
||||
__user: User
|
||||
|
||||
__proc_list: List[Tuple[Connection, PolicyTask]] = []
|
||||
|
||||
def __init__(self, policies, user: User = None, request: HttpRequest = None):
|
||||
self.policies = policies
|
||||
self.__request = request
|
||||
self.__user = user
|
||||
|
||||
def for_user(self, user: User) -> 'PolicyEngine':
|
||||
"""Check policies for user"""
|
||||
self.__user = user
|
||||
return self
|
||||
|
||||
def with_request(self, request: HttpRequest) -> 'PolicyEngine':
|
||||
"""Set request"""
|
||||
self.__request = request
|
||||
return self
|
||||
|
||||
def build(self) -> 'PolicyEngine':
|
||||
"""Build task group"""
|
||||
if not self.__user:
|
||||
raise ValueError("User not set.")
|
||||
cached_policies = []
|
||||
kwargs = {
|
||||
'__password__': getattr(self.__user, '__password__', None),
|
||||
'session': dict(getattr(self.__request, 'session', {}).items()),
|
||||
'request': self.__request,
|
||||
}
|
||||
for policy in self.policies:
|
||||
cached_policy = cache.get(_cache_key(policy, self.__user), None)
|
||||
if cached_policy:
|
||||
LOGGER.debug("Taking result from cache for %s", policy.pk.hex)
|
||||
cached_policies.append(cached_policy)
|
||||
else:
|
||||
LOGGER.debug("Looking up real class of policy...")
|
||||
# TODO: Rewrite this to lookup all policies at once
|
||||
policy = Policy.objects.get_subclass(pk=policy.id)
|
||||
LOGGER.debug("Evaluating policy %s", policy.pk.hex)
|
||||
our_end, task_end = Pipe(False)
|
||||
task = PolicyTask()
|
||||
task.ret = task_end
|
||||
task.user = self.__user
|
||||
task.policy = policy
|
||||
task.params = kwargs
|
||||
LOGGER.debug("Starting Process %s", task.__class__.__name__)
|
||||
task.start()
|
||||
self.__proc_list.append((our_end, task))
|
||||
# If all policies are cached, we have an empty list here.
|
||||
if self.__proc_list:
|
||||
for _, running_proc in self.__proc_list:
|
||||
running_proc.join()
|
||||
return self
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
"""Get policy-checking result"""
|
||||
results: List[PolicyResult] = []
|
||||
messages: List[str] = []
|
||||
for our_end, _ in self.__proc_list:
|
||||
results.append(our_end.recv())
|
||||
for policy_result in results:
|
||||
# passing = (policy_action == Policy.ACTION_ALLOW and policy_result) or \
|
||||
# (policy_action == Policy.ACTION_DENY and not policy_result)
|
||||
LOGGER.debug('Result=%r => %r', policy_result, policy_result.passing)
|
||||
if policy_result.messages:
|
||||
messages += policy_result.messages
|
||||
if not policy_result.passing:
|
||||
return False, messages
|
||||
return True, messages
|
||||
|
||||
@property
|
||||
def passing(self):
|
||||
"""Only get true/false if user passes"""
|
||||
return self.result[0]
|
||||
4
passbook/policy/exceptions.py
Normal file
4
passbook/policy/exceptions.py
Normal file
@ -0,0 +1,4 @@
|
||||
"""policy exceptions"""
|
||||
|
||||
class PolicyException(Exception):
|
||||
"""Exception that should be raised during Policy Evaluation, and can be recovered from."""
|
||||
44
passbook/policy/task.py
Normal file
44
passbook/policy/task.py
Normal file
@ -0,0 +1,44 @@
|
||||
"""passbook policy task"""
|
||||
from multiprocessing import Process
|
||||
from multiprocessing.connection import Connection
|
||||
from typing import Any, Dict
|
||||
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import Policy, User, PolicyResult
|
||||
from passbook.policy.exceptions import PolicyException
|
||||
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
def _cache_key(policy, user):
|
||||
return "policy_%s#%s" % (policy.uuid, user.pk)
|
||||
|
||||
class PolicyTask(Process):
|
||||
"""Evaluate a single policy within a seprate process"""
|
||||
|
||||
ret: Connection
|
||||
user: User
|
||||
policy: Policy
|
||||
params: Dict[str, Any]
|
||||
|
||||
def run(self):
|
||||
"""Task wrapper to run policy checking"""
|
||||
for key, value in self.params.items():
|
||||
setattr(self.user, key, value)
|
||||
LOGGER.debug("Running policy `%s`#%s for user %s...", self.policy.name,
|
||||
self.policy.pk.hex, self.user)
|
||||
try:
|
||||
policy_result = self.policy.passes(self.user)
|
||||
except PolicyException as exc:
|
||||
LOGGER.debug(exc)
|
||||
policy_result = PolicyResult(False, str(exc))
|
||||
# Invert result if policy.negate is set
|
||||
if self.policy.negate:
|
||||
policy_result = not policy_result
|
||||
LOGGER.debug("Policy %r#%s got %s", self.policy.name, self.policy.pk.hex, policy_result)
|
||||
# cache_key = _cache_key(self.policy, self.user)
|
||||
# cache.set(cache_key, (self.policy.action, policy_result, message))
|
||||
# LOGGER.debug("Cached entry as %s", cache_key)
|
||||
self.ret.send(policy_result)
|
||||
self.ret.close()
|
||||
@ -1,15 +1,15 @@
|
||||
"""passbook core celery"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from logging.config import dictConfig
|
||||
|
||||
from celery import Celery, signals
|
||||
from django.conf import settings
|
||||
from structlog import get_logger
|
||||
|
||||
# set the default Django settings module for the 'celery' program.
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings")
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
CELERY_APP = Celery('passbook')
|
||||
@ -19,7 +19,7 @@ CELERY_APP = Celery('passbook')
|
||||
@signals.setup_logging.connect
|
||||
def config_loggers(*args, **kwags):
|
||||
"""Apply logging settings from settings.py to celery"""
|
||||
logging.config.dictConfig(settings.LOGGING)
|
||||
dictConfig(settings.LOGGING)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
@ -11,23 +11,18 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from celery.schedules import crontab
|
||||
from django.contrib import messages
|
||||
import structlog
|
||||
from sentry_sdk import init as sentry_init
|
||||
from sentry_sdk.integrations.celery import CeleryIntegration
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||
|
||||
from passbook import __version__
|
||||
from passbook.lib.config import CONFIG
|
||||
from passbook.lib.sentry import before_send
|
||||
|
||||
VERSION = __version__
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
STATIC_ROOT = BASE_DIR + '/static'
|
||||
@ -36,12 +31,13 @@ STATIC_ROOT = BASE_DIR + '/static'
|
||||
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = CONFIG.get('secret_key')
|
||||
SECRET_KEY = CONFIG.y('secret_key',
|
||||
"9$@r!d^1^jrn#fk#1#@ks#9&i$^s#1)_13%$rwjrhd=e8jfi_s") # noqa Debug
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = CONFIG.get('debug')
|
||||
DEBUG = CONFIG.y_bool('debug')
|
||||
INTERNAL_IPS = ['127.0.0.1']
|
||||
# ALLOWED_HOSTS = CONFIG.get('domains', []) + [CONFIG.get('primary_domain')]
|
||||
# ALLOWED_HOSTS = CONFIG.y('domains', []) + [CONFIG.y('primary_domain')]
|
||||
ALLOWED_HOSTS = ['*']
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
|
||||
@ -53,7 +49,7 @@ AUTH_USER_MODEL = 'passbook_core.User'
|
||||
|
||||
CSRF_COOKIE_NAME = 'passbook_csrf'
|
||||
SESSION_COOKIE_NAME = 'passbook_session'
|
||||
SESSION_COOKIE_DOMAIN = CONFIG.get('primary_domain')
|
||||
SESSION_COOKIE_DOMAIN = CONFIG.y('primary_domain')
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
SESSION_CACHE_ALIAS = "default"
|
||||
LANGUAGE_COOKIE_NAME = 'passbook_language'
|
||||
@ -72,8 +68,8 @@ INSTALLED_APPS = [
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.postgres',
|
||||
'rest_framework',
|
||||
'drf_yasg',
|
||||
# 'rest_framework',
|
||||
# 'drf_yasg',
|
||||
'passbook.core.apps.PassbookCoreConfig',
|
||||
'passbook.admin.apps.PassbookAdminConfig',
|
||||
'passbook.api.apps.PassbookAPIConfig',
|
||||
@ -93,16 +89,6 @@ INSTALLED_APPS = [
|
||||
'passbook.app_gw.apps.PassbookApplicationApplicationGatewayConfig',
|
||||
]
|
||||
|
||||
# Message Tag fix for bootstrap CSS Classes
|
||||
MESSAGE_TAGS = {
|
||||
messages.DEBUG: 'primary',
|
||||
messages.INFO: 'info',
|
||||
messages.SUCCESS: 'success',
|
||||
messages.WARNING: 'warning',
|
||||
messages.ERROR: 'danger',
|
||||
}
|
||||
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
# Use Django's standard `django.contrib.auth` permissions,
|
||||
# or allow read-only access for unauthenticated users.
|
||||
@ -114,17 +100,21 @@ REST_FRAMEWORK = {
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": "redis://%s" % CONFIG.get('redis'),
|
||||
"LOCATION": (f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:6379"
|
||||
f"/{CONFIG.y('redis.cache_db')}"),
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
}
|
||||
}
|
||||
}
|
||||
DJANGO_REDIS_IGNORE_EXCEPTIONS = True
|
||||
DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
SESSION_CACHE_ALIAS = "default"
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'passbook.app_gw.middleware.ApplicationGatewayMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
@ -150,22 +140,20 @@ TEMPLATES = [
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'passbook.core.wsgi.application'
|
||||
|
||||
WSGI_APPLICATION = 'passbook.root.wsgi.application'
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
|
||||
|
||||
DATABASES = {}
|
||||
for db_alias, db_config in CONFIG.get('databases').items():
|
||||
DATABASES[db_alias] = {
|
||||
'ENGINE': db_config.get('engine'),
|
||||
'HOST': db_config.get('host'),
|
||||
'NAME': db_config.get('name'),
|
||||
'USER': db_config.get('user'),
|
||||
'PASSWORD': db_config.get('password'),
|
||||
'OPTIONS': db_config.get('options', {}),
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'HOST': CONFIG.y('postgresql.host'),
|
||||
'NAME': CONFIG.y('postgresql.name'),
|
||||
'USER': CONFIG.y('postgresql.user'),
|
||||
'PASSWORD': CONFIG.y('postgresql.password'),
|
||||
}
|
||||
}
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
|
||||
@ -203,20 +191,13 @@ USE_TZ = True
|
||||
# Celery settings
|
||||
# Add a 10 minute timeout to all Celery tasks.
|
||||
CELERY_TASK_SOFT_TIME_LIMIT = 600
|
||||
CELERY_TIMEZONE = TIME_ZONE
|
||||
CELERY_BEAT_SCHEDULE = {}
|
||||
CELERY_CREATE_MISSING_QUEUES = True
|
||||
CELERY_TASK_DEFAULT_QUEUE = 'passbook'
|
||||
CELERY_BROKER_URL = 'amqp://%s' % CONFIG.get('rabbitmq')
|
||||
CELERY_RESULT_BACKEND = 'rpc://'
|
||||
CELERY_ACKS_LATE = True
|
||||
CELERY_BROKER_HEARTBEAT = 0
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
'cleanup-expired-nonces': {
|
||||
'task': 'passbook.core.tasks.clean_nonces',
|
||||
'schedule': crontab(hour=1, minute=1)
|
||||
}
|
||||
}
|
||||
CELERY_BROKER_URL = (f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}"
|
||||
f":6379/{CONFIG.y('redis.message_queue_db')}")
|
||||
CELERY_RESULT_BACKEND = (f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}"
|
||||
f":6379/{CONFIG.y('redis.message_queue_db')}")
|
||||
|
||||
|
||||
if not DEBUG:
|
||||
@ -224,11 +205,7 @@ if not DEBUG:
|
||||
dsn="https://33cdbcb23f8b436dbe0ee06847410b67@sentry.beryju.org/3",
|
||||
integrations=[
|
||||
DjangoIntegration(),
|
||||
CeleryIntegration(),
|
||||
LoggingIntegration(
|
||||
level=logging.INFO,
|
||||
event_level=logging.ERROR
|
||||
)
|
||||
CeleryIntegration()
|
||||
],
|
||||
send_default_pii=True,
|
||||
before_send=before_send,
|
||||
@ -240,96 +217,77 @@ if not DEBUG:
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
|
||||
structlog.configure_once(
|
||||
processors=[
|
||||
structlog.stdlib.add_log_level,
|
||||
structlog.stdlib.PositionalArgumentsFormatter(),
|
||||
structlog.processors.TimeStamper(),
|
||||
structlog.processors.StackInfoRenderer(),
|
||||
# structlog.processors.format_exc_info,
|
||||
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
|
||||
],
|
||||
context_class=structlog.threadlocal.wrap_dict(dict),
|
||||
logger_factory=structlog.stdlib.LoggerFactory(),
|
||||
wrapper_class=structlog.stdlib.BoundLogger,
|
||||
cache_logger_on_first_use=True,
|
||||
)
|
||||
|
||||
LOG_PRE_CHAIN = [
|
||||
# Add the log level and a timestamp to the event_dict if the log entry
|
||||
# is not from structlog.
|
||||
structlog.stdlib.add_log_level,
|
||||
structlog.processors.TimeStamper(),
|
||||
]
|
||||
|
||||
with CONFIG.cd('log'):
|
||||
LOGGING_HANDLER_MAP = {
|
||||
'passbook': 'DEBUG',
|
||||
'django': 'WARNING',
|
||||
'celery': 'WARNING',
|
||||
'grpc': 'DEBUG',
|
||||
'oauthlib': 'DEBUG',
|
||||
'oauth2_provider': 'DEBUG',
|
||||
'daphne': 'INFO',
|
||||
}
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': True,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': ('%(asctime)s %(levelname)-8s %(name)-55s '
|
||||
'%(funcName)-20s %(message)s'),
|
||||
"plain": {
|
||||
"()": structlog.stdlib.ProcessorFormatter,
|
||||
"processor": structlog.processors.JSONRenderer(),
|
||||
"foreign_pre_chain": LOG_PRE_CHAIN,
|
||||
},
|
||||
"colored": {
|
||||
"()": structlog.stdlib.ProcessorFormatter,
|
||||
"processor": structlog.dev.ConsoleRenderer(colors=DEBUG),
|
||||
"foreign_pre_chain": LOG_PRE_CHAIN,
|
||||
},
|
||||
'color': {
|
||||
'()': 'colorlog.ColoredFormatter',
|
||||
'format': ('%(log_color)s%(asctime)s %(levelname)-8s %(name)-55s '
|
||||
'%(funcName)-20s %(message)s'),
|
||||
'log_colors': {
|
||||
'DEBUG': 'bold_black',
|
||||
'INFO': 'white',
|
||||
'WARNING': 'yellow',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'bold_red',
|
||||
'SUCCESS': 'green',
|
||||
},
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': CONFIG.get('level').get('console'),
|
||||
'level': DEBUG,
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'color',
|
||||
},
|
||||
'syslog': {
|
||||
'level': CONFIG.get('level').get('file'),
|
||||
'class': 'logging.handlers.SysLogHandler',
|
||||
'formatter': 'verbose',
|
||||
'address': (CONFIG.get('syslog').get('host'),
|
||||
CONFIG.get('syslog').get('port'))
|
||||
},
|
||||
'file': {
|
||||
'level': CONFIG.get('level').get('file'),
|
||||
'class': 'logging.FileHandler',
|
||||
'formatter': 'verbose',
|
||||
'filename': CONFIG.get('file'),
|
||||
'formatter': "colored" if DEBUG else "plain",
|
||||
},
|
||||
'queue': {
|
||||
'level': CONFIG.get('level').get('console'),
|
||||
'level': DEBUG,
|
||||
'class': 'passbook.lib.log.QueueListenerHandler',
|
||||
'handlers': [
|
||||
'cfg://handlers.console',
|
||||
# 'cfg://handlers.syslog',
|
||||
'cfg://handlers.file',
|
||||
],
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'passbook': {
|
||||
'handlers': ['queue'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
'django': {
|
||||
'handlers': ['queue'],
|
||||
'level': 'INFO',
|
||||
'propagate': True,
|
||||
},
|
||||
'tasks': {
|
||||
'handlers': ['queue'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
'cherrypy': {
|
||||
'handlers': ['queue'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
'oauthlib': {
|
||||
'handlers': ['queue'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
'oauth2_provider': {
|
||||
'handlers': ['queue'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
'daphne': {
|
||||
'handlers': ['queue'],
|
||||
'level': 'INFO',
|
||||
'propagate': True,
|
||||
}
|
||||
}
|
||||
}
|
||||
for handler_name, level in LOGGING_HANDLER_MAP.items():
|
||||
LOGGING['loggers'][handler_name] = {
|
||||
'handlers': ['console'],
|
||||
'level': level,
|
||||
'propagate': True,
|
||||
}
|
||||
|
||||
TEST = False
|
||||
TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
|
||||
@ -342,6 +300,7 @@ if any('test' in arg for arg in sys.argv):
|
||||
TEST = True
|
||||
CELERY_TASK_ALWAYS_EAGER = True
|
||||
|
||||
|
||||
_DISALLOWED_ITEMS = ['INSTALLED_APPS', 'MIDDLEWARE', 'AUTHENTICATION_BACKENDS']
|
||||
# Load subapps's INSTALLED_APPS
|
||||
for _app in INSTALLED_APPS:
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
"""passbook URL Configuration"""
|
||||
from logging import getLogger
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from django.views.generic import RedirectView
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.views import error
|
||||
from passbook.lib.utils.reflection import get_apps
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
admin.autodiscover()
|
||||
admin.site.login = RedirectView.as_view(pattern_name='passbook_core:auth-login')
|
||||
|
||||
|
||||
@ -12,6 +12,6 @@ import os
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'passbook.settings')
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'passbook.root.settings')
|
||||
|
||||
application = SentryWsgiMiddleware(get_wsgi_application())
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
"""passbook mod saml_idp app config"""
|
||||
from importlib import import_module
|
||||
from logging import getLogger
|
||||
|
||||
from django.apps import AppConfig
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.lib.config import CONFIG
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
class PassbookSAMLIDPConfig(AppConfig):
|
||||
"""passbook saml_idp app config"""
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
|
||||
import time
|
||||
import uuid
|
||||
from logging import getLogger
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.saml_idp import exceptions, utils, xml_render
|
||||
|
||||
@ -65,7 +65,7 @@ class Processor:
|
||||
def __init__(self, remote):
|
||||
self.name = remote.name
|
||||
self._remote = remote
|
||||
self._logger = getLogger(__name__)
|
||||
self._logger = get_logger(__name__)
|
||||
self._system_params['ISSUER'] = self._remote.issuer
|
||||
self._logger.debug('processor configured')
|
||||
|
||||
@ -260,7 +260,6 @@ class Processor:
|
||||
def _validate_user(self):
|
||||
"""Validates the User. Sub-classes should override this and
|
||||
throw an CannotHandleAssertion Exception if the validation does not succeed."""
|
||||
pass
|
||||
|
||||
def can_handle(self, request):
|
||||
"""Returns true if this processor can handle this request."""
|
||||
|
||||
@ -3,9 +3,7 @@
|
||||
|
||||
class CannotHandleAssertion(Exception):
|
||||
"""This processor does not handle this assertion."""
|
||||
pass
|
||||
|
||||
|
||||
class UserNotAuthorized(Exception):
|
||||
"""User not authorized for SAML 2.0 authentication."""
|
||||
pass
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
"""passbook saml_idp Models"""
|
||||
from logging import getLogger
|
||||
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db import models
|
||||
from django.shortcuts import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import PropertyMapping, Provider
|
||||
from passbook.lib.utils.reflection import class_to_path, path_to_class
|
||||
from passbook.saml_idp.base import Processor
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
class SAMLProvider(Provider):
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
"""passbook SAML IDP Views"""
|
||||
from logging import getLogger
|
||||
|
||||
from django.contrib.auth import logout
|
||||
from django.contrib.auth.mixins import AccessMixin
|
||||
from django.core.exceptions import ValidationError
|
||||
@ -13,16 +11,17 @@ from django.utils.translation import gettext as _
|
||||
from django.views import View
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from signxml.util import strip_pem_header
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.audit.models import AuditEntry
|
||||
from passbook.core.models import Application
|
||||
from passbook.core.policies import PolicyEngine
|
||||
from passbook.lib.mixins import CSRFExemptMixin
|
||||
from passbook.lib.utils.template import render_to_string
|
||||
from passbook.policy.engine import PolicyEngine
|
||||
from passbook.saml_idp import exceptions
|
||||
from passbook.saml_idp.models import SAMLProvider
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
URL_VALIDATOR = URLValidator(schemes=('http', 'https'))
|
||||
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
"""Functions for creating XML output."""
|
||||
|
||||
from logging import getLogger
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.lib.utils.template import render_to_string
|
||||
from passbook.saml_idp.xml_signing import get_signature_xml, sign_with_signxml
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
def _get_attribute_statement(params):
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
"""Signing code goes here."""
|
||||
from logging import getLogger
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from lxml import etree # nosec
|
||||
from signxml import XMLSigner, XMLVerifier
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.lib.utils.template import render_to_string
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
LOGGER = get_logger(__name__)
|
||||
|
||||
|
||||
def sign_with_signxml(private_key, data, cert, reference_uri=None):
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from passbook.core.models import Policy, User
|
||||
from passbook.core.models import Policy, PolicyResult, User
|
||||
|
||||
|
||||
class SuspiciousRequestPolicy(Policy):
|
||||
@ -14,7 +14,7 @@ class SuspiciousRequestPolicy(Policy):
|
||||
|
||||
form = 'passbook.suspicious_policy.forms.SuspiciousRequestPolicyForm'
|
||||
|
||||
def passes(self, user: User):
|
||||
def passes(self, user: User) -> PolicyResult:
|
||||
remote_ip = user.remote_ip
|
||||
passing = True
|
||||
if self.check_ip:
|
||||
@ -23,7 +23,7 @@ class SuspiciousRequestPolicy(Policy):
|
||||
if self.check_username:
|
||||
user_scores = UserScore.objects.filter(user=user, score__lte=self.threshold)
|
||||
passing = passing and user_scores.exists()
|
||||
return passing
|
||||
return PolicyResult(passing)
|
||||
|
||||
class Meta:
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user