Compare commits
44 Commits
version/0.
...
version/0.
| Author | SHA1 | Date | |
|---|---|---|---|
| f124314eab | |||
| 684e4ffdcf | |||
| d9ff5c69c8 | |||
| 8142e3df45 | |||
| 73920899de | |||
| 13666965a7 | |||
| 86f16e2781 | |||
| 2ed8e72c62 | |||
| edeed18ae8 | |||
| d24133d8a2 | |||
| b9733e56aa | |||
| cd34413914 | |||
| c3a4a76d43 | |||
| a59a29b256 | |||
| dce1edbe53 | |||
| 264d43827a | |||
| 6207226bdf | |||
| ebf33f39c9 | |||
| 696cd1f247 | |||
| b7b3abc462 | |||
| 575739d07c | |||
| 2d7e70eebf | |||
| 387f3c981f | |||
| 865435fb25 | |||
| b10c5306b9 | |||
| 7c706369cd | |||
| 20dd6355c1 | |||
| ba8d5d6e27 | |||
| c448f87027 | |||
| 2b8c70a61f | |||
| 9d7ed9a0ed | |||
| ff69b4affe | |||
| d77afd1ded | |||
| c3909f9196 | |||
| fa55ba5ef0 | |||
| 766518ee0e | |||
| 74b2b26a20 | |||
| 4ebbc6f065 | |||
| 3bd1eadd51 | |||
| 8eb3f0f708 | |||
| 31ea2e7139 | |||
| 323b4b4a5d | |||
| 7b8e1bea92 | |||
| f986dc89ad |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.7.5-beta
|
||||
current_version = 0.7.12-beta
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
||||
@ -19,7 +19,7 @@ values =
|
||||
|
||||
[bumpversion:file:helm/Chart.yaml]
|
||||
|
||||
[bumpversion:file:.gitlab-ci.yml]
|
||||
[bumpversion:file:.github/workflows/release.yml]
|
||||
|
||||
[bumpversion:file:passbook/__init__.py]
|
||||
|
||||
|
||||
147
.github/workflows/ci.yml
vendored
Normal file
147
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
name: passbook-ci
|
||||
on:
|
||||
- push
|
||||
env:
|
||||
POSTGRES_DB: passbook
|
||||
POSTGRES_USER: passbook
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
|
||||
jobs:
|
||||
# Linting
|
||||
pylint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs/
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pipenv-
|
||||
- name: Install dependencies
|
||||
run: pip install -U pip pipenv && pipenv install --dev
|
||||
- name: Lint with pylint
|
||||
run: pipenv run pylint passbook
|
||||
black:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs/
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pipenv-
|
||||
- name: Install dependencies
|
||||
run: pip install -U pip pipenv && pipenv install --dev
|
||||
- name: Lint with black
|
||||
run: pipenv run black --check passbook
|
||||
prospector:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs/
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pipenv-
|
||||
- name: Install dependencies
|
||||
run: pip install -U pip pipenv && pipenv install --dev
|
||||
- name: Lint with prospector
|
||||
run: pipenv run prospector
|
||||
bandit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs/
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pipenv-
|
||||
- name: Install dependencies
|
||||
run: pip install -U pip pipenv && pipenv install --dev
|
||||
- name: Lint with bandit
|
||||
run: pipenv run bandit -r passbook
|
||||
# Actual CI tests
|
||||
migrations:
|
||||
needs:
|
||||
- pylint
|
||||
- black
|
||||
- prospector
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
env:
|
||||
POSTGRES_DB: passbook
|
||||
POSTGRES_USER: passbook
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
ports:
|
||||
- 5432:5432
|
||||
redis:
|
||||
image: redis:latest
|
||||
ports:
|
||||
- 6379:6379
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs/
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pipenv-
|
||||
- name: Install dependencies
|
||||
run: pip install -U pip pipenv && pipenv install --dev
|
||||
- name: Run migrations
|
||||
run: pipenv run ./manage.py migrate
|
||||
coverage:
|
||||
needs:
|
||||
- pylint
|
||||
- black
|
||||
- prospector
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
env:
|
||||
POSTGRES_DB: passbook
|
||||
POSTGRES_USER: passbook
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
ports:
|
||||
- 5432:5432
|
||||
redis:
|
||||
image: redis:latest
|
||||
ports:
|
||||
- 6379:6379
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: '3.7'
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs/
|
||||
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pipenv-
|
||||
- name: Install dependencies
|
||||
run: pip install -U pip pipenv && pipenv install --dev
|
||||
- name: Run coverage
|
||||
run: pipenv run ./scripts/coverage.sh
|
||||
89
.github/workflows/release.yml
vendored
Normal file
89
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
name: passbook-release
|
||||
on:
|
||||
release
|
||||
|
||||
jobs:
|
||||
# Build
|
||||
build-server:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Docker Login Registry
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
|
||||
- name: Building Docker Image
|
||||
run: docker build
|
||||
--no-cache
|
||||
-t beryju/passbook:0.7.12-beta
|
||||
-t beryju/passbook:latest
|
||||
-f Dockerfile .
|
||||
- name: Push Docker Container to Registry (versioned)
|
||||
run: docker push beryju/passbook:0.7.12-beta
|
||||
- name: Push Docker Container to Registry (latest)
|
||||
run: docker push beryju/passbook:latest
|
||||
build-gatekeeper:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Docker Login Registry
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
|
||||
- name: Building Docker Image
|
||||
run: |
|
||||
cd gatekeeper
|
||||
docker build \
|
||||
--no-cache \
|
||||
-t beryju/passbook-gatekeeper:0.7.12-beta \
|
||||
-t beryju/passbook-gatekeeper:latest \
|
||||
-f Dockerfile .
|
||||
- name: Push Docker Container to Registry (versioned)
|
||||
run: docker push beryju/passbook-gatekeeper:0.7.12-beta
|
||||
- name: Push Docker Container to Registry (latest)
|
||||
run: docker push beryju/passbook-gatekeeper:latest
|
||||
build-static:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
env:
|
||||
POSTGRES_DB: passbook
|
||||
POSTGRES_USER: passbook
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
redis:
|
||||
image: redis:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Docker Login Registry
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
|
||||
- name: Building Docker Image
|
||||
run: docker build
|
||||
--no-cache
|
||||
--network=$(docker network ls | grep github | awk '{print $1}')
|
||||
-t beryju/passbook-static:0.7.12-beta
|
||||
-t beryju/passbook-static:latest
|
||||
-f static.Dockerfile .
|
||||
- name: Push Docker Container to Registry (versioned)
|
||||
run: docker push beryju/passbook-static:0.7.12-beta
|
||||
- name: Push Docker Container to Registry (latest)
|
||||
run: docker push beryju/passbook-static:latest
|
||||
test-release:
|
||||
needs:
|
||||
- build-server
|
||||
- build-static
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Run test suite in final docker images
|
||||
run: |
|
||||
export PASSBOOK_DOMAIN=localhost
|
||||
docker-compose pull
|
||||
docker-compose up --no-start
|
||||
docker-compose start postgresql redis
|
||||
docker-compose run -u root server bash -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test"
|
||||
60
.github/workflows/tag.yml
vendored
Normal file
60
.github/workflows/tag.yml
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'version/*'
|
||||
|
||||
name: passbook-version-tag
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Create Release from Tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Pre-release test
|
||||
run: |
|
||||
export PASSBOOK_DOMAIN=localhost
|
||||
docker-compose pull
|
||||
docker build \
|
||||
--no-cache \
|
||||
-t beryju/passbook:latest \
|
||||
-f Dockerfile .
|
||||
docker-compose up --no-start
|
||||
docker-compose start postgresql redis
|
||||
docker-compose run -u root server bash -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test"
|
||||
- name: Install Helm
|
||||
run: |
|
||||
apt update && apt install -y curl
|
||||
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
|
||||
- name: Helm package
|
||||
run: |
|
||||
helm dependency update helm/
|
||||
helm package helm/
|
||||
mv passbook-*.tgz passbook-chart.tgz
|
||||
- name: Extract verison number
|
||||
id: get_version
|
||||
uses: actions/github-script@0.2.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
return context.payload.ref.replace(/\/refs\/tags\/version\//, '');
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ steps.get_version.outputs.result }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
- name: Upload packaged Helm Chart
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./passbook-chart.tgz
|
||||
asset_name: passbook-chart.tgz
|
||||
asset_content_type: application/gzip
|
||||
160
.gitlab-ci.yml
160
.gitlab-ci.yml
@ -1,160 +0,0 @@
|
||||
# Global Variables
|
||||
stages:
|
||||
- build-base-image
|
||||
- build-dev-image
|
||||
- test
|
||||
- build
|
||||
- package
|
||||
- post-release
|
||||
image: docker.beryju.org/passbook/dev:latest
|
||||
|
||||
variables:
|
||||
POSTGRES_DB: passbook
|
||||
POSTGRES_USER: passbook
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
|
||||
before_script:
|
||||
- pip install pipenv
|
||||
# Ensure all dependencies are installed, even those not included in passbook/dev
|
||||
# According to pipenv docs, -d outputs all packages, however it actually does not
|
||||
- pipenv lock -r > requirements-all.txt
|
||||
- pipenv lock -rd >> requirements-all.txt
|
||||
- pip install -r requirements-all.txt
|
||||
|
||||
create-base-image:
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:debug
|
||||
entrypoint: [""]
|
||||
before_script:
|
||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/base.Dockerfile --destination docker.beryju.org/passbook/base:latest
|
||||
stage: build-base-image
|
||||
only:
|
||||
refs:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
|
||||
build-dev-image:
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:debug
|
||||
entrypoint: [""]
|
||||
before_script:
|
||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dev.Dockerfile --destination docker.beryju.org/passbook/dev:latest
|
||||
stage: build-dev-image
|
||||
only:
|
||||
refs:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
|
||||
isort:
|
||||
script:
|
||||
- isort -c -sg env
|
||||
stage: test
|
||||
services:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
migrations:
|
||||
script:
|
||||
- python manage.py migrate
|
||||
stage: test
|
||||
services:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
prospector:
|
||||
script:
|
||||
- prospector
|
||||
stage: test
|
||||
services:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
pylint:
|
||||
script:
|
||||
- pylint passbook
|
||||
stage: test
|
||||
services:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
coverage:
|
||||
script:
|
||||
- ./scripts/coverage.sh
|
||||
stage: test
|
||||
services:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
|
||||
build-passbook-server:
|
||||
stage: build
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:debug
|
||||
entrypoint: [""]
|
||||
before_script:
|
||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.7.5-beta
|
||||
only:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
build-docs:
|
||||
stage: build
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:debug
|
||||
entrypoint: [""]
|
||||
before_script:
|
||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/docs/Dockerfile --destination docker.beryju.org/passbook/docs:latest --destination docker.beryju.org/passbook/docs:0.7.5-beta
|
||||
only:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
build-passbook-static:
|
||||
stage: build
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:debug
|
||||
entrypoint: [""]
|
||||
before_script:
|
||||
- echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
|
||||
script:
|
||||
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/static.Dockerfile --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.7.5-beta
|
||||
only:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
# running collectstatic fully initialises django, hence we need that databases
|
||||
services:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
|
||||
package-helm:
|
||||
image: debian:stretch-slim
|
||||
stage: package
|
||||
before_script:
|
||||
- apt update && apt install -y curl
|
||||
- curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
|
||||
script:
|
||||
- helm dependency update helm
|
||||
- helm package helm
|
||||
artifacts:
|
||||
paths:
|
||||
- passbook-*.tgz
|
||||
expire_in: 1 week
|
||||
only:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
|
||||
notify-sentry:
|
||||
image: getsentry/sentry-cli
|
||||
stage: post-release
|
||||
variables:
|
||||
SENTRY_URL: https://sentry.beryju.org
|
||||
SENTRY_ORG: beryjuorg
|
||||
SENTRY_PROJECT: passbook
|
||||
before_script:
|
||||
- apk add curl
|
||||
script:
|
||||
- sentry-cli releases new passbook@0.7.5-beta
|
||||
- sentry-cli releases set-commits --auto passbook@0.7.5-beta
|
||||
only:
|
||||
- tags
|
||||
- /^version/.*$/
|
||||
@ -1,6 +1,6 @@
|
||||
[MASTER]
|
||||
|
||||
disable=redefined-outer-name,arguments-differ,no-self-use,cyclic-import,fixme,locally-disabled,unpacking-non-sequence,too-many-ancestors,too-many-branches,too-few-public-methods
|
||||
disable=redefined-outer-name,arguments-differ,no-self-use,cyclic-import,fixme,locally-disabled,unpacking-non-sequence,too-many-ancestors,too-many-branches,too-few-public-methods,import-outside-toplevel,bad-continuation
|
||||
load-plugins=pylint_django,pylint.extensions.bad_builtin
|
||||
extension-pkg-whitelist=lxml
|
||||
const-rgx=[a-zA-Z0-9_]{1,40}$
|
||||
|
||||
24
Dockerfile
24
Dockerfile
@ -1,4 +1,26 @@
|
||||
FROM docker.beryju.org/passbook/base:latest
|
||||
FROM python:3.7-slim-buster as locker
|
||||
|
||||
COPY ./Pipfile /app/
|
||||
COPY ./Pipfile.lock /app/
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
RUN pip install pipenv && \
|
||||
pipenv lock -r > requirements.txt && \
|
||||
pipenv lock -rd > requirements-dev.txt
|
||||
|
||||
FROM python:3.7-slim-buster
|
||||
|
||||
COPY --from=locker /app/requirements.txt /app/
|
||||
COPY --from=locker /app/requirements-dev.txt /app/
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends postgresql-client-11 && \
|
||||
rm -rf /var/lib/apt/ && \
|
||||
pip install -r requirements.txt --no-cache-dir && \
|
||||
adduser --system --no-create-home --uid 1000 --group --home /app passbook
|
||||
|
||||
COPY ./passbook/ /app/passbook
|
||||
COPY ./manage.py /app/
|
||||
|
||||
9
Pipfile
9
Pipfile
@ -23,7 +23,7 @@ django-rest-framework = "*"
|
||||
django-storages = "*"
|
||||
djangorestframework-guardian = "*"
|
||||
drf-yasg = "*"
|
||||
kombu = "==4.5.0"
|
||||
kombu = "*"
|
||||
ldap3 = "*"
|
||||
lxml = "*"
|
||||
oauthlib = "*"
|
||||
@ -51,8 +51,11 @@ bumpversion = "*"
|
||||
colorama = "*"
|
||||
coverage = "*"
|
||||
django-debug-toolbar = "*"
|
||||
isort = "*"
|
||||
prospector = "*"
|
||||
pylint = "==2.3.1"
|
||||
pylint = "*"
|
||||
pylint-django = "*"
|
||||
unittest-xml-reporting = "*"
|
||||
black = "*"
|
||||
|
||||
[pipenv]
|
||||
allow_prereleases = true
|
||||
|
||||
337
Pipfile.lock
generated
337
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "865b57ef5ef326de114d39d8505f60f19b5f7e42a50d988ea3fc9dfc9b9371ec"
|
||||
"sha256": "138816efaba5be0b175cfd5b5e6a0b58e5ba551567f0efb441740344da3986d8"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@ -46,26 +46,26 @@
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:d280f2bf7dc373e8aeab296f81aadefabf8780ff8c8ad27cdc36f8f112ca95ed",
|
||||
"sha256:edbf4636e700c46e49f555ac87ab48b8c385fde604528db15fc5189d5a73dc72"
|
||||
"sha256:982823e7c992d27e5954c81db93238ffc42c7a1210d863b4f5e048fdc088040e",
|
||||
"sha256:f05ee90a738c2f1ec8088121030229f26ef6a809fb9a1338de2118fd088dd99a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.10.33"
|
||||
"version": "==1.10.45"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:4861785b52b0b3f97da91613c31f8e501f12517c9c79482b44efbdb56b69aefc",
|
||||
"sha256:9cc87d7906693c9c8fe862c574a1bebbe22a0475d6991e9b7251bc93cb1954d9"
|
||||
"sha256:88ee646f7a0fe6a418681c6f119a590fae23d8439c48c2aec6878f7f89430b1f",
|
||||
"sha256:f48ba1ef04b25323c1d27fa6399795baa0ca9d316911b87be4d33acda5cef07c"
|
||||
],
|
||||
"version": "==1.13.33"
|
||||
"version": "==1.13.45"
|
||||
},
|
||||
"celery": {
|
||||
"hashes": [
|
||||
"sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9",
|
||||
"sha256:528e56767ae7e43a16cfef24ee1062491f5754368d38fcfffa861cdb9ef219be"
|
||||
"sha256:7c544f37a84a5eadc44cab1aa8c9580dff94636bb81978cdf9bf8012d9ea7d8f",
|
||||
"sha256:d3363bb5df72d74420986a435449f3c3979285941dff57d5d97ecba352a0e3e2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.0"
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
@ -169,19 +169,19 @@
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:a4ad4f6f9c6a4b7af7e2deec8d0cbff28501852e5010d6c2dc695d3d1fae7ca0",
|
||||
"sha256:fa98ec9cc9bf5d72a08ebf3654a9452e761fbb8566e3f80de199cbc15477e891"
|
||||
"sha256:662a1ff78792e3fd77f16f71b1f31149489434de4b62a74895bd5d6534e635a5",
|
||||
"sha256:687c37153486cf26c3fdcbdd177ef16de38dc3463f094b5f9c9955d91f277b14"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.8"
|
||||
"version": "==2.2.9"
|
||||
},
|
||||
"django-cors-middleware": {
|
||||
"hashes": [
|
||||
"sha256:85904a3401e7bc0c86502ff2b01d726917af3aaa7dafb77799b27ace637e8c92",
|
||||
"sha256:bca8888ed33a94ba5472bde37ed71ec3d08231d6817fd4d799296b016073da95"
|
||||
"sha256:5bbdea85e22909d596e26f6e0dbc174d5521429fa3943ae02a2c6c48e76c88c7",
|
||||
"sha256:856dbe4d7aae65844ccc68acb49c6da7dbf7cbacaf5bcf37019f4c0c60b3be84"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.4.0"
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"django-dbbackup": {
|
||||
"hashes": [
|
||||
@ -208,11 +208,11 @@
|
||||
},
|
||||
"django-model-utils": {
|
||||
"hashes": [
|
||||
"sha256:3f130a262e45d73e0950d2be76af4bf4ee86804dd60e5f90afc5cd948fcfe760",
|
||||
"sha256:682f58c1de330cedcda58cc85d5232c5b47a9e2cb67bef4541fb43fdaeb18e96"
|
||||
"sha256:9cf882e5b604421b62dbe57ad2b18464dc9c8f963fc3f9831badccae66c1139c",
|
||||
"sha256:adf09e5be15122a7f4e372cb5a6dd512bbf8d78a23a90770ad0983ee9d909061"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.2.0"
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"django-oauth-toolkit": {
|
||||
"hashes": [
|
||||
@ -230,19 +230,19 @@
|
||||
},
|
||||
"django-otp": {
|
||||
"hashes": [
|
||||
"sha256:1b6025bbbd2517b7c246828b1d11c83d53567904836ae6d57bc0058f3cd18b50",
|
||||
"sha256:76a698466178ce40473726ffd8c33f68d1c47f27c53f67fa4aeeb6fdde74d37b"
|
||||
"sha256:1f16c2b93fe484706ff16ac6f5e64ecc73dd240318c333e0560384ba548d3837",
|
||||
"sha256:cd4975539be478417033561e9832a1a69a583189f680e92a649f412c661f90aa"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.4"
|
||||
"version": "==0.7.5"
|
||||
},
|
||||
"django-prometheus": {
|
||||
"hashes": [
|
||||
"sha256:60f331788f9846891e9ea8d7ccd2928b1042e2e99c8d673f97e2b85f5bc20112",
|
||||
"sha256:bb2d4f8acd681fa5787df77e7482391017f0090c70473bccd2aa7cad327800ad"
|
||||
"sha256:f0657d4b887309086b71b55f6aa4a95f967b35fe115128b501f95422c423b12c",
|
||||
"sha256:f645016ae5270ac2025a70788cd2bd636244a0c5705b323cc086994bf828181e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.1.0"
|
||||
"version": "==2.0.0.dev124"
|
||||
},
|
||||
"django-recaptcha": {
|
||||
"hashes": [
|
||||
@ -254,11 +254,11 @@
|
||||
},
|
||||
"django-redis": {
|
||||
"hashes": [
|
||||
"sha256:af0b393864e91228dd30d8c85b5c44d670b5524cb161b7f9e41acc98b6e5ace7",
|
||||
"sha256:f46115577063d00a890867c6964ba096057f07cb756e78e0503b89cd18e4e083"
|
||||
"sha256:a5b1e3ffd3198735e6c529d9bdf38ca3fcb3155515249b98dc4d966b8ddf9d2b",
|
||||
"sha256:e1aad4cc5bd743d8d0b13d5cae0cef5410eaace33e83bff5fc3a139ad8db50b4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.10.0"
|
||||
"version": "==4.11.0"
|
||||
},
|
||||
"django-rest-framework": {
|
||||
"hashes": [
|
||||
@ -277,10 +277,10 @@
|
||||
},
|
||||
"djangorestframework": {
|
||||
"hashes": [
|
||||
"sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8",
|
||||
"sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090"
|
||||
"sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
|
||||
"sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
|
||||
],
|
||||
"version": "==3.10.3"
|
||||
"version": "==3.11.0"
|
||||
},
|
||||
"djangorestframework-guardian": {
|
||||
"hashes": [
|
||||
@ -328,11 +328,11 @@
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:3a8b2dfd0a2c6a3636e7c016a7e54ae04b997d30e69d5eacdca7a6c2221a1402",
|
||||
"sha256:41e688146d000891f32b1669e8573c57e39e5060e7f5f647aa617cd9a9568278"
|
||||
"sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45",
|
||||
"sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==1.2.0"
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"inflection": {
|
||||
"hashes": [
|
||||
@ -369,11 +369,11 @@
|
||||
},
|
||||
"kombu": {
|
||||
"hashes": [
|
||||
"sha256:389ba09e03b15b55b1a7371a441c894fd8121d174f5583bbbca032b9ea8c9edd",
|
||||
"sha256:7b92303af381ef02fad6899fd5f5a9a96031d781356cd8e505fa54ae5ddee181"
|
||||
"sha256:2a9e7adff14d046c9996752b2c48b6d9185d0b992106d5160e1a179907a5d4ac",
|
||||
"sha256:67b32ccb6fea030f8799f8fd50dd08e03a4b99464ebc4952d71d8747b1a52ad1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.5.0"
|
||||
"version": "==4.6.7"
|
||||
},
|
||||
"ldap3": {
|
||||
"hashes": [
|
||||
@ -450,10 +450,10 @@
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:53ff73f186307d9c8ef17a9600309154a6ae27f25579e80af4db8f047ba14bc2",
|
||||
"sha256:a0ea684c39bc4315ba7aae406596ef191fd84f873d2d2751f84d64e81a7a2d45"
|
||||
"sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d",
|
||||
"sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"
|
||||
],
|
||||
"version": "==8.0.0"
|
||||
"version": "==8.0.2"
|
||||
},
|
||||
"oauthlib": {
|
||||
"hashes": [
|
||||
@ -625,10 +625,10 @@
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f",
|
||||
"sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a"
|
||||
"sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
|
||||
"sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
|
||||
],
|
||||
"version": "==2.4.5"
|
||||
"version": "==2.4.6"
|
||||
},
|
||||
"pyrsistent": {
|
||||
"hashes": [
|
||||
@ -638,11 +638,11 @@
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
|
||||
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
|
||||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||
],
|
||||
"markers": "python_version >= '2.7'",
|
||||
"version": "==2.8.0"
|
||||
"version": "==2.8.1"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
@ -685,20 +685,20 @@
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
|
||||
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
|
||||
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
|
||||
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
|
||||
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
|
||||
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
|
||||
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
|
||||
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
|
||||
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
|
||||
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
|
||||
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
|
||||
"sha256:21a8e19e2007a4047ffabbd8f0ee32c0dabae3b7f4b6c645110ae53e7714b470",
|
||||
"sha256:74ad685bfb065f4bdd36d24aa97092f04bcbb1179b5ffdd3d5f994023fb8c292",
|
||||
"sha256:79c3ba1da22e61c2a71aaa382c57518ab492278c8974c40187b900b50f3e0282",
|
||||
"sha256:94ad913ab3fd967d14ecffda8182d7d0e1f7dd919b352773c492ec51890d3224",
|
||||
"sha256:998db501e3a627c3e5678d6505f0e182d1529545df289db036cdc717f35d8058",
|
||||
"sha256:9b69d4645bff5820713e8912bc61c4277dc127a6f8c197b52b6436503c42600f",
|
||||
"sha256:9da13b536533518343a04f3c6564782ec8a13c705310b26b4832d77fa4d92a47",
|
||||
"sha256:a76159f13b47fb44fb2acac8fef798a1940dd31b4acec6f4560bd11b2d92d31b",
|
||||
"sha256:a9e9175c1e47a089a2b45d9e2afc6aae1f1f725538c32eec761894a42ba1227f",
|
||||
"sha256:ea51ce7b96646ecd3bb12c2702e570c2bd7dd4d9f146db7fa83c5008ede35f66",
|
||||
"sha256:ffbaaa05de60fc444eda3f6300d1af27d965b09b67f1fb4ebcc88dd0fb4ab1b4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.2"
|
||||
"version": "==5.3b1"
|
||||
},
|
||||
"qrcode": {
|
||||
"hashes": [
|
||||
@ -752,6 +752,7 @@
|
||||
"sha256:a0ff786d2a7dbe55f9544b3f6ebbcc495d7e730df92a08434604f6f470b899c5",
|
||||
"sha256:b1b7fcee6aedcdc7e62c3a73f238b3d080c7ba6650cd808bce8d7761ec484070",
|
||||
"sha256:b66832ea8077d9b3f6e311c4a53d06273db5dc2db6e8a908550f3c14d67e718c",
|
||||
"sha256:be018933c2f4ee7de55e7bd7d0d801b3dfb09d21dad0cce8a97995fd3e44be30",
|
||||
"sha256:d0d3ac228c9bbab08134b4004d748cf9f8743504875b3603b3afbb97e3472947",
|
||||
"sha256:d10e9dd744cf85c219bf747c75194b624cc7a94f0c80ead624b06bfa9f61d3bc",
|
||||
"sha256:ea4362548ee0cbc266949d8a441238d9ad3600ca9910c3fe4e82ee3a50706973",
|
||||
@ -770,11 +771,11 @@
|
||||
},
|
||||
"sentry-sdk": {
|
||||
"hashes": [
|
||||
"sha256:a7c2c8d3f53b6b57454830cd6a4b73d272f1ba91952f59e6545b3cf885f3c22f",
|
||||
"sha256:bfc486af718c268cf49ff43d6334ed4db7333ace420240b630acdd8f8a3a8f60"
|
||||
"sha256:05285942901d38c7ce2498aba50d8e87b361fc603281a5902dda98f3f8c5e145",
|
||||
"sha256:c6b919623e488134a728f16326c6f0bcdab7e3f59e7f4c472a90eea4d6d8fe82"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.13.4"
|
||||
"version": "==0.13.5"
|
||||
},
|
||||
"service-identity": {
|
||||
"hashes": [
|
||||
@ -824,11 +825,10 @@
|
||||
},
|
||||
"uritemplate": {
|
||||
"hashes": [
|
||||
"sha256:01c69f4fe8ed503b2951bef85d996a9d22434d2431584b5b107b2981ff416fbd",
|
||||
"sha256:1b9c467a940ce9fb9f50df819e8ddd14696f89b9a8cc87ac77952ba416e0a8fd",
|
||||
"sha256:c02643cebe23fc8adb5e6becffe201185bf06c40bda5c0b4028a93f1527d011d"
|
||||
"sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f",
|
||||
"sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"
|
||||
],
|
||||
"version": "==3.0.0"
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"extras": [
|
||||
@ -858,6 +858,13 @@
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
||||
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
|
||||
],
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"asgiref": {
|
||||
"hashes": [
|
||||
"sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
|
||||
@ -867,10 +874,17 @@
|
||||
},
|
||||
"astroid": {
|
||||
"hashes": [
|
||||
"sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4",
|
||||
"sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4"
|
||||
"sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a",
|
||||
"sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"
|
||||
],
|
||||
"version": "==2.2.5"
|
||||
"version": "==2.3.3"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||
],
|
||||
"version": "==19.3.0"
|
||||
},
|
||||
"autopep8": {
|
||||
"hashes": [
|
||||
@ -887,6 +901,14 @@
|
||||
"index": "pypi",
|
||||
"version": "==1.6.2"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b",
|
||||
"sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==19.10b0"
|
||||
},
|
||||
"bumpversion": {
|
||||
"hashes": [
|
||||
"sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e",
|
||||
@ -895,59 +917,65 @@
|
||||
"index": "pypi",
|
||||
"version": "==0.5.3"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
||||
],
|
||||
"version": "==7.0"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
|
||||
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
|
||||
"sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
|
||||
"sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.4.1"
|
||||
"version": "==0.4.3"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6",
|
||||
"sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650",
|
||||
"sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5",
|
||||
"sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d",
|
||||
"sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351",
|
||||
"sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755",
|
||||
"sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef",
|
||||
"sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca",
|
||||
"sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca",
|
||||
"sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9",
|
||||
"sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc",
|
||||
"sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5",
|
||||
"sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f",
|
||||
"sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe",
|
||||
"sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888",
|
||||
"sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5",
|
||||
"sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce",
|
||||
"sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5",
|
||||
"sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e",
|
||||
"sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e",
|
||||
"sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9",
|
||||
"sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437",
|
||||
"sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1",
|
||||
"sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c",
|
||||
"sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24",
|
||||
"sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47",
|
||||
"sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2",
|
||||
"sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28",
|
||||
"sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c",
|
||||
"sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7",
|
||||
"sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0",
|
||||
"sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"
|
||||
"sha256:0101888bd1592a20ccadae081ba10e8b204d20235d18d05c6f7d5e904a38fc10",
|
||||
"sha256:04b961862334687549eb91cd5178a6fbe977ad365bddc7c60f2227f2f9880cf4",
|
||||
"sha256:1ca43dbd739c0fc30b0a3637a003a0d2c7edc1dd618359d58cc1e211742f8bd1",
|
||||
"sha256:1cbb88b34187bdb841f2599770b7e6ff8e259dc3bb64fc7893acf44998acf5f8",
|
||||
"sha256:232f0b52a5b978288f0bbc282a6c03fe48cd19a04202df44309919c142b3bb9c",
|
||||
"sha256:24bcfa86fd9ce86b73a8368383c39d919c497a06eebb888b6f0c12f13e920b1a",
|
||||
"sha256:25b8f60b5c7da71e64c18888f3067d5b6f1334b9681876b2fb41eea26de881ae",
|
||||
"sha256:2714160a63da18aed9340c70ed514973971ee7e665e6b336917ff4cca81a25b1",
|
||||
"sha256:2ca2cd5264e84b2cafc73f0045437f70c6378c0d7dbcddc9ee3fe192c1e29e5d",
|
||||
"sha256:2cc707fc9aad2592fc686d63ef72dc0031fc98b6fb921d2f5395d9ab84fbc3ef",
|
||||
"sha256:348630edea485f4228233c2f310a598abf8afa5f8c716c02a9698089687b6085",
|
||||
"sha256:40fbfd6b044c9db13aeec1daf5887d322c710d811f944011757526ef6e323fd9",
|
||||
"sha256:46c9c6a1d1190c0b75ec7c0f339088309952b82ae8d67a79ff1319eb4e749b96",
|
||||
"sha256:591506e088901bdc25620c37aec885e82cc896528f28c57e113751e3471fc314",
|
||||
"sha256:5ac71bba1e07eab403b082c4428f868c1c9e26a21041436b4905c4c3d4e49b08",
|
||||
"sha256:5f622f19abda4e934938e24f1d67599249abc201844933a6f01aaa8663094489",
|
||||
"sha256:65bead1ac8c8930cf92a1ccaedcce19a57298547d5d1db5c9d4d068a0675c38b",
|
||||
"sha256:7362a7f829feda10c7265b553455de596b83d1623b3d436b6d3c51c688c57bf6",
|
||||
"sha256:7f2675750c50151f806070ec11258edf4c328340916c53bac0adbc465abd6b1e",
|
||||
"sha256:960d7f42277391e8b1c0b0ae427a214e1b31a1278de6b73f8807b20c2e913bba",
|
||||
"sha256:a50b0888d8a021a3342d36a6086501e30de7d840ab68fca44913e97d14487dc1",
|
||||
"sha256:b7dbc5e8c39ea3ad3db22715f1b5401cd698a621218680c6daf42c2f9d36e205",
|
||||
"sha256:bb3d29df5d07d5399d58a394d0ef50adf303ab4fbf66dfd25b9ef258effcb692",
|
||||
"sha256:c0fff2733f7c2950f58a4fd09b5db257b00c6fec57bf3f68c5bae004d804b407",
|
||||
"sha256:c792d3707a86c01c02607ae74364854220fb3e82735f631cd0a345dea6b4cee5",
|
||||
"sha256:c90bda74e16bcd03861b09b1d37c0a4158feda5d5a036bb2d6e58de6ff65793e",
|
||||
"sha256:cfce79ce41cc1a1dc7fc85bb41eeeb32d34a4cf39a645c717c0550287e30ff06",
|
||||
"sha256:eeafb646f374988c22c8e6da5ab9fb81367ecfe81c70c292623373d2a021b1a1",
|
||||
"sha256:f425f50a6dd807cb9043d15a4fcfba3b5874a54d9587ccbb748899f70dc18c47",
|
||||
"sha256:fcd4459fe35a400b8f416bc57906862693c9f88b66dc925e7f2a933e77f6b18b",
|
||||
"sha256:ff3936dd5feaefb4f91c8c1f50a06c588b5dc69fba4f7d9c79a6617ad80bb7df"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.5.4"
|
||||
"version": "==5.0.1"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:a4ad4f6f9c6a4b7af7e2deec8d0cbff28501852e5010d6c2dc695d3d1fae7ca0",
|
||||
"sha256:fa98ec9cc9bf5d72a08ebf3654a9452e761fbb8566e3f80de199cbc15477e891"
|
||||
"sha256:662a1ff78792e3fd77f16f71b1f31149489434de4b62a74895bd5d6534e635a5",
|
||||
"sha256:687c37153486cf26c3fdcbdd177ef16de38dc3463f094b5f9c9955d91f277b14"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.8"
|
||||
"version": "==2.2.9"
|
||||
},
|
||||
"django-debug-toolbar": {
|
||||
"hashes": [
|
||||
@ -982,7 +1010,6 @@
|
||||
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
|
||||
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.21"
|
||||
},
|
||||
"lazy-object-proxy": {
|
||||
@ -1018,6 +1045,13 @@
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"pathspec": {
|
||||
"hashes": [
|
||||
"sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424",
|
||||
"sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"
|
||||
],
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"pbr": {
|
||||
"hashes": [
|
||||
"sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b",
|
||||
@ -1034,10 +1068,10 @@
|
||||
},
|
||||
"prospector": {
|
||||
"hashes": [
|
||||
"sha256:aba551e53dc1a5a432afa67385eaa81d7b4cf4c162dc1a4d0ee00b3a0712ad90"
|
||||
"sha256:ea910794b53cfefcb5dfb6b4eb0323e42d1a88132e165b85b016cc7f0b6ae635"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.1.7"
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
@ -1048,25 +1082,25 @@
|
||||
},
|
||||
"pydocstyle": {
|
||||
"hashes": [
|
||||
"sha256:04c84e034ebb56eb6396c820442b8c4499ac5eb94a3bda88951ac3dc519b6058",
|
||||
"sha256:66aff87ffe34b1e49bff2dd03a88ce6843be2f3346b0c9814410d34987fbab59"
|
||||
"sha256:4167fe954b8f27ebbbef2fbcf73c6e8ad1e7bb31488fce44a69fdfc4b0cd0fae",
|
||||
"sha256:a0de36e549125d0a16a72a8c8c6c9ba267750656e72e466e994c222f1b6e92cb"
|
||||
],
|
||||
"version": "==4.0.1"
|
||||
"version": "==5.0.1"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
|
||||
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
|
||||
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
|
||||
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
|
||||
],
|
||||
"version": "==1.6.0"
|
||||
"version": "==2.1.1"
|
||||
},
|
||||
"pylint": {
|
||||
"hashes": [
|
||||
"sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09",
|
||||
"sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1"
|
||||
"sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd",
|
||||
"sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.3.1"
|
||||
"version": "==2.4.4"
|
||||
},
|
||||
"pylint-celery": {
|
||||
"hashes": [
|
||||
@ -1076,11 +1110,11 @@
|
||||
},
|
||||
"pylint-django": {
|
||||
"hashes": [
|
||||
"sha256:75c69d1ec2275918c37f175976da20e2f1e1e62e067098a685cd263ffa833dfd",
|
||||
"sha256:c7cb6384ea7b33ea77052a5ae07358c10d377807390ef27b2e6ff997303fadb7"
|
||||
"sha256:9bdb0e022b19881218a25ffb8ad05e83b83bc5cdbc58e5ee8ffbe99965193f6c",
|
||||
"sha256:9eea6a026eaa5ecfad5fed7a33faf77ef55a43cc78afbcaf2f6ddd071156b3f8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.10"
|
||||
"version": "==2.0.12"
|
||||
},
|
||||
"pylint-flask": {
|
||||
"hashes": [
|
||||
@ -1104,20 +1138,46 @@
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
|
||||
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
|
||||
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
|
||||
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
|
||||
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
|
||||
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
|
||||
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
|
||||
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
|
||||
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
|
||||
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
|
||||
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
|
||||
"sha256:21a8e19e2007a4047ffabbd8f0ee32c0dabae3b7f4b6c645110ae53e7714b470",
|
||||
"sha256:74ad685bfb065f4bdd36d24aa97092f04bcbb1179b5ffdd3d5f994023fb8c292",
|
||||
"sha256:79c3ba1da22e61c2a71aaa382c57518ab492278c8974c40187b900b50f3e0282",
|
||||
"sha256:94ad913ab3fd967d14ecffda8182d7d0e1f7dd919b352773c492ec51890d3224",
|
||||
"sha256:998db501e3a627c3e5678d6505f0e182d1529545df289db036cdc717f35d8058",
|
||||
"sha256:9b69d4645bff5820713e8912bc61c4277dc127a6f8c197b52b6436503c42600f",
|
||||
"sha256:9da13b536533518343a04f3c6564782ec8a13c705310b26b4832d77fa4d92a47",
|
||||
"sha256:a76159f13b47fb44fb2acac8fef798a1940dd31b4acec6f4560bd11b2d92d31b",
|
||||
"sha256:a9e9175c1e47a089a2b45d9e2afc6aae1f1f725538c32eec761894a42ba1227f",
|
||||
"sha256:ea51ce7b96646ecd3bb12c2702e570c2bd7dd4d9f146db7fa83c5008ede35f66",
|
||||
"sha256:ffbaaa05de60fc444eda3f6300d1af27d965b09b67f1fb4ebcc88dd0fb4ab1b4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.2"
|
||||
"version": "==5.3b1"
|
||||
},
|
||||
"regex": {
|
||||
"hashes": [
|
||||
"sha256:032fdcc03406e1a6485ec09b826eac78732943840c4b29e503b789716f051d8d",
|
||||
"sha256:0e6cf1e747f383f52a0964452658c04300a9a01e8a89c55ea22813931b580aa8",
|
||||
"sha256:106e25a841921d8259dcef2a42786caae35bc750fb996f830065b3dfaa67b77e",
|
||||
"sha256:1768cf42a78a11dae63152685e7a1d90af7a8d71d2d4f6d2387edea53a9e0588",
|
||||
"sha256:27d1bd20d334f50b7ef078eba0f0756a640fd25f5f1708d3b5bed18a5d6bced9",
|
||||
"sha256:29b20f66f2e044aafba86ecf10a84e611b4667643c42baa004247f5dfef4f90b",
|
||||
"sha256:4850c78b53acf664a6578bba0e9ebeaf2807bb476c14ec7e0f936f2015133cae",
|
||||
"sha256:57eacd38a5ec40ed7b19a968a9d01c0d977bda55664210be713e750dd7b33540",
|
||||
"sha256:724eb24b92fc5fdc1501a1b4df44a68b9c1dda171c8ef8736799e903fb100f63",
|
||||
"sha256:77ae8d926f38700432807ba293d768ba9e7652df0cbe76df2843b12f80f68885",
|
||||
"sha256:78b3712ec529b2a71731fbb10b907b54d9c53a17ca589b42a578bc1e9a2c82ea",
|
||||
"sha256:7bbbdbada3078dc360d4692a9b28479f569db7fc7f304b668787afc9feb38ec8",
|
||||
"sha256:8d9ef7f6c403e35e73b7fc3cde9f6decdc43b1cb2ff8d058c53b9084bfcb553e",
|
||||
"sha256:a83049eb717ae828ced9cf607845929efcb086a001fc8af93ff15c50012a5716",
|
||||
"sha256:adc35d38952e688535980ae2109cad3a109520033642e759f987cf47fe278aa1",
|
||||
"sha256:c29a77ad4463f71a506515d9ec3a899ed026b4b015bf43245c919ff36275444b",
|
||||
"sha256:cfd31b3300fefa5eecb2fe596c6dee1b91b3a05ece9d5cfd2631afebf6c6fadd",
|
||||
"sha256:d3ee0b035816e0520fac928de31b6572106f0d75597f6fa3206969a02baba06f",
|
||||
"sha256:d508875793efdf6bab3d47850df8f40d4040ae9928d9d80864c1768d6aeaf8e3",
|
||||
"sha256:ef0b828a7e22e58e06a1cceddba7b4665c6af8afeb22a0d8083001330572c147",
|
||||
"sha256:faad39fdbe2c2ccda9846cd21581063086330efafa47d87afea4073a08128656"
|
||||
],
|
||||
"version": "==2019.12.20"
|
||||
},
|
||||
"requirements-detector": {
|
||||
"hashes": [
|
||||
@ -1166,6 +1226,13 @@
|
||||
],
|
||||
"version": "==1.31.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
||||
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
|
||||
],
|
||||
"version": "==0.10.0"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161",
|
||||
@ -1189,7 +1256,7 @@
|
||||
"sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66",
|
||||
"sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"
|
||||
],
|
||||
"markers": "implementation_name == 'cpython'",
|
||||
"markers": "implementation_name == 'cpython' and python_version < '3.8'",
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"unittest-xml-reporting": {
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
# passbook
|
||||
|
||||

|
||||
|
||||
## Quick instance
|
||||
|
||||
```
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
FROM python:3.7-slim-buster as locker
|
||||
|
||||
COPY ./Pipfile /app/
|
||||
COPY ./Pipfile.lock /app/
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
RUN pip install pipenv && \
|
||||
pipenv lock -r > requirements.txt && \
|
||||
pipenv lock -rd > requirements-dev.txt
|
||||
|
||||
FROM python:3.7-slim-buster
|
||||
|
||||
COPY --from=locker /app/requirements.txt /app/
|
||||
COPY --from=locker /app/requirements-dev.txt /app/
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends postgresql-client-11 && \
|
||||
rm -rf /var/lib/apt/ && \
|
||||
pip install -r requirements.txt --no-cache-dir && \
|
||||
adduser --system --no-create-home --uid 1000 --group --home /app passbook
|
||||
@ -1,3 +0,0 @@
|
||||
FROM docker.beryju.org/passbook/base:latest
|
||||
|
||||
RUN pip install -r /app/requirements-dev.txt --no-cache-dir
|
||||
@ -21,7 +21,7 @@ services:
|
||||
labels:
|
||||
- traefik.enable=false
|
||||
server:
|
||||
image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest}
|
||||
image: beryju/passbook:${SERVER_TAG:-latest}
|
||||
command:
|
||||
- uwsgi
|
||||
- uwsgi.ini
|
||||
@ -40,7 +40,7 @@ services:
|
||||
- traefik.docker.network=internal
|
||||
- traefik.frontend.rule=PathPrefix:/
|
||||
worker:
|
||||
image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest}
|
||||
image: beryju/passbook:${SERVER_TAG:-latest}
|
||||
command:
|
||||
- celery
|
||||
- worker
|
||||
@ -60,7 +60,7 @@ services:
|
||||
- PASSBOOK_POSTGRESQL__HOST=postgresql
|
||||
- PASSBOOK_POSTGRESQL__PASSWORD=${PG_PASS:-thisisnotagoodpassword}
|
||||
static:
|
||||
image: docker.beryju.org/passbook/static:latest
|
||||
image: beryju/passbook-static:latest
|
||||
networks:
|
||||
- internal
|
||||
labels:
|
||||
|
||||
@ -9,7 +9,7 @@ This installation Method is for test-setups and small-scale productive setups.
|
||||
|
||||
## Install
|
||||
|
||||
Download the latest `docker-compose.yml` from [here](https://git.beryju.org/BeryJu.org/passbook/raw/master/docker-compose.yml). Place it in a directory of your choice.
|
||||
Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml). Place it in a directory of your choice.
|
||||
|
||||
passbook needs to know it's primary URL to create links in E-Mails and set cookies, so you have to run the following command:
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: passbook-docs
|
||||
image: "docker.beryju.org/passbook/docs:latest"
|
||||
image: "beryju/passbook-docs:latest"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
apiVersion: v1
|
||||
appVersion: "0.7.5-beta"
|
||||
appVersion: "0.7.12-beta"
|
||||
description: A Helm chart for passbook.
|
||||
name: passbook
|
||||
version: "0.7.5-beta"
|
||||
version: "0.7.12-beta"
|
||||
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png
|
||||
|
||||
@ -21,7 +21,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}-static
|
||||
image: "docker.beryju.org/passbook/static:{{ .Values.image.tag }}"
|
||||
image: "beryju/passbook-static:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
|
||||
@ -26,7 +26,7 @@ spec:
|
||||
name: {{ include "passbook.fullname" . }}-config
|
||||
initContainers:
|
||||
- name: passbook-database-migrations
|
||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
||||
image: "beryju/passbook:{{ .Values.image.tag }}"
|
||||
command:
|
||||
- ./manage.py
|
||||
args:
|
||||
@ -56,7 +56,7 @@ spec:
|
||||
key: postgresql-password
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
||||
image: "beryju/passbook:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- uwsgi
|
||||
|
||||
@ -26,7 +26,7 @@ spec:
|
||||
name: {{ include "passbook.fullname" . }}-config
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
||||
image: "beryju/passbook:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- celery
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
image:
|
||||
tag: 0.7.5-beta
|
||||
tag: 0.7.12-beta
|
||||
|
||||
nameOverride: ""
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ nav:
|
||||
- Sentry: integrations/services/sentry/index.md
|
||||
|
||||
repo_name: "BeryJu.org/passbook"
|
||||
repo_url: https://git.beryju.org/BeryJu.org/passbook
|
||||
repo_url: https://github.com/BeryJu/passbook
|
||||
theme:
|
||||
name: "material"
|
||||
logo: "images/logo.svg"
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
"""passbook"""
|
||||
__version__ = '0.7.5-beta'
|
||||
__version__ = "0.7.12-beta"
|
||||
|
||||
@ -5,7 +5,7 @@ from django.apps import AppConfig
|
||||
class PassbookAdminConfig(AppConfig):
|
||||
"""passbook admin app config"""
|
||||
|
||||
name = 'passbook.admin'
|
||||
label = 'passbook_admin'
|
||||
mountpoint = 'administration/'
|
||||
verbose_name = 'passbook Admin'
|
||||
name = "passbook.admin"
|
||||
label = "passbook_admin"
|
||||
mountpoint = "administration/"
|
||||
verbose_name = "passbook Admin"
|
||||
|
||||
@ -16,7 +16,7 @@ class YAMLField(forms.CharField):
|
||||
"""Django's JSON Field converted to YAML"""
|
||||
|
||||
default_error_messages = {
|
||||
'invalid': _("'%(value)s' value must be valid YAML."),
|
||||
"invalid": _("'%(value)s' value must be valid YAML."),
|
||||
}
|
||||
widget = forms.Textarea
|
||||
|
||||
@ -31,9 +31,7 @@ class YAMLField(forms.CharField):
|
||||
converted = yaml.safe_load(value)
|
||||
except yaml.YAMLError:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['invalid'],
|
||||
code='invalid',
|
||||
params={'value': value},
|
||||
self.error_messages["invalid"], code="invalid", params={"value": value},
|
||||
)
|
||||
if isinstance(converted, str):
|
||||
return YAMLString(converted)
|
||||
|
||||
@ -9,29 +9,32 @@ class TagModelForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Check if we have an instance, load tags otherwise use an empty dict
|
||||
instance = kwargs.get('instance', None)
|
||||
instance = kwargs.get("instance", None)
|
||||
tags = instance.tags if instance else {}
|
||||
# Make sure all predefined tags exist in tags, and set default if they don't
|
||||
predefined_tags = self._meta.model().get_predefined_tags() # pylint: disable=no-member
|
||||
predefined_tags = (
|
||||
self._meta.model().get_predefined_tags() # pylint: disable=no-member
|
||||
)
|
||||
for key, value in predefined_tags.items():
|
||||
if key not in tags:
|
||||
tags[key] = value
|
||||
# Format JSON
|
||||
kwargs['initial']['tags'] = tags
|
||||
kwargs["initial"]["tags"] = tags
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean_tags(self):
|
||||
"""Make sure all required tags are set"""
|
||||
if hasattr(self.instance, 'get_required_keys') and hasattr(self.instance, 'tags'):
|
||||
if hasattr(self.instance, "get_required_keys") and hasattr(
|
||||
self.instance, "tags"
|
||||
):
|
||||
for key in self.instance.get_required_keys():
|
||||
if key not in self.cleaned_data.get('tags'):
|
||||
if key not in self.cleaned_data.get("tags"):
|
||||
raise forms.ValidationError("Tag %s missing." % key)
|
||||
return self.cleaned_data.get('tags')
|
||||
return self.cleaned_data.get("tags")
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class TagModelFormMeta:
|
||||
"""Base Meta class that uses the YAMLField"""
|
||||
|
||||
field_classes = {
|
||||
'tags': YAMLField
|
||||
}
|
||||
field_classes = {"tags": YAMLField}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"""passbook core source form fields"""
|
||||
# from django import forms
|
||||
|
||||
SOURCE_FORM_FIELDS = ['name', 'slug', 'enabled', 'policies']
|
||||
SOURCE_SERIALIZER_FIELDS = ['pk', 'name', 'slug', 'enabled', 'policies']
|
||||
SOURCE_FORM_FIELDS = ["name", "slug", "enabled", "policies"]
|
||||
SOURCE_SERIALIZER_FIELDS = ["pk", "name", "slug", "enabled", "policies"]
|
||||
|
||||
# class SourceForm(forms.Form)
|
||||
|
||||
@ -12,10 +12,10 @@ class UserForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
||||
model = User
|
||||
fields = ['username', 'name', 'email', 'is_staff', 'is_active', 'attributes']
|
||||
fields = ["username", "name", "email", "is_staff", "is_active", "attributes"]
|
||||
widgets = {
|
||||
'name': forms.TextInput,
|
||||
"name": forms.TextInput,
|
||||
}
|
||||
field_classes = {
|
||||
'attributes': YAMLField,
|
||||
"attributes": YAMLField,
|
||||
}
|
||||
|
||||
@ -11,15 +11,16 @@ def impersonate(get_response):
|
||||
|
||||
# User is superuser and has __impersonate ID set
|
||||
if request.user.is_superuser and "__impersonate" in request.GET:
|
||||
request.session['impersonate_id'] = request.GET["__impersonate"]
|
||||
request.session["impersonate_id"] = request.GET["__impersonate"]
|
||||
# user wants to stop impersonation
|
||||
elif "__unimpersonate" in request.GET and 'impersonate_id' in request.session:
|
||||
del request.session['impersonate_id']
|
||||
elif "__unimpersonate" in request.GET and "impersonate_id" in request.session:
|
||||
del request.session["impersonate_id"]
|
||||
|
||||
# Actually impersonate user
|
||||
if request.user.is_superuser and 'impersonate_id' in request.session:
|
||||
request.user = User.objects.get(pk=request.session['impersonate_id'])
|
||||
if request.user.is_superuser and "impersonate_id" in request.session:
|
||||
request.user = User.objects.get(pk=request.session["impersonate_id"])
|
||||
|
||||
response = get_response(request)
|
||||
return response
|
||||
|
||||
return middleware
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
"""passbook admin settings"""
|
||||
|
||||
MIDDLEWARE = [
|
||||
'passbook.admin.middleware.impersonate',
|
||||
"passbook.admin.middleware.impersonate",
|
||||
]
|
||||
|
||||
@ -10,10 +10,11 @@ from passbook.lib.utils.template import render_to_string
|
||||
register = template.Library()
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def get_links(model_instance):
|
||||
"""Find all link_ methods on an object instance, run them and return as dict"""
|
||||
prefix = 'link_'
|
||||
prefix = "link_"
|
||||
links = {}
|
||||
|
||||
if not isinstance(model_instance, Model):
|
||||
@ -21,9 +22,11 @@ def get_links(model_instance):
|
||||
return links
|
||||
|
||||
try:
|
||||
for name, method in inspect.getmembers(model_instance, predicate=inspect.ismethod):
|
||||
for name, method in inspect.getmembers(
|
||||
model_instance, predicate=inspect.ismethod
|
||||
):
|
||||
if name.startswith(prefix):
|
||||
human_name = name.replace(prefix, '').replace('_', ' ').capitalize()
|
||||
human_name = name.replace(prefix, "").replace("_", " ").capitalize()
|
||||
link = method()
|
||||
if link:
|
||||
links[human_name] = link
|
||||
@ -36,7 +39,7 @@ def get_links(model_instance):
|
||||
@register.simple_tag(takes_context=True)
|
||||
def get_htmls(context, model_instance):
|
||||
"""Find all html_ methods on an object instance, run them and return as dict"""
|
||||
prefix = 'html_'
|
||||
prefix = "html_"
|
||||
htmls = []
|
||||
|
||||
if not isinstance(model_instance, Model):
|
||||
@ -44,9 +47,11 @@ def get_htmls(context, model_instance):
|
||||
return htmls
|
||||
|
||||
try:
|
||||
for name, method in inspect.getmembers(model_instance, predicate=inspect.ismethod):
|
||||
for name, method in inspect.getmembers(
|
||||
model_instance, predicate=inspect.ismethod
|
||||
):
|
||||
if name.startswith(prefix):
|
||||
template, _context = method(context.get('request'))
|
||||
template, _context = method(context.get("request"))
|
||||
htmls.append(render_to_string(template, _context))
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
@ -1,82 +1,157 @@
|
||||
"""passbook URL Configuration"""
|
||||
from django.urls import path
|
||||
|
||||
from passbook.admin.views import (applications, audit, debug, factors, groups,
|
||||
invitations, overview, policy,
|
||||
property_mapping, providers, sources, users)
|
||||
from passbook.admin.views import (
|
||||
applications,
|
||||
audit,
|
||||
debug,
|
||||
factors,
|
||||
groups,
|
||||
invitations,
|
||||
overview,
|
||||
policy,
|
||||
property_mapping,
|
||||
providers,
|
||||
sources,
|
||||
users,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path('', overview.AdministrationOverviewView.as_view(), name='overview'),
|
||||
path("", overview.AdministrationOverviewView.as_view(), name="overview"),
|
||||
# Applications
|
||||
path('applications/', applications.ApplicationListView.as_view(),
|
||||
name='applications'),
|
||||
path('applications/create/', applications.ApplicationCreateView.as_view(),
|
||||
name='application-create'),
|
||||
path('applications/<uuid:pk>/update/',
|
||||
applications.ApplicationUpdateView.as_view(), name='application-update'),
|
||||
path('applications/<uuid:pk>/delete/',
|
||||
applications.ApplicationDeleteView.as_view(), name='application-delete'),
|
||||
path(
|
||||
"applications/", applications.ApplicationListView.as_view(), name="applications"
|
||||
),
|
||||
path(
|
||||
"applications/create/",
|
||||
applications.ApplicationCreateView.as_view(),
|
||||
name="application-create",
|
||||
),
|
||||
path(
|
||||
"applications/<uuid:pk>/update/",
|
||||
applications.ApplicationUpdateView.as_view(),
|
||||
name="application-update",
|
||||
),
|
||||
path(
|
||||
"applications/<uuid:pk>/delete/",
|
||||
applications.ApplicationDeleteView.as_view(),
|
||||
name="application-delete",
|
||||
),
|
||||
# Sources
|
||||
path('sources/', sources.SourceListView.as_view(), name='sources'),
|
||||
path('sources/create/', sources.SourceCreateView.as_view(), name='source-create'),
|
||||
path('sources/<uuid:pk>/update/', sources.SourceUpdateView.as_view(), name='source-update'),
|
||||
path('sources/<uuid:pk>/delete/', sources.SourceDeleteView.as_view(), name='source-delete'),
|
||||
path("sources/", sources.SourceListView.as_view(), name="sources"),
|
||||
path("sources/create/", sources.SourceCreateView.as_view(), name="source-create"),
|
||||
path(
|
||||
"sources/<uuid:pk>/update/",
|
||||
sources.SourceUpdateView.as_view(),
|
||||
name="source-update",
|
||||
),
|
||||
path(
|
||||
"sources/<uuid:pk>/delete/",
|
||||
sources.SourceDeleteView.as_view(),
|
||||
name="source-delete",
|
||||
),
|
||||
# Policies
|
||||
path('policies/', policy.PolicyListView.as_view(), name='policies'),
|
||||
path('policies/create/', policy.PolicyCreateView.as_view(), name='policy-create'),
|
||||
path('policies/<uuid:pk>/update/', policy.PolicyUpdateView.as_view(), name='policy-update'),
|
||||
path('policies/<uuid:pk>/delete/', policy.PolicyDeleteView.as_view(), name='policy-delete'),
|
||||
path('policies/<uuid:pk>/test/', policy.PolicyTestView.as_view(), name='policy-test'),
|
||||
path("policies/", policy.PolicyListView.as_view(), name="policies"),
|
||||
path("policies/create/", policy.PolicyCreateView.as_view(), name="policy-create"),
|
||||
path(
|
||||
"policies/<uuid:pk>/update/",
|
||||
policy.PolicyUpdateView.as_view(),
|
||||
name="policy-update",
|
||||
),
|
||||
path(
|
||||
"policies/<uuid:pk>/delete/",
|
||||
policy.PolicyDeleteView.as_view(),
|
||||
name="policy-delete",
|
||||
),
|
||||
path(
|
||||
"policies/<uuid:pk>/test/", policy.PolicyTestView.as_view(), name="policy-test"
|
||||
),
|
||||
# Providers
|
||||
path('providers/', providers.ProviderListView.as_view(), name='providers'),
|
||||
path('providers/create/',
|
||||
providers.ProviderCreateView.as_view(), name='provider-create'),
|
||||
path('providers/<int:pk>/update/',
|
||||
providers.ProviderUpdateView.as_view(), name='provider-update'),
|
||||
path('providers/<int:pk>/delete/',
|
||||
providers.ProviderDeleteView.as_view(), name='provider-delete'),
|
||||
path("providers/", providers.ProviderListView.as_view(), name="providers"),
|
||||
path(
|
||||
"providers/create/",
|
||||
providers.ProviderCreateView.as_view(),
|
||||
name="provider-create",
|
||||
),
|
||||
path(
|
||||
"providers/<int:pk>/update/",
|
||||
providers.ProviderUpdateView.as_view(),
|
||||
name="provider-update",
|
||||
),
|
||||
path(
|
||||
"providers/<int:pk>/delete/",
|
||||
providers.ProviderDeleteView.as_view(),
|
||||
name="provider-delete",
|
||||
),
|
||||
# Factors
|
||||
path('factors/', factors.FactorListView.as_view(), name='factors'),
|
||||
path('factors/create/',
|
||||
factors.FactorCreateView.as_view(), name='factor-create'),
|
||||
path('factors/<uuid:pk>/update/',
|
||||
factors.FactorUpdateView.as_view(), name='factor-update'),
|
||||
path('factors/<uuid:pk>/delete/',
|
||||
factors.FactorDeleteView.as_view(), name='factor-delete'),
|
||||
path("factors/", factors.FactorListView.as_view(), name="factors"),
|
||||
path("factors/create/", factors.FactorCreateView.as_view(), name="factor-create"),
|
||||
path(
|
||||
"factors/<uuid:pk>/update/",
|
||||
factors.FactorUpdateView.as_view(),
|
||||
name="factor-update",
|
||||
),
|
||||
path(
|
||||
"factors/<uuid:pk>/delete/",
|
||||
factors.FactorDeleteView.as_view(),
|
||||
name="factor-delete",
|
||||
),
|
||||
# Factors
|
||||
path('property-mappings/', property_mapping.PropertyMappingListView.as_view(),
|
||||
name='property-mappings'),
|
||||
path('property-mappings/create/',
|
||||
property_mapping.PropertyMappingCreateView.as_view(), name='property-mapping-create'),
|
||||
path('property-mappings/<uuid:pk>/update/',
|
||||
property_mapping.PropertyMappingUpdateView.as_view(), name='property-mapping-update'),
|
||||
path('property-mappings/<uuid:pk>/delete/',
|
||||
property_mapping.PropertyMappingDeleteView.as_view(), name='property-mapping-delete'),
|
||||
path(
|
||||
"property-mappings/",
|
||||
property_mapping.PropertyMappingListView.as_view(),
|
||||
name="property-mappings",
|
||||
),
|
||||
path(
|
||||
"property-mappings/create/",
|
||||
property_mapping.PropertyMappingCreateView.as_view(),
|
||||
name="property-mapping-create",
|
||||
),
|
||||
path(
|
||||
"property-mappings/<uuid:pk>/update/",
|
||||
property_mapping.PropertyMappingUpdateView.as_view(),
|
||||
name="property-mapping-update",
|
||||
),
|
||||
path(
|
||||
"property-mappings/<uuid:pk>/delete/",
|
||||
property_mapping.PropertyMappingDeleteView.as_view(),
|
||||
name="property-mapping-delete",
|
||||
),
|
||||
# Invitations
|
||||
path('invitations/', invitations.InvitationListView.as_view(), name='invitations'),
|
||||
path('invitations/create/',
|
||||
invitations.InvitationCreateView.as_view(), name='invitation-create'),
|
||||
path('invitations/<uuid:pk>/delete/',
|
||||
invitations.InvitationDeleteView.as_view(), name='invitation-delete'),
|
||||
path("invitations/", invitations.InvitationListView.as_view(), name="invitations"),
|
||||
path(
|
||||
"invitations/create/",
|
||||
invitations.InvitationCreateView.as_view(),
|
||||
name="invitation-create",
|
||||
),
|
||||
path(
|
||||
"invitations/<uuid:pk>/delete/",
|
||||
invitations.InvitationDeleteView.as_view(),
|
||||
name="invitation-delete",
|
||||
),
|
||||
# Users
|
||||
path('users/', users.UserListView.as_view(),
|
||||
name='users'),
|
||||
path('users/create/', users.UserCreateView.as_view(), name='user-create'),
|
||||
path('users/<int:pk>/update/',
|
||||
users.UserUpdateView.as_view(), name='user-update'),
|
||||
path('users/<int:pk>/delete/',
|
||||
users.UserDeleteView.as_view(), name='user-delete'),
|
||||
path('users/<int:pk>/reset/',
|
||||
users.UserPasswordResetView.as_view(), name='user-password-reset'),
|
||||
path("users/", users.UserListView.as_view(), name="users"),
|
||||
path("users/create/", users.UserCreateView.as_view(), name="user-create"),
|
||||
path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"),
|
||||
path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"),
|
||||
path(
|
||||
"users/<int:pk>/reset/",
|
||||
users.UserPasswordResetView.as_view(),
|
||||
name="user-password-reset",
|
||||
),
|
||||
# Groups
|
||||
path('group/', groups.GroupListView.as_view(), name='group'),
|
||||
path('group/create/', groups.GroupCreateView.as_view(), name='group-create'),
|
||||
path('group/<uuid:pk>/update/', groups.GroupUpdateView.as_view(), name='group-update'),
|
||||
path('group/<uuid:pk>/delete/', groups.GroupDeleteView.as_view(), name='group-delete'),
|
||||
path("group/", groups.GroupListView.as_view(), name="group"),
|
||||
path("group/create/", groups.GroupCreateView.as_view(), name="group-create"),
|
||||
path(
|
||||
"group/<uuid:pk>/update/", groups.GroupUpdateView.as_view(), name="group-update"
|
||||
),
|
||||
path(
|
||||
"group/<uuid:pk>/delete/", groups.GroupDeleteView.as_view(), name="group-delete"
|
||||
),
|
||||
# Audit Log
|
||||
path('audit/', audit.EventListView.as_view(), name='audit-log'),
|
||||
path("audit/", audit.EventListView.as_view(), name="audit-log"),
|
||||
# Groups
|
||||
path('groups/', groups.GroupListView.as_view(), name='groups'),
|
||||
path("groups/", groups.GroupListView.as_view(), name="groups"),
|
||||
# Debug
|
||||
path('debug/request/', debug.DebugRequestView.as_view(), name='debug-request'),
|
||||
path("debug/request/", debug.DebugRequestView.as_view(), name="debug-request"),
|
||||
]
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
"""passbook Application administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
@ -18,55 +19,61 @@ class ApplicationListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all applications"""
|
||||
|
||||
model = Application
|
||||
permission_required = 'passbook_core.view_application'
|
||||
ordering = 'name'
|
||||
permission_required = "passbook_core.view_application"
|
||||
ordering = "name"
|
||||
paginate_by = 40
|
||||
template_name = 'administration/application/list.html'
|
||||
template_name = "administration/application/list.html"
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_subclasses()
|
||||
|
||||
|
||||
class ApplicationCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
class ApplicationCreateView(
|
||||
SuccessMessageMixin,
|
||||
LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin,
|
||||
CreateAssignPermView,
|
||||
):
|
||||
"""Create new Application"""
|
||||
|
||||
model = Application
|
||||
form_class = ApplicationForm
|
||||
permission_required = 'passbook_core.add_application'
|
||||
permission_required = "passbook_core.add_application"
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:applications')
|
||||
success_message = _('Successfully created Application')
|
||||
template_name = "generic/create.html"
|
||||
success_url = reverse_lazy("passbook_admin:applications")
|
||||
success_message = _("Successfully created Application")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['type'] = 'Application'
|
||||
kwargs["type"] = "Application"
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class ApplicationUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
class ApplicationUpdateView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||
):
|
||||
"""Update application"""
|
||||
|
||||
model = Application
|
||||
form_class = ApplicationForm
|
||||
permission_required = 'passbook_core.change_application'
|
||||
permission_required = "passbook_core.change_application"
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:applications')
|
||||
success_message = _('Successfully updated Application')
|
||||
template_name = "generic/update.html"
|
||||
success_url = reverse_lazy("passbook_admin:applications")
|
||||
success_message = _("Successfully updated Application")
|
||||
|
||||
|
||||
class ApplicationDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
class ApplicationDeleteView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||
):
|
||||
"""Delete application"""
|
||||
|
||||
model = Application
|
||||
permission_required = 'passbook_core.delete_application'
|
||||
permission_required = "passbook_core.delete_application"
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:applications')
|
||||
success_message = _('Successfully deleted Application')
|
||||
template_name = "generic/delete.html"
|
||||
success_url = reverse_lazy("passbook_admin:applications")
|
||||
success_message = _("Successfully deleted Application")
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(self.request, self.success_message)
|
||||
|
||||
@ -9,10 +9,10 @@ class EventListView(PermissionListMixin, ListView):
|
||||
"""Show list of all invitations"""
|
||||
|
||||
model = Event
|
||||
template_name = 'administration/audit/list.html'
|
||||
permission_required = 'passbook_audit.view_event'
|
||||
ordering = '-created'
|
||||
template_name = "administration/audit/list.html"
|
||||
permission_required = "passbook_audit.view_event"
|
||||
ordering = "-created"
|
||||
paginate_by = 10
|
||||
|
||||
def get_queryset(self):
|
||||
return Event.objects.all().order_by('-created')
|
||||
return Event.objects.all().order_by("-created")
|
||||
|
||||
@ -6,10 +6,10 @@ from django.views.generic import TemplateView
|
||||
class DebugRequestView(LoginRequiredMixin, TemplateView):
|
||||
"""Show debug info about request"""
|
||||
|
||||
template_name = 'administration/debug/request.html'
|
||||
template_name = "administration/debug/request.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['request_dict'] = {}
|
||||
kwargs["request_dict"] = {}
|
||||
for key in dir(self.request):
|
||||
kwargs['request_dict'][key] = getattr(self.request, key)
|
||||
kwargs["request_dict"][key] = getattr(self.request, key)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
"""passbook Factor administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import Http404
|
||||
from django.urls import reverse_lazy
|
||||
@ -18,62 +19,69 @@ from passbook.lib.views import CreateAssignPermView
|
||||
def all_subclasses(cls):
|
||||
"""Recursively return all subclassess of cls"""
|
||||
return set(cls.__subclasses__()).union(
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)])
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)]
|
||||
)
|
||||
|
||||
|
||||
class FactorListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all factors"""
|
||||
|
||||
model = Factor
|
||||
template_name = 'administration/factor/list.html'
|
||||
permission_required = 'passbook_core.view_factor'
|
||||
ordering = 'order'
|
||||
template_name = "administration/factor/list.html"
|
||||
permission_required = "passbook_core.view_factor"
|
||||
ordering = "order"
|
||||
paginate_by = 40
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['types'] = {
|
||||
x.__name__: x._meta.verbose_name for x in all_subclasses(Factor)}
|
||||
kwargs["types"] = {
|
||||
x.__name__: x._meta.verbose_name for x in all_subclasses(Factor)
|
||||
}
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_subclasses()
|
||||
|
||||
|
||||
class FactorCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
class FactorCreateView(
|
||||
SuccessMessageMixin,
|
||||
LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin,
|
||||
CreateAssignPermView,
|
||||
):
|
||||
"""Create new Factor"""
|
||||
|
||||
model = Factor
|
||||
template_name = 'generic/create.html'
|
||||
permission_required = 'passbook_core.add_factor'
|
||||
template_name = "generic/create.html"
|
||||
permission_required = "passbook_core.add_factor"
|
||||
|
||||
success_url = reverse_lazy('passbook_admin:factors')
|
||||
success_message = _('Successfully created Factor')
|
||||
success_url = reverse_lazy("passbook_admin:factors")
|
||||
success_message = _("Successfully created Factor")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs = super().get_context_data(**kwargs)
|
||||
factor_type = self.request.GET.get('type')
|
||||
factor_type = self.request.GET.get("type")
|
||||
model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
|
||||
kwargs['type'] = model._meta.verbose_name
|
||||
kwargs["type"] = model._meta.verbose_name
|
||||
return kwargs
|
||||
|
||||
def get_form_class(self):
|
||||
factor_type = self.request.GET.get('type')
|
||||
factor_type = self.request.GET.get("type")
|
||||
model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
|
||||
if not model:
|
||||
raise Http404
|
||||
return path_to_class(model.form)
|
||||
|
||||
|
||||
class FactorUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
class FactorUpdateView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||
):
|
||||
"""Update factor"""
|
||||
|
||||
model = Factor
|
||||
permission_required = 'passbook_core.update_application'
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:factors')
|
||||
success_message = _('Successfully updated Factor')
|
||||
permission_required = "passbook_core.update_application"
|
||||
template_name = "generic/update.html"
|
||||
success_url = reverse_lazy("passbook_admin:factors")
|
||||
success_message = _("Successfully updated Factor")
|
||||
|
||||
def get_form_class(self):
|
||||
form_class_path = self.get_object().form
|
||||
@ -81,21 +89,26 @@ class FactorUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
return form_class
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return Factor.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
return (
|
||||
Factor.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
|
||||
)
|
||||
|
||||
|
||||
class FactorDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
class FactorDeleteView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||
):
|
||||
"""Delete factor"""
|
||||
|
||||
model = Factor
|
||||
template_name = 'generic/delete.html'
|
||||
permission_required = 'passbook_core.delete_factor'
|
||||
success_url = reverse_lazy('passbook_admin:factors')
|
||||
success_message = _('Successfully deleted Factor')
|
||||
template_name = "generic/delete.html"
|
||||
permission_required = "passbook_core.delete_factor"
|
||||
success_url = reverse_lazy("passbook_admin:factors")
|
||||
success_message = _("Successfully deleted Factor")
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return Factor.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
return (
|
||||
Factor.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
|
||||
)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(self.request, self.success_message)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
"""passbook Group administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
@ -18,40 +19,45 @@ class GroupListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all groups"""
|
||||
|
||||
model = Group
|
||||
permission_required = 'passbook_core.view_group'
|
||||
ordering = 'name'
|
||||
permission_required = "passbook_core.view_group"
|
||||
ordering = "name"
|
||||
paginate_by = 40
|
||||
template_name = 'administration/group/list.html'
|
||||
template_name = "administration/group/list.html"
|
||||
|
||||
|
||||
class GroupCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
class GroupCreateView(
|
||||
SuccessMessageMixin,
|
||||
LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin,
|
||||
CreateAssignPermView,
|
||||
):
|
||||
"""Create new Group"""
|
||||
|
||||
model = Group
|
||||
form_class = GroupForm
|
||||
permission_required = 'passbook_core.add_group'
|
||||
permission_required = "passbook_core.add_group"
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:groups')
|
||||
success_message = _('Successfully created Group')
|
||||
template_name = "generic/create.html"
|
||||
success_url = reverse_lazy("passbook_admin:groups")
|
||||
success_message = _("Successfully created Group")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['type'] = 'Group'
|
||||
kwargs["type"] = "Group"
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class GroupUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
class GroupUpdateView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||
):
|
||||
"""Update group"""
|
||||
|
||||
model = Group
|
||||
form_class = GroupForm
|
||||
permission_required = 'passbook_core.change_group'
|
||||
permission_required = "passbook_core.change_group"
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:groups')
|
||||
success_message = _('Successfully updated Group')
|
||||
template_name = "generic/update.html"
|
||||
success_url = reverse_lazy("passbook_admin:groups")
|
||||
success_message = _("Successfully updated Group")
|
||||
|
||||
|
||||
class GroupDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView):
|
||||
@ -59,9 +65,9 @@ class GroupDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView):
|
||||
|
||||
model = Group
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:groups')
|
||||
success_message = _('Successfully deleted Group')
|
||||
template_name = "generic/delete.html"
|
||||
success_url = reverse_lazy("passbook_admin:groups")
|
||||
success_message = _("Successfully deleted Group")
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(self.request, self.success_message)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
"""passbook Invitation administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse_lazy
|
||||
@ -20,47 +21,49 @@ class InvitationListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all invitations"""
|
||||
|
||||
model = Invitation
|
||||
permission_required = 'passbook_core.view_invitation'
|
||||
template_name = 'administration/invitation/list.html'
|
||||
permission_required = "passbook_core.view_invitation"
|
||||
template_name = "administration/invitation/list.html"
|
||||
|
||||
|
||||
class InvitationCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
class InvitationCreateView(
|
||||
SuccessMessageMixin,
|
||||
LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin,
|
||||
CreateAssignPermView,
|
||||
):
|
||||
"""Create new Invitation"""
|
||||
|
||||
model = Invitation
|
||||
form_class = InvitationForm
|
||||
permission_required = 'passbook_core.add_invitation'
|
||||
permission_required = "passbook_core.add_invitation"
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:invitations')
|
||||
success_message = _('Successfully created Invitation')
|
||||
template_name = "generic/create.html"
|
||||
success_url = reverse_lazy("passbook_admin:invitations")
|
||||
success_message = _("Successfully created Invitation")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['type'] = 'Invitation'
|
||||
kwargs["type"] = "Invitation"
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
obj = form.save(commit=False)
|
||||
obj.created_by = self.request.user
|
||||
obj.save()
|
||||
invitation_created.send(
|
||||
sender=self,
|
||||
request=self.request,
|
||||
invitation=obj)
|
||||
invitation_created.send(sender=self, request=self.request, invitation=obj)
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
|
||||
class InvitationDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
class InvitationDeleteView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||
):
|
||||
"""Delete invitation"""
|
||||
|
||||
model = Invitation
|
||||
permission_required = 'passbook_core.delete_invitation'
|
||||
permission_required = "passbook_core.delete_invitation"
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:invitations')
|
||||
success_message = _('Successfully deleted Invitation')
|
||||
template_name = "generic/delete.html"
|
||||
success_url = reverse_lazy("passbook_admin:invitations")
|
||||
success_message = _("Successfully deleted Invitation")
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(self.request, self.success_message)
|
||||
|
||||
@ -5,34 +5,45 @@ from django.views.generic import TemplateView
|
||||
|
||||
from passbook import __version__
|
||||
from passbook.admin.mixins import AdminRequiredMixin
|
||||
from passbook.core.models import (Application, Factor, Invitation, Policy,
|
||||
Provider, Source, User)
|
||||
from passbook.core.models import (
|
||||
Application,
|
||||
Factor,
|
||||
Invitation,
|
||||
Policy,
|
||||
Provider,
|
||||
Source,
|
||||
User,
|
||||
)
|
||||
from passbook.root.celery import CELERY_APP
|
||||
|
||||
|
||||
class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
||||
"""Overview View"""
|
||||
|
||||
template_name = 'administration/overview.html'
|
||||
template_name = "administration/overview.html"
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
"""Handle post (clear cache from modal)"""
|
||||
if 'clear' in self.request.POST:
|
||||
if "clear" in self.request.POST:
|
||||
cache.clear()
|
||||
return redirect(reverse('passbook_core:auth-login'))
|
||||
return redirect(reverse("passbook_core:auth-login"))
|
||||
return self.get(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['application_count'] = len(Application.objects.all())
|
||||
kwargs['policy_count'] = len(Policy.objects.all())
|
||||
kwargs['user_count'] = len(User.objects.all())
|
||||
kwargs['provider_count'] = len(Provider.objects.all())
|
||||
kwargs['source_count'] = len(Source.objects.all())
|
||||
kwargs['factor_count'] = len(Factor.objects.all())
|
||||
kwargs['invitation_count'] = len(Invitation.objects.all())
|
||||
kwargs['version'] = __version__
|
||||
kwargs['worker_count'] = len(CELERY_APP.control.ping(timeout=0.5))
|
||||
kwargs['providers_without_application'] = Provider.objects.filter(application=None)
|
||||
kwargs['policies_without_attachment'] = len(Policy.objects.filter(policymodel__isnull=True))
|
||||
kwargs['cached_policies'] = len(cache.keys('policy_*'))
|
||||
kwargs["application_count"] = len(Application.objects.all())
|
||||
kwargs["policy_count"] = len(Policy.objects.all())
|
||||
kwargs["user_count"] = len(User.objects.all())
|
||||
kwargs["provider_count"] = len(Provider.objects.all())
|
||||
kwargs["source_count"] = len(Source.objects.all())
|
||||
kwargs["factor_count"] = len(Factor.objects.all())
|
||||
kwargs["invitation_count"] = len(Invitation.objects.all())
|
||||
kwargs["version"] = __version__
|
||||
kwargs["worker_count"] = len(CELERY_APP.control.ping(timeout=0.5))
|
||||
kwargs["providers_without_application"] = Provider.objects.filter(
|
||||
application=None
|
||||
)
|
||||
kwargs["policies_without_attachment"] = len(
|
||||
Policy.objects.filter(policymodel__isnull=True)
|
||||
)
|
||||
kwargs["cached_policies"] = len(cache.keys("policy_*"))
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
"""passbook Policy administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import Http404
|
||||
from django.urls import reverse_lazy
|
||||
@ -22,49 +23,54 @@ class PolicyListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all policies"""
|
||||
|
||||
model = Policy
|
||||
permission_required = 'passbook_core.view_policy'
|
||||
permission_required = "passbook_core.view_policy"
|
||||
|
||||
template_name = 'administration/policy/list.html'
|
||||
template_name = "administration/policy/list.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['types'] = {
|
||||
x.__name__: x._meta.verbose_name for x in Policy.__subclasses__()}
|
||||
kwargs["types"] = {
|
||||
x.__name__: x._meta.verbose_name for x in Policy.__subclasses__()
|
||||
}
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().order_by('order').select_subclasses()
|
||||
return super().get_queryset().order_by("order").select_subclasses()
|
||||
|
||||
|
||||
class PolicyCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
class PolicyCreateView(
|
||||
SuccessMessageMixin,
|
||||
LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin,
|
||||
CreateAssignPermView,
|
||||
):
|
||||
"""Create new Policy"""
|
||||
|
||||
model = Policy
|
||||
permission_required = 'passbook_core.add_policy'
|
||||
permission_required = "passbook_core.add_policy"
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:policies')
|
||||
success_message = _('Successfully created Policy')
|
||||
template_name = "generic/create.html"
|
||||
success_url = reverse_lazy("passbook_admin:policies")
|
||||
success_message = _("Successfully created Policy")
|
||||
|
||||
def get_form_class(self):
|
||||
policy_type = self.request.GET.get('type')
|
||||
model = next(x for x in Policy.__subclasses__()
|
||||
if x.__name__ == policy_type)
|
||||
policy_type = self.request.GET.get("type")
|
||||
model = next(x for x in Policy.__subclasses__() if x.__name__ == policy_type)
|
||||
if not model:
|
||||
raise Http404
|
||||
return path_to_class(model.form)
|
||||
|
||||
|
||||
class PolicyUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
class PolicyUpdateView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||
):
|
||||
"""Update policy"""
|
||||
|
||||
model = Policy
|
||||
permission_required = 'passbook_core.change_policy'
|
||||
permission_required = "passbook_core.change_policy"
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:policies')
|
||||
success_message = _('Successfully updated Policy')
|
||||
template_name = "generic/update.html"
|
||||
success_url = reverse_lazy("passbook_admin:policies")
|
||||
success_message = _("Successfully updated Policy")
|
||||
|
||||
def get_form_class(self):
|
||||
form_class_path = self.get_object().form
|
||||
@ -72,22 +78,27 @@ class PolicyUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
return form_class
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
return (
|
||||
Policy.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
|
||||
)
|
||||
|
||||
|
||||
class PolicyDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
class PolicyDeleteView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||
):
|
||||
"""Delete policy"""
|
||||
|
||||
model = Policy
|
||||
permission_required = 'passbook_core.delete_policy'
|
||||
permission_required = "passbook_core.delete_policy"
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:policies')
|
||||
success_message = _('Successfully deleted Policy')
|
||||
template_name = "generic/delete.html"
|
||||
success_url = reverse_lazy("passbook_admin:policies")
|
||||
success_message = _("Successfully deleted Policy")
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
return (
|
||||
Policy.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
|
||||
)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(self.request, self.success_message)
|
||||
@ -99,15 +110,17 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
|
||||
|
||||
model = Policy
|
||||
form_class = PolicyTestForm
|
||||
permission_required = 'passbook_core.view_policy'
|
||||
template_name = 'administration/policy/test.html'
|
||||
permission_required = "passbook_core.view_policy"
|
||||
template_name = "administration/policy/test.html"
|
||||
object = None
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
return (
|
||||
Policy.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['policy'] = self.get_object()
|
||||
kwargs["policy"] = self.get_object()
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
@ -116,13 +129,13 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
|
||||
|
||||
def form_valid(self, form):
|
||||
policy = self.get_object()
|
||||
user = form.cleaned_data.get('user')
|
||||
user = form.cleaned_data.get("user")
|
||||
policy_engine = PolicyEngine([policy], user, self.request)
|
||||
policy_engine.use_cache = False
|
||||
policy_engine.build()
|
||||
result = policy_engine.passing
|
||||
if result:
|
||||
messages.success(self.request, _('User successfully passed policy.'))
|
||||
messages.success(self.request, _("User successfully passed policy."))
|
||||
else:
|
||||
messages.error(self.request, _("User didn't pass policy."))
|
||||
return self.render_to_response(self.get_context_data(form=form, result=result))
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
"""passbook PropertyMapping administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import Http404
|
||||
from django.urls import reverse_lazy
|
||||
@ -18,65 +19,78 @@ from passbook.lib.views import CreateAssignPermView
|
||||
def all_subclasses(cls):
|
||||
"""Recursively return all subclassess of cls"""
|
||||
return set(cls.__subclasses__()).union(
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)])
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)]
|
||||
)
|
||||
|
||||
|
||||
class PropertyMappingListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all property_mappings"""
|
||||
|
||||
model = PropertyMapping
|
||||
permission_required = 'passbook_core.view_propertymapping'
|
||||
template_name = 'administration/property_mapping/list.html'
|
||||
ordering = 'name'
|
||||
permission_required = "passbook_core.view_propertymapping"
|
||||
template_name = "administration/property_mapping/list.html"
|
||||
ordering = "name"
|
||||
paginate_by = 40
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['types'] = {
|
||||
x.__name__: x._meta.verbose_name for x in all_subclasses(PropertyMapping)}
|
||||
kwargs["types"] = {
|
||||
x.__name__: x._meta.verbose_name for x in all_subclasses(PropertyMapping)
|
||||
}
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_subclasses()
|
||||
|
||||
|
||||
class PropertyMappingCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
class PropertyMappingCreateView(
|
||||
SuccessMessageMixin,
|
||||
LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin,
|
||||
CreateAssignPermView,
|
||||
):
|
||||
"""Create new PropertyMapping"""
|
||||
|
||||
model = PropertyMapping
|
||||
permission_required = 'passbook_core.add_propertymapping'
|
||||
permission_required = "passbook_core.add_propertymapping"
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:property-mappings')
|
||||
success_message = _('Successfully created Property Mapping')
|
||||
template_name = "generic/create.html"
|
||||
success_url = reverse_lazy("passbook_admin:property-mappings")
|
||||
success_message = _("Successfully created Property Mapping")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs = super().get_context_data(**kwargs)
|
||||
property_mapping_type = self.request.GET.get('type')
|
||||
model = next(x for x in all_subclasses(PropertyMapping)
|
||||
if x.__name__ == property_mapping_type)
|
||||
kwargs['type'] = model._meta.verbose_name
|
||||
property_mapping_type = self.request.GET.get("type")
|
||||
model = next(
|
||||
x
|
||||
for x in all_subclasses(PropertyMapping)
|
||||
if x.__name__ == property_mapping_type
|
||||
)
|
||||
kwargs["type"] = model._meta.verbose_name
|
||||
return kwargs
|
||||
|
||||
def get_form_class(self):
|
||||
property_mapping_type = self.request.GET.get('type')
|
||||
model = next(x for x in all_subclasses(PropertyMapping)
|
||||
if x.__name__ == property_mapping_type)
|
||||
property_mapping_type = self.request.GET.get("type")
|
||||
model = next(
|
||||
x
|
||||
for x in all_subclasses(PropertyMapping)
|
||||
if x.__name__ == property_mapping_type
|
||||
)
|
||||
if not model:
|
||||
raise Http404
|
||||
return path_to_class(model.form)
|
||||
|
||||
|
||||
class PropertyMappingUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
class PropertyMappingUpdateView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||
):
|
||||
"""Update property_mapping"""
|
||||
|
||||
model = PropertyMapping
|
||||
permission_required = 'passbook_core.change_propertymapping'
|
||||
permission_required = "passbook_core.change_propertymapping"
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:property-mappings')
|
||||
success_message = _('Successfully updated Property Mapping')
|
||||
template_name = "generic/update.html"
|
||||
success_url = reverse_lazy("passbook_admin:property-mappings")
|
||||
success_message = _("Successfully updated Property Mapping")
|
||||
|
||||
def get_form_class(self):
|
||||
form_class_path = self.get_object().form
|
||||
@ -84,22 +98,31 @@ class PropertyMappingUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
return form_class
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return PropertyMapping.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
return (
|
||||
PropertyMapping.objects.filter(pk=self.kwargs.get("pk"))
|
||||
.select_subclasses()
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
class PropertyMappingDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
class PropertyMappingDeleteView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||
):
|
||||
"""Delete property_mapping"""
|
||||
|
||||
model = PropertyMapping
|
||||
permission_required = 'passbook_core.delete_propertymapping'
|
||||
permission_required = "passbook_core.delete_propertymapping"
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:property-mappings')
|
||||
success_message = _('Successfully deleted Property Mapping')
|
||||
template_name = "generic/delete.html"
|
||||
success_url = reverse_lazy("passbook_admin:property-mappings")
|
||||
success_message = _("Successfully deleted Property Mapping")
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return PropertyMapping.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
return (
|
||||
PropertyMapping.objects.filter(pk=self.kwargs.get("pk"))
|
||||
.select_subclasses()
|
||||
.first()
|
||||
)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(self.request, self.success_message)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
"""passbook Provider administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import Http404
|
||||
from django.urls import reverse_lazy
|
||||
@ -19,48 +20,55 @@ class ProviderListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all providers"""
|
||||
|
||||
model = Provider
|
||||
permission_required = 'passbook_core.add_provider'
|
||||
template_name = 'administration/provider/list.html'
|
||||
permission_required = "passbook_core.add_provider"
|
||||
template_name = "administration/provider/list.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['types'] = {
|
||||
x.__name__: x._meta.verbose_name for x in Provider.__subclasses__()}
|
||||
kwargs["types"] = {
|
||||
x.__name__: x._meta.verbose_name for x in Provider.__subclasses__()
|
||||
}
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_subclasses()
|
||||
|
||||
|
||||
class ProviderCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
class ProviderCreateView(
|
||||
SuccessMessageMixin,
|
||||
LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin,
|
||||
CreateAssignPermView,
|
||||
):
|
||||
"""Create new Provider"""
|
||||
|
||||
model = Provider
|
||||
permission_required = 'passbook_core.add_provider'
|
||||
permission_required = "passbook_core.add_provider"
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:providers')
|
||||
success_message = _('Successfully created Provider')
|
||||
template_name = "generic/create.html"
|
||||
success_url = reverse_lazy("passbook_admin:providers")
|
||||
success_message = _("Successfully created Provider")
|
||||
|
||||
def get_form_class(self):
|
||||
provider_type = self.request.GET.get('type')
|
||||
model = next(x for x in Provider.__subclasses__()
|
||||
if x.__name__ == provider_type)
|
||||
provider_type = self.request.GET.get("type")
|
||||
model = next(
|
||||
x for x in Provider.__subclasses__() if x.__name__ == provider_type
|
||||
)
|
||||
if not model:
|
||||
raise Http404
|
||||
return path_to_class(model.form)
|
||||
|
||||
|
||||
class ProviderUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
class ProviderUpdateView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||
):
|
||||
"""Update provider"""
|
||||
|
||||
model = Provider
|
||||
permission_required = 'passbook_core.change_provider'
|
||||
permission_required = "passbook_core.change_provider"
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:providers')
|
||||
success_message = _('Successfully updated Provider')
|
||||
template_name = "generic/update.html"
|
||||
success_url = reverse_lazy("passbook_admin:providers")
|
||||
success_message = _("Successfully updated Provider")
|
||||
|
||||
def get_form_class(self):
|
||||
form_class_path = self.get_object().form
|
||||
@ -68,22 +76,31 @@ class ProviderUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
return form_class
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return Provider.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
return (
|
||||
Provider.objects.filter(pk=self.kwargs.get("pk"))
|
||||
.select_subclasses()
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
class ProviderDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
class ProviderDeleteView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||
):
|
||||
"""Delete provider"""
|
||||
|
||||
model = Provider
|
||||
permission_required = 'passbook_core.delete_provider'
|
||||
permission_required = "passbook_core.delete_provider"
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:providers')
|
||||
success_message = _('Successfully deleted Provider')
|
||||
template_name = "generic/delete.html"
|
||||
success_url = reverse_lazy("passbook_admin:providers")
|
||||
success_message = _("Successfully deleted Provider")
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return Provider.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
return (
|
||||
Provider.objects.filter(pk=self.kwargs.get("pk"))
|
||||
.select_subclasses()
|
||||
.first()
|
||||
)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(self.request, self.success_message)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
"""passbook Source administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.http import Http404
|
||||
from django.urls import reverse_lazy
|
||||
@ -18,55 +19,63 @@ from passbook.lib.views import CreateAssignPermView
|
||||
def all_subclasses(cls):
|
||||
"""Recursively return all subclassess of cls"""
|
||||
return set(cls.__subclasses__()).union(
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)])
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)]
|
||||
)
|
||||
|
||||
|
||||
class SourceListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all sources"""
|
||||
|
||||
model = Source
|
||||
permission_required = 'passbook_core.view_source'
|
||||
ordering = 'name'
|
||||
permission_required = "passbook_core.view_source"
|
||||
ordering = "name"
|
||||
paginate_by = 40
|
||||
template_name = 'administration/source/list.html'
|
||||
template_name = "administration/source/list.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['types'] = {
|
||||
x.__name__: x._meta.verbose_name for x in all_subclasses(Source)}
|
||||
kwargs["types"] = {
|
||||
x.__name__: x._meta.verbose_name for x in all_subclasses(Source)
|
||||
}
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_subclasses()
|
||||
|
||||
|
||||
class SourceCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
class SourceCreateView(
|
||||
SuccessMessageMixin,
|
||||
LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin,
|
||||
CreateAssignPermView,
|
||||
):
|
||||
"""Create new Source"""
|
||||
|
||||
model = Source
|
||||
permission_required = 'passbook_core.add_source'
|
||||
permission_required = "passbook_core.add_source"
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:sources')
|
||||
success_message = _('Successfully created Source')
|
||||
template_name = "generic/create.html"
|
||||
success_url = reverse_lazy("passbook_admin:sources")
|
||||
success_message = _("Successfully created Source")
|
||||
|
||||
def get_form_class(self):
|
||||
source_type = self.request.GET.get('type')
|
||||
source_type = self.request.GET.get("type")
|
||||
model = next(x for x in all_subclasses(Source) if x.__name__ == source_type)
|
||||
if not model:
|
||||
raise Http404
|
||||
return path_to_class(model.form)
|
||||
|
||||
|
||||
class SourceUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
class SourceUpdateView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||
):
|
||||
"""Update source"""
|
||||
|
||||
model = Source
|
||||
permission_required = 'passbook_core.change_source'
|
||||
permission_required = "passbook_core.change_source"
|
||||
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:sources')
|
||||
success_message = _('Successfully updated Source')
|
||||
template_name = "generic/update.html"
|
||||
success_url = reverse_lazy("passbook_admin:sources")
|
||||
success_message = _("Successfully updated Source")
|
||||
|
||||
def get_form_class(self):
|
||||
form_class_path = self.get_object().form
|
||||
@ -74,22 +83,27 @@ class SourceUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
return form_class
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return Source.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
return (
|
||||
Source.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
|
||||
)
|
||||
|
||||
|
||||
class SourceDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
class SourceDeleteView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||
):
|
||||
"""Delete source"""
|
||||
|
||||
model = Source
|
||||
permission_required = 'passbook_core.delete_source'
|
||||
permission_required = "passbook_core.delete_source"
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:sources')
|
||||
success_message = _('Successfully deleted Source')
|
||||
template_name = "generic/delete.html"
|
||||
success_url = reverse_lazy("passbook_admin:sources")
|
||||
success_message = _("Successfully deleted Source")
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return Source.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
|
||||
return (
|
||||
Source.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
|
||||
)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(self.request, self.success_message)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
"""passbook User administration"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import \
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse, reverse_lazy
|
||||
@ -19,50 +20,56 @@ class UserListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
"""Show list of all users"""
|
||||
|
||||
model = User
|
||||
permission_required = 'passbook_core.view_user'
|
||||
ordering = 'username'
|
||||
permission_required = "passbook_core.view_user"
|
||||
ordering = "username"
|
||||
paginate_by = 40
|
||||
template_name = 'administration/user/list.html'
|
||||
template_name = "administration/user/list.html"
|
||||
|
||||
|
||||
class UserCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
||||
class UserCreateView(
|
||||
SuccessMessageMixin,
|
||||
LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin,
|
||||
CreateAssignPermView,
|
||||
):
|
||||
"""Create user"""
|
||||
|
||||
model = User
|
||||
form_class = UserForm
|
||||
permission_required = 'passbook_core.add_user'
|
||||
permission_required = "passbook_core.add_user"
|
||||
|
||||
template_name = 'generic/create.html'
|
||||
success_url = reverse_lazy('passbook_admin:users')
|
||||
success_message = _('Successfully created User')
|
||||
template_name = "generic/create.html"
|
||||
success_url = reverse_lazy("passbook_admin:users")
|
||||
success_message = _("Successfully created User")
|
||||
|
||||
|
||||
class UserUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, UpdateView):
|
||||
class UserUpdateView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||
):
|
||||
"""Update user"""
|
||||
|
||||
model = User
|
||||
form_class = UserForm
|
||||
permission_required = 'passbook_core.change_user'
|
||||
permission_required = "passbook_core.change_user"
|
||||
|
||||
# By default the object's name is user which is used by other checks
|
||||
context_object_name = 'object'
|
||||
template_name = 'generic/update.html'
|
||||
success_url = reverse_lazy('passbook_admin:users')
|
||||
success_message = _('Successfully updated User')
|
||||
context_object_name = "object"
|
||||
template_name = "generic/update.html"
|
||||
success_url = reverse_lazy("passbook_admin:users")
|
||||
success_message = _("Successfully updated User")
|
||||
|
||||
|
||||
class UserDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
||||
PermissionRequiredMixin, DeleteView):
|
||||
class UserDeleteView(
|
||||
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||
):
|
||||
"""Delete user"""
|
||||
|
||||
model = User
|
||||
permission_required = 'passbook_core.delete_user'
|
||||
permission_required = "passbook_core.delete_user"
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
success_url = reverse_lazy('passbook_admin:users')
|
||||
success_message = _('Successfully deleted User')
|
||||
template_name = "generic/delete.html"
|
||||
success_url = reverse_lazy("passbook_admin:users")
|
||||
success_message = _("Successfully deleted User")
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(self.request, self.success_message)
|
||||
@ -73,13 +80,16 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
|
||||
"""Get Password reset link for user"""
|
||||
|
||||
model = User
|
||||
permission_required = 'passbook_core.reset_user_password'
|
||||
permission_required = "passbook_core.reset_user_password"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Create nonce for user and return link"""
|
||||
super().get(request, *args, **kwargs)
|
||||
nonce = Nonce.objects.create(user=self.object)
|
||||
link = request.build_absolute_uri(reverse(
|
||||
'passbook_core:auth-password-reset', kwargs={'nonce': nonce.uuid}))
|
||||
messages.success(request, _('Password reset link: <pre>%(link)s</pre>' % {'link': link}))
|
||||
return redirect('passbook_admin:users')
|
||||
link = request.build_absolute_uri(
|
||||
reverse("passbook_core:auth-password-reset", kwargs={"nonce": nonce.uuid})
|
||||
)
|
||||
messages.success(
|
||||
request, _("Password reset link: <pre>%(link)s</pre>" % {"link": link})
|
||||
)
|
||||
return redirect("passbook_admin:users")
|
||||
|
||||
@ -6,7 +6,7 @@ from django.apps import AppConfig
|
||||
class PassbookAPIConfig(AppConfig):
|
||||
"""passbook API Config"""
|
||||
|
||||
name = 'passbook.api'
|
||||
label = 'passbook_api'
|
||||
mountpoint = 'api/'
|
||||
verbose_name = 'passbook API'
|
||||
name = "passbook.api"
|
||||
label = "passbook_api"
|
||||
mountpoint = "api/"
|
||||
verbose_name = "passbook API"
|
||||
|
||||
@ -9,13 +9,13 @@ class CustomObjectPermissions(DjangoObjectPermissions):
|
||||
"""Similar to `DjangoObjectPermissions`, but adding 'view' permissions."""
|
||||
|
||||
perms_map = {
|
||||
'GET': ['%(app_label)s.view_%(model_name)s'],
|
||||
'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
|
||||
'HEAD': ['%(app_label)s.view_%(model_name)s'],
|
||||
'POST': ['%(app_label)s.add_%(model_name)s'],
|
||||
'PUT': ['%(app_label)s.change_%(model_name)s'],
|
||||
'PATCH': ['%(app_label)s.change_%(model_name)s'],
|
||||
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
|
||||
"GET": ["%(app_label)s.view_%(model_name)s"],
|
||||
"OPTIONS": ["%(app_label)s.view_%(model_name)s"],
|
||||
"HEAD": ["%(app_label)s.view_%(model_name)s"],
|
||||
"POST": ["%(app_label)s.add_%(model_name)s"],
|
||||
"PUT": ["%(app_label)s.change_%(model_name)s"],
|
||||
"PATCH": ["%(app_label)s.change_%(model_name)s"],
|
||||
"DELETE": ["%(app_label)s.delete_%(model_name)s"],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -5,6 +5,6 @@ from passbook.api.v1.urls import urlpatterns as v1_urls
|
||||
from passbook.api.v2.urls import urlpatterns as v2_urls
|
||||
|
||||
urlpatterns = [
|
||||
path('v1/', include(v1_urls)),
|
||||
path('v2/', include(v2_urls)),
|
||||
path("v1/", include(v1_urls)),
|
||||
path("v2/", include(v2_urls)),
|
||||
]
|
||||
|
||||
@ -7,16 +7,16 @@ from oauth2_provider.views.mixins import ScopedResourceMixin
|
||||
class OpenIDUserInfoView(ScopedResourceMixin, View):
|
||||
"""Passbook v1 OpenID API"""
|
||||
|
||||
required_scopes = ['openid:userinfo']
|
||||
required_scopes = ["openid:userinfo"]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
def get(self, request, *_, **__):
|
||||
"""Passbook v1 OpenID API"""
|
||||
payload = {
|
||||
'sub': request.user.uuid.int,
|
||||
'name': request.user.get_full_name(),
|
||||
'given_name': request.user.name,
|
||||
'family_name': '',
|
||||
'preferred_username': request.user.username,
|
||||
'email': request.user.email,
|
||||
"sub": request.user.uuid.int,
|
||||
"name": request.user.get_full_name(),
|
||||
"given_name": request.user.name,
|
||||
"family_name": "",
|
||||
"preferred_username": request.user.username,
|
||||
"email": request.user.email,
|
||||
}
|
||||
return JsonResponse(payload)
|
||||
|
||||
@ -3,6 +3,4 @@ from django.urls import path
|
||||
|
||||
from passbook.api.v1.openid import OpenIDUserInfoView
|
||||
|
||||
urlpatterns = [
|
||||
path('openid/', OpenIDUserInfoView.as_view(), name='openid')
|
||||
]
|
||||
urlpatterns = [path("openid/", OpenIDUserInfoView.as_view(), name="openid")]
|
||||
|
||||
@ -34,70 +34,73 @@ from passbook.policies.webhook.api import WebhookPolicyViewSet
|
||||
from passbook.providers.app_gw.api import ApplicationGatewayProviderViewSet
|
||||
from passbook.providers.oauth.api import OAuth2ProviderViewSet
|
||||
from passbook.providers.oidc.api import OpenIDProviderViewSet
|
||||
from passbook.providers.saml.api import (SAMLPropertyMappingViewSet,
|
||||
SAMLProviderViewSet)
|
||||
from passbook.sources.ldap.api import (LDAPPropertyMappingViewSet,
|
||||
LDAPSourceViewSet)
|
||||
from passbook.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProviderViewSet
|
||||
from passbook.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
|
||||
from passbook.sources.oauth.api import OAuthSourceViewSet
|
||||
|
||||
LOGGER = get_logger()
|
||||
router = routers.DefaultRouter()
|
||||
|
||||
for _passbook_app in get_apps():
|
||||
if hasattr(_passbook_app, 'api_mountpoint'):
|
||||
if hasattr(_passbook_app, "api_mountpoint"):
|
||||
for prefix, viewset in _passbook_app.api_mountpoint:
|
||||
router.register(prefix, viewset)
|
||||
LOGGER.debug("Mounted API URLs", app_name=_passbook_app.name)
|
||||
|
||||
router.register('core/applications', ApplicationViewSet)
|
||||
router.register('core/invitations', InvitationViewSet)
|
||||
router.register('core/groups', GroupViewSet)
|
||||
router.register('core/users', UserViewSet)
|
||||
router.register('audit/events', EventViewSet)
|
||||
router.register('sources/all', SourceViewSet)
|
||||
router.register('sources/ldap', LDAPSourceViewSet)
|
||||
router.register('sources/oauth', OAuthSourceViewSet)
|
||||
router.register('policies/all', PolicyViewSet)
|
||||
router.register('policies/passwordexpiry', PasswordExpiryPolicyViewSet)
|
||||
router.register('policies/groupmembership', GroupMembershipPolicyViewSet)
|
||||
router.register('policies/haveibeenpwned', HaveIBeenPwendPolicyViewSet)
|
||||
router.register('policies/fieldmatcher', FieldMatcherPolicyViewSet)
|
||||
router.register('policies/password', PasswordPolicyViewSet)
|
||||
router.register('policies/reputation', ReputationPolicyViewSet)
|
||||
router.register('policies/ssologin', SSOLoginPolicyViewSet)
|
||||
router.register('policies/webhook', WebhookPolicyViewSet)
|
||||
router.register('providers/all', ProviderViewSet)
|
||||
router.register('providers/applicationgateway', ApplicationGatewayProviderViewSet)
|
||||
router.register('providers/oauth', OAuth2ProviderViewSet)
|
||||
router.register('providers/openid', OpenIDProviderViewSet)
|
||||
router.register('providers/saml', SAMLProviderViewSet)
|
||||
router.register('propertymappings/all', PropertyMappingViewSet)
|
||||
router.register('propertymappings/ldap', LDAPPropertyMappingViewSet)
|
||||
router.register('propertymappings/saml', SAMLPropertyMappingViewSet)
|
||||
router.register('factors/all', FactorViewSet)
|
||||
router.register('factors/captcha', CaptchaFactorViewSet)
|
||||
router.register('factors/dummy', DummyFactorViewSet)
|
||||
router.register('factors/email', EmailFactorViewSet)
|
||||
router.register('factors/otp', OTPFactorViewSet)
|
||||
router.register('factors/password', PasswordFactorViewSet)
|
||||
router.register("core/applications", ApplicationViewSet)
|
||||
router.register("core/invitations", InvitationViewSet)
|
||||
router.register("core/groups", GroupViewSet)
|
||||
router.register("core/users", UserViewSet)
|
||||
router.register("audit/events", EventViewSet)
|
||||
router.register("sources/all", SourceViewSet)
|
||||
router.register("sources/ldap", LDAPSourceViewSet)
|
||||
router.register("sources/oauth", OAuthSourceViewSet)
|
||||
router.register("policies/all", PolicyViewSet)
|
||||
router.register("policies/passwordexpiry", PasswordExpiryPolicyViewSet)
|
||||
router.register("policies/groupmembership", GroupMembershipPolicyViewSet)
|
||||
router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet)
|
||||
router.register("policies/fieldmatcher", FieldMatcherPolicyViewSet)
|
||||
router.register("policies/password", PasswordPolicyViewSet)
|
||||
router.register("policies/reputation", ReputationPolicyViewSet)
|
||||
router.register("policies/ssologin", SSOLoginPolicyViewSet)
|
||||
router.register("policies/webhook", WebhookPolicyViewSet)
|
||||
router.register("providers/all", ProviderViewSet)
|
||||
router.register("providers/applicationgateway", ApplicationGatewayProviderViewSet)
|
||||
router.register("providers/oauth", OAuth2ProviderViewSet)
|
||||
router.register("providers/openid", OpenIDProviderViewSet)
|
||||
router.register("providers/saml", SAMLProviderViewSet)
|
||||
router.register("propertymappings/all", PropertyMappingViewSet)
|
||||
router.register("propertymappings/ldap", LDAPPropertyMappingViewSet)
|
||||
router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
|
||||
router.register("factors/all", FactorViewSet)
|
||||
router.register("factors/captcha", CaptchaFactorViewSet)
|
||||
router.register("factors/dummy", DummyFactorViewSet)
|
||||
router.register("factors/email", EmailFactorViewSet)
|
||||
router.register("factors/otp", OTPFactorViewSet)
|
||||
router.register("factors/password", PasswordFactorViewSet)
|
||||
|
||||
info = openapi.Info(
|
||||
title="passbook API",
|
||||
default_version='v2',
|
||||
default_version="v2",
|
||||
# description="Test description",
|
||||
# terms_of_service="https://www.google.com/policies/terms/",
|
||||
contact=openapi.Contact(email="hello@beryju.org"),
|
||||
license=openapi.License(name="MIT License"),
|
||||
)
|
||||
SchemaView = get_schema_view(
|
||||
info,
|
||||
public=True,
|
||||
permission_classes=(CustomObjectPermissions,),
|
||||
info, public=True, permission_classes=(CustomObjectPermissions,),
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^swagger(?P<format>\.json|\.yaml)$',
|
||||
SchemaView.without_ui(cache_timeout=0), name='schema-json'),
|
||||
path('swagger/', SchemaView.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
path('redoc/', SchemaView.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||
url(
|
||||
r"^swagger(?P<format>\.json|\.yaml)$",
|
||||
SchemaView.without_ui(cache_timeout=0),
|
||||
name="schema-json",
|
||||
),
|
||||
path(
|
||||
"swagger/",
|
||||
SchemaView.with_ui("swagger", cache_timeout=0),
|
||||
name="schema-swagger-ui",
|
||||
),
|
||||
path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
|
||||
] + router.urls
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
|
||||
from passbook.lib.admin import admin_autoregister
|
||||
|
||||
admin_autoregister('passbook_audit')
|
||||
admin_autoregister("passbook_audit")
|
||||
|
||||
@ -11,7 +11,16 @@ class EventSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
|
||||
model = Event
|
||||
fields = ['pk', 'user', 'action', 'date', 'app', 'context', 'request_ip', 'created', ]
|
||||
fields = [
|
||||
"pk",
|
||||
"user",
|
||||
"action",
|
||||
"date",
|
||||
"app",
|
||||
"context",
|
||||
"request_ip",
|
||||
"created",
|
||||
]
|
||||
|
||||
|
||||
class EventViewSet(ReadOnlyModelViewSet):
|
||||
|
||||
@ -7,10 +7,10 @@ from django.apps import AppConfig
|
||||
class PassbookAuditConfig(AppConfig):
|
||||
"""passbook audit app"""
|
||||
|
||||
name = 'passbook.audit'
|
||||
label = 'passbook_audit'
|
||||
verbose_name = 'passbook Audit'
|
||||
mountpoint = 'audit/'
|
||||
name = "passbook.audit"
|
||||
label = "passbook_audit"
|
||||
verbose_name = "passbook Audit"
|
||||
mountpoint = "audit/"
|
||||
|
||||
def ready(self):
|
||||
import_module('passbook.audit.signals')
|
||||
import_module("passbook.audit.signals")
|
||||
|
||||
@ -18,20 +18,55 @@ class Migration(migrations.Migration):
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AuditEntry',
|
||||
name="AuditEntry",
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])),
|
||||
('date', models.DateTimeField(auto_now_add=True)),
|
||||
('app', models.TextField()),
|
||||
('context', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
|
||||
('request_ip', models.GenericIPAddressField()),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"action",
|
||||
models.TextField(
|
||||
choices=[
|
||||
("login", "login"),
|
||||
("login_failed", "login_failed"),
|
||||
("logout", "logout"),
|
||||
("authorize_application", "authorize_application"),
|
||||
("suspicious_request", "suspicious_request"),
|
||||
("sign_up", "sign_up"),
|
||||
("password_reset", "password_reset"),
|
||||
("invitation_created", "invitation_created"),
|
||||
("invitation_used", "invitation_used"),
|
||||
]
|
||||
),
|
||||
),
|
||||
("date", models.DateTimeField(auto_now_add=True)),
|
||||
("app", models.TextField()),
|
||||
(
|
||||
"context",
|
||||
django.contrib.postgres.fields.jsonb.JSONField(
|
||||
blank=True, default=dict
|
||||
),
|
||||
),
|
||||
("request_ip", models.GenericIPAddressField()),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Audit Entry',
|
||||
'verbose_name_plural': 'Audit Entries',
|
||||
"verbose_name": "Audit Entry",
|
||||
"verbose_name_plural": "Audit Entries",
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@ -8,12 +8,9 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('passbook_audit', '0001_initial'),
|
||||
("passbook_audit", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='AuditEntry',
|
||||
new_name='Event',
|
||||
),
|
||||
migrations.RenameModel(old_name="AuditEntry", new_name="Event",),
|
||||
]
|
||||
|
||||
@ -8,17 +8,33 @@ import passbook.audit.models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_audit', '0002_auto_20191028_0829'),
|
||||
("passbook_audit", "0002_auto_20191028_0829"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='event',
|
||||
options={'verbose_name': 'Audit Event', 'verbose_name_plural': 'Audit Events'},
|
||||
name="event",
|
||||
options={
|
||||
"verbose_name": "Audit Event",
|
||||
"verbose_name_plural": "Audit Events",
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='action',
|
||||
field=models.TextField(choices=[('LOGIN', 'login'), ('LOGIN_FAILED', 'login_failed'), ('LOGOUT', 'logout'), ('AUTHORIZE_APPLICATION', 'authorize_application'), ('SUSPICIOUS_REQUEST', 'suspicious_request'), ('SIGN_UP', 'sign_up'), ('PASSWORD_RESET', 'password_reset'), ('INVITE_CREATED', 'invitation_created'), ('INVITE_USED', 'invitation_used'), ('CUSTOM', 'custom')]),
|
||||
model_name="event",
|
||||
name="action",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("LOGIN", "login"),
|
||||
("LOGIN_FAILED", "login_failed"),
|
||||
("LOGOUT", "logout"),
|
||||
("AUTHORIZE_APPLICATION", "authorize_application"),
|
||||
("SUSPICIOUS_REQUEST", "suspicious_request"),
|
||||
("SIGN_UP", "sign_up"),
|
||||
("PASSWORD_RESET", "password_reset"),
|
||||
("INVITE_CREATED", "invitation_created"),
|
||||
("INVITE_USED", "invitation_used"),
|
||||
("CUSTOM", "custom"),
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@ -6,17 +6,14 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_audit', '0003_auto_20191205_1407'),
|
||||
("passbook_audit", "0003_auto_20191205_1407"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='event',
|
||||
name='request_ip',
|
||||
),
|
||||
migrations.RemoveField(model_name="event", name="request_ip",),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='client_ip',
|
||||
model_name="event",
|
||||
name="client_ip",
|
||||
field=models.GenericIPAddressField(null=True),
|
||||
),
|
||||
]
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
"""passbook audit models"""
|
||||
from enum import Enum
|
||||
from uuid import UUID
|
||||
from inspect import getmodule, stack
|
||||
from typing import Optional
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.http import HttpRequest
|
||||
from django.utils.translation import gettext as _
|
||||
@ -18,19 +20,44 @@ from passbook.lib.utils.http import get_client_ip
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
|
||||
"""clean source of all Models that would interfere with the JSONField.
|
||||
Models are replaced with a dictionary of {
|
||||
app: str,
|
||||
name: str,
|
||||
pk: Any
|
||||
}"""
|
||||
for key, value in source.items():
|
||||
if isinstance(value, dict):
|
||||
source[key] = sanitize_dict(value)
|
||||
elif isinstance(value, models.Model):
|
||||
model_content_type = ContentType.objects.get_for_model(value)
|
||||
source[key] = sanitize_dict(
|
||||
{
|
||||
"app": model_content_type.app_label,
|
||||
"name": model_content_type.model,
|
||||
"pk": value.pk,
|
||||
}
|
||||
)
|
||||
elif isinstance(value, UUID):
|
||||
source[key] = value.hex
|
||||
return source
|
||||
|
||||
|
||||
class EventAction(Enum):
|
||||
"""All possible actions to save into the audit log"""
|
||||
|
||||
LOGIN = 'login'
|
||||
LOGIN_FAILED = 'login_failed'
|
||||
LOGOUT = 'logout'
|
||||
AUTHORIZE_APPLICATION = 'authorize_application'
|
||||
SUSPICIOUS_REQUEST = 'suspicious_request'
|
||||
SIGN_UP = 'sign_up'
|
||||
PASSWORD_RESET = 'password_reset' # noqa # nosec
|
||||
INVITE_CREATED = 'invitation_created'
|
||||
INVITE_USED = 'invitation_used'
|
||||
CUSTOM = 'custom'
|
||||
LOGIN = "login"
|
||||
LOGIN_FAILED = "login_failed"
|
||||
LOGOUT = "logout"
|
||||
AUTHORIZE_APPLICATION = "authorize_application"
|
||||
SUSPICIOUS_REQUEST = "suspicious_request"
|
||||
SIGN_UP = "sign_up"
|
||||
PASSWORD_RESET = "password_reset" # noqa # nosec
|
||||
INVITE_CREATED = "invitation_created"
|
||||
INVITE_USED = "invitation_used"
|
||||
CUSTOM = "custom"
|
||||
|
||||
@staticmethod
|
||||
def as_choices():
|
||||
@ -41,7 +68,9 @@ class EventAction(Enum):
|
||||
class Event(UUIDModel):
|
||||
"""An individual audit log event"""
|
||||
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL
|
||||
)
|
||||
action = models.TextField(choices=EventAction.as_choices())
|
||||
date = models.DateTimeField(auto_now_add=True)
|
||||
app = models.TextField()
|
||||
@ -56,28 +85,31 @@ class Event(UUIDModel):
|
||||
return request.resolver_match.app_name
|
||||
|
||||
@staticmethod
|
||||
def new(action: EventAction,
|
||||
app: Optional[str] = None,
|
||||
_inspect_offset: int = 1,
|
||||
**kwargs) -> 'Event':
|
||||
def new(
|
||||
action: EventAction,
|
||||
app: Optional[str] = None,
|
||||
_inspect_offset: int = 1,
|
||||
**kwargs,
|
||||
) -> "Event":
|
||||
"""Create new Event instance from arguments. Instance is NOT saved."""
|
||||
if not isinstance(action, EventAction):
|
||||
raise ValueError(f"action must be EventAction instance but was {type(action)}")
|
||||
raise ValueError(
|
||||
f"action must be EventAction instance but was {type(action)}"
|
||||
)
|
||||
if not app:
|
||||
app = getmodule(stack()[_inspect_offset][0]).__name__
|
||||
event = Event(
|
||||
action=action.value,
|
||||
app=app,
|
||||
context=kwargs)
|
||||
LOGGER.debug("Created Audit event", action=action, context=kwargs)
|
||||
cleaned_kwargs = sanitize_dict(kwargs)
|
||||
event = Event(action=action.value, app=app, context=cleaned_kwargs)
|
||||
LOGGER.debug("Created Audit event", action=action, context=cleaned_kwargs)
|
||||
return event
|
||||
|
||||
def from_http(self, request: HttpRequest,
|
||||
user: Optional[settings.AUTH_USER_MODEL] = None) -> 'Event':
|
||||
def from_http(
|
||||
self, request: HttpRequest, user: Optional[settings.AUTH_USER_MODEL] = None
|
||||
) -> "Event":
|
||||
"""Add data from a Django-HttpRequest, allowing the creation of
|
||||
Events independently from requests.
|
||||
`user` arguments optionally overrides user from requests."""
|
||||
if hasattr(request, 'user'):
|
||||
if hasattr(request, "user"):
|
||||
if isinstance(request.user, AnonymousUser):
|
||||
self.user = get_anonymous_user()
|
||||
else:
|
||||
@ -85,7 +117,7 @@ class Event(UUIDModel):
|
||||
if user:
|
||||
self.user = user
|
||||
# User 255.255.255.255 as fallback if IP cannot be determined
|
||||
self.client_ip = get_client_ip(request) or '255.255.255.255'
|
||||
self.client_ip = get_client_ip(request) or "255.255.255.255"
|
||||
# If there's no app set, we get it from the requests too
|
||||
if not self.app:
|
||||
self.app = Event._get_app_from_request(request)
|
||||
@ -94,10 +126,12 @@ class Event(UUIDModel):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self._state.adding:
|
||||
raise ValidationError("you may not edit an existing %s" % self._meta.model_name)
|
||||
raise ValidationError(
|
||||
"you may not edit an existing %s" % self._meta.model_name
|
||||
)
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Audit Event')
|
||||
verbose_name_plural = _('Audit Events')
|
||||
verbose_name = _("Audit Event")
|
||||
verbose_name_plural = _("Audit Events")
|
||||
|
||||
@ -3,31 +3,43 @@ from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||
from django.dispatch import receiver
|
||||
|
||||
from passbook.audit.models import Event, EventAction
|
||||
from passbook.core.signals import (invitation_created, invitation_used,
|
||||
user_signed_up)
|
||||
from passbook.core.signals import invitation_created, invitation_used, user_signed_up
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
def on_user_logged_in(sender, request, user, **kwargs):
|
||||
# pylint: disable=unused-argument
|
||||
def on_user_logged_in(sender, request, user, **_):
|
||||
"""Log successful login"""
|
||||
Event.new(EventAction.LOGIN).from_http(request)
|
||||
|
||||
|
||||
@receiver(user_logged_out)
|
||||
def on_user_logged_out(sender, request, user, **kwargs):
|
||||
# pylint: disable=unused-argument
|
||||
def on_user_logged_out(sender, request, user, **_):
|
||||
"""Log successfully logout"""
|
||||
Event.new(EventAction.LOGOUT).from_http(request)
|
||||
|
||||
|
||||
@receiver(user_signed_up)
|
||||
def on_user_signed_up(sender, request, user, **kwargs):
|
||||
# pylint: disable=unused-argument
|
||||
def on_user_signed_up(sender, request, user, **_):
|
||||
"""Log successfully signed up"""
|
||||
Event.new(EventAction.SIGN_UP).from_http(request)
|
||||
|
||||
|
||||
@receiver(invitation_created)
|
||||
def on_invitation_created(sender, request, invitation, **kwargs):
|
||||
# pylint: disable=unused-argument
|
||||
def on_invitation_created(sender, request, invitation, **_):
|
||||
"""Log Invitation creation"""
|
||||
Event.new(EventAction.INVITE_CREATED, invitation_uuid=invitation.uuid.hex).from_http(request)
|
||||
Event.new(
|
||||
EventAction.INVITE_CREATED, invitation_uuid=invitation.uuid.hex
|
||||
).from_http(request)
|
||||
|
||||
|
||||
@receiver(invitation_used)
|
||||
def on_invitation_used(sender, request, invitation, **kwargs):
|
||||
# pylint: disable=unused-argument
|
||||
def on_invitation_used(sender, request, invitation, **_):
|
||||
"""Log Invitation usage"""
|
||||
Event.new(EventAction.INVITE_USED, invitation_uuid=invitation.uuid.hex).from_http(request)
|
||||
Event.new(EventAction.INVITE_USED, invitation_uuid=invitation.uuid.hex).from_http(
|
||||
request
|
||||
)
|
||||
|
||||
0
passbook/audit/tests/__init__.py
Normal file
0
passbook/audit/tests/__init__.py
Normal file
33
passbook/audit/tests/test_event.py
Normal file
33
passbook/audit/tests/test_event.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""audit event tests"""
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
|
||||
from passbook.audit.models import Event, EventAction
|
||||
from passbook.core.models import Policy
|
||||
|
||||
|
||||
class TestAuditEvent(TestCase):
|
||||
"""Test Audit Event"""
|
||||
|
||||
def test_new_with_model(self):
|
||||
"""Create a new Event passing a model as kwarg"""
|
||||
event = Event.new(EventAction.CUSTOM, test={"model": get_anonymous_user()})
|
||||
event.save() # We save to ensure nothing is un-saveable
|
||||
model_content_type = ContentType.objects.get_for_model(get_anonymous_user())
|
||||
self.assertEqual(
|
||||
event.context.get("test").get("model").get("app"),
|
||||
model_content_type.app_label,
|
||||
)
|
||||
|
||||
def test_new_with_uuid_model(self):
|
||||
"""Create a new Event passing a model (with UUID PK) as kwarg"""
|
||||
temp_model = Policy.objects.create()
|
||||
event = Event.new(EventAction.CUSTOM, model=temp_model)
|
||||
event.save() # We save to ensure nothing is un-saveable
|
||||
model_content_type = ContentType.objects.get_for_model(temp_model)
|
||||
self.assertEqual(
|
||||
event.context.get("model").get("app"), model_content_type.app_label
|
||||
)
|
||||
self.assertEqual(event.context.get("model").get("pk"), temp_model.pk.hex)
|
||||
@ -2,4 +2,4 @@
|
||||
|
||||
from passbook.lib.admin import admin_autoregister
|
||||
|
||||
admin_autoregister('passbook_core')
|
||||
admin_autoregister("passbook_core")
|
||||
|
||||
@ -11,8 +11,16 @@ class ApplicationSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
|
||||
model = Application
|
||||
fields = ['pk', 'name', 'slug', 'launch_url', 'icon_url',
|
||||
'provider', 'policies', 'skip_authorization']
|
||||
fields = [
|
||||
"pk",
|
||||
"name",
|
||||
"slug",
|
||||
"launch_url",
|
||||
"icon_url",
|
||||
"provider",
|
||||
"policies",
|
||||
"skip_authorization",
|
||||
]
|
||||
|
||||
|
||||
class ApplicationViewSet(ModelViewSet):
|
||||
|
||||
@ -8,16 +8,16 @@ from passbook.core.models import Factor
|
||||
class FactorSerializer(ModelSerializer):
|
||||
"""Factor Serializer"""
|
||||
|
||||
__type__ = SerializerMethodField(method_name='get_type')
|
||||
__type__ = SerializerMethodField(method_name="get_type")
|
||||
|
||||
def get_type(self, obj):
|
||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace('factor', '')
|
||||
return obj._meta.object_name.lower().replace("factor", "")
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Factor
|
||||
fields = ['pk', 'name', 'slug', 'order', 'enabled', '__type__']
|
||||
fields = ["pk", "name", "slug", "order", "enabled", "__type__"]
|
||||
|
||||
|
||||
class FactorViewSet(ReadOnlyModelViewSet):
|
||||
|
||||
@ -11,7 +11,7 @@ class GroupSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
|
||||
model = Group
|
||||
fields = ['pk', 'name', 'parent', 'user_set', 'attributes']
|
||||
fields = ["pk", "name", "parent", "user_set", "attributes"]
|
||||
|
||||
|
||||
class GroupViewSet(ModelViewSet):
|
||||
|
||||
@ -11,7 +11,13 @@ class InvitationSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
|
||||
model = Invitation
|
||||
fields = ['pk', 'expires', 'fixed_username', 'fixed_email', 'needs_confirmation']
|
||||
fields = [
|
||||
"pk",
|
||||
"expires",
|
||||
"fixed_username",
|
||||
"fixed_email",
|
||||
"needs_confirmation",
|
||||
]
|
||||
|
||||
|
||||
class InvitationViewSet(ModelViewSet):
|
||||
|
||||
@ -9,16 +9,16 @@ from passbook.policies.forms import GENERAL_FIELDS
|
||||
class PolicySerializer(ModelSerializer):
|
||||
"""Policy Serializer"""
|
||||
|
||||
__type__ = SerializerMethodField(method_name='get_type')
|
||||
__type__ = SerializerMethodField(method_name="get_type")
|
||||
|
||||
def get_type(self, obj):
|
||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace('policy', '')
|
||||
return obj._meta.object_name.lower().replace("policy", "")
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Policy
|
||||
fields = ['pk'] + GENERAL_FIELDS + ['__type__']
|
||||
fields = ["pk"] + GENERAL_FIELDS + ["__type__"]
|
||||
|
||||
|
||||
class PolicyViewSet(ReadOnlyModelViewSet):
|
||||
|
||||
@ -8,16 +8,16 @@ from passbook.core.models import PropertyMapping
|
||||
class PropertyMappingSerializer(ModelSerializer):
|
||||
"""PropertyMapping Serializer"""
|
||||
|
||||
__type__ = SerializerMethodField(method_name='get_type')
|
||||
__type__ = SerializerMethodField(method_name="get_type")
|
||||
|
||||
def get_type(self, obj):
|
||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace('propertymapping', '')
|
||||
return obj._meta.object_name.lower().replace("propertymapping", "")
|
||||
|
||||
class Meta:
|
||||
|
||||
model = PropertyMapping
|
||||
fields = ['pk', 'name', '__type__']
|
||||
fields = ["pk", "name", "__type__"]
|
||||
|
||||
|
||||
class PropertyMappingViewSet(ReadOnlyModelViewSet):
|
||||
|
||||
@ -8,16 +8,16 @@ from passbook.core.models import Provider
|
||||
class ProviderSerializer(ModelSerializer):
|
||||
"""Provider Serializer"""
|
||||
|
||||
__type__ = SerializerMethodField(method_name='get_type')
|
||||
__type__ = SerializerMethodField(method_name="get_type")
|
||||
|
||||
def get_type(self, obj):
|
||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace('provider', '')
|
||||
return obj._meta.object_name.lower().replace("provider", "")
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Provider
|
||||
fields = ['pk', 'property_mappings', '__type__']
|
||||
fields = ["pk", "property_mappings", "__type__"]
|
||||
|
||||
|
||||
class ProviderViewSet(ReadOnlyModelViewSet):
|
||||
|
||||
@ -9,16 +9,16 @@ from passbook.core.models import Source
|
||||
class SourceSerializer(ModelSerializer):
|
||||
"""Source Serializer"""
|
||||
|
||||
__type__ = SerializerMethodField(method_name='get_type')
|
||||
__type__ = SerializerMethodField(method_name="get_type")
|
||||
|
||||
def get_type(self, obj):
|
||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
||||
return obj._meta.object_name.lower().replace('source', '')
|
||||
return obj._meta.object_name.lower().replace("source", "")
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Source
|
||||
fields = SOURCE_SERIALIZER_FIELDS + ['__type__']
|
||||
fields = SOURCE_SERIALIZER_FIELDS + ["__type__"]
|
||||
|
||||
|
||||
class SourceViewSet(ReadOnlyModelViewSet):
|
||||
|
||||
@ -11,7 +11,7 @@ class UserSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
|
||||
model = User
|
||||
fields = ['pk', 'username', 'name', 'email']
|
||||
fields = ["pk", "username", "name", "email"]
|
||||
|
||||
|
||||
class UserViewSet(ModelViewSet):
|
||||
|
||||
@ -5,7 +5,7 @@ from django.apps import AppConfig
|
||||
class PassbookCoreConfig(AppConfig):
|
||||
"""passbook core app config"""
|
||||
|
||||
name = 'passbook.core'
|
||||
label = 'passbook_core'
|
||||
verbose_name = 'passbook Core'
|
||||
mountpoint = ''
|
||||
name = "passbook.core"
|
||||
label = "passbook_core"
|
||||
verbose_name = "passbook Core"
|
||||
mountpoint = ""
|
||||
|
||||
@ -9,21 +9,29 @@ from passbook.core.models import Application, Provider
|
||||
class ApplicationForm(forms.ModelForm):
|
||||
"""Application Form"""
|
||||
|
||||
provider = forms.ModelChoiceField(queryset=Provider.objects.all().select_subclasses(),
|
||||
required=False)
|
||||
provider = forms.ModelChoiceField(
|
||||
queryset=Provider.objects.all().select_subclasses(), required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Application
|
||||
fields = ['name', 'slug', 'launch_url', 'icon_url',
|
||||
'provider', 'policies', 'skip_authorization']
|
||||
fields = [
|
||||
"name",
|
||||
"slug",
|
||||
"launch_url",
|
||||
"icon_url",
|
||||
"provider",
|
||||
"policies",
|
||||
"skip_authorization",
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
'launch_url': forms.TextInput(),
|
||||
'icon_url': forms.TextInput(),
|
||||
'policies': FilteredSelectMultiple(_('policies'), False)
|
||||
"name": forms.TextInput(),
|
||||
"launch_url": forms.TextInput(),
|
||||
"icon_url": forms.TextInput(),
|
||||
"policies": FilteredSelectMultiple(_("policies"), False),
|
||||
}
|
||||
labels = {
|
||||
'launch_url': _('Launch URL'),
|
||||
'icon_url': _('Icon URL'),
|
||||
"launch_url": _("Launch URL"),
|
||||
"icon_url": _("Icon URL"),
|
||||
}
|
||||
|
||||
@ -11,55 +11,64 @@ from passbook.lib.utils.ui import human_list
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class LoginForm(forms.Form):
|
||||
"""Allow users to login"""
|
||||
|
||||
title = _('Log in to your account')
|
||||
title = _("Log in to your account")
|
||||
uid_field = forms.CharField()
|
||||
remember_me = forms.BooleanField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if CONFIG.y('passbook.uid_fields') == ['e-mail']:
|
||||
self.fields['uid_field'] = forms.EmailField()
|
||||
self.fields['uid_field'].widget.attrs = {
|
||||
'placeholder': _(human_list([x.title() for x in CONFIG.y('passbook.uid_fields')]))
|
||||
if CONFIG.y("passbook.uid_fields") == ["e-mail"]:
|
||||
self.fields["uid_field"] = forms.EmailField()
|
||||
self.fields["uid_field"].widget.attrs = {
|
||||
"placeholder": _(
|
||||
human_list([x.title() for x in CONFIG.y("passbook.uid_fields")])
|
||||
)
|
||||
}
|
||||
|
||||
def clean_uid_field(self):
|
||||
"""Validate uid_field after EmailValidator if 'email' is the only selected uid_fields"""
|
||||
if CONFIG.y('passbook.uid_fields') == ['email']:
|
||||
validate_email(self.cleaned_data.get('uid_field'))
|
||||
return self.cleaned_data.get('uid_field')
|
||||
if CONFIG.y("passbook.uid_fields") == ["email"]:
|
||||
validate_email(self.cleaned_data.get("uid_field"))
|
||||
return self.cleaned_data.get("uid_field")
|
||||
|
||||
|
||||
class SignUpForm(forms.Form):
|
||||
"""SignUp Form"""
|
||||
|
||||
title = _('Sign Up')
|
||||
name = forms.CharField(label=_('Name'),
|
||||
widget=forms.TextInput(attrs={'placeholder': _('Name')}))
|
||||
username = forms.CharField(label=_('Username'),
|
||||
widget=forms.TextInput(attrs={'placeholder': _('Username')}))
|
||||
email = forms.EmailField(label=_('E-Mail'),
|
||||
widget=forms.TextInput(attrs={'placeholder': _('E-Mail')}))
|
||||
password = forms.CharField(label=_('Password'),
|
||||
widget=forms.PasswordInput(attrs={'placeholder': _('Password')}))
|
||||
password_repeat = forms.CharField(label=_('Repeat Password'),
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'placeholder': _('Repeat Password')
|
||||
}))
|
||||
title = _("Sign Up")
|
||||
name = forms.CharField(
|
||||
label=_("Name"), widget=forms.TextInput(attrs={"placeholder": _("Name")})
|
||||
)
|
||||
username = forms.CharField(
|
||||
label=_("Username"),
|
||||
widget=forms.TextInput(attrs={"placeholder": _("Username")}),
|
||||
)
|
||||
email = forms.EmailField(
|
||||
label=_("E-Mail"), widget=forms.TextInput(attrs={"placeholder": _("E-Mail")})
|
||||
)
|
||||
password = forms.CharField(
|
||||
label=_("Password"),
|
||||
widget=forms.PasswordInput(attrs={"placeholder": _("Password")}),
|
||||
)
|
||||
password_repeat = forms.CharField(
|
||||
label=_("Repeat Password"),
|
||||
widget=forms.PasswordInput(attrs={"placeholder": _("Repeat Password")}),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# All fields which have initial data supplied are set to read only
|
||||
if 'initial' in kwargs:
|
||||
for field in kwargs.get('initial').keys():
|
||||
self.fields[field].widget.attrs['readonly'] = 'readonly'
|
||||
if "initial" in kwargs:
|
||||
for field in kwargs.get("initial").keys():
|
||||
self.fields[field].widget.attrs["readonly"] = "readonly"
|
||||
|
||||
def clean_username(self):
|
||||
"""Check if username is used already"""
|
||||
username = self.cleaned_data.get('username')
|
||||
username = self.cleaned_data.get("username")
|
||||
if User.objects.filter(username=username).exists():
|
||||
LOGGER.warning("Username %s already exists", username)
|
||||
raise ValidationError(_("Username already exists"))
|
||||
@ -67,7 +76,7 @@ class SignUpForm(forms.Form):
|
||||
|
||||
def clean_email(self):
|
||||
"""Check if email is already used in django or other auth sources"""
|
||||
email = self.cleaned_data.get('email')
|
||||
email = self.cleaned_data.get("email")
|
||||
# Check if user exists already, error early
|
||||
if User.objects.filter(email=email).exists():
|
||||
LOGGER.debug("email %s exists in django", email)
|
||||
@ -76,8 +85,8 @@ class SignUpForm(forms.Form):
|
||||
|
||||
def clean_password_repeat(self):
|
||||
"""Check if Password adheres to filter and if passwords matche"""
|
||||
password = self.cleaned_data.get('password')
|
||||
password_repeat = self.cleaned_data.get('password_repeat')
|
||||
password = self.cleaned_data.get("password")
|
||||
password_repeat = self.cleaned_data.get("password_repeat")
|
||||
if password != password_repeat:
|
||||
raise ValidationError(_("Passwords don't match"))
|
||||
return self.cleaned_data.get('password_repeat')
|
||||
return self.cleaned_data.get("password_repeat")
|
||||
|
||||
@ -9,24 +9,29 @@ class GroupForm(forms.ModelForm):
|
||||
"""Group Form"""
|
||||
|
||||
members = forms.ModelMultipleChoiceField(
|
||||
User.objects.all(), required=False, widget=FilteredSelectMultiple('users', False))
|
||||
User.objects.all(),
|
||||
required=False,
|
||||
widget=FilteredSelectMultiple("users", False),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.instance.pk:
|
||||
self.initial['members'] = self.instance.user_set.values_list('pk', flat=True)
|
||||
self.initial["members"] = self.instance.user_set.values_list(
|
||||
"pk", flat=True
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
instance = super().save(*args, **kwargs)
|
||||
if instance.pk:
|
||||
instance.user_set.clear()
|
||||
instance.user_set.add(*self.cleaned_data['members'])
|
||||
instance.user_set.add(*self.cleaned_data["members"])
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Group
|
||||
fields = ['name', 'parent', 'members', 'attributes']
|
||||
fields = ["name", "parent", "members", "attributes"]
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
"name": forms.TextInput(),
|
||||
}
|
||||
|
||||
@ -12,27 +12,27 @@ class InvitationForm(forms.ModelForm):
|
||||
|
||||
def clean_fixed_username(self):
|
||||
"""Check if username is already used"""
|
||||
username = self.cleaned_data.get('fixed_username')
|
||||
username = self.cleaned_data.get("fixed_username")
|
||||
if User.objects.filter(username=username).exists():
|
||||
raise ValidationError(_('Username is already in use.'))
|
||||
raise ValidationError(_("Username is already in use."))
|
||||
return username
|
||||
|
||||
def clean_fixed_email(self):
|
||||
"""Check if email is already used"""
|
||||
email = self.cleaned_data.get('fixed_email')
|
||||
email = self.cleaned_data.get("fixed_email")
|
||||
if User.objects.filter(email=email).exists():
|
||||
raise ValidationError(_('E-Mail is already in use.'))
|
||||
raise ValidationError(_("E-Mail is already in use."))
|
||||
return email
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Invitation
|
||||
fields = ['expires', 'fixed_username', 'fixed_email', 'needs_confirmation']
|
||||
fields = ["expires", "fixed_username", "fixed_email", "needs_confirmation"]
|
||||
labels = {
|
||||
'fixed_username': "Force user's username (optional)",
|
||||
'fixed_email': "Force user's email (optional)",
|
||||
"fixed_username": "Force user's username (optional)",
|
||||
"fixed_email": "Force user's email (optional)",
|
||||
}
|
||||
widgets = {
|
||||
'fixed_username': forms.TextInput(),
|
||||
'fixed_email': forms.TextInput(),
|
||||
"fixed_username": forms.TextInput(),
|
||||
"fixed_email": forms.TextInput(),
|
||||
}
|
||||
|
||||
@ -13,10 +13,8 @@ class DebugPolicyForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
||||
model = DebugPolicy
|
||||
fields = GENERAL_FIELDS + ['result', 'wait_min', 'wait_max']
|
||||
fields = GENERAL_FIELDS + ["result", "wait_min", "wait_max"]
|
||||
widgets = {
|
||||
'name': forms.TextInput(),
|
||||
}
|
||||
labels = {
|
||||
'result': _('Allow user')
|
||||
"name": forms.TextInput(),
|
||||
}
|
||||
labels = {"result": _("Allow user")}
|
||||
|
||||
@ -13,29 +13,30 @@ class UserDetailForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
||||
model = User
|
||||
fields = ['username', 'name', 'email']
|
||||
widgets = {
|
||||
'name': forms.TextInput
|
||||
}
|
||||
fields = ["username", "name", "email"]
|
||||
widgets = {"name": forms.TextInput}
|
||||
|
||||
|
||||
class PasswordChangeForm(forms.Form):
|
||||
"""Form to update password"""
|
||||
|
||||
password = forms.CharField(label=_('Password'),
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'placeholder': _('New Password'),
|
||||
'autocomplete': 'new-password'
|
||||
}))
|
||||
password_repeat = forms.CharField(label=_('Repeat Password'),
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'placeholder': _('Repeat Password'),
|
||||
'autocomplete': 'new-password'
|
||||
}))
|
||||
password = forms.CharField(
|
||||
label=_("Password"),
|
||||
widget=forms.PasswordInput(
|
||||
attrs={"placeholder": _("New Password"), "autocomplete": "new-password"}
|
||||
),
|
||||
)
|
||||
password_repeat = forms.CharField(
|
||||
label=_("Repeat Password"),
|
||||
widget=forms.PasswordInput(
|
||||
attrs={"placeholder": _("Repeat Password"), "autocomplete": "new-password"}
|
||||
),
|
||||
)
|
||||
|
||||
def clean_password_repeat(self):
|
||||
"""Check if Password adheres to filter and if passwords matche"""
|
||||
password = self.cleaned_data.get('password')
|
||||
password_repeat = self.cleaned_data.get('password_repeat')
|
||||
password = self.cleaned_data.get("password")
|
||||
password_repeat = self.cleaned_data.get("password_repeat")
|
||||
if password != password_repeat:
|
||||
raise ValidationError(_("Passwords don't match"))
|
||||
return self.cleaned_data.get('password_repeat')
|
||||
return self.cleaned_data.get("password_repeat")
|
||||
|
||||
@ -18,206 +18,433 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0011_update_proxy_permissions'),
|
||||
("auth", "0011_update_proxy_permissions"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
name="User",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
|
||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
|
||||
('name', models.TextField()),
|
||||
('password_change_date', models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||
(
|
||||
"last_login",
|
||||
models.DateTimeField(
|
||||
blank=True, null=True, verbose_name="last login"
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_superuser",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||
verbose_name="superuser status",
|
||||
),
|
||||
),
|
||||
(
|
||||
"username",
|
||||
models.CharField(
|
||||
error_messages={
|
||||
"unique": "A user with that username already exists."
|
||||
},
|
||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||
max_length=150,
|
||||
unique=True,
|
||||
validators=[
|
||||
django.contrib.auth.validators.UnicodeUsernameValidator()
|
||||
],
|
||||
verbose_name="username",
|
||||
),
|
||||
),
|
||||
(
|
||||
"first_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=30, verbose_name="first name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"last_name",
|
||||
models.CharField(
|
||||
blank=True, max_length=150, verbose_name="last name"
|
||||
),
|
||||
),
|
||||
(
|
||||
"email",
|
||||
models.EmailField(
|
||||
blank=True, max_length=254, verbose_name="email address"
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_staff",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="Designates whether the user can log into this admin site.",
|
||||
verbose_name="staff status",
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_active",
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
||||
verbose_name="active",
|
||||
),
|
||||
),
|
||||
(
|
||||
"date_joined",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name="date joined"
|
||||
),
|
||||
),
|
||||
("uuid", models.UUIDField(default=uuid.uuid4, editable=False)),
|
||||
("name", models.TextField()),
|
||||
("password_change_date", models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user',
|
||||
'verbose_name_plural': 'users',
|
||||
'abstract': False,
|
||||
"verbose_name": "user",
|
||||
"verbose_name_plural": "users",
|
||||
"abstract": False,
|
||||
},
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
managers=[("objects", django.contrib.auth.models.UserManager()),],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Policy',
|
||||
name="Policy",
|
||||
fields=[
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.TextField(blank=True, null=True)),
|
||||
('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)),
|
||||
('negate', models.BooleanField(default=False)),
|
||||
('order', models.IntegerField(default=0)),
|
||||
('timeout', models.IntegerField(default=30)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("last_updated", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("name", models.TextField(blank=True, null=True)),
|
||||
(
|
||||
"action",
|
||||
models.CharField(
|
||||
choices=[("allow", "allow"), ("deny", "deny")], max_length=20
|
||||
),
|
||||
),
|
||||
("negate", models.BooleanField(default=False)),
|
||||
("order", models.IntegerField(default=0)),
|
||||
("timeout", models.IntegerField(default=30)),
|
||||
],
|
||||
options={"abstract": False,},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PolicyModel",
|
||||
fields=[
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("last_updated", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"policies",
|
||||
models.ManyToManyField(blank=True, to="passbook_core.Policy"),
|
||||
),
|
||||
],
|
||||
options={"abstract": False,},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PropertyMapping",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
"verbose_name": "Property Mapping",
|
||||
"verbose_name_plural": "Property Mappings",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PolicyModel',
|
||||
name="DebugPolicy",
|
||||
fields=[
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')),
|
||||
(
|
||||
"policy_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="passbook_core.Policy",
|
||||
),
|
||||
),
|
||||
("result", models.BooleanField(default=False)),
|
||||
("wait_min", models.IntegerField(default=5)),
|
||||
("wait_max", models.IntegerField(default=30)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
"verbose_name": "Debug Policy",
|
||||
"verbose_name_plural": "Debug Policies",
|
||||
},
|
||||
bases=("passbook_core.policy",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PropertyMapping',
|
||||
name="Factor",
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.TextField()),
|
||||
(
|
||||
"policymodel_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="passbook_core.PolicyModel",
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
("slug", models.SlugField(unique=True)),
|
||||
("order", models.IntegerField()),
|
||||
("enabled", models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Property Mapping',
|
||||
'verbose_name_plural': 'Property Mappings',
|
||||
},
|
||||
options={"abstract": False,},
|
||||
bases=("passbook_core.policymodel",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DebugPolicy',
|
||||
name="Source",
|
||||
fields=[
|
||||
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
|
||||
('result', models.BooleanField(default=False)),
|
||||
('wait_min', models.IntegerField(default=5)),
|
||||
('wait_max', models.IntegerField(default=30)),
|
||||
(
|
||||
"policymodel_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="passbook_core.PolicyModel",
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
("slug", models.SlugField()),
|
||||
("enabled", models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Debug Policy',
|
||||
'verbose_name_plural': 'Debug Policies',
|
||||
},
|
||||
bases=('passbook_core.policy',),
|
||||
options={"abstract": False,},
|
||||
bases=("passbook_core.policymodel",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Factor',
|
||||
name="Provider",
|
||||
fields=[
|
||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
||||
('name', models.TextField()),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('order', models.IntegerField()),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('passbook_core.policymodel',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Source',
|
||||
fields=[
|
||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
||||
('name', models.TextField()),
|
||||
('slug', models.SlugField()),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('passbook_core.policymodel',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Provider',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('property_mappings', models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"property_mappings",
|
||||
models.ManyToManyField(
|
||||
blank=True, default=None, to="passbook_core.PropertyMapping"
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Nonce',
|
||||
name="Nonce",
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)),
|
||||
('expiring', models.BooleanField(default=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"expires",
|
||||
models.DateTimeField(
|
||||
default=passbook.core.models.default_nonce_duration
|
||||
),
|
||||
),
|
||||
("expiring", models.BooleanField(default=True)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={"verbose_name": "Nonce", "verbose_name_plural": "Nonces",},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Invitation",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("expires", models.DateTimeField(blank=True, default=None, null=True)),
|
||||
("fixed_username", models.TextField(blank=True, default=None)),
|
||||
("fixed_email", models.TextField(blank=True, default=None)),
|
||||
("needs_confirmation", models.BooleanField(default=True)),
|
||||
(
|
||||
"created_by",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Nonce',
|
||||
'verbose_name_plural': 'Nonces',
|
||||
"verbose_name": "Invitation",
|
||||
"verbose_name_plural": "Invitations",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Invitation',
|
||||
name="Group",
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('expires', models.DateTimeField(blank=True, default=None, null=True)),
|
||||
('fixed_username', models.TextField(blank=True, default=None)),
|
||||
('fixed_email', models.TextField(blank=True, default=None)),
|
||||
('needs_confirmation', models.BooleanField(default=True)),
|
||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=80, verbose_name="name")),
|
||||
(
|
||||
"tags",
|
||||
django.contrib.postgres.fields.jsonb.JSONField(
|
||||
blank=True, default=dict
|
||||
),
|
||||
),
|
||||
(
|
||||
"parent",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="children",
|
||||
to="passbook_core.Group",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Invitation',
|
||||
'verbose_name_plural': 'Invitations',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Group',
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=80, verbose_name='name')),
|
||||
('tags', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('name', 'parent')},
|
||||
},
|
||||
options={"unique_together": {("name", "parent")},},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='groups',
|
||||
field=models.ManyToManyField(to='passbook_core.Group'),
|
||||
model_name="user",
|
||||
name="groups",
|
||||
field=models.ManyToManyField(to="passbook_core.Group"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='user_permissions',
|
||||
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
|
||||
model_name="user",
|
||||
name="user_permissions",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Specific permissions for this user.",
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
to="auth.Permission",
|
||||
verbose_name="user permissions",
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserSourceConnection',
|
||||
name="UserSourceConnection",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("last_updated", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"source",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="passbook_core.Source",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('user', 'source')},
|
||||
},
|
||||
options={"unique_together": {("user", "source")},},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Application',
|
||||
name="Application",
|
||||
fields=[
|
||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
||||
('name', models.TextField()),
|
||||
('slug', models.SlugField()),
|
||||
('launch_url', models.URLField(blank=True, null=True)),
|
||||
('icon_url', models.TextField(blank=True, null=True)),
|
||||
('skip_authorization', models.BooleanField(default=False)),
|
||||
('provider', models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='passbook_core.Provider')),
|
||||
(
|
||||
"policymodel_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="passbook_core.PolicyModel",
|
||||
),
|
||||
),
|
||||
("name", models.TextField()),
|
||||
("slug", models.SlugField()),
|
||||
("launch_url", models.URLField(blank=True, null=True)),
|
||||
("icon_url", models.TextField(blank=True, null=True)),
|
||||
("skip_authorization", models.BooleanField(default=False)),
|
||||
(
|
||||
"provider",
|
||||
models.OneToOneField(
|
||||
blank=True,
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||
to="passbook_core.Provider",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('passbook_core.policymodel',),
|
||||
options={"abstract": False,},
|
||||
bases=("passbook_core.policymodel",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='sources',
|
||||
field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'),
|
||||
model_name="user",
|
||||
name="sources",
|
||||
field=models.ManyToManyField(
|
||||
through="passbook_core.UserSourceConnection", to="passbook_core.Source"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@ -6,12 +6,12 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0001_initial'),
|
||||
("passbook_core", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='user',
|
||||
options={'permissions': (('reset_user_password', 'Reset Password'),)},
|
||||
name="user",
|
||||
options={"permissions": (("reset_user_password", "Reset Password"),)},
|
||||
),
|
||||
]
|
||||
|
||||
@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0001_initial'),
|
||||
("passbook_core", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='nonce',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, default=''),
|
||||
model_name="nonce",
|
||||
name="description",
|
||||
field=models.TextField(blank=True, default=""),
|
||||
),
|
||||
]
|
||||
|
||||
@ -7,23 +7,25 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0002_nonce_description'),
|
||||
("passbook_core", "0002_nonce_description"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='group',
|
||||
old_name='tags',
|
||||
new_name='attributes',
|
||||
model_name="group", old_name="tags", new_name="attributes",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='source',
|
||||
name='property_mappings',
|
||||
field=models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping'),
|
||||
model_name="source",
|
||||
name="property_mappings",
|
||||
field=models.ManyToManyField(
|
||||
blank=True, default=None, to="passbook_core.PropertyMapping"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='attributes',
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
|
||||
model_name="user",
|
||||
name="attributes",
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(
|
||||
blank=True, default=dict
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@ -6,9 +6,8 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0002_auto_20191010_1058'),
|
||||
('passbook_core', '0002_nonce_description'),
|
||||
("passbook_core", "0002_auto_20191010_1058"),
|
||||
("passbook_core", "0002_nonce_description"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
operations = []
|
||||
|
||||
@ -6,12 +6,9 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0003_auto_20191011_0914'),
|
||||
("passbook_core", "0003_auto_20191011_0914"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='policy',
|
||||
name='action',
|
||||
),
|
||||
migrations.RemoveField(model_name="policy", name="action",),
|
||||
]
|
||||
|
||||
@ -6,9 +6,8 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('passbook_core', '0004_remove_policy_action'),
|
||||
('passbook_core', '0003_merge_20191010_1541'),
|
||||
("passbook_core", "0004_remove_policy_action"),
|
||||
("passbook_core", "0003_merge_20191010_1541"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
operations = []
|
||||
|
||||
@ -27,12 +27,18 @@ def default_nonce_duration():
|
||||
"""Default duration a Nonce is valid"""
|
||||
return now() + timedelta(hours=4)
|
||||
|
||||
|
||||
class Group(UUIDModel):
|
||||
"""Custom Group model which supports a basic hierarchy"""
|
||||
|
||||
name = models.CharField(_('name'), max_length=80)
|
||||
parent = models.ForeignKey('Group', blank=True, null=True,
|
||||
on_delete=models.SET_NULL, related_name='children')
|
||||
name = models.CharField(_("name"), max_length=80)
|
||||
parent = models.ForeignKey(
|
||||
"Group",
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="children",
|
||||
)
|
||||
attributes = JSONField(default=dict, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
@ -40,7 +46,8 @@ class Group(UUIDModel):
|
||||
|
||||
class Meta:
|
||||
|
||||
unique_together = (('name', 'parent',),)
|
||||
unique_together = (("name", "parent",),)
|
||||
|
||||
|
||||
class User(GuardianUserMixin, AbstractUser):
|
||||
"""Custom User model to allow easier adding o f user-based settings"""
|
||||
@ -48,8 +55,8 @@ class User(GuardianUserMixin, AbstractUser):
|
||||
uuid = models.UUIDField(default=uuid4, editable=False)
|
||||
name = models.TextField()
|
||||
|
||||
sources = models.ManyToManyField('Source', through='UserSourceConnection')
|
||||
groups = models.ManyToManyField('Group')
|
||||
sources = models.ManyToManyField("Source", through="UserSourceConnection")
|
||||
groups = models.ManyToManyField("Group")
|
||||
password_change_date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
attributes = JSONField(default=dict, blank=True)
|
||||
@ -62,28 +69,29 @@ class User(GuardianUserMixin, AbstractUser):
|
||||
|
||||
class Meta:
|
||||
|
||||
permissions = (
|
||||
('reset_user_password', 'Reset Password'),
|
||||
)
|
||||
permissions = (("reset_user_password", "Reset Password"),)
|
||||
|
||||
|
||||
class Provider(models.Model):
|
||||
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
|
||||
|
||||
property_mappings = models.ManyToManyField('PropertyMapping', default=None, blank=True)
|
||||
property_mappings = models.ManyToManyField(
|
||||
"PropertyMapping", default=None, blank=True
|
||||
)
|
||||
|
||||
objects = InheritanceManager()
|
||||
|
||||
# This class defines no field for easier inheritance
|
||||
def __str__(self):
|
||||
if hasattr(self, 'name'):
|
||||
return getattr(self, 'name')
|
||||
if hasattr(self, "name"):
|
||||
return getattr(self, "name")
|
||||
return super().__str__()
|
||||
|
||||
|
||||
class PolicyModel(UUIDModel, CreatedUpdatedModel):
|
||||
"""Base model which can have policies applied to it"""
|
||||
|
||||
policies = models.ManyToManyField('Policy', blank=True)
|
||||
policies = models.ManyToManyField("Policy", blank=True)
|
||||
|
||||
|
||||
class UserSettings:
|
||||
@ -108,8 +116,8 @@ class Factor(PolicyModel):
|
||||
enabled = models.BooleanField(default=True)
|
||||
|
||||
objects = InheritanceManager()
|
||||
type = ''
|
||||
form = ''
|
||||
type = ""
|
||||
form = ""
|
||||
|
||||
def user_settings(self) -> Optional[UserSettings]:
|
||||
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||
@ -129,8 +137,9 @@ class Application(PolicyModel):
|
||||
slug = models.SlugField()
|
||||
launch_url = models.URLField(null=True, blank=True)
|
||||
icon_url = models.TextField(null=True, blank=True)
|
||||
provider = models.OneToOneField('Provider', null=True, blank=True,
|
||||
default=None, on_delete=models.SET_DEFAULT)
|
||||
provider = models.OneToOneField(
|
||||
"Provider", null=True, blank=True, default=None, on_delete=models.SET_DEFAULT
|
||||
)
|
||||
skip_authorization = models.BooleanField(default=False)
|
||||
|
||||
objects = InheritanceManager()
|
||||
@ -151,9 +160,11 @@ class Source(PolicyModel):
|
||||
name = models.TextField()
|
||||
slug = models.SlugField()
|
||||
enabled = models.BooleanField(default=True)
|
||||
property_mappings = models.ManyToManyField('PropertyMapping', default=None, blank=True)
|
||||
property_mappings = models.ManyToManyField(
|
||||
"PropertyMapping", default=None, blank=True
|
||||
)
|
||||
|
||||
form = '' # ModelForm-based class ued to create/edit instance
|
||||
form = "" # ModelForm-based class ued to create/edit instance
|
||||
|
||||
objects = InheritanceManager()
|
||||
|
||||
@ -185,7 +196,7 @@ class UserSourceConnection(CreatedUpdatedModel):
|
||||
|
||||
class Meta:
|
||||
|
||||
unique_together = (('user', 'source'),)
|
||||
unique_together = (("user", "source"),)
|
||||
|
||||
|
||||
class Policy(UUIDModel, CreatedUpdatedModel):
|
||||
@ -215,25 +226,25 @@ class DebugPolicy(Policy):
|
||||
wait_min = models.IntegerField(default=5)
|
||||
wait_max = models.IntegerField(default=30)
|
||||
|
||||
form = 'passbook.core.forms.policies.DebugPolicyForm'
|
||||
form = "passbook.core.forms.policies.DebugPolicyForm"
|
||||
|
||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||
"""Wait random time then return result"""
|
||||
wait = SystemRandom().randrange(self.wait_min, self.wait_max)
|
||||
LOGGER.debug("Policy waiting", policy=self, delay=wait)
|
||||
sleep(wait)
|
||||
return PolicyResult(self.result, 'Debugging')
|
||||
return PolicyResult(self.result, "Debugging")
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Debug Policy')
|
||||
verbose_name_plural = _('Debug Policies')
|
||||
verbose_name = _("Debug Policy")
|
||||
verbose_name_plural = _("Debug Policies")
|
||||
|
||||
|
||||
class Invitation(UUIDModel):
|
||||
"""Single-use invitation link"""
|
||||
|
||||
created_by = models.ForeignKey('User', on_delete=models.CASCADE)
|
||||
created_by = models.ForeignKey("User", on_delete=models.CASCADE)
|
||||
expires = models.DateTimeField(default=None, blank=True, null=True)
|
||||
fixed_username = models.TextField(blank=True, default=None)
|
||||
fixed_email = models.TextField(blank=True, default=None)
|
||||
@ -242,24 +253,26 @@ class Invitation(UUIDModel):
|
||||
@property
|
||||
def link(self):
|
||||
"""Get link to use invitation"""
|
||||
return reverse_lazy('passbook_core:auth-sign-up') + f'?invitation={self.uuid.hex}'
|
||||
return (
|
||||
reverse_lazy("passbook_core:auth-sign-up") + f"?invitation={self.uuid.hex}"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"Invitation {self.uuid.hex} created by {self.created_by}"
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Invitation')
|
||||
verbose_name_plural = _('Invitations')
|
||||
verbose_name = _("Invitation")
|
||||
verbose_name_plural = _("Invitations")
|
||||
|
||||
|
||||
class Nonce(UUIDModel):
|
||||
"""One-time link for password resets/sign-up-confirmations"""
|
||||
|
||||
expires = models.DateTimeField(default=default_nonce_duration)
|
||||
user = models.ForeignKey('User', on_delete=models.CASCADE)
|
||||
user = models.ForeignKey("User", on_delete=models.CASCADE)
|
||||
expiring = models.BooleanField(default=True)
|
||||
description = models.TextField(default='', blank=True)
|
||||
description = models.TextField(default="", blank=True)
|
||||
|
||||
@property
|
||||
def is_expired(self) -> bool:
|
||||
@ -271,8 +284,8 @@ class Nonce(UUIDModel):
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Nonce')
|
||||
verbose_name_plural = _('Nonces')
|
||||
verbose_name = _("Nonce")
|
||||
verbose_name_plural = _("Nonces")
|
||||
|
||||
|
||||
class PropertyMapping(UUIDModel):
|
||||
@ -280,7 +293,7 @@ class PropertyMapping(UUIDModel):
|
||||
|
||||
name = models.TextField()
|
||||
|
||||
form = ''
|
||||
form = ""
|
||||
objects = InheritanceManager()
|
||||
|
||||
def __str__(self):
|
||||
@ -288,5 +301,5 @@ class PropertyMapping(UUIDModel):
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _('Property Mapping')
|
||||
verbose_name_plural = _('Property Mappings')
|
||||
verbose_name = _("Property Mapping")
|
||||
verbose_name_plural = _("Property Mappings")
|
||||
|
||||
@ -7,16 +7,18 @@ from structlog import get_logger
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
user_signed_up = Signal(providing_args=['request', 'user'])
|
||||
invitation_created = Signal(providing_args=['request', 'invitation'])
|
||||
invitation_used = Signal(providing_args=['request', 'invitation', 'user'])
|
||||
password_changed = Signal(providing_args=['user', 'password'])
|
||||
user_signed_up = Signal(providing_args=["request", "user"])
|
||||
invitation_created = Signal(providing_args=["request", "invitation"])
|
||||
invitation_used = Signal(providing_args=["request", "invitation", "user"])
|
||||
password_changed = Signal(providing_args=["user", "password"])
|
||||
|
||||
|
||||
@receiver(post_save)
|
||||
# pylint: disable=unused-argument
|
||||
def invalidate_policy_cache(sender, instance, **_):
|
||||
"""Invalidate Policy cache when policy is updated"""
|
||||
from passbook.core.models import Policy
|
||||
|
||||
if isinstance(instance, Policy):
|
||||
LOGGER.debug("Invalidating policy cache", policy=instance)
|
||||
keys = cache.keys("%s#*" % instance.pk)
|
||||
|
||||
@ -7,8 +7,9 @@ from passbook.root.celery import CELERY_APP
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@CELERY_APP.task()
|
||||
def clean_nonces():
|
||||
"""Remove expired nonces"""
|
||||
amount, _ = Nonce.objects.filter(expires__lt=now(), expiring=True).delete()
|
||||
LOGGER.debug('Deleted expired nonces', amount=amount)
|
||||
LOGGER.debug("Deleted expired nonces", amount=amount)
|
||||
|
||||
@ -9,29 +9,37 @@ from passbook.policies.engine import PolicyEngine
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def user_factors(context: RequestContext) -> List[UserSettings]:
|
||||
"""Return list of all factors which apply to user"""
|
||||
user = context.get('request').user
|
||||
_all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses()
|
||||
user = context.get("request").user
|
||||
_all_factors = (
|
||||
Factor.objects.filter(enabled=True).order_by("order").select_subclasses()
|
||||
)
|
||||
matching_factors: List[UserSettings] = []
|
||||
for factor in _all_factors:
|
||||
user_settings = factor.user_settings()
|
||||
policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request'))
|
||||
policy_engine = PolicyEngine(
|
||||
factor.policies.all(), user, context.get("request")
|
||||
)
|
||||
policy_engine.build()
|
||||
if policy_engine.passing and user_settings:
|
||||
matching_factors.append(user_settings)
|
||||
return matching_factors
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def user_sources(context: RequestContext) -> List[UserSettings]:
|
||||
"""Return a list of all sources which are enabled for the user"""
|
||||
user = context.get('request').user
|
||||
user = context.get("request").user
|
||||
_all_sources = Source.objects.filter(enabled=True).select_subclasses()
|
||||
matching_sources: List[UserSettings] = []
|
||||
for factor in _all_sources:
|
||||
user_settings = factor.user_settings()
|
||||
policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request'))
|
||||
policy_engine = PolicyEngine(
|
||||
factor.policies.all(), user, context.get("request")
|
||||
)
|
||||
policy_engine.build()
|
||||
if policy_engine.passing and user_settings:
|
||||
matching_sources.append(user_settings)
|
||||
|
||||
@ -15,70 +15,78 @@ class TestAuthenticationViews(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.sign_up_data = {
|
||||
'name': 'Test',
|
||||
'username': 'beryjuorg',
|
||||
'email': 'unittest@passbook.beryju.org',
|
||||
'password': 'B3ryju0rg!',
|
||||
'password_repeat': 'B3ryju0rg!',
|
||||
"name": "Test",
|
||||
"username": "beryjuorg",
|
||||
"email": "unittest@passbook.beryju.org",
|
||||
"password": "B3ryju0rg!",
|
||||
"password_repeat": "B3ryju0rg!",
|
||||
}
|
||||
self.login_data = {
|
||||
'uid_field': 'unittest@example.com',
|
||||
"uid_field": "unittest@example.com",
|
||||
}
|
||||
self.user = User.objects.create_superuser(
|
||||
username='unittest user',
|
||||
email='unittest@example.com',
|
||||
password=''.join(SystemRandom().choice(
|
||||
string.ascii_uppercase + string.digits) for _ in range(8)))
|
||||
|
||||
username="unittest user",
|
||||
email="unittest@example.com",
|
||||
password="".join(
|
||||
SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||
for _ in range(8)
|
||||
),
|
||||
)
|
||||
|
||||
def test_sign_up_view(self):
|
||||
"""Test account.sign_up view (Anonymous)"""
|
||||
self.client.logout()
|
||||
response = self.client.get(reverse('passbook_core:auth-sign-up'))
|
||||
response = self.client.get(reverse("passbook_core:auth-sign-up"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_login_view(self):
|
||||
"""Test account.login view (Anonymous)"""
|
||||
self.client.logout()
|
||||
response = self.client.get(reverse('passbook_core:auth-login'))
|
||||
response = self.client.get(reverse("passbook_core:auth-login"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# test login with post
|
||||
form = LoginForm(self.login_data)
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
response = self.client.post(reverse('passbook_core:auth-login'), data=form.cleaned_data)
|
||||
response = self.client.post(
|
||||
reverse("passbook_core:auth-login"), data=form.cleaned_data
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_logout_view(self):
|
||||
"""Test account.logout view"""
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.get(reverse('passbook_core:auth-logout'))
|
||||
response = self.client.get(reverse("passbook_core:auth-logout"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_sign_up_view_auth(self):
|
||||
"""Test account.sign_up view (Authenticated)"""
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.get(reverse('passbook_core:auth-logout'))
|
||||
response = self.client.get(reverse("passbook_core:auth-logout"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_login_view_auth(self):
|
||||
"""Test account.login view (Authenticated)"""
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.get(reverse('passbook_core:auth-login'))
|
||||
response = self.client.get(reverse("passbook_core:auth-login"))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_login_view_post(self):
|
||||
"""Test account.login view POST (Anonymous)"""
|
||||
login_response = self.client.post(reverse('passbook_core:auth-login'), data=self.login_data)
|
||||
login_response = self.client.post(
|
||||
reverse("passbook_core:auth-login"), data=self.login_data
|
||||
)
|
||||
self.assertEqual(login_response.status_code, 302)
|
||||
self.assertEqual(login_response.url, reverse('passbook_core:auth-process'))
|
||||
self.assertEqual(login_response.url, reverse("passbook_core:auth-process"))
|
||||
|
||||
def test_sign_up_view_post(self):
|
||||
"""Test account.sign_up view POST (Anonymous)"""
|
||||
form = SignUpForm(self.sign_up_data)
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
response = self.client.post(reverse('passbook_core:auth-sign-up'), data=form.cleaned_data)
|
||||
response = self.client.post(
|
||||
reverse("passbook_core:auth-sign-up"), data=form.cleaned_data
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
# def test_reset_password_init_view(self):
|
||||
|
||||
@ -14,12 +14,17 @@ class TestOverviewViews(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = User.objects.create_superuser(
|
||||
username='unittest user',
|
||||
email='unittest@example.com',
|
||||
password=''.join(SystemRandom().choice(
|
||||
string.ascii_uppercase + string.digits) for _ in range(8)))
|
||||
username="unittest user",
|
||||
email="unittest@example.com",
|
||||
password="".join(
|
||||
SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||
for _ in range(8)
|
||||
),
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_overview(self):
|
||||
"""Test UserSettingsView"""
|
||||
self.assertEqual(self.client.get(reverse('passbook_core:overview')).status_code, 200)
|
||||
self.assertEqual(
|
||||
self.client.get(reverse("passbook_core:overview")).status_code, 200
|
||||
)
|
||||
|
||||
@ -15,33 +15,43 @@ class TestUserViews(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = User.objects.create_superuser(
|
||||
username='unittest user',
|
||||
email='unittest@example.com',
|
||||
password=''.join(SystemRandom().choice(
|
||||
string.ascii_uppercase + string.digits) for _ in range(8)))
|
||||
username="unittest user",
|
||||
email="unittest@example.com",
|
||||
password="".join(
|
||||
SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||
for _ in range(8)
|
||||
),
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_user_settings(self):
|
||||
"""Test UserSettingsView"""
|
||||
self.assertEqual(self.client.get(reverse('passbook_core:user-settings')).status_code, 200)
|
||||
self.assertEqual(
|
||||
self.client.get(reverse("passbook_core:user-settings")).status_code, 200
|
||||
)
|
||||
|
||||
def test_user_delete(self):
|
||||
"""Test UserDeleteView"""
|
||||
self.assertEqual(self.client.post(reverse('passbook_core:user-delete')).status_code, 302)
|
||||
self.assertEqual(User.objects.filter(username='unittest user').exists(), False)
|
||||
self.assertEqual(
|
||||
self.client.post(reverse("passbook_core:user-delete")).status_code, 302
|
||||
)
|
||||
self.assertEqual(User.objects.filter(username="unittest user").exists(), False)
|
||||
self.setUp()
|
||||
|
||||
def test_user_change_password(self):
|
||||
"""Test UserChangePasswordView"""
|
||||
form_data = {
|
||||
'password': 'test2',
|
||||
'password_repeat': 'test2'
|
||||
}
|
||||
form_data = {"password": "test2", "password_repeat": "test2"}
|
||||
form = PasswordChangeForm(data=form_data)
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertEqual(self.client.get(
|
||||
reverse('passbook_core:user-change-password')).status_code, 200)
|
||||
self.assertEqual(self.client.post(
|
||||
reverse('passbook_core:user-change-password'), data=form_data).status_code, 302)
|
||||
self.assertEqual(
|
||||
self.client.get(reverse("passbook_core:user-change-password")).status_code,
|
||||
200,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.client.post(
|
||||
reverse("passbook_core:user-change-password"), data=form_data
|
||||
).status_code,
|
||||
302,
|
||||
)
|
||||
self.user.refresh_from_db()
|
||||
self.assertTrue(self.user.check_password('test2'))
|
||||
self.assertTrue(self.user.check_password("test2"))
|
||||
|
||||
@ -13,22 +13,25 @@ class TestUtilViews(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_superuser(
|
||||
username='unittest user',
|
||||
email='unittest@example.com',
|
||||
password=''.join(SystemRandom().choice(
|
||||
string.ascii_uppercase + string.digits) for _ in range(8)))
|
||||
username="unittest user",
|
||||
email="unittest@example.com",
|
||||
password="".join(
|
||||
SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||
for _ in range(8)
|
||||
),
|
||||
)
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_loading_view(self):
|
||||
"""Test loading view"""
|
||||
request = self.factory.get('something')
|
||||
response = LoadingView.as_view(target_url='somestring')(request)
|
||||
request = self.factory.get("something")
|
||||
response = LoadingView.as_view(target_url="somestring")(request)
|
||||
response.render()
|
||||
self.assertIn('somestring', response.content.decode('utf-8'))
|
||||
self.assertIn("somestring", response.content.decode("utf-8"))
|
||||
|
||||
def test_permission_denied_view(self):
|
||||
"""Test PermissionDeniedView"""
|
||||
request = self.factory.get('something')
|
||||
request = self.factory.get("something")
|
||||
request.user = self.user
|
||||
response = PermissionDeniedView.as_view()(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@ -9,21 +9,38 @@ LOGGER = get_logger()
|
||||
|
||||
urlpatterns = [
|
||||
# Authentication views
|
||||
path('auth/login/', authentication.LoginView.as_view(), name='auth-login'),
|
||||
path('auth/logout/', authentication.LogoutView.as_view(), name='auth-logout'),
|
||||
path('auth/sign_up/', authentication.SignUpView.as_view(), name='auth-sign-up'),
|
||||
path('auth/sign_up/<uuid:nonce>/confirm/', authentication.SignUpConfirmView.as_view(),
|
||||
name='auth-sign-up-confirm'),
|
||||
path('auth/process/denied/', view.FactorPermissionDeniedView.as_view(), name='auth-denied'),
|
||||
path('auth/password/reset/<uuid:nonce>/', authentication.PasswordResetView.as_view(),
|
||||
name='auth-password-reset'),
|
||||
path('auth/process/', view.AuthenticationView.as_view(), name='auth-process'),
|
||||
path('auth/process/<slug:factor>/', view.AuthenticationView.as_view(), name='auth-process'),
|
||||
path("auth/login/", authentication.LoginView.as_view(), name="auth-login"),
|
||||
path("auth/logout/", authentication.LogoutView.as_view(), name="auth-logout"),
|
||||
path("auth/sign_up/", authentication.SignUpView.as_view(), name="auth-sign-up"),
|
||||
path(
|
||||
"auth/sign_up/<uuid:nonce>/confirm/",
|
||||
authentication.SignUpConfirmView.as_view(),
|
||||
name="auth-sign-up-confirm",
|
||||
),
|
||||
path(
|
||||
"auth/process/denied/",
|
||||
view.FactorPermissionDeniedView.as_view(),
|
||||
name="auth-denied",
|
||||
),
|
||||
path(
|
||||
"auth/password/reset/<uuid:nonce>/",
|
||||
authentication.PasswordResetView.as_view(),
|
||||
name="auth-password-reset",
|
||||
),
|
||||
path("auth/process/", view.AuthenticationView.as_view(), name="auth-process"),
|
||||
path(
|
||||
"auth/process/<slug:factor>/",
|
||||
view.AuthenticationView.as_view(),
|
||||
name="auth-process",
|
||||
),
|
||||
# User views
|
||||
path('_/user/', user.UserSettingsView.as_view(), name='user-settings'),
|
||||
path('_/user/delete/', user.UserDeleteView.as_view(), name='user-delete'),
|
||||
path('_/user/change_password/', user.UserChangePasswordView.as_view(),
|
||||
name='user-change-password'),
|
||||
path("_/user/", user.UserSettingsView.as_view(), name="user-settings"),
|
||||
path("_/user/delete/", user.UserDeleteView.as_view(), name="user-delete"),
|
||||
path(
|
||||
"_/user/change_password/",
|
||||
user.UserChangePasswordView.as_view(),
|
||||
name="user-change-password",
|
||||
),
|
||||
# Overview
|
||||
path('', overview.OverviewView.as_view(), name='overview'),
|
||||
path("", overview.OverviewView.as_view(), name="overview"),
|
||||
]
|
||||
|
||||
@ -11,6 +11,7 @@ from passbook.policies.engine import PolicyEngine
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class AccessMixin:
|
||||
"""Mixin class for usage in Authorization views.
|
||||
Provider functions to check application access, etc"""
|
||||
@ -23,12 +24,18 @@ class AccessMixin:
|
||||
try:
|
||||
return provider.application
|
||||
except Application.DoesNotExist as exc:
|
||||
messages.error(self.request, _('Provider "%(name)s" has no application assigned' % {
|
||||
'name': provider
|
||||
}))
|
||||
messages.error(
|
||||
self.request,
|
||||
_(
|
||||
'Provider "%(name)s" has no application assigned'
|
||||
% {"name": provider}
|
||||
),
|
||||
)
|
||||
raise exc
|
||||
|
||||
def user_has_access(self, application: Application, user: User) -> Tuple[bool, List[str]]:
|
||||
def user_has_access(
|
||||
self, application: Application, user: User
|
||||
) -> Tuple[bool, List[str]]:
|
||||
"""Check if user has access to application."""
|
||||
LOGGER.debug("Checking permissions", user=user, application=application)
|
||||
policy_engine = PolicyEngine(application.policies.all(), user, self.request)
|
||||
|
||||
@ -25,41 +25,41 @@ LOGGER = get_logger()
|
||||
class LoginView(UserPassesTestMixin, FormView):
|
||||
"""Allow users to sign in"""
|
||||
|
||||
template_name = 'login/form.html'
|
||||
template_name = "login/form.html"
|
||||
form_class = LoginForm
|
||||
success_url = '.'
|
||||
success_url = "."
|
||||
|
||||
# Allow only not authenticated users to login
|
||||
def test_func(self):
|
||||
return self.request.user.is_authenticated is False
|
||||
|
||||
def handle_no_permission(self):
|
||||
if 'next' in self.request.GET:
|
||||
return redirect(self.request.GET.get('next'))
|
||||
return redirect(reverse('passbook_core:overview'))
|
||||
if "next" in self.request.GET:
|
||||
return redirect(self.request.GET.get("next"))
|
||||
return redirect(reverse("passbook_core:overview"))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['config'] = CONFIG.y('passbook')
|
||||
kwargs['is_login'] = True
|
||||
kwargs['title'] = _('Log in to your account')
|
||||
kwargs['primary_action'] = _('Log in')
|
||||
kwargs['show_sign_up_notice'] = CONFIG.y('passbook.sign_up.enabled')
|
||||
kwargs['sources'] = []
|
||||
kwargs["config"] = CONFIG.y("passbook")
|
||||
kwargs["is_login"] = True
|
||||
kwargs["title"] = _("Log in to your account")
|
||||
kwargs["primary_action"] = _("Log in")
|
||||
kwargs["show_sign_up_notice"] = CONFIG.y("passbook.sign_up.enabled")
|
||||
kwargs["sources"] = []
|
||||
sources = Source.objects.filter(enabled=True).select_subclasses()
|
||||
for source in sources:
|
||||
login_button = source.login_button
|
||||
if login_button:
|
||||
kwargs['sources'].append(login_button)
|
||||
if kwargs['sources']:
|
||||
self.template_name = 'login/with_sources.html'
|
||||
kwargs["sources"].append(login_button)
|
||||
if kwargs["sources"]:
|
||||
self.template_name = "login/with_sources.html"
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_user(self, uid_value) -> Optional[User]:
|
||||
"""Find user instance. Returns None if no user was found."""
|
||||
for search_field in CONFIG.y('passbook.uid_fields'):
|
||||
for search_field in CONFIG.y("passbook.uid_fields"):
|
||||
# Workaround for E-Mail -> email
|
||||
if search_field == 'e-mail':
|
||||
search_field = 'email'
|
||||
if search_field == "e-mail":
|
||||
search_field = "email"
|
||||
users = User.objects.filter(**{search_field: uid_value})
|
||||
if users.exists():
|
||||
LOGGER.debug("Found user", user=users.first(), uid_field=search_field)
|
||||
@ -68,17 +68,20 @@ class LoginView(UserPassesTestMixin, FormView):
|
||||
|
||||
def form_valid(self, form: LoginForm) -> HttpResponse:
|
||||
"""Form data is valid"""
|
||||
pre_user = self.get_user(form.cleaned_data.get('uid_field'))
|
||||
pre_user = self.get_user(form.cleaned_data.get("uid_field"))
|
||||
if not pre_user:
|
||||
# No user found
|
||||
return self.invalid_login(self.request)
|
||||
# self.request.session.flush()
|
||||
self.request.session[AuthenticationView.SESSION_PENDING_USER] = pre_user.pk
|
||||
return _redirect_with_qs('passbook_core:auth-process', self.request.GET)
|
||||
return _redirect_with_qs("passbook_core:auth-process", self.request.GET)
|
||||
|
||||
def invalid_login(self, request: HttpRequest, disabled_user: User = None) -> HttpResponse:
|
||||
def invalid_login(
|
||||
self, request: HttpRequest, disabled_user: User = None
|
||||
) -> HttpResponse:
|
||||
"""Handle login for disabled users/invalid login attempts"""
|
||||
messages.error(request, _('Failed to authenticate.'))
|
||||
LOGGER.debug("invalid_login", user=disabled_user)
|
||||
messages.error(request, _("Failed to authenticate."))
|
||||
return self.render_to_response(self.get_context_data())
|
||||
|
||||
|
||||
@ -89,15 +92,15 @@ class LogoutView(LoginRequiredMixin, View):
|
||||
"""Log current user out"""
|
||||
logout(request)
|
||||
messages.success(request, _("You've successfully been logged out."))
|
||||
return redirect(reverse('passbook_core:auth-login'))
|
||||
return redirect(reverse("passbook_core:auth-login"))
|
||||
|
||||
|
||||
class SignUpView(UserPassesTestMixin, FormView):
|
||||
"""Sign up new user, optionally consume one-use invitation link."""
|
||||
|
||||
template_name = 'login/form.html'
|
||||
template_name = "login/form.html"
|
||||
form_class = SignUpForm
|
||||
success_url = '.'
|
||||
success_url = "."
|
||||
# Invitation instance, if invitation link was used
|
||||
_invitation = None
|
||||
# Instance of newly created user
|
||||
@ -108,38 +111,38 @@ class SignUpView(UserPassesTestMixin, FormView):
|
||||
return self.request.user.is_authenticated is False
|
||||
|
||||
def handle_no_permission(self):
|
||||
return redirect(reverse('passbook_core:overview'))
|
||||
return redirect(reverse("passbook_core:overview"))
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""Check if sign-up is enabled or invitation link given"""
|
||||
allowed = False
|
||||
if 'invitation' in request.GET:
|
||||
invitations = Invitation.objects.filter(uuid=request.GET.get('invitation'))
|
||||
if "invitation" in request.GET:
|
||||
invitations = Invitation.objects.filter(uuid=request.GET.get("invitation"))
|
||||
allowed = invitations.exists()
|
||||
if allowed:
|
||||
self._invitation = invitations.first()
|
||||
if CONFIG.y('passbook.sign_up.enabled'):
|
||||
if CONFIG.y("passbook.sign_up.enabled"):
|
||||
allowed = True
|
||||
if not allowed:
|
||||
messages.error(request, _('Sign-ups are currently disabled.'))
|
||||
return redirect(reverse('passbook_core:auth-login'))
|
||||
messages.error(request, _("Sign-ups are currently disabled."))
|
||||
return redirect(reverse("passbook_core:auth-login"))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_initial(self):
|
||||
if self._invitation:
|
||||
initial = {}
|
||||
if self._invitation.fixed_username:
|
||||
initial['username'] = self._invitation.fixed_username
|
||||
initial["username"] = self._invitation.fixed_username
|
||||
if self._invitation.fixed_email:
|
||||
initial['email'] = self._invitation.fixed_email
|
||||
initial["email"] = self._invitation.fixed_email
|
||||
return initial
|
||||
return super().get_initial()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['config'] = CONFIG.y('passbook')
|
||||
kwargs['is_login'] = True
|
||||
kwargs['title'] = _('Sign Up')
|
||||
kwargs['primary_action'] = _('Sign up')
|
||||
kwargs["config"] = CONFIG.y("passbook")
|
||||
kwargs["is_login"] = True
|
||||
kwargs["title"] = _("Sign Up")
|
||||
kwargs["primary_action"] = _("Sign up")
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def form_valid(self, form: SignUpForm) -> HttpResponse:
|
||||
@ -172,9 +175,8 @@ class SignUpView(UserPassesTestMixin, FormView):
|
||||
# self._user.save()
|
||||
self.consume_invitation()
|
||||
messages.success(self.request, _("Successfully signed up!"))
|
||||
LOGGER.debug("Successfully signed up %s",
|
||||
form.cleaned_data.get('email'))
|
||||
return redirect(reverse('passbook_core:auth-login'))
|
||||
LOGGER.debug("Successfully signed up %s", form.cleaned_data.get("email"))
|
||||
return redirect(reverse("passbook_core:auth-login"))
|
||||
|
||||
def consume_invitation(self):
|
||||
"""Consume invitation if an invitation was used"""
|
||||
@ -183,7 +185,8 @@ class SignUpView(UserPassesTestMixin, FormView):
|
||||
sender=self,
|
||||
request=self.request,
|
||||
invitation=self._invitation,
|
||||
user=self._user)
|
||||
user=self._user,
|
||||
)
|
||||
self._invitation.delete()
|
||||
|
||||
@staticmethod
|
||||
@ -203,20 +206,17 @@ class SignUpView(UserPassesTestMixin, FormView):
|
||||
"""
|
||||
# Create user
|
||||
new_user = User.objects.create(
|
||||
username=data.get('username'),
|
||||
email=data.get('email'),
|
||||
name=data.get('name'),
|
||||
username=data.get("username"),
|
||||
email=data.get("email"),
|
||||
name=data.get("name"),
|
||||
)
|
||||
new_user.is_active = True
|
||||
try:
|
||||
new_user.set_password(data.get('password'))
|
||||
new_user.set_password(data.get("password"))
|
||||
new_user.save()
|
||||
request.user = new_user
|
||||
# Send signal for other auth sources
|
||||
user_signed_up.send(
|
||||
sender=SignUpView,
|
||||
user=new_user,
|
||||
request=request)
|
||||
user_signed_up.send(sender=SignUpView, user=new_user, request=request)
|
||||
return new_user
|
||||
except PasswordPolicyInvalid as exc:
|
||||
new_user.delete()
|
||||
@ -232,11 +232,11 @@ class SignUpConfirmView(View):
|
||||
nonce.user.is_active = True
|
||||
nonce.user.save()
|
||||
# Workaround: hardcoded reference to ModelBackend, needs testing
|
||||
nonce.user.backend = 'django.contrib.auth.backends.ModelBackend'
|
||||
nonce.user.backend = "django.contrib.auth.backends.ModelBackend"
|
||||
login(request, nonce.user)
|
||||
nonce.delete()
|
||||
messages.success(request, _('Successfully confirmed registration.'))
|
||||
return redirect('passbook_core:overview')
|
||||
messages.success(request, _("Successfully confirmed registration."))
|
||||
return redirect("passbook_core:overview")
|
||||
|
||||
|
||||
class PasswordResetView(View):
|
||||
@ -247,9 +247,11 @@ class PasswordResetView(View):
|
||||
# 3. (Optional) Trap user in password change view
|
||||
nonce = get_object_or_404(Nonce, uuid=nonce)
|
||||
# Workaround: hardcoded reference to ModelBackend, needs testing
|
||||
nonce.user.backend = 'django.contrib.auth.backends.ModelBackend'
|
||||
nonce.user.backend = "django.contrib.auth.backends.ModelBackend"
|
||||
login(request, nonce.user)
|
||||
nonce.delete()
|
||||
messages.success(request, _(('Temporarily authenticated with Nonce, '
|
||||
'please change your password')))
|
||||
return redirect('passbook_core:user-change-password')
|
||||
messages.success(
|
||||
request,
|
||||
_(("Temporarily authenticated with Nonce, " "please change your password")),
|
||||
)
|
||||
return redirect("passbook_core:user-change-password")
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
"""passbook core error views"""
|
||||
|
||||
from django.http.response import (HttpResponseBadRequest,
|
||||
HttpResponseForbidden, HttpResponseNotFound,
|
||||
HttpResponseServerError)
|
||||
from django.http.response import (
|
||||
HttpResponseBadRequest,
|
||||
HttpResponseForbidden,
|
||||
HttpResponseNotFound,
|
||||
HttpResponseServerError,
|
||||
)
|
||||
from django.template.response import TemplateResponse
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
@ -10,54 +13,53 @@ from django.views.generic import TemplateView
|
||||
class BadRequestTemplateResponse(TemplateResponse, HttpResponseBadRequest):
|
||||
"""Combine Template response with Http Code 400"""
|
||||
|
||||
|
||||
class ForbiddenTemplateResponse(TemplateResponse, HttpResponseForbidden):
|
||||
"""Combine Template response with Http Code 403"""
|
||||
|
||||
|
||||
class NotFoundTemplateResponse(TemplateResponse, HttpResponseNotFound):
|
||||
"""Combine Template response with Http Code 404"""
|
||||
|
||||
|
||||
class ServerErrorTemplateResponse(TemplateResponse, HttpResponseServerError):
|
||||
"""Combine Template response with Http Code 500"""
|
||||
|
||||
|
||||
class BadRequestView(TemplateView):
|
||||
"""Show Bad Request message"""
|
||||
|
||||
response_class = BadRequestTemplateResponse
|
||||
template_name = 'error/400.html'
|
||||
template_name = "error/400.html"
|
||||
|
||||
extra_context = {"is_login": True}
|
||||
|
||||
extra_context = {
|
||||
'is_login': True
|
||||
}
|
||||
|
||||
class ForbiddenView(TemplateView):
|
||||
"""Show Forbidden message"""
|
||||
|
||||
response_class = ForbiddenTemplateResponse
|
||||
template_name = 'error/403.html'
|
||||
template_name = "error/403.html"
|
||||
|
||||
extra_context = {"is_login": True}
|
||||
|
||||
extra_context = {
|
||||
'is_login': True
|
||||
}
|
||||
|
||||
class NotFoundView(TemplateView):
|
||||
"""Show Not Found message"""
|
||||
|
||||
response_class = NotFoundTemplateResponse
|
||||
template_name = 'error/404.html'
|
||||
template_name = "error/404.html"
|
||||
|
||||
extra_context = {"is_login": True}
|
||||
|
||||
extra_context = {
|
||||
'is_login': True
|
||||
}
|
||||
|
||||
class ServerErrorView(TemplateView):
|
||||
"""Show Server Error message"""
|
||||
|
||||
response_class = ServerErrorTemplateResponse
|
||||
template_name = 'error/500.html'
|
||||
template_name = "error/500.html"
|
||||
|
||||
extra_context = {
|
||||
'is_login': True
|
||||
}
|
||||
extra_context = {"is_login": True}
|
||||
|
||||
# pylint: disable=useless-super-delegation
|
||||
def dispatch(self, *args, **kwargs):
|
||||
|
||||
@ -11,13 +11,15 @@ class OverviewView(LoginRequiredMixin, TemplateView):
|
||||
"""Overview for logged in user, incase user opens passbook directly
|
||||
and is not being forwarded"""
|
||||
|
||||
template_name = 'overview/index.html'
|
||||
template_name = "overview/index.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['applications'] = []
|
||||
kwargs["applications"] = []
|
||||
for application in Application.objects.all():
|
||||
engine = PolicyEngine(application.policies.all(), self.request.user, self.request)
|
||||
engine = PolicyEngine(
|
||||
application.policies.all(), self.request.user, self.request
|
||||
)
|
||||
engine.build()
|
||||
if engine.passing:
|
||||
kwargs['applications'].append(application)
|
||||
kwargs["applications"].append(application)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
@ -17,11 +17,11 @@ from passbook.lib.config import CONFIG
|
||||
class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
||||
"""Update User settings"""
|
||||
|
||||
template_name = 'user/settings.html'
|
||||
template_name = "user/settings.html"
|
||||
form_class = UserDetailForm
|
||||
|
||||
success_message = _('Successfully updated user.')
|
||||
success_url = reverse_lazy('passbook_core:user-settings')
|
||||
success_message = _("Successfully updated user.")
|
||||
success_url = reverse_lazy("passbook_core:user-settings")
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
@ -30,44 +30,44 @@ class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
||||
class UserDeleteView(LoginRequiredMixin, DeleteView):
|
||||
"""Delete user account"""
|
||||
|
||||
template_name = 'generic/delete.html'
|
||||
template_name = "generic/delete.html"
|
||||
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
def get_success_url(self):
|
||||
messages.success(self.request, _('Successfully deleted user.'))
|
||||
messages.success(self.request, _("Successfully deleted user."))
|
||||
logout(self.request)
|
||||
return reverse('passbook_core:auth-login')
|
||||
return reverse("passbook_core:auth-login")
|
||||
|
||||
|
||||
class UserChangePasswordView(LoginRequiredMixin, FormView):
|
||||
"""View for users to update their password"""
|
||||
|
||||
form_class = PasswordChangeForm
|
||||
template_name = 'login/form_with_user.html'
|
||||
template_name = "login/form_with_user.html"
|
||||
|
||||
def form_valid(self, form: PasswordChangeForm):
|
||||
try:
|
||||
# user.set_password checks against Policies so we don't need to manually do it here
|
||||
self.request.user.set_password(form.cleaned_data.get('password'))
|
||||
self.request.user.set_password(form.cleaned_data.get("password"))
|
||||
self.request.user.save()
|
||||
update_session_auth_hash(self.request, self.request.user)
|
||||
messages.success(self.request, _('Successfully changed password'))
|
||||
messages.success(self.request, _("Successfully changed password"))
|
||||
except PasswordPolicyInvalid as exc:
|
||||
# Manually inject error into form
|
||||
# pylint: disable=protected-access
|
||||
errors = form._errors.setdefault("password_repeat", ErrorList(''))
|
||||
errors = form._errors.setdefault("password_repeat", ErrorList(""))
|
||||
# pylint: disable=protected-access
|
||||
errors = form._errors.setdefault("password", ErrorList())
|
||||
for error in exc.messages:
|
||||
errors.append(error)
|
||||
return self.form_invalid(form)
|
||||
return redirect('passbook_core:overview')
|
||||
return redirect("passbook_core:overview")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['config'] = CONFIG.y('passbook')
|
||||
kwargs['is_login'] = True
|
||||
kwargs['title'] = _('Change Password')
|
||||
kwargs['primary_action'] = _('Change')
|
||||
kwargs["config"] = CONFIG.y("passbook")
|
||||
kwargs["is_login"] = True
|
||||
kwargs["title"] = _("Change Password")
|
||||
kwargs["primary_action"] = _("Change")
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
@ -6,8 +6,8 @@ from django.views.generic import TemplateView
|
||||
class LoadingView(TemplateView):
|
||||
"""View showing a loading template, and forwarding to real view using html forwarding."""
|
||||
|
||||
template_name = 'login/loading.html'
|
||||
title = _('Loading')
|
||||
template_name = "login/loading.html"
|
||||
title = _("Loading")
|
||||
target_url = None
|
||||
|
||||
def get_url(self):
|
||||
@ -15,18 +15,19 @@ class LoadingView(TemplateView):
|
||||
return self.target_url
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['is_login'] = True
|
||||
kwargs['title'] = self.title
|
||||
kwargs['target_url'] = self.get_url()
|
||||
kwargs["is_login"] = True
|
||||
kwargs["title"] = self.title
|
||||
kwargs["target_url"] = self.get_url()
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class PermissionDeniedView(TemplateView):
|
||||
"""Generic Permission denied view"""
|
||||
|
||||
template_name = 'login/denied.html'
|
||||
title = _('Permission denied.')
|
||||
template_name = "login/denied.html"
|
||||
title = _("Permission denied.")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['is_login'] = True
|
||||
kwargs['title'] = self.title
|
||||
kwargs["is_login"] = True
|
||||
kwargs["title"] = self.title
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
@ -17,16 +17,16 @@ class AuthenticationFactor(TemplateView):
|
||||
authenticator: AuthenticationView
|
||||
pending_user: User
|
||||
request: HttpRequest = None
|
||||
template_name = 'login/form_with_user.html'
|
||||
template_name = "login/form_with_user.html"
|
||||
|
||||
def __init__(self, authenticator: AuthenticationView):
|
||||
self.authenticator = authenticator
|
||||
self.pending_user = None
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['config'] = CONFIG.y('passbook')
|
||||
kwargs['is_login'] = True
|
||||
kwargs['title'] = _('Log in to your account')
|
||||
kwargs['primary_action'] = _('Log in')
|
||||
kwargs['user'] = self.pending_user
|
||||
kwargs["config"] = CONFIG.y("passbook")
|
||||
kwargs["is_login"] = True
|
||||
kwargs["title"] = _("Log in to your account")
|
||||
kwargs["primary_action"] = _("Log in")
|
||||
kwargs["user"] = self.pending_user
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user