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]
|
[bumpversion]
|
||||||
current_version = 0.2.8-beta
|
current_version = 0.4.1-beta
|
||||||
tag = True
|
tag = True
|
||||||
commit = True
|
commit = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -191,3 +191,4 @@ pip-selfcheck.json
|
|||||||
# End of https://www.gitignore.io/api/python,django
|
# End of https://www.gitignore.io/api/python,django
|
||||||
/static/
|
/static/
|
||||||
local.env.yml
|
local.env.yml
|
||||||
|
.vscode/
|
||||||
|
|||||||
@ -13,9 +13,12 @@ variables:
|
|||||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
|
- pip install pipenv
|
||||||
# Ensure all dependencies are installed, even those not included in passbook/dev
|
# Ensure all dependencies are installed, even those not included in passbook/dev
|
||||||
- pip install -r requirements.txt
|
# According to pipenv docs, -d outputs all packages, however it actually does not
|
||||||
- pip install -r requirements-dev.txt
|
- pipenv lock -r > requirements-all.txt
|
||||||
|
- pipenv lock -rd >> requirements-all.txt
|
||||||
|
- pip install -r requirements-all.txt
|
||||||
|
|
||||||
create-base-image:
|
create-base-image:
|
||||||
image:
|
image:
|
||||||
@ -24,7 +27,7 @@ create-base-image:
|
|||||||
before_script:
|
before_script:
|
||||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||||
script:
|
script:
|
||||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/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
|
stage: build-base-image
|
||||||
only:
|
only:
|
||||||
refs:
|
refs:
|
||||||
@ -38,7 +41,7 @@ build-dev-image:
|
|||||||
before_script:
|
before_script:
|
||||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||||
script:
|
script:
|
||||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/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
|
stage: build-dev-image
|
||||||
only:
|
only:
|
||||||
refs:
|
refs:
|
||||||
@ -60,20 +63,20 @@ migrations:
|
|||||||
services:
|
services:
|
||||||
- postgres:latest
|
- postgres:latest
|
||||||
- redis:latest
|
- redis:latest
|
||||||
prospector:
|
# prospector:
|
||||||
script:
|
# script:
|
||||||
- prospector
|
# - prospector
|
||||||
stage: test
|
# stage: test
|
||||||
services:
|
# services:
|
||||||
- postgres:latest
|
# - postgres:latest
|
||||||
- redis:latest
|
# - redis:latest
|
||||||
pylint:
|
# pylint:
|
||||||
script:
|
# script:
|
||||||
- pylint passbook
|
# - pylint passbook
|
||||||
stage: test
|
# stage: test
|
||||||
services:
|
# services:
|
||||||
- postgres:latest
|
# - postgres:latest
|
||||||
- redis:latest
|
# - redis:latest
|
||||||
coverage:
|
coverage:
|
||||||
script:
|
script:
|
||||||
- coverage run manage.py test
|
- coverage run manage.py test
|
||||||
@ -91,7 +94,7 @@ package-passbook-server:
|
|||||||
before_script:
|
before_script:
|
||||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||||
script:
|
script:
|
||||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.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
|
stage: build
|
||||||
only:
|
only:
|
||||||
- tags
|
- tags
|
||||||
@ -104,7 +107,7 @@ build-passbook-static:
|
|||||||
before_script:
|
before_script:
|
||||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||||
script:
|
script:
|
||||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/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:
|
only:
|
||||||
- tags
|
- tags
|
||||||
- /^version/.*$/
|
- /^version/.*$/
|
||||||
@ -121,7 +124,7 @@ package-helm:
|
|||||||
- curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash
|
- curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash
|
||||||
script:
|
script:
|
||||||
- helm init --client-only
|
- helm init --client-only
|
||||||
- helm dependency build helm/passbook
|
- helm dependency update helm/passbook
|
||||||
- helm package helm/passbook
|
- helm package helm/passbook
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
|
|||||||
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
|
FROM python:3.7-alpine
|
||||||
|
|
||||||
COPY ./requirements.txt /app/
|
COPY ./Pipfile /app/
|
||||||
|
COPY ./Pipfile.lock /app/
|
||||||
|
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
RUN apk update && \
|
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 && \
|
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 && \
|
adduser -S passbook && \
|
||||||
chown -R passbook /app
|
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
|
apiVersion: v1
|
||||||
appVersion: "0.2.8-beta"
|
appVersion: "0.4.1-beta"
|
||||||
description: A Helm chart for passbook.
|
description: A Helm chart for passbook.
|
||||||
name: 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
|
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:
|
dependencies:
|
||||||
- name: rabbitmq
|
|
||||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
|
||||||
version: 4.3.2
|
|
||||||
- name: postgresql
|
- name: postgresql
|
||||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||||
version: 3.10.1
|
version: 4.2.2
|
||||||
- name: redis
|
- name: redis
|
||||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||||
version: 5.1.0
|
version: 9.2.1
|
||||||
digest: sha256:8bf68bc928a2e3c0f05139635be05fa0840554c7bde4cecd624fac78fb5fa5a3
|
digest: sha256:8782e974a1094eaeecf1d68f093ca4fb84977217b2bd38b09790a05ec289aec2
|
||||||
generated: 2019-03-21T11:06:51.553379+01:00
|
generated: "2019-10-02T21:03:25.90491153Z"
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
- name: rabbitmq
|
|
||||||
version: 4.3.2
|
|
||||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
|
||||||
- name: postgresql
|
- name: postgresql
|
||||||
version: 3.10.1
|
version: 4.2.2
|
||||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||||
- name: redis
|
- name: redis
|
||||||
version: 5.1.0
|
version: 9.2.1
|
||||||
repository: https://kubernetes-charts.storage.googleapis.com/
|
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||||
|
|||||||
@ -32,6 +32,21 @@ spec:
|
|||||||
- ./manage.py
|
- ./manage.py
|
||||||
args:
|
args:
|
||||||
- app_gw_web
|
- 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:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 8000
|
containerPort: 8000
|
||||||
|
|||||||
@ -4,41 +4,16 @@ metadata:
|
|||||||
name: {{ include "passbook.fullname" . }}-config
|
name: {{ include "passbook.fullname" . }}-config
|
||||||
data:
|
data:
|
||||||
config.yml: |
|
config.yml: |
|
||||||
# Env for Docker images
|
postgresql:
|
||||||
databases:
|
host: "{{ .Release.Name }}-postgresql"
|
||||||
default:
|
name: "{{ .Values.postgresql.postgresqlDatabase }}"
|
||||||
engine: django.db.backends.postgresql
|
|
||||||
name: {{ .Values.postgresql.postgresqlDatabase }}
|
|
||||||
user: postgres
|
user: postgres
|
||||||
password: {{ .Values.postgresql.postgresqlPassword }}
|
redis:
|
||||||
host: {{ .Release.Name }}-postgresql
|
host: "{{ .Release.Name }}-redis-master"
|
||||||
port: ''
|
cache_db: 0
|
||||||
log:
|
message_queue_db: 1
|
||||||
level:
|
|
||||||
console: WARNING
|
# Error reporting, sends stacktrace to sentry.beryju.org
|
||||||
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
|
|
||||||
error_report_enabled: {{ .Values.config.error_reporting }}
|
error_report_enabled: {{ .Values.config.error_reporting }}
|
||||||
|
|
||||||
{{- if .Values.config.secret_key }}
|
{{- if .Values.config.secret_key }}
|
||||||
|
|||||||
@ -31,6 +31,21 @@ spec:
|
|||||||
- ./manage.py
|
- ./manage.py
|
||||||
args:
|
args:
|
||||||
- migrate
|
- 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:
|
volumeMounts:
|
||||||
- mountPath: /etc/passbook
|
- mountPath: /etc/passbook
|
||||||
name: config-volume
|
name: config-volume
|
||||||
@ -39,9 +54,31 @@ spec:
|
|||||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command:
|
command:
|
||||||
- ./manage.py
|
- uwsgi
|
||||||
args:
|
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:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 8000
|
containerPort: 8000
|
||||||
|
|||||||
@ -32,6 +32,21 @@ spec:
|
|||||||
- ./manage.py
|
- ./manage.py
|
||||||
args:
|
args:
|
||||||
- worker
|
- 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:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 8000
|
containerPort: 8000
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
|
|
||||||
image:
|
image:
|
||||||
tag: 0.2.8-beta
|
tag: 0.4.1-beta
|
||||||
|
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
"""passbook"""
|
"""passbook"""
|
||||||
__version__ = '0.2.8-beta'
|
__version__ = '0.4.1-beta'
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
"""passbook admin templatetags"""
|
"""passbook admin templatetags"""
|
||||||
import inspect
|
import inspect
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.lib.utils.template import render_to_string
|
from passbook.lib.utils.template import render_to_string
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def get_links(model_instance):
|
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.forms.policies import PolicyTestForm
|
||||||
from passbook.admin.mixins import AdminRequiredMixin
|
from passbook.admin.mixins import AdminRequiredMixin
|
||||||
from passbook.core.models import Policy
|
from passbook.core.models import Policy
|
||||||
from passbook.core.policies import PolicyEngine
|
|
||||||
from passbook.lib.utils.reflection import path_to_class
|
from passbook.lib.utils.reflection import path_to_class
|
||||||
|
from passbook.policy.engine import PolicyEngine
|
||||||
|
|
||||||
|
|
||||||
class PolicyListView(AdminRequiredMixin, ListView):
|
class PolicyListView(AdminRequiredMixin, ListView):
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
"""passbook app_gw webserver management command"""
|
"""passbook app_gw webserver management command"""
|
||||||
|
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from daphne.cli import CommandLineInterface
|
from daphne.cli import CommandLineInterface
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.utils import autoreload
|
from django.utils import autoreload
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|||||||
@ -27,7 +27,7 @@ class ApplicationGatewayMiddleware:
|
|||||||
handler = RequestHandler(app_gw, request)
|
handler = RequestHandler(app_gw, request)
|
||||||
|
|
||||||
if not handler.check_permission():
|
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 RedirectView.as_view(url=to_url)(request)
|
||||||
|
|
||||||
return handler.get_response()
|
return handler.get_response()
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
"""passbook app_gw request handler"""
|
"""passbook app_gw request handler"""
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from logging import getLogger
|
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
@ -8,6 +7,7 @@ import certifi
|
|||||||
import urllib3
|
import urllib3
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.app_gw.models import ApplicationGatewayProvider
|
from passbook.app_gw.models import ApplicationGatewayProvider
|
||||||
from passbook.app_gw.proxy.exceptions import InvalidUpstream
|
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.rewrite import Rewriter
|
||||||
from passbook.app_gw.proxy.utils import encode_items, normalize_request_headers
|
from passbook.app_gw.proxy.utils import encode_items, normalize_request_headers
|
||||||
from passbook.core.models import Application
|
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'
|
SESSION_UPSTREAM_KEY = 'passbook_app_gw_upstream'
|
||||||
IGNORED_HOSTNAMES_KEY = 'passbook_app_gw_ignored'
|
IGNORED_HOSTNAMES_KEY = 'passbook_app_gw_ignored'
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
QUOTE_SAFE = r'<.;>\(}*+|~=-$/_:^@)[{]&\'!,"`'
|
QUOTE_SAFE = r'<.;>\(}*+|~=-$/_:^@)[{]&\'!,"`'
|
||||||
ERRORS_MESSAGES = {
|
ERRORS_MESSAGES = {
|
||||||
'upstream-no-scheme': ("Upstream URL scheme must be either "
|
'upstream-no-scheme': ("Upstream URL scheme must be either "
|
||||||
@ -204,8 +204,8 @@ class RequestHandler:
|
|||||||
def _set_content_type(self, proxy_response):
|
def _set_content_type(self, proxy_response):
|
||||||
content_type = proxy_response.headers.get('Content-Type')
|
content_type = proxy_response.headers.get('Content-Type')
|
||||||
if not content_type:
|
if not content_type:
|
||||||
content_type = (mimetypes.guess_type(self.request.path)[0] or
|
content_type = (mimetypes.guess_type(self.request.path)
|
||||||
self.app_gw.default_content_type)
|
[0] or self.app_gw.default_content_type)
|
||||||
proxy_response.headers['Content-Type'] = content_type
|
proxy_response.headers['Content-Type'] = content_type
|
||||||
# LOGGER.debug("Proxy response CONTENT-TYPE: %s",
|
# LOGGER.debug("Proxy response CONTENT-TYPE: %s",
|
||||||
# proxy_response.headers['Content-Type'])
|
# proxy_response.headers['Content-Type'])
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
"""response functions from django-revproxy"""
|
"""response functions from django-revproxy"""
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.http import HttpResponse, StreamingHttpResponse
|
from django.http import HttpResponse, StreamingHttpResponse
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.app_gw.proxy.utils import (cookie_from_string,
|
from passbook.app_gw.proxy.utils import (cookie_from_string,
|
||||||
set_response_headers, should_stream)
|
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 number of bytes that are going to be read in a file lecture
|
||||||
DEFAULT_AMT = 2 ** 16
|
DEFAULT_AMT = 2 ** 16
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_django_response(proxy_response, strict_cookies=False):
|
def get_django_response(proxy_response, strict_cookies=False):
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
"""Utils from django-revproxy, slightly adjusted"""
|
"""Utils from django-revproxy, slightly adjusted"""
|
||||||
import logging
|
|
||||||
import re
|
import re
|
||||||
from wsgiref.util import is_hop_by_hop
|
from wsgiref.util import is_hop_by_hop
|
||||||
|
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
COOKIE_PREFIX = ''
|
COOKIE_PREFIX = ''
|
||||||
@ -155,7 +156,7 @@ def encode_items(items):
|
|||||||
return encoded
|
return encoded
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('revproxy.cookies')
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
def cookie_from_string(cookie_string, strict_cookies=False):
|
def cookie_from_string(cookie_string, strict_cookies=False):
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
"""passbook app_gw cache clean signals"""
|
"""passbook app_gw cache clean signals"""
|
||||||
|
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.app_gw.models import ApplicationGatewayProvider
|
from passbook.app_gw.models import ApplicationGatewayProvider
|
||||||
from passbook.app_gw.proxy.handler import IGNORED_HOSTNAMES_KEY
|
from passbook.app_gw.proxy.handler import IGNORED_HOSTNAMES_KEY
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
@receiver(post_save)
|
@receiver(post_save)
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
"""websocket proxy consumer"""
|
"""websocket proxy consumer"""
|
||||||
import threading
|
import threading
|
||||||
from logging import getLogger
|
|
||||||
from ssl import CERT_NONE
|
from ssl import CERT_NONE
|
||||||
|
|
||||||
import websocket
|
import websocket
|
||||||
from channels.generic.websocket import WebsocketConsumer
|
from channels.generic.websocket import WebsocketConsumer
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.app_gw.models import ApplicationGatewayProvider
|
from passbook.app_gw.models import ApplicationGatewayProvider
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
class ProxyConsumer(WebsocketConsumer):
|
class ProxyConsumer(WebsocketConsumer):
|
||||||
"""Proxy websocket connection to upstream"""
|
"""Proxy websocket connection to upstream"""
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
"""passbook audit models"""
|
"""passbook audit models"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.contrib.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
@ -8,10 +6,11 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from ipware import get_client_ip
|
from ipware import get_client_ip
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.lib.models import UUIDModel
|
from passbook.lib.models import UUIDModel
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
class AuditEntry(UUIDModel):
|
class AuditEntry(UUIDModel):
|
||||||
"""An individual audit log entry"""
|
"""An individual audit log entry"""
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
"""passbook core app config"""
|
"""passbook core app config"""
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
class PassbookCoreConfig(AppConfig):
|
class PassbookCoreConfig(AppConfig):
|
||||||
"""passbook core app config"""
|
"""passbook core app config"""
|
||||||
@ -17,7 +17,7 @@ class PassbookCoreConfig(AppConfig):
|
|||||||
mountpoint = ''
|
mountpoint = ''
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import_module('passbook.core.policies')
|
import_module('passbook.policy.engine')
|
||||||
factors_to_load = CONFIG.y('passbook.factors', [])
|
factors_to_load = CONFIG.y('passbook.factors', [])
|
||||||
for factors_to_load in factors_to_load:
|
for factors_to_load in factors_to_load:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -19,7 +19,7 @@ class AuthenticationFactor(TemplateView):
|
|||||||
self.authenticator = authenticator
|
self.authenticator = authenticator
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['config'] = CONFIG.get('passbook')
|
kwargs['config'] = CONFIG.y('passbook')
|
||||||
kwargs['is_login'] = True
|
kwargs['is_login'] = True
|
||||||
kwargs['title'] = _('Log in to your account')
|
kwargs['title'] = _('Log in to your account')
|
||||||
kwargs['primary_action'] = _('Log in')
|
kwargs['primary_action'] = _('Log in')
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
"""passbook multi-factor authentication engine"""
|
"""passbook multi-factor authentication engine"""
|
||||||
from logging import getLogger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.auth.factor import AuthenticationFactor
|
from passbook.core.auth.factor import AuthenticationFactor
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DummyFactor(AuthenticationFactor):
|
class DummyFactor(AuthenticationFactor):
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
"""passbook multi-factor authentication engine"""
|
"""passbook multi-factor authentication engine"""
|
||||||
from inspect import Signature
|
from inspect import Signature
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth import _clean_credentials
|
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.shortcuts import redirect, reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.auth.factor import AuthenticationFactor
|
from passbook.core.auth.factor import AuthenticationFactor
|
||||||
from passbook.core.auth.view import AuthenticationView
|
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.config import CONFIG
|
||||||
from passbook.lib.utils.reflection import path_to_class
|
from passbook.lib.utils.reflection import path_to_class
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def authenticate(request, backends, **credentials):
|
def authenticate(request, backends, **credentials):
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
"""passbook multi-factor authentication engine"""
|
"""passbook multi-factor authentication engine"""
|
||||||
from logging import getLogger
|
from typing import List, Tuple
|
||||||
|
|
||||||
from django.contrib.auth import login
|
from django.contrib.auth import login
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
from django.shortcuts import get_object_or_404, redirect, reverse
|
from django.shortcuts import get_object_or_404, redirect, reverse
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Factor, User
|
from passbook.core.models import Factor, User
|
||||||
from passbook.core.policies import PolicyEngine
|
|
||||||
from passbook.core.views.utils import PermissionDeniedView
|
from passbook.core.views.utils import PermissionDeniedView
|
||||||
from passbook.lib.utils.reflection import class_to_path, path_to_class
|
from passbook.lib.utils.reflection import class_to_path, path_to_class
|
||||||
from passbook.lib.utils.urls import is_url_absolute
|
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):
|
def _redirect_with_qs(view, get_query_set=None):
|
||||||
"""Wrapper to redirect whilst keeping GET Parameters"""
|
"""Wrapper to redirect whilst keeping GET Parameters"""
|
||||||
@ -31,12 +32,12 @@ class AuthenticationView(UserPassesTestMixin, View):
|
|||||||
SESSION_USER_BACKEND = 'passbook_user_backend'
|
SESSION_USER_BACKEND = 'passbook_user_backend'
|
||||||
SESSION_IS_SSO_LOGIN = 'passbook_sso_login'
|
SESSION_IS_SSO_LOGIN = 'passbook_sso_login'
|
||||||
|
|
||||||
pending_user = None
|
pending_user: User
|
||||||
pending_factors = []
|
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
|
# Allow only not authenticated users to login
|
||||||
def test_func(self):
|
def test_func(self):
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
"""passbook core authentication forms"""
|
"""passbook core authentication forms"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import validate_email
|
from django.core.validators import validate_email
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import User
|
from passbook.core.models import User
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
from passbook.lib.utils.ui import human_list
|
from passbook.lib.utils.ui import human_list
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
class LoginForm(forms.Form):
|
class LoginForm(forms.Form):
|
||||||
"""Allow users to login"""
|
"""Allow users to login"""
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
"""passbook import_users management command"""
|
"""passbook import_users management command"""
|
||||||
from csv import DictReader
|
from csv import DictReader
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.core.validators import EmailValidator, ValidationError
|
from django.core.validators import EmailValidator, ValidationError
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import User
|
from passbook.core.models import User
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
"""Import users from CSV file"""
|
"""Import users from CSV file"""
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
"""passbook Webserver management command"""
|
"""passbook Webserver management command"""
|
||||||
|
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
from passbook.root.wsgi import application
|
from passbook.root.wsgi import application
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@ -17,7 +16,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
"""passbook cherrypy server"""
|
"""passbook cherrypy server"""
|
||||||
cherrypy.config.update(CONFIG.get('web'))
|
cherrypy.config.update(CONFIG.y('web'))
|
||||||
cherrypy.tree.graft(application, '/')
|
cherrypy.tree.graft(application, '/')
|
||||||
# Mount NullObject to serve static files
|
# Mount NullObject to serve static files
|
||||||
cherrypy.tree.mount(None, settings.STATIC_URL, config={
|
cherrypy.tree.mount(None, settings.STATIC_URL, config={
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
"""passbook Worker management command"""
|
"""passbook Worker management command"""
|
||||||
|
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.utils import autoreload
|
from django.utils import autoreload
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.root.celery import CELERY_APP
|
from passbook.root.celery import CELERY_APP
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
"""passbook core models"""
|
"""passbook core models"""
|
||||||
import re
|
import re
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from logging import getLogger
|
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Tuple, Union
|
from typing import List
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
@ -14,17 +13,33 @@ from django.urls import reverse_lazy
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from model_utils.managers import InheritanceManager
|
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.core.signals import password_changed
|
||||||
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
|
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def default_nonce_duration():
|
def default_nonce_duration():
|
||||||
"""Default duration a Nonce is valid"""
|
"""Default duration a Nonce is valid"""
|
||||||
return now() + timedelta(hours=4)
|
return now() + timedelta(hours=4)
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyResult:
|
||||||
|
"""Small data-class to hold policy results"""
|
||||||
|
|
||||||
|
passing: bool = False
|
||||||
|
messages: List[str] = []
|
||||||
|
|
||||||
|
def __init__(self, passing: bool, *messages: str):
|
||||||
|
self.passing = passing
|
||||||
|
self.messages = messages
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"<PolicyResult passing={self.passing}>"
|
||||||
|
|
||||||
class Group(UUIDModel):
|
class Group(UUIDModel):
|
||||||
"""Custom Group model which supports a basic hierarchy"""
|
"""Custom Group model which supports a basic hierarchy"""
|
||||||
|
|
||||||
@ -229,9 +244,9 @@ class Policy(UUIDModel, CreatedUpdatedModel):
|
|||||||
return self.name
|
return self.name
|
||||||
return "%s action %s" % (self.name, self.action)
|
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"""
|
"""Check if user instance passes this policy"""
|
||||||
raise NotImplementedError()
|
raise PolicyException()
|
||||||
|
|
||||||
class FieldMatcherPolicy(Policy):
|
class FieldMatcherPolicy(Policy):
|
||||||
"""Policy which checks if a field of the User model matches/doesn't match a
|
"""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)
|
description = "%s: %s" % (self.name, description)
|
||||||
return 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"""
|
"""Check if user instance passes this role"""
|
||||||
if not hasattr(user, self.user_field):
|
if not hasattr(user, self.user_field):
|
||||||
raise ValueError("Field does not exist")
|
raise ValueError("Field does not exist")
|
||||||
@ -294,7 +309,7 @@ class FieldMatcherPolicy(Policy):
|
|||||||
passes = user_field_value == self.value
|
passes = user_field_value == self.value
|
||||||
|
|
||||||
LOGGER.debug("User got '%r'", passes)
|
LOGGER.debug("User got '%r'", passes)
|
||||||
return passes
|
return PolicyResult(passes)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
@ -313,10 +328,10 @@ class PasswordPolicy(Policy):
|
|||||||
|
|
||||||
form = 'passbook.core.forms.policies.PasswordPolicyForm'
|
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
|
# Only check if password is being set
|
||||||
if not hasattr(user, '__password__'):
|
if not hasattr(user, '__password__'):
|
||||||
return True
|
return PolicyResult(True)
|
||||||
password = getattr(user, '__password__')
|
password = getattr(user, '__password__')
|
||||||
|
|
||||||
filter_regex = r''
|
filter_regex = r''
|
||||||
@ -329,8 +344,8 @@ class PasswordPolicy(Policy):
|
|||||||
result = bool(re.compile(filter_regex).match(password))
|
result = bool(re.compile(filter_regex).match(password))
|
||||||
LOGGER.debug("User got %r", result)
|
LOGGER.debug("User got %r", result)
|
||||||
if not result:
|
if not result:
|
||||||
return result, self.error_message
|
return PolicyResult(result, self.error_message)
|
||||||
return result
|
return PolicyResult(result)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
@ -364,7 +379,7 @@ class WebhookPolicy(Policy):
|
|||||||
|
|
||||||
form = 'passbook.core.forms.policies.WebhookPolicyForm'
|
form = 'passbook.core.forms.policies.WebhookPolicyForm'
|
||||||
|
|
||||||
def passes(self, user: User):
|
def passes(self, user: User) -> PolicyResult:
|
||||||
"""Call webhook asynchronously and report back"""
|
"""Call webhook asynchronously and report back"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@ -383,12 +398,12 @@ class DebugPolicy(Policy):
|
|||||||
|
|
||||||
form = 'passbook.core.forms.policies.DebugPolicyForm'
|
form = 'passbook.core.forms.policies.DebugPolicyForm'
|
||||||
|
|
||||||
def passes(self, user: User):
|
def passes(self, user: User) -> PolicyResult:
|
||||||
"""Wait random time then return result"""
|
"""Wait random time then return result"""
|
||||||
wait = SystemRandom().randrange(self.wait_min, self.wait_max)
|
wait = SystemRandom().randrange(self.wait_min, self.wait_max)
|
||||||
LOGGER.debug("Policy '%s' waiting for %ds", self.name, wait)
|
LOGGER.debug("Policy '%s' waiting for %ds", self.name, wait)
|
||||||
sleep(wait)
|
sleep(wait)
|
||||||
return self.result, 'Debugging'
|
return PolicyResult(self.result, 'Debugging')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
@ -402,8 +417,8 @@ class GroupMembershipPolicy(Policy):
|
|||||||
|
|
||||||
form = 'passbook.core.forms.policies.GroupMembershipPolicyForm'
|
form = 'passbook.core.forms.policies.GroupMembershipPolicyForm'
|
||||||
|
|
||||||
def passes(self, user: User) -> Union[bool, Tuple[bool, str]]:
|
def passes(self, user: User) -> PolicyResult:
|
||||||
return self.group.user_set.filter(pk=user.pk).exists()
|
return PolicyResult(self.group.user_set.filter(pk=user.pk).exists())
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
@ -415,10 +430,10 @@ class SSOLoginPolicy(Policy):
|
|||||||
|
|
||||||
form = 'passbook.core.forms.policies.SSOLoginPolicyForm'
|
form = 'passbook.core.forms.policies.SSOLoginPolicyForm'
|
||||||
|
|
||||||
def passes(self, user):
|
def passes(self, user) -> PolicyResult:
|
||||||
"""Check if user instance passes this policy"""
|
"""Check if user instance passes this policy"""
|
||||||
from passbook.core.auth.view import AuthenticationView
|
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:
|
class Meta:
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,7 @@ http {
|
|||||||
gzip on;
|
gzip on;
|
||||||
gzip_types application/javascript image/* text/css;
|
gzip_types application/javascript image/* text/css;
|
||||||
gunzip on;
|
gunzip on;
|
||||||
add_header X-passbook-Version 0.2.8-beta;
|
add_header X-passbook-Version 0.4.1-beta;
|
||||||
add_header Vary X-passbook-Version;
|
add_header Vary X-passbook-Version;
|
||||||
root /static/;
|
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"""
|
"""passbook core signals"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.signals import Signal
|
from django.core.signals import Signal
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.exceptions import PasswordPolicyInvalid
|
from passbook.core.exceptions import PasswordPolicyInvalid
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
user_signed_up = Signal(providing_args=['request', 'user'])
|
user_signed_up = Signal(providing_args=['request', 'user'])
|
||||||
invitation_created = Signal(providing_args=['request', 'invitation'])
|
invitation_created = Signal(providing_args=['request', 'invitation'])
|
||||||
@ -20,7 +19,7 @@ password_changed = Signal(providing_args=['user', 'password'])
|
|||||||
def password_policy_checker(sender, password, **kwargs):
|
def password_policy_checker(sender, password, **kwargs):
|
||||||
"""Run password through all password policies which are applied to the user"""
|
"""Run password through all password policies which are applied to the user"""
|
||||||
from passbook.core.models import PasswordFactor
|
from passbook.core.models import PasswordFactor
|
||||||
from passbook.core.policies import PolicyEngine
|
from passbook.policy.engine import PolicyEngine
|
||||||
setattr(sender, '__password__', password)
|
setattr(sender, '__password__', password)
|
||||||
_all_factors = PasswordFactor.objects.filter(enabled=True).order_by('order')
|
_all_factors = PasswordFactor.objects.filter(enabled=True).order_by('order')
|
||||||
for factor in _all_factors:
|
for factor in _all_factors:
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
"""passbook core tasks"""
|
"""passbook core tasks"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.core.mail import EmailMultiAlternatives
|
from django.core.mail import EmailMultiAlternatives
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Nonce
|
from passbook.core.models import Nonce
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
from passbook.root.celery import CELERY_APP
|
from passbook.root.celery import CELERY_APP
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
@CELERY_APP.task()
|
@CELERY_APP.task()
|
||||||
def send_email(to_address, subject, template, context):
|
def send_email(to_address, subject, template, context):
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
from django import template
|
from django import template
|
||||||
|
|
||||||
from passbook.core.models import Factor, Source
|
from passbook.core.models import Factor, Source
|
||||||
from passbook.core.policies import PolicyEngine
|
from passbook.policy.engine import PolicyEngine
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|||||||
@ -77,7 +77,7 @@ class TestFactorAuthentication(TestCase):
|
|||||||
request.user = AnonymousUser()
|
request.user = AnonymousUser()
|
||||||
middleware = SessionMiddleware()
|
middleware = SessionMiddleware()
|
||||||
middleware.process_request(request)
|
middleware.process_request(request)
|
||||||
request.session.save()
|
request.session.save() # pylint: disable=no-member
|
||||||
request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk
|
request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk
|
||||||
|
|
||||||
response = AuthenticationView.as_view()(request)
|
response = AuthenticationView.as_view()(request)
|
||||||
@ -93,7 +93,7 @@ class TestFactorAuthentication(TestCase):
|
|||||||
request.user = AnonymousUser()
|
request.user = AnonymousUser()
|
||||||
middleware = SessionMiddleware()
|
middleware = SessionMiddleware()
|
||||||
middleware.process_request(request)
|
middleware.process_request(request)
|
||||||
request.session.save()
|
request.session.save() # pylint: disable=no-member
|
||||||
request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk
|
request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk
|
||||||
|
|
||||||
response = AuthenticationView.as_view()(request)
|
response = AuthenticationView.as_view()(request)
|
||||||
@ -111,7 +111,7 @@ class TestFactorAuthentication(TestCase):
|
|||||||
request.user = AnonymousUser()
|
request.user = AnonymousUser()
|
||||||
middleware = SessionMiddleware()
|
middleware = SessionMiddleware()
|
||||||
middleware.process_request(request)
|
middleware.process_request(request)
|
||||||
request.session.save()
|
request.session.save() # pylint: disable=no-member
|
||||||
request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk
|
request.session[AuthenticationView.SESSION_PENDING_USER] = self.user.pk
|
||||||
|
|
||||||
response = AuthenticationView.as_view()(request)
|
response = AuthenticationView.as_view()(request)
|
||||||
@ -127,7 +127,7 @@ class TestFactorAuthentication(TestCase):
|
|||||||
middleware.process_request(request)
|
middleware.process_request(request)
|
||||||
for key, value in session_copy:
|
for key, value in session_copy:
|
||||||
request.session[key] = value
|
request.session[key] = value
|
||||||
request.session.save()
|
request.session.save() # pylint: disable=no-member
|
||||||
response = AuthenticationView.as_view()(request)
|
response = AuthenticationView.as_view()(request)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(response.url, reverse('passbook_core:overview'))
|
self.assertEqual(response.url, reverse('passbook_core:overview'))
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
"""passbook URL Configuration"""
|
"""passbook URL Configuration"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.auth import view
|
from passbook.core.auth import view
|
||||||
from passbook.core.views import authentication, overview, user
|
from passbook.core.views import authentication, overview, user
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Authentication views
|
# Authentication views
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
"""passbook access helper classes"""
|
"""passbook access helper classes"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Application
|
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:
|
class AccessMixin:
|
||||||
"""Mixin class for usage in Authorization views.
|
"""Mixin class for usage in Authorization views.
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
"""passbook core authentication views"""
|
"""passbook core authentication views"""
|
||||||
from logging import getLogger
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from django.contrib import messages
|
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.utils.translation import ugettext as _
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.auth.view import AuthenticationView, _redirect_with_qs
|
from passbook.core.auth.view import AuthenticationView, _redirect_with_qs
|
||||||
from passbook.core.exceptions import PasswordPolicyInvalid
|
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.core.tasks import send_email
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LoginView(UserPassesTestMixin, FormView):
|
class LoginView(UserPassesTestMixin, FormView):
|
||||||
@ -40,7 +40,7 @@ class LoginView(UserPassesTestMixin, FormView):
|
|||||||
return redirect(reverse('passbook_core:overview'))
|
return redirect(reverse('passbook_core:overview'))
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['config'] = CONFIG.get('passbook')
|
kwargs['config'] = CONFIG.y('passbook')
|
||||||
kwargs['is_login'] = True
|
kwargs['is_login'] = True
|
||||||
kwargs['title'] = _('Log in to your account')
|
kwargs['title'] = _('Log in to your account')
|
||||||
kwargs['primary_action'] = _('Log in')
|
kwargs['primary_action'] = _('Log in')
|
||||||
@ -135,7 +135,7 @@ class SignUpView(UserPassesTestMixin, FormView):
|
|||||||
return super().get_initial()
|
return super().get_initial()
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['config'] = CONFIG.get('passbook')
|
kwargs['config'] = CONFIG.y('passbook')
|
||||||
kwargs['is_login'] = True
|
kwargs['is_login'] = True
|
||||||
kwargs['title'] = _('Sign Up')
|
kwargs['title'] = _('Sign Up')
|
||||||
kwargs['primary_action'] = _('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 django.views.generic import TemplateView
|
||||||
|
|
||||||
from passbook.core.models import Application
|
from passbook.core.models import Application
|
||||||
from passbook.core.policies import PolicyEngine
|
from passbook.policy.engine import PolicyEngine
|
||||||
|
|
||||||
|
|
||||||
class OverviewView(LoginRequiredMixin, TemplateView):
|
class OverviewView(LoginRequiredMixin, TemplateView):
|
||||||
|
|||||||
@ -66,7 +66,7 @@ class UserChangePasswordView(LoginRequiredMixin, FormView):
|
|||||||
return redirect('passbook_core:overview')
|
return redirect('passbook_core:overview')
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['config'] = CONFIG.get('passbook')
|
kwargs['config'] = CONFIG.y('passbook')
|
||||||
kwargs['is_login'] = True
|
kwargs['is_login'] = True
|
||||||
kwargs['title'] = _('Change Password')
|
kwargs['title'] = _('Change Password')
|
||||||
kwargs['primary_action'] = _('Change')
|
kwargs['primary_action'] = _('Change')
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
"""passbook HIBP Models"""
|
"""passbook HIBP Models"""
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from requests import get
|
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):
|
class HaveIBeenPwendPolicy(Policy):
|
||||||
"""Check if password is on HaveIBeenPwned's list by upload the first
|
"""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'
|
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
|
"""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
|
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."""
|
if Password is not in result otherwise the count of how many times it was used."""
|
||||||
# Only check if password is being set
|
# Only check if password is being set
|
||||||
if not hasattr(user, '__password__'):
|
if not hasattr(user, '__password__'):
|
||||||
return True
|
return PolicyResult(True)
|
||||||
password = getattr(user, '__password__')
|
password = getattr(user, '__password__')
|
||||||
pw_hash = sha1(password.encode('utf-8')).hexdigest() # nosec
|
pw_hash = sha1(password.encode('utf-8')).hexdigest() # nosec
|
||||||
url = 'https://api.pwnedpasswords.com/range/%s' % pw_hash[:5]
|
url = 'https://api.pwnedpasswords.com/range/%s' % pw_hash[:5]
|
||||||
@ -36,8 +36,9 @@ class HaveIBeenPwendPolicy(Policy):
|
|||||||
final_count = int(count)
|
final_count = int(count)
|
||||||
LOGGER.debug("Got count %d for hash %s", final_count, pw_hash[:5])
|
LOGGER.debug("Got count %d for hash %s", final_count, pw_hash[:5])
|
||||||
if final_count > self.allowed_count:
|
if final_count > self.allowed_count:
|
||||||
return False, _("Password exists on %(count)d online lists." % {'count': final_count})
|
message = _("Password exists on %(count)d online lists." % {'count': final_count})
|
||||||
return True
|
return PolicyResult(False, message)
|
||||||
|
return PolicyResult(True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
"""passbook LDAP Authentication Backend"""
|
"""passbook LDAP Authentication Backend"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.contrib.auth.backends import ModelBackend
|
from django.contrib.auth.backends import ModelBackend
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.ldap.ldap_connector import LDAPConnector
|
from passbook.ldap.ldap_connector import LDAPConnector
|
||||||
from passbook.ldap.models import LDAPSource
|
from passbook.ldap.models import LDAPSource
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LDAPBackend(ModelBackend):
|
class LDAPBackend(ModelBackend):
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
"""Wrapper for ldap3 to easily manage user"""
|
"""Wrapper for ldap3 to easily manage user"""
|
||||||
from logging import getLogger
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
import ldap3
|
import ldap3
|
||||||
import ldap3.core.exceptions
|
import ldap3.core.exceptions
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import User
|
from passbook.core.models import User
|
||||||
from passbook.ldap.models import LDAPSource
|
from passbook.ldap.models import LDAPSource
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
USERNAME_FIELD = CONFIG.y('ldap.username_field', 'sAMAccountName')
|
USERNAME_FIELD = CONFIG.y('ldap.username_field', 'sAMAccountName')
|
||||||
LOGIN_FIELD = CONFIG.y('ldap.login_field', 'userPrincipalName')
|
LOGIN_FIELD = CONFIG.y('ldap.login_field', 'userPrincipalName')
|
||||||
@ -166,7 +166,7 @@ class LDAPConnector:
|
|||||||
if not self._source.enabled:
|
if not self._source.enabled:
|
||||||
return None
|
return None
|
||||||
# FIXME: Adapt user_uid
|
# 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')
|
email = filters.pop('email')
|
||||||
user_dn = self.lookup(self.generate_filter(**{LOGIN_FIELD: email}))
|
user_dn = self.lookup(self.generate_filter(**{LOGIN_FIELD: email}))
|
||||||
if not user_dn:
|
if not user_dn:
|
||||||
|
|||||||
@ -1,37 +1,40 @@
|
|||||||
"""passbook lib config loader"""
|
"""passbook core config loader"""
|
||||||
import os
|
import os
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from logging import getLogger
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from django.conf import ImproperlyConfigured
|
from django.conf import ImproperlyConfigured
|
||||||
from django.utils.autoreload import autoreload_started
|
from django.utils.autoreload import autoreload_started
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
SEARCH_PATHS = [
|
SEARCH_PATHS = [
|
||||||
'passbook/lib/default.yml',
|
'passbook/lib/default.yml',
|
||||||
'/etc/passbook/config.yml',
|
'/etc/passbook/config.yml',
|
||||||
'.',
|
'',
|
||||||
] + glob('/etc/passbook/config.d/*.yml', recursive=True)
|
] + glob('/etc/passbook/config.d/*.yml', recursive=True)
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger()
|
||||||
ENVIRONMENT = os.getenv('PASSBOOK_ENV', 'local')
|
ENV_PREFIX = 'PASSBOOK'
|
||||||
|
ENVIRONMENT = os.getenv(f'{ENV_PREFIX}_ENV', 'local')
|
||||||
|
|
||||||
|
|
||||||
class ConfigLoader:
|
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 = []
|
loaded_file = []
|
||||||
|
|
||||||
__config = {}
|
__config = {}
|
||||||
__context_default = None
|
|
||||||
__sub_dicts = []
|
__sub_dicts = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
base_dir = os.path.realpath(os.path.join(
|
base_dir = os.path.realpath(os.path.join(os.path.dirname(__file__), '../..'))
|
||||||
os.path.dirname(__file__), '../..'))
|
|
||||||
for path in SEARCH_PATHS:
|
for path in SEARCH_PATHS:
|
||||||
# Check if path is relative, and if so join with base_dir
|
# Check if path is relative, and if so join with base_dir
|
||||||
if not os.path.isabs(path):
|
if not os.path.isabs(path):
|
||||||
@ -47,15 +50,7 @@ class ConfigLoader:
|
|||||||
if os.path.isfile(env_file) and os.path.exists(env_file):
|
if os.path.isfile(env_file) and os.path.exists(env_file):
|
||||||
# Update config with env file
|
# Update config with env file
|
||||||
self.update_from_file(env_file)
|
self.update_from_file(env_file)
|
||||||
self.handle_secret_key()
|
self.update_from_env()
|
||||||
|
|
||||||
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', '')
|
|
||||||
|
|
||||||
def update(self, root, updatee):
|
def update(self, root, updatee):
|
||||||
"""Recursively update dictionary"""
|
"""Recursively update dictionary"""
|
||||||
@ -63,16 +58,25 @@ class ConfigLoader:
|
|||||||
if isinstance(value, Mapping):
|
if isinstance(value, Mapping):
|
||||||
root[key] = self.update(root.get(key, {}), value)
|
root[key] = self.update(root.get(key, {}), value)
|
||||||
else:
|
else:
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = self.parse_uri(value)
|
||||||
root[key] = value
|
root[key] = value
|
||||||
return root
|
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):
|
def update_from_file(self, path: str):
|
||||||
"""Update config from file contents"""
|
"""Update config from file contents"""
|
||||||
try:
|
try:
|
||||||
with open(path) as file:
|
with open(path) as file:
|
||||||
try:
|
try:
|
||||||
self.update(self.__config, yaml.safe_load(file))
|
self.update(self.__config, yaml.safe_load(file))
|
||||||
LOGGER.debug("Loaded %s", path)
|
LOGGER.debug("Loaded config", file=path)
|
||||||
self.loaded_file.append(path)
|
self.loaded_file.append(path)
|
||||||
except yaml.YAMLError as exc:
|
except yaml.YAMLError as exc:
|
||||||
raise ImproperlyConfigured from exc
|
raise ImproperlyConfigured from exc
|
||||||
@ -83,12 +87,26 @@ class ConfigLoader:
|
|||||||
"""Update config from dict"""
|
"""Update config from dict"""
|
||||||
self.__config.update(update)
|
self.__config.update(update)
|
||||||
|
|
||||||
@contextmanager
|
def update_from_env(self):
|
||||||
def default(self, value: Any):
|
"""Check environment variables"""
|
||||||
"""Contextmanage that sets default"""
|
outer = {}
|
||||||
self.__context_default = value
|
idx = 0
|
||||||
yield
|
for key, value in os.environ.items():
|
||||||
self.__context_default = None
|
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
|
@contextmanager
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
@ -98,15 +116,6 @@ class ConfigLoader:
|
|||||||
yield
|
yield
|
||||||
self.__sub_dicts.pop()
|
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
|
@property
|
||||||
def raw(self) -> dict:
|
def raw(self) -> dict:
|
||||||
"""Get raw config dictionary"""
|
"""Get raw config dictionary"""
|
||||||
@ -115,8 +124,6 @@ class ConfigLoader:
|
|||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
def y(self, path: str, default=None, sep='.') -> Any:
|
def y(self, path: str, default=None, sep='.') -> Any:
|
||||||
"""Access attribute by using yaml path"""
|
"""Access attribute by using yaml path"""
|
||||||
if default is None:
|
|
||||||
default = self.__context_default
|
|
||||||
# Walk sub_dicts before parsing path
|
# Walk sub_dicts before parsing path
|
||||||
root = self.raw
|
root = self.raw
|
||||||
for sub in self.__sub_dicts:
|
for sub in self.__sub_dicts:
|
||||||
@ -129,11 +136,14 @@ class ConfigLoader:
|
|||||||
return default
|
return default
|
||||||
return root
|
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()
|
CONFIG = ConfigLoader()
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
def signal_handler(sender, **_):
|
||||||
def signal_handler(sender, **kwargs):
|
|
||||||
"""Add all loaded config files to autoreload watcher"""
|
"""Add all loaded config files to autoreload watcher"""
|
||||||
for path in CONFIG.loaded_file:
|
for path in CONFIG.loaded_file:
|
||||||
sender.watch_file(path)
|
sender.watch_file(path)
|
||||||
|
|||||||
@ -32,8 +32,7 @@ def reauth_required(view_function):
|
|||||||
|
|
||||||
if RE_AUTH_KEY not in request.session:
|
if RE_AUTH_KEY not in request.session:
|
||||||
# Timestamp not in session, force user to reauth
|
# Timestamp not in session, force user to reauth
|
||||||
return redirect(reverse('account-reauth') + '?' +
|
return redirect(reverse('account-reauth') + '?' + urlencode({'next': request.path}))
|
||||||
urlencode({'next': request.path}))
|
|
||||||
|
|
||||||
if RE_AUTH_KEY in request.session and \
|
if RE_AUTH_KEY in request.session and \
|
||||||
request.session[RE_AUTH_KEY] >= (now - RE_AUTH_MARGAIN) and \
|
request.session[RE_AUTH_KEY] >= (now - RE_AUTH_MARGAIN) and \
|
||||||
|
|||||||
@ -1,43 +1,20 @@
|
|||||||
# This is the default configuration file
|
# This is the default configuration file
|
||||||
databases:
|
postgresql:
|
||||||
default:
|
host: localhost
|
||||||
engine: 'django.db.backends.postgresql'
|
|
||||||
name: passbook
|
name: passbook
|
||||||
user: passbook
|
user: passbook
|
||||||
password: 'EK-5jnKfjrGRm<77'
|
password: 'env://POSTGRES_PASSWORD'
|
||||||
|
|
||||||
|
redis:
|
||||||
host: localhost
|
host: localhost
|
||||||
log:
|
|
||||||
level:
|
|
||||||
console: DEBUG
|
|
||||||
file: DEBUG
|
|
||||||
file: /dev/null
|
|
||||||
syslog:
|
|
||||||
host: 127.0.0.1
|
|
||||||
port: 514
|
|
||||||
email:
|
|
||||||
host: localhost
|
|
||||||
port: 25
|
|
||||||
user: ''
|
|
||||||
password: ''
|
password: ''
|
||||||
use_tls: false
|
cache_db: 0
|
||||||
use_ssl: false
|
message_queue_db: 1
|
||||||
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: ''
|
|
||||||
|
|
||||||
debug: false
|
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 reporting, sends stacktrace to sentry.services.beryju.org
|
||||||
error_report_enabled: true
|
error_report_enabled: true
|
||||||
secret_key: 9$@r!d^1^jrn#fk#1#@ks#9&i$^s#1)_13%$rwjrhd=e8jfi_s
|
|
||||||
|
|
||||||
domains:
|
domains:
|
||||||
- passbook.local
|
- passbook.local
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"""passbook sentry integration"""
|
"""passbook sentry integration"""
|
||||||
from logging import getLogger
|
from structlog import get_logger
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def before_send(event, hint):
|
def before_send(event, hint):
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
"""passbook lib navbar Templatetag"""
|
"""passbook lib navbar Templatetag"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
"""passbook Core Reflection templatetags Templatetag"""
|
"""passbook Core Reflection templatetags Templatetag"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_key_unique(context):
|
def get_key_unique(context):
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
from django.template import Context, Template, loader
|
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"""
|
"""Render template from string to string"""
|
||||||
template = Template(template)
|
template = Template(tmpl)
|
||||||
return template.render(ctx)
|
return template.render(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
"""passbook oauth_client config"""
|
"""passbook oauth_client config"""
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
class PassbookOAuthClientConfig(AppConfig):
|
class PassbookOAuthClientConfig(AppConfig):
|
||||||
"""passbook oauth_client config"""
|
"""passbook oauth_client config"""
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
"""OAuth Clients"""
|
"""OAuth Clients"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from logging import getLogger
|
|
||||||
from urllib.parse import parse_qs, urlencode
|
from urllib.parse import parse_qs, urlencode
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -10,8 +9,9 @@ from django.utils.encoding import force_text
|
|||||||
from requests import Session
|
from requests import Session
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
from requests_oauthlib import OAuth1
|
from requests_oauthlib import OAuth1
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BaseOAuthClient:
|
class BaseOAuthClient:
|
||||||
@ -120,9 +120,9 @@ class OAuthClient(BaseOAuthClient):
|
|||||||
"Parse token and secret from raw token response."
|
"Parse token and secret from raw token response."
|
||||||
if raw_token is None:
|
if raw_token is None:
|
||||||
return (None, None)
|
return (None, None)
|
||||||
qs = parse_qs(raw_token)
|
query_string = parse_qs(raw_token)
|
||||||
token = qs.get('oauth_token', [None])[0]
|
token = query_string.get('oauth_token', [None])[0]
|
||||||
secret = qs.get('oauth_token_secret', [None])[0]
|
secret = query_string.get('oauth_token_secret', [None])[0]
|
||||||
return (token, secret)
|
return (token, secret)
|
||||||
|
|
||||||
def request(self, method, url, **kwargs):
|
def request(self, method, url, **kwargs):
|
||||||
@ -217,8 +217,7 @@ class OAuth2Client(BaseOAuthClient):
|
|||||||
try:
|
try:
|
||||||
token_data = json.loads(raw_token)
|
token_data = json.loads(raw_token)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
qs = parse_qs(raw_token)
|
token = parse_qs(raw_token).get('access_token', [None])[0]
|
||||||
token = qs.get('access_token', [None])[0]
|
|
||||||
else:
|
else:
|
||||||
token = token_data.get('access_token', None)
|
token = token_data.get('access_token', None)
|
||||||
return (token, None)
|
return (token, None)
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
"""AzureAD OAuth2 Views"""
|
"""AzureAD OAuth2 Views"""
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.oauth_client.clients import OAuth2Client
|
from passbook.oauth_client.clients import OAuth2Client
|
||||||
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
||||||
from passbook.oauth_client.utils import user_get_or_create
|
from passbook.oauth_client.utils import user_get_or_create
|
||||||
from passbook.oauth_client.views.core import OAuthCallback
|
from passbook.oauth_client.views.core import OAuthCallback
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AzureADOAuth2Client(OAuth2Client):
|
class AzureADOAuth2Client(OAuth2Client):
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
"""Discord OAuth Views"""
|
"""Discord OAuth Views"""
|
||||||
import json
|
import json
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.oauth_client.clients import OAuth2Client
|
from passbook.oauth_client.clients import OAuth2Client
|
||||||
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
||||||
from passbook.oauth_client.utils import user_get_or_create
|
from passbook.oauth_client.utils import user_get_or_create
|
||||||
from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect
|
from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@MANAGER.source(kind=RequestKind.redirect, name='Discord')
|
@MANAGER.source(kind=RequestKind.redirect, name='Discord')
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
"""Source type manager"""
|
"""Source type manager"""
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from logging import getLogger
|
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect
|
from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
class RequestKind(Enum):
|
class RequestKind(Enum):
|
||||||
"""Enum of OAuth Request types"""
|
"""Enum of OAuth Request types"""
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
"""Reddit OAuth Views"""
|
"""Reddit OAuth Views"""
|
||||||
import json
|
import json
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.oauth_client.clients import OAuth2Client
|
from passbook.oauth_client.clients import OAuth2Client
|
||||||
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
||||||
from passbook.oauth_client.utils import user_get_or_create
|
from passbook.oauth_client.utils import user_get_or_create
|
||||||
from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect
|
from passbook.oauth_client.views.core import OAuthCallback, OAuthRedirect
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@MANAGER.source(kind=RequestKind.redirect, name='reddit')
|
@MANAGER.source(kind=RequestKind.redirect, name='reddit')
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
"""Supervisr OAuth2 Views"""
|
"""Supervisr OAuth2 Views"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.oauth_client.clients import OAuth2Client
|
from passbook.oauth_client.clients import OAuth2Client
|
||||||
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
||||||
from passbook.oauth_client.utils import user_get_or_create
|
from passbook.oauth_client.utils import user_get_or_create
|
||||||
from passbook.oauth_client.views.core import OAuthCallback
|
from passbook.oauth_client.views.core import OAuthCallback
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SupervisrOAuth2Client(OAuth2Client):
|
class SupervisrOAuth2Client(OAuth2Client):
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
"""Twitter OAuth Views"""
|
"""Twitter OAuth Views"""
|
||||||
|
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from requests.exceptions import RequestException
|
from requests.exceptions import RequestException
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.oauth_client.clients import OAuthClient
|
from passbook.oauth_client.clients import OAuthClient
|
||||||
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
from passbook.oauth_client.source_types.manager import MANAGER, RequestKind
|
||||||
from passbook.oauth_client.utils import user_get_or_create
|
from passbook.oauth_client.utils import user_get_or_create
|
||||||
from passbook.oauth_client.views.core import OAuthCallback
|
from passbook.oauth_client.views.core import OAuthCallback
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TwitterOAuthClient(OAuthClient):
|
class TwitterOAuthClient(OAuthClient):
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
"""Core OAauth Views"""
|
"""Core OAauth Views"""
|
||||||
|
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth import authenticate
|
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.urls import reverse
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.views.generic import RedirectView, View
|
from django.views.generic import RedirectView, View
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.auth.view import AuthenticationView, _redirect_with_qs
|
from passbook.core.auth.view import AuthenticationView, _redirect_with_qs
|
||||||
from passbook.lib.utils.reflection import app
|
from passbook.lib.utils.reflection import app
|
||||||
from passbook.oauth_client.clients import get_client
|
from passbook.oauth_client.clients import get_client
|
||||||
from passbook.oauth_client.models import OAuthSource, UserOAuthSourceConnection
|
from passbook.oauth_client.models import OAuthSource, UserOAuthSourceConnection
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
"""passbook OAuth2 Views"""
|
"""passbook OAuth2 Views"""
|
||||||
from logging import getLogger
|
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from django.contrib import messages
|
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.shortcuts import get_object_or_404, redirect, reverse
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from oauth2_provider.views.base import AuthorizationView
|
from oauth2_provider.views.base import AuthorizationView
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.audit.models import AuditEntry
|
from passbook.audit.models import AuditEntry
|
||||||
from passbook.core.models import Application
|
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.core.views.utils import LoadingView, PermissionDeniedView
|
||||||
from passbook.oauth_provider.models import OAuth2Provider
|
from passbook.oauth_provider.models import OAuth2Provider
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PassbookAuthorizationLoadingView(LoginRequiredMixin, LoadingView):
|
class PassbookAuthorizationLoadingView(LoginRequiredMixin, LoadingView):
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
"""passbook auth oidc provider app config"""
|
"""passbook auth oidc provider app config"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.utils import InternalError, OperationalError, ProgrammingError
|
from django.db.utils import InternalError, OperationalError, ProgrammingError
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
class PassbookOIDCProviderConfig(AppConfig):
|
class PassbookOIDCProviderConfig(AppConfig):
|
||||||
"""passbook auth oidc provider app config"""
|
"""passbook auth oidc provider app config"""
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
"""OIDC Permission checking"""
|
"""OIDC Permission checking"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Application
|
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):
|
def check_permissions(request, user, client):
|
||||||
"""Check permissions, used for
|
"""Check permissions, used for
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
"""OTP Factor logic"""
|
"""OTP Factor logic"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
from django_otp import match_token, user_has_device
|
from django_otp import match_token, user_has_device
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.auth.factor import AuthenticationFactor
|
from passbook.core.auth.factor import AuthenticationFactor
|
||||||
from passbook.otp.forms import OTPVerifyForm
|
from passbook.otp.forms import OTPVerifyForm
|
||||||
from passbook.otp.views import OTP_SETTING_UP_KEY, EnableView
|
from passbook.otp.views import OTP_SETTING_UP_KEY, EnableView
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
class OTPFactor(FormView, AuthenticationFactor):
|
class OTPFactor(FormView, AuthenticationFactor):
|
||||||
"""OTP Factor View"""
|
"""OTP Factor View"""
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
"""passbook OTP Views"""
|
"""passbook OTP Views"""
|
||||||
from base64 import b32encode
|
from base64 import b32encode
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
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 django_otp.plugins.otp_totp.models import TOTPDevice
|
||||||
from qrcode import make
|
from qrcode import make
|
||||||
from qrcode.image.svg import SvgPathImage
|
from qrcode.image.svg import SvgPathImage
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.lib.boilerplate import NeverCacheMixin
|
from passbook.lib.boilerplate import NeverCacheMixin
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
@ -23,7 +23,7 @@ from passbook.otp.utils import otpauth_url
|
|||||||
|
|
||||||
OTP_SESSION_KEY = 'passbook_otp_key'
|
OTP_SESSION_KEY = 'passbook_otp_key'
|
||||||
OTP_SETTING_UP_KEY = 'passbook_otp_setup'
|
OTP_SETTING_UP_KEY = 'passbook_otp_setup'
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
class UserSettingsView(LoginRequiredMixin, TemplateView):
|
class UserSettingsView(LoginRequiredMixin, TemplateView):
|
||||||
"""View for user settings to control OTP"""
|
"""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
|
# TODO: Check if OTP Factor exists and applies to user
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['config'] = CONFIG.get('passbook')
|
kwargs['config'] = CONFIG.y('passbook')
|
||||||
kwargs['is_login'] = True
|
kwargs['is_login'] = True
|
||||||
kwargs['title'] = _('Configue OTP')
|
kwargs['title'] = _('Configue OTP')
|
||||||
kwargs['primary_action'] = _('Setup')
|
kwargs['primary_action'] = _('Setup')
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
"""passbook password_expiry_policy Models"""
|
"""passbook password_expiry_policy Models"""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext as _
|
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):
|
class PasswordExpiryPolicy(Policy):
|
||||||
@ -20,7 +20,7 @@ class PasswordExpiryPolicy(Policy):
|
|||||||
|
|
||||||
form = 'passbook.password_expiry_policy.forms.PasswordExpiryPolicyForm'
|
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
|
"""If password change date is more than x days in the past, call set_unusable_password
|
||||||
and show a notice"""
|
and show a notice"""
|
||||||
actual_days = (now() - user.password_change_date).days
|
actual_days = (now() - user.password_change_date).days
|
||||||
@ -29,12 +29,13 @@ class PasswordExpiryPolicy(Policy):
|
|||||||
if not self.deny_only:
|
if not self.deny_only:
|
||||||
user.set_unusable_password()
|
user.set_unusable_password()
|
||||||
user.save()
|
user.save()
|
||||||
return False, _(('Password expired %(days)d days ago. '
|
message = _(('Password expired %(days)d days ago. '
|
||||||
'Please update your password.') % {
|
'Please update your password.') % {
|
||||||
'days': days_since_expiry
|
'days': days_since_expiry
|
||||||
})
|
})
|
||||||
return False, _('Password has expired.')
|
return PolicyResult(False, message)
|
||||||
return True
|
return PolicyResult(False, _('Password has expired.'))
|
||||||
|
return PolicyResult(True)
|
||||||
|
|
||||||
class Meta:
|
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"""
|
"""passbook core celery"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
|
from logging.config import dictConfig
|
||||||
|
|
||||||
from celery import Celery, signals
|
from celery import Celery, signals
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
# set the default Django settings module for the 'celery' program.
|
# set the default Django settings module for the 'celery' program.
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings")
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
CELERY_APP = Celery('passbook')
|
CELERY_APP = Celery('passbook')
|
||||||
@ -19,7 +19,7 @@ CELERY_APP = Celery('passbook')
|
|||||||
@signals.setup_logging.connect
|
@signals.setup_logging.connect
|
||||||
def config_loggers(*args, **kwags):
|
def config_loggers(*args, **kwags):
|
||||||
"""Apply logging settings from settings.py to celery"""
|
"""Apply logging settings from settings.py to celery"""
|
||||||
logging.config.dictConfig(settings.LOGGING)
|
dictConfig(settings.LOGGING)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
|||||||
@ -11,23 +11,18 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from celery.schedules import crontab
|
import structlog
|
||||||
from django.contrib import messages
|
|
||||||
from sentry_sdk import init as sentry_init
|
from sentry_sdk import init as sentry_init
|
||||||
from sentry_sdk.integrations.celery import CeleryIntegration
|
from sentry_sdk.integrations.celery import CeleryIntegration
|
||||||
from sentry_sdk.integrations.django import DjangoIntegration
|
from sentry_sdk.integrations.django import DjangoIntegration
|
||||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
|
||||||
|
|
||||||
from passbook import __version__
|
from passbook import __version__
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
from passbook.lib.sentry import before_send
|
from passbook.lib.sentry import before_send
|
||||||
|
|
||||||
VERSION = __version__
|
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# 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__))))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
STATIC_ROOT = BASE_DIR + '/static'
|
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/
|
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# 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!
|
# 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']
|
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 = ['*']
|
ALLOWED_HOSTS = ['*']
|
||||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
|
|
||||||
@ -53,7 +49,7 @@ AUTH_USER_MODEL = 'passbook_core.User'
|
|||||||
|
|
||||||
CSRF_COOKIE_NAME = 'passbook_csrf'
|
CSRF_COOKIE_NAME = 'passbook_csrf'
|
||||||
SESSION_COOKIE_NAME = 'passbook_session'
|
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_ENGINE = "django.contrib.sessions.backends.cache"
|
||||||
SESSION_CACHE_ALIAS = "default"
|
SESSION_CACHE_ALIAS = "default"
|
||||||
LANGUAGE_COOKIE_NAME = 'passbook_language'
|
LANGUAGE_COOKIE_NAME = 'passbook_language'
|
||||||
@ -72,8 +68,8 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django.contrib.postgres',
|
'django.contrib.postgres',
|
||||||
'rest_framework',
|
# 'rest_framework',
|
||||||
'drf_yasg',
|
# 'drf_yasg',
|
||||||
'passbook.core.apps.PassbookCoreConfig',
|
'passbook.core.apps.PassbookCoreConfig',
|
||||||
'passbook.admin.apps.PassbookAdminConfig',
|
'passbook.admin.apps.PassbookAdminConfig',
|
||||||
'passbook.api.apps.PassbookAPIConfig',
|
'passbook.api.apps.PassbookAPIConfig',
|
||||||
@ -93,16 +89,6 @@ INSTALLED_APPS = [
|
|||||||
'passbook.app_gw.apps.PassbookApplicationApplicationGatewayConfig',
|
'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 = {
|
REST_FRAMEWORK = {
|
||||||
# Use Django's standard `django.contrib.auth` permissions,
|
# Use Django's standard `django.contrib.auth` permissions,
|
||||||
# or allow read-only access for unauthenticated users.
|
# or allow read-only access for unauthenticated users.
|
||||||
@ -114,17 +100,21 @@ REST_FRAMEWORK = {
|
|||||||
CACHES = {
|
CACHES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "django_redis.cache.RedisCache",
|
"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": {
|
"OPTIONS": {
|
||||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
"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 = [
|
MIDDLEWARE = [
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'passbook.app_gw.middleware.ApplicationGatewayMiddleware',
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
@ -150,21 +140,19 @@ TEMPLATES = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'passbook.core.wsgi.application'
|
WSGI_APPLICATION = 'passbook.root.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
|
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {}
|
DATABASES = {
|
||||||
for db_alias, db_config in CONFIG.get('databases').items():
|
'default': {
|
||||||
DATABASES[db_alias] = {
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
'ENGINE': db_config.get('engine'),
|
'HOST': CONFIG.y('postgresql.host'),
|
||||||
'HOST': db_config.get('host'),
|
'NAME': CONFIG.y('postgresql.name'),
|
||||||
'NAME': db_config.get('name'),
|
'USER': CONFIG.y('postgresql.user'),
|
||||||
'USER': db_config.get('user'),
|
'PASSWORD': CONFIG.y('postgresql.password'),
|
||||||
'PASSWORD': db_config.get('password'),
|
}
|
||||||
'OPTIONS': db_config.get('options', {}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
@ -203,20 +191,13 @@ USE_TZ = True
|
|||||||
# Celery settings
|
# Celery settings
|
||||||
# Add a 10 minute timeout to all Celery tasks.
|
# Add a 10 minute timeout to all Celery tasks.
|
||||||
CELERY_TASK_SOFT_TIME_LIMIT = 600
|
CELERY_TASK_SOFT_TIME_LIMIT = 600
|
||||||
CELERY_TIMEZONE = TIME_ZONE
|
|
||||||
CELERY_BEAT_SCHEDULE = {}
|
CELERY_BEAT_SCHEDULE = {}
|
||||||
CELERY_CREATE_MISSING_QUEUES = True
|
CELERY_CREATE_MISSING_QUEUES = True
|
||||||
CELERY_TASK_DEFAULT_QUEUE = 'passbook'
|
CELERY_TASK_DEFAULT_QUEUE = 'passbook'
|
||||||
CELERY_BROKER_URL = 'amqp://%s' % CONFIG.get('rabbitmq')
|
CELERY_BROKER_URL = (f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}"
|
||||||
CELERY_RESULT_BACKEND = 'rpc://'
|
f":6379/{CONFIG.y('redis.message_queue_db')}")
|
||||||
CELERY_ACKS_LATE = True
|
CELERY_RESULT_BACKEND = (f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}"
|
||||||
CELERY_BROKER_HEARTBEAT = 0
|
f":6379/{CONFIG.y('redis.message_queue_db')}")
|
||||||
CELERY_BEAT_SCHEDULE = {
|
|
||||||
'cleanup-expired-nonces': {
|
|
||||||
'task': 'passbook.core.tasks.clean_nonces',
|
|
||||||
'schedule': crontab(hour=1, minute=1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if not DEBUG:
|
if not DEBUG:
|
||||||
@ -224,11 +205,7 @@ if not DEBUG:
|
|||||||
dsn="https://33cdbcb23f8b436dbe0ee06847410b67@sentry.beryju.org/3",
|
dsn="https://33cdbcb23f8b436dbe0ee06847410b67@sentry.beryju.org/3",
|
||||||
integrations=[
|
integrations=[
|
||||||
DjangoIntegration(),
|
DjangoIntegration(),
|
||||||
CeleryIntegration(),
|
CeleryIntegration()
|
||||||
LoggingIntegration(
|
|
||||||
level=logging.INFO,
|
|
||||||
event_level=logging.ERROR
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
send_default_pii=True,
|
send_default_pii=True,
|
||||||
before_send=before_send,
|
before_send=before_send,
|
||||||
@ -240,95 +217,76 @@ if not DEBUG:
|
|||||||
|
|
||||||
STATIC_URL = '/static/'
|
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'):
|
with CONFIG.cd('log'):
|
||||||
|
LOGGING_HANDLER_MAP = {
|
||||||
|
'passbook': 'DEBUG',
|
||||||
|
'django': 'WARNING',
|
||||||
|
'celery': 'WARNING',
|
||||||
|
'grpc': 'DEBUG',
|
||||||
|
'oauthlib': 'DEBUG',
|
||||||
|
'oauth2_provider': 'DEBUG',
|
||||||
|
'daphne': 'INFO',
|
||||||
|
}
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
'disable_existing_loggers': True,
|
'disable_existing_loggers': False,
|
||||||
'formatters': {
|
'formatters': {
|
||||||
'verbose': {
|
"plain": {
|
||||||
'format': ('%(asctime)s %(levelname)-8s %(name)-55s '
|
"()": structlog.stdlib.ProcessorFormatter,
|
||||||
'%(funcName)-20s %(message)s'),
|
"processor": structlog.processors.JSONRenderer(),
|
||||||
|
"foreign_pre_chain": LOG_PRE_CHAIN,
|
||||||
},
|
},
|
||||||
'color': {
|
"colored": {
|
||||||
'()': 'colorlog.ColoredFormatter',
|
"()": structlog.stdlib.ProcessorFormatter,
|
||||||
'format': ('%(log_color)s%(asctime)s %(levelname)-8s %(name)-55s '
|
"processor": structlog.dev.ConsoleRenderer(colors=DEBUG),
|
||||||
'%(funcName)-20s %(message)s'),
|
"foreign_pre_chain": LOG_PRE_CHAIN,
|
||||||
'log_colors': {
|
|
||||||
'DEBUG': 'bold_black',
|
|
||||||
'INFO': 'white',
|
|
||||||
'WARNING': 'yellow',
|
|
||||||
'ERROR': 'red',
|
|
||||||
'CRITICAL': 'bold_red',
|
|
||||||
'SUCCESS': 'green',
|
|
||||||
},
|
},
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'handlers': {
|
'handlers': {
|
||||||
'console': {
|
'console': {
|
||||||
'level': CONFIG.get('level').get('console'),
|
'level': DEBUG,
|
||||||
'class': 'logging.StreamHandler',
|
'class': 'logging.StreamHandler',
|
||||||
'formatter': 'color',
|
'formatter': "colored" if DEBUG else "plain",
|
||||||
},
|
|
||||||
'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'),
|
|
||||||
},
|
},
|
||||||
'queue': {
|
'queue': {
|
||||||
'level': CONFIG.get('level').get('console'),
|
'level': DEBUG,
|
||||||
'class': 'passbook.lib.log.QueueListenerHandler',
|
'class': 'passbook.lib.log.QueueListenerHandler',
|
||||||
'handlers': [
|
'handlers': [
|
||||||
'cfg://handlers.console',
|
'cfg://handlers.console',
|
||||||
# 'cfg://handlers.syslog',
|
|
||||||
'cfg://handlers.file',
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'loggers': {
|
'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 = False
|
||||||
@ -342,6 +300,7 @@ if any('test' in arg for arg in sys.argv):
|
|||||||
TEST = True
|
TEST = True
|
||||||
CELERY_TASK_ALWAYS_EAGER = True
|
CELERY_TASK_ALWAYS_EAGER = True
|
||||||
|
|
||||||
|
|
||||||
_DISALLOWED_ITEMS = ['INSTALLED_APPS', 'MIDDLEWARE', 'AUTHENTICATION_BACKENDS']
|
_DISALLOWED_ITEMS = ['INSTALLED_APPS', 'MIDDLEWARE', 'AUTHENTICATION_BACKENDS']
|
||||||
# Load subapps's INSTALLED_APPS
|
# Load subapps's INSTALLED_APPS
|
||||||
for _app in INSTALLED_APPS:
|
for _app in INSTALLED_APPS:
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
"""passbook URL Configuration"""
|
"""passbook URL Configuration"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.views import error
|
from passbook.core.views import error
|
||||||
from passbook.lib.utils.reflection import get_apps
|
from passbook.lib.utils.reflection import get_apps
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
admin.site.login = RedirectView.as_view(pattern_name='passbook_core:auth-login')
|
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 django.core.wsgi import get_wsgi_application
|
||||||
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
|
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())
|
application = SentryWsgiMiddleware(get_wsgi_application())
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
"""passbook mod saml_idp app config"""
|
"""passbook mod saml_idp app config"""
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
class PassbookSAMLIDPConfig(AppConfig):
|
class PassbookSAMLIDPConfig(AppConfig):
|
||||||
"""passbook saml_idp app config"""
|
"""passbook saml_idp app config"""
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.saml_idp import exceptions, utils, xml_render
|
from passbook.saml_idp import exceptions, utils, xml_render
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ class Processor:
|
|||||||
def __init__(self, remote):
|
def __init__(self, remote):
|
||||||
self.name = remote.name
|
self.name = remote.name
|
||||||
self._remote = remote
|
self._remote = remote
|
||||||
self._logger = getLogger(__name__)
|
self._logger = get_logger(__name__)
|
||||||
self._system_params['ISSUER'] = self._remote.issuer
|
self._system_params['ISSUER'] = self._remote.issuer
|
||||||
self._logger.debug('processor configured')
|
self._logger.debug('processor configured')
|
||||||
|
|
||||||
@ -260,7 +260,6 @@ class Processor:
|
|||||||
def _validate_user(self):
|
def _validate_user(self):
|
||||||
"""Validates the User. Sub-classes should override this and
|
"""Validates the User. Sub-classes should override this and
|
||||||
throw an CannotHandleAssertion Exception if the validation does not succeed."""
|
throw an CannotHandleAssertion Exception if the validation does not succeed."""
|
||||||
pass
|
|
||||||
|
|
||||||
def can_handle(self, request):
|
def can_handle(self, request):
|
||||||
"""Returns true if this processor can handle this request."""
|
"""Returns true if this processor can handle this request."""
|
||||||
|
|||||||
@ -3,9 +3,7 @@
|
|||||||
|
|
||||||
class CannotHandleAssertion(Exception):
|
class CannotHandleAssertion(Exception):
|
||||||
"""This processor does not handle this assertion."""
|
"""This processor does not handle this assertion."""
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UserNotAuthorized(Exception):
|
class UserNotAuthorized(Exception):
|
||||||
"""User not authorized for SAML 2.0 authentication."""
|
"""User not authorized for SAML 2.0 authentication."""
|
||||||
pass
|
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
"""passbook saml_idp Models"""
|
"""passbook saml_idp Models"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.shortcuts import reverse
|
from django.shortcuts import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import PropertyMapping, Provider
|
from passbook.core.models import PropertyMapping, Provider
|
||||||
from passbook.lib.utils.reflection import class_to_path, path_to_class
|
from passbook.lib.utils.reflection import class_to_path, path_to_class
|
||||||
from passbook.saml_idp.base import Processor
|
from passbook.saml_idp.base import Processor
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SAMLProvider(Provider):
|
class SAMLProvider(Provider):
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
"""passbook SAML IDP Views"""
|
"""passbook SAML IDP Views"""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from django.contrib.auth import logout
|
from django.contrib.auth import logout
|
||||||
from django.contrib.auth.mixins import AccessMixin
|
from django.contrib.auth.mixins import AccessMixin
|
||||||
from django.core.exceptions import ValidationError
|
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 import View
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from signxml.util import strip_pem_header
|
from signxml.util import strip_pem_header
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.audit.models import AuditEntry
|
from passbook.audit.models import AuditEntry
|
||||||
from passbook.core.models import Application
|
from passbook.core.models import Application
|
||||||
from passbook.core.policies import PolicyEngine
|
|
||||||
from passbook.lib.mixins import CSRFExemptMixin
|
from passbook.lib.mixins import CSRFExemptMixin
|
||||||
from passbook.lib.utils.template import render_to_string
|
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 import exceptions
|
||||||
from passbook.saml_idp.models import SAMLProvider
|
from passbook.saml_idp.models import SAMLProvider
|
||||||
|
|
||||||
LOGGER = getLogger(__name__)
|
LOGGER = get_logger(__name__)
|
||||||
URL_VALIDATOR = URLValidator(schemes=('http', 'https'))
|
URL_VALIDATOR = URLValidator(schemes=('http', 'https'))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
"""Functions for creating XML output."""
|
"""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.lib.utils.template import render_to_string
|
||||||
from passbook.saml_idp.xml_signing import get_signature_xml, sign_with_signxml
|
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):
|
def _get_attribute_statement(params):
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
"""Signing code goes here."""
|
"""Signing code goes here."""
|
||||||
from logging import getLogger
|
|
||||||
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives import serialization
|
from cryptography.hazmat.primitives import serialization
|
||||||
from lxml import etree # nosec
|
from lxml import etree # nosec
|
||||||
from signxml import XMLSigner, XMLVerifier
|
from signxml import XMLSigner, XMLVerifier
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.lib.utils.template import render_to_string
|
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):
|
def sign_with_signxml(private_key, data, cert, reference_uri=None):
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext as _
|
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):
|
class SuspiciousRequestPolicy(Policy):
|
||||||
@ -14,7 +14,7 @@ class SuspiciousRequestPolicy(Policy):
|
|||||||
|
|
||||||
form = 'passbook.suspicious_policy.forms.SuspiciousRequestPolicyForm'
|
form = 'passbook.suspicious_policy.forms.SuspiciousRequestPolicyForm'
|
||||||
|
|
||||||
def passes(self, user: User):
|
def passes(self, user: User) -> PolicyResult:
|
||||||
remote_ip = user.remote_ip
|
remote_ip = user.remote_ip
|
||||||
passing = True
|
passing = True
|
||||||
if self.check_ip:
|
if self.check_ip:
|
||||||
@ -23,7 +23,7 @@ class SuspiciousRequestPolicy(Policy):
|
|||||||
if self.check_username:
|
if self.check_username:
|
||||||
user_scores = UserScore.objects.filter(user=user, score__lte=self.threshold)
|
user_scores = UserScore.objects.filter(user=user, score__lte=self.threshold)
|
||||||
passing = passing and user_scores.exists()
|
passing = passing and user_scores.exists()
|
||||||
return passing
|
return PolicyResult(passing)
|
||||||
|
|
||||||
class Meta:
|
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