Compare commits

..

81 Commits

Author SHA1 Message Date
c77f4204c0 new release: 0.8.15-beta 2020-04-10 21:57:20 +02:00
5f4452470b providers/saml: fix metadata rendering when no singing keypair is selected
closes PASSBOOK-44
2020-04-10 21:54:23 +02:00
9a1270c693 providers/saml: fix wrong signing property being checked
closes PASSBOOK-45
2020-04-10 21:52:03 +02:00
7b9d1a1159 new release: 0.8.14-beta 2020-04-10 21:23:55 +02:00
cdbe1f6161 bump dependencies 2020-04-10 21:17:31 +02:00
e43db2e065 new release: 0.8.13-beta 2020-04-10 21:11:24 +02:00
d1c74d2160 lib: fix imports being changed every time 2020-03-05 17:28:03 +01:00
f2119ce567 providers/saml: fix signing_kp typo 2020-03-05 17:09:08 +01:00
2c4dcb9cf0 actions: remove cache 2020-03-04 21:15:44 +01:00
93b8266821 actions: install pipenv as root 2020-03-04 21:13:40 +01:00
443797d9b0 actions: install wheel package 2020-03-04 21:11:26 +01:00
a4365ca02c actions: don't update system pip 2020-03-04 21:09:46 +01:00
3750083667 actions: don't setup custom python, use system pip 2020-03-04 21:08:24 +01:00
66ef067ecf actions: don't update pip to fix CI 2020-03-04 20:10:46 +01:00
b489b0e691 Merge pull request #7 from BeryJu/crypto
generic cert management
2020-03-04 19:43:52 +01:00
f2154d9875 crypto: add property for private_key 2020-03-04 19:43:18 +01:00
80a50f9bdb providers/saml: switch to new crypto 2020-03-03 23:35:50 +01:00
dc8b89a6b9 sources/saml: switch to new crypto 2020-03-03 23:35:38 +01:00
8df55f22aa crypto: implement simple certificate-key pair for easier management 2020-03-03 23:35:25 +01:00
f6c322be27 providers/oidc: fix skip_authorization not being synced to oidc_client 2020-03-02 17:40:38 +01:00
a144552059 providers/oidc: fill claims with userinfo 2020-03-01 22:55:56 +01:00
535d529193 ui: fix title, fix navigation on user settings 2020-02-29 14:46:58 +01:00
6ed2e137a2 new release: 0.8.12-beta 2020-02-28 11:54:03 +01:00
45bd63c720 api: update old field names 2020-02-28 11:48:55 +01:00
736e13fc35 ui: add template for csrf errors 2020-02-28 11:41:28 +01:00
966fff008c ui: re-enable branding on navbar 2020-02-28 11:37:07 +01:00
64f15eadbd providers/saml: fix CSRF errors with POST binding 2020-02-28 10:50:16 +01:00
81b66ecdcd core: remove some more dead code, add more help texts for factors 2020-02-27 16:39:30 +01:00
53e5cf7826 admin: fix some models not being paginated 2020-02-27 15:30:28 +01:00
82654b3fd9 ui: re-organize some of the navigation to make it cleaner for end-users 2020-02-27 14:59:34 +01:00
9b72c604dd docs: fix some typos 2020-02-27 13:00:55 +01:00
5fb1b8044c new release: 0.8.11-beta 2020-02-25 11:38:50 +01:00
b8daab4377 providers/saml: fix AccessRequiredView.dispatch not being called 2020-02-25 11:38:26 +01:00
c5b91bdae8 providers/saml: fix CannotHandleAssertion Error still being sent to sentry 2020-02-24 19:14:43 +01:00
39a208c55f providers/saml: fix wrong key being used for params 2020-02-24 17:48:03 +01:00
a5bfef9b6b providers/saml: fix leftover data in session, fix IdP initiated login
move can_handle calls to binding endpoints (/login/ and /login/initiate/), so that /login/authorize/ works either way, can clean up the session and audit
2020-02-24 17:34:52 +01:00
f1f4cbef9b lib/sentry: fix SentryIgnoredException not being ignored correctly 2020-02-24 17:01:31 +01:00
8388120b06 new release: 0.8.10-beta 2020-02-24 15:30:57 +01:00
2bf96828f1 root: fix logging.basicConfig being called by pyjwkest 2020-02-24 15:30:28 +01:00
22838e66fe providers/saml: fix users being able to authenticate without audit logs being created 2020-02-24 14:40:12 +01:00
484dd6de09 providers/oidc: add error template 2020-02-24 14:19:02 +01:00
b743736c26 lib/logging: fix typo 2020-02-24 14:10:58 +01:00
af91e2079b core: sort provider by pk when selection application provider 2020-02-24 14:10:51 +01:00
cad1c17f14 helm: fix inconsistent labels 2020-02-24 13:49:42 +01:00
120d32e4dc new release: 0.8.9-beta 2020-02-24 13:23:20 +01:00
238b489e07 root: add process ID to logging output 2020-02-24 13:20:32 +01:00
4daa70c894 core: fix saving of policy not correctly clearing it's cache 2020-02-24 13:15:52 +01:00
f8599438df ui: fix lists not being rendered correctly 2020-02-24 13:13:42 +01:00
155c9a4c3f ui: update remaining forms, completely remove jQuery 2020-02-24 13:13:28 +01:00
8433b5e583 ui: fix automatic slug generation 2020-02-24 12:40:16 +01:00
dc5ba144f1 ui: fix height of multiple select input 2020-02-24 12:40:06 +01:00
521a8b5356 ui: update more remaining templates 2020-02-23 22:49:56 +01:00
3453077d7b root: set SameSite to None when debugging 2020-02-23 22:49:33 +01:00
70ede8581a core: sort sources on login view 2020-02-23 20:19:01 +01:00
6e9d297f02 deploy: use new bootstrap command 2020-02-23 20:12:48 +01:00
6a7545fd43 lib: add bootstrap command 2020-02-23 19:52:41 +01:00
a8926cbd07 lib: add more errors to sentry ignore 2020-02-23 19:48:14 +01:00
64d7b009ab sources/oauth: fix invalid headers, fix invalid function signature 2020-02-23 19:42:57 +01:00
2b5fddb7bf policies: add unittests for evaluator 2020-02-23 15:54:26 +01:00
b99d23c119 all: remove dead code 2020-02-23 15:32:20 +01:00
03905b74ff admin: exclude anonymous user from listing 2020-02-23 15:27:28 +01:00
6b8a59cfbd admin: show prettified yaml 2020-02-23 15:27:11 +01:00
d6fdcd3ef9 ui: re-add automatic slug generation 2020-02-23 15:20:41 +01:00
53ebc551d2 ui: fix icon sizing on login 2020-02-23 15:13:18 +01:00
3d4f43d6e3 ui: show default icon for source without icon 2020-02-23 15:09:58 +01:00
074cde7cd5 audit: save model's name or string representation 2020-02-23 15:04:30 +01:00
382e563590 new release: 0.8.8-beta 2020-02-23 14:45:45 +01:00
ca61a7cc21 audit: don't pop password as its censored already 2020-02-23 14:43:33 +01:00
fa2870afe0 sources: remove policies as they are not used currently 2020-02-23 14:40:06 +01:00
0f46207ea4 admin: fix provider list not having pagination 2020-02-23 14:29:21 +01:00
1e7d912144 actions: build :<branch-name> on push 2020-02-23 13:54:19 +01:00
f4a676e2fb sources/oauth: slugify provider type instead of just lowercase 2020-02-23 13:53:16 +01:00
b2c10e2387 ui: add missing discord and twitter icons 2020-02-23 13:47:21 +01:00
8c329dca7d core: add migration to fix null fields in core.application 2020-02-22 19:26:34 +01:00
83da175749 policies/expression: add pb_client_ip field 2020-02-22 19:26:16 +01:00
995c87938f core: fix default Null causing issues in translation 2020-02-21 23:10:00 +01:00
40678b2f84 new release: 0.8.7-beta 2020-02-21 22:17:11 +01:00
8dbbe9102b ui: fix application grid icons, fix SAML Authorize 2020-02-21 22:16:58 +01:00
2f51f354de ui: fix app icon not showing 2020-02-21 22:02:44 +01:00
04b815a33e admin: show object usage count instead of list 2020-02-21 22:02:03 +01:00
2a4d68911b helm: change static healthcheck and port 2020-02-21 21:50:16 +01:00
127 changed files with 1961 additions and 1162 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.8.6-beta
current_version = 0.8.15-beta
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)

View File

