Compare commits
30 Commits
version/0.
...
version/0.
| Author | SHA1 | Date | |
|---|---|---|---|
| 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]
|
[bumpversion]
|
||||||
current_version = 0.7.5-beta
|
current_version = 0.7.10-beta
|
||||||
tag = True
|
tag = True
|
||||||
commit = True
|
commit = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
||||||
@ -19,7 +19,7 @@ values =
|
|||||||
|
|
||||||
[bumpversion:file:helm/Chart.yaml]
|
[bumpversion:file:helm/Chart.yaml]
|
||||||
|
|
||||||
[bumpversion:file:.gitlab-ci.yml]
|
[bumpversion:file:.github/workflows/release.yml]
|
||||||
|
|
||||||
[bumpversion:file:passbook/__init__.py]
|
[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
|
||||||
68
.github/workflows/release.yml
vendored
Normal file
68
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
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.10-beta
|
||||||
|
-t beryju/passbook:latest
|
||||||
|
-f Dockerfile .
|
||||||
|
- name: Push Docker Container to Registry (versioned)
|
||||||
|
run: docker push beryju/passbook:0.7.10-beta
|
||||||
|
- name: Push Docker Container to Registry (latest)
|
||||||
|
run: docker push beryju/passbook: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.10-beta
|
||||||
|
-t beryju/passbook-static:latest
|
||||||
|
-f static.Dockerfile .
|
||||||
|
- name: Push Docker Container to Registry (versioned)
|
||||||
|
run: docker push beryju/passbook-static:0.7.10-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]
|
[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
|
load-plugins=pylint_django,pylint.extensions.bad_builtin
|
||||||
extension-pkg-whitelist=lxml
|
extension-pkg-whitelist=lxml
|
||||||
const-rgx=[a-zA-Z0-9_]{1,40}$
|
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 ./passbook/ /app/passbook
|
||||||
COPY ./manage.py /app/
|
COPY ./manage.py /app/
|
||||||
|
|||||||
9
Pipfile
9
Pipfile
@ -23,7 +23,7 @@ django-rest-framework = "*"
|
|||||||
django-storages = "*"
|
django-storages = "*"
|
||||||
djangorestframework-guardian = "*"
|
djangorestframework-guardian = "*"
|
||||||
drf-yasg = "*"
|
drf-yasg = "*"
|
||||||
kombu = "==4.5.0"
|
kombu = "*"
|
||||||
ldap3 = "*"
|
ldap3 = "*"
|
||||||
lxml = "*"
|
lxml = "*"
|
||||||
oauthlib = "*"
|
oauthlib = "*"
|
||||||
@ -51,8 +51,11 @@ bumpversion = "*"
|
|||||||
colorama = "*"
|
colorama = "*"
|
||||||
coverage = "*"
|
coverage = "*"
|
||||||
django-debug-toolbar = "*"
|
django-debug-toolbar = "*"
|
||||||
isort = "*"
|
|
||||||
prospector = "*"
|
prospector = "*"
|
||||||
pylint = "==2.3.1"
|
pylint = "*"
|
||||||
pylint-django = "*"
|
pylint-django = "*"
|
||||||
unittest-xml-reporting = "*"
|
unittest-xml-reporting = "*"
|
||||||
|
black = "*"
|
||||||
|
|
||||||
|
[pipenv]
|
||||||
|
allow_prereleases = true
|
||||||
|
|||||||
337
Pipfile.lock
generated
337
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "865b57ef5ef326de114d39d8505f60f19b5f7e42a50d988ea3fc9dfc9b9371ec"
|
"sha256": "138816efaba5be0b175cfd5b5e6a0b58e5ba551567f0efb441740344da3986d8"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -46,26 +46,26 @@
|
|||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:d280f2bf7dc373e8aeab296f81aadefabf8780ff8c8ad27cdc36f8f112ca95ed",
|
"sha256:982823e7c992d27e5954c81db93238ffc42c7a1210d863b4f5e048fdc088040e",
|
||||||
"sha256:edbf4636e700c46e49f555ac87ab48b8c385fde604528db15fc5189d5a73dc72"
|
"sha256:f05ee90a738c2f1ec8088121030229f26ef6a809fb9a1338de2118fd088dd99a"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.10.33"
|
"version": "==1.10.45"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4861785b52b0b3f97da91613c31f8e501f12517c9c79482b44efbdb56b69aefc",
|
"sha256:88ee646f7a0fe6a418681c6f119a590fae23d8439c48c2aec6878f7f89430b1f",
|
||||||
"sha256:9cc87d7906693c9c8fe862c574a1bebbe22a0475d6991e9b7251bc93cb1954d9"
|
"sha256:f48ba1ef04b25323c1d27fa6399795baa0ca9d316911b87be4d33acda5cef07c"
|
||||||
],
|
],
|
||||||
"version": "==1.13.33"
|
"version": "==1.13.45"
|
||||||
},
|
},
|
||||||
"celery": {
|
"celery": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9",
|
"sha256:7c544f37a84a5eadc44cab1aa8c9580dff94636bb81978cdf9bf8012d9ea7d8f",
|
||||||
"sha256:528e56767ae7e43a16cfef24ee1062491f5754368d38fcfffa861cdb9ef219be"
|
"sha256:d3363bb5df72d74420986a435449f3c3979285941dff57d5d97ecba352a0e3e2"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.3.0"
|
"version": "==4.4.0"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -169,19 +169,19 @@
|
|||||||
},
|
},
|
||||||
"django": {
|
"django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a4ad4f6f9c6a4b7af7e2deec8d0cbff28501852e5010d6c2dc695d3d1fae7ca0",
|
"sha256:662a1ff78792e3fd77f16f71b1f31149489434de4b62a74895bd5d6534e635a5",
|
||||||
"sha256:fa98ec9cc9bf5d72a08ebf3654a9452e761fbb8566e3f80de199cbc15477e891"
|
"sha256:687c37153486cf26c3fdcbdd177ef16de38dc3463f094b5f9c9955d91f277b14"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.2.8"
|
"version": "==2.2.9"
|
||||||
},
|
},
|
||||||
"django-cors-middleware": {
|
"django-cors-middleware": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:85904a3401e7bc0c86502ff2b01d726917af3aaa7dafb77799b27ace637e8c92",
|
"sha256:5bbdea85e22909d596e26f6e0dbc174d5521429fa3943ae02a2c6c48e76c88c7",
|
||||||
"sha256:bca8888ed33a94ba5472bde37ed71ec3d08231d6817fd4d799296b016073da95"
|
"sha256:856dbe4d7aae65844ccc68acb49c6da7dbf7cbacaf5bcf37019f4c0c60b3be84"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.4.0"
|
"version": "==1.5.0"
|
||||||
},
|
},
|
||||||
"django-dbbackup": {
|
"django-dbbackup": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -208,11 +208,11 @@
|
|||||||
},
|
},
|
||||||
"django-model-utils": {
|
"django-model-utils": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3f130a262e45d73e0950d2be76af4bf4ee86804dd60e5f90afc5cd948fcfe760",
|
"sha256:9cf882e5b604421b62dbe57ad2b18464dc9c8f963fc3f9831badccae66c1139c",
|
||||||
"sha256:682f58c1de330cedcda58cc85d5232c5b47a9e2cb67bef4541fb43fdaeb18e96"
|
"sha256:adf09e5be15122a7f4e372cb5a6dd512bbf8d78a23a90770ad0983ee9d909061"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.2.0"
|
"version": "==4.0.0"
|
||||||
},
|
},
|
||||||
"django-oauth-toolkit": {
|
"django-oauth-toolkit": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -230,19 +230,19 @@
|
|||||||
},
|
},
|
||||||
"django-otp": {
|
"django-otp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1b6025bbbd2517b7c246828b1d11c83d53567904836ae6d57bc0058f3cd18b50",
|
"sha256:1f16c2b93fe484706ff16ac6f5e64ecc73dd240318c333e0560384ba548d3837",
|
||||||
"sha256:76a698466178ce40473726ffd8c33f68d1c47f27c53f67fa4aeeb6fdde74d37b"
|
"sha256:cd4975539be478417033561e9832a1a69a583189f680e92a649f412c661f90aa"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.7.4"
|
"version": "==0.7.5"
|
||||||
},
|
},
|
||||||
"django-prometheus": {
|
"django-prometheus": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:60f331788f9846891e9ea8d7ccd2928b1042e2e99c8d673f97e2b85f5bc20112",
|
"sha256:f0657d4b887309086b71b55f6aa4a95f967b35fe115128b501f95422c423b12c",
|
||||||
"sha256:bb2d4f8acd681fa5787df77e7482391017f0090c70473bccd2aa7cad327800ad"
|
"sha256:f645016ae5270ac2025a70788cd2bd636244a0c5705b323cc086994bf828181e"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.1.0"
|
"version": "==2.0.0.dev124"
|
||||||
},
|
},
|
||||||
"django-recaptcha": {
|
"django-recaptcha": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -254,11 +254,11 @@
|
|||||||
},
|
},
|
||||||
"django-redis": {
|
"django-redis": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:af0b393864e91228dd30d8c85b5c44d670b5524cb161b7f9e41acc98b6e5ace7",
|
"sha256:a5b1e3ffd3198735e6c529d9bdf38ca3fcb3155515249b98dc4d966b8ddf9d2b",
|
||||||
"sha256:f46115577063d00a890867c6964ba096057f07cb756e78e0503b89cd18e4e083"
|
"sha256:e1aad4cc5bd743d8d0b13d5cae0cef5410eaace33e83bff5fc3a139ad8db50b4"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.10.0"
|
"version": "==4.11.0"
|
||||||
},
|
},
|
||||||
"django-rest-framework": {
|
"django-rest-framework": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -277,10 +277,10 @@
|
|||||||
},
|
},
|
||||||
"djangorestframework": {
|
"djangorestframework": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8",
|
"sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
|
||||||
"sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090"
|
"sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
|
||||||
],
|
],
|
||||||
"version": "==3.10.3"
|
"version": "==3.11.0"
|
||||||
},
|
},
|
||||||
"djangorestframework-guardian": {
|
"djangorestframework-guardian": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -328,11 +328,11 @@
|
|||||||
},
|
},
|
||||||
"importlib-metadata": {
|
"importlib-metadata": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3a8b2dfd0a2c6a3636e7c016a7e54ae04b997d30e69d5eacdca7a6c2221a1402",
|
"sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45",
|
||||||
"sha256:41e688146d000891f32b1669e8573c57e39e5060e7f5f647aa617cd9a9568278"
|
"sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"
|
||||||
],
|
],
|
||||||
"markers": "python_version < '3.8'",
|
"markers": "python_version < '3.8'",
|
||||||
"version": "==1.2.0"
|
"version": "==1.3.0"
|
||||||
},
|
},
|
||||||
"inflection": {
|
"inflection": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -369,11 +369,11 @@
|
|||||||
},
|
},
|
||||||
"kombu": {
|
"kombu": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:389ba09e03b15b55b1a7371a441c894fd8121d174f5583bbbca032b9ea8c9edd",
|
"sha256:2a9e7adff14d046c9996752b2c48b6d9185d0b992106d5160e1a179907a5d4ac",
|
||||||
"sha256:7b92303af381ef02fad6899fd5f5a9a96031d781356cd8e505fa54ae5ddee181"
|
"sha256:67b32ccb6fea030f8799f8fd50dd08e03a4b99464ebc4952d71d8747b1a52ad1"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.5.0"
|
"version": "==4.6.7"
|
||||||
},
|
},
|
||||||
"ldap3": {
|
"ldap3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -450,10 +450,10 @@
|
|||||||
},
|
},
|
||||||
"more-itertools": {
|
"more-itertools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:53ff73f186307d9c8ef17a9600309154a6ae27f25579e80af4db8f047ba14bc2",
|
"sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d",
|
||||||
"sha256:a0ea684c39bc4315ba7aae406596ef191fd84f873d2d2751f84d64e81a7a2d45"
|
"sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"
|
||||||
],
|
],
|
||||||
"version": "==8.0.0"
|
"version": "==8.0.2"
|
||||||
},
|
},
|
||||||
"oauthlib": {
|
"oauthlib": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -625,10 +625,10 @@
|
|||||||
},
|
},
|
||||||
"pyparsing": {
|
"pyparsing": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f",
|
"sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
|
||||||
"sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a"
|
"sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
|
||||||
],
|
],
|
||||||
"version": "==2.4.5"
|
"version": "==2.4.6"
|
||||||
},
|
},
|
||||||
"pyrsistent": {
|
"pyrsistent": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -638,11 +638,11 @@
|
|||||||
},
|
},
|
||||||
"python-dateutil": {
|
"python-dateutil": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
|
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||||
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
|
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7'",
|
"markers": "python_version >= '2.7'",
|
||||||
"version": "==2.8.0"
|
"version": "==2.8.1"
|
||||||
},
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -685,20 +685,20 @@
|
|||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
|
"sha256:21a8e19e2007a4047ffabbd8f0ee32c0dabae3b7f4b6c645110ae53e7714b470",
|
||||||
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
|
"sha256:74ad685bfb065f4bdd36d24aa97092f04bcbb1179b5ffdd3d5f994023fb8c292",
|
||||||
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
|
"sha256:79c3ba1da22e61c2a71aaa382c57518ab492278c8974c40187b900b50f3e0282",
|
||||||
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
|
"sha256:94ad913ab3fd967d14ecffda8182d7d0e1f7dd919b352773c492ec51890d3224",
|
||||||
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
|
"sha256:998db501e3a627c3e5678d6505f0e182d1529545df289db036cdc717f35d8058",
|
||||||
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
|
"sha256:9b69d4645bff5820713e8912bc61c4277dc127a6f8c197b52b6436503c42600f",
|
||||||
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
|
"sha256:9da13b536533518343a04f3c6564782ec8a13c705310b26b4832d77fa4d92a47",
|
||||||
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
|
"sha256:a76159f13b47fb44fb2acac8fef798a1940dd31b4acec6f4560bd11b2d92d31b",
|
||||||
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
|
"sha256:a9e9175c1e47a089a2b45d9e2afc6aae1f1f725538c32eec761894a42ba1227f",
|
||||||
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
|
"sha256:ea51ce7b96646ecd3bb12c2702e570c2bd7dd4d9f146db7fa83c5008ede35f66",
|
||||||
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
|
"sha256:ffbaaa05de60fc444eda3f6300d1af27d965b09b67f1fb4ebcc88dd0fb4ab1b4"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==5.2"
|
"version": "==5.3b1"
|
||||||
},
|
},
|
||||||
"qrcode": {
|
"qrcode": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -752,6 +752,7 @@
|
|||||||
"sha256:a0ff786d2a7dbe55f9544b3f6ebbcc495d7e730df92a08434604f6f470b899c5",
|
"sha256:a0ff786d2a7dbe55f9544b3f6ebbcc495d7e730df92a08434604f6f470b899c5",
|
||||||
"sha256:b1b7fcee6aedcdc7e62c3a73f238b3d080c7ba6650cd808bce8d7761ec484070",
|
"sha256:b1b7fcee6aedcdc7e62c3a73f238b3d080c7ba6650cd808bce8d7761ec484070",
|
||||||
"sha256:b66832ea8077d9b3f6e311c4a53d06273db5dc2db6e8a908550f3c14d67e718c",
|
"sha256:b66832ea8077d9b3f6e311c4a53d06273db5dc2db6e8a908550f3c14d67e718c",
|
||||||
|
"sha256:be018933c2f4ee7de55e7bd7d0d801b3dfb09d21dad0cce8a97995fd3e44be30",
|
||||||
"sha256:d0d3ac228c9bbab08134b4004d748cf9f8743504875b3603b3afbb97e3472947",
|
"sha256:d0d3ac228c9bbab08134b4004d748cf9f8743504875b3603b3afbb97e3472947",
|
||||||
"sha256:d10e9dd744cf85c219bf747c75194b624cc7a94f0c80ead624b06bfa9f61d3bc",
|
"sha256:d10e9dd744cf85c219bf747c75194b624cc7a94f0c80ead624b06bfa9f61d3bc",
|
||||||
"sha256:ea4362548ee0cbc266949d8a441238d9ad3600ca9910c3fe4e82ee3a50706973",
|
"sha256:ea4362548ee0cbc266949d8a441238d9ad3600ca9910c3fe4e82ee3a50706973",
|
||||||
@ -770,11 +771,11 @@
|
|||||||
},
|
},
|
||||||
"sentry-sdk": {
|
"sentry-sdk": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a7c2c8d3f53b6b57454830cd6a4b73d272f1ba91952f59e6545b3cf885f3c22f",
|
"sha256:05285942901d38c7ce2498aba50d8e87b361fc603281a5902dda98f3f8c5e145",
|
||||||
"sha256:bfc486af718c268cf49ff43d6334ed4db7333ace420240b630acdd8f8a3a8f60"
|
"sha256:c6b919623e488134a728f16326c6f0bcdab7e3f59e7f4c472a90eea4d6d8fe82"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.13.4"
|
"version": "==0.13.5"
|
||||||
},
|
},
|
||||||
"service-identity": {
|
"service-identity": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -824,11 +825,10 @@
|
|||||||
},
|
},
|
||||||
"uritemplate": {
|
"uritemplate": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:01c69f4fe8ed503b2951bef85d996a9d22434d2431584b5b107b2981ff416fbd",
|
"sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f",
|
||||||
"sha256:1b9c467a940ce9fb9f50df819e8ddd14696f89b9a8cc87ac77952ba416e0a8fd",
|
"sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"
|
||||||
"sha256:c02643cebe23fc8adb5e6becffe201185bf06c40bda5c0b4028a93f1527d011d"
|
|
||||||
],
|
],
|
||||||
"version": "==3.0.0"
|
"version": "==3.0.1"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"extras": [
|
"extras": [
|
||||||
@ -858,6 +858,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
|
"appdirs": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
||||||
|
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
|
||||||
|
],
|
||||||
|
"version": "==1.4.3"
|
||||||
|
},
|
||||||
"asgiref": {
|
"asgiref": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
|
"sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
|
||||||
@ -867,10 +874,17 @@
|
|||||||
},
|
},
|
||||||
"astroid": {
|
"astroid": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4",
|
"sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a",
|
||||||
"sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4"
|
"sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"
|
||||||
],
|
],
|
||||||
"version": "==2.2.5"
|
"version": "==2.3.3"
|
||||||
|
},
|
||||||
|
"attrs": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
|
||||||
|
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
|
||||||
|
],
|
||||||
|
"version": "==19.3.0"
|
||||||
},
|
},
|
||||||
"autopep8": {
|
"autopep8": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -887,6 +901,14 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.6.2"
|
"version": "==1.6.2"
|
||||||
},
|
},
|
||||||
|
"black": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b",
|
||||||
|
"sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==19.10b0"
|
||||||
|
},
|
||||||
"bumpversion": {
|
"bumpversion": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e",
|
"sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e",
|
||||||
@ -895,59 +917,65 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.5.3"
|
"version": "==0.5.3"
|
||||||
},
|
},
|
||||||
|
"click": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||||
|
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
||||||
|
],
|
||||||
|
"version": "==7.0"
|
||||||
|
},
|
||||||
"colorama": {
|
"colorama": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
|
"sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
|
||||||
"sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
|
"sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.4.1"
|
"version": "==0.4.3"
|
||||||
},
|
},
|
||||||
"coverage": {
|
"coverage": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6",
|
"sha256:0101888bd1592a20ccadae081ba10e8b204d20235d18d05c6f7d5e904a38fc10",
|
||||||
"sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650",
|
"sha256:04b961862334687549eb91cd5178a6fbe977ad365bddc7c60f2227f2f9880cf4",
|
||||||
"sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5",
|
"sha256:1ca43dbd739c0fc30b0a3637a003a0d2c7edc1dd618359d58cc1e211742f8bd1",
|
||||||
"sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d",
|
"sha256:1cbb88b34187bdb841f2599770b7e6ff8e259dc3bb64fc7893acf44998acf5f8",
|
||||||
"sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351",
|
"sha256:232f0b52a5b978288f0bbc282a6c03fe48cd19a04202df44309919c142b3bb9c",
|
||||||
"sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755",
|
"sha256:24bcfa86fd9ce86b73a8368383c39d919c497a06eebb888b6f0c12f13e920b1a",
|
||||||
"sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef",
|
"sha256:25b8f60b5c7da71e64c18888f3067d5b6f1334b9681876b2fb41eea26de881ae",
|
||||||
"sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca",
|
"sha256:2714160a63da18aed9340c70ed514973971ee7e665e6b336917ff4cca81a25b1",
|
||||||
"sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca",
|
"sha256:2ca2cd5264e84b2cafc73f0045437f70c6378c0d7dbcddc9ee3fe192c1e29e5d",
|
||||||
"sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9",
|
"sha256:2cc707fc9aad2592fc686d63ef72dc0031fc98b6fb921d2f5395d9ab84fbc3ef",
|
||||||
"sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc",
|
"sha256:348630edea485f4228233c2f310a598abf8afa5f8c716c02a9698089687b6085",
|
||||||
"sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5",
|
"sha256:40fbfd6b044c9db13aeec1daf5887d322c710d811f944011757526ef6e323fd9",
|
||||||
"sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f",
|
"sha256:46c9c6a1d1190c0b75ec7c0f339088309952b82ae8d67a79ff1319eb4e749b96",
|
||||||
"sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe",
|
"sha256:591506e088901bdc25620c37aec885e82cc896528f28c57e113751e3471fc314",
|
||||||
"sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888",
|
"sha256:5ac71bba1e07eab403b082c4428f868c1c9e26a21041436b4905c4c3d4e49b08",
|
||||||
"sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5",
|
"sha256:5f622f19abda4e934938e24f1d67599249abc201844933a6f01aaa8663094489",
|
||||||
"sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce",
|
"sha256:65bead1ac8c8930cf92a1ccaedcce19a57298547d5d1db5c9d4d068a0675c38b",
|
||||||
"sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5",
|
"sha256:7362a7f829feda10c7265b553455de596b83d1623b3d436b6d3c51c688c57bf6",
|
||||||
"sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e",
|
"sha256:7f2675750c50151f806070ec11258edf4c328340916c53bac0adbc465abd6b1e",
|
||||||
"sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e",
|
"sha256:960d7f42277391e8b1c0b0ae427a214e1b31a1278de6b73f8807b20c2e913bba",
|
||||||
"sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9",
|
"sha256:a50b0888d8a021a3342d36a6086501e30de7d840ab68fca44913e97d14487dc1",
|
||||||
"sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437",
|
"sha256:b7dbc5e8c39ea3ad3db22715f1b5401cd698a621218680c6daf42c2f9d36e205",
|
||||||
"sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1",
|
"sha256:bb3d29df5d07d5399d58a394d0ef50adf303ab4fbf66dfd25b9ef258effcb692",
|
||||||
"sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c",
|
"sha256:c0fff2733f7c2950f58a4fd09b5db257b00c6fec57bf3f68c5bae004d804b407",
|
||||||
"sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24",
|
"sha256:c792d3707a86c01c02607ae74364854220fb3e82735f631cd0a345dea6b4cee5",
|
||||||
"sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47",
|
"sha256:c90bda74e16bcd03861b09b1d37c0a4158feda5d5a036bb2d6e58de6ff65793e",
|
||||||
"sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2",
|
"sha256:cfce79ce41cc1a1dc7fc85bb41eeeb32d34a4cf39a645c717c0550287e30ff06",
|
||||||
"sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28",
|
"sha256:eeafb646f374988c22c8e6da5ab9fb81367ecfe81c70c292623373d2a021b1a1",
|
||||||
"sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c",
|
"sha256:f425f50a6dd807cb9043d15a4fcfba3b5874a54d9587ccbb748899f70dc18c47",
|
||||||
"sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7",
|
"sha256:fcd4459fe35a400b8f416bc57906862693c9f88b66dc925e7f2a933e77f6b18b",
|
||||||
"sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0",
|
"sha256:ff3936dd5feaefb4f91c8c1f50a06c588b5dc69fba4f7d9c79a6617ad80bb7df"
|
||||||
"sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"
|
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.5.4"
|
"version": "==5.0.1"
|
||||||
},
|
},
|
||||||
"django": {
|
"django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a4ad4f6f9c6a4b7af7e2deec8d0cbff28501852e5010d6c2dc695d3d1fae7ca0",
|
"sha256:662a1ff78792e3fd77f16f71b1f31149489434de4b62a74895bd5d6534e635a5",
|
||||||
"sha256:fa98ec9cc9bf5d72a08ebf3654a9452e761fbb8566e3f80de199cbc15477e891"
|
"sha256:687c37153486cf26c3fdcbdd177ef16de38dc3463f094b5f9c9955d91f277b14"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.2.8"
|
"version": "==2.2.9"
|
||||||
},
|
},
|
||||||
"django-debug-toolbar": {
|
"django-debug-toolbar": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -982,7 +1010,6 @@
|
|||||||
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
|
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
|
||||||
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
|
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
|
||||||
"version": "==4.3.21"
|
"version": "==4.3.21"
|
||||||
},
|
},
|
||||||
"lazy-object-proxy": {
|
"lazy-object-proxy": {
|
||||||
@ -1018,6 +1045,13 @@
|
|||||||
],
|
],
|
||||||
"version": "==0.6.1"
|
"version": "==0.6.1"
|
||||||
},
|
},
|
||||||
|
"pathspec": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424",
|
||||||
|
"sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"
|
||||||
|
],
|
||||||
|
"version": "==0.7.0"
|
||||||
|
},
|
||||||
"pbr": {
|
"pbr": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b",
|
"sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b",
|
||||||
@ -1034,10 +1068,10 @@
|
|||||||
},
|
},
|
||||||
"prospector": {
|
"prospector": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:aba551e53dc1a5a432afa67385eaa81d7b4cf4c162dc1a4d0ee00b3a0712ad90"
|
"sha256:ea910794b53cfefcb5dfb6b4eb0323e42d1a88132e165b85b016cc7f0b6ae635"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.1.7"
|
"version": "==1.2.0"
|
||||||
},
|
},
|
||||||
"pycodestyle": {
|
"pycodestyle": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1048,25 +1082,25 @@
|
|||||||
},
|
},
|
||||||
"pydocstyle": {
|
"pydocstyle": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:04c84e034ebb56eb6396c820442b8c4499ac5eb94a3bda88951ac3dc519b6058",
|
"sha256:4167fe954b8f27ebbbef2fbcf73c6e8ad1e7bb31488fce44a69fdfc4b0cd0fae",
|
||||||
"sha256:66aff87ffe34b1e49bff2dd03a88ce6843be2f3346b0c9814410d34987fbab59"
|
"sha256:a0de36e549125d0a16a72a8c8c6c9ba267750656e72e466e994c222f1b6e92cb"
|
||||||
],
|
],
|
||||||
"version": "==4.0.1"
|
"version": "==5.0.1"
|
||||||
},
|
},
|
||||||
"pyflakes": {
|
"pyflakes": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
|
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
|
||||||
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
|
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
|
||||||
],
|
],
|
||||||
"version": "==1.6.0"
|
"version": "==2.1.1"
|
||||||
},
|
},
|
||||||
"pylint": {
|
"pylint": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09",
|
"sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd",
|
||||||
"sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1"
|
"sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.3.1"
|
"version": "==2.4.4"
|
||||||
},
|
},
|
||||||
"pylint-celery": {
|
"pylint-celery": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1076,11 +1110,11 @@
|
|||||||
},
|
},
|
||||||
"pylint-django": {
|
"pylint-django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:75c69d1ec2275918c37f175976da20e2f1e1e62e067098a685cd263ffa833dfd",
|
"sha256:9bdb0e022b19881218a25ffb8ad05e83b83bc5cdbc58e5ee8ffbe99965193f6c",
|
||||||
"sha256:c7cb6384ea7b33ea77052a5ae07358c10d377807390ef27b2e6ff997303fadb7"
|
"sha256:9eea6a026eaa5ecfad5fed7a33faf77ef55a43cc78afbcaf2f6ddd071156b3f8"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.0.10"
|
"version": "==2.0.12"
|
||||||
},
|
},
|
||||||
"pylint-flask": {
|
"pylint-flask": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1104,20 +1138,46 @@
|
|||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
|
"sha256:21a8e19e2007a4047ffabbd8f0ee32c0dabae3b7f4b6c645110ae53e7714b470",
|
||||||
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
|
"sha256:74ad685bfb065f4bdd36d24aa97092f04bcbb1179b5ffdd3d5f994023fb8c292",
|
||||||
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
|
"sha256:79c3ba1da22e61c2a71aaa382c57518ab492278c8974c40187b900b50f3e0282",
|
||||||
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
|
"sha256:94ad913ab3fd967d14ecffda8182d7d0e1f7dd919b352773c492ec51890d3224",
|
||||||
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
|
"sha256:998db501e3a627c3e5678d6505f0e182d1529545df289db036cdc717f35d8058",
|
||||||
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
|
"sha256:9b69d4645bff5820713e8912bc61c4277dc127a6f8c197b52b6436503c42600f",
|
||||||
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
|
"sha256:9da13b536533518343a04f3c6564782ec8a13c705310b26b4832d77fa4d92a47",
|
||||||
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
|
"sha256:a76159f13b47fb44fb2acac8fef798a1940dd31b4acec6f4560bd11b2d92d31b",
|
||||||
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
|
"sha256:a9e9175c1e47a089a2b45d9e2afc6aae1f1f725538c32eec761894a42ba1227f",
|
||||||
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
|
"sha256:ea51ce7b96646ecd3bb12c2702e570c2bd7dd4d9f146db7fa83c5008ede35f66",
|
||||||
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
|
"sha256:ffbaaa05de60fc444eda3f6300d1af27d965b09b67f1fb4ebcc88dd0fb4ab1b4"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"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": {
|
"requirements-detector": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1166,6 +1226,13 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.31.0"
|
"version": "==1.31.0"
|
||||||
},
|
},
|
||||||
|
"toml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
|
||||||
|
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
|
||||||
|
],
|
||||||
|
"version": "==0.10.0"
|
||||||
|
},
|
||||||
"typed-ast": {
|
"typed-ast": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161",
|
"sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161",
|
||||||
@ -1189,7 +1256,7 @@
|
|||||||
"sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66",
|
"sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66",
|
||||||
"sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"
|
"sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"
|
||||||
],
|
],
|
||||||
"markers": "implementation_name == 'cpython'",
|
"markers": "implementation_name == 'cpython' and python_version < '3.8'",
|
||||||
"version": "==1.4.0"
|
"version": "==1.4.0"
|
||||||
},
|
},
|
||||||
"unittest-xml-reporting": {
|
"unittest-xml-reporting": {
|
||||||
|
|||||||
@ -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:
|
labels:
|
||||||
- traefik.enable=false
|
- traefik.enable=false
|
||||||
server:
|
server:
|
||||||
image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest}
|
image: beryju/passbook:${SERVER_TAG:-latest}
|
||||||
command:
|
command:
|
||||||
- uwsgi
|
- uwsgi
|
||||||
- uwsgi.ini
|
- uwsgi.ini
|
||||||
@ -40,7 +40,7 @@ services:
|
|||||||
- traefik.docker.network=internal
|
- traefik.docker.network=internal
|
||||||
- traefik.frontend.rule=PathPrefix:/
|
- traefik.frontend.rule=PathPrefix:/
|
||||||
worker:
|
worker:
|
||||||
image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest}
|
image: beryju/passbook:${SERVER_TAG:-latest}
|
||||||
command:
|
command:
|
||||||
- celery
|
- celery
|
||||||
- worker
|
- worker
|
||||||
@ -60,7 +60,7 @@ services:
|
|||||||
- PASSBOOK_POSTGRESQL__HOST=postgresql
|
- PASSBOOK_POSTGRESQL__HOST=postgresql
|
||||||
- PASSBOOK_POSTGRESQL__PASSWORD=${PG_PASS:-thisisnotagoodpassword}
|
- PASSBOOK_POSTGRESQL__PASSWORD=${PG_PASS:-thisisnotagoodpassword}
|
||||||
static:
|
static:
|
||||||
image: docker.beryju.org/passbook/static:latest
|
image: beryju/passbook-static:latest
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
@ -9,7 +9,7 @@ This installation Method is for test-setups and small-scale productive setups.
|
|||||||
|
|
||||||
## Install
|
## 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:
|
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:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: passbook-docs
|
- name: passbook-docs
|
||||||
image: "docker.beryju.org/passbook/docs:latest"
|
image: "beryju/passbook-docs:latest"
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 80
|
containerPort: 80
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
appVersion: "0.7.5-beta"
|
appVersion: "0.7.10-beta"
|
||||||
description: A Helm chart for passbook.
|
description: A Helm chart for passbook.
|
||||||
name: passbook
|
name: passbook
|
||||||
version: "0.7.5-beta"
|
version: "0.7.10-beta"
|
||||||
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png
|
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png
|
||||||
|
|||||||
@ -21,7 +21,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Chart.Name }}-static
|
- name: {{ .Chart.Name }}-static
|
||||||
image: "docker.beryju.org/passbook/static:{{ .Values.image.tag }}"
|
image: "beryju/passbook-static:{{ .Values.image.tag }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
|
|||||||
@ -26,7 +26,7 @@ spec:
|
|||||||
name: {{ include "passbook.fullname" . }}-config
|
name: {{ include "passbook.fullname" . }}-config
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: passbook-database-migrations
|
- name: passbook-database-migrations
|
||||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
image: "beryju/passbook:{{ .Values.image.tag }}"
|
||||||
command:
|
command:
|
||||||
- ./manage.py
|
- ./manage.py
|
||||||
args:
|
args:
|
||||||
@ -56,7 +56,7 @@ spec:
|
|||||||
key: postgresql-password
|
key: postgresql-password
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Chart.Name }}
|
- name: {{ .Chart.Name }}
|
||||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
image: "beryju/passbook:{{ .Values.image.tag }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command:
|
command:
|
||||||
- uwsgi
|
- uwsgi
|
||||||
|
|||||||
@ -26,7 +26,7 @@ spec:
|
|||||||
name: {{ include "passbook.fullname" . }}-config
|
name: {{ include "passbook.fullname" . }}-config
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Chart.Name }}
|
- name: {{ .Chart.Name }}
|
||||||
image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
|
image: "beryju/passbook:{{ .Values.image.tag }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command:
|
command:
|
||||||
- celery
|
- celery
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
# This is a YAML-formatted file.
|
# This is a YAML-formatted file.
|
||||||
# Declare variables to be passed into your templates.
|
# Declare variables to be passed into your templates.
|
||||||
image:
|
image:
|
||||||
tag: 0.7.5-beta
|
tag: 0.7.10-beta
|
||||||
|
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ nav:
|
|||||||
- Sentry: integrations/services/sentry/index.md
|
- Sentry: integrations/services/sentry/index.md
|
||||||
|
|
||||||
repo_name: "BeryJu.org/passbook"
|
repo_name: "BeryJu.org/passbook"
|
||||||
repo_url: https://git.beryju.org/BeryJu.org/passbook
|
repo_url: https://github.com/BeryJu/passbook
|
||||||
theme:
|
theme:
|
||||||
name: "material"
|
name: "material"
|
||||||
logo: "images/logo.svg"
|
logo: "images/logo.svg"
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
"""passbook"""
|
"""passbook"""
|
||||||
__version__ = '0.7.5-beta'
|
__version__ = "0.7.10-beta"
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from django.apps import AppConfig
|
|||||||
class PassbookAdminConfig(AppConfig):
|
class PassbookAdminConfig(AppConfig):
|
||||||
"""passbook admin app config"""
|
"""passbook admin app config"""
|
||||||
|
|
||||||
name = 'passbook.admin'
|
name = "passbook.admin"
|
||||||
label = 'passbook_admin'
|
label = "passbook_admin"
|
||||||
mountpoint = 'administration/'
|
mountpoint = "administration/"
|
||||||
verbose_name = 'passbook Admin'
|
verbose_name = "passbook Admin"
|
||||||
|
|||||||
@ -16,7 +16,7 @@ class YAMLField(forms.CharField):
|
|||||||
"""Django's JSON Field converted to YAML"""
|
"""Django's JSON Field converted to YAML"""
|
||||||
|
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _("'%(value)s' value must be valid YAML."),
|
"invalid": _("'%(value)s' value must be valid YAML."),
|
||||||
}
|
}
|
||||||
widget = forms.Textarea
|
widget = forms.Textarea
|
||||||
|
|
||||||
@ -31,9 +31,7 @@ class YAMLField(forms.CharField):
|
|||||||
converted = yaml.safe_load(value)
|
converted = yaml.safe_load(value)
|
||||||
except yaml.YAMLError:
|
except yaml.YAMLError:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
self.error_messages['invalid'],
|
self.error_messages["invalid"], code="invalid", params={"value": value},
|
||||||
code='invalid',
|
|
||||||
params={'value': value},
|
|
||||||
)
|
)
|
||||||
if isinstance(converted, str):
|
if isinstance(converted, str):
|
||||||
return YAMLString(converted)
|
return YAMLString(converted)
|
||||||
|
|||||||
@ -9,29 +9,32 @@ class TagModelForm(forms.ModelForm):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Check if we have an instance, load tags otherwise use an empty dict
|
# 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 {}
|
tags = instance.tags if instance else {}
|
||||||
# Make sure all predefined tags exist in tags, and set default if they don't
|
# 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():
|
for key, value in predefined_tags.items():
|
||||||
if key not in tags:
|
if key not in tags:
|
||||||
tags[key] = value
|
tags[key] = value
|
||||||
# Format JSON
|
# Format JSON
|
||||||
kwargs['initial']['tags'] = tags
|
kwargs["initial"]["tags"] = tags
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def clean_tags(self):
|
def clean_tags(self):
|
||||||
"""Make sure all required tags are set"""
|
"""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():
|
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)
|
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
|
# pylint: disable=too-few-public-methods
|
||||||
class TagModelFormMeta:
|
class TagModelFormMeta:
|
||||||
"""Base Meta class that uses the YAMLField"""
|
"""Base Meta class that uses the YAMLField"""
|
||||||
|
|
||||||
field_classes = {
|
field_classes = {"tags": YAMLField}
|
||||||
'tags': YAMLField
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"""passbook core source form fields"""
|
"""passbook core source form fields"""
|
||||||
# from django import forms
|
# from django import forms
|
||||||
|
|
||||||
SOURCE_FORM_FIELDS = ['name', 'slug', 'enabled', 'policies']
|
SOURCE_FORM_FIELDS = ["name", "slug", "enabled", "policies"]
|
||||||
SOURCE_SERIALIZER_FIELDS = ['pk', 'name', 'slug', 'enabled', 'policies']
|
SOURCE_SERIALIZER_FIELDS = ["pk", "name", "slug", "enabled", "policies"]
|
||||||
|
|
||||||
# class SourceForm(forms.Form)
|
# class SourceForm(forms.Form)
|
||||||
|
|||||||
@ -12,10 +12,10 @@ class UserForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
fields = ['username', 'name', 'email', 'is_staff', 'is_active', 'attributes']
|
fields = ["username", "name", "email", "is_staff", "is_active", "attributes"]
|
||||||
widgets = {
|
widgets = {
|
||||||
'name': forms.TextInput,
|
"name": forms.TextInput,
|
||||||
}
|
}
|
||||||
field_classes = {
|
field_classes = {
|
||||||
'attributes': YAMLField,
|
"attributes": YAMLField,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,15 +11,16 @@ def impersonate(get_response):
|
|||||||
|
|
||||||
# User is superuser and has __impersonate ID set
|
# User is superuser and has __impersonate ID set
|
||||||
if request.user.is_superuser and "__impersonate" in request.GET:
|
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
|
# user wants to stop impersonation
|
||||||
elif "__unimpersonate" in request.GET and 'impersonate_id' in request.session:
|
elif "__unimpersonate" in request.GET and "impersonate_id" in request.session:
|
||||||
del request.session['impersonate_id']
|
del request.session["impersonate_id"]
|
||||||
|
|
||||||
# Actually impersonate user
|
# Actually impersonate user
|
||||||
if request.user.is_superuser and 'impersonate_id' in request.session:
|
if request.user.is_superuser and "impersonate_id" in request.session:
|
||||||
request.user = User.objects.get(pk=request.session['impersonate_id'])
|
request.user = User.objects.get(pk=request.session["impersonate_id"])
|
||||||
|
|
||||||
response = get_response(request)
|
response = get_response(request)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
return middleware
|
return middleware
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
"""passbook admin settings"""
|
"""passbook admin settings"""
|
||||||
|
|
||||||
MIDDLEWARE = [
|
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()
|
register = template.Library()
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def get_links(model_instance):
|
def get_links(model_instance):
|
||||||
"""Find all link_ methods on an object instance, run them and return as dict"""
|
"""Find all link_ methods on an object instance, run them and return as dict"""
|
||||||
prefix = 'link_'
|
prefix = "link_"
|
||||||
links = {}
|
links = {}
|
||||||
|
|
||||||
if not isinstance(model_instance, Model):
|
if not isinstance(model_instance, Model):
|
||||||
@ -21,9 +22,11 @@ def get_links(model_instance):
|
|||||||
return links
|
return links
|
||||||
|
|
||||||
try:
|
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):
|
if name.startswith(prefix):
|
||||||
human_name = name.replace(prefix, '').replace('_', ' ').capitalize()
|
human_name = name.replace(prefix, "").replace("_", " ").capitalize()
|
||||||
link = method()
|
link = method()
|
||||||
if link:
|
if link:
|
||||||
links[human_name] = link
|
links[human_name] = link
|
||||||
@ -36,7 +39,7 @@ def get_links(model_instance):
|
|||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def get_htmls(context, model_instance):
|
def get_htmls(context, model_instance):
|
||||||
"""Find all html_ methods on an object instance, run them and return as dict"""
|
"""Find all html_ methods on an object instance, run them and return as dict"""
|
||||||
prefix = 'html_'
|
prefix = "html_"
|
||||||
htmls = []
|
htmls = []
|
||||||
|
|
||||||
if not isinstance(model_instance, Model):
|
if not isinstance(model_instance, Model):
|
||||||
@ -44,9 +47,11 @@ def get_htmls(context, model_instance):
|
|||||||
return htmls
|
return htmls
|
||||||
|
|
||||||
try:
|
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):
|
if name.startswith(prefix):
|
||||||
template, _context = method(context.get('request'))
|
template, _context = method(context.get("request"))
|
||||||
htmls.append(render_to_string(template, _context))
|
htmls.append(render_to_string(template, _context))
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -1,82 +1,157 @@
|
|||||||
"""passbook URL Configuration"""
|
"""passbook URL Configuration"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from passbook.admin.views import (applications, audit, debug, factors, groups,
|
from passbook.admin.views import (
|
||||||
invitations, overview, policy,
|
applications,
|
||||||
property_mapping, providers, sources, users)
|
audit,
|
||||||
|
debug,
|
||||||
|
factors,
|
||||||
|
groups,
|
||||||
|
invitations,
|
||||||
|
overview,
|
||||||
|
policy,
|
||||||
|
property_mapping,
|
||||||
|
providers,
|
||||||
|
sources,
|
||||||
|
users,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', overview.AdministrationOverviewView.as_view(), name='overview'),
|
path("", overview.AdministrationOverviewView.as_view(), name="overview"),
|
||||||
# Applications
|
# Applications
|
||||||
path('applications/', applications.ApplicationListView.as_view(),
|
path(
|
||||||
name='applications'),
|
"applications/", applications.ApplicationListView.as_view(), name="applications"
|
||||||
path('applications/create/', applications.ApplicationCreateView.as_view(),
|
),
|
||||||
name='application-create'),
|
path(
|
||||||
path('applications/<uuid:pk>/update/',
|
"applications/create/",
|
||||||
applications.ApplicationUpdateView.as_view(), name='application-update'),
|
applications.ApplicationCreateView.as_view(),
|
||||||
path('applications/<uuid:pk>/delete/',
|
name="application-create",
|
||||||
applications.ApplicationDeleteView.as_view(), name='application-delete'),
|
),
|
||||||
|
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
|
# Sources
|
||||||
path('sources/', sources.SourceListView.as_view(), name='sources'),
|
path("sources/", sources.SourceListView.as_view(), name="sources"),
|
||||||
path('sources/create/', sources.SourceCreateView.as_view(), name='source-create'),
|
path("sources/create/", sources.SourceCreateView.as_view(), name="source-create"),
|
||||||
path('sources/<uuid:pk>/update/', sources.SourceUpdateView.as_view(), name='source-update'),
|
path(
|
||||||
path('sources/<uuid:pk>/delete/', sources.SourceDeleteView.as_view(), name='source-delete'),
|
"sources/<uuid:pk>/update/",
|
||||||
|
sources.SourceUpdateView.as_view(),
|
||||||
|
name="source-update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"sources/<uuid:pk>/delete/",
|
||||||
|
sources.SourceDeleteView.as_view(),
|
||||||
|
name="source-delete",
|
||||||
|
),
|
||||||
# Policies
|
# Policies
|
||||||
path('policies/', policy.PolicyListView.as_view(), name='policies'),
|
path("policies/", policy.PolicyListView.as_view(), name="policies"),
|
||||||
path('policies/create/', policy.PolicyCreateView.as_view(), name='policy-create'),
|
path("policies/create/", policy.PolicyCreateView.as_view(), name="policy-create"),
|
||||||
path('policies/<uuid:pk>/update/', policy.PolicyUpdateView.as_view(), name='policy-update'),
|
path(
|
||||||
path('policies/<uuid:pk>/delete/', policy.PolicyDeleteView.as_view(), name='policy-delete'),
|
"policies/<uuid:pk>/update/",
|
||||||
path('policies/<uuid:pk>/test/', policy.PolicyTestView.as_view(), name='policy-test'),
|
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
|
# Providers
|
||||||
path('providers/', providers.ProviderListView.as_view(), name='providers'),
|
path("providers/", providers.ProviderListView.as_view(), name="providers"),
|
||||||
path('providers/create/',
|
path(
|
||||||
providers.ProviderCreateView.as_view(), name='provider-create'),
|
"providers/create/",
|
||||||
path('providers/<int:pk>/update/',
|
providers.ProviderCreateView.as_view(),
|
||||||
providers.ProviderUpdateView.as_view(), name='provider-update'),
|
name="provider-create",
|
||||||
path('providers/<int:pk>/delete/',
|
),
|
||||||
providers.ProviderDeleteView.as_view(), name='provider-delete'),
|
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
|
# Factors
|
||||||
path('factors/', factors.FactorListView.as_view(), name='factors'),
|
path("factors/", factors.FactorListView.as_view(), name="factors"),
|
||||||
path('factors/create/',
|
path("factors/create/", factors.FactorCreateView.as_view(), name="factor-create"),
|
||||||
factors.FactorCreateView.as_view(), name='factor-create'),
|
path(
|
||||||
path('factors/<uuid:pk>/update/',
|
"factors/<uuid:pk>/update/",
|
||||||
factors.FactorUpdateView.as_view(), name='factor-update'),
|
factors.FactorUpdateView.as_view(),
|
||||||
path('factors/<uuid:pk>/delete/',
|
name="factor-update",
|
||||||
factors.FactorDeleteView.as_view(), name='factor-delete'),
|
),
|
||||||
|
path(
|
||||||
|
"factors/<uuid:pk>/delete/",
|
||||||
|
factors.FactorDeleteView.as_view(),
|
||||||
|
name="factor-delete",
|
||||||
|
),
|
||||||
# Factors
|
# Factors
|
||||||
path('property-mappings/', property_mapping.PropertyMappingListView.as_view(),
|
path(
|
||||||
name='property-mappings'),
|
"property-mappings/",
|
||||||
path('property-mappings/create/',
|
property_mapping.PropertyMappingListView.as_view(),
|
||||||
property_mapping.PropertyMappingCreateView.as_view(), name='property-mapping-create'),
|
name="property-mappings",
|
||||||
path('property-mappings/<uuid:pk>/update/',
|
),
|
||||||
property_mapping.PropertyMappingUpdateView.as_view(), name='property-mapping-update'),
|
path(
|
||||||
path('property-mappings/<uuid:pk>/delete/',
|
"property-mappings/create/",
|
||||||
property_mapping.PropertyMappingDeleteView.as_view(), name='property-mapping-delete'),
|
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
|
# Invitations
|
||||||
path('invitations/', invitations.InvitationListView.as_view(), name='invitations'),
|
path("invitations/", invitations.InvitationListView.as_view(), name="invitations"),
|
||||||
path('invitations/create/',
|
path(
|
||||||
invitations.InvitationCreateView.as_view(), name='invitation-create'),
|
"invitations/create/",
|
||||||
path('invitations/<uuid:pk>/delete/',
|
invitations.InvitationCreateView.as_view(),
|
||||||
invitations.InvitationDeleteView.as_view(), name='invitation-delete'),
|
name="invitation-create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"invitations/<uuid:pk>/delete/",
|
||||||
|
invitations.InvitationDeleteView.as_view(),
|
||||||
|
name="invitation-delete",
|
||||||
|
),
|
||||||
# Users
|
# Users
|
||||||
path('users/', users.UserListView.as_view(),
|
path("users/", users.UserListView.as_view(), name="users"),
|
||||||
name='users'),
|
path("users/create/", users.UserCreateView.as_view(), name="user-create"),
|
||||||
path('users/create/', users.UserCreateView.as_view(), name='user-create'),
|
path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"),
|
||||||
path('users/<int:pk>/update/',
|
path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"),
|
||||||
users.UserUpdateView.as_view(), name='user-update'),
|
path(
|
||||||
path('users/<int:pk>/delete/',
|
"users/<int:pk>/reset/",
|
||||||
users.UserDeleteView.as_view(), name='user-delete'),
|
users.UserPasswordResetView.as_view(),
|
||||||
path('users/<int:pk>/reset/',
|
name="user-password-reset",
|
||||||
users.UserPasswordResetView.as_view(), name='user-password-reset'),
|
),
|
||||||
# Groups
|
# Groups
|
||||||
path('group/', groups.GroupListView.as_view(), name='group'),
|
path("group/", groups.GroupListView.as_view(), name="group"),
|
||||||
path('group/create/', groups.GroupCreateView.as_view(), name='group-create'),
|
path("group/create/", groups.GroupCreateView.as_view(), name="group-create"),
|
||||||
path('group/<uuid:pk>/update/', groups.GroupUpdateView.as_view(), name='group-update'),
|
path(
|
||||||
path('group/<uuid:pk>/delete/', groups.GroupDeleteView.as_view(), name='group-delete'),
|
"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
|
# Audit Log
|
||||||
path('audit/', audit.EventListView.as_view(), name='audit-log'),
|
path("audit/", audit.EventListView.as_view(), name="audit-log"),
|
||||||
# Groups
|
# Groups
|
||||||
path('groups/', groups.GroupListView.as_view(), name='groups'),
|
path("groups/", groups.GroupListView.as_view(), name="groups"),
|
||||||
# Debug
|
# 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"""
|
"""passbook Application administration"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.mixins import \
|
from django.contrib.auth.mixins import (
|
||||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||||
|
)
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
@ -18,55 +19,61 @@ class ApplicationListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
|||||||
"""Show list of all applications"""
|
"""Show list of all applications"""
|
||||||
|
|
||||||
model = Application
|
model = Application
|
||||||
permission_required = 'passbook_core.view_application'
|
permission_required = "passbook_core.view_application"
|
||||||
ordering = 'name'
|
ordering = "name"
|
||||||
paginate_by = 40
|
paginate_by = 40
|
||||||
template_name = 'administration/application/list.html'
|
template_name = "administration/application/list.html"
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().select_subclasses()
|
return super().get_queryset().select_subclasses()
|
||||||
|
|
||||||
|
|
||||||
class ApplicationCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
class ApplicationCreateView(
|
||||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
SuccessMessageMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
DjangoPermissionRequiredMixin,
|
||||||
|
CreateAssignPermView,
|
||||||
|
):
|
||||||
"""Create new Application"""
|
"""Create new Application"""
|
||||||
|
|
||||||
model = Application
|
model = Application
|
||||||
form_class = ApplicationForm
|
form_class = ApplicationForm
|
||||||
permission_required = 'passbook_core.add_application'
|
permission_required = "passbook_core.add_application"
|
||||||
|
|
||||||
template_name = 'generic/create.html'
|
template_name = "generic/create.html"
|
||||||
success_url = reverse_lazy('passbook_admin:applications')
|
success_url = reverse_lazy("passbook_admin:applications")
|
||||||
success_message = _('Successfully created Application')
|
success_message = _("Successfully created Application")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['type'] = 'Application'
|
kwargs["type"] = "Application"
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ApplicationUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
class ApplicationUpdateView(
|
||||||
PermissionRequiredMixin, UpdateView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||||
|
):
|
||||||
"""Update application"""
|
"""Update application"""
|
||||||
|
|
||||||
model = Application
|
model = Application
|
||||||
form_class = ApplicationForm
|
form_class = ApplicationForm
|
||||||
permission_required = 'passbook_core.change_application'
|
permission_required = "passbook_core.change_application"
|
||||||
|
|
||||||
template_name = 'generic/update.html'
|
template_name = "generic/update.html"
|
||||||
success_url = reverse_lazy('passbook_admin:applications')
|
success_url = reverse_lazy("passbook_admin:applications")
|
||||||
success_message = _('Successfully updated Application')
|
success_message = _("Successfully updated Application")
|
||||||
|
|
||||||
|
|
||||||
class ApplicationDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
class ApplicationDeleteView(
|
||||||
PermissionRequiredMixin, DeleteView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||||
|
):
|
||||||
"""Delete application"""
|
"""Delete application"""
|
||||||
|
|
||||||
model = Application
|
model = Application
|
||||||
permission_required = 'passbook_core.delete_application'
|
permission_required = "passbook_core.delete_application"
|
||||||
|
|
||||||
template_name = 'generic/delete.html'
|
template_name = "generic/delete.html"
|
||||||
success_url = reverse_lazy('passbook_admin:applications')
|
success_url = reverse_lazy("passbook_admin:applications")
|
||||||
success_message = _('Successfully deleted Application')
|
success_message = _("Successfully deleted Application")
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
messages.success(self.request, self.success_message)
|
messages.success(self.request, self.success_message)
|
||||||
|
|||||||
@ -9,10 +9,10 @@ class EventListView(PermissionListMixin, ListView):
|
|||||||
"""Show list of all invitations"""
|
"""Show list of all invitations"""
|
||||||
|
|
||||||
model = Event
|
model = Event
|
||||||
template_name = 'administration/audit/list.html'
|
template_name = "administration/audit/list.html"
|
||||||
permission_required = 'passbook_audit.view_event'
|
permission_required = "passbook_audit.view_event"
|
||||||
ordering = '-created'
|
ordering = "-created"
|
||||||
paginate_by = 10
|
paginate_by = 10
|
||||||
|
|
||||||
def get_queryset(self):
|
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):
|
class DebugRequestView(LoginRequiredMixin, TemplateView):
|
||||||
"""Show debug info about request"""
|
"""Show debug info about request"""
|
||||||
|
|
||||||
template_name = 'administration/debug/request.html'
|
template_name = "administration/debug/request.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['request_dict'] = {}
|
kwargs["request_dict"] = {}
|
||||||
for key in dir(self.request):
|
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)
|
return super().get_context_data(**kwargs)
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
"""passbook Factor administration"""
|
"""passbook Factor administration"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.mixins import \
|
from django.contrib.auth.mixins import (
|
||||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||||
|
)
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
@ -18,62 +19,69 @@ from passbook.lib.views import CreateAssignPermView
|
|||||||
def all_subclasses(cls):
|
def all_subclasses(cls):
|
||||||
"""Recursively return all subclassess of cls"""
|
"""Recursively return all subclassess of cls"""
|
||||||
return set(cls.__subclasses__()).union(
|
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):
|
class FactorListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||||
"""Show list of all factors"""
|
"""Show list of all factors"""
|
||||||
|
|
||||||
model = Factor
|
model = Factor
|
||||||
template_name = 'administration/factor/list.html'
|
template_name = "administration/factor/list.html"
|
||||||
permission_required = 'passbook_core.view_factor'
|
permission_required = "passbook_core.view_factor"
|
||||||
ordering = 'order'
|
ordering = "order"
|
||||||
paginate_by = 40
|
paginate_by = 40
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['types'] = {
|
kwargs["types"] = {
|
||||||
x.__name__: x._meta.verbose_name for x in all_subclasses(Factor)}
|
x.__name__: x._meta.verbose_name for x in all_subclasses(Factor)
|
||||||
|
}
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().select_subclasses()
|
return super().get_queryset().select_subclasses()
|
||||||
|
|
||||||
|
|
||||||
class FactorCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
class FactorCreateView(
|
||||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
SuccessMessageMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
DjangoPermissionRequiredMixin,
|
||||||
|
CreateAssignPermView,
|
||||||
|
):
|
||||||
"""Create new Factor"""
|
"""Create new Factor"""
|
||||||
|
|
||||||
model = Factor
|
model = Factor
|
||||||
template_name = 'generic/create.html'
|
template_name = "generic/create.html"
|
||||||
permission_required = 'passbook_core.add_factor'
|
permission_required = "passbook_core.add_factor"
|
||||||
|
|
||||||
success_url = reverse_lazy('passbook_admin:factors')
|
success_url = reverse_lazy("passbook_admin:factors")
|
||||||
success_message = _('Successfully created Factor')
|
success_message = _("Successfully created Factor")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs = super().get_context_data(**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)
|
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
|
return kwargs
|
||||||
|
|
||||||
def get_form_class(self):
|
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)
|
model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
|
||||||
if not model:
|
if not model:
|
||||||
raise Http404
|
raise Http404
|
||||||
return path_to_class(model.form)
|
return path_to_class(model.form)
|
||||||
|
|
||||||
|
|
||||||
class FactorUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
class FactorUpdateView(
|
||||||
PermissionRequiredMixin, UpdateView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||||
|
):
|
||||||
"""Update factor"""
|
"""Update factor"""
|
||||||
|
|
||||||
model = Factor
|
model = Factor
|
||||||
permission_required = 'passbook_core.update_application'
|
permission_required = "passbook_core.update_application"
|
||||||
template_name = 'generic/update.html'
|
template_name = "generic/update.html"
|
||||||
success_url = reverse_lazy('passbook_admin:factors')
|
success_url = reverse_lazy("passbook_admin:factors")
|
||||||
success_message = _('Successfully updated Factor')
|
success_message = _("Successfully updated Factor")
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
form_class_path = self.get_object().form
|
form_class_path = self.get_object().form
|
||||||
@ -81,21 +89,26 @@ class FactorUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
|||||||
return form_class
|
return form_class
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
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,
|
class FactorDeleteView(
|
||||||
PermissionRequiredMixin, DeleteView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||||
|
):
|
||||||
"""Delete factor"""
|
"""Delete factor"""
|
||||||
|
|
||||||
model = Factor
|
model = Factor
|
||||||
template_name = 'generic/delete.html'
|
template_name = "generic/delete.html"
|
||||||
permission_required = 'passbook_core.delete_factor'
|
permission_required = "passbook_core.delete_factor"
|
||||||
success_url = reverse_lazy('passbook_admin:factors')
|
success_url = reverse_lazy("passbook_admin:factors")
|
||||||
success_message = _('Successfully deleted Factor')
|
success_message = _("Successfully deleted Factor")
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
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):
|
def delete(self, request, *args, **kwargs):
|
||||||
messages.success(self.request, self.success_message)
|
messages.success(self.request, self.success_message)
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
"""passbook Group administration"""
|
"""passbook Group administration"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.mixins import \
|
from django.contrib.auth.mixins import (
|
||||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||||
|
)
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
@ -18,40 +19,45 @@ class GroupListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
|||||||
"""Show list of all groups"""
|
"""Show list of all groups"""
|
||||||
|
|
||||||
model = Group
|
model = Group
|
||||||
permission_required = 'passbook_core.view_group'
|
permission_required = "passbook_core.view_group"
|
||||||
ordering = 'name'
|
ordering = "name"
|
||||||
paginate_by = 40
|
paginate_by = 40
|
||||||
template_name = 'administration/group/list.html'
|
template_name = "administration/group/list.html"
|
||||||
|
|
||||||
|
|
||||||
class GroupCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
class GroupCreateView(
|
||||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
SuccessMessageMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
DjangoPermissionRequiredMixin,
|
||||||
|
CreateAssignPermView,
|
||||||
|
):
|
||||||
"""Create new Group"""
|
"""Create new Group"""
|
||||||
|
|
||||||
model = Group
|
model = Group
|
||||||
form_class = GroupForm
|
form_class = GroupForm
|
||||||
permission_required = 'passbook_core.add_group'
|
permission_required = "passbook_core.add_group"
|
||||||
|
|
||||||
template_name = 'generic/create.html'
|
template_name = "generic/create.html"
|
||||||
success_url = reverse_lazy('passbook_admin:groups')
|
success_url = reverse_lazy("passbook_admin:groups")
|
||||||
success_message = _('Successfully created Group')
|
success_message = _("Successfully created Group")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['type'] = 'Group'
|
kwargs["type"] = "Group"
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class GroupUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
class GroupUpdateView(
|
||||||
PermissionRequiredMixin, UpdateView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||||
|
):
|
||||||
"""Update group"""
|
"""Update group"""
|
||||||
|
|
||||||
model = Group
|
model = Group
|
||||||
form_class = GroupForm
|
form_class = GroupForm
|
||||||
permission_required = 'passbook_core.change_group'
|
permission_required = "passbook_core.change_group"
|
||||||
|
|
||||||
template_name = 'generic/update.html'
|
template_name = "generic/update.html"
|
||||||
success_url = reverse_lazy('passbook_admin:groups')
|
success_url = reverse_lazy("passbook_admin:groups")
|
||||||
success_message = _('Successfully updated Group')
|
success_message = _("Successfully updated Group")
|
||||||
|
|
||||||
|
|
||||||
class GroupDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView):
|
class GroupDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView):
|
||||||
@ -59,9 +65,9 @@ class GroupDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView):
|
|||||||
|
|
||||||
model = Group
|
model = Group
|
||||||
|
|
||||||
template_name = 'generic/delete.html'
|
template_name = "generic/delete.html"
|
||||||
success_url = reverse_lazy('passbook_admin:groups')
|
success_url = reverse_lazy("passbook_admin:groups")
|
||||||
success_message = _('Successfully deleted Group')
|
success_message = _("Successfully deleted Group")
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
messages.success(self.request, self.success_message)
|
messages.success(self.request, self.success_message)
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
"""passbook Invitation administration"""
|
"""passbook Invitation administration"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.mixins import \
|
from django.contrib.auth.mixins import (
|
||||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||||
|
)
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
@ -20,47 +21,49 @@ class InvitationListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
|||||||
"""Show list of all invitations"""
|
"""Show list of all invitations"""
|
||||||
|
|
||||||
model = Invitation
|
model = Invitation
|
||||||
permission_required = 'passbook_core.view_invitation'
|
permission_required = "passbook_core.view_invitation"
|
||||||
template_name = 'administration/invitation/list.html'
|
template_name = "administration/invitation/list.html"
|
||||||
|
|
||||||
|
|
||||||
class InvitationCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
class InvitationCreateView(
|
||||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
SuccessMessageMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
DjangoPermissionRequiredMixin,
|
||||||
|
CreateAssignPermView,
|
||||||
|
):
|
||||||
"""Create new Invitation"""
|
"""Create new Invitation"""
|
||||||
|
|
||||||
model = Invitation
|
model = Invitation
|
||||||
form_class = InvitationForm
|
form_class = InvitationForm
|
||||||
permission_required = 'passbook_core.add_invitation'
|
permission_required = "passbook_core.add_invitation"
|
||||||
|
|
||||||
template_name = 'generic/create.html'
|
template_name = "generic/create.html"
|
||||||
success_url = reverse_lazy('passbook_admin:invitations')
|
success_url = reverse_lazy("passbook_admin:invitations")
|
||||||
success_message = _('Successfully created Invitation')
|
success_message = _("Successfully created Invitation")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['type'] = 'Invitation'
|
kwargs["type"] = "Invitation"
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
obj = form.save(commit=False)
|
obj = form.save(commit=False)
|
||||||
obj.created_by = self.request.user
|
obj.created_by = self.request.user
|
||||||
obj.save()
|
obj.save()
|
||||||
invitation_created.send(
|
invitation_created.send(sender=self, request=self.request, invitation=obj)
|
||||||
sender=self,
|
|
||||||
request=self.request,
|
|
||||||
invitation=obj)
|
|
||||||
return HttpResponseRedirect(self.success_url)
|
return HttpResponseRedirect(self.success_url)
|
||||||
|
|
||||||
|
|
||||||
class InvitationDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
class InvitationDeleteView(
|
||||||
PermissionRequiredMixin, DeleteView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||||
|
):
|
||||||
"""Delete invitation"""
|
"""Delete invitation"""
|
||||||
|
|
||||||
model = Invitation
|
model = Invitation
|
||||||
permission_required = 'passbook_core.delete_invitation'
|
permission_required = "passbook_core.delete_invitation"
|
||||||
|
|
||||||
template_name = 'generic/delete.html'
|
template_name = "generic/delete.html"
|
||||||
success_url = reverse_lazy('passbook_admin:invitations')
|
success_url = reverse_lazy("passbook_admin:invitations")
|
||||||
success_message = _('Successfully deleted Invitation')
|
success_message = _("Successfully deleted Invitation")
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
messages.success(self.request, self.success_message)
|
messages.success(self.request, self.success_message)
|
||||||
|
|||||||
@ -5,34 +5,45 @@ from django.views.generic import TemplateView
|
|||||||
|
|
||||||
from passbook import __version__
|
from passbook import __version__
|
||||||
from passbook.admin.mixins import AdminRequiredMixin
|
from passbook.admin.mixins import AdminRequiredMixin
|
||||||
from passbook.core.models import (Application, Factor, Invitation, Policy,
|
from passbook.core.models import (
|
||||||
Provider, Source, User)
|
Application,
|
||||||
|
Factor,
|
||||||
|
Invitation,
|
||||||
|
Policy,
|
||||||
|
Provider,
|
||||||
|
Source,
|
||||||
|
User,
|
||||||
|
)
|
||||||
from passbook.root.celery import CELERY_APP
|
from passbook.root.celery import CELERY_APP
|
||||||
|
|
||||||
|
|
||||||
class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
||||||
"""Overview View"""
|
"""Overview View"""
|
||||||
|
|
||||||
template_name = 'administration/overview.html'
|
template_name = "administration/overview.html"
|
||||||
|
|
||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs):
|
||||||
"""Handle post (clear cache from modal)"""
|
"""Handle post (clear cache from modal)"""
|
||||||
if 'clear' in self.request.POST:
|
if "clear" in self.request.POST:
|
||||||
cache.clear()
|
cache.clear()
|
||||||
return redirect(reverse('passbook_core:auth-login'))
|
return redirect(reverse("passbook_core:auth-login"))
|
||||||
return self.get(*args, **kwargs)
|
return self.get(*args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['application_count'] = len(Application.objects.all())
|
kwargs["application_count"] = len(Application.objects.all())
|
||||||
kwargs['policy_count'] = len(Policy.objects.all())
|
kwargs["policy_count"] = len(Policy.objects.all())
|
||||||
kwargs['user_count'] = len(User.objects.all())
|
kwargs["user_count"] = len(User.objects.all())
|
||||||
kwargs['provider_count'] = len(Provider.objects.all())
|
kwargs["provider_count"] = len(Provider.objects.all())
|
||||||
kwargs['source_count'] = len(Source.objects.all())
|
kwargs["source_count"] = len(Source.objects.all())
|
||||||
kwargs['factor_count'] = len(Factor.objects.all())
|
kwargs["factor_count"] = len(Factor.objects.all())
|
||||||
kwargs['invitation_count'] = len(Invitation.objects.all())
|
kwargs["invitation_count"] = len(Invitation.objects.all())
|
||||||
kwargs['version'] = __version__
|
kwargs["version"] = __version__
|
||||||
kwargs['worker_count'] = len(CELERY_APP.control.ping(timeout=0.5))
|
kwargs["worker_count"] = len(CELERY_APP.control.ping(timeout=0.5))
|
||||||
kwargs['providers_without_application'] = Provider.objects.filter(application=None)
|
kwargs["providers_without_application"] = Provider.objects.filter(
|
||||||
kwargs['policies_without_attachment'] = len(Policy.objects.filter(policymodel__isnull=True))
|
application=None
|
||||||
kwargs['cached_policies'] = len(cache.keys('policy_*'))
|
)
|
||||||
|
kwargs["policies_without_attachment"] = len(
|
||||||
|
Policy.objects.filter(policymodel__isnull=True)
|
||||||
|
)
|
||||||
|
kwargs["cached_policies"] = len(cache.keys("policy_*"))
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
"""passbook Policy administration"""
|
"""passbook Policy administration"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.mixins import \
|
from django.contrib.auth.mixins import (
|
||||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||||
|
)
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
@ -22,49 +23,54 @@ class PolicyListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
|||||||
"""Show list of all policies"""
|
"""Show list of all policies"""
|
||||||
|
|
||||||
model = Policy
|
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):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['types'] = {
|
kwargs["types"] = {
|
||||||
x.__name__: x._meta.verbose_name for x in Policy.__subclasses__()}
|
x.__name__: x._meta.verbose_name for x in Policy.__subclasses__()
|
||||||
|
}
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
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,
|
class PolicyCreateView(
|
||||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
SuccessMessageMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
DjangoPermissionRequiredMixin,
|
||||||
|
CreateAssignPermView,
|
||||||
|
):
|
||||||
"""Create new Policy"""
|
"""Create new Policy"""
|
||||||
|
|
||||||
model = Policy
|
model = Policy
|
||||||
permission_required = 'passbook_core.add_policy'
|
permission_required = "passbook_core.add_policy"
|
||||||
|
|
||||||
template_name = 'generic/create.html'
|
template_name = "generic/create.html"
|
||||||
success_url = reverse_lazy('passbook_admin:policies')
|
success_url = reverse_lazy("passbook_admin:policies")
|
||||||
success_message = _('Successfully created Policy')
|
success_message = _("Successfully created Policy")
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
policy_type = self.request.GET.get('type')
|
policy_type = self.request.GET.get("type")
|
||||||
model = next(x for x in Policy.__subclasses__()
|
model = next(x for x in Policy.__subclasses__() if x.__name__ == policy_type)
|
||||||
if x.__name__ == policy_type)
|
|
||||||
if not model:
|
if not model:
|
||||||
raise Http404
|
raise Http404
|
||||||
return path_to_class(model.form)
|
return path_to_class(model.form)
|
||||||
|
|
||||||
|
|
||||||
class PolicyUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
class PolicyUpdateView(
|
||||||
PermissionRequiredMixin, UpdateView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||||
|
):
|
||||||
"""Update policy"""
|
"""Update policy"""
|
||||||
|
|
||||||
model = Policy
|
model = Policy
|
||||||
permission_required = 'passbook_core.change_policy'
|
permission_required = "passbook_core.change_policy"
|
||||||
|
|
||||||
template_name = 'generic/update.html'
|
template_name = "generic/update.html"
|
||||||
success_url = reverse_lazy('passbook_admin:policies')
|
success_url = reverse_lazy("passbook_admin:policies")
|
||||||
success_message = _('Successfully updated Policy')
|
success_message = _("Successfully updated Policy")
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
form_class_path = self.get_object().form
|
form_class_path = self.get_object().form
|
||||||
@ -72,22 +78,27 @@ class PolicyUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
|||||||
return form_class
|
return form_class
|
||||||
|
|
||||||
def get_object(self, queryset=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()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PolicyDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
class PolicyDeleteView(
|
||||||
PermissionRequiredMixin, DeleteView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||||
|
):
|
||||||
"""Delete policy"""
|
"""Delete policy"""
|
||||||
|
|
||||||
model = Policy
|
model = Policy
|
||||||
permission_required = 'passbook_core.delete_policy'
|
permission_required = "passbook_core.delete_policy"
|
||||||
|
|
||||||
template_name = 'generic/delete.html'
|
template_name = "generic/delete.html"
|
||||||
success_url = reverse_lazy('passbook_admin:policies')
|
success_url = reverse_lazy("passbook_admin:policies")
|
||||||
success_message = _('Successfully deleted Policy')
|
success_message = _("Successfully deleted Policy")
|
||||||
|
|
||||||
def get_object(self, queryset=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 delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
messages.success(self.request, self.success_message)
|
messages.success(self.request, self.success_message)
|
||||||
@ -99,15 +110,17 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
|
|||||||
|
|
||||||
model = Policy
|
model = Policy
|
||||||
form_class = PolicyTestForm
|
form_class = PolicyTestForm
|
||||||
permission_required = 'passbook_core.view_policy'
|
permission_required = "passbook_core.view_policy"
|
||||||
template_name = 'administration/policy/test.html'
|
template_name = "administration/policy/test.html"
|
||||||
object = None
|
object = None
|
||||||
|
|
||||||
def get_object(self, queryset=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):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['policy'] = self.get_object()
|
kwargs["policy"] = self.get_object()
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs):
|
||||||
@ -116,13 +129,13 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
policy = self.get_object()
|
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 = PolicyEngine([policy], user, self.request)
|
||||||
policy_engine.use_cache = False
|
policy_engine.use_cache = False
|
||||||
policy_engine.build()
|
policy_engine.build()
|
||||||
result = policy_engine.passing
|
result = policy_engine.passing
|
||||||
if result:
|
if result:
|
||||||
messages.success(self.request, _('User successfully passed policy.'))
|
messages.success(self.request, _("User successfully passed policy."))
|
||||||
else:
|
else:
|
||||||
messages.error(self.request, _("User didn't pass policy."))
|
messages.error(self.request, _("User didn't pass policy."))
|
||||||
return self.render_to_response(self.get_context_data(form=form, result=result))
|
return self.render_to_response(self.get_context_data(form=form, result=result))
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
"""passbook PropertyMapping administration"""
|
"""passbook PropertyMapping administration"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.mixins import \
|
from django.contrib.auth.mixins import (
|
||||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||||
|
)
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
@ -18,65 +19,78 @@ from passbook.lib.views import CreateAssignPermView
|
|||||||
def all_subclasses(cls):
|
def all_subclasses(cls):
|
||||||
"""Recursively return all subclassess of cls"""
|
"""Recursively return all subclassess of cls"""
|
||||||
return set(cls.__subclasses__()).union(
|
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):
|
class PropertyMappingListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||||
"""Show list of all property_mappings"""
|
"""Show list of all property_mappings"""
|
||||||
|
|
||||||
model = PropertyMapping
|
model = PropertyMapping
|
||||||
permission_required = 'passbook_core.view_propertymapping'
|
permission_required = "passbook_core.view_propertymapping"
|
||||||
template_name = 'administration/property_mapping/list.html'
|
template_name = "administration/property_mapping/list.html"
|
||||||
ordering = 'name'
|
ordering = "name"
|
||||||
paginate_by = 40
|
paginate_by = 40
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['types'] = {
|
kwargs["types"] = {
|
||||||
x.__name__: x._meta.verbose_name for x in all_subclasses(PropertyMapping)}
|
x.__name__: x._meta.verbose_name for x in all_subclasses(PropertyMapping)
|
||||||
|
}
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().select_subclasses()
|
return super().get_queryset().select_subclasses()
|
||||||
|
|
||||||
|
|
||||||
class PropertyMappingCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
class PropertyMappingCreateView(
|
||||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
SuccessMessageMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
DjangoPermissionRequiredMixin,
|
||||||
|
CreateAssignPermView,
|
||||||
|
):
|
||||||
"""Create new PropertyMapping"""
|
"""Create new PropertyMapping"""
|
||||||
|
|
||||||
model = PropertyMapping
|
model = PropertyMapping
|
||||||
permission_required = 'passbook_core.add_propertymapping'
|
permission_required = "passbook_core.add_propertymapping"
|
||||||
|
|
||||||
template_name = 'generic/create.html'
|
template_name = "generic/create.html"
|
||||||
success_url = reverse_lazy('passbook_admin:property-mappings')
|
success_url = reverse_lazy("passbook_admin:property-mappings")
|
||||||
success_message = _('Successfully created Property Mapping')
|
success_message = _("Successfully created Property Mapping")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs = super().get_context_data(**kwargs)
|
kwargs = super().get_context_data(**kwargs)
|
||||||
property_mapping_type = self.request.GET.get('type')
|
property_mapping_type = self.request.GET.get("type")
|
||||||
model = next(x for x in all_subclasses(PropertyMapping)
|
model = next(
|
||||||
if x.__name__ == property_mapping_type)
|
x
|
||||||
kwargs['type'] = model._meta.verbose_name
|
for x in all_subclasses(PropertyMapping)
|
||||||
|
if x.__name__ == property_mapping_type
|
||||||
|
)
|
||||||
|
kwargs["type"] = model._meta.verbose_name
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
property_mapping_type = self.request.GET.get('type')
|
property_mapping_type = self.request.GET.get("type")
|
||||||
model = next(x for x in all_subclasses(PropertyMapping)
|
model = next(
|
||||||
if x.__name__ == property_mapping_type)
|
x
|
||||||
|
for x in all_subclasses(PropertyMapping)
|
||||||
|
if x.__name__ == property_mapping_type
|
||||||
|
)
|
||||||
if not model:
|
if not model:
|
||||||
raise Http404
|
raise Http404
|
||||||
return path_to_class(model.form)
|
return path_to_class(model.form)
|
||||||
|
|
||||||
|
|
||||||
class PropertyMappingUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
class PropertyMappingUpdateView(
|
||||||
PermissionRequiredMixin, UpdateView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||||
|
):
|
||||||
"""Update property_mapping"""
|
"""Update property_mapping"""
|
||||||
|
|
||||||
model = PropertyMapping
|
model = PropertyMapping
|
||||||
permission_required = 'passbook_core.change_propertymapping'
|
permission_required = "passbook_core.change_propertymapping"
|
||||||
|
|
||||||
template_name = 'generic/update.html'
|
template_name = "generic/update.html"
|
||||||
success_url = reverse_lazy('passbook_admin:property-mappings')
|
success_url = reverse_lazy("passbook_admin:property-mappings")
|
||||||
success_message = _('Successfully updated Property Mapping')
|
success_message = _("Successfully updated Property Mapping")
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
form_class_path = self.get_object().form
|
form_class_path = self.get_object().form
|
||||||
@ -84,22 +98,31 @@ class PropertyMappingUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
|||||||
return form_class
|
return form_class
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
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,
|
class PropertyMappingDeleteView(
|
||||||
PermissionRequiredMixin, DeleteView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||||
|
):
|
||||||
"""Delete property_mapping"""
|
"""Delete property_mapping"""
|
||||||
|
|
||||||
model = PropertyMapping
|
model = PropertyMapping
|
||||||
permission_required = 'passbook_core.delete_propertymapping'
|
permission_required = "passbook_core.delete_propertymapping"
|
||||||
|
|
||||||
template_name = 'generic/delete.html'
|
template_name = "generic/delete.html"
|
||||||
success_url = reverse_lazy('passbook_admin:property-mappings')
|
success_url = reverse_lazy("passbook_admin:property-mappings")
|
||||||
success_message = _('Successfully deleted Property Mapping')
|
success_message = _("Successfully deleted Property Mapping")
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
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):
|
def delete(self, request, *args, **kwargs):
|
||||||
messages.success(self.request, self.success_message)
|
messages.success(self.request, self.success_message)
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
"""passbook Provider administration"""
|
"""passbook Provider administration"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.mixins import \
|
from django.contrib.auth.mixins import (
|
||||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||||
|
)
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
@ -19,48 +20,55 @@ class ProviderListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
|||||||
"""Show list of all providers"""
|
"""Show list of all providers"""
|
||||||
|
|
||||||
model = Provider
|
model = Provider
|
||||||
permission_required = 'passbook_core.add_provider'
|
permission_required = "passbook_core.add_provider"
|
||||||
template_name = 'administration/provider/list.html'
|
template_name = "administration/provider/list.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['types'] = {
|
kwargs["types"] = {
|
||||||
x.__name__: x._meta.verbose_name for x in Provider.__subclasses__()}
|
x.__name__: x._meta.verbose_name for x in Provider.__subclasses__()
|
||||||
|
}
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().select_subclasses()
|
return super().get_queryset().select_subclasses()
|
||||||
|
|
||||||
|
|
||||||
class ProviderCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
class ProviderCreateView(
|
||||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
SuccessMessageMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
DjangoPermissionRequiredMixin,
|
||||||
|
CreateAssignPermView,
|
||||||
|
):
|
||||||
"""Create new Provider"""
|
"""Create new Provider"""
|
||||||
|
|
||||||
model = Provider
|
model = Provider
|
||||||
permission_required = 'passbook_core.add_provider'
|
permission_required = "passbook_core.add_provider"
|
||||||
|
|
||||||
template_name = 'generic/create.html'
|
template_name = "generic/create.html"
|
||||||
success_url = reverse_lazy('passbook_admin:providers')
|
success_url = reverse_lazy("passbook_admin:providers")
|
||||||
success_message = _('Successfully created Provider')
|
success_message = _("Successfully created Provider")
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
provider_type = self.request.GET.get('type')
|
provider_type = self.request.GET.get("type")
|
||||||
model = next(x for x in Provider.__subclasses__()
|
model = next(
|
||||||
if x.__name__ == provider_type)
|
x for x in Provider.__subclasses__() if x.__name__ == provider_type
|
||||||
|
)
|
||||||
if not model:
|
if not model:
|
||||||
raise Http404
|
raise Http404
|
||||||
return path_to_class(model.form)
|
return path_to_class(model.form)
|
||||||
|
|
||||||
|
|
||||||
class ProviderUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
class ProviderUpdateView(
|
||||||
PermissionRequiredMixin, UpdateView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||||
|
):
|
||||||
"""Update provider"""
|
"""Update provider"""
|
||||||
|
|
||||||
model = Provider
|
model = Provider
|
||||||
permission_required = 'passbook_core.change_provider'
|
permission_required = "passbook_core.change_provider"
|
||||||
|
|
||||||
template_name = 'generic/update.html'
|
template_name = "generic/update.html"
|
||||||
success_url = reverse_lazy('passbook_admin:providers')
|
success_url = reverse_lazy("passbook_admin:providers")
|
||||||
success_message = _('Successfully updated Provider')
|
success_message = _("Successfully updated Provider")
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
form_class_path = self.get_object().form
|
form_class_path = self.get_object().form
|
||||||
@ -68,22 +76,31 @@ class ProviderUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
|||||||
return form_class
|
return form_class
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
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,
|
class ProviderDeleteView(
|
||||||
PermissionRequiredMixin, DeleteView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||||
|
):
|
||||||
"""Delete provider"""
|
"""Delete provider"""
|
||||||
|
|
||||||
model = Provider
|
model = Provider
|
||||||
permission_required = 'passbook_core.delete_provider'
|
permission_required = "passbook_core.delete_provider"
|
||||||
|
|
||||||
template_name = 'generic/delete.html'
|
template_name = "generic/delete.html"
|
||||||
success_url = reverse_lazy('passbook_admin:providers')
|
success_url = reverse_lazy("passbook_admin:providers")
|
||||||
success_message = _('Successfully deleted Provider')
|
success_message = _("Successfully deleted Provider")
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
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):
|
def delete(self, request, *args, **kwargs):
|
||||||
messages.success(self.request, self.success_message)
|
messages.success(self.request, self.success_message)
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
"""passbook Source administration"""
|
"""passbook Source administration"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.mixins import \
|
from django.contrib.auth.mixins import (
|
||||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||||
|
)
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
@ -18,55 +19,63 @@ from passbook.lib.views import CreateAssignPermView
|
|||||||
def all_subclasses(cls):
|
def all_subclasses(cls):
|
||||||
"""Recursively return all subclassess of cls"""
|
"""Recursively return all subclassess of cls"""
|
||||||
return set(cls.__subclasses__()).union(
|
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):
|
class SourceListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||||
"""Show list of all sources"""
|
"""Show list of all sources"""
|
||||||
|
|
||||||
model = Source
|
model = Source
|
||||||
permission_required = 'passbook_core.view_source'
|
permission_required = "passbook_core.view_source"
|
||||||
ordering = 'name'
|
ordering = "name"
|
||||||
paginate_by = 40
|
paginate_by = 40
|
||||||
template_name = 'administration/source/list.html'
|
template_name = "administration/source/list.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['types'] = {
|
kwargs["types"] = {
|
||||||
x.__name__: x._meta.verbose_name for x in all_subclasses(Source)}
|
x.__name__: x._meta.verbose_name for x in all_subclasses(Source)
|
||||||
|
}
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().select_subclasses()
|
return super().get_queryset().select_subclasses()
|
||||||
|
|
||||||
|
|
||||||
class SourceCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
class SourceCreateView(
|
||||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
SuccessMessageMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
DjangoPermissionRequiredMixin,
|
||||||
|
CreateAssignPermView,
|
||||||
|
):
|
||||||
"""Create new Source"""
|
"""Create new Source"""
|
||||||
|
|
||||||
model = Source
|
model = Source
|
||||||
permission_required = 'passbook_core.add_source'
|
permission_required = "passbook_core.add_source"
|
||||||
|
|
||||||
template_name = 'generic/create.html'
|
template_name = "generic/create.html"
|
||||||
success_url = reverse_lazy('passbook_admin:sources')
|
success_url = reverse_lazy("passbook_admin:sources")
|
||||||
success_message = _('Successfully created Source')
|
success_message = _("Successfully created Source")
|
||||||
|
|
||||||
def get_form_class(self):
|
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)
|
model = next(x for x in all_subclasses(Source) if x.__name__ == source_type)
|
||||||
if not model:
|
if not model:
|
||||||
raise Http404
|
raise Http404
|
||||||
return path_to_class(model.form)
|
return path_to_class(model.form)
|
||||||
|
|
||||||
|
|
||||||
class SourceUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
class SourceUpdateView(
|
||||||
PermissionRequiredMixin, UpdateView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||||
|
):
|
||||||
"""Update source"""
|
"""Update source"""
|
||||||
|
|
||||||
model = Source
|
model = Source
|
||||||
permission_required = 'passbook_core.change_source'
|
permission_required = "passbook_core.change_source"
|
||||||
|
|
||||||
template_name = 'generic/update.html'
|
template_name = "generic/update.html"
|
||||||
success_url = reverse_lazy('passbook_admin:sources')
|
success_url = reverse_lazy("passbook_admin:sources")
|
||||||
success_message = _('Successfully updated Source')
|
success_message = _("Successfully updated Source")
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
form_class_path = self.get_object().form
|
form_class_path = self.get_object().form
|
||||||
@ -74,22 +83,27 @@ class SourceUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
|||||||
return form_class
|
return form_class
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
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,
|
class SourceDeleteView(
|
||||||
PermissionRequiredMixin, DeleteView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||||
|
):
|
||||||
"""Delete source"""
|
"""Delete source"""
|
||||||
|
|
||||||
model = Source
|
model = Source
|
||||||
permission_required = 'passbook_core.delete_source'
|
permission_required = "passbook_core.delete_source"
|
||||||
|
|
||||||
template_name = 'generic/delete.html'
|
template_name = "generic/delete.html"
|
||||||
success_url = reverse_lazy('passbook_admin:sources')
|
success_url = reverse_lazy("passbook_admin:sources")
|
||||||
success_message = _('Successfully deleted Source')
|
success_message = _("Successfully deleted Source")
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
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):
|
def delete(self, request, *args, **kwargs):
|
||||||
messages.success(self.request, self.success_message)
|
messages.success(self.request, self.success_message)
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
"""passbook User administration"""
|
"""passbook User administration"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.mixins import \
|
from django.contrib.auth.mixins import (
|
||||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin
|
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||||
|
)
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
@ -19,50 +20,56 @@ class UserListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
|||||||
"""Show list of all users"""
|
"""Show list of all users"""
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
permission_required = 'passbook_core.view_user'
|
permission_required = "passbook_core.view_user"
|
||||||
ordering = 'username'
|
ordering = "username"
|
||||||
paginate_by = 40
|
paginate_by = 40
|
||||||
template_name = 'administration/user/list.html'
|
template_name = "administration/user/list.html"
|
||||||
|
|
||||||
|
|
||||||
class UserCreateView(SuccessMessageMixin, LoginRequiredMixin,
|
class UserCreateView(
|
||||||
DjangoPermissionRequiredMixin, CreateAssignPermView):
|
SuccessMessageMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
DjangoPermissionRequiredMixin,
|
||||||
|
CreateAssignPermView,
|
||||||
|
):
|
||||||
"""Create user"""
|
"""Create user"""
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
form_class = UserForm
|
form_class = UserForm
|
||||||
permission_required = 'passbook_core.add_user'
|
permission_required = "passbook_core.add_user"
|
||||||
|
|
||||||
template_name = 'generic/create.html'
|
template_name = "generic/create.html"
|
||||||
success_url = reverse_lazy('passbook_admin:users')
|
success_url = reverse_lazy("passbook_admin:users")
|
||||||
success_message = _('Successfully created User')
|
success_message = _("Successfully created User")
|
||||||
|
|
||||||
|
|
||||||
class UserUpdateView(SuccessMessageMixin, LoginRequiredMixin,
|
class UserUpdateView(
|
||||||
PermissionRequiredMixin, UpdateView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
|
||||||
|
):
|
||||||
"""Update user"""
|
"""Update user"""
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
form_class = UserForm
|
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
|
# By default the object's name is user which is used by other checks
|
||||||
context_object_name = 'object'
|
context_object_name = "object"
|
||||||
template_name = 'generic/update.html'
|
template_name = "generic/update.html"
|
||||||
success_url = reverse_lazy('passbook_admin:users')
|
success_url = reverse_lazy("passbook_admin:users")
|
||||||
success_message = _('Successfully updated User')
|
success_message = _("Successfully updated User")
|
||||||
|
|
||||||
|
|
||||||
class UserDeleteView(SuccessMessageMixin, LoginRequiredMixin,
|
class UserDeleteView(
|
||||||
PermissionRequiredMixin, DeleteView):
|
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
|
||||||
|
):
|
||||||
"""Delete user"""
|
"""Delete user"""
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
permission_required = 'passbook_core.delete_user'
|
permission_required = "passbook_core.delete_user"
|
||||||
|
|
||||||
template_name = 'generic/delete.html'
|
template_name = "generic/delete.html"
|
||||||
success_url = reverse_lazy('passbook_admin:users')
|
success_url = reverse_lazy("passbook_admin:users")
|
||||||
success_message = _('Successfully deleted User')
|
success_message = _("Successfully deleted User")
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
messages.success(self.request, self.success_message)
|
messages.success(self.request, self.success_message)
|
||||||
@ -73,13 +80,16 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
|
|||||||
"""Get Password reset link for user"""
|
"""Get Password reset link for user"""
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
permission_required = 'passbook_core.reset_user_password'
|
permission_required = "passbook_core.reset_user_password"
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Create nonce for user and return link"""
|
"""Create nonce for user and return link"""
|
||||||
super().get(request, *args, **kwargs)
|
super().get(request, *args, **kwargs)
|
||||||
nonce = Nonce.objects.create(user=self.object)
|
nonce = Nonce.objects.create(user=self.object)
|
||||||
link = request.build_absolute_uri(reverse(
|
link = request.build_absolute_uri(
|
||||||
'passbook_core:auth-password-reset', kwargs={'nonce': nonce.uuid}))
|
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')
|
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):
|
class PassbookAPIConfig(AppConfig):
|
||||||
"""passbook API Config"""
|
"""passbook API Config"""
|
||||||
|
|
||||||
name = 'passbook.api'
|
name = "passbook.api"
|
||||||
label = 'passbook_api'
|
label = "passbook_api"
|
||||||
mountpoint = 'api/'
|
mountpoint = "api/"
|
||||||
verbose_name = 'passbook API'
|
verbose_name = "passbook API"
|
||||||
|
|||||||
@ -9,13 +9,13 @@ class CustomObjectPermissions(DjangoObjectPermissions):
|
|||||||
"""Similar to `DjangoObjectPermissions`, but adding 'view' permissions."""
|
"""Similar to `DjangoObjectPermissions`, but adding 'view' permissions."""
|
||||||
|
|
||||||
perms_map = {
|
perms_map = {
|
||||||
'GET': ['%(app_label)s.view_%(model_name)s'],
|
"GET": ["%(app_label)s.view_%(model_name)s"],
|
||||||
'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
|
"OPTIONS": ["%(app_label)s.view_%(model_name)s"],
|
||||||
'HEAD': ['%(app_label)s.view_%(model_name)s'],
|
"HEAD": ["%(app_label)s.view_%(model_name)s"],
|
||||||
'POST': ['%(app_label)s.add_%(model_name)s'],
|
"POST": ["%(app_label)s.add_%(model_name)s"],
|
||||||
'PUT': ['%(app_label)s.change_%(model_name)s'],
|
"PUT": ["%(app_label)s.change_%(model_name)s"],
|
||||||
'PATCH': ['%(app_label)s.change_%(model_name)s'],
|
"PATCH": ["%(app_label)s.change_%(model_name)s"],
|
||||||
'DELETE': ['%(app_label)s.delete_%(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
|
from passbook.api.v2.urls import urlpatterns as v2_urls
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('v1/', include(v1_urls)),
|
path("v1/", include(v1_urls)),
|
||||||
path('v2/', include(v2_urls)),
|
path("v2/", include(v2_urls)),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -7,16 +7,16 @@ from oauth2_provider.views.mixins import ScopedResourceMixin
|
|||||||
class OpenIDUserInfoView(ScopedResourceMixin, View):
|
class OpenIDUserInfoView(ScopedResourceMixin, View):
|
||||||
"""Passbook v1 OpenID API"""
|
"""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"""
|
"""Passbook v1 OpenID API"""
|
||||||
payload = {
|
payload = {
|
||||||
'sub': request.user.uuid.int,
|
"sub": request.user.uuid.int,
|
||||||
'name': request.user.get_full_name(),
|
"name": request.user.get_full_name(),
|
||||||
'given_name': request.user.name,
|
"given_name": request.user.name,
|
||||||
'family_name': '',
|
"family_name": "",
|
||||||
'preferred_username': request.user.username,
|
"preferred_username": request.user.username,
|
||||||
'email': request.user.email,
|
"email": request.user.email,
|
||||||
}
|
}
|
||||||
return JsonResponse(payload)
|
return JsonResponse(payload)
|
||||||
|
|||||||
@ -3,6 +3,4 @@ from django.urls import path
|
|||||||
|
|
||||||
from passbook.api.v1.openid import OpenIDUserInfoView
|
from passbook.api.v1.openid import OpenIDUserInfoView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [path("openid/", OpenIDUserInfoView.as_view(), name="openid")]
|
||||||
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.app_gw.api import ApplicationGatewayProviderViewSet
|
||||||
from passbook.providers.oauth.api import OAuth2ProviderViewSet
|
from passbook.providers.oauth.api import OAuth2ProviderViewSet
|
||||||
from passbook.providers.oidc.api import OpenIDProviderViewSet
|
from passbook.providers.oidc.api import OpenIDProviderViewSet
|
||||||
from passbook.providers.saml.api import (SAMLPropertyMappingViewSet,
|
from passbook.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProviderViewSet
|
||||||
SAMLProviderViewSet)
|
from passbook.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
|
||||||
from passbook.sources.ldap.api import (LDAPPropertyMappingViewSet,
|
|
||||||
LDAPSourceViewSet)
|
|
||||||
from passbook.sources.oauth.api import OAuthSourceViewSet
|
from passbook.sources.oauth.api import OAuthSourceViewSet
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
|
|
||||||
for _passbook_app in get_apps():
|
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:
|
for prefix, viewset in _passbook_app.api_mountpoint:
|
||||||
router.register(prefix, viewset)
|
router.register(prefix, viewset)
|
||||||
LOGGER.debug("Mounted API URLs", app_name=_passbook_app.name)
|
LOGGER.debug("Mounted API URLs", app_name=_passbook_app.name)
|
||||||
|
|
||||||
router.register('core/applications', ApplicationViewSet)
|
router.register("core/applications", ApplicationViewSet)
|
||||||
router.register('core/invitations', InvitationViewSet)
|
router.register("core/invitations", InvitationViewSet)
|
||||||
router.register('core/groups', GroupViewSet)
|
router.register("core/groups", GroupViewSet)
|
||||||
router.register('core/users', UserViewSet)
|
router.register("core/users", UserViewSet)
|
||||||
router.register('audit/events', EventViewSet)
|
router.register("audit/events", EventViewSet)
|
||||||
router.register('sources/all', SourceViewSet)
|
router.register("sources/all", SourceViewSet)
|
||||||
router.register('sources/ldap', LDAPSourceViewSet)
|
router.register("sources/ldap", LDAPSourceViewSet)
|
||||||
router.register('sources/oauth', OAuthSourceViewSet)
|
router.register("sources/oauth", OAuthSourceViewSet)
|
||||||
router.register('policies/all', PolicyViewSet)
|
router.register("policies/all", PolicyViewSet)
|
||||||
router.register('policies/passwordexpiry', PasswordExpiryPolicyViewSet)
|
router.register("policies/passwordexpiry", PasswordExpiryPolicyViewSet)
|
||||||
router.register('policies/groupmembership', GroupMembershipPolicyViewSet)
|
router.register("policies/groupmembership", GroupMembershipPolicyViewSet)
|
||||||
router.register('policies/haveibeenpwned', HaveIBeenPwendPolicyViewSet)
|
router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet)
|
||||||
router.register('policies/fieldmatcher', FieldMatcherPolicyViewSet)
|
router.register("policies/fieldmatcher", FieldMatcherPolicyViewSet)
|
||||||
router.register('policies/password', PasswordPolicyViewSet)
|
router.register("policies/password", PasswordPolicyViewSet)
|
||||||
router.register('policies/reputation', ReputationPolicyViewSet)
|
router.register("policies/reputation", ReputationPolicyViewSet)
|
||||||
router.register('policies/ssologin', SSOLoginPolicyViewSet)
|
router.register("policies/ssologin", SSOLoginPolicyViewSet)
|
||||||
router.register('policies/webhook', WebhookPolicyViewSet)
|
router.register("policies/webhook", WebhookPolicyViewSet)
|
||||||
router.register('providers/all', ProviderViewSet)
|
router.register("providers/all", ProviderViewSet)
|
||||||
router.register('providers/applicationgateway', ApplicationGatewayProviderViewSet)
|
router.register("providers/applicationgateway", ApplicationGatewayProviderViewSet)
|
||||||
router.register('providers/oauth', OAuth2ProviderViewSet)
|
router.register("providers/oauth", OAuth2ProviderViewSet)
|
||||||
router.register('providers/openid', OpenIDProviderViewSet)
|
router.register("providers/openid", OpenIDProviderViewSet)
|
||||||
router.register('providers/saml', SAMLProviderViewSet)
|
router.register("providers/saml", SAMLProviderViewSet)
|
||||||
router.register('propertymappings/all', PropertyMappingViewSet)
|
router.register("propertymappings/all", PropertyMappingViewSet)
|
||||||
router.register('propertymappings/ldap', LDAPPropertyMappingViewSet)
|
router.register("propertymappings/ldap", LDAPPropertyMappingViewSet)
|
||||||
router.register('propertymappings/saml', SAMLPropertyMappingViewSet)
|
router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
|
||||||
router.register('factors/all', FactorViewSet)
|
router.register("factors/all", FactorViewSet)
|
||||||
router.register('factors/captcha', CaptchaFactorViewSet)
|
router.register("factors/captcha", CaptchaFactorViewSet)
|
||||||
router.register('factors/dummy', DummyFactorViewSet)
|
router.register("factors/dummy", DummyFactorViewSet)
|
||||||
router.register('factors/email', EmailFactorViewSet)
|
router.register("factors/email", EmailFactorViewSet)
|
||||||
router.register('factors/otp', OTPFactorViewSet)
|
router.register("factors/otp", OTPFactorViewSet)
|
||||||
router.register('factors/password', PasswordFactorViewSet)
|
router.register("factors/password", PasswordFactorViewSet)
|
||||||
|
|
||||||
info = openapi.Info(
|
info = openapi.Info(
|
||||||
title="passbook API",
|
title="passbook API",
|
||||||
default_version='v2',
|
default_version="v2",
|
||||||
# description="Test description",
|
# description="Test description",
|
||||||
# terms_of_service="https://www.google.com/policies/terms/",
|
# terms_of_service="https://www.google.com/policies/terms/",
|
||||||
contact=openapi.Contact(email="hello@beryju.org"),
|
contact=openapi.Contact(email="hello@beryju.org"),
|
||||||
license=openapi.License(name="MIT License"),
|
license=openapi.License(name="MIT License"),
|
||||||
)
|
)
|
||||||
SchemaView = get_schema_view(
|
SchemaView = get_schema_view(
|
||||||
info,
|
info, public=True, permission_classes=(CustomObjectPermissions,),
|
||||||
public=True,
|
|
||||||
permission_classes=(CustomObjectPermissions,),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^swagger(?P<format>\.json|\.yaml)$',
|
url(
|
||||||
SchemaView.without_ui(cache_timeout=0), name='schema-json'),
|
r"^swagger(?P<format>\.json|\.yaml)$",
|
||||||
path('swagger/', SchemaView.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
SchemaView.without_ui(cache_timeout=0),
|
||||||
path('redoc/', SchemaView.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
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
|
] + router.urls
|
||||||
|
|||||||
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
from passbook.lib.admin import admin_autoregister
|
from passbook.lib.admin import admin_autoregister
|
||||||
|
|
||||||
admin_autoregister('passbook_audit')
|
admin_autoregister("passbook_audit")
|
||||||
|
|||||||
@ -11,7 +11,16 @@ class EventSerializer(ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Event
|
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):
|
class EventViewSet(ReadOnlyModelViewSet):
|
||||||
|
|||||||
@ -7,10 +7,10 @@ from django.apps import AppConfig
|
|||||||
class PassbookAuditConfig(AppConfig):
|
class PassbookAuditConfig(AppConfig):
|
||||||
"""passbook audit app"""
|
"""passbook audit app"""
|
||||||
|
|
||||||
name = 'passbook.audit'
|
name = "passbook.audit"
|
||||||
label = 'passbook_audit'
|
label = "passbook_audit"
|
||||||
verbose_name = 'passbook Audit'
|
verbose_name = "passbook Audit"
|
||||||
mountpoint = 'audit/'
|
mountpoint = "audit/"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import_module('passbook.audit.signals')
|
import_module("passbook.audit.signals")
|
||||||
|
|||||||
@ -18,20 +18,55 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='AuditEntry',
|
name="AuditEntry",
|
||||||
fields=[
|
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')])),
|
"uuid",
|
||||||
('date', models.DateTimeField(auto_now_add=True)),
|
models.UUIDField(
|
||||||
('app', models.TextField()),
|
default=uuid.uuid4,
|
||||||
('context', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
|
editable=False,
|
||||||
('request_ip', models.GenericIPAddressField()),
|
primary_key=True,
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
serialize=False,
|
||||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"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={
|
options={
|
||||||
'verbose_name': 'Audit Entry',
|
"verbose_name": "Audit Entry",
|
||||||
'verbose_name_plural': 'Audit Entries',
|
"verbose_name_plural": "Audit Entries",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -8,12 +8,9 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('passbook_audit', '0001_initial'),
|
("passbook_audit", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RenameModel(
|
migrations.RenameModel(old_name="AuditEntry", new_name="Event",),
|
||||||
old_name='AuditEntry',
|
|
||||||
new_name='Event',
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -8,17 +8,33 @@ import passbook.audit.models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_audit', '0002_auto_20191028_0829'),
|
("passbook_audit", "0002_auto_20191028_0829"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='event',
|
name="event",
|
||||||
options={'verbose_name': 'Audit Event', 'verbose_name_plural': 'Audit Events'},
|
options={
|
||||||
|
"verbose_name": "Audit Event",
|
||||||
|
"verbose_name_plural": "Audit Events",
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='action',
|
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')]),
|
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):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_audit', '0003_auto_20191205_1407'),
|
("passbook_audit", "0003_auto_20191205_1407"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(model_name="event", name="request_ip",),
|
||||||
model_name='event',
|
|
||||||
name='request_ip',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='event',
|
model_name="event",
|
||||||
name='client_ip',
|
name="client_ip",
|
||||||
field=models.GenericIPAddressField(null=True),
|
field=models.GenericIPAddressField(null=True),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
"""passbook audit models"""
|
"""passbook audit models"""
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from uuid import UUID
|
||||||
from inspect import getmodule, stack
|
from inspect import getmodule, stack
|
||||||
from typing import Optional
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.contrib.postgres.fields import JSONField
|
from django.contrib.postgres.fields import JSONField
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
@ -18,19 +20,44 @@ from passbook.lib.utils.http import get_client_ip
|
|||||||
|
|
||||||
LOGGER = get_logger()
|
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):
|
class EventAction(Enum):
|
||||||
"""All possible actions to save into the audit log"""
|
"""All possible actions to save into the audit log"""
|
||||||
|
|
||||||
LOGIN = 'login'
|
LOGIN = "login"
|
||||||
LOGIN_FAILED = 'login_failed'
|
LOGIN_FAILED = "login_failed"
|
||||||
LOGOUT = 'logout'
|
LOGOUT = "logout"
|
||||||
AUTHORIZE_APPLICATION = 'authorize_application'
|
AUTHORIZE_APPLICATION = "authorize_application"
|
||||||
SUSPICIOUS_REQUEST = 'suspicious_request'
|
SUSPICIOUS_REQUEST = "suspicious_request"
|
||||||
SIGN_UP = 'sign_up'
|
SIGN_UP = "sign_up"
|
||||||
PASSWORD_RESET = 'password_reset' # noqa # nosec
|
PASSWORD_RESET = "password_reset" # noqa # nosec
|
||||||
INVITE_CREATED = 'invitation_created'
|
INVITE_CREATED = "invitation_created"
|
||||||
INVITE_USED = 'invitation_used'
|
INVITE_USED = "invitation_used"
|
||||||
CUSTOM = 'custom'
|
CUSTOM = "custom"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def as_choices():
|
def as_choices():
|
||||||
@ -41,7 +68,9 @@ class EventAction(Enum):
|
|||||||
class Event(UUIDModel):
|
class Event(UUIDModel):
|
||||||
"""An individual audit log event"""
|
"""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())
|
action = models.TextField(choices=EventAction.as_choices())
|
||||||
date = models.DateTimeField(auto_now_add=True)
|
date = models.DateTimeField(auto_now_add=True)
|
||||||
app = models.TextField()
|
app = models.TextField()
|
||||||
@ -56,28 +85,31 @@ class Event(UUIDModel):
|
|||||||
return request.resolver_match.app_name
|
return request.resolver_match.app_name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def new(action: EventAction,
|
def new(
|
||||||
|
action: EventAction,
|
||||||
app: Optional[str] = None,
|
app: Optional[str] = None,
|
||||||
_inspect_offset: int = 1,
|
_inspect_offset: int = 1,
|
||||||
**kwargs) -> 'Event':
|
**kwargs,
|
||||||
|
) -> "Event":
|
||||||
"""Create new Event instance from arguments. Instance is NOT saved."""
|
"""Create new Event instance from arguments. Instance is NOT saved."""
|
||||||
if not isinstance(action, EventAction):
|
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:
|
if not app:
|
||||||
app = getmodule(stack()[_inspect_offset][0]).__name__
|
app = getmodule(stack()[_inspect_offset][0]).__name__
|
||||||
event = Event(
|
cleaned_kwargs = sanitize_dict(kwargs)
|
||||||
action=action.value,
|
event = Event(action=action.value, app=app, context=cleaned_kwargs)
|
||||||
app=app,
|
LOGGER.debug("Created Audit event", action=action, context=cleaned_kwargs)
|
||||||
context=kwargs)
|
|
||||||
LOGGER.debug("Created Audit event", action=action, context=kwargs)
|
|
||||||
return event
|
return event
|
||||||
|
|
||||||
def from_http(self, request: HttpRequest,
|
def from_http(
|
||||||
user: Optional[settings.AUTH_USER_MODEL] = None) -> 'Event':
|
self, request: HttpRequest, user: Optional[settings.AUTH_USER_MODEL] = None
|
||||||
|
) -> "Event":
|
||||||
"""Add data from a Django-HttpRequest, allowing the creation of
|
"""Add data from a Django-HttpRequest, allowing the creation of
|
||||||
Events independently from requests.
|
Events independently from requests.
|
||||||
`user` arguments optionally overrides user from requests."""
|
`user` arguments optionally overrides user from requests."""
|
||||||
if hasattr(request, 'user'):
|
if hasattr(request, "user"):
|
||||||
if isinstance(request.user, AnonymousUser):
|
if isinstance(request.user, AnonymousUser):
|
||||||
self.user = get_anonymous_user()
|
self.user = get_anonymous_user()
|
||||||
else:
|
else:
|
||||||
@ -85,7 +117,7 @@ class Event(UUIDModel):
|
|||||||
if user:
|
if user:
|
||||||
self.user = user
|
self.user = user
|
||||||
# User 255.255.255.255 as fallback if IP cannot be determined
|
# 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 there's no app set, we get it from the requests too
|
||||||
if not self.app:
|
if not self.app:
|
||||||
self.app = Event._get_app_from_request(request)
|
self.app = Event._get_app_from_request(request)
|
||||||
@ -94,10 +126,12 @@ class Event(UUIDModel):
|
|||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self._state.adding:
|
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)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
verbose_name = _('Audit Event')
|
verbose_name = _("Audit Event")
|
||||||
verbose_name_plural = _('Audit Events')
|
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 django.dispatch import receiver
|
||||||
|
|
||||||
from passbook.audit.models import Event, EventAction
|
from passbook.audit.models import Event, EventAction
|
||||||
from passbook.core.signals import (invitation_created, invitation_used,
|
from passbook.core.signals import invitation_created, invitation_used, user_signed_up
|
||||||
user_signed_up)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_logged_in)
|
@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"""
|
"""Log successful login"""
|
||||||
Event.new(EventAction.LOGIN).from_http(request)
|
Event.new(EventAction.LOGIN).from_http(request)
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_logged_out)
|
@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"""
|
"""Log successfully logout"""
|
||||||
Event.new(EventAction.LOGOUT).from_http(request)
|
Event.new(EventAction.LOGOUT).from_http(request)
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_signed_up)
|
@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"""
|
"""Log successfully signed up"""
|
||||||
Event.new(EventAction.SIGN_UP).from_http(request)
|
Event.new(EventAction.SIGN_UP).from_http(request)
|
||||||
|
|
||||||
|
|
||||||
@receiver(invitation_created)
|
@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"""
|
"""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)
|
@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"""
|
"""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
|
from passbook.lib.admin import admin_autoregister
|
||||||
|
|
||||||
admin_autoregister('passbook_core')
|
admin_autoregister("passbook_core")
|
||||||
|
|||||||
@ -11,8 +11,16 @@ class ApplicationSerializer(ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Application
|
model = Application
|
||||||
fields = ['pk', 'name', 'slug', 'launch_url', 'icon_url',
|
fields = [
|
||||||
'provider', 'policies', 'skip_authorization']
|
"pk",
|
||||||
|
"name",
|
||||||
|
"slug",
|
||||||
|
"launch_url",
|
||||||
|
"icon_url",
|
||||||
|
"provider",
|
||||||
|
"policies",
|
||||||
|
"skip_authorization",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ApplicationViewSet(ModelViewSet):
|
class ApplicationViewSet(ModelViewSet):
|
||||||
|
|||||||
@ -8,16 +8,16 @@ from passbook.core.models import Factor
|
|||||||
class FactorSerializer(ModelSerializer):
|
class FactorSerializer(ModelSerializer):
|
||||||
"""Factor Serializer"""
|
"""Factor Serializer"""
|
||||||
|
|
||||||
__type__ = SerializerMethodField(method_name='get_type')
|
__type__ = SerializerMethodField(method_name="get_type")
|
||||||
|
|
||||||
def get_type(self, obj):
|
def get_type(self, obj):
|
||||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
"""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:
|
class Meta:
|
||||||
|
|
||||||
model = Factor
|
model = Factor
|
||||||
fields = ['pk', 'name', 'slug', 'order', 'enabled', '__type__']
|
fields = ["pk", "name", "slug", "order", "enabled", "__type__"]
|
||||||
|
|
||||||
|
|
||||||
class FactorViewSet(ReadOnlyModelViewSet):
|
class FactorViewSet(ReadOnlyModelViewSet):
|
||||||
|
|||||||
@ -11,7 +11,7 @@ class GroupSerializer(ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Group
|
model = Group
|
||||||
fields = ['pk', 'name', 'parent', 'user_set', 'attributes']
|
fields = ["pk", "name", "parent", "user_set", "attributes"]
|
||||||
|
|
||||||
|
|
||||||
class GroupViewSet(ModelViewSet):
|
class GroupViewSet(ModelViewSet):
|
||||||
|
|||||||
@ -11,7 +11,13 @@ class InvitationSerializer(ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Invitation
|
model = Invitation
|
||||||
fields = ['pk', 'expires', 'fixed_username', 'fixed_email', 'needs_confirmation']
|
fields = [
|
||||||
|
"pk",
|
||||||
|
"expires",
|
||||||
|
"fixed_username",
|
||||||
|
"fixed_email",
|
||||||
|
"needs_confirmation",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class InvitationViewSet(ModelViewSet):
|
class InvitationViewSet(ModelViewSet):
|
||||||
|
|||||||
@ -9,16 +9,16 @@ from passbook.policies.forms import GENERAL_FIELDS
|
|||||||
class PolicySerializer(ModelSerializer):
|
class PolicySerializer(ModelSerializer):
|
||||||
"""Policy Serializer"""
|
"""Policy Serializer"""
|
||||||
|
|
||||||
__type__ = SerializerMethodField(method_name='get_type')
|
__type__ = SerializerMethodField(method_name="get_type")
|
||||||
|
|
||||||
def get_type(self, obj):
|
def get_type(self, obj):
|
||||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
"""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:
|
class Meta:
|
||||||
|
|
||||||
model = Policy
|
model = Policy
|
||||||
fields = ['pk'] + GENERAL_FIELDS + ['__type__']
|
fields = ["pk"] + GENERAL_FIELDS + ["__type__"]
|
||||||
|
|
||||||
|
|
||||||
class PolicyViewSet(ReadOnlyModelViewSet):
|
class PolicyViewSet(ReadOnlyModelViewSet):
|
||||||
|
|||||||
@ -8,16 +8,16 @@ from passbook.core.models import PropertyMapping
|
|||||||
class PropertyMappingSerializer(ModelSerializer):
|
class PropertyMappingSerializer(ModelSerializer):
|
||||||
"""PropertyMapping Serializer"""
|
"""PropertyMapping Serializer"""
|
||||||
|
|
||||||
__type__ = SerializerMethodField(method_name='get_type')
|
__type__ = SerializerMethodField(method_name="get_type")
|
||||||
|
|
||||||
def get_type(self, obj):
|
def get_type(self, obj):
|
||||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
"""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:
|
class Meta:
|
||||||
|
|
||||||
model = PropertyMapping
|
model = PropertyMapping
|
||||||
fields = ['pk', 'name', '__type__']
|
fields = ["pk", "name", "__type__"]
|
||||||
|
|
||||||
|
|
||||||
class PropertyMappingViewSet(ReadOnlyModelViewSet):
|
class PropertyMappingViewSet(ReadOnlyModelViewSet):
|
||||||
|
|||||||
@ -8,16 +8,16 @@ from passbook.core.models import Provider
|
|||||||
class ProviderSerializer(ModelSerializer):
|
class ProviderSerializer(ModelSerializer):
|
||||||
"""Provider Serializer"""
|
"""Provider Serializer"""
|
||||||
|
|
||||||
__type__ = SerializerMethodField(method_name='get_type')
|
__type__ = SerializerMethodField(method_name="get_type")
|
||||||
|
|
||||||
def get_type(self, obj):
|
def get_type(self, obj):
|
||||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
"""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:
|
class Meta:
|
||||||
|
|
||||||
model = Provider
|
model = Provider
|
||||||
fields = ['pk', 'property_mappings', '__type__']
|
fields = ["pk", "property_mappings", "__type__"]
|
||||||
|
|
||||||
|
|
||||||
class ProviderViewSet(ReadOnlyModelViewSet):
|
class ProviderViewSet(ReadOnlyModelViewSet):
|
||||||
|
|||||||
@ -9,16 +9,16 @@ from passbook.core.models import Source
|
|||||||
class SourceSerializer(ModelSerializer):
|
class SourceSerializer(ModelSerializer):
|
||||||
"""Source Serializer"""
|
"""Source Serializer"""
|
||||||
|
|
||||||
__type__ = SerializerMethodField(method_name='get_type')
|
__type__ = SerializerMethodField(method_name="get_type")
|
||||||
|
|
||||||
def get_type(self, obj):
|
def get_type(self, obj):
|
||||||
"""Get object type so that we know which API Endpoint to use to get the full object"""
|
"""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:
|
class Meta:
|
||||||
|
|
||||||
model = Source
|
model = Source
|
||||||
fields = SOURCE_SERIALIZER_FIELDS + ['__type__']
|
fields = SOURCE_SERIALIZER_FIELDS + ["__type__"]
|
||||||
|
|
||||||
|
|
||||||
class SourceViewSet(ReadOnlyModelViewSet):
|
class SourceViewSet(ReadOnlyModelViewSet):
|
||||||
|
|||||||
@ -11,7 +11,7 @@ class UserSerializer(ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
fields = ['pk', 'username', 'name', 'email']
|
fields = ["pk", "username", "name", "email"]
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(ModelViewSet):
|
class UserViewSet(ModelViewSet):
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from django.apps import AppConfig
|
|||||||
class PassbookCoreConfig(AppConfig):
|
class PassbookCoreConfig(AppConfig):
|
||||||
"""passbook core app config"""
|
"""passbook core app config"""
|
||||||
|
|
||||||
name = 'passbook.core'
|
name = "passbook.core"
|
||||||
label = 'passbook_core'
|
label = "passbook_core"
|
||||||
verbose_name = 'passbook Core'
|
verbose_name = "passbook Core"
|
||||||
mountpoint = ''
|
mountpoint = ""
|
||||||
|
|||||||
@ -9,21 +9,29 @@ from passbook.core.models import Application, Provider
|
|||||||
class ApplicationForm(forms.ModelForm):
|
class ApplicationForm(forms.ModelForm):
|
||||||
"""Application Form"""
|
"""Application Form"""
|
||||||
|
|
||||||
provider = forms.ModelChoiceField(queryset=Provider.objects.all().select_subclasses(),
|
provider = forms.ModelChoiceField(
|
||||||
required=False)
|
queryset=Provider.objects.all().select_subclasses(), required=False
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Application
|
model = Application
|
||||||
fields = ['name', 'slug', 'launch_url', 'icon_url',
|
fields = [
|
||||||
'provider', 'policies', 'skip_authorization']
|
"name",
|
||||||
|
"slug",
|
||||||
|
"launch_url",
|
||||||
|
"icon_url",
|
||||||
|
"provider",
|
||||||
|
"policies",
|
||||||
|
"skip_authorization",
|
||||||
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'name': forms.TextInput(),
|
"name": forms.TextInput(),
|
||||||
'launch_url': forms.TextInput(),
|
"launch_url": forms.TextInput(),
|
||||||
'icon_url': forms.TextInput(),
|
"icon_url": forms.TextInput(),
|
||||||
'policies': FilteredSelectMultiple(_('policies'), False)
|
"policies": FilteredSelectMultiple(_("policies"), False),
|
||||||
}
|
}
|
||||||
labels = {
|
labels = {
|
||||||
'launch_url': _('Launch URL'),
|
"launch_url": _("Launch URL"),
|
||||||
'icon_url': _('Icon URL'),
|
"icon_url": _("Icon URL"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,55 +11,64 @@ from passbook.lib.utils.ui import human_list
|
|||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
class LoginForm(forms.Form):
|
class LoginForm(forms.Form):
|
||||||
"""Allow users to login"""
|
"""Allow users to login"""
|
||||||
|
|
||||||
title = _('Log in to your account')
|
title = _("Log in to your account")
|
||||||
uid_field = forms.CharField()
|
uid_field = forms.CharField()
|
||||||
remember_me = forms.BooleanField(required=False)
|
remember_me = forms.BooleanField(required=False)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if CONFIG.y('passbook.uid_fields') == ['e-mail']:
|
if CONFIG.y("passbook.uid_fields") == ["e-mail"]:
|
||||||
self.fields['uid_field'] = forms.EmailField()
|
self.fields["uid_field"] = forms.EmailField()
|
||||||
self.fields['uid_field'].widget.attrs = {
|
self.fields["uid_field"].widget.attrs = {
|
||||||
'placeholder': _(human_list([x.title() for x in CONFIG.y('passbook.uid_fields')]))
|
"placeholder": _(
|
||||||
|
human_list([x.title() for x in CONFIG.y("passbook.uid_fields")])
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def clean_uid_field(self):
|
def clean_uid_field(self):
|
||||||
"""Validate uid_field after EmailValidator if 'email' is the only selected uid_fields"""
|
"""Validate uid_field after EmailValidator if 'email' is the only selected uid_fields"""
|
||||||
if CONFIG.y('passbook.uid_fields') == ['email']:
|
if CONFIG.y("passbook.uid_fields") == ["email"]:
|
||||||
validate_email(self.cleaned_data.get('uid_field'))
|
validate_email(self.cleaned_data.get("uid_field"))
|
||||||
return self.cleaned_data.get('uid_field')
|
return self.cleaned_data.get("uid_field")
|
||||||
|
|
||||||
|
|
||||||
class SignUpForm(forms.Form):
|
class SignUpForm(forms.Form):
|
||||||
"""SignUp Form"""
|
"""SignUp Form"""
|
||||||
|
|
||||||
title = _('Sign Up')
|
title = _("Sign Up")
|
||||||
name = forms.CharField(label=_('Name'),
|
name = forms.CharField(
|
||||||
widget=forms.TextInput(attrs={'placeholder': _('Name')}))
|
label=_("Name"), widget=forms.TextInput(attrs={"placeholder": _("Name")})
|
||||||
username = forms.CharField(label=_('Username'),
|
)
|
||||||
widget=forms.TextInput(attrs={'placeholder': _('Username')}))
|
username = forms.CharField(
|
||||||
email = forms.EmailField(label=_('E-Mail'),
|
label=_("Username"),
|
||||||
widget=forms.TextInput(attrs={'placeholder': _('E-Mail')}))
|
widget=forms.TextInput(attrs={"placeholder": _("Username")}),
|
||||||
password = forms.CharField(label=_('Password'),
|
)
|
||||||
widget=forms.PasswordInput(attrs={'placeholder': _('Password')}))
|
email = forms.EmailField(
|
||||||
password_repeat = forms.CharField(label=_('Repeat Password'),
|
label=_("E-Mail"), widget=forms.TextInput(attrs={"placeholder": _("E-Mail")})
|
||||||
widget=forms.PasswordInput(attrs={
|
)
|
||||||
'placeholder': _('Repeat Password')
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# All fields which have initial data supplied are set to read only
|
# All fields which have initial data supplied are set to read only
|
||||||
if 'initial' in kwargs:
|
if "initial" in kwargs:
|
||||||
for field in kwargs.get('initial').keys():
|
for field in kwargs.get("initial").keys():
|
||||||
self.fields[field].widget.attrs['readonly'] = 'readonly'
|
self.fields[field].widget.attrs["readonly"] = "readonly"
|
||||||
|
|
||||||
def clean_username(self):
|
def clean_username(self):
|
||||||
"""Check if username is used already"""
|
"""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():
|
if User.objects.filter(username=username).exists():
|
||||||
LOGGER.warning("Username %s already exists", username)
|
LOGGER.warning("Username %s already exists", username)
|
||||||
raise ValidationError(_("Username already exists"))
|
raise ValidationError(_("Username already exists"))
|
||||||
@ -67,7 +76,7 @@ class SignUpForm(forms.Form):
|
|||||||
|
|
||||||
def clean_email(self):
|
def clean_email(self):
|
||||||
"""Check if email is already used in django or other auth sources"""
|
"""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
|
# Check if user exists already, error early
|
||||||
if User.objects.filter(email=email).exists():
|
if User.objects.filter(email=email).exists():
|
||||||
LOGGER.debug("email %s exists in django", email)
|
LOGGER.debug("email %s exists in django", email)
|
||||||
@ -76,8 +85,8 @@ class SignUpForm(forms.Form):
|
|||||||
|
|
||||||
def clean_password_repeat(self):
|
def clean_password_repeat(self):
|
||||||
"""Check if Password adheres to filter and if passwords matche"""
|
"""Check if Password adheres to filter and if passwords matche"""
|
||||||
password = self.cleaned_data.get('password')
|
password = self.cleaned_data.get("password")
|
||||||
password_repeat = self.cleaned_data.get('password_repeat')
|
password_repeat = self.cleaned_data.get("password_repeat")
|
||||||
if password != password_repeat:
|
if password != password_repeat:
|
||||||
raise ValidationError(_("Passwords don't match"))
|
raise ValidationError(_("Passwords don't match"))
|
||||||
return self.cleaned_data.get('password_repeat')
|
return self.cleaned_data.get("password_repeat")
|
||||||
|
|||||||
@ -9,24 +9,29 @@ class GroupForm(forms.ModelForm):
|
|||||||
"""Group Form"""
|
"""Group Form"""
|
||||||
|
|
||||||
members = forms.ModelMultipleChoiceField(
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.instance.pk:
|
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):
|
def save(self, *args, **kwargs):
|
||||||
instance = super().save(*args, **kwargs)
|
instance = super().save(*args, **kwargs)
|
||||||
if instance.pk:
|
if instance.pk:
|
||||||
instance.user_set.clear()
|
instance.user_set.clear()
|
||||||
instance.user_set.add(*self.cleaned_data['members'])
|
instance.user_set.add(*self.cleaned_data["members"])
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Group
|
model = Group
|
||||||
fields = ['name', 'parent', 'members', 'attributes']
|
fields = ["name", "parent", "members", "attributes"]
|
||||||
widgets = {
|
widgets = {
|
||||||
'name': forms.TextInput(),
|
"name": forms.TextInput(),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,27 +12,27 @@ class InvitationForm(forms.ModelForm):
|
|||||||
|
|
||||||
def clean_fixed_username(self):
|
def clean_fixed_username(self):
|
||||||
"""Check if username is already used"""
|
"""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():
|
if User.objects.filter(username=username).exists():
|
||||||
raise ValidationError(_('Username is already in use.'))
|
raise ValidationError(_("Username is already in use."))
|
||||||
return username
|
return username
|
||||||
|
|
||||||
def clean_fixed_email(self):
|
def clean_fixed_email(self):
|
||||||
"""Check if email is already used"""
|
"""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():
|
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
|
return email
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = Invitation
|
model = Invitation
|
||||||
fields = ['expires', 'fixed_username', 'fixed_email', 'needs_confirmation']
|
fields = ["expires", "fixed_username", "fixed_email", "needs_confirmation"]
|
||||||
labels = {
|
labels = {
|
||||||
'fixed_username': "Force user's username (optional)",
|
"fixed_username": "Force user's username (optional)",
|
||||||
'fixed_email': "Force user's email (optional)",
|
"fixed_email": "Force user's email (optional)",
|
||||||
}
|
}
|
||||||
widgets = {
|
widgets = {
|
||||||
'fixed_username': forms.TextInput(),
|
"fixed_username": forms.TextInput(),
|
||||||
'fixed_email': forms.TextInput(),
|
"fixed_email": forms.TextInput(),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,10 +13,8 @@ class DebugPolicyForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = DebugPolicy
|
model = DebugPolicy
|
||||||
fields = GENERAL_FIELDS + ['result', 'wait_min', 'wait_max']
|
fields = GENERAL_FIELDS + ["result", "wait_min", "wait_max"]
|
||||||
widgets = {
|
widgets = {
|
||||||
'name': forms.TextInput(),
|
"name": forms.TextInput(),
|
||||||
}
|
|
||||||
labels = {
|
|
||||||
'result': _('Allow user')
|
|
||||||
}
|
}
|
||||||
|
labels = {"result": _("Allow user")}
|
||||||
|
|||||||
@ -13,29 +13,30 @@ class UserDetailForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
model = User
|
model = User
|
||||||
fields = ['username', 'name', 'email']
|
fields = ["username", "name", "email"]
|
||||||
widgets = {
|
widgets = {"name": forms.TextInput}
|
||||||
'name': forms.TextInput
|
|
||||||
}
|
|
||||||
|
|
||||||
class PasswordChangeForm(forms.Form):
|
class PasswordChangeForm(forms.Form):
|
||||||
"""Form to update password"""
|
"""Form to update password"""
|
||||||
|
|
||||||
password = forms.CharField(label=_('Password'),
|
password = forms.CharField(
|
||||||
widget=forms.PasswordInput(attrs={
|
label=_("Password"),
|
||||||
'placeholder': _('New Password'),
|
widget=forms.PasswordInput(
|
||||||
'autocomplete': 'new-password'
|
attrs={"placeholder": _("New Password"), "autocomplete": "new-password"}
|
||||||
}))
|
),
|
||||||
password_repeat = forms.CharField(label=_('Repeat Password'),
|
)
|
||||||
widget=forms.PasswordInput(attrs={
|
password_repeat = forms.CharField(
|
||||||
'placeholder': _('Repeat Password'),
|
label=_("Repeat Password"),
|
||||||
'autocomplete': 'new-password'
|
widget=forms.PasswordInput(
|
||||||
}))
|
attrs={"placeholder": _("Repeat Password"), "autocomplete": "new-password"}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def clean_password_repeat(self):
|
def clean_password_repeat(self):
|
||||||
"""Check if Password adheres to filter and if passwords matche"""
|
"""Check if Password adheres to filter and if passwords matche"""
|
||||||
password = self.cleaned_data.get('password')
|
password = self.cleaned_data.get("password")
|
||||||
password_repeat = self.cleaned_data.get('password_repeat')
|
password_repeat = self.cleaned_data.get("password_repeat")
|
||||||
if password != password_repeat:
|
if password != password_repeat:
|
||||||
raise ValidationError(_("Passwords don't match"))
|
raise ValidationError(_("Passwords don't match"))
|
||||||
return self.cleaned_data.get('password_repeat')
|
return self.cleaned_data.get("password_repeat")
|
||||||
|
|||||||
@ -18,206 +18,433 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('auth', '0011_update_proxy_permissions'),
|
("auth", "0011_update_proxy_permissions"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='User',
|
name="User",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
"id",
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
models.AutoField(
|
||||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
auto_created=True,
|
||||||
('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')),
|
primary_key=True,
|
||||||
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
|
serialize=False,
|
||||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
verbose_name="ID",
|
||||||
('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')),
|
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
(
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
|
"last_login",
|
||||||
('name', models.TextField()),
|
models.DateTimeField(
|
||||||
('password_change_date', models.DateTimeField(auto_now_add=True)),
|
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={
|
options={
|
||||||
'verbose_name': 'user',
|
"verbose_name": "user",
|
||||||
'verbose_name_plural': 'users',
|
"verbose_name_plural": "users",
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
managers=[
|
managers=[("objects", django.contrib.auth.models.UserManager()),],
|
||||||
('objects', django.contrib.auth.models.UserManager()),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Policy',
|
name="Policy",
|
||||||
fields=[
|
fields=[
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
("last_updated", models.DateTimeField(auto_now=True)),
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('name', models.TextField(blank=True, null=True)),
|
"uuid",
|
||||||
('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)),
|
models.UUIDField(
|
||||||
('negate', models.BooleanField(default=False)),
|
default=uuid.uuid4,
|
||||||
('order', models.IntegerField(default=0)),
|
editable=False,
|
||||||
('timeout', models.IntegerField(default=30)),
|
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={
|
options={
|
||||||
'abstract': False,
|
"verbose_name": "Property Mapping",
|
||||||
|
"verbose_name_plural": "Property Mappings",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='PolicyModel',
|
name="DebugPolicy",
|
||||||
fields=[
|
fields=[
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
(
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
"policy_ptr",
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
models.OneToOneField(
|
||||||
('policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')),
|
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={
|
options={
|
||||||
'abstract': False,
|
"verbose_name": "Debug Policy",
|
||||||
|
"verbose_name_plural": "Debug Policies",
|
||||||
},
|
},
|
||||||
|
bases=("passbook_core.policy",),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='PropertyMapping',
|
name="Factor",
|
||||||
fields=[
|
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={
|
options={"abstract": False,},
|
||||||
'verbose_name': 'Property Mapping',
|
bases=("passbook_core.policymodel",),
|
||||||
'verbose_name_plural': 'Property Mappings',
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DebugPolicy',
|
name="Source",
|
||||||
fields=[
|
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)),
|
"policymodel_ptr",
|
||||||
('wait_min', models.IntegerField(default=5)),
|
models.OneToOneField(
|
||||||
('wait_max', models.IntegerField(default=30)),
|
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={
|
options={"abstract": False,},
|
||||||
'verbose_name': 'Debug Policy',
|
bases=("passbook_core.policymodel",),
|
||||||
'verbose_name_plural': 'Debug Policies',
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policy',),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Factor',
|
name="Provider",
|
||||||
fields=[
|
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()),
|
"id",
|
||||||
('slug', models.SlugField(unique=True)),
|
models.AutoField(
|
||||||
('order', models.IntegerField()),
|
auto_created=True,
|
||||||
('enabled', models.BooleanField(default=True)),
|
primary_key=True,
|
||||||
],
|
serialize=False,
|
||||||
options={
|
verbose_name="ID",
|
||||||
'abstract': False,
|
),
|
||||||
},
|
),
|
||||||
bases=('passbook_core.policymodel',),
|
(
|
||||||
|
"property_mappings",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True, default=None, to="passbook_core.PropertyMapping"
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='Source',
|
|
||||||
fields=[
|
|
||||||
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
|
|
||||||
('name', models.TextField()),
|
|
||||||
('slug', models.SlugField()),
|
|
||||||
('enabled', models.BooleanField(default=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=('passbook_core.policymodel',),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='Provider',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('property_mappings', models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping')),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Nonce',
|
name="Nonce",
|
||||||
fields=[
|
fields=[
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)),
|
"uuid",
|
||||||
('expiring', models.BooleanField(default=True)),
|
models.UUIDField(
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
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={
|
options={
|
||||||
'verbose_name': 'Nonce',
|
"verbose_name": "Invitation",
|
||||||
'verbose_name_plural': 'Nonces',
|
"verbose_name_plural": "Invitations",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Invitation',
|
name="Group",
|
||||||
fields=[
|
fields=[
|
||||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
(
|
||||||
('expires', models.DateTimeField(blank=True, default=None, null=True)),
|
"uuid",
|
||||||
('fixed_username', models.TextField(blank=True, default=None)),
|
models.UUIDField(
|
||||||
('fixed_email', models.TextField(blank=True, default=None)),
|
default=uuid.uuid4,
|
||||||
('needs_confirmation', models.BooleanField(default=True)),
|
editable=False,
|
||||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
primary_key=True,
|
||||||
],
|
serialize=False,
|
||||||
options={
|
),
|
||||||
'verbose_name': 'Invitation',
|
),
|
||||||
'verbose_name_plural': 'Invitations',
|
("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",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
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={
|
options={"unique_together": {("name", "parent")},},
|
||||||
'unique_together': {('name', 'parent')},
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='user',
|
model_name="user",
|
||||||
name='groups',
|
name="groups",
|
||||||
field=models.ManyToManyField(to='passbook_core.Group'),
|
field=models.ManyToManyField(to="passbook_core.Group"),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='user',
|
model_name="user",
|
||||||
name='user_permissions',
|
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'),
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Specific permissions for this user.",
|
||||||
|
related_name="user_set",
|
||||||
|
related_query_name="user",
|
||||||
|
to="auth.Permission",
|
||||||
|
verbose_name="user permissions",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='UserSourceConnection',
|
name="UserSourceConnection",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
"id",
|
||||||
('last_updated', models.DateTimeField(auto_now=True)),
|
models.AutoField(
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
auto_created=True,
|
||||||
('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source')),
|
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={
|
options={"unique_together": {("user", "source")},},
|
||||||
'unique_together': {('user', 'source')},
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Application',
|
name="Application",
|
||||||
fields=[
|
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()),
|
"policymodel_ptr",
|
||||||
('slug', models.SlugField()),
|
models.OneToOneField(
|
||||||
('launch_url', models.URLField(blank=True, null=True)),
|
auto_created=True,
|
||||||
('icon_url', models.TextField(blank=True, null=True)),
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
('skip_authorization', models.BooleanField(default=False)),
|
parent_link=True,
|
||||||
('provider', models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='passbook_core.Provider')),
|
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={
|
options={"abstract": False,},
|
||||||
'abstract': False,
|
bases=("passbook_core.policymodel",),
|
||||||
},
|
|
||||||
bases=('passbook_core.policymodel',),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='user',
|
model_name="user",
|
||||||
name='sources',
|
name="sources",
|
||||||
field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'),
|
field=models.ManyToManyField(
|
||||||
|
through="passbook_core.UserSourceConnection", to="passbook_core.Source"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -6,12 +6,12 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_core', '0001_initial'),
|
("passbook_core", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='user',
|
name="user",
|
||||||
options={'permissions': (('reset_user_password', 'Reset Password'),)},
|
options={"permissions": (("reset_user_password", "Reset Password"),)},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -6,13 +6,13 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_core', '0001_initial'),
|
("passbook_core", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='nonce',
|
model_name="nonce",
|
||||||
name='description',
|
name="description",
|
||||||
field=models.TextField(blank=True, default=''),
|
field=models.TextField(blank=True, default=""),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -7,23 +7,25 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_core', '0002_nonce_description'),
|
("passbook_core", "0002_nonce_description"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RenameField(
|
migrations.RenameField(
|
||||||
model_name='group',
|
model_name="group", old_name="tags", new_name="attributes",
|
||||||
old_name='tags',
|
|
||||||
new_name='attributes',
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='source',
|
model_name="source",
|
||||||
name='property_mappings',
|
name="property_mappings",
|
||||||
field=models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping'),
|
field=models.ManyToManyField(
|
||||||
|
blank=True, default=None, to="passbook_core.PropertyMapping"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='user',
|
model_name="user",
|
||||||
name='attributes',
|
name="attributes",
|
||||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
|
field=django.contrib.postgres.fields.jsonb.JSONField(
|
||||||
|
blank=True, default=dict
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -6,9 +6,8 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_core', '0002_auto_20191010_1058'),
|
("passbook_core", "0002_auto_20191010_1058"),
|
||||||
('passbook_core', '0002_nonce_description'),
|
("passbook_core", "0002_nonce_description"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = []
|
||||||
]
|
|
||||||
|
|||||||
@ -6,12 +6,9 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_core', '0003_auto_20191011_0914'),
|
("passbook_core", "0003_auto_20191011_0914"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(model_name="policy", name="action",),
|
||||||
model_name='policy',
|
|
||||||
name='action',
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -6,9 +6,8 @@ from django.db import migrations
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('passbook_core', '0004_remove_policy_action'),
|
("passbook_core", "0004_remove_policy_action"),
|
||||||
('passbook_core', '0003_merge_20191010_1541'),
|
("passbook_core", "0003_merge_20191010_1541"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = []
|
||||||
]
|
|
||||||
|
|||||||
@ -27,12 +27,18 @@ def default_nonce_duration():
|
|||||||
"""Default duration a Nonce is valid"""
|
"""Default duration a Nonce is valid"""
|
||||||
return now() + timedelta(hours=4)
|
return now() + timedelta(hours=4)
|
||||||
|
|
||||||
|
|
||||||
class Group(UUIDModel):
|
class Group(UUIDModel):
|
||||||
"""Custom Group model which supports a basic hierarchy"""
|
"""Custom Group model which supports a basic hierarchy"""
|
||||||
|
|
||||||
name = models.CharField(_('name'), max_length=80)
|
name = models.CharField(_("name"), max_length=80)
|
||||||
parent = models.ForeignKey('Group', blank=True, null=True,
|
parent = models.ForeignKey(
|
||||||
on_delete=models.SET_NULL, related_name='children')
|
"Group",
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name="children",
|
||||||
|
)
|
||||||
attributes = JSONField(default=dict, blank=True)
|
attributes = JSONField(default=dict, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -40,7 +46,8 @@ class Group(UUIDModel):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
unique_together = (('name', 'parent',),)
|
unique_together = (("name", "parent",),)
|
||||||
|
|
||||||
|
|
||||||
class User(GuardianUserMixin, AbstractUser):
|
class User(GuardianUserMixin, AbstractUser):
|
||||||
"""Custom User model to allow easier adding o f user-based settings"""
|
"""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)
|
uuid = models.UUIDField(default=uuid4, editable=False)
|
||||||
name = models.TextField()
|
name = models.TextField()
|
||||||
|
|
||||||
sources = models.ManyToManyField('Source', through='UserSourceConnection')
|
sources = models.ManyToManyField("Source", through="UserSourceConnection")
|
||||||
groups = models.ManyToManyField('Group')
|
groups = models.ManyToManyField("Group")
|
||||||
password_change_date = models.DateTimeField(auto_now_add=True)
|
password_change_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
attributes = JSONField(default=dict, blank=True)
|
attributes = JSONField(default=dict, blank=True)
|
||||||
@ -62,28 +69,29 @@ class User(GuardianUserMixin, AbstractUser):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
permissions = (
|
permissions = (("reset_user_password", "Reset Password"),)
|
||||||
('reset_user_password', 'Reset Password'),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Provider(models.Model):
|
class Provider(models.Model):
|
||||||
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
|
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
|
||||||
|
|
||||||
property_mappings = models.ManyToManyField('PropertyMapping', default=None, blank=True)
|
property_mappings = models.ManyToManyField(
|
||||||
|
"PropertyMapping", default=None, blank=True
|
||||||
|
)
|
||||||
|
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
# This class defines no field for easier inheritance
|
# This class defines no field for easier inheritance
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if hasattr(self, 'name'):
|
if hasattr(self, "name"):
|
||||||
return getattr(self, 'name')
|
return getattr(self, "name")
|
||||||
return super().__str__()
|
return super().__str__()
|
||||||
|
|
||||||
|
|
||||||
class PolicyModel(UUIDModel, CreatedUpdatedModel):
|
class PolicyModel(UUIDModel, CreatedUpdatedModel):
|
||||||
"""Base model which can have policies applied to it"""
|
"""Base model which can have policies applied to it"""
|
||||||
|
|
||||||
policies = models.ManyToManyField('Policy', blank=True)
|
policies = models.ManyToManyField("Policy", blank=True)
|
||||||
|
|
||||||
|
|
||||||
class UserSettings:
|
class UserSettings:
|
||||||
@ -108,8 +116,8 @@ class Factor(PolicyModel):
|
|||||||
enabled = models.BooleanField(default=True)
|
enabled = models.BooleanField(default=True)
|
||||||
|
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
type = ''
|
type = ""
|
||||||
form = ''
|
form = ""
|
||||||
|
|
||||||
def user_settings(self) -> Optional[UserSettings]:
|
def user_settings(self) -> Optional[UserSettings]:
|
||||||
"""Entrypoint to integrate with User settings. Can either return None if no
|
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||||
@ -129,8 +137,9 @@ class Application(PolicyModel):
|
|||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
launch_url = models.URLField(null=True, blank=True)
|
launch_url = models.URLField(null=True, blank=True)
|
||||||
icon_url = models.TextField(null=True, blank=True)
|
icon_url = models.TextField(null=True, blank=True)
|
||||||
provider = models.OneToOneField('Provider', null=True, blank=True,
|
provider = models.OneToOneField(
|
||||||
default=None, on_delete=models.SET_DEFAULT)
|
"Provider", null=True, blank=True, default=None, on_delete=models.SET_DEFAULT
|
||||||
|
)
|
||||||
skip_authorization = models.BooleanField(default=False)
|
skip_authorization = models.BooleanField(default=False)
|
||||||
|
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
@ -151,9 +160,11 @@ class Source(PolicyModel):
|
|||||||
name = models.TextField()
|
name = models.TextField()
|
||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
enabled = models.BooleanField(default=True)
|
enabled = models.BooleanField(default=True)
|
||||||
property_mappings = models.ManyToManyField('PropertyMapping', default=None, blank=True)
|
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()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
@ -185,7 +196,7 @@ class UserSourceConnection(CreatedUpdatedModel):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
unique_together = (('user', 'source'),)
|
unique_together = (("user", "source"),)
|
||||||
|
|
||||||
|
|
||||||
class Policy(UUIDModel, CreatedUpdatedModel):
|
class Policy(UUIDModel, CreatedUpdatedModel):
|
||||||
@ -215,25 +226,25 @@ class DebugPolicy(Policy):
|
|||||||
wait_min = models.IntegerField(default=5)
|
wait_min = models.IntegerField(default=5)
|
||||||
wait_max = models.IntegerField(default=30)
|
wait_max = models.IntegerField(default=30)
|
||||||
|
|
||||||
form = 'passbook.core.forms.policies.DebugPolicyForm'
|
form = "passbook.core.forms.policies.DebugPolicyForm"
|
||||||
|
|
||||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||||
"""Wait random time then return result"""
|
"""Wait random time then return result"""
|
||||||
wait = SystemRandom().randrange(self.wait_min, self.wait_max)
|
wait = SystemRandom().randrange(self.wait_min, self.wait_max)
|
||||||
LOGGER.debug("Policy waiting", policy=self, delay=wait)
|
LOGGER.debug("Policy waiting", policy=self, delay=wait)
|
||||||
sleep(wait)
|
sleep(wait)
|
||||||
return PolicyResult(self.result, 'Debugging')
|
return PolicyResult(self.result, "Debugging")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
verbose_name = _('Debug Policy')
|
verbose_name = _("Debug Policy")
|
||||||
verbose_name_plural = _('Debug Policies')
|
verbose_name_plural = _("Debug Policies")
|
||||||
|
|
||||||
|
|
||||||
class Invitation(UUIDModel):
|
class Invitation(UUIDModel):
|
||||||
"""Single-use invitation link"""
|
"""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)
|
expires = models.DateTimeField(default=None, blank=True, null=True)
|
||||||
fixed_username = models.TextField(blank=True, default=None)
|
fixed_username = models.TextField(blank=True, default=None)
|
||||||
fixed_email = models.TextField(blank=True, default=None)
|
fixed_email = models.TextField(blank=True, default=None)
|
||||||
@ -242,24 +253,26 @@ class Invitation(UUIDModel):
|
|||||||
@property
|
@property
|
||||||
def link(self):
|
def link(self):
|
||||||
"""Get link to use invitation"""
|
"""Get link to use invitation"""
|
||||||
return reverse_lazy('passbook_core:auth-sign-up') + f'?invitation={self.uuid.hex}'
|
return (
|
||||||
|
reverse_lazy("passbook_core:auth-sign-up") + f"?invitation={self.uuid.hex}"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Invitation {self.uuid.hex} created by {self.created_by}"
|
return f"Invitation {self.uuid.hex} created by {self.created_by}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
verbose_name = _('Invitation')
|
verbose_name = _("Invitation")
|
||||||
verbose_name_plural = _('Invitations')
|
verbose_name_plural = _("Invitations")
|
||||||
|
|
||||||
|
|
||||||
class Nonce(UUIDModel):
|
class Nonce(UUIDModel):
|
||||||
"""One-time link for password resets/sign-up-confirmations"""
|
"""One-time link for password resets/sign-up-confirmations"""
|
||||||
|
|
||||||
expires = models.DateTimeField(default=default_nonce_duration)
|
expires = models.DateTimeField(default=default_nonce_duration)
|
||||||
user = models.ForeignKey('User', on_delete=models.CASCADE)
|
user = models.ForeignKey("User", on_delete=models.CASCADE)
|
||||||
expiring = models.BooleanField(default=True)
|
expiring = models.BooleanField(default=True)
|
||||||
description = models.TextField(default='', blank=True)
|
description = models.TextField(default="", blank=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_expired(self) -> bool:
|
def is_expired(self) -> bool:
|
||||||
@ -271,8 +284,8 @@ class Nonce(UUIDModel):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
verbose_name = _('Nonce')
|
verbose_name = _("Nonce")
|
||||||
verbose_name_plural = _('Nonces')
|
verbose_name_plural = _("Nonces")
|
||||||
|
|
||||||
|
|
||||||
class PropertyMapping(UUIDModel):
|
class PropertyMapping(UUIDModel):
|
||||||
@ -280,7 +293,7 @@ class PropertyMapping(UUIDModel):
|
|||||||
|
|
||||||
name = models.TextField()
|
name = models.TextField()
|
||||||
|
|
||||||
form = ''
|
form = ""
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -288,5 +301,5 @@ class PropertyMapping(UUIDModel):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
verbose_name = _('Property Mapping')
|
verbose_name = _("Property Mapping")
|
||||||
verbose_name_plural = _('Property Mappings')
|
verbose_name_plural = _("Property Mappings")
|
||||||
|
|||||||
@ -7,16 +7,18 @@ from structlog import get_logger
|
|||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
user_signed_up = Signal(providing_args=['request', 'user'])
|
user_signed_up = Signal(providing_args=["request", "user"])
|
||||||
invitation_created = Signal(providing_args=['request', 'invitation'])
|
invitation_created = Signal(providing_args=["request", "invitation"])
|
||||||
invitation_used = Signal(providing_args=['request', 'invitation', 'user'])
|
invitation_used = Signal(providing_args=["request", "invitation", "user"])
|
||||||
password_changed = Signal(providing_args=['user', 'password'])
|
password_changed = Signal(providing_args=["user", "password"])
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save)
|
@receiver(post_save)
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def invalidate_policy_cache(sender, instance, **_):
|
def invalidate_policy_cache(sender, instance, **_):
|
||||||
"""Invalidate Policy cache when policy is updated"""
|
"""Invalidate Policy cache when policy is updated"""
|
||||||
from passbook.core.models import Policy
|
from passbook.core.models import Policy
|
||||||
|
|
||||||
if isinstance(instance, Policy):
|
if isinstance(instance, Policy):
|
||||||
LOGGER.debug("Invalidating policy cache", policy=instance)
|
LOGGER.debug("Invalidating policy cache", policy=instance)
|
||||||
keys = cache.keys("%s#*" % instance.pk)
|
keys = cache.keys("%s#*" % instance.pk)
|
||||||
|
|||||||
@ -7,8 +7,9 @@ from passbook.root.celery import CELERY_APP
|
|||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
@CELERY_APP.task()
|
@CELERY_APP.task()
|
||||||
def clean_nonces():
|
def clean_nonces():
|
||||||
"""Remove expired nonces"""
|
"""Remove expired nonces"""
|
||||||
amount, _ = Nonce.objects.filter(expires__lt=now(), expiring=True).delete()
|
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 = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def user_factors(context: RequestContext) -> List[UserSettings]:
|
def user_factors(context: RequestContext) -> List[UserSettings]:
|
||||||
"""Return list of all factors which apply to user"""
|
"""Return list of all factors which apply to user"""
|
||||||
user = context.get('request').user
|
user = context.get("request").user
|
||||||
_all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses()
|
_all_factors = (
|
||||||
|
Factor.objects.filter(enabled=True).order_by("order").select_subclasses()
|
||||||
|
)
|
||||||
matching_factors: List[UserSettings] = []
|
matching_factors: List[UserSettings] = []
|
||||||
for factor in _all_factors:
|
for factor in _all_factors:
|
||||||
user_settings = factor.user_settings()
|
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()
|
policy_engine.build()
|
||||||
if policy_engine.passing and user_settings:
|
if policy_engine.passing and user_settings:
|
||||||
matching_factors.append(user_settings)
|
matching_factors.append(user_settings)
|
||||||
return matching_factors
|
return matching_factors
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def user_sources(context: RequestContext) -> List[UserSettings]:
|
def user_sources(context: RequestContext) -> List[UserSettings]:
|
||||||
"""Return a list of all sources which are enabled for the user"""
|
"""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()
|
_all_sources = Source.objects.filter(enabled=True).select_subclasses()
|
||||||
matching_sources: List[UserSettings] = []
|
matching_sources: List[UserSettings] = []
|
||||||
for factor in _all_sources:
|
for factor in _all_sources:
|
||||||
user_settings = factor.user_settings()
|
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()
|
policy_engine.build()
|
||||||
if policy_engine.passing and user_settings:
|
if policy_engine.passing and user_settings:
|
||||||
matching_sources.append(user_settings)
|
matching_sources.append(user_settings)
|
||||||
|
|||||||
@ -15,70 +15,78 @@ class TestAuthenticationViews(TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.sign_up_data = {
|
self.sign_up_data = {
|
||||||
'name': 'Test',
|
"name": "Test",
|
||||||
'username': 'beryjuorg',
|
"username": "beryjuorg",
|
||||||
'email': 'unittest@passbook.beryju.org',
|
"email": "unittest@passbook.beryju.org",
|
||||||
'password': 'B3ryju0rg!',
|
"password": "B3ryju0rg!",
|
||||||
'password_repeat': 'B3ryju0rg!',
|
"password_repeat": "B3ryju0rg!",
|
||||||
}
|
}
|
||||||
self.login_data = {
|
self.login_data = {
|
||||||
'uid_field': 'unittest@example.com',
|
"uid_field": "unittest@example.com",
|
||||||
}
|
}
|
||||||
self.user = User.objects.create_superuser(
|
self.user = User.objects.create_superuser(
|
||||||
username='unittest user',
|
username="unittest user",
|
||||||
email='unittest@example.com',
|
email="unittest@example.com",
|
||||||
password=''.join(SystemRandom().choice(
|
password="".join(
|
||||||
string.ascii_uppercase + string.digits) for _ in range(8)))
|
SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||||
|
for _ in range(8)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def test_sign_up_view(self):
|
def test_sign_up_view(self):
|
||||||
"""Test account.sign_up view (Anonymous)"""
|
"""Test account.sign_up view (Anonymous)"""
|
||||||
self.client.logout()
|
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)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_login_view(self):
|
def test_login_view(self):
|
||||||
"""Test account.login view (Anonymous)"""
|
"""Test account.login view (Anonymous)"""
|
||||||
self.client.logout()
|
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)
|
self.assertEqual(response.status_code, 200)
|
||||||
# test login with post
|
# test login with post
|
||||||
form = LoginForm(self.login_data)
|
form = LoginForm(self.login_data)
|
||||||
self.assertTrue(form.is_valid())
|
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)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
def test_logout_view(self):
|
def test_logout_view(self):
|
||||||
"""Test account.logout view"""
|
"""Test account.logout view"""
|
||||||
self.client.force_login(self.user)
|
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)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
def test_sign_up_view_auth(self):
|
def test_sign_up_view_auth(self):
|
||||||
"""Test account.sign_up view (Authenticated)"""
|
"""Test account.sign_up view (Authenticated)"""
|
||||||
self.client.force_login(self.user)
|
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)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
def test_login_view_auth(self):
|
def test_login_view_auth(self):
|
||||||
"""Test account.login view (Authenticated)"""
|
"""Test account.login view (Authenticated)"""
|
||||||
self.client.force_login(self.user)
|
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)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
def test_login_view_post(self):
|
def test_login_view_post(self):
|
||||||
"""Test account.login view POST (Anonymous)"""
|
"""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.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):
|
def test_sign_up_view_post(self):
|
||||||
"""Test account.sign_up view POST (Anonymous)"""
|
"""Test account.sign_up view POST (Anonymous)"""
|
||||||
form = SignUpForm(self.sign_up_data)
|
form = SignUpForm(self.sign_up_data)
|
||||||
self.assertTrue(form.is_valid())
|
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)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
# def test_reset_password_init_view(self):
|
# def test_reset_password_init_view(self):
|
||||||
|
|||||||
@ -14,12 +14,17 @@ class TestOverviewViews(TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.user = User.objects.create_superuser(
|
self.user = User.objects.create_superuser(
|
||||||
username='unittest user',
|
username="unittest user",
|
||||||
email='unittest@example.com',
|
email="unittest@example.com",
|
||||||
password=''.join(SystemRandom().choice(
|
password="".join(
|
||||||
string.ascii_uppercase + string.digits) for _ in range(8)))
|
SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||||
|
for _ in range(8)
|
||||||
|
),
|
||||||
|
)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
def test_overview(self):
|
def test_overview(self):
|
||||||
"""Test UserSettingsView"""
|
"""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):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.user = User.objects.create_superuser(
|
self.user = User.objects.create_superuser(
|
||||||
username='unittest user',
|
username="unittest user",
|
||||||
email='unittest@example.com',
|
email="unittest@example.com",
|
||||||
password=''.join(SystemRandom().choice(
|
password="".join(
|
||||||
string.ascii_uppercase + string.digits) for _ in range(8)))
|
SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||||
|
for _ in range(8)
|
||||||
|
),
|
||||||
|
)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
def test_user_settings(self):
|
def test_user_settings(self):
|
||||||
"""Test UserSettingsView"""
|
"""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):
|
def test_user_delete(self):
|
||||||
"""Test UserDeleteView"""
|
"""Test UserDeleteView"""
|
||||||
self.assertEqual(self.client.post(reverse('passbook_core:user-delete')).status_code, 302)
|
self.assertEqual(
|
||||||
self.assertEqual(User.objects.filter(username='unittest user').exists(), False)
|
self.client.post(reverse("passbook_core:user-delete")).status_code, 302
|
||||||
|
)
|
||||||
|
self.assertEqual(User.objects.filter(username="unittest user").exists(), False)
|
||||||
self.setUp()
|
self.setUp()
|
||||||
|
|
||||||
def test_user_change_password(self):
|
def test_user_change_password(self):
|
||||||
"""Test UserChangePasswordView"""
|
"""Test UserChangePasswordView"""
|
||||||
form_data = {
|
form_data = {"password": "test2", "password_repeat": "test2"}
|
||||||
'password': 'test2',
|
|
||||||
'password_repeat': 'test2'
|
|
||||||
}
|
|
||||||
form = PasswordChangeForm(data=form_data)
|
form = PasswordChangeForm(data=form_data)
|
||||||
self.assertTrue(form.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
self.assertEqual(self.client.get(
|
self.assertEqual(
|
||||||
reverse('passbook_core:user-change-password')).status_code, 200)
|
self.client.get(reverse("passbook_core:user-change-password")).status_code,
|
||||||
self.assertEqual(self.client.post(
|
200,
|
||||||
reverse('passbook_core:user-change-password'), data=form_data).status_code, 302)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.client.post(
|
||||||
|
reverse("passbook_core:user-change-password"), data=form_data
|
||||||
|
).status_code,
|
||||||
|
302,
|
||||||
|
)
|
||||||
self.user.refresh_from_db()
|
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):
|
def setUp(self):
|
||||||
self.user = User.objects.create_superuser(
|
self.user = User.objects.create_superuser(
|
||||||
username='unittest user',
|
username="unittest user",
|
||||||
email='unittest@example.com',
|
email="unittest@example.com",
|
||||||
password=''.join(SystemRandom().choice(
|
password="".join(
|
||||||
string.ascii_uppercase + string.digits) for _ in range(8)))
|
SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||||
|
for _ in range(8)
|
||||||
|
),
|
||||||
|
)
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
def test_loading_view(self):
|
def test_loading_view(self):
|
||||||
"""Test loading view"""
|
"""Test loading view"""
|
||||||
request = self.factory.get('something')
|
request = self.factory.get("something")
|
||||||
response = LoadingView.as_view(target_url='somestring')(request)
|
response = LoadingView.as_view(target_url="somestring")(request)
|
||||||
response.render()
|
response.render()
|
||||||
self.assertIn('somestring', response.content.decode('utf-8'))
|
self.assertIn("somestring", response.content.decode("utf-8"))
|
||||||
|
|
||||||
def test_permission_denied_view(self):
|
def test_permission_denied_view(self):
|
||||||
"""Test PermissionDeniedView"""
|
"""Test PermissionDeniedView"""
|
||||||
request = self.factory.get('something')
|
request = self.factory.get("something")
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
response = PermissionDeniedView.as_view()(request)
|
response = PermissionDeniedView.as_view()(request)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|||||||
@ -9,21 +9,38 @@ LOGGER = get_logger()
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Authentication views
|
# Authentication views
|
||||||
path('auth/login/', authentication.LoginView.as_view(), name='auth-login'),
|
path("auth/login/", authentication.LoginView.as_view(), name="auth-login"),
|
||||||
path('auth/logout/', authentication.LogoutView.as_view(), name='auth-logout'),
|
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/", authentication.SignUpView.as_view(), name="auth-sign-up"),
|
||||||
path('auth/sign_up/<uuid:nonce>/confirm/', authentication.SignUpConfirmView.as_view(),
|
path(
|
||||||
name='auth-sign-up-confirm'),
|
"auth/sign_up/<uuid:nonce>/confirm/",
|
||||||
path('auth/process/denied/', view.FactorPermissionDeniedView.as_view(), name='auth-denied'),
|
authentication.SignUpConfirmView.as_view(),
|
||||||
path('auth/password/reset/<uuid:nonce>/', authentication.PasswordResetView.as_view(),
|
name="auth-sign-up-confirm",
|
||||||
name='auth-password-reset'),
|
),
|
||||||
path('auth/process/', view.AuthenticationView.as_view(), name='auth-process'),
|
path(
|
||||||
path('auth/process/<slug:factor>/', view.AuthenticationView.as_view(), name='auth-process'),
|
"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
|
# User views
|
||||||
path('_/user/', user.UserSettingsView.as_view(), name='user-settings'),
|
path("_/user/", user.UserSettingsView.as_view(), name="user-settings"),
|
||||||
path('_/user/delete/', user.UserDeleteView.as_view(), name='user-delete'),
|
path("_/user/delete/", user.UserDeleteView.as_view(), name="user-delete"),
|
||||||
path('_/user/change_password/', user.UserChangePasswordView.as_view(),
|
path(
|
||||||
name='user-change-password'),
|
"_/user/change_password/",
|
||||||
|
user.UserChangePasswordView.as_view(),
|
||||||
|
name="user-change-password",
|
||||||
|
),
|
||||||
# Overview
|
# 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()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
class AccessMixin:
|
class AccessMixin:
|
||||||
"""Mixin class for usage in Authorization views.
|
"""Mixin class for usage in Authorization views.
|
||||||
Provider functions to check application access, etc"""
|
Provider functions to check application access, etc"""
|
||||||
@ -23,12 +24,18 @@ class AccessMixin:
|
|||||||
try:
|
try:
|
||||||
return provider.application
|
return provider.application
|
||||||
except Application.DoesNotExist as exc:
|
except Application.DoesNotExist as exc:
|
||||||
messages.error(self.request, _('Provider "%(name)s" has no application assigned' % {
|
messages.error(
|
||||||
'name': provider
|
self.request,
|
||||||
}))
|
_(
|
||||||
|
'Provider "%(name)s" has no application assigned'
|
||||||
|
% {"name": provider}
|
||||||
|
),
|
||||||
|
)
|
||||||
raise exc
|
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."""
|
"""Check if user has access to application."""
|
||||||
LOGGER.debug("Checking permissions", user=user, application=application)
|
LOGGER.debug("Checking permissions", user=user, application=application)
|
||||||
policy_engine = PolicyEngine(application.policies.all(), user, self.request)
|
policy_engine = PolicyEngine(application.policies.all(), user, self.request)
|
||||||
|
|||||||
@ -25,41 +25,41 @@ LOGGER = get_logger()
|
|||||||
class LoginView(UserPassesTestMixin, FormView):
|
class LoginView(UserPassesTestMixin, FormView):
|
||||||
"""Allow users to sign in"""
|
"""Allow users to sign in"""
|
||||||
|
|
||||||
template_name = 'login/form.html'
|
template_name = "login/form.html"
|
||||||
form_class = LoginForm
|
form_class = LoginForm
|
||||||
success_url = '.'
|
success_url = "."
|
||||||
|
|
||||||
# Allow only not authenticated users to login
|
# Allow only not authenticated users to login
|
||||||
def test_func(self):
|
def test_func(self):
|
||||||
return self.request.user.is_authenticated is False
|
return self.request.user.is_authenticated is False
|
||||||
|
|
||||||
def handle_no_permission(self):
|
def handle_no_permission(self):
|
||||||
if 'next' in self.request.GET:
|
if "next" in self.request.GET:
|
||||||
return redirect(self.request.GET.get('next'))
|
return redirect(self.request.GET.get("next"))
|
||||||
return redirect(reverse('passbook_core:overview'))
|
return redirect(reverse("passbook_core:overview"))
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['config'] = CONFIG.y('passbook')
|
kwargs["config"] = CONFIG.y("passbook")
|
||||||
kwargs['is_login'] = True
|
kwargs["is_login"] = True
|
||||||
kwargs['title'] = _('Log in to your account')
|
kwargs["title"] = _("Log in to your account")
|
||||||
kwargs['primary_action'] = _('Log in')
|
kwargs["primary_action"] = _("Log in")
|
||||||
kwargs['show_sign_up_notice'] = CONFIG.y('passbook.sign_up.enabled')
|
kwargs["show_sign_up_notice"] = CONFIG.y("passbook.sign_up.enabled")
|
||||||
kwargs['sources'] = []
|
kwargs["sources"] = []
|
||||||
sources = Source.objects.filter(enabled=True).select_subclasses()
|
sources = Source.objects.filter(enabled=True).select_subclasses()
|
||||||
for source in sources:
|
for source in sources:
|
||||||
login_button = source.login_button
|
login_button = source.login_button
|
||||||
if login_button:
|
if login_button:
|
||||||
kwargs['sources'].append(login_button)
|
kwargs["sources"].append(login_button)
|
||||||
if kwargs['sources']:
|
if kwargs["sources"]:
|
||||||
self.template_name = 'login/with_sources.html'
|
self.template_name = "login/with_sources.html"
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def get_user(self, uid_value) -> Optional[User]:
|
def get_user(self, uid_value) -> Optional[User]:
|
||||||
"""Find user instance. Returns None if no user was found."""
|
"""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
|
# Workaround for E-Mail -> email
|
||||||
if search_field == 'e-mail':
|
if search_field == "e-mail":
|
||||||
search_field = 'email'
|
search_field = "email"
|
||||||
users = User.objects.filter(**{search_field: uid_value})
|
users = User.objects.filter(**{search_field: uid_value})
|
||||||
if users.exists():
|
if users.exists():
|
||||||
LOGGER.debug("Found user", user=users.first(), uid_field=search_field)
|
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:
|
def form_valid(self, form: LoginForm) -> HttpResponse:
|
||||||
"""Form data is valid"""
|
"""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:
|
if not pre_user:
|
||||||
# No user found
|
# No user found
|
||||||
return self.invalid_login(self.request)
|
return self.invalid_login(self.request)
|
||||||
# self.request.session.flush()
|
# self.request.session.flush()
|
||||||
self.request.session[AuthenticationView.SESSION_PENDING_USER] = pre_user.pk
|
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"""
|
"""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())
|
return self.render_to_response(self.get_context_data())
|
||||||
|
|
||||||
|
|
||||||
@ -89,15 +92,15 @@ class LogoutView(LoginRequiredMixin, View):
|
|||||||
"""Log current user out"""
|
"""Log current user out"""
|
||||||
logout(request)
|
logout(request)
|
||||||
messages.success(request, _("You've successfully been logged out."))
|
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):
|
class SignUpView(UserPassesTestMixin, FormView):
|
||||||
"""Sign up new user, optionally consume one-use invitation link."""
|
"""Sign up new user, optionally consume one-use invitation link."""
|
||||||
|
|
||||||
template_name = 'login/form.html'
|
template_name = "login/form.html"
|
||||||
form_class = SignUpForm
|
form_class = SignUpForm
|
||||||
success_url = '.'
|
success_url = "."
|
||||||
# Invitation instance, if invitation link was used
|
# Invitation instance, if invitation link was used
|
||||||
_invitation = None
|
_invitation = None
|
||||||
# Instance of newly created user
|
# Instance of newly created user
|
||||||
@ -108,38 +111,38 @@ class SignUpView(UserPassesTestMixin, FormView):
|
|||||||
return self.request.user.is_authenticated is False
|
return self.request.user.is_authenticated is False
|
||||||
|
|
||||||
def handle_no_permission(self):
|
def handle_no_permission(self):
|
||||||
return redirect(reverse('passbook_core:overview'))
|
return redirect(reverse("passbook_core:overview"))
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
"""Check if sign-up is enabled or invitation link given"""
|
"""Check if sign-up is enabled or invitation link given"""
|
||||||
allowed = False
|
allowed = False
|
||||||
if 'invitation' in request.GET:
|
if "invitation" in request.GET:
|
||||||
invitations = Invitation.objects.filter(uuid=request.GET.get('invitation'))
|
invitations = Invitation.objects.filter(uuid=request.GET.get("invitation"))
|
||||||
allowed = invitations.exists()
|
allowed = invitations.exists()
|
||||||
if allowed:
|
if allowed:
|
||||||
self._invitation = invitations.first()
|
self._invitation = invitations.first()
|
||||||
if CONFIG.y('passbook.sign_up.enabled'):
|
if CONFIG.y("passbook.sign_up.enabled"):
|
||||||
allowed = True
|
allowed = True
|
||||||
if not allowed:
|
if not allowed:
|
||||||
messages.error(request, _('Sign-ups are currently disabled.'))
|
messages.error(request, _("Sign-ups are currently disabled."))
|
||||||
return redirect(reverse('passbook_core:auth-login'))
|
return redirect(reverse("passbook_core:auth-login"))
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
if self._invitation:
|
if self._invitation:
|
||||||
initial = {}
|
initial = {}
|
||||||
if self._invitation.fixed_username:
|
if self._invitation.fixed_username:
|
||||||
initial['username'] = self._invitation.fixed_username
|
initial["username"] = self._invitation.fixed_username
|
||||||
if self._invitation.fixed_email:
|
if self._invitation.fixed_email:
|
||||||
initial['email'] = self._invitation.fixed_email
|
initial["email"] = self._invitation.fixed_email
|
||||||
return initial
|
return initial
|
||||||
return super().get_initial()
|
return super().get_initial()
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['config'] = CONFIG.y('passbook')
|
kwargs["config"] = CONFIG.y("passbook")
|
||||||
kwargs['is_login'] = True
|
kwargs["is_login"] = True
|
||||||
kwargs['title'] = _('Sign Up')
|
kwargs["title"] = _("Sign Up")
|
||||||
kwargs['primary_action'] = _('Sign up')
|
kwargs["primary_action"] = _("Sign up")
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
def form_valid(self, form: SignUpForm) -> HttpResponse:
|
def form_valid(self, form: SignUpForm) -> HttpResponse:
|
||||||
@ -172,9 +175,8 @@ class SignUpView(UserPassesTestMixin, FormView):
|
|||||||
# self._user.save()
|
# self._user.save()
|
||||||
self.consume_invitation()
|
self.consume_invitation()
|
||||||
messages.success(self.request, _("Successfully signed up!"))
|
messages.success(self.request, _("Successfully signed up!"))
|
||||||
LOGGER.debug("Successfully signed up %s",
|
LOGGER.debug("Successfully signed up %s", form.cleaned_data.get("email"))
|
||||||
form.cleaned_data.get('email'))
|
return redirect(reverse("passbook_core:auth-login"))
|
||||||
return redirect(reverse('passbook_core:auth-login'))
|
|
||||||
|
|
||||||
def consume_invitation(self):
|
def consume_invitation(self):
|
||||||
"""Consume invitation if an invitation was used"""
|
"""Consume invitation if an invitation was used"""
|
||||||
@ -183,7 +185,8 @@ class SignUpView(UserPassesTestMixin, FormView):
|
|||||||
sender=self,
|
sender=self,
|
||||||
request=self.request,
|
request=self.request,
|
||||||
invitation=self._invitation,
|
invitation=self._invitation,
|
||||||
user=self._user)
|
user=self._user,
|
||||||
|
)
|
||||||
self._invitation.delete()
|
self._invitation.delete()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -203,20 +206,17 @@ class SignUpView(UserPassesTestMixin, FormView):
|
|||||||
"""
|
"""
|
||||||
# Create user
|
# Create user
|
||||||
new_user = User.objects.create(
|
new_user = User.objects.create(
|
||||||
username=data.get('username'),
|
username=data.get("username"),
|
||||||
email=data.get('email'),
|
email=data.get("email"),
|
||||||
name=data.get('name'),
|
name=data.get("name"),
|
||||||
)
|
)
|
||||||
new_user.is_active = True
|
new_user.is_active = True
|
||||||
try:
|
try:
|
||||||
new_user.set_password(data.get('password'))
|
new_user.set_password(data.get("password"))
|
||||||
new_user.save()
|
new_user.save()
|
||||||
request.user = new_user
|
request.user = new_user
|
||||||
# Send signal for other auth sources
|
# Send signal for other auth sources
|
||||||
user_signed_up.send(
|
user_signed_up.send(sender=SignUpView, user=new_user, request=request)
|
||||||
sender=SignUpView,
|
|
||||||
user=new_user,
|
|
||||||
request=request)
|
|
||||||
return new_user
|
return new_user
|
||||||
except PasswordPolicyInvalid as exc:
|
except PasswordPolicyInvalid as exc:
|
||||||
new_user.delete()
|
new_user.delete()
|
||||||
@ -232,11 +232,11 @@ class SignUpConfirmView(View):
|
|||||||
nonce.user.is_active = True
|
nonce.user.is_active = True
|
||||||
nonce.user.save()
|
nonce.user.save()
|
||||||
# Workaround: hardcoded reference to ModelBackend, needs testing
|
# 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)
|
login(request, nonce.user)
|
||||||
nonce.delete()
|
nonce.delete()
|
||||||
messages.success(request, _('Successfully confirmed registration.'))
|
messages.success(request, _("Successfully confirmed registration."))
|
||||||
return redirect('passbook_core:overview')
|
return redirect("passbook_core:overview")
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetView(View):
|
class PasswordResetView(View):
|
||||||
@ -247,9 +247,11 @@ class PasswordResetView(View):
|
|||||||
# 3. (Optional) Trap user in password change view
|
# 3. (Optional) Trap user in password change view
|
||||||
nonce = get_object_or_404(Nonce, uuid=nonce)
|
nonce = get_object_or_404(Nonce, uuid=nonce)
|
||||||
# Workaround: hardcoded reference to ModelBackend, needs testing
|
# 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)
|
login(request, nonce.user)
|
||||||
nonce.delete()
|
nonce.delete()
|
||||||
messages.success(request, _(('Temporarily authenticated with Nonce, '
|
messages.success(
|
||||||
'please change your password')))
|
request,
|
||||||
return redirect('passbook_core:user-change-password')
|
_(("Temporarily authenticated with Nonce, " "please change your password")),
|
||||||
|
)
|
||||||
|
return redirect("passbook_core:user-change-password")
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
"""passbook core error views"""
|
"""passbook core error views"""
|
||||||
|
|
||||||
from django.http.response import (HttpResponseBadRequest,
|
from django.http.response import (
|
||||||
HttpResponseForbidden, HttpResponseNotFound,
|
HttpResponseBadRequest,
|
||||||
HttpResponseServerError)
|
HttpResponseForbidden,
|
||||||
|
HttpResponseNotFound,
|
||||||
|
HttpResponseServerError,
|
||||||
|
)
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
@ -10,54 +13,53 @@ from django.views.generic import TemplateView
|
|||||||
class BadRequestTemplateResponse(TemplateResponse, HttpResponseBadRequest):
|
class BadRequestTemplateResponse(TemplateResponse, HttpResponseBadRequest):
|
||||||
"""Combine Template response with Http Code 400"""
|
"""Combine Template response with Http Code 400"""
|
||||||
|
|
||||||
|
|
||||||
class ForbiddenTemplateResponse(TemplateResponse, HttpResponseForbidden):
|
class ForbiddenTemplateResponse(TemplateResponse, HttpResponseForbidden):
|
||||||
"""Combine Template response with Http Code 403"""
|
"""Combine Template response with Http Code 403"""
|
||||||
|
|
||||||
|
|
||||||
class NotFoundTemplateResponse(TemplateResponse, HttpResponseNotFound):
|
class NotFoundTemplateResponse(TemplateResponse, HttpResponseNotFound):
|
||||||
"""Combine Template response with Http Code 404"""
|
"""Combine Template response with Http Code 404"""
|
||||||
|
|
||||||
|
|
||||||
class ServerErrorTemplateResponse(TemplateResponse, HttpResponseServerError):
|
class ServerErrorTemplateResponse(TemplateResponse, HttpResponseServerError):
|
||||||
"""Combine Template response with Http Code 500"""
|
"""Combine Template response with Http Code 500"""
|
||||||
|
|
||||||
|
|
||||||
class BadRequestView(TemplateView):
|
class BadRequestView(TemplateView):
|
||||||
"""Show Bad Request message"""
|
"""Show Bad Request message"""
|
||||||
|
|
||||||
response_class = BadRequestTemplateResponse
|
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):
|
class ForbiddenView(TemplateView):
|
||||||
"""Show Forbidden message"""
|
"""Show Forbidden message"""
|
||||||
|
|
||||||
response_class = ForbiddenTemplateResponse
|
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):
|
class NotFoundView(TemplateView):
|
||||||
"""Show Not Found message"""
|
"""Show Not Found message"""
|
||||||
|
|
||||||
response_class = NotFoundTemplateResponse
|
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):
|
class ServerErrorView(TemplateView):
|
||||||
"""Show Server Error message"""
|
"""Show Server Error message"""
|
||||||
|
|
||||||
response_class = ServerErrorTemplateResponse
|
response_class = ServerErrorTemplateResponse
|
||||||
template_name = 'error/500.html'
|
template_name = "error/500.html"
|
||||||
|
|
||||||
extra_context = {
|
extra_context = {"is_login": True}
|
||||||
'is_login': True
|
|
||||||
}
|
|
||||||
|
|
||||||
# pylint: disable=useless-super-delegation
|
# pylint: disable=useless-super-delegation
|
||||||
def dispatch(self, *args, **kwargs):
|
def dispatch(self, *args, **kwargs):
|
||||||
|
|||||||
@ -11,13 +11,15 @@ class OverviewView(LoginRequiredMixin, TemplateView):
|
|||||||
"""Overview for logged in user, incase user opens passbook directly
|
"""Overview for logged in user, incase user opens passbook directly
|
||||||
and is not being forwarded"""
|
and is not being forwarded"""
|
||||||
|
|
||||||
template_name = 'overview/index.html'
|
template_name = "overview/index.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['applications'] = []
|
kwargs["applications"] = []
|
||||||
for application in Application.objects.all():
|
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()
|
engine.build()
|
||||||
if engine.passing:
|
if engine.passing:
|
||||||
kwargs['applications'].append(application)
|
kwargs["applications"].append(application)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|||||||
@ -17,11 +17,11 @@ from passbook.lib.config import CONFIG
|
|||||||
class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
||||||
"""Update User settings"""
|
"""Update User settings"""
|
||||||
|
|
||||||
template_name = 'user/settings.html'
|
template_name = "user/settings.html"
|
||||||
form_class = UserDetailForm
|
form_class = UserDetailForm
|
||||||
|
|
||||||
success_message = _('Successfully updated user.')
|
success_message = _("Successfully updated user.")
|
||||||
success_url = reverse_lazy('passbook_core:user-settings')
|
success_url = reverse_lazy("passbook_core:user-settings")
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
@ -30,44 +30,44 @@ class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
|||||||
class UserDeleteView(LoginRequiredMixin, DeleteView):
|
class UserDeleteView(LoginRequiredMixin, DeleteView):
|
||||||
"""Delete user account"""
|
"""Delete user account"""
|
||||||
|
|
||||||
template_name = 'generic/delete.html'
|
template_name = "generic/delete.html"
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
messages.success(self.request, _('Successfully deleted user.'))
|
messages.success(self.request, _("Successfully deleted user."))
|
||||||
logout(self.request)
|
logout(self.request)
|
||||||
return reverse('passbook_core:auth-login')
|
return reverse("passbook_core:auth-login")
|
||||||
|
|
||||||
|
|
||||||
class UserChangePasswordView(LoginRequiredMixin, FormView):
|
class UserChangePasswordView(LoginRequiredMixin, FormView):
|
||||||
"""View for users to update their password"""
|
"""View for users to update their password"""
|
||||||
|
|
||||||
form_class = PasswordChangeForm
|
form_class = PasswordChangeForm
|
||||||
template_name = 'login/form_with_user.html'
|
template_name = "login/form_with_user.html"
|
||||||
|
|
||||||
def form_valid(self, form: PasswordChangeForm):
|
def form_valid(self, form: PasswordChangeForm):
|
||||||
try:
|
try:
|
||||||
# user.set_password checks against Policies so we don't need to manually do it here
|
# 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()
|
self.request.user.save()
|
||||||
update_session_auth_hash(self.request, self.request.user)
|
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:
|
except PasswordPolicyInvalid as exc:
|
||||||
# Manually inject error into form
|
# Manually inject error into form
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
errors = form._errors.setdefault("password_repeat", ErrorList(''))
|
errors = form._errors.setdefault("password_repeat", ErrorList(""))
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
errors = form._errors.setdefault("password", ErrorList())
|
errors = form._errors.setdefault("password", ErrorList())
|
||||||
for error in exc.messages:
|
for error in exc.messages:
|
||||||
errors.append(error)
|
errors.append(error)
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
return redirect('passbook_core:overview')
|
return redirect("passbook_core:overview")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['config'] = CONFIG.y('passbook')
|
kwargs["config"] = CONFIG.y("passbook")
|
||||||
kwargs['is_login'] = True
|
kwargs["is_login"] = True
|
||||||
kwargs['title'] = _('Change Password')
|
kwargs["title"] = _("Change Password")
|
||||||
kwargs['primary_action'] = _('Change')
|
kwargs["primary_action"] = _("Change")
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|||||||
@ -6,8 +6,8 @@ from django.views.generic import TemplateView
|
|||||||
class LoadingView(TemplateView):
|
class LoadingView(TemplateView):
|
||||||
"""View showing a loading template, and forwarding to real view using html forwarding."""
|
"""View showing a loading template, and forwarding to real view using html forwarding."""
|
||||||
|
|
||||||
template_name = 'login/loading.html'
|
template_name = "login/loading.html"
|
||||||
title = _('Loading')
|
title = _("Loading")
|
||||||
target_url = None
|
target_url = None
|
||||||
|
|
||||||
def get_url(self):
|
def get_url(self):
|
||||||
@ -15,18 +15,19 @@ class LoadingView(TemplateView):
|
|||||||
return self.target_url
|
return self.target_url
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['is_login'] = True
|
kwargs["is_login"] = True
|
||||||
kwargs['title'] = self.title
|
kwargs["title"] = self.title
|
||||||
kwargs['target_url'] = self.get_url()
|
kwargs["target_url"] = self.get_url()
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class PermissionDeniedView(TemplateView):
|
class PermissionDeniedView(TemplateView):
|
||||||
"""Generic Permission denied view"""
|
"""Generic Permission denied view"""
|
||||||
|
|
||||||
template_name = 'login/denied.html'
|
template_name = "login/denied.html"
|
||||||
title = _('Permission denied.')
|
title = _("Permission denied.")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['is_login'] = True
|
kwargs["is_login"] = True
|
||||||
kwargs['title'] = self.title
|
kwargs["title"] = self.title
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|||||||
@ -17,16 +17,16 @@ class AuthenticationFactor(TemplateView):
|
|||||||
authenticator: AuthenticationView
|
authenticator: AuthenticationView
|
||||||
pending_user: User
|
pending_user: User
|
||||||
request: HttpRequest = None
|
request: HttpRequest = None
|
||||||
template_name = 'login/form_with_user.html'
|
template_name = "login/form_with_user.html"
|
||||||
|
|
||||||
def __init__(self, authenticator: AuthenticationView):
|
def __init__(self, authenticator: AuthenticationView):
|
||||||
self.authenticator = authenticator
|
self.authenticator = authenticator
|
||||||
self.pending_user = None
|
self.pending_user = None
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs['config'] = CONFIG.y('passbook')
|
kwargs["config"] = CONFIG.y("passbook")
|
||||||
kwargs['is_login'] = True
|
kwargs["is_login"] = True
|
||||||
kwargs['title'] = _('Log in to your account')
|
kwargs["title"] = _("Log in to your account")
|
||||||
kwargs['primary_action'] = _('Log in')
|
kwargs["primary_action"] = _("Log in")
|
||||||
kwargs['user'] = self.pending_user
|
kwargs["user"] = self.pending_user
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|||||||
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
from passbook.lib.admin import admin_autoregister
|
from passbook.lib.admin import admin_autoregister
|
||||||
|
|
||||||
admin_autoregister('passbook_factors_captcha')
|
admin_autoregister("passbook_factors_captcha")
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user