@ -15,14 +15,8 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- 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
run: sudo pip install -U wheel pipenv && pipenv install --dev
- name: Lint with pylint
run: pipenv run pylint passbook
black:
@ -32,14 +26,8 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- 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
run: sudo pip install -U wheel pipenv && pipenv install --dev
- name: Lint with black
run: pipenv run black --check passbook
prospector:
@ -49,14 +37,8 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- 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 && pipenv install --dev prospector --skip-lock
run: sudo pip install -U wheel pipenv && pipenv install --dev && pipenv install --dev prospector --skip-lock
- name: Lint with prospector
run: pipenv run prospector
bandit:
@ -66,14 +48,8 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- 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
run: sudo pip install -U wheel pipenv && pipenv install --dev
- name: Lint with bandit
run: pipenv run bandit -r passbook
# Actual CI tests
@ -101,14 +77,8 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- 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
run: sudo pip install -U wheel pipenv && pipenv install --dev
- name: Run migrations
run: pipenv run ./manage.py migrate
coverage:
@ -135,13 +105,77 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- 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
run: sudo pip install -U wheel pipenv && pipenv install --dev
- name: Run coverage
run: pipenv run ./scripts/coverage.sh
# Build
build-server:
needs:
- migrations
- coverage
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:${GITHUB_REF##*/}
-f Dockerfile .
- name: Push Docker Container to Registry
run: docker push beryju/passbook:${GITHUB_REF##*/}
build-gatekeeper:
needs:
- migrations
- coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Docker Login Registry
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- name: Building Docker Image
run: |
cd gatekeeper
docker build \
--no-cache \
-t beryju/passbook-gatekeeper:${GITHUB_REF##*/} \
-f Dockerfile .
- name: Push Docker Container to Registry
run: docker push beryju/passbook-gatekeeper:${GITHUB_REF##*/}
build-static:
needs:
- migrations
- coverage
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:${GITHUB_REF##*/}
-f static.Dockerfile .
- name: Push Docker Container to Registry
run: docker push beryju/passbook-static:${GITHUB_REF##*/}

View File

@ -16,11 +16,11 @@ jobs:
- name: Building Docker Image
run: docker build
--no-cache
-t beryju/passbook:0.8.6-beta
-t beryju/passbook:0.8.15-beta
-t beryju/passbook:latest
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook:0.8.6-beta
run: docker push beryju/passbook:0.8.15-beta
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook:latest
build-gatekeeper:
@ -37,11 +37,11 @@ jobs:
cd gatekeeper
docker build \
--no-cache \
-t beryju/passbook-gatekeeper:0.8.6-beta \
-t beryju/passbook-gatekeeper:0.8.15-beta \
-t beryju/passbook-gatekeeper:latest \
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-gatekeeper:0.8.6-beta
run: docker push beryju/passbook-gatekeeper:0.8.15-beta
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-gatekeeper:latest
build-static:
@ -66,11 +66,11 @@ jobs:
run: docker build
--no-cache
--network=$(docker network ls | grep github | awk '{print $1}')
-t beryju/passbook-static:0.8.6-beta
-t beryju/passbook-static:0.8.15-beta
-t beryju/passbook-static:latest
-f static.Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-static:0.8.6-beta
run: docker push beryju/passbook-static:0.8.15-beta
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-static:latest
test-release:

6
.isort.cfg Normal file
View File

@ -0,0 +1,6 @@
[settings]
multi_line_output=3
include_trailing_comma=True
force_grid_wrap=0
use_parentheses=True
line_length=88

View File

@ -7,7 +7,3 @@ const-rgx=[a-zA-Z0-9_]{1,40}$
ignored-modules=django-otp
jobs=4
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=20

587
Pipfile.lock generated
View File

@ -25,10 +25,10 @@
},
"asgiref": {
"hashes": [
"sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
"sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"
"sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5",
"sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"
],
"version": "==3.2.3"
"version": "==3.2.7"
},
"asn1crypto": {
"hashes": [
@ -46,40 +46,40 @@
},
"billiard": {
"hashes": [
"sha256:26fd494dc3251f8ce1f5559744f18aeed427fdaf29a75d7baae26752a5d3816f",
"sha256:f4e09366653aa3cb3ae8ed16423f9ba1665ff426f087bcdbbed86bf3664fe02c"
"sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede",
"sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a"
],
"version": "==3.6.2.0"
"version": "==3.6.3.0"
},
"boto3": {
"hashes": [
"sha256:33462a79d57c9c4a215e075472509537d03545f54566fc4f776fb0f4cfa616f6",
"sha256:34f9a04f529dc849f0e427782d6f3c6b62f7fb734d8f4859b17e5dee0855323e"
"sha256:970bd7b332e73d7b51077ed36772c634811b38c81b0cc6ed0f910e50d7ebadf8",
"sha256:cdd79a3a7bbe1f33a365f0acfcc75c4405b482b3eb9ce3f4e6b16c418e201ac3"
],
"index": "pypi",
"version": "==1.12.0"
"version": "==1.12.39"
},
"botocore": {
"hashes": [
"sha256:055da4826f6c9158e4a61549d57a2ce449c27d44ce34ab4c96c7bb7b5c993efc",
"sha256:1f7cecfcd38c7cac17b5386014eb04626d1c7559ee8d8ec1526058cd23f6d1d4"
"sha256:94232b44e1540b7e043e220bd43f855400d0a243e926b26b3fb72994e971d518",
"sha256:e20ba56476b1031ce5ac8e22b59dabc75bd0e03231f124ed6b9ff99fe0b0c96b"
],
"version": "==1.15.0"
"version": "==1.15.39"
},
"celery": {
"hashes": [
"sha256:7c544f37a84a5eadc44cab1aa8c9580dff94636bb81978cdf9bf8012d9ea7d8f",
"sha256:d3363bb5df72d74420986a435449f3c3979285941dff57d5d97ecba352a0e3e2"
"sha256:108a0bf9018a871620936c33a3ee9f6336a89f8ef0a0f567a9001f4aa361415f",
"sha256:5b4b37e276033fe47575107a2775469f0b721646a08c96ec2c61531e4fe45f2a"
],
"index": "pypi",
"version": "==4.4.0"
"version": "==4.4.2"
},
"certifi": {
"hashes": [
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
"sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
"sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
],
"version": "==2019.11.28"
"version": "==2020.4.5.1"
},
"cffi": {
"hashes": [
@ -137,29 +137,27 @@
},
"cryptography": {
"hashes": [
"sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c",
"sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595",
"sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad",
"sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651",
"sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2",
"sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff",
"sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d",
"sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42",
"sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d",
"sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e",
"sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912",
"sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793",
"sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13",
"sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7",
"sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0",
"sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879",
"sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f",
"sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9",
"sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2",
"sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf",
"sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"
"sha256:0cacd3ef5c604b8e5f59bf2582c076c98a37fe206b31430d0cd08138aff0986e",
"sha256:192ca04a36852a994ef21df13cca4d822adbbdc9d5009c0f96f1d2929e375d4f",
"sha256:19ae795137682a9778892fb4390c07811828b173741bce91e30f899424b3934d",
"sha256:1b9b535d6b55936a79dbe4990b64bb16048f48747c76c29713fea8c50eca2acf",
"sha256:2a2ad24d43398d89f92209289f15265107928f22a8d10385f70def7a698d6a02",
"sha256:3be7a5722d5bfe69894d3f7bbed15547b17619f3a88a318aab2e37f457524164",
"sha256:49870684da168b90110bbaf86140d4681032c5e6a2461adc7afdd93be5634216",
"sha256:587f98ce27ac4547177a0c6fe0986b8736058daffe9160dcf5f1bd411b7fbaa1",
"sha256:5aca6f00b2f42546b9bdf11a69f248d1881212ce5b9e2618b04935b87f6f82a1",
"sha256:6b744039b55988519cc183149cceb573189b3e46e16ccf6f8c46798bb767c9dc",
"sha256:6b91cab3841b4c7cb70e4db1697c69f036c8bc0a253edc0baa6783154f1301e4",
"sha256:7598974f6879a338c785c513e7c5a4329fbc58b9f6b9a6305035fca5b1076552",
"sha256:7a279f33a081d436e90e91d1a7c338553c04e464de1c9302311a5e7e4b746088",
"sha256:95e1296e0157361fe2f5f0ed307fd31f94b0ca13372e3673fa95095a627636a1",
"sha256:9fc9da390e98cb6975eadf251b6e5fa088820141061bf041cd5c72deba1dc526",
"sha256:cc20316e3f5a6b582fc3b029d8dc03aabeb645acfcb7fc1d9848841a33265748",
"sha256:d1bf5a1a0d60c7f9a78e448adcb99aa101f3f9588b16708044638881be15d6bc",
"sha256:ed1d0760c7e46436ec90834d6f10477ff09475c692ed1695329d324b2c5cd547",
"sha256:ef9a55013676907df6c9d7dd943eb1770d014f68beaa7e73250fb43c759f4585"
],
"version": "==2.8"
"version": "==2.9"
},
"defusedxml": {
"hashes": [
@ -171,11 +169,11 @@
},
"django": {
"hashes": [
"sha256:2f1ba1db8648484dd5c238fb62504777b7ad090c81c5f1fd8d5eb5ec21b5f283",
"sha256:c91c91a7ad6ef67a874a4f76f58ba534f9208412692a840e1d125eb5c279cb0a"
"sha256:642d8eceab321ca743ae71e0f985ff8fdca59f07aab3a9fb362c617d23e33a76",
"sha256:d4666c2edefa38c5ede0ec1655424c56dc47ceb04b6d8d62a7eac09db89545c1"
],
"index": "pypi",
"version": "==3.0.3"
"version": "==3.0.5"
},
"django-cors-middleware": {
"hashes": [
@ -218,10 +216,11 @@
},
"django-oauth-toolkit": {
"hashes": [
"sha256:ad1b76275950ebbff708222cec57bbdb879f89bac7df6b9dee0f4b9db485c264"
"sha256:28508f83385ab4313936ddedfb310eaa8a1dcb737153d2956383ce47e75c2fab",
"sha256:d5a1044af9419ddc048390c5974777ea97874e5b78e33c609e17eebb8423afb2"
],
"index": "pypi",
"version": "==1.2.0"
"version": "==1.3.2"
},
"django-oidc-provider": {
"hashes": [
@ -240,11 +239,11 @@
},
"django-prometheus": {
"hashes": [
"sha256:362ea45e5ee26bdba85ce978aeb370659ca6bbc0d6bac69868a055179e053bd1",
"sha256:facaa677386899303ea26c45552371cc43f476e42a81c081011a49cb5564af0b"
"sha256:1a8cb752ae4181e38df00e7bd7d5f6495cde18b8b3ff697c22f9d8d2fe48bf28",
"sha256:9f024af5495447c8e309f07e5289e7bc1100c5a380ac7cd0afe3a1b2a0b3b534"
],
"index": "pypi",
"version": "==2.1.0.dev5"
"version": "==2.1.0.dev14"
},
"django-recaptcha": {
"hashes": [
@ -322,16 +321,17 @@
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
],
"version": "==2.8"
"version": "==2.9"
},
"inflection": {
"hashes": [
"sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca"
"sha256:32a5c3341d9583ec319548b9015b7fbdf8c429cbcb575d326c33ae3a0e90d52c",
"sha256:9a15d3598f01220e93f2207c432cfede50daff53137ce660fb8be838ef1ca6cc"
],
"version": "==0.3.1"
"version": "==0.4.0"
},
"itypes": {
"hashes": [
@ -349,10 +349,10 @@
},
"jmespath": {
"hashes": [
"sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6",
"sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c"
"sha256:695cb76fa78a10663425d5b73ddc5714eb711157e52704d69be03b1a02ba4fec",
"sha256:cca55c8d153173e21baa59983015ad0daf603f9cb799904ff057bfb8ff8dc2d9"
],
"version": "==0.9.4"
"version": "==0.9.5"
},
"jsonschema": {
"hashes": [
@ -363,19 +363,19 @@
},
"kombu": {
"hashes": [
"sha256:2a9e7adff14d046c9996752b2c48b6d9185d0b992106d5160e1a179907a5d4ac",
"sha256:67b32ccb6fea030f8799f8fd50dd08e03a4b99464ebc4952d71d8747b1a52ad1"
"sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76",
"sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2"
],
"index": "pypi",
"version": "==4.6.7"
"version": "==4.6.8"
},
"ldap3": {
"hashes": [
"sha256:1898194d872539670a2f36d4b56fe5a35d4b9ead28103bec78f05a8993e8122f",
"sha256:27cb673e7afcb539f6adcae5a3ecac4e74eb37ca0a2d50dc98f29a3829eee529"
"sha256:17f04298b70bf7ecaa5db8a7d8622b5a962ef7fc2b245b2eea705ac1c24338c0",
"sha256:81df4ac8b6df10fb1f05b17c18d0cb8c4c344d5a03083c382824960ed959cf5b"
],
"index": "pypi",
"version": "==2.6.1"
"version": "==2.7"
},
"lxml": {
"hashes": [
@ -458,11 +458,11 @@
},
"packaging": {
"hashes": [
"sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73",
"sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"
"sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3",
"sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"
],
"index": "pypi",
"version": "==20.1"
"version": "==20.3"
},
"prometheus-client": {
"hashes": [
@ -472,41 +472,39 @@
},
"psycopg2-binary": {
"hashes": [
"sha256:040234f8a4a8dfd692662a8308d78f63f31a97e1c42d2480e5e6810c48966a29",
"sha256:086f7e89ec85a6704db51f68f0dcae432eff9300809723a6e8782c41c2f48e03",
"sha256:18ca813fdb17bc1db73fe61b196b05dd1ca2165b884dd5ec5568877cabf9b039",
"sha256:19dc39616850342a2a6db70559af55b22955f86667b5f652f40c0e99253d9881",
"sha256:2166e770cb98f02ed5ee2b0b569d40db26788e0bf2ec3ae1a0d864ea6f1d8309",
"sha256:3a2522b1d9178575acee4adf8fd9f979f9c0449b00b4164bb63c3475ea6528ed",
"sha256:3aa773580f85a28ffdf6f862e59cb5a3cc7ef6885121f2de3fca8d6ada4dbf3b",
"sha256:3b5deaa3ee7180585a296af33e14c9b18c218d148e735c7accf78130765a47e3",
"sha256:407af6d7e46593415f216c7f56ba087a9a42bd6dc2ecb86028760aa45b802bd7",
"sha256:4c3c09fb674401f630626310bcaf6cd6285daf0d5e4c26d6e55ca26a2734e39b",
"sha256:4c6717962247445b4f9e21c962ea61d2e884fc17df5ddf5e35863b016f8a1f03",
"sha256:50446fae5681fc99f87e505d4e77c9407e683ab60c555ec302f9ac9bffa61103",
"sha256:5057669b6a66aa9ca118a2a860159f0ee3acf837eda937bdd2a64f3431361a2d",
"sha256:5dd90c5438b4f935c9d01fcbad3620253da89d19c1f5fca9158646407ed7df35",
"sha256:659c815b5b8e2a55193ede2795c1e2349b8011497310bb936da7d4745652823b",
"sha256:69b13fdf12878b10dc6003acc8d0abf3ad93e79813fd5f3812497c1c9fb9be49",
"sha256:7a1cb80e35e1ccea3e11a48afe65d38744a0e0bde88795cc56a4d05b6e4f9d70",
"sha256:7e6e3c52e6732c219c07bd97fff6c088f8df4dae3b79752ee3a817e6f32e177e",
"sha256:7f42a8490c4fe854325504ce7a6e4796b207960dabb2cbafe3c3959cb00d1d7e",
"sha256:84156313f258eafff716b2961644a4483a9be44a5d43551d554844d15d4d224e",
"sha256:8578d6b8192e4c805e85f187bc530d0f52ba86c39172e61cd51f68fddd648103",
"sha256:890167d5091279a27e2505ff0e1fb273f8c48c41d35c5b92adbf4af80e6b2ed6",
"sha256:98e10634792ac0e9e7a92a76b4991b44c2325d3e7798270a808407355e7bb0a1",
"sha256:9aadff9032e967865f9778485571e93908d27dab21d0fdfdec0ca779bb6f8ad9",
"sha256:9f24f383a298a0c0f9b3113b982e21751a8ecde6615494a3f1470eb4a9d70e9e",
"sha256:a73021b44813b5c84eda4a3af5826dd72356a900bac9bd9dd1f0f81ee1c22c2f",
"sha256:afd96845e12638d2c44d213d4810a08f4dc4a563f9a98204b7428e567014b1cd",
"sha256:b73ddf033d8cd4cc9dfed6324b1ad2a89ba52c410ef6877998422fcb9c23e3a8",
"sha256:b8f490f5fad1767a1331df1259763b3bad7d7af12a75b950c2843ba319b2415f",
"sha256:dbc5cd56fff1a6152ca59445178652756f4e509f672e49ccdf3d79c1043113a4",
"sha256:eac8a3499754790187bb00574ab980df13e754777d346f85e0ff6df929bcd964",
"sha256:eaed1c65f461a959284649e37b5051224f4db6ebdc84e40b5e65f2986f101a08"
"sha256:008da3ab51adc70a5f1cfbbe5db3a22607ab030eb44bcecf517ad11a0c2b3cac",
"sha256:07cf82c870ec2d2ce94d18e70c13323c89f2f2a2628cbf1feee700630be2519a",
"sha256:08507efbe532029adee21b8d4c999170a83760d38249936038bd0602327029b5",
"sha256:107d9be3b614e52a192719c6bf32e8813030020ea1d1215daa86ded9a24d8b04",
"sha256:17a0ea0b0eabf07035e5e0d520dabc7950aeb15a17c6d36128ba99b2721b25b1",
"sha256:3286541b9d85a340ee4ed42732d15fc1bb441dc500c97243a768154ab8505bb5",
"sha256:3939cf75fc89c5e9ed836e228c4a63604dff95ad19aed2bbf71d5d04c15ed5ce",
"sha256:40abc319f7f26c042a11658bf3dd3b0b3bceccf883ec1c565d5c909a90204434",
"sha256:51f7823f1b087d2020d8e8c9e6687473d3d239ba9afc162d9b2ab6e80b53f9f9",
"sha256:6bb2dd006a46a4a4ce95201f836194eb6a1e863f69ee5bab506673e0ca767057",
"sha256:702f09d8f77dc4794651f650828791af82f7c2efd8c91ae79e3d9fe4bb7d4c98",
"sha256:7036ccf715925251fac969f4da9ad37e4b7e211b1e920860148a10c0de963522",
"sha256:7b832d76cc65c092abd9505cc670c4e3421fd136fb6ea5b94efbe4c146572505",
"sha256:8f74e631b67482d504d7e9cf364071fc5d54c28e79a093ff402d5f8f81e23bfa",
"sha256:930315ac53dc65cbf52ab6b6d27422611f5fb461d763c531db229c7e1af6c0b3",
"sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f",
"sha256:a20299ee0ea2f9cca494396ac472d6e636745652a64a418b39522c120fd0a0a4",
"sha256:a34826d6465c2e2bbe9d0605f944f19d2480589f89863ed5f091943be27c9de4",
"sha256:a69970ee896e21db4c57e398646af9edc71c003bc52a3cc77fb150240fefd266",
"sha256:b9a8b391c2b0321e0cd7ec6b4cfcc3dd6349347bd1207d48bcb752aa6c553a66",
"sha256:ba13346ff6d3eb2dca0b6fa0d8a9d999eff3dcd9b55f3a890f12b0b6362b2b38",
"sha256:bb0608694a91db1e230b4a314e8ed00ad07ed0c518f9a69b83af2717e31291a3",
"sha256:c8830b7d5f16fd79d39b21e3d94f247219036b29b30c8270314c46bf8b732389",
"sha256:cac918cd7c4c498a60f5d2a61d4f0a6091c2c9490d81bc805c963444032d0dab",
"sha256:cc30cb900f42c8a246e2cb76539d9726f407330bc244ca7729c41a44e8d807fb",
"sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6",
"sha256:d1a8b01f6a964fec702d6b6dac1f91f2b9f9fe41b310cbb16c7ef1fac82df06d",
"sha256:e004db88e5a75e5fdab1620fb9f90c9598c2a195a594225ac4ed2a6f1c23e162",
"sha256:eb2f43ae3037f1ef5e19339c41cf56947021ac892f668765cd65f8ab9814192e",
"sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd"
],
"index": "pypi",
"version": "==2.8.4"
"version": "==2.8.5"
},
"pyasn1": {
"hashes": [
@ -524,80 +522,81 @@
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
],
"version": "==2.19"
"version": "==2.20"
},
"pycryptodome": {
"hashes": [
"sha256:012ca77c2105600e3c6aef43188101ac1d95052c633a4ae8fbebffab20c25f8a",
"sha256:05b4d865710f9a6378d3ada28195ff78e52642d3ecffe6fa9d379d870b9bf29d",
"sha256:07daddb98f98f771ba027f8f835bdb675aeb84effe41ed5221f520b267429354",
"sha256:09bf05a489fe10f9280a5e0163f195e7b9630cafb15f7d72fb9c8f5eb2afa84f",
"sha256:0a8d5f2dbb4bbe830ace54286b829bfa529f0853bedaab6225fcb2e6d1f7e356",
"sha256:1259b8ca49662b8a941177357f08147d858595c0042e63ff81e9628e925b5c9d",
"sha256:238d8b6dd27bd1a04816a68aa90a739e6dd23b192fcd83b50f9360958bff192a",
"sha256:2a57daef18a2022a5e4b6f7376c9ddd0c2d946e4b1f1e59b837f5bf295be7380",
"sha256:39e5ca2f66d1eac7abcba5ce1a03370d123dc6085620f1cd532dfee27e650178",
"sha256:3d516df693c195b8da3795e381429bd420e87081b7e6c2871c62c9897c812cda",
"sha256:3e486c5b7228e864665fc479e9f596b2547b5fe29c6f5c8ed3807784d06faed7",
"sha256:5029c46b0d41dfb763c3981c0af68eab029f06fe2b94f2299112fc18cf9e8d6d",
"sha256:5817c0b3c263025d851da96b90cbc7e95348008f88b990e90d10683dba376666",
"sha256:79320f1fc5c9ca682869087c565bb29ca6f334692e940d7365771e9a94382e12",
"sha256:887d08beca6368d3d70dc75126607ad76317a9fd07fe61323d8c3cb42add12b6",
"sha256:9163fec630495c10c767991e3f8dab32f4427bfb2dfeaa59bb28fe3e52ba66f2",
"sha256:95d324e603c5cec5d89e8595236bbf59ade5fe3a72d100ce61eebb323d598750",
"sha256:9927aa8a8cb4af681279b6f28a1dcb14e0eb556c1aea8413a1e27608a8516e0c",
"sha256:9948c2d5c5c0ee45ed44cee0e2eba2ce60a03be006ed3074521f3da3be162e72",
"sha256:a719bd708207fa219fcbf4c8ebbcbc52846045f78179d00445b429fdabdbc1c4",
"sha256:bc22ced26ebc46546798fa0141f4418f1db116dec517f0aeaecec87cf7b2416c",
"sha256:c41b7e10b72cef00cd63410f31fe50e72dc3a40eafbd146e288384fbe4208064",
"sha256:cdb0ad83a5d6bac986a37fcb7562bcbef0aabae8ea19505bab5cf83c4d18af12",
"sha256:d8e480f65ac7105cbc288eec2417dc61eaac6ed6e75595aa15b8c7c77c53a68b",
"sha256:da2d581da279bc7408d38e16ff77754f5448c4352f2acfe530a5d14d8fc6934a",
"sha256:de61091dd68326b600422cf731eb4810c4c6363f18a65bccd6061784b7454f5b",
"sha256:ec7d39589f9cfc2a8b83b1d2fc673441757c99d43283e97b2dd46e0e23730db8",
"sha256:f3204006869ab037604b1d9f045c4e84882ddd365e4ee8caa5eb1ff47a59188e",
"sha256:f4d2174e168d0eabd1fffaf88b4f62c2b6f30a67b8816f31024b8e48be3e2d75",
"sha256:fcff8c9d88d58880f7eda2139c7c444552a38f98a9e77ba5970b6e78f54ac358"
"sha256:07024fc364869eae8d6ac0d316e089956e6aeffe42dbdcf44fe1320d96becf7f",
"sha256:09b6d6bcc01a4eb1a2b4deeff5aa602a108ec5aed8ac75ae554f97d1d7f0a5ad",
"sha256:0e10f352ccbbcb5bb2dc4ecaf106564e65702a717d72ab260f9ac4c19753cfc2",
"sha256:1f4752186298caf2e9ff5354f2e694d607ca7342aa313a62005235d46e28cf04",
"sha256:2fbc472e0b567318fe2052281d5a8c0ae70099b446679815f655e9fbc18c3a65",
"sha256:3ec3dc2f80f71fd0c955ce48b81bfaf8914c6f63a41a738f28885a1c4892968a",
"sha256:426c188c83c10df71f053e04b4003b1437bae5cb37606440e498b00f160d71d0",
"sha256:626c0a1d4d83ec6303f970a17158114f75c3ba1736f7f2983f7b40a265861bd8",
"sha256:767ad0fb5d23efc36a4d5c2fc608ac603f3de028909bcf59abc943e0d0bc5a36",
"sha256:7ac729d9091ed5478af2b4a4f44f5335a98febbc008af619e4569a59fe503e40",
"sha256:83295a3fb5cf50c48631eb5b440cb5e9832d8c14d81d1d45f4497b67a9987de8",
"sha256:8be56bde3312e022d9d1d6afa124556460ad5c844c2fc63642f6af723c098d35",
"sha256:8f06556a8f7ea7b1e42eff39726bb0dca1c251205debae64e6eebea3cd7b438a",
"sha256:9230fcb5d948c3fb40049bace4d33c5d254f8232c2c0bba05d2570aea3ba4520",
"sha256:9378c309aec1f8cd8bad361ed0816a440151b97a2a3f6ffdaba1d1a1fb76873a",
"sha256:9977086e0f93adb326379897437373871b80501e1d176fec63c7f46fb300c862",
"sha256:9a94fca11fdc161460bd8659c15b6adef45c1b20da86402256eaf3addfaab324",
"sha256:9c739b7795ccf2ef1fdad8d44e539a39ad300ee6786e804ea7f0c6a786eb5343",
"sha256:b1e332587b3b195542e77681389c296e1837ca01240399d88803a075447d3557",
"sha256:c109a26a21f21f695d369ff9b87f5d43e0d6c768d8384e10bc74142bed2e092e",
"sha256:c818dc1f3eace93ee50c2b6b5c2becf7c418fa5dd1ba6fc0ef7db279ea21d5e4",
"sha256:cff31f5a8977534f255f729d5d2467526f2b10563a30bbdade92223e0bf264bd",
"sha256:d4f94368ce2d65873a87ad867eb3bf63f4ba81eb97a9ee66d38c2b71ce5a7439",
"sha256:d61b012baa8c2b659e9890011358455c0019a4108536b811602d2f638c40802a",
"sha256:d6e1bc5c94873bec742afe2dfadce0d20445b18e75c47afc0c115b19e5dd38dd",
"sha256:ea83bcd9d6c03248ebd46e71ac313858e0afd5aa2fa81478c0e653242f3eb476",
"sha256:ed5761b37615a1f222c5345bbf45272ae2cf8c7dff88a4f53a1e9f977cbb6d95",
"sha256:f011cd0062e54658b7086a76f8cf0f4222812acc66e219e196ea2d0a8849d0ed",
"sha256:f1add21b6d179179b3c177c33d18a2186a09cc0d3af41ff5ed3f377360b869f2",
"sha256:f655addaaaa9974108d4808f4150652589cada96074c87115c52e575bfcd87d5"
],
"index": "pypi",
"version": "==3.9.6"
"version": "==3.9.7"
},
"pycryptodomex": {
"hashes": [
"sha256:04646e40ef5788bad6d415e52862ffcdf2ac2d888ba4a5c82d5cb44607a042f7",
"sha256:132f1e5fa84921f25695a313a6d4988847dfaee7fb1fd0d1fbe03ef678836f58",
"sha256:17ad1ebaa00806305d34550fe5d3c776e38a27b8a2678dfb7871ef0209d64e46",
"sha256:27736fa02a2d3502e1ca4b150457e56ce3b98f132462f540073498884e5f8975",
"sha256:38050b3fd86c74c6c79e40bbe824bec6431c3e4e36f6080ed544673ba2dc133a",
"sha256:3b9306b360bddbc8e098b16eab7adacf49389d212db9c3739588ab840a1ca868",
"sha256:466e36ba74a7e725625e717fad3f36e0b9293c247b7d0439c66528026ef2834f",
"sha256:4f77360b23a21db32a4c35dacffac33dc30ac6a5a77162a34e99ab11ab631516",
"sha256:5002388178845683c330a02f4faeddfe7cd477b87824987cca4718fa0c4f2085",
"sha256:51be76756abfc1ddc97e1e2e3c38f4e62fb940161162368308ea9e5919e86c34",
"sha256:544628ae67d61c31c28a60e621dadd738b303c5266492355d5ebdb6e7dd1e78f",
"sha256:6ff9d4a06bc40211eee05cd88436740d698a01233f4aaff9eb70d8a90e578966",
"sha256:718329c6ca60260f1c27b8392e372dd51e4e691f7dcb88adc53eb3b76af6363c",
"sha256:918bc5a0170fe8ed7b72f202245b34f84a1997f5ca1520b9c7db71126e5acd62",
"sha256:a8ea72adde0d010f89abece5f024b1be95a5c52472e9a57b3ac7d59aee3c8238",
"sha256:a979d2c7bcc67282b7ec2600db384c63d37d74e250edb99168483605a380bf62",
"sha256:b350f9ad09b692aed57e669fc3f8cf918557fae9f0229c6ce9286a6fe8c1b60f",
"sha256:be838abc8557a21a60d453c5a4e64c738966b8a0b7d7f8f97eb8bb44041ca452",
"sha256:bfa99692d3c8f994c5850cc8a894cba001abd76d34069a8bfaad173dd46387d6",
"sha256:c021b66f5b3c4ea0c45422ec3241bfea4a16651e1ee5459a136639d0716ccb3c",
"sha256:c7babb64484080057a24c74a82dbf7997904b1710b74caf62e261610f989b437",
"sha256:c96b7762b601dc8a58d7712235c3c152868116f58a7ffa40dcd1c6f6cd97405e",
"sha256:d67b6e0bae0777a2c6c83275fbd7cbf53cd5f23c2028f908b0f7d996466e5b15",
"sha256:e15f39fcfb949cfd5536cc9647daba942b1a99b67e4d7211e3bdbcedbc2f823c",
"sha256:e380448f1e39736f6230ec284cd6d771956ad802d6ce5bc56947a2481080cac1",
"sha256:e5236f2171b21e704d1854fd809a7228eb22e29c894af31459e41986e6a53f87",
"sha256:ea7b48ce8dbbc86ebadcfe56ebc10d413bdd12c9a5ff0b9147a41993f12b80b3",
"sha256:f39f5b58d8fe348ed604bb44a89ca93b26130c275db2b249f718f1538cb70500",
"sha256:f545f776e45f74c41329e4020463fdd4d0cd0a7501bdf9e50251dafe7bd959a9",
"sha256:f667ac7ae29c19530f199854635f1a97e73d0bfd24163e0db6bdba7dba04eb9f"
"sha256:1537d2d15b604b303aef56e7f440895a1c81adbee786b91f1f06eddc34da5314",
"sha256:1d20ab8369b7558168fc014a0745c678613f9f486dae468cca2d68145196b8a4",
"sha256:1ecc9db7409db67765eb008e558879d298406642d33ade43a6488224d23e8081",
"sha256:37033976f72af829fe15f7fe5fe1dbed308cc43a98d9dd9d2a0a76de8ca5ee78",
"sha256:3c3dd9d4c9c1e279d3945ae422895c901f98987333acc132dc094faf52afec35",
"sha256:3c9b3fba037ea52c626060c5a87ee6de7e86c99e8a7c6ee07302539985d2bd64",
"sha256:45ee555fc5e28c119a46d44ce373f5237e54a35c61b750fb3a94446b09855dbc",
"sha256:4c93038ac011b36512cb0bf2ee3e2aec774e8bc81021d015917c89fe02bb0ee5",
"sha256:50163324834edd0c9ce3e4512ded3e221c969086e10fdd5d3fdcaadac5e24a78",
"sha256:59b0ea9cda5490f924771456912a225d8d9e678891f9f986661af718534719b2",
"sha256:5cf306a17cccc327a33cdc3845629fa13f4573a4ec620ed607c79cf6785f2e27",
"sha256:5fff8da399af16a1855f58771223acbbdac720b9969cd03fc5013d2e9a7bd9a4",
"sha256:68650ce5b9f7152b8283302a4617269f821695a612692640dd247bd12ab21c0b",
"sha256:6b3a9a562688996f760b5077714c3ab8b62ca56061b6e9ab7906841e43e19f91",
"sha256:7e938ed51a59e29431ea86fab60423ada2757728db0f78952329fa02a789bd31",
"sha256:87aa70daad6f039e814790a06422a3189311198b674b62f13933a2bdcb6b1bcc",
"sha256:99be3a1df2b2b9f731ebe1c264a2c07c465e71cee68e35e1640b645b5213a755",
"sha256:a3f2908666e6f74b8c4893f86dd02e16170f50e4a78ae7f3468b6208d54bc205",
"sha256:ae3d44a639fd11dbdeca47e35e94febb1ee8bc15daf26673331add37146e0b85",
"sha256:afb4c2fa3c6f492fd9a8b38d76e13f32d429b8e5e1e00238309391b5591cde0d",
"sha256:b1515ce3a8a2c3fa537d137c5ca5f8b7a902044d04e07d7c3aa26c3e026120fb",
"sha256:bf391b377413a197000b43ef2b74359974d8927d329a897c9f5ba7b63dca7b9c",
"sha256:c436919117c23355740c669f89720673578b9aa4569bbfe105f6c10101fc1966",
"sha256:d2c3c280975638e2a2c2fd9cb36ab111980219757fa163a2755594b9448e4138",
"sha256:e585d530764c459cbd5d460aed0288807bb881f376ca9a20e653645217895961",
"sha256:e76e6638ead4a7d93262a24218f0ff3ff74de6b6c823b7e19dccb31b6a481978",
"sha256:ebfc2f885cafda076c31ae30fa0dd81e7e919ec34059a88d3018ed66e83fcce3",
"sha256:f5797a39933a3d41526da60856735e6684b2b71a8ca99d5f79555ca121be2f4b",
"sha256:f7e5fc5e124200b19a14be33fb0099e956e6ebb5e25d287b0829ef0a78ed76c7",
"sha256:fb350e31e55211fec8ddc89fc0256f3b9bc3b44b68a8bde1cf44b3b4e80c0e42"
],
"version": "==3.9.6"
"version": "==3.9.7"
},
"pyjwkest": {
"hashes": [
@ -614,16 +613,16 @@
},
"pyparsing": {
"hashes": [
"sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
"sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
"sha256:67199f0c41a9c702154efb0e7a8cc08accf830eb003b4d9fa42c4059002e2492",
"sha256:700d17888d441604b0bd51535908dcb297561b040819cccde647a92439db5a2a"
],
"version": "==2.4.6"
"version": "==3.0.0a1"
},
"pyrsistent": {
"hashes": [
"sha256:cdc7b5e3ed77bed61270a47d35434a30617b9becdf2478af76ad2c6ade307280"
"sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3"
],
"version": "==0.15.7"
"version": "==0.16.0"
},
"python-dateutil": {
"hashes": [
@ -673,20 +672,20 @@
},
"pyyaml": {
"hashes": [
"sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
"sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
"sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
"sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
"sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
"sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
"sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
"sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
"sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
"sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
"sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
],
"index": "pypi",
"version": "==5.3"
"version": "==5.3.1"
},
"qrcode": {
"hashes": [
@ -705,10 +704,10 @@
},
"requests": {
"hashes": [
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
],
"version": "==2.22.0"
"version": "==2.23.0"
},
"requests-oauthlib": {
"hashes": [
@ -759,11 +758,11 @@
},
"sentry-sdk": {
"hashes": [
"sha256:b06dd27391fd11fb32f84fe054e6a64736c469514a718a99fb5ce1dff95d6b28",
"sha256:e023da07cfbead3868e1e2ba994160517885a32dfd994fc455b118e37989479b"
"sha256:23808d571d2461a4ce3784ec12bbee5bdb8c026c143fe79d36cef8a6d653e71f",
"sha256:bb90a4e19c7233a580715fc986cc44be2c48fc10b31e71580a2037e1c94b6950"
],
"index": "pypi",
"version": "==0.14.1"
"version": "==0.14.3"
},
"service-identity": {
"hashes": [
@ -790,10 +789,10 @@
},
"sqlparse": {
"hashes": [
"sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177",
"sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
],
"version": "==0.3.0"
"version": "==0.3.1"
},
"structlog": {
"hashes": [
@ -805,11 +804,11 @@
},
"swagger-spec-validator": {
"hashes": [
"sha256:57e29feb3aa921a9fb98bd70af148746b27c77d3207266f5571cebcce211e685",
"sha256:62ef22eca3f429d93fddda5d793d2a1a9057d3732e7a14606e641805326ae4a6"
"sha256:61f2d2a732b886cf33c2c24886565be9692e5814cacc17fd973e095b72b33e4f",
"sha256:8eb82682871f8d63067b455e2e055c8dd953ca260e791635b58dfe0b73ba1f43"
],
"index": "pypi",
"version": "==2.4.3"
"version": "==2.5.0"
},
"uritemplate": {
"hashes": [
@ -848,10 +847,10 @@
},
"asgiref": {
"hashes": [
"sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
"sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"
"sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5",
"sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"
],
"version": "==3.2.3"
"version": "==3.2.7"
},
"astroid": {
"hashes": [
@ -869,10 +868,10 @@
},
"autopep8": {
"hashes": [
"sha256:0f592a0447acea0c2b0a9602be1e4e3d86db52badd2e3c84f0193bfd89fd3a43"
"sha256:cc6be1dfd46f2c7fa00e84a357f1a269683985b09eaffb47654ed551194399eb"
],
"index": "pypi",
"version": "==1.5"
"version": "==1.5.1"
},
"bandit": {
"hashes": [
@ -900,10 +899,10 @@
},
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
"sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc",
"sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"
],
"version": "==7.0"
"version": "==7.1.1"
},
"colorama": {
"hashes": [
@ -915,48 +914,48 @@
},
"coverage": {
"hashes": [
"sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3",
"sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c",
"sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0",
"sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477",
"sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a",
"sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf",
"sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691",
"sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73",
"sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987",
"sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894",
"sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e",
"sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef",
"sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf",
"sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68",
"sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8",
"sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954",
"sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2",
"sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40",
"sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc",
"sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc",
"sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e",
"sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d",
"sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f",
"sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc",
"sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301",
"sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea",
"sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb",
"sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af",
"sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52",
"sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37",
"sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0"
"sha256:03f630aba2b9b0d69871c2e8d23a69b7fe94a1e2f5f10df5049c0df99db639a0",
"sha256:046a1a742e66d065d16fb564a26c2a15867f17695e7f3d358d7b1ad8a61bca30",
"sha256:0a907199566269e1cfa304325cc3b45c72ae341fbb3253ddde19fa820ded7a8b",
"sha256:165a48268bfb5a77e2d9dbb80de7ea917332a79c7adb747bd005b3a07ff8caf0",
"sha256:1b60a95fc995649464e0cd48cecc8288bac5f4198f21d04b8229dc4097d76823",
"sha256:1f66cf263ec77af5b8fe14ef14c5e46e2eb4a795ac495ad7c03adc72ae43fafe",
"sha256:2e08c32cbede4a29e2a701822291ae2bc9b5220a971bba9d1e7615312efd3037",
"sha256:3844c3dab800ca8536f75ae89f3cf566848a3eb2af4d9f7b1103b4f4f7a5dad6",
"sha256:408ce64078398b2ee2ec08199ea3fcf382828d2f8a19c5a5ba2946fe5ddc6c31",
"sha256:443be7602c790960b9514567917af538cac7807a7c0c0727c4d2bbd4014920fd",
"sha256:4482f69e0701139d0f2c44f3c395d1d1d37abd81bfafbf9b6efbe2542679d892",
"sha256:4a8a259bf990044351baf69d3b23e575699dd60b18460c71e81dc565f5819ac1",
"sha256:513e6526e0082c59a984448f4104c9bf346c2da9961779ede1fc458e8e8a1f78",
"sha256:5f587dfd83cb669933186661a351ad6fc7166273bc3e3a1531ec5c783d997aac",
"sha256:62061e87071497951155cbccee487980524d7abea647a1b2a6eb6b9647df9006",
"sha256:641e329e7f2c01531c45c687efcec8aeca2a78a4ff26d49184dce3d53fc35014",
"sha256:65a7e00c00472cd0f59ae09d2fb8a8aaae7f4a0cf54b2b74f3138d9f9ceb9cb2",
"sha256:6ad6ca45e9e92c05295f638e78cd42bfaaf8ee07878c9ed73e93190b26c125f7",
"sha256:73aa6e86034dad9f00f4bbf5a666a889d17d79db73bc5af04abd6c20a014d9c8",
"sha256:7c9762f80a25d8d0e4ab3cb1af5d9dffbddb3ee5d21c43e3474c84bf5ff941f7",
"sha256:85596aa5d9aac1bf39fe39d9fa1051b0f00823982a1de5766e35d495b4a36ca9",
"sha256:86a0ea78fd851b313b2e712266f663e13b6bc78c2fb260b079e8b67d970474b1",
"sha256:8a620767b8209f3446197c0e29ba895d75a1e272a36af0786ec70fe7834e4307",
"sha256:922fb9ef2c67c3ab20e22948dcfd783397e4c043a5c5fa5ff5e9df5529074b0a",
"sha256:9fad78c13e71546a76c2f8789623eec8e499f8d2d799f4b4547162ce0a4df435",
"sha256:a37c6233b28e5bc340054cf6170e7090a4e85069513320275a4dc929144dccf0",
"sha256:c3fc325ce4cbf902d05a80daa47b645d07e796a80682c1c5800d6ac5045193e5",
"sha256:cda33311cb9fb9323958a69499a667bd728a39a7aa4718d7622597a44c4f1441",
"sha256:db1d4e38c9b15be1521722e946ee24f6db95b189d1447fa9ff18dd16ba89f732",
"sha256:eda55e6e9ea258f5e4add23bcf33dc53b2c319e70806e180aecbff8d90ea24de",
"sha256:f372cdbb240e09ee855735b9d85e7f50730dcfb6296b74b95a3e5dea0615c4c1"
],
"index": "pypi",
"version": "==5.0.3"
"version": "==5.0.4"
},
"django": {
"hashes": [
"sha256:2f1ba1db8648484dd5c238fb62504777b7ad090c81c5f1fd8d5eb5ec21b5f283",
"sha256:c91c91a7ad6ef67a874a4f76f58ba534f9208412692a840e1d125eb5c279cb0a"
"sha256:642d8eceab321ca743ae71e0f985ff8fdca59f07aab3a9fb362c617d23e33a76",
"sha256:d4666c2edefa38c5ede0ec1655424c56dc47ceb04b6d8d62a7eac09db89545c1"
],
"index": "pypi",
"version": "==3.0.3"
"version": "==3.0.5"
},
"django-debug-toolbar": {
"hashes": [
@ -966,19 +965,19 @@
"index": "pypi",
"version": "==2.2"
},
"gitdb2": {
"gitdb": {
"hashes": [
"sha256:0375d983fd887d03c8942e81b1b0abc6c320cfb500cd3fe0d9c0eac87fbf2b52",
"sha256:b2b3a67090c17dc61f8407ca485e79ae811225ab5ebcd98ac5ee01448e8987b5"
"sha256:284a6a4554f954d6e737cddcff946404393e030b76a282c6640df8efd6b3da5e",
"sha256:598e0096bb3175a0aab3a0b5aedaa18a9a25c6707e0eca0695ba1a0baf1b2150"
],
"version": "==3.0.2"
"version": "==4.0.2"
},
"gitpython": {
"hashes": [
"sha256:620b3c729bbc143b498cfea77e302999deedc55faec5b1067086c9ef90e101bc",
"sha256:a43a5d88a5bbc3cf32bb5223e4b4e68fd716db5e9996cad6e561bbfee6e5f4af"
"sha256:43da89427bdf18bf07f1164c6d415750693b4d50e28fc9b68de706245147b9dd",
"sha256:e426c3b587bd58c482f0b7fe6145ff4ac7ae6c82673fc656f489719abca6f4cb"
],
"version": "==3.0.8"
"version": "==3.1.0"
},
"isort": {
"hashes": [
@ -1022,17 +1021,17 @@
},
"pathspec": {
"hashes": [
"sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424",
"sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"
"sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0",
"sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"
],
"version": "==0.7.0"
"version": "==0.8.0"
},
"pbr": {
"hashes": [
"sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b",
"sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488"
"sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c",
"sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"
],
"version": "==5.4.4"
"version": "==5.4.5"
},
"pycodestyle": {
"hashes": [
@ -1051,11 +1050,11 @@
},
"pylint-django": {
"hashes": [
"sha256:440beb814464928aedd2e21196bb6e47a83b63e2cbe886a701ba0f4a64206bbb",
"sha256:d5d113605a64cf0e638b707d4cb42106e626f8851bc30a44d5b22bd698ad8483"
"sha256:3a4cc19dd6301fc2d36c9fb6e15163001a6d12723c1f7f8c2249223c2a8c68f0",
"sha256:c9bbcff6b87ee8466fae274fd7aae3d2d3d4c4d1ea20c48cbce673e837e36048"
],
"index": "pypi",
"version": "==2.0.13"
"version": "==2.0.14"
},
"pylint-plugin-utils": {
"hashes": [
@ -1073,46 +1072,46 @@
},
"pyyaml": {
"hashes": [
"sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
"sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
"sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
"sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
"sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
"sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
"sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
"sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
"sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
"sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
"sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
],
"index": "pypi",
"version": "==5.3"
"version": "==5.3.1"
},
"regex": {
"hashes": [
"sha256:07b39bf943d3d2fe63d46281d8504f8df0ff3fe4c57e13d1656737950e53e525",
"sha256:0932941cdfb3afcbc26cc3bcf7c3f3d73d5a9b9c56955d432dbf8bbc147d4c5b",
"sha256:0e182d2f097ea8549a249040922fa2b92ae28be4be4895933e369a525ba36576",
"sha256:10671601ee06cf4dc1bc0b4805309040bb34c9af423c12c379c83d7895622bb5",
"sha256:23e2c2c0ff50f44877f64780b815b8fd2e003cda9ce817a7fd00dea5600c84a0",
"sha256:26ff99c980f53b3191d8931b199b29d6787c059f2e029b2b0c694343b1708c35",
"sha256:27429b8d74ba683484a06b260b7bb00f312e7c757792628ea251afdbf1434003",
"sha256:3e77409b678b21a056415da3a56abfd7c3ad03da71f3051bbcdb68cf44d3c34d",
"sha256:4e8f02d3d72ca94efc8396f8036c0d3bcc812aefc28ec70f35bb888c74a25161",
"sha256:4eae742636aec40cf7ab98171ab9400393360b97e8f9da67b1867a9ee0889b26",
"sha256:6a6ae17bf8f2d82d1e8858a47757ce389b880083c4ff2498dba17c56e6c103b9",
"sha256:6a6ba91b94427cd49cd27764679024b14a96874e0dc638ae6bdd4b1a3ce97be1",
"sha256:7bcd322935377abcc79bfe5b63c44abd0b29387f267791d566bbb566edfdd146",
"sha256:98b8ed7bb2155e2cbb8b76f627b2fd12cf4b22ab6e14873e8641f266e0fb6d8f",
"sha256:bd25bb7980917e4e70ccccd7e3b5740614f1c408a642c245019cff9d7d1b6149",
"sha256:d0f424328f9822b0323b3b6f2e4b9c90960b24743d220763c7f07071e0778351",
"sha256:d58e4606da2a41659c84baeb3cfa2e4c87a74cec89a1e7c56bee4b956f9d7461",
"sha256:e3cd21cc2840ca67de0bbe4071f79f031c81418deb544ceda93ad75ca1ee9f7b",
"sha256:e6c02171d62ed6972ca8631f6f34fa3281d51db8b326ee397b9c83093a6b7242",
"sha256:e7c7661f7276507bce416eaae22040fd91ca471b5b33c13f8ff21137ed6f248c",
"sha256:ecc6de77df3ef68fee966bb8cb4e067e84d4d1f397d0ef6fce46913663540d77"
"sha256:08119f707f0ebf2da60d2f24c2f39ca616277bb67ef6c92b72cbf90cbe3a556b",
"sha256:0ce9537396d8f556bcfc317c65b6a0705320701e5ce511f05fc04421ba05b8a8",
"sha256:1cbe0fa0b7f673400eb29e9ef41d4f53638f65f9a2143854de6b1ce2899185c3",
"sha256:2294f8b70e058a2553cd009df003a20802ef75b3c629506be20687df0908177e",
"sha256:23069d9c07e115537f37270d1d5faea3e0bdded8279081c4d4d607a2ad393683",
"sha256:24f4f4062eb16c5bbfff6a22312e8eab92c2c99c51a02e39b4eae54ce8255cd1",
"sha256:295badf61a51add2d428a46b8580309c520d8b26e769868b922750cf3ce67142",
"sha256:2a3bf8b48f8e37c3a40bb3f854bf0121c194e69a650b209628d951190b862de3",
"sha256:4385f12aa289d79419fede43f979e372f527892ac44a541b5446617e4406c468",
"sha256:5635cd1ed0a12b4c42cce18a8d2fb53ff13ff537f09de5fd791e97de27b6400e",
"sha256:5bfed051dbff32fd8945eccca70f5e22b55e4148d2a8a45141a3b053d6455ae3",
"sha256:7e1037073b1b7053ee74c3c6c0ada80f3501ec29d5f46e42669378eae6d4405a",
"sha256:90742c6ff121a9c5b261b9b215cb476eea97df98ea82037ec8ac95d1be7a034f",
"sha256:a58dd45cb865be0ce1d5ecc4cfc85cd8c6867bea66733623e54bd95131f473b6",
"sha256:c087bff162158536387c53647411db09b6ee3f9603c334c90943e97b1052a156",
"sha256:c162a21e0da33eb3d31a3ac17a51db5e634fc347f650d271f0305d96601dc15b",
"sha256:c9423a150d3a4fc0f3f2aae897a59919acd293f4cb397429b120a5fcd96ea3db",
"sha256:ccccdd84912875e34c5ad2d06e1989d890d43af6c2242c6fcfa51556997af6cd",
"sha256:e91ba11da11cf770f389e47c3f5c30473e6d85e06d7fd9dcba0017d2867aab4a",
"sha256:ea4adf02d23b437684cd388d557bf76e3afa72f7fed5bbc013482cc00c816948",
"sha256:fb95debbd1a824b2c4376932f2216cc186912e389bdb0e27147778cf6acb3f89"
],
"version": "==2020.1.8"
"version": "==2020.4.4"
},
"six": {
"hashes": [
@ -1121,19 +1120,19 @@
],
"version": "==1.14.0"
},
"smmap2": {
"smmap": {
"hashes": [
"sha256:0555a7bf4df71d1ef4218e4807bbf9b201f910174e6e08af2e138d4e517b4dde",
"sha256:29a9ffa0497e7f2be94ca0ed1ca1aa3cd4cf25a1f6b4f5f87f74b46ed91d609a"
"sha256:171484fe62793e3626c8b05dd752eb2ca01854b0c55a1efc0dc4210fccb65446",
"sha256:5fead614cf2de17ee0707a8c6a5f2aa5a2fc6c698c70993ba42f515485ffda78"
],
"version": "==2.0.5"
"version": "==3.0.1"
},
"sqlparse": {
"hashes": [
"sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177",
"sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
],
"version": "==0.3.0"
"version": "==0.3.1"
},
"stevedore": {
"hashes": [
@ -1177,11 +1176,11 @@
},
"unittest-xml-reporting": {
"hashes": [
"sha256:6584562cde8226fc79fa29e38903c669a02799074a563bb0b70fcd3a8e87829c",
"sha256:dd8046a64dc62f3d30301523a54992e0be75a945194491e0a3b718130cb429e0"
"sha256:74eaf7739a7957a74f52b8187c5616f61157372189bef0a32ba5c30bbc00e58a",
"sha256:e09b8ae70cce9904cdd331f53bf929150962869a5324ab7ff3dd6c8b87e01f7d"
],
"index": "pypi",
"version": "==3.0.1"
"version": "==3.0.2"
},
"wrapt": {
"hashes": [

View File

@ -23,6 +23,8 @@ services:
server:
image: beryju/passbook:${SERVER_TAG:-latest}
command:
- ./manage.py
- bootstrap
- uwsgi
- uwsgi.ini
environment:
@ -42,6 +44,8 @@ services:
worker:
image: beryju/passbook:${SERVER_TAG:-latest}
command:
- ./manage.py
- bootstrap
- celery
- worker
- --autoscale=10,3

View File

@ -12,6 +12,8 @@ The following objects are passed into the variable:
- `request.obj`: A Django Model instance. This is only set if the Policy is ran against an object.
- `pb_is_sso_flow`: Boolean which is true if request was initiated by authenticating through an external Provider.
- `pb_is_group_member(user, group_name)`: Function which checks if `user` is member of a Group with Name `gorup_name`.
- `pb_logger`: Standard Python Logger Object, which can be used to debug expressions.
- `pb_client_ip`: Client's IP Address.
There are also the following custom filters available:

View File

@ -1,6 +1,6 @@
apiVersion: v1
appVersion: "0.8.6-beta"
appVersion: "0.8.15-beta"
description: A Helm chart for passbook.
name: passbook
version: "0.8.6-beta"
version: "0.8.15-beta"
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png

View File

@ -25,19 +25,19 @@ spec:
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
containerPort: 80
protocol: TCP
livenessProbe:
initialDelaySeconds: 10
timeoutSeconds: 5
httpGet:
path: /-/ping
path: /
port: http
readinessProbe:
initialDelaySeconds: 10
timeoutSeconds: 5
httpGet:
path: /-/ping
path: /
port: http
resources:
requests:

View File

@ -11,7 +11,7 @@ metadata:
spec:
type: ClusterIP
ports:
- port: 8080
- port: 80
targetPort: http
protocol: TCP
name: http

View File

@ -18,7 +18,7 @@ spec:
labels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
passbook.io/component: web
k8s.passbook.io/component: web
spec:
volumes:
- name: config-volume
@ -27,9 +27,12 @@ spec:
initContainers:
- name: passbook-database-migrations
image: "beryju/passbook:{{ .Values.image.tag }}"
imagePullPolicy: Always
command:
- ./manage.py
args:
- bootstrap
- ./manage.py
- migrate
volumeMounts:
- mountPath: /etc/passbook
@ -57,10 +60,12 @@ spec:
containers:
- name: {{ .Chart.Name }}
image: "beryju/passbook:{{ .Values.image.tag }}"
imagePullPolicy: IfNotPresent
imagePullPolicy: Always
command:
- uwsgi
- ./manage.py
args:
- bootstrap
- uwsgi
- uwsgi.ini
volumeMounts:
- mountPath: /etc/passbook

View File

@ -18,4 +18,4 @@ spec:
selector:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
passbook.io/component: web
k8s.passbook.io/component: web

View File

@ -18,7 +18,7 @@ spec:
labels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
passbook.io/component: worker
k8s.passbook.io/component: worker
spec:
volumes:
- name: config-volume
@ -29,8 +29,10 @@ spec:
image: "beryju/passbook:{{ .Values.image.tag }}"
imagePullPolicy: IfNotPresent
command:
- celery
- ./manage.py
args:
- bootstrap
- celery
- worker
- --autoscale=10,3
- -E

View File

@ -2,7 +2,7 @@
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
image:
tag: 0.8.6-beta
tag: 0.8.15-beta
nameOverride: ""

View File

@ -27,7 +27,7 @@ nav:
- Sentry: integrations/services/sentry/index.md
- Ansible Tower/AWX: integrations/services/tower-awx/index.md
repo_name: "BeryJu.org/passbook"
repo_name: "BeryJu/passbook"
repo_url: https://github.com/BeryJu/passbook
theme:
name: "material"

View File

@ -1,2 +1,2 @@
"""passbook"""
__version__ = "0.8.6-beta"
__version__ = "0.8.15-beta"

View File

@ -48,7 +48,7 @@ class YAMLField(forms.CharField):
def prepare_value(self, value):
if isinstance(value, InvalidYAMLInput):
return value
return yaml.dump(value, explicit_start=True)
return yaml.dump(value, explicit_start=True, default_flow_style=False)
def has_changed(self, initial, data):
if super().has_changed(initial, data):

View File

@ -1,40 +0,0 @@
"""passbook form helpers"""
from django import forms
from passbook.admin.fields import YAMLField
class TagModelForm(forms.ModelForm):
"""Base form for models that have attributes"""
def __init__(self, *args, **kwargs):
# Check if we have an instance, load tags otherwise use an empty dict
instance = kwargs.get("instance", None)
tags = instance.tags if instance else {}
# Make sure all predefined tags exist in tags, and set default if they don't
predefined_tags = (
self._meta.model().get_predefined_tags() # pylint: disable=no-member
)
for key, value in predefined_tags.items():
if key not in tags:
tags[key] = value
# Format JSON
kwargs["initial"]["tags"] = tags
super().__init__(*args, **kwargs)
def clean_tags(self):
"""Make sure all required tags are set"""
if hasattr(self.instance, "get_required_keys") and hasattr(
self.instance, "tags"
):
for key in self.instance.get_required_keys():
if key not in self.cleaned_data.get("tags"):
raise forms.ValidationError("Tag %s missing." % key)
return self.cleaned_data.get("tags")
# pylint: disable=too-few-public-methods
class TagModelFormMeta:
"""Base Meta class that uses the YAMLField"""
field_classes = {"tags": YAMLField}

View File

@ -1,7 +1,4 @@
"""passbook core source form fields"""
# from django import forms
SOURCE_FORM_FIELDS = ["name", "slug", "enabled", "policies"]
SOURCE_SERIALIZER_FIELDS = ["pk", "name", "slug", "enabled", "policies"]
# class SourceForm(forms.Form)
SOURCE_FORM_FIELDS = ["name", "slug", "enabled"]
SOURCE_SERIALIZER_FIELDS = ["pk", "name", "slug", "enabled"]

View File

@ -1,66 +1,68 @@
{% extends "administration/base.html" %}
{% extends "base/page.html" %}
{% load i18n %}
{% load utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-catalog"></i>
{% trans 'Audit Log' %}
</h1>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-top">
{% include 'partials/pagination.html' %}
{% block page_content %}
<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-catalog"></i>
{% trans 'Audit Log' %}
</h1>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Action' %}</th>
<th role="columnheader" scope="col">{% trans 'Context' %}</th>
<th role="columnheader" scope="col">{% trans 'User' %}</th>
<th role="columnheader" scope="col">{% trans 'Creation Date' %}</th>
<th role="columnheader" scope="col">{% trans 'Client IP' %}</th>
</tr>
</thead>
<tbody role="rowgroup">
{% for entry in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ entry.action }}</div>
<small>{{ entry.app|default:'-' }}</small>
</div>
</th>
<td role="cell">
<code>{{ entry.context }}</code>
</td>
<td role="cell">
<span>
{{ entry.user }}
</span>
</td>
<td role="cell">
<span>
{{ entry.created }}
</span>
</td>
<td role="cell">
<span>
{{ entry.client_ip }}
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom">
{% include 'partials/pagination.html' %}
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-top">
{% include 'partials/pagination.html' %}
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Action' %}</th>
<th role="columnheader" scope="col">{% trans 'Context' %}</th>
<th role="columnheader" scope="col">{% trans 'User' %}</th>
<th role="columnheader" scope="col">{% trans 'Creation Date' %}</th>
<th role="columnheader" scope="col">{% trans 'Client IP' %}</th>
</tr>
</thead>
<tbody role="rowgroup">
{% for entry in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ entry.action }}</div>
<small>{{ entry.app|default:'-' }}</small>
</div>
</th>
<td role="cell">
<code>{{ entry.context }}</code>
</td>
<td role="cell">
<span>
{{ entry.user }}
</span>
</td>
<td role="cell">
<span>
{{ entry.created }}
</span>
</td>
<td role="cell">
<span>
{{ entry.client_ip }}
</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom">
{% include 'partials/pagination.html' %}
</div>
</div>
</div>
</section>
</section>
</main>
{% endblock %}

View File

@ -1,6 +1,10 @@
{% extends "overview/base.html" %}
{% extends "base/page.html" %}
{% load static %}
{% load i18n %}
{% load is_active %}
{% load utils %}
{% block head %}
{{ block.super }}
@ -12,3 +16,84 @@
<script src="{% static 'node_modules/codemirror/mode/yaml/yaml.js' %}"></script>
<script src="{% static 'node_modules/codemirror/mode/jinja2/jinja2.js' %}"></script>
{% endblock %}
{% block page_content %}
<div class="pf-c-page__sidebar">
<div class="pf-c-page__sidebar-body">
<nav class="pf-c-nav" id="page-default-nav-example-primary-nav" aria-label="Global">
<ul class="pf-c-nav__list">
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:overview' %}"
class="pf-c-nav__link {% is_active_url 'passbook_admin:overview' %}">
{% trans 'System Status' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:applications' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:applications' 'passbook_admin:application-create' 'passbook_admin:application-update' 'passbook_admin:application-delete' %}">
{% trans 'Applications' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:sources' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:sources' 'passbook_admin:source-create' 'passbook_admin:source-update' 'passbook_admin:source-delete' %}">
{% trans 'Sources' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:providers' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:providers' 'passbook_admin:provider-create' 'passbook_admin:provider-update' 'passbook_admin:provider-delete' %}">
{% trans 'Providers' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:property-mappings' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:property-mappings' 'passbook_admin:property-mapping-create' 'passbook_admin:property-mapping-update' 'passbook_admin:property-mapping-delete' %}">
{% trans 'Property Mappings' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:factors' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}">
{% trans 'Factors' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:policies' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:policies' 'passbook_admin:policy-create' 'passbook_admin:policy-update' 'passbook_admin:policy-delete' 'passbook_admin:policy-test' %}">
{% trans 'Policies' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:certificate_key_pair' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:certificate_key_pair' 'passbook_admin:certificatekeypair-create' 'passbook_admin:certificatekeypair-update' 'passbook_admin:certificatekeypair-delete' %}">
{% trans 'Certificates' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:invitations' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}">
{% trans 'Invitations' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:users' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:users' 'passbook_admin:user-update' 'passbook_admin:user-delete' %}">
{% trans 'Users' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:groups' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:groups' 'passbook_admin:group-update' 'passbook_admin:group-delete' %}">
{% trans 'Groups' %}
</a>
</li>
</ul>
</nav>
</div>
</div>
<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
{% block content %}
{% endblock %}
</main>
{% endblock %}

View File

@ -0,0 +1,69 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-key"></i>
{% trans 'Certificate-Key Pairs' %}
</h1>
<p>{% trans "Import certificates of external providers or create certificates to sign requests with." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-top">
<div class="pf-c-toolbar__action-group">
<a href="{% url 'passbook_admin:certificatekeypair-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
</div>
{% include 'partials/pagination.html' %}
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Private Key available' %}</th>
<th role="columnheader" scope="col">{% trans 'Fingerprint' %}</th>
<th role="columnheader" scope="col">{% trans 'Provider Type' %}</th>
<th role="cell"></th>
</tr>
</thead>
<tbody role="rowgroup">
{% for kp in object_list %}
<tr role="row">
<th role="columnheader">
<div>
<div>{{ kp.name }}</div>
</div>
</th>
<td role="cell">
<span>
{% if kp.key_data is not None %}
{% trans 'Yes' %}
{% else %}
{% trans 'No' %}
{% endif %}
</span>
</td>
<td role="cell">
<span>
{{ kp.fingerprint }}
</span>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:certificatekeypair-update' pk=kp.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:certificatekeypair-delete' pk=kp.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pf-c-toolbar" id="page-layout-table-simple-toolbar-bottom">
{% include 'partials/pagination.html' %}
</div>
</div>
</section>
{% endblock %}

View File

@ -52,7 +52,7 @@
<small>{% trans 'Warning: Policy is not assigned.' %}</small>
{% else %}
<i class="pf-icon pf-icon-ok"></i>
<small>{% blocktrans with objects=policy.policymodel_set.all|join:', ' %}Assigned to objects {{ objects }}{% endblocktrans %}</small>
<small>{% blocktrans with object_count=policy.policymodel_set.all|length %}Assigned to {{ object_count }} objects.{% endblocktrans %}</small>
{% endif %}
</div>
</th>

View File

@ -11,16 +11,22 @@
{% endblock %}
{% block beneath_form %}
<p class="loading" style="display: none;">
<span class="spinner spinner-xs spinner-inline"></span> {% trans 'Processing, please wait...' %}
</p>
<div class="pf-c-form__group pf-m-action" style="display: none;" id="loading">
<div class="pf-c-form__horizontal-group">
<span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ block.super }}
<script>
$('form').on('submit', function () {
$('p.loading').show();
})
document.querySelector("form").addEventListener("submit", (e) => {
document.getElementById("loading").removeAttribute("style");
});
</script>
{% endblock %}

View File

@ -4,6 +4,7 @@ from django.urls import path
from passbook.admin.views import (
applications,
audit,
certificate_key_pair,
debug,
factors,
groups,
@ -148,6 +149,27 @@ urlpatterns = [
path(
"group/<uuid:pk>/delete/", groups.GroupDeleteView.as_view(), name="group-delete"
),
# Certificate-Key Pairs
path(
"crypto/certificates/",
certificate_key_pair.CertificateKeyPairListView.as_view(),
name="certificate_key_pair",
),
path(
"crypto/certificates/create/",
certificate_key_pair.CertificateKeyPairCreateView.as_view(),
name="certificatekeypair-create",
),
path(
"crypto/certificates/<uuid:pk>/update/",
certificate_key_pair.CertificateKeyPairUpdateView.as_view(),
name="certificatekeypair-update",
),
path(
"crypto/certificates/<uuid:pk>/delete/",
certificate_key_pair.CertificateKeyPairDeleteView.as_view(),
name="certificatekeypair-delete",
),
# Audit Log
path("audit/", audit.EventListView.as_view(), name="audit-log"),
# Groups

View File

@ -12,4 +12,4 @@ class EventListView(PermissionListMixin, ListView):
template_name = "administration/audit/list.html"
permission_required = "passbook_audit.view_event"
ordering = "-created"
paginate_by = 10
paginate_by = 20

View File

@ -0,0 +1,77 @@
"""passbook CertificateKeyPair administration"""
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from django.views.generic import DeleteView, ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from passbook.crypto.forms import CertificateKeyPairForm
from passbook.crypto.models import CertificateKeyPair
from passbook.lib.views import CreateAssignPermView
class CertificateKeyPairListView(LoginRequiredMixin, PermissionListMixin, ListView):
"""Show list of all keypairs"""
model = CertificateKeyPair
permission_required = "passbook_crypto.view_certificatekeypair"
ordering = "name"
paginate_by = 40
template_name = "administration/certificatekeypair/list.html"
class CertificateKeyPairCreateView(
SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create new CertificateKeyPair"""
model = CertificateKeyPair
form_class = CertificateKeyPairForm
permission_required = "passbook_crypto.add_certificatekeypair"
template_name = "generic/create.html"
success_url = reverse_lazy("passbook_admin:certificate_key_pair")
success_message = _("Successfully created CertificateKeyPair")
def get_context_data(self, **kwargs):
kwargs["type"] = "Certificate-Key Pair"
return super().get_context_data(**kwargs)
class CertificateKeyPairUpdateView(
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
"""Update certificatekeypair"""
model = CertificateKeyPair
form_class = CertificateKeyPairForm
permission_required = "passbook_crypto.change_certificatekeypair"
template_name = "generic/update.html"
success_url = reverse_lazy("passbook_admin:certificate_key_pair")
success_message = _("Successfully updated Certificate-Key Pair")
class CertificateKeyPairDeleteView(
SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
):
"""Delete certificatekeypair"""
model = CertificateKeyPair
permission_required = "passbook_crypto.delete_certificatekeypair"
template_name = "generic/delete.html"
success_url = reverse_lazy("passbook_admin:certificate_key_pair")
success_message = _("Successfully deleted Certificate-Key Pair")
def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message)
return super().delete(request, *args, **kwargs)

View File

@ -23,6 +23,8 @@ class InvitationListView(LoginRequiredMixin, PermissionListMixin, ListView):
model = Invitation
permission_required = "passbook_core.view_invitation"
template_name = "administration/invitation/list.html"
paginate_by = 10
ordering = "-expires"
class InvitationCreateView(

View File

@ -24,7 +24,8 @@ class PolicyListView(LoginRequiredMixin, PermissionListMixin, ListView):
model = Policy
permission_required = "passbook_core.view_policy"
paginate_by = 10
ordering = "order"
template_name = "administration/policy/list.html"
def get_context_data(self, **kwargs):
@ -34,7 +35,7 @@ class PolicyListView(LoginRequiredMixin, PermissionListMixin, ListView):
return super().get_context_data(**kwargs)
def get_queryset(self):
return super().get_queryset().order_by("order").select_subclasses()
return super().get_queryset().select_subclasses()
class PolicyCreateView(

View File

@ -22,6 +22,8 @@ class ProviderListView(LoginRequiredMixin, PermissionListMixin, ListView):
model = Provider
permission_required = "passbook_core.add_provider"
template_name = "administration/provider/list.html"
paginate_by = 10
ordering = "id"
def get_context_data(self, **kwargs):
kwargs["types"] = {

View File

@ -9,7 +9,11 @@ from django.shortcuts import redirect
from django.urls import reverse, reverse_lazy
from django.utils.translation import ugettext as _
from django.views.generic import DeleteView, DetailView, ListView, UpdateView
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
from guardian.mixins import (
PermissionListMixin,
PermissionRequiredMixin,
get_anonymous_user,
)
from passbook.admin.forms.users import UserForm
from passbook.core.models import Nonce, User
@ -25,6 +29,9 @@ class UserListView(LoginRequiredMixin, PermissionListMixin, ListView):
paginate_by = 40
template_name = "administration/user/list.html"
def get_queryset(self):
return super().get_queryset().exclude(pk=get_anonymous_user().pk)
class UserCreateView(
SuccessMessageMixin,

View File

@ -7,7 +7,7 @@ from rest_framework import routers
from structlog import get_logger
from passbook.api.permissions import CustomObjectPermissions
from passbook.audit.api.events import EventViewSet
from passbook.audit.api import EventViewSet
from passbook.core.api.applications import ApplicationViewSet
from passbook.core.api.factors import FactorViewSet
from passbook.core.api.groups import GroupViewSet

View File

@ -18,7 +18,7 @@ class EventSerializer(ModelSerializer):
"date",
"app",
"context",
"request_ip",
"client_ip",
"created",
]

View File

@ -33,11 +33,15 @@ def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
source[key] = sanitize_dict(value)
elif isinstance(value, models.Model):
model_content_type = ContentType.objects.get_for_model(value)
name = str(value)
if hasattr(value, "name"):
name = value.name
source[key] = sanitize_dict(
{
"app": model_content_type.app_label,
"name": model_content_type.model,
"model_name": model_content_type.model,
"pk": value.pk,
"name": name,
}
)
elif isinstance(value, UUID):
@ -133,6 +137,7 @@ class Event(UUIDModel):
action=self.action,
context=self.context,
client_ip=self.client_ip,
user=self.user,
)
return super().save(*args, **kwargs)

View File

@ -34,7 +34,6 @@ def on_user_login_failed(
sender, credentials: Dict[str, str], request: HttpRequest, **_
):
"""Failed Login"""
credentials.pop("password")
Event.new(EventAction.LOGIN_FAILED, **credentials).from_http(request)

View File

@ -17,7 +17,7 @@ class PropertyMappingSerializer(ModelSerializer):
class Meta:
model = PropertyMapping
fields = ["pk", "name", "__type__"]
fields = ["pk", "name", "expression", "__type__"]
class PropertyMappingViewSet(ReadOnlyModelViewSet):

View File

@ -10,7 +10,8 @@ class ApplicationForm(forms.ModelForm):
"""Application Form"""
provider = forms.ModelChoiceField(
queryset=Provider.objects.all().select_subclasses(), required=False
queryset=Provider.objects.all().order_by("pk").select_subclasses(),
required=False,
)
class Meta:

View File

@ -0,0 +1,33 @@
# Generated by Django 3.0.3 on 2020-02-21 22:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0009_auto_20200221_1410"),
]
operations = [
migrations.AlterField(
model_name="application",
name="meta_description",
field=models.TextField(blank=True, default=""),
),
migrations.AlterField(
model_name="application",
name="meta_icon_url",
field=models.TextField(blank=True, default=""),
),
migrations.AlterField(
model_name="application",
name="meta_launch_url",
field=models.URLField(blank=True, default=""),
),
migrations.AlterField(
model_name="application",
name="meta_publisher",
field=models.TextField(blank=True, default=""),
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 3.0.3 on 2020-02-22 18:22
from django.db import migrations
def fix_application_null(apps, schema_editor):
"""Fix Application meta_fields being null"""
Application = apps.get_model("passbook_core", "Application")
for app in Application.objects.all():
if app.meta_launch_url is None:
app.meta_launch_url = ""
if app.meta_icon_url is None:
app.meta_icon_url = ""
if app.meta_description is None:
app.meta_description = ""
if app.meta_publisher is None:
app.meta_publisher = ""
app.save()
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0010_auto_20200221_2208"),
]
operations = [migrations.RunPython(fix_application_null)]

View File

@ -139,10 +139,10 @@ class Application(ExportModelOperationsMixin("application"), PolicyModel):
"Provider", null=True, blank=True, default=None, on_delete=models.SET_DEFAULT
)
meta_launch_url = models.URLField(null=True, blank=True)
meta_icon_url = models.TextField(null=True, blank=True)
meta_description = models.TextField(null=True, blank=True)
meta_publisher = models.TextField(null=True, blank=True)
meta_launch_url = models.URLField(default="", blank=True)
meta_icon_url = models.TextField(default="", blank=True)
meta_description = models.TextField(default="", blank=True)
meta_publisher = models.TextField(default="", blank=True)
objects = InheritanceManager()

View File

@ -18,9 +18,11 @@ password_changed = Signal(providing_args=["user", "password"])
def invalidate_policy_cache(sender, instance, **_):
"""Invalidate Policy cache when policy is updated"""
from passbook.core.models import Policy
from passbook.policies.process import cache_key
if isinstance(instance, Policy):
LOGGER.debug("Invalidating policy cache", policy=instance)
keys = cache.keys("%s#*" % instance.pk)
prefix = cache_key(instance) + "*"
keys = cache.keys(prefix)
cache.delete_many(keys)
LOGGER.debug("Deleted %d keys", len(keys))

View File

@ -0,0 +1,27 @@
{% extends 'login/base.html' %}
{% load static %}
{% load i18n %}
{% load utils %}
{% block card_title %}
{{ title }} <span>(403)</span>
{% endblock %}
{% block card %}
<form>
<h3>{{ main }}</h3>
{% if no_referer %}
<p>{{ no_referer1 }}</p>
<p>{{ no_referer2 }}</p>
<p>{{ no_referer3 }}</p>
{% endif %}
{% if no_cookie %}
<p>{{ no_cookie1 }}</p>
<p>{{ no_cookie2 }}</p>
{% endif %}
{% if 'back' in request.GET %}
<a href="{% back %}" class="btn btn-primary btn-block btn-lg">{% trans 'Back' %}</a>
{% endif %}
</form>
{% endblock %}

View File

@ -0,0 +1,58 @@
{% extends "base/skeleton.html" %}
{% load static %}
{% load i18n %}
{% load is_active %}
{% load utils %}
{% block body %}
{% include 'partials/messages.html' %}
<div class="pf-c-page" id="page-default-nav-example">
<a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content">{% trans 'Skip to content' %}</a>
<header role="banner" class="pf-c-page__header ws-page-header">
<div class="pf-c-page__header-brand">
<div class="pf-c-page__header-brand-toggle">
<button class="pf-c-button pf-m-plain" type="button" id="page-default-nav-example-nav-toggle"
aria-label="Global navigation" aria-expanded="true"
aria-controls="page-default-nav-example-primary-nav">
<i class="fas fa-bars" aria-hidden="true"></i>
</button>
</div>
<a class="pf-c-page__header-brand-link">
<img class="pf-c-brand" src="{% static 'passbook/logo.png' %}" alt="" />
<img class="pf-c-brand" src="{% static 'passbook/brand.svg' %}" alt="passbook" />
</a>
</div>
<div class="pf-c-page__header-nav">
<nav class="pf-c-nav" aria-label="Nav">
<ul class="pf-c-nav__horizontal-list ws-top-nav">
<li class="pf-c-nav__item"><a class="pf-c-nav__link {% is_active_url 'passbook_core:overview' %}"
href="{% url 'passbook_core:overview' %}">{% trans 'Access' %}</a></li>
{% if user.is_superuser %}
<li class="pf-c-nav__item"><a class="pf-c-nav__link {% is_active_url 'passbook_admin:overview' %}"
href="{% url 'passbook_admin:overview' %}">{% trans 'Administrate' %}</a></li>
<li class="pf-c-nav__item"><a class="pf-c-nav__link {% is_active_url 'passbook_admin:audit-log' %}"
href="{% url 'passbook_admin:audit-log' %}">{% trans 'Monitor' %}</a></li>
{% endif %}
</ul>
</nav>
</div>
<div class="pf-c-page__header-tools">
<div class="pf-c-page__header-tools-group pf-m-icons">
<a href="{% url 'passbook_core:auth-logout' %}" class="pf-c-button pf-m-plain" type="button">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
</a>
</div>
<div class="pf-c-page__header-tools-group">
<a href="{% url 'passbook_core:user-settings' %}" class="pf-c-button">
{{ user.username }}
</a>
</div>
<img class="pf-c-avatar" src="{% gravatar user.email %}" alt="">
</div>
</header>
{% block page_content %}
{% endblock %}
</div>
{% endblock %}

View File

@ -8,7 +8,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}{% title %}{% endblock %}</title>
<title>{% block title %}{% trans title|default:"passbook" %}{% endblock %}</title>
<link rel="icon" type="image/png" href="{% static 'passbook/logo.png' %}">
<link rel="shortcut icon" type="image/png" href="{% static 'passbook/logo.png' %}">
<link rel="stylesheet" type="text/css" href="{% static 'node_modules/@patternfly/patternfly/patternfly.css' %}">

View File

@ -8,7 +8,6 @@
{% endblock %}
{% block content %}
{% config 'passbook.branding' as branding %}
<!-- HERO -->
<tr>
<td bgcolor="#7c72dc" align="center" style="padding: 0px 10px 0px 10px;">

View File

@ -5,7 +5,7 @@
<!DOCTYPE html>
<html>
<head>
<title>{% config passbook.branding %}</title>
<title>passbook</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
@ -118,7 +118,7 @@
<!-- ADDRESS -->
<tr>
<td bgcolor="#1b2a32" align="left" style="padding: 0px 30px 30px 30px; color: #E9ECEF; font-family: 'Metropolis', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 18px;">
<p style="margin: 0;"><a href="{% config 'passbook.branding' %}">{% config 'passbook.branding' %}</a></p>
<p style="margin: 0;"><a href="passbook">passbook</a></p>
</td>
</tr>
</table>

View File

@ -23,7 +23,7 @@
<header class="pf-c-login__header">
<img class="pf-c-brand" src="{% static 'passbook/logo.svg' %}" style="height: 60px;"
alt="passbook icon" />
<img class="pf-c-brand" src="{% static 'passbook/brand.svg' %}" style="height: 80px;"
<img class="pf-c-brand" src="{% static 'passbook/brand.svg' %}" style="height: 60px;"
alt="passbook branding" />
</header>
<main class="pf-c-login__main">
@ -54,9 +54,10 @@
<a href="{{ source.url }}" class="pf-c-login__main-footer-links-item-link">
{% if source.icon_path %}
<img src="{% static source.icon_path %}" alt="{{ source.name }}">
{% endif %}
{% if source.icon_url %}
{% elif source.icon_url %}
<img src="icon_url" alt="{{ source.name }}">
{% else %}
<i class="pf-icon pf-icon-arrow" title="{{ source.name }}"></i>
{% endif %}
</a>
</li>

View File

@ -4,27 +4,18 @@
{% load i18n %}
{% load utils %}
{% block head %}
{{ block.super }}
<style>
.pf-icon {
font-size: 48px;
text-align: center;
}
</style>
{% endblock %}
{% block card %}
<header class="login-pf-header">
<h1>{% trans title %}</h1>
</header>
<form method="POST">
{% csrf_token %}
{% include 'partials/form.html' %}
<span class="pf-icon pficon-error-circle-o btn-block"></span>
Access denied
{% if 'back' in request.GET %}
<a href="{% back %}" class="btn btn-primary btn-block btn-lg">{% trans 'Back' %}</a>
{% endif %}
<form method="POST" class="pf-c-form">
{% csrf_token %}
{% include 'partials/form.html' %}
<div class="pf-c-form__group">
<p>
<i class="pf-icon pf-icon-error-circle-o"></i>
{% trans 'Access denied' %}
</p>
</div>
{% if 'back' in request.GET %}
<a href="{% back %}" class="btn btn-primary btn-block btn-lg">{% trans 'Back' %}</a>
{% endif %}
</form>
{% endblock %}

View File

@ -5,7 +5,7 @@
{% load utils %}
{% block title %}
{% title title %}
{% trans title %}
{% endblock %}
{% block head %}

View File

@ -1,4 +1,4 @@
{% extends "base/skeleton.html" %}
{% extends "base/page.html" %}
{% load static %}
{% load i18n %}
@ -6,120 +6,9 @@
{% load is_active %}
{% load utils %}
{% block body %}
{% include 'partials/messages.html' %}
<div class="pf-c-page" id="page-default-nav-example">
<a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content">{% trans 'Skip to content' %}</a>
<header role="banner" class="pf-c-page__header">
<div class="pf-c-page__header-brand">
<div class="pf-c-page__header-brand-toggle">
<button class="pf-c-button pf-m-plain" type="button" id="page-default-nav-example-nav-toggle"
aria-label="Global navigation" aria-expanded="true"
aria-controls="page-default-nav-example-primary-nav">
<i class="fas fa-bars" aria-hidden="true"></i>
</button>
</div>
<a class="pf-c-page__header-brand-link">
<img class="pf-c-brand" src="{% static 'passbook/logo.png' %}" alt="" />
<img class="pf-c-brand" src="{% static 'passbook/brand.svg' %}" alt="passbook" />
</a>
</div>
<div class="pf-c-page__header-tools">
<div class="pf-c-page__header-tools-group pf-m-icons">
<a href="{% url 'passbook_core:auth-logout' %}" class="pf-c-button pf-m-plain" type="button">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
</a>
</div>
<div class="pf-c-page__header-tools-group">
<a href="{% url 'passbook_core:user-settings' %}" class="pf-c-button">
{{ user.username }}
</a>
</div>
<img class="pf-c-avatar" src="{% gravatar user.email %}" alt="">
</div>
</header>
<div class="pf-c-page__sidebar pf-m-dark">
<div class="pf-c-page__sidebar-body">
<nav class="pf-c-nav pf-m-dark" id="page-default-nav-example-primary-nav" aria-label="Global">
<ul class="pf-c-nav__list">
<li class="pf-c-nav__item">
<a href="{% url 'passbook_core:overview' %}" class="pf-c-nav__link {% is_active_url 'passbook_core:overview' %}">
{% trans 'Overview' %}
</a>
</li>
{% if user.is_superuser %}
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:overview' %}" class="pf-c-nav__link {% is_active_url 'passbook_admin:overview' %}">
{% trans 'System Status' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:applications' %}" class="pf-c-nav__link {% is_active 'passbook_admin:applications' 'passbook_admin:application-create' 'passbook_admin:application-update' 'passbook_admin:application-delete' %}">
{% trans 'Applications' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:sources' %}" class="pf-c-nav__link {% is_active 'passbook_admin:sources' 'passbook_admin:source-create' 'passbook_admin:source-update' 'passbook_admin:source-delete' %}">
{% trans 'Sources' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:providers' %}" class="pf-c-nav__link {% is_active 'passbook_admin:providers' 'passbook_admin:provider-create' 'passbook_admin:provider-update' 'passbook_admin:provider-delete' %}">
{% trans 'Providers' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:property-mappings' %}" class="pf-c-nav__link {% is_active 'passbook_admin:property-mappings' 'passbook_admin:property-mapping-create' 'passbook_admin:property-mapping-update' 'passbook_admin:property-mapping-delete' %}">
{% trans 'Property Mappings' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:factors' %}" class="pf-c-nav__link {% is_active 'passbook_admin:factors' 'passbook_admin:factor-create' 'passbook_admin:factor-update' 'passbook_admin:factor-delete' %}">
{% trans 'Factors' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:policies' %}" class="pf-c-nav__link {% is_active 'passbook_admin:policies' 'passbook_admin:policy-create' 'passbook_admin:policy-update' 'passbook_admin:policy-delete' 'passbook_admin:policy-test' %}">
{% trans 'Policies' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:invitations' %}" class="pf-c-nav__link {% is_active 'passbook_admin:invitations' 'passbook_admin:invitation-create' 'passbook_admin:invitation-update' 'passbook_admin:invitation-delete' 'passbook_admin:invitation-test' %}">
{% trans 'Invitations' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:users' %}" class="pf-c-nav__link {% is_active 'passbook_admin:users' 'passbook_admin:user-update' 'passbook_admin:user-delete' %}">
{% trans 'Users' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:groups' %}" class="pf-c-nav__link {% is_active 'passbook_admin:groups' 'passbook_admin:group-update' 'passbook_admin:group-delete' %}">
{% trans 'Groups' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:audit-log' %}" class="pf-c-nav__link {% is_active 'passbook_admin:audit-log' %}">
{% trans 'Audit Log' %}
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>Main title</h1>
<p>This is a demo of the Page component.</p>
</div>
</section>
<section class="pf-c-page__main-section">
</section>
{% endblock %}
</main>
</div>
{% block page_content %}
<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
{% block content %}
{% endblock %}
</main>
{% endblock %}

View File

@ -24,21 +24,21 @@
{% if applications %}
<div class="pf-l-gallery pf-m-gutter">
{% for app in applications %}
<a href="{{ app.launch_url }}" class="pf-c-card pf-m-hoverable pf-m-compact" id="card-1">
<a href="{{ app.meta_launch_url }}" class="pf-c-card pf-m-hoverable pf-m-compact" id="card-1">
<div class="pf-c-card__head">
{% if not app.icon_url %}
{% if not app.meta_icon_url %}
<i class="pf-icon pf-icon-arrow"></i>
{% else %}
<img class="app-icon" src="{{ app.icon_url }}" alt="{% trans 'Application Icon' %}">
<img class="app-icon pf-c-avatar" src="{{ app.meta_icon_url }}" alt="{% trans 'Application Icon' %}">
{% endif %}
</div>
<div class="pf-c-card__header pf-c-title pf-m-md">
<p id="card-1-check-label">{{ app.name }}</p>
<div class="pf-c-content">
<small>{{ app.subtitle }}</small>
<small>{{ app.meta_publisher }}</small>
</div>
</div>
<div class="pf-c-card__body">{% trans app.description %}</div>
<div class="pf-c-card__body">{% trans app.meta_description %}</div>
</a>
{% endfor %}
</div>

View File

@ -1,67 +1,69 @@
{% extends "overview/base.html" %}
{% extends "base/page.html" %}
{% load i18n %}
{% load is_active %}
{% load static %}
{% load passbook_user_settings %}
{% block content %}
<section class="pf-c-page__main-section">
<div class="pf-l-split pf-m-gutter">
<div class="pf-l-split__item">
<div class="pf-c-card">
<div class="pf-c-card__body">
<nav class="pf-c-nav" aria-label="Global">
<section class="pf-c-nav__section">
<h2 class="pf-c-nav__section-title">{% trans 'General Settings' %}</h2>
<ul class="pf-c-nav__list">
<li class="pf-c-nav__item">
<a href="{% url 'passbook_core:user-settings' %}" class="pf-c-nav__link {% is_active 'passbook_core:user-settings' %}">{% trans 'User Details' %}</a>
</li>
</ul>
</section>
{% user_factors as user_factors_loc %}
{% if user_factors_loc %}
<section class="pf-c-nav__section">
<h2 class="pf-c-nav__section-title">{% trans 'Factors' %}</h2>
<ul class="pf-c-nav__list">
{% for factor in user_factors_loc %}
<li class="pf-c-nav__item">
<a href="{% url factor.view_name %}" class="pf-c-nav__link {% is_active factor.view_name %}">
<i class="{{ factor.icon }}"></i>
{{ factor.name }}
</a>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
{% user_sources as user_sources_loc %}
{% if user_sources_loc %}
<section class="pf-c-nav__section">
<h2 class="pf-c-nav__section-title">{% trans 'Sources' %}</h2>
<ul class="pf-c-nav__list">
{% for source in user_sources_loc %}
<li class="pf-c-nav__item">
<a href="{{ source.view_name }}" class="pf-c-nav__link {% if user_settings.view_name == request.get_full_path %} pf-m-current {% endif %}">
<i class="{{ source.icon }}"></i>
{{ source.name }}
</a>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
</nav>
{% block page_content %}
<div class="pf-c-page__sidebar">
<div class="pf-c-page__sidebar-body">
<nav class="pf-c-nav" id="page-default-nav-example-primary-nav" aria-label="Global">
<section class="pf-c-nav__section">
<h2 class="pf-c-nav__section-title">{% trans 'General Settings' %}</h2>
<ul class="pf-c-nav__list">
<li class="pf-c-nav__item">
<a href="{% url 'passbook_core:user-settings' %}"
class="pf-c-nav__link {% is_active 'passbook_core:user-settings' %}">{% trans 'User Details' %}</a>
</li>
</ul>
</section>
{% user_factors as user_factors_loc %}
{% if user_factors_loc %}
<section class="pf-c-nav__section">
<h2 class="pf-c-nav__section-title">{% trans 'Factors' %}</h2>
<ul class="pf-c-nav__list">
{% for factor in user_factors_loc %}
<li class="pf-c-nav__item">
<a href="{% url factor.view_name %}" class="pf-c-nav__link {% is_active factor.view_name %}">
<i class="{{ factor.icon }}"></i>
{{ factor.name }}
</a>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
{% user_sources as user_sources_loc %}
{% if user_sources_loc %}
<section class="pf-c-nav__section">
<h2 class="pf-c-nav__section-title">{% trans 'Sources' %}</h2>
<ul class="pf-c-nav__list">
{% for source in user_sources_loc %}
<li class="pf-c-nav__item">
<a href="{{ source.view_name }}"
class="pf-c-nav__link {% if user_settings.view_name == request.get_full_path %} pf-m-current {% endif %}">
<i class="{{ source.icon }}"></i>
{{ source.name }}
</a>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
</nav>
</div>
</div>
<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
<section class="pf-c-page__main-section">
<div class="pf-l-split pf-m-gutter">
<div class="pf-l-split__item">
<div class="pf-c-card">
{% block page %}
{% endblock %}
</div>
</div>
</div>
<div class="pf-l-split__item">
<div class="pf-c-card">
{% block page %}
{% endblock %}
</div>
</div>
</div>
</section>
</section>
</main>
{% endblock %}

View File

@ -44,13 +44,13 @@ class LoginView(UserPassesTestMixin, FormView):
kwargs["primary_action"] = _("Log in")
kwargs["show_sign_up_notice"] = CONFIG.y("passbook.sign_up.enabled")
kwargs["sources"] = []
sources = Source.objects.filter(enabled=True).select_subclasses()
sources = (
Source.objects.filter(enabled=True).order_by("name").select_subclasses()
)
for source in sources:
ui_login_button = source.ui_login_button
if ui_login_button:
kwargs["sources"].append(ui_login_button)
# if kwargs["sources"]:
# self.template_name = "login/with_sources.html"
return super().get_context_data(**kwargs)
def get_user(self, uid_value) -> Optional[User]:
@ -231,7 +231,6 @@ class PasswordResetView(View):
login(request, nonce.user)
nonce.delete()
messages.success(
request,
_(("Temporarily authenticated with Nonce, " "please change your password")),
request, _(("Temporarily authenticated, please change your password")),
)
return redirect("passbook_core:user-change-password")

5
passbook/crypto/admin.py Normal file
View File

@ -0,0 +1,5 @@
"""passbook crypto model admin"""
from passbook.lib.admin import admin_autoregister
admin_autoregister("passbook_crypto")

10
passbook/crypto/apps.py Normal file
View File

@ -0,0 +1,10 @@
"""passbook crypto app config"""
from django.apps import AppConfig
class PassbookCryptoConfig(AppConfig):
"""passbook crypto app config"""
name = "passbook.crypto"
label = "passbook_crypto"
verbose_name = "passbook Crypto"

View File

@ -36,8 +36,7 @@ class CertificateBuilder:
x509.Name(
[
x509.NameAttribute(
NameOID.COMMON_NAME,
u"passbook Self-signed SAML Certificate",
NameOID.COMMON_NAME, u"passbook Self-signed Certificate",
),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"passbook"),
x509.NameAttribute(
@ -50,8 +49,7 @@ class CertificateBuilder:
x509.Name(
[
x509.NameAttribute(
NameOID.COMMON_NAME,
u"passbook Self-signed SAML Certificate",
NameOID.COMMON_NAME, u"passbook Self-signed Certificate",
),
]
)

27
passbook/crypto/forms.py Normal file
View File

@ -0,0 +1,27 @@
"""passbook Crypto forms"""
from django import forms
from django.utils.translation import gettext_lazy as _
from passbook.crypto.models import CertificateKeyPair
class CertificateKeyPairForm(forms.ModelForm):
"""CertificateKeyPair Form"""
class Meta:
model = CertificateKeyPair
fields = [
"name",
"certificate_data",
"key_data",
]
widgets = {
"name": forms.TextInput(),
"certificate_data": forms.Textarea(attrs={"class": "monospaced"}),
"key_data": forms.Textarea(attrs={"class": "monospaced"}),
}
labels = {
"certificate_data": _("Certificate"),
"key_data": _("Private Key"),
}

View File

@ -0,0 +1,67 @@
# Generated by Django 3.0.3 on 2020-03-03 21:45
import uuid
from django.db import migrations, models
def create_self_signed(apps, schema_editor):
CertificateKeyPair = apps.get_model("passbook_crypto", "CertificateKeyPair")
db_alias = schema_editor.connection.alias
from passbook.crypto.builder import CertificateBuilder
builder = CertificateBuilder()
builder.build()
CertificateKeyPair.objects.using(db_alias).create(
name="passbook Self-signed Certificate",
certificate_data=builder.certificate,
key_data=builder.private_key,
)
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="CertificateKeyPair",
fields=[
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("name", models.TextField()),
("certificate_data", models.TextField()),
("key_data", models.TextField(blank=True, default="")),
],
options={
"verbose_name": "Certificate-Key Pair",
"verbose_name_plural": "Certificate-Key Pairs",
},
),
migrations.RunPython(create_self_signed),
migrations.AlterField(
model_name="certificatekeypair",
name="certificate_data",
field=models.TextField(help_text="PEM-encoded Certificate data"),
),
migrations.AlterField(
model_name="certificatekeypair",
name="key_data",
field=models.TextField(
blank=True,
default="",
help_text="Optional Private Key. If this is set, you can use this keypair for encryption.",
),
),
]

View File

64
passbook/crypto/models.py Normal file
View File

@ -0,0 +1,64 @@
"""passbook crypto models"""
from binascii import hexlify
from typing import Optional
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import Certificate, load_pem_x509_certificate
from django.db import models
from django.utils.translation import gettext_lazy as _
from passbook.lib.models import CreatedUpdatedModel, UUIDModel
class CertificateKeyPair(UUIDModel, CreatedUpdatedModel):
"""CertificateKeyPair that can be used for signing or encrypting if `key_data`
is set, otherwise it can be used to verify remote data."""
name = models.TextField()
certificate_data = models.TextField(help_text=_("PEM-encoded Certificate data"))
key_data = models.TextField(
help_text=_(
"Optional Private Key. If this is set, you can use this keypair for encryption."
),
blank=True,
default="",
)
_cert: Optional[Certificate] = None
_key: Optional[RSAPrivateKey] = None
@property
def certificate(self) -> Certificate:
"""Get python cryptography Certificate instance"""
if not self._cert:
self._cert = load_pem_x509_certificate(
self.certificate_data.encode("utf-8"), default_backend()
)
return self._cert
@property
def private_key(self) -> Optional[RSAPrivateKey]:
"""Get python cryptography PrivateKey instance"""
if not self._key:
self._key = load_pem_private_key(
str.encode("\n".join([x.strip() for x in self.key_data.split("\n")])),
password=None,
backend=default_backend(),
)
return self._key
@property
def fingerprint(self) -> str:
"""Get SHA256 Fingerprint of certificate_data"""
return hexlify(self.certificate.fingerprint(hashes.SHA256())).decode("utf-8")
def __str__(self) -> str:
return f"Certificate-Key Pair {self.name} {self.fingerprint}"
class Meta:
verbose_name = _("Certificate-Key Pair")
verbose_name_plural = _("Certificate-Key Pairs")

View File

@ -2,7 +2,7 @@
from captcha.fields import ReCaptchaField
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from passbook.factors.captcha.models import CaptchaFactor
from passbook.factors.forms import GENERAL_FIELDS
@ -28,3 +28,8 @@ class CaptchaFactorForm(forms.ModelForm):
"public_key": forms.TextInput(),
"private_key": forms.TextInput(),
}
help_texts = {
"policies": _(
"Policies which determine if this factor applies to the current user."
)
}

View File

@ -1,7 +1,7 @@
"""passbook administration forms"""
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from passbook.factors.email.models import EmailFactor
from passbook.factors.forms import GENERAL_FIELDS
@ -41,3 +41,8 @@ class EmailFactorForm(forms.ModelForm):
"ssl_keyfile": _("SSL Keyfile (optional)"),
"ssl_certfile": _("SSL Certfile (optional)"),
}
help_texts = {
"policies": _(
"Policies which determine if this factor applies to the current user."
)
}

View File

@ -4,7 +4,7 @@ from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.core.validators import RegexValidator
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
from django_otp.models import Device
from passbook.factors.forms import GENERAL_FIELDS
@ -80,3 +80,8 @@ class OTPFactorForm(forms.ModelForm):
"order": forms.NumberInput(),
"policies": FilteredSelectMultiple(_("policies"), False),
}
help_texts = {
"policies": _(
"Policies which determine if this factor applies to the current user."
)
}

View File

@ -2,7 +2,7 @@
from django import forms
from django.conf import settings
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from passbook.factors.forms import GENERAL_FIELDS
from passbook.factors.password.models import PasswordFactor
@ -49,3 +49,8 @@ class PasswordFactorForm(forms.ModelForm):
"password_policies": FilteredSelectMultiple(_("password policies"), False),
"reset_factors": FilteredSelectMultiple(_("reset factors"), False),
}
help_texts = {
"policies": _(
"Policies which determine if this factor applies to the current user."
)
}

View File

@ -25,18 +25,6 @@ passbook:
password_reset:
# Enable password reset, passwords are reset in internal Database and in LDAP if ldap.reset_password is true
enabled: true
# Verification the user has to provide in order to be able to reset passwords. Can be any combination of `email`, `2fa`, `security_questions`
verification:
- email
# Text used in title, on login page and multiple other places
branding: passbook
login:
# Override URL used for logo
logo_url: null
# Override URL used for Background on Login page
bg_url: null
# Optionally add a subtext, placed below logo on the login page
subtext: null
footer:
links:
# Optionally add links to the footer on the login page
@ -46,14 +34,3 @@ passbook:
uid_fields:
- username
- email
# Provider-specific settings
ldap:
# Which field from `uid_fields` maps to which LDAP Attribute
login_field_map:
username: sAMAccountName
email: mail # or userPrincipalName
user_attribute_map:
active_directory:
username: "%(sAMAccountName)s"
email: "%(mail)s"
name: "%(displayName)"

View File

@ -1,48 +0,0 @@
"""passbook lib fields"""
from itertools import chain
from django import forms
from django.contrib.postgres.utils import prefix_validation_error
from passbook.lib.widgets import DynamicArrayWidget
class DynamicArrayField(forms.Field):
"""Show array field as a dynamic amount of textboxes"""
default_error_messages = {
"item_invalid": "Item %(nth)s in the array did not validate: "
}
def __init__(self, base_field, **kwargs):
self.base_field = base_field
self.max_length = kwargs.pop("max_length", None)
kwargs.setdefault("widget", DynamicArrayWidget)
super().__init__(**kwargs)
def clean(self, value):
cleaned_data = []
errors = []
value = [x for x in value if x]
for index, item in enumerate(value):
try:
cleaned_data.append(self.base_field.clean(item))
except forms.ValidationError as error:
errors.append(
prefix_validation_error(
error,
self.error_messages["item_invalid"],
code="item_invalid",
params={"nth": index},
)
)
if errors:
raise forms.ValidationError(list(chain.from_iterable(errors)))
if not cleaned_data and self.required:
raise forms.ValidationError(self.error_messages["required"])
return cleaned_data
def has_changed(self, initial, data):
if not data and not initial:
return False
return super().has_changed(initial, data)

9
passbook/lib/logging.py Normal file
View File

@ -0,0 +1,9 @@
"""logging helpers"""
from os import getpid
# pylint: disable=unused-argument
def add_process_id(logger, method_name, event_dict):
"""Add the current process ID"""
event_dict["pid"] = getpid()
return event_dict

View File

View File

@ -0,0 +1,65 @@
"""passbook management command to bootstrap"""
from argparse import REMAINDER
from subprocess import Popen # nosec
from sys import exit as _exit
from sys import stderr, stdin, stdout
from time import sleep
from typing import List
from django.core.management.base import BaseCommand
from django.db import connection
from django.db.utils import OperationalError
from django_redis import get_redis_connection
from redis.exceptions import ConnectionError as RedisConnectionError
from structlog import get_logger
LOGGER = get_logger()
class Command(BaseCommand):
"""Bootstrap passbook, ensure Database and Cache are
reachable, and directories are writeable"""
help = """Bootstrap passbook, ensure Database and Cache are
reachable, and directories are writeable"""
def add_arguments(self, parser):
parser.add_argument("command", nargs=REMAINDER)
def check_database(self) -> bool:
"""Return true if database is reachable, false otherwise"""
try:
connection.cursor()
LOGGER.info("Database reachable")
return True
except OperationalError:
LOGGER.info("Database unreachable")
return False
def check_cache(self) -> bool:
"""Return true if cache is reachable, false otherwise"""
try:
con = get_redis_connection("default")
con.ping()
LOGGER.info("Cache reachable")
return True
except RedisConnectionError:
LOGGER.info("Cache unreachable")
return False
def handle(self, *args, **options):
LOGGER.info("passbook bootstrapping...")
should_check = True
while should_check:
should_check = not (self.check_database() and self.check_cache())
sleep(1)
LOGGER.info("Dependencies are up, starting command...")
commands: List[str] = options.get("command", ["exit", "1"])
proc = Popen(args=commands, stdout=stdout, stderr=stderr, stdin=stdin) # nosec
try:
proc.wait()
_exit(proc.returncode)
except KeyboardInterrupt:
LOGGER.info("Killing process")
proc.kill()
_exit(254)

View File

@ -1,29 +1,28 @@
"""passbook sentry integration"""
from billiard.exceptions import WorkerLostError
from botocore.client import ClientError
from django.core.exceptions import DisallowedHost, ValidationError
from django.db import InternalError, OperationalError, ProgrammingError
from django_redis.exceptions import ConnectionInterrupted
from redis.exceptions import RedisError
from rest_framework.exceptions import APIException
from structlog import get_logger
LOGGER = get_logger()
class SentryIgnoredException(Exception):
"""Base Class for all errors that are supressed, and not sent to sentry."""
"""Base Class for all errors that are suppressed, and not sent to sentry."""
def before_send(event, hint):
"""Check if error is database error, and ignore if so"""
from django_redis.exceptions import ConnectionInterrupted
from django.db import OperationalError, InternalError
from django.core.exceptions import ValidationError
from rest_framework.exceptions import APIException
from billiard.exceptions import WorkerLostError
from django.core.exceptions import DisallowedHost
from botocore.client import ClientError
from redis.exceptions import RedisError
ignored_classes = (
OperationalError,
InternalError,
ProgrammingError,
ConnectionInterrupted,
APIException,
InternalError,
ConnectionResetError,
WorkerLostError,
DisallowedHost,

View File

@ -3,11 +3,9 @@ from hashlib import md5
from urllib.parse import urlencode
from django import template
from django.apps import apps
from django.db.models import Model
from django.template import Context
from django.utils.html import escape
from django.utils.translation import ugettext as _
from passbook.lib.config import CONFIG
from passbook.lib.utils.urls import is_url_absolute
@ -40,38 +38,6 @@ def fieldtype(field):
return field.__class__.__name__
@register.simple_tag(takes_context=True)
def title(context: Context, *title) -> str:
"""Return either just branding or title - branding"""
branding = CONFIG.y("passbook.branding", "passbook")
if not title:
return branding
if "request" not in context:
return ""
resolver_match = context.request.resolver_match
if not resolver_match:
return ""
# Include App Title in title
app = ""
if resolver_match.namespace != "":
dj_app = None
namespace = context.request.resolver_match.namespace.split(":")[0]
# New label (App URL Namespace == App Label)
dj_app = apps.get_app_config(namespace)
title_modifier = getattr(dj_app, "title_modifier", None)
if title_modifier:
app_title = dj_app.title_modifier(context.request)
app = app_title + " -"
return _(
"%(title)s - %(app)s %(branding)s"
% {
"title": " - ".join([str(x) for x in title]),
"branding": branding,
"app": app,
}
)
@register.simple_tag
def config(path, default=""):
"""Get a setting from the database. Returns default is setting doesn't exist."""

View File

@ -9,6 +9,7 @@ from jinja2.nativetypes import NativeEnvironment
from structlog import get_logger
from passbook.factors.view import AuthenticationView
from passbook.lib.utils.http import get_client_ip
from passbook.policies.types import PolicyRequest, PolicyResult
if TYPE_CHECKING:
@ -18,7 +19,7 @@ LOGGER = get_logger()
class Evaluator:
"""Validate and evaulate jinja2-based expressions"""
"""Validate and evaluate jinja2-based expressions"""
_env: NativeEnvironment
@ -50,11 +51,15 @@ class Evaluator:
"""Return dictionary with additional global variables passed to expression"""
# update passbook/policies/expression/templates/policy/expression/form.html
# update docs/policies/expression/index.md
kwargs["pb_is_sso_flow"] = request.http_request.session.get(
AuthenticationView.SESSION_IS_SSO_LOGIN, False
)
kwargs["pb_is_group_member"] = Evaluator.jinja2_func_is_group_member
kwargs["pb_logger"] = get_logger()
if request.http_request:
kwargs["pb_is_sso_flow"] = request.http_request.session.get(
AuthenticationView.SESSION_IS_SSO_LOGIN, False
)
kwargs["pb_client_ip"] = (
get_client_ip(request.http_request) or "255.255.255.255"
)
return kwargs
def evaluate(self, expression_source: str, request: PolicyRequest) -> PolicyResult:
@ -77,7 +82,7 @@ class Evaluator:
req=request,
)
return PolicyResult(False)
if isinstance(result, list) and len(result) == 2:
if isinstance(result, (list, tuple)) and len(result) == 2:
return PolicyResult(*result)
if result:
return PolicyResult(result)

View File

@ -9,15 +9,17 @@
<p>
Expression using <a href="https://jinja.palletsprojects.com/en/2.11.x/templates/">Jinja</a>. Following variables are available:
</p>
<ul>
<ul class="pf-c-list">
<li><code>request.user</code>: Passbook User Object (<a href="https://beryju.github.io/passbook/property-mappings/reference/user-object/">Reference</a>)</li>
<li><code>request.http_request</code>: Django HTTP Request Object (<a href="https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects">Reference</a>) </li>
<li><code>request.obj</code>: Model the Policy is run against. </li>
<li><code>pb_is_sso_flow</code>: Boolean which is true if request was initiated by authenticating through an external Provider.</li>
<li><code>pb_is_group_member(user, group_name)</code>: Function which checks if <code>user</code> is member of a Group with Name <code>group_name</code>.</li>
<li><code>pb_logger</code>: Standard Python Logger Object, which can be used to debug expressions.</li>
<li><code>pb_client_ip</code>: Client's IP Address.</li>
</ul>
<p>Custom Filters:</p>
<ul>
<ul class="pf-c-list">
<li><code>regex_match(regex)</code>: Checks if value matches <code>regex</code></li>
<li><code>regex_replace(regex, repl)</code>: Replace string matched by <code>regex</code> with <code>repl</code></li>
</ul>

View File

@ -0,0 +1,58 @@
"""evaluator tests"""
from django.core.exceptions import ValidationError
from django.test import TestCase
from guardian.shortcuts import get_anonymous_user
from passbook.policies.expression.evaluator import Evaluator
from passbook.policies.types import PolicyRequest
class TestEvaluator(TestCase):
"""Evaluator tests"""
def setUp(self):
self.request = PolicyRequest(user=get_anonymous_user())
def test_valid(self):
"""test simple value expression"""
template = "True"
evaluator = Evaluator()
self.assertEqual(evaluator.evaluate(template, self.request).passing, True)
def test_messages(self):
"""test expression with message return"""
template = "False, 'some message'"
evaluator = Evaluator()
result = evaluator.evaluate(template, self.request)
self.assertEqual(result.passing, False)
self.assertEqual(result.messages, ("some message",))
def test_invalid_syntax(self):
"""test invalid syntax"""
template = "{%"
evaluator = Evaluator()
result = evaluator.evaluate(template, self.request)
self.assertEqual(result.passing, False)
self.assertEqual(result.messages, ("tag name expected",))
def test_undefined(self):
"""test undefined result"""
template = "{{ foo.bar }}"
evaluator = Evaluator()
result = evaluator.evaluate(template, self.request)
self.assertEqual(result.passing, False)
self.assertEqual(result.messages, ("'foo' is undefined",))
def test_validate(self):
"""test validate"""
template = "True"
evaluator = Evaluator()
result = evaluator.validate(template)
self.assertEqual(result, True)
def test_validate_invalid(self):
"""test validate"""
template = "{%"
evaluator = Evaluator()
with self.assertRaises(ValidationError):
evaluator.validate(template)

View File

@ -5,16 +5,19 @@ from multiprocessing.connection import Connection
from django.core.cache import cache
from structlog import get_logger
from passbook.core.models import Policy
from passbook.core.models import Policy, User
from passbook.policies.exceptions import PolicyException
from passbook.policies.types import PolicyRequest, PolicyResult
LOGGER = get_logger()
def cache_key(policy, user):
def cache_key(policy: Policy, user: User = None) -> str:
"""Generate Cache key for policy"""
return f"policy_{policy.pk}#{user.pk}"
prefix = f"policy_{policy.pk}"
if user:
prefix += f"#{user.pk}"
return prefix
class PolicyProcess(Process):
@ -33,7 +36,7 @@ class PolicyProcess(Process):
def run(self):
"""Task wrapper to run policy checking"""
LOGGER.debug(
"Running policy",
"P_ENG(proc): Running policy",
policy=self.policy,
user=self.request.user,
process="PolicyProcess",
@ -41,13 +44,13 @@ class PolicyProcess(Process):
try:
policy_result = self.policy.passes(self.request)
except PolicyException as exc:
LOGGER.debug(exc)
LOGGER.debug("P_ENG(proc): error", exc=exc)
policy_result = PolicyResult(False, str(exc))
# Invert result if policy.negate is set
if self.policy.negate:
policy_result.passing = not policy_result.passing
LOGGER.debug(
"Got result",
"P_ENG(proc): Finished",
policy=self.policy,
result=policy_result,
process="PolicyProcess",
@ -56,5 +59,5 @@ class PolicyProcess(Process):
)
key = cache_key(self.policy, self.request.user)
cache.set(key, policy_result)
LOGGER.debug("Cached policy evaluation", key=key)
LOGGER.debug("P_ENG(proc): Cached policy evaluation", key=key)
self.connection.send(policy_result)

View File

@ -1,7 +1,7 @@
"""policy structures"""
from __future__ import annotations
from typing import TYPE_CHECKING, Tuple
from typing import TYPE_CHECKING, Optional, Tuple
from django.db.models import Model
from django.http import HttpRequest
@ -14,11 +14,13 @@ class PolicyRequest:
"""Data-class to hold policy request data"""
user: User
http_request: HttpRequest
obj: Model
http_request: Optional[HttpRequest]
obj: Optional[Model]
def __init__(self, user: User):
self.user = user
self.http_request = None
self.obj = None
def __str__(self):
return f"<PolicyRequest user={self.user}>"

View File

@ -34,7 +34,7 @@ class ApplicationGatewayProviderSerializer(ModelSerializer):
class Meta:
model = ApplicationGatewayProvider
fields = ["pk", "name", "host", "client"]
fields = ["pk", "name", "internal_host", "external_host", "client"]
read_only_fields = ["client"]

View File

@ -3,15 +3,12 @@
{% load utils %}
{% load i18n %}
{% block title %}
{% title 'Authorize Application' %}
{% block card_title %}
{% trans 'Authorize Application' %}
{% endblock %}
{% block card %}
<header class="login-pf-header">
<h1>{% trans 'Authorize Application' %}</h1>
</header>
<form method="POST">
<form method="POST" class="pf-c-form">
{% csrf_token %}
{% if not error %}
{% csrf_token %}
@ -20,32 +17,40 @@
{{ field }}
{% endif %}
{% endfor %}
<div class="form-group">
<div class="pf-c-form__group">
<p class="subtitle">
{% blocktrans with remote=application.name %}
You're about to sign into {{ remote }}
You're about to sign into {{ remote }}.
{% endblocktrans %}
</p>
<p>{% trans "Application requires following permissions" %}</p>
<ul>
<ul class="pf-c-list">
{% for scope in scopes_descriptions %}
<li>{{ scope }}</li>
{% endfor %}
</ul>
{{ form.errors }}
{{ form.non_field_errors }}
</div>
<div class="pf-c-form__group">
<p>
{% blocktrans with user=user %}
You are logged in as {{ user }}. Not you?
{% endblocktrans %}
<a href="{% url 'passbook_core:auth-logout' %}">{% trans 'Logout' %}</a>
</p>
<div class="form-group">
<input type="submit" class="btn btn-success btn-disabled btn-lg click-spinner" name="allow" value="{% trans 'Continue' %}">
<a href="{% back %}" class="btn btn-default btn-lg">{% trans "Cancel" %}</a>
</div>
<div class="form-group spinner-hidden hidden">
<div class="spinner"></div>
</div>
<div class="pf-c-form__group pf-m-action">
<input type="submit" class="pf-c-button pf-m-primary" name="allow" value="{% trans 'Continue' %}">
<a href="{% back %}" class="pf-c-button pf-m-secondary">{% trans "Cancel" %}</a>
</div>
<div class="pf-c-form__group" style="display: none;" id="loading">
<div class="pf-c-form__horizontal-group">
<span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</div>
</div>
{% else %}
@ -61,9 +66,8 @@
{% block scripts %}
<script>
$('.click-spinner').on('click', function (e) {
$('.spinner-hidden').removeClass('hidden');
$(e.target).addClass('disabled');
})
document.querySelector("form").addEventListener("submit", (e) => {
document.getElementById("loading").removeAttribute("style");
});
</script>
{% endblock %}

View File

@ -1,4 +1,6 @@
"""passbook auth oidc provider app config"""
from importlib import import_module
from django.apps import AppConfig
from django.db.utils import InternalError, OperationalError, ProgrammingError
from django.urls import include, path
@ -34,3 +36,5 @@ class PassbookProviderOIDCConfig(AppConfig):
include("oidc_provider.urls", namespace="oidc_provider"),
),
)
import_module("passbook.providers.oidc.signals")

View File

@ -0,0 +1,14 @@
"""passbook oidc claim helpers"""
from typing import Any, Dict
from passbook.core.models import User
def userinfo(claims: Dict[str, Any], user: User) -> Dict[str, Any]:
"""Populate claims from userdata"""
claims["name"] = user.name
claims["given_name"] = user.name
claims["family_name"] = user.name
claims["email"] = user.email
return claims

View File

@ -19,6 +19,8 @@ class OIDCProviderForm(forms.ModelForm):
self.fields["client_secret"].initial = generate_client_secret()
def save(self, *args, **kwargs):
self.instance.reuse_consent = False # This is managed by passbook
self.instance.require_consent = True # This is managed by passbook
response = super().save(*args, **kwargs)
# Check if openidprovider class instance exists
if not OpenIDProvider.objects.filter(oidc_client=self.instance).exists():

View File

@ -4,5 +4,6 @@ INSTALLED_APPS = [
"oidc_provider",
]
OIDC_AFTER_USERLOGIN_HOOK = "passbook.providers.oidc.lib.check_permissions"
OIDC_AFTER_USERLOGIN_HOOK = "passbook.providers.oidc.auth.check_permissions"
OIDC_IDTOKEN_INCLUDE_CLAIMS = True
OIDC_USERINFO = "passbook.providers.oidc.claims.userinfo"

View File

@ -0,0 +1,16 @@
"""OIDC Provider signals"""
from django.db.models.signals import post_save
from django.dispatch import receiver
from passbook.core.models import Application
from passbook.providers.oidc.models import OpenIDProvider
@receiver(post_save, sender=Application)
# pylint: disable=unused-argument
def on_application_save(sender, instance: Application, **_):
"""Synchronize application's skip_authorization with oidc_client's require_consent"""
if isinstance(instance.provider, OpenIDProvider):
instance.provider.oidc_client.require_consent = not instance.skip_authorization
instance.provider.oidc_client.save()
print("updating skip_authz")

View File

@ -3,15 +3,12 @@
{% load utils %}
{% load i18n %}
{% block title %}
{% title 'Authorize Application' %}
{% block card_title %}
{% trans 'Authorize Application' %}
{% endblock %}
{% block card %}
<header class="login-pf-header">
<h1>{% trans 'Authorize Application' %}</h1>
</header>
<form method="POST">
<form method="POST" class="pf-c-form">
{% csrf_token %}
{% if not error %}
{% csrf_token %}
@ -20,14 +17,14 @@
{{ field }}
{% endif %}
{% endfor %}
<div class="form-group">
<div class="pf-c-form__group">
<p class="subtitle">
{% blocktrans with remote=client.name %}
You're about to sign into {{ remote }}
You're about to sign into {{ remote }}.
{% endblocktrans %}
</p>
<p>{% trans "Application requires following permissions" %}</p>
<ul>
<ul class="pf-c-list">
{% for scope in scopes %}
<li>{{ scope.name }}</li>
{% endfor %}
@ -35,18 +32,26 @@
{{ hidden_inputs }}
{{ form.errors }}
{{ form.non_field_errors }}
</div>
<div class="pf-c-form__group">
<p>
{% blocktrans with user=user %}
You are logged in as {{ user }}. Not you?
{% endblocktrans %}
<a href="{% url 'passbook_core:auth-logout' %}">{% trans 'Logout' %}</a>
</p>
<div class="form-group">
<input type="submit" class="btn btn-success btn-disabled btn-lg click-spinner" name="allow" value="{% trans 'Continue' %}">
<a href="{% back %}" class="btn btn-default btn-lg">{% trans "Cancel" %}</a>
</div>
<div class="form-group spinner-hidden hidden">
<div class="spinner"></div>
</div>
<div class="pf-c-form__group pf-m-action">
<input type="submit" class="pf-c-button pf-m-primary" name="allow" value="{% trans 'Continue' %}">
<a href="{% back %}" class="pf-c-button pf-m-secondary">{% trans "Cancel" %}</a>
</div>
<div class="pf-c-form__group" style="display: none;" id="loading">
<div class="pf-c-form__horizontal-group">
<span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</div>
</div>
{% else %}
@ -62,9 +67,8 @@
{% block scripts %}
<script>
$('.click-spinner').on('click', function (e) {
$('.spinner-hidden').removeClass('hidden');
$(e.target).addClass('disabled');
})
document.querySelector("form").addEventListener("submit", (e) => {
document.getElementById("loading").removeAttribute("style");
});
</script>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends 'login/base.html' %}
{% load static %}
{% load i18n %}
{% load utils %}
{% block card_title %}
{% trans error %}
{% endblock %}
{% block card %}
<form>
<h3>{% trans description %}</h3>
{% if 'back' in request.GET %}
<a href="{% back %}" class="btn btn-primary btn-block btn-lg">{% trans 'Back' %}</a>
{% endif %}
</form>
{% endblock %}

View File

@ -24,9 +24,7 @@ class SAMLProviderSerializer(ModelSerializer):
"property_mappings",
"digest_algorithm",
"signature_algorithm",
"signing",
"signing_cert",
"signing_key",
"signing_kp",
]

View File

@ -9,7 +9,6 @@ from passbook.providers.saml.models import (
SAMLProvider,
get_provider_choices,
)
from passbook.providers.saml.utils.cert import CertificateBuilder
class SAMLProviderForm(forms.ModelForm):
@ -19,13 +18,6 @@ class SAMLProviderForm(forms.ModelForm):
choices=get_provider_choices(), label="Processor"
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
builder = CertificateBuilder()
builder.build()
self.fields["signing_cert"].initial = builder.certificate
self.fields["signing_key"].initial = builder.private_key
class Meta:
model = SAMLProvider
@ -41,9 +33,7 @@ class SAMLProviderForm(forms.ModelForm):
"property_mappings",
"digest_algorithm",
"signature_algorithm",
"signing",
"signing_cert",
"signing_key",
"signing_kp",
]
widgets = {
"name": forms.TextInput(),

View File

@ -0,0 +1,29 @@
# Generated by Django 3.0.3 on 2020-03-03 21:57
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_crypto", "0001_initial"),
("passbook_providers_saml", "0006_auto_20200217_2031"),
]
operations = [
migrations.RemoveField(model_name="samlprovider", name="signing",),
migrations.RemoveField(model_name="samlprovider", name="signing_cert",),
migrations.RemoveField(model_name="samlprovider", name="signing_key",),
migrations.AddField(
model_name="samlprovider",
name="singing_kp",
field=models.ForeignKey(
default=None,
help_text="Singing is enabled upon selection of a Key Pair.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="passbook_crypto.CertificateKeyPair",
),
),
]

View File

@ -0,0 +1,16 @@
# Generated by Django 3.0.3 on 2020-03-05 16:06
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("passbook_providers_saml", "0007_auto_20200303_2157"),
]
operations = [
migrations.RenameField(
model_name="samlprovider", old_name="singing_kp", new_name="signing_kp",
),
]

View File

@ -8,6 +8,7 @@ from django.utils.translation import ugettext_lazy as _
from structlog import get_logger
from passbook.core.models import PropertyMapping, Provider
from passbook.crypto.models import CertificateKeyPair
from passbook.lib.utils.reflection import class_to_path, path_to_class
from passbook.lib.utils.template import render_to_string
from passbook.providers.saml.processors.base import Processor
@ -74,9 +75,13 @@ class SAMLProvider(Provider):
default="rsa-sha256",
)
signing = models.BooleanField(default=True)
signing_cert = models.TextField(verbose_name=_("Singing Certificate"))
signing_key = models.TextField()
signing_kp = models.ForeignKey(
CertificateKeyPair,
default=None,
null=True,
help_text=_("Singing is enabled upon selection of a Key Pair."),
on_delete=models.SET_NULL,
)
form = "passbook.providers.saml.forms.SAMLProviderForm"
_processor = None

View File

@ -184,7 +184,7 @@ class Processor:
try:
self._extract_saml_request()
except KeyError:
raise CannotHandleAssertion(f"Couldn't find SAML request in user session:")
raise CannotHandleAssertion(f"Couldn't find SAML request in user session")
try:
self._decode_and_parse_request()

View File

@ -3,23 +3,17 @@
{% load utils %}
{% load i18n %}
{% block title %}
{% title 'Redirecting...' %}
{% block card_title %}
{% trans 'Redirecting...' %}
{% endblock %}
{% block card %}
<header class="login-pf-header">
<h1>{% trans 'Redirecting...' %}</h1>
</header>
<form method="POST" action="{{ url }}">
{% csrf_token %}
{% for key, value in attrs.items %}
<input type="hidden" name="{{ key }}" value="{{ value }}">
{% endfor %}
<div class="login-group">
<h3>
{% trans "Redirecting..." %}
</h3>
<p>
{% blocktrans with user=user %}
You are logged in as {{ user }}.
@ -34,6 +28,6 @@
{% block scripts %}
{{ block.super }}
<script>
$('form').submit();
document.querySelector("form").submit();
</script>
{% endblock %}

View File

@ -3,20 +3,10 @@
{% load utils %}
{% load i18n %}
{% block title %}
{% title 'Authorize Application' %}
{% endblock %}
{% block card %}
<header class="login-pf-header">
<h1>{% trans 'Authorize Application' %}</h1>
</header>
<form method="POST" action="{{ saml_params.acs_url }}">
<form method="POST" class="pf-c-form">
{% csrf_token %}
<input type="hidden" name="ACSUrl" value="{{ saml_params.acs_url }}">
<input type="hidden" name="RelayState" value="{{ saml_params.relay_state }}" />
<input type="hidden" name="SAMLResponse" value="{{ saml_params.saml_response }}" />
<div class="login-group">
<div class="pf-c-form__group">
<h3>
{% blocktrans with provider=provider.application.name %}
You're about to sign into {{ provider }}
@ -28,7 +18,9 @@
{% endblocktrans %}
<a href="{% url 'passbook_core:auth-logout' %}">{% trans 'Not you?' %}</a>
</p>
<input class="btn btn-primary btn-block btn-lg" type="submit" value="{% trans 'Continue' %}" />
</div>
<div class="pf-c-form__group pf-m-action">
<input class="pf-c-button pf-m-primary pf-m-block" type="submit" value="{% trans 'Continue' %}" />
</div>
</form>
{% endblock %}

View File

@ -8,7 +8,7 @@
<div class="c-form__horizontal-group">
<p>
Expression using <a href="https://jinja.palletsprojects.com/en/2.11.x/templates/">Jinja</a>. Following variables are available:
<ul>
<ul class="pf-c-list">
<li><code>user</code>: Passbook User Object (<a href="https://beryju.github.io/passbook/reference/property-mappings/user-object/">Reference</a>)</li>
<li><code>request</code>: Django HTTP Request Object (<a href="https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects">Reference</a>) </li>
<li><code>provider</code>: Passbook SAML Provider Object (<a href="https://github.com/BeryJu/passbook/blob/master/passbook/providers/saml/models.py#L16">Reference</a>) </li>

View File

@ -1,6 +1,7 @@
<?xml version="1.0"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="{{ entity_id }}">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
{% if cert_public_key %}
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
@ -8,13 +9,7 @@
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>{{ cert_public_key }}</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
{% endif %}
<md:NameIDFormat>{{ subject_format }}</md:NameIDFormat>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="{{ slo_url }}"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="{{ sso_post_url }}"/>

Some files were not shown because too many files have changed in this diff Show More