Compare commits
203 Commits
version/0.
...
version/0.
| Author | SHA1 | Date | |
|---|---|---|---|
| 7401278707 | |||
| e99f6e289b | |||
| 07da6ffa69 | |||
| dc18730094 | |||
| a202679bfb | |||
| 1edcda58ba | |||
| 5cb7f0794e | |||
| 7e8e3893eb | |||
| e91e286ebc | |||
| ef4a115b61 | |||
| b79b73f5c6 | |||
| 056e3ed15b | |||
| fb5e210af8 | |||
| e5e2615f15 | |||
| 6c72a9e2e8 | |||
| c04d0a373a | |||
| bd74e518a7 | |||
| 3b76af4eaa | |||
| 706448dc14 | |||
| 34793f7cef | |||
| ba96c9526e | |||
| 617432deaa | |||
| 36bf2be16d | |||
| 912ed343e6 | |||
| 2e15df295a | |||
| eaab3f62cb | |||
| aa615b0fd6 | |||
| b775f2788c | |||
| 9c28db3d89 | |||
| 67360bd6e9 | |||
| 4f6f8c7cae | |||
| 3b82ad798b | |||
| 8827f06ac1 | |||
| 251672a67d | |||
| 4ffc0e2a08 | |||
| 4e1808632d | |||
| 791627d3ce | |||
| f3df3a0157 | |||
| 6aaae53a19 | |||
| 4d84f6d598 | |||
| 4e2349b6d9 | |||
| cd57b8f7f3 | |||
| 40b1fc06b0 | |||
| 02fa217e28 | |||
| 6652514358 | |||
| dcd3dc9744 | |||
| d6afdc575e | |||
| 287b38efee | |||
| e805fb62fb | |||
| c92dda77f1 | |||
| f12fd78822 | |||
| caba183c9b | |||
| 3aeaa121a3 | |||
| a9f3118a7d | |||
| 054b819262 | |||
| 6b3411f63b | |||
| 6a8000ea0d | |||
| 352d4db0d7 | |||
| 4b665cfb8f | |||
| 4e12003944 | |||
| 6bfd465855 | |||
| e8670aa693 | |||
| 5263e750b1 | |||
| a2a9d73296 | |||
| 6befc9d627 | |||
| 73497a27cc | |||
| f3098418f2 | |||
| a5197963b2 | |||
| e4634bcc78 | |||
| 74da44a6a9 | |||
| 3324473cd0 | |||
| 39d8038533 | |||
| bbcf58705f | |||
| 7b5a0964b2 | |||
| 8eca76e464 | |||
| fb9ab368f8 | |||
| 877279b2ee | |||
| 301be4b411 | |||
| 728f527ccb | |||
| 3f1c790b1d | |||
| b00573bde2 | |||
| aeee3ad7f9 | |||
| ef021495ef | |||
| 061eab4b36 | |||
| 870e01f836 | |||
| e2ca72adf0 | |||
| 395ef43eae | |||
| a4cc653757 | |||
| db4ff20906 | |||
| 1f0fbd33b6 | |||
| 5de8d2721e | |||
| 0d65da9a9e | |||
| 4316ee4330 | |||
| 2ed9a1dbe3 | |||
| 8e03824d20 | |||
| 754dbdd0e5 | |||
| e13d348315 | |||
| 169f3ebe5b | |||
| f8ad604e85 | |||
| 774b9c8a61 | |||
| d8c522233e | |||
| 82d50f7eaa | |||
| 1c426c5136 | |||
| d6e14cc551 | |||
| c3917ebc2e | |||
| 7203bd37a3 | |||
| 597188c7ee | |||
| ac4c314042 | |||
| 05866d3544 | |||
| 6596bc6034 | |||
| c6661ef4d2 | |||
| 386e23dfac | |||
| 5d7220ca70 | |||
| 5de0d03acf | |||
| b0cc91f343 | |||
| 029a78f108 | |||
| 3f4a8dc4f6 | |||
| 32f6ba6302 | |||
| 8da0b14f29 | |||
| 83eb4aff02 | |||
| 927d02f591 | |||
| d04afcd6d0 | |||
| 89c6db66fd | |||
| e6ffa65a7e | |||
| 8a2f982a77 | |||
| 16cf6315e3 | |||
| 1d85874f41 | |||
| ff64182ae8 | |||
| a9ee67bf2d | |||
| e87d52a76b | |||
| 8b09cf55a2 | |||
| 0203d20759 | |||
| 7861e2e0bd | |||
| ad29d54bbf | |||
| c698ba37d9 | |||
| 6a53069653 | |||
| 152b2d863d | |||
| ee670d5e19 | |||
| 36e095671c | |||
| 1088b947a8 | |||
| c4a30c50ac | |||
| 2831df45a0 | |||
| ee5bac099f | |||
| 69f7b41044 | |||
| f9cede7b31 | |||
| 903cdeaa7f | |||
| e909e7fa8a | |||
| bee38551f3 | |||
| c0ec6388df | |||
| 8f08836885 | |||
| dd0d7e7481 | |||
| 25d0ac6534 | |||
| 971713d1aa | |||
| 5135d828b4 | |||
| b2c571bf1b | |||
| 6b1d30d230 | |||
| 3454760731 | |||
| 96846220c3 | |||
| a4f5678144 | |||
| a18baa3cb3 | |||
| dfedd4a7f1 | |||
| 897f64600a | |||
| c6eb015d18 | |||
| 54088239ab | |||
| aa9c7a6567 | |||
| 6c0c12c90a | |||
| c49b57ad1d | |||
| 2339e855bb | |||
| bdc019c7cf | |||
| 5e2fb6d56e | |||
| 3b9524cdfc | |||
| 7154f19668 | |||
| 8fedd9ec07 | |||
| 4ac87d8739 | |||
| e4f45eba0a | |||
| 4b3e0f0f96 | |||
| 482da81522 | |||
| c5226fd0e8 | |||
| 7806cff96f | |||
| fa504e4bf9 | |||
| 86cfb10b9b | |||
| f6b8171624 | |||
| 91ce7f7363 | |||
| 17060238f0 | |||
| c392c2a74b | |||
| 8cbaec8ba8 | |||
| 4750f8c653 | |||
| 69d2a1cf3b | |||
| 635f6c1ef2 | |||
| 18da7565c2 | |||
| 45699a1a69 | |||
| 5556e9f8e7 | |||
| 327bb09dd4 | |||
| 8ca23451c6 | |||
| b99e2b10fe | |||
| e966dff1a7 | |||
| 481fbedef2 | |||
| d104012eee | |||
| b03a508475 | |||
| 8ede4b6a13 | |||
| 41323afccc | |||
| 4a10b4999b | |||
| 20ee634cda |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.11.0-stable
|
current_version = 0.12.9-stable
|
||||||
tag = True
|
tag = True
|
||||||
commit = True
|
commit = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
||||||
|
|||||||
20
.github/dependabot.yml
vendored
20
.github/dependabot.yml
vendored
@ -6,12 +6,16 @@ updates:
|
|||||||
interval: daily
|
interval: daily
|
||||||
time: "04:00"
|
time: "04:00"
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
|
assignees:
|
||||||
|
- BeryJu
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/passbook/static/static"
|
directory: "/passbook/static/static"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
time: "04:00"
|
time: "04:00"
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
|
assignees:
|
||||||
|
- BeryJu
|
||||||
- package-ecosystem: pip
|
- package-ecosystem: pip
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
@ -20,3 +24,19 @@ updates:
|
|||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
assignees:
|
assignees:
|
||||||
- BeryJu
|
- BeryJu
|
||||||
|
- package-ecosystem: docker
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
time: "04:00"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
assignees:
|
||||||
|
- BeryJu
|
||||||
|
- package-ecosystem: docker
|
||||||
|
directory: "/proxy"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
time: "04:00"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
assignees:
|
||||||
|
- BeryJu
|
||||||
|
|||||||
54
.github/workflows/codeql-analysis.yml
vendored
54
.github/workflows/codeql-analysis.yml
vendored
@ -1,54 +0,0 @@
|
|||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master, admin-more-info, ci-deploy-dev, gh-pages, provider-saml-v2]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [master]
|
|
||||||
schedule:
|
|
||||||
- cron: '0 20 * * 2'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyse:
|
|
||||||
name: Analyse
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
# We must fetch at least the immediate parents so that if this is
|
|
||||||
# a pull request then we can checkout the head.
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
# If this run was triggered by a pull request event, then checkout
|
|
||||||
# the head of the pull request instead of the merge commit.
|
|
||||||
- run: git checkout HEAD^2
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v1
|
|
||||||
# Override language selection by uncommenting this and choosing your languages
|
|
||||||
# with:
|
|
||||||
# languages: go, javascript, csharp, python, cpp, java
|
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v1
|
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 https://git.io/JvXDl
|
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
||||||
# and modify them (or add more) to build your code if your project
|
|
||||||
# uses a compiled language
|
|
||||||
|
|
||||||
#- run: |
|
|
||||||
# make bootstrap
|
|
||||||
# make release
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v1
|
|
||||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@ -18,11 +18,11 @@ jobs:
|
|||||||
- name: Building Docker Image
|
- name: Building Docker Image
|
||||||
run: docker build
|
run: docker build
|
||||||
--no-cache
|
--no-cache
|
||||||
-t beryju/passbook:0.11.0-stable
|
-t beryju/passbook:0.12.9-stable
|
||||||
-t beryju/passbook:latest
|
-t beryju/passbook:latest
|
||||||
-f Dockerfile .
|
-f Dockerfile .
|
||||||
- name: Push Docker Container to Registry (versioned)
|
- name: Push Docker Container to Registry (versioned)
|
||||||
run: docker push beryju/passbook:0.11.0-stable
|
run: docker push beryju/passbook:0.12.9-stable
|
||||||
- name: Push Docker Container to Registry (latest)
|
- name: Push Docker Container to Registry (latest)
|
||||||
run: docker push beryju/passbook:latest
|
run: docker push beryju/passbook:latest
|
||||||
build-proxy:
|
build-proxy:
|
||||||
@ -48,11 +48,11 @@ jobs:
|
|||||||
cd proxy
|
cd proxy
|
||||||
docker build \
|
docker build \
|
||||||
--no-cache \
|
--no-cache \
|
||||||
-t beryju/passbook-proxy:0.11.0-stable \
|
-t beryju/passbook-proxy:0.12.9-stable \
|
||||||
-t beryju/passbook-proxy:latest \
|
-t beryju/passbook-proxy:latest \
|
||||||
-f Dockerfile .
|
-f Dockerfile .
|
||||||
- name: Push Docker Container to Registry (versioned)
|
- name: Push Docker Container to Registry (versioned)
|
||||||
run: docker push beryju/passbook-proxy:0.11.0-stable
|
run: docker push beryju/passbook-proxy:0.12.9-stable
|
||||||
- name: Push Docker Container to Registry (latest)
|
- name: Push Docker Container to Registry (latest)
|
||||||
run: docker push beryju/passbook-proxy:latest
|
run: docker push beryju/passbook-proxy:latest
|
||||||
build-static:
|
build-static:
|
||||||
@ -77,11 +77,11 @@ jobs:
|
|||||||
run: docker build
|
run: docker build
|
||||||
--no-cache
|
--no-cache
|
||||||
--network=$(docker network ls | grep github | awk '{print $1}')
|
--network=$(docker network ls | grep github | awk '{print $1}')
|
||||||
-t beryju/passbook-static:0.11.0-stable
|
-t beryju/passbook-static:0.12.9-stable
|
||||||
-t beryju/passbook-static:latest
|
-t beryju/passbook-static:latest
|
||||||
-f static.Dockerfile .
|
-f static.Dockerfile .
|
||||||
- name: Push Docker Container to Registry (versioned)
|
- name: Push Docker Container to Registry (versioned)
|
||||||
run: docker push beryju/passbook-static:0.11.0-stable
|
run: docker push beryju/passbook-static:0.12.9-stable
|
||||||
- name: Push Docker Container to Registry (latest)
|
- name: Push Docker Container to Registry (latest)
|
||||||
run: docker push beryju/passbook-static:latest
|
run: docker push beryju/passbook-static:latest
|
||||||
test-release:
|
test-release:
|
||||||
@ -114,5 +114,5 @@ jobs:
|
|||||||
SENTRY_PROJECT: passbook
|
SENTRY_PROJECT: passbook
|
||||||
SENTRY_URL: https://sentry.beryju.org
|
SENTRY_URL: https://sentry.beryju.org
|
||||||
with:
|
with:
|
||||||
tagName: 0.11.0-stable
|
tagName: 0.12.9-stable
|
||||||
environment: beryjuorg-prod
|
environment: beryjuorg-prod
|
||||||
|
|||||||
11
Dockerfile
11
Dockerfile
@ -25,7 +25,16 @@ RUN apt-get update && \
|
|||||||
pip install -r /requirements.txt --no-cache-dir && \
|
pip install -r /requirements.txt --no-cache-dir && \
|
||||||
apt-get remove --purge -y build-essential && \
|
apt-get remove --purge -y build-essential && \
|
||||||
apt-get autoremove --purge -y && \
|
apt-get autoremove --purge -y && \
|
||||||
adduser --system --no-create-home --uid 1000 --group --home /passbook passbook
|
# This is quite hacky, but docker has no guaranteed Group ID
|
||||||
|
# we could instead check for the GID of the socket and add the user dynamically,
|
||||||
|
# but then we have to drop permmissions later
|
||||||
|
groupadd -g 998 docker_998 && \
|
||||||
|
groupadd -g 999 docker_999 && \
|
||||||
|
adduser --system --no-create-home --uid 1000 --group --home /passbook passbook && \
|
||||||
|
usermod -a -G docker_998 passbook && \
|
||||||
|
usermod -a -G docker_999 passbook && \
|
||||||
|
mkdir /backups && \
|
||||||
|
chown passbook:passbook /backups
|
||||||
|
|
||||||
COPY ./passbook/ /passbook
|
COPY ./passbook/ /passbook
|
||||||
COPY ./manage.py /
|
COPY ./manage.py /
|
||||||
|
|||||||
2
Makefile
2
Makefile
@ -12,7 +12,7 @@ lint-fix:
|
|||||||
|
|
||||||
lint:
|
lint:
|
||||||
pyright passbook e2e lifecycle
|
pyright passbook e2e lifecycle
|
||||||
bandit -r passbook e2e lifecycle
|
bandit -r passbook e2e lifecycle -x node_modules
|
||||||
pylint passbook e2e lifecycle
|
pylint passbook e2e lifecycle
|
||||||
prospector
|
prospector
|
||||||
|
|
||||||
|
|||||||
4
Pipfile
4
Pipfile
@ -17,10 +17,10 @@ django-otp = "*"
|
|||||||
django-prometheus = "*"
|
django-prometheus = "*"
|
||||||
django-recaptcha = "*"
|
django-recaptcha = "*"
|
||||||
django-redis = "*"
|
django-redis = "*"
|
||||||
djangorestframework = "==3.11.1"
|
djangorestframework = "*"
|
||||||
django-storages = "*"
|
django-storages = "*"
|
||||||
djangorestframework-guardian = "*"
|
djangorestframework-guardian = "*"
|
||||||
drf-yasg = "*"
|
drf_yasg2 = "*"
|
||||||
facebook-sdk = "*"
|
facebook-sdk = "*"
|
||||||
ldap3 = "*"
|
ldap3 = "*"
|
||||||
lxml = "*"
|
lxml = "*"
|
||||||
|
|||||||
656
Pipfile.lock
generated
656
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "77737b63b2469755fd2a3d06b23054ae42b07b3f24cf887472f05fb8ab165cc6"
|
"sha256": "d1a9883d864e25f18e34b298b72b58db333a037571c7a20cefb7ba7a4037a434"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -25,17 +25,17 @@
|
|||||||
},
|
},
|
||||||
"amqp": {
|
"amqp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:9881f8e6fe23e3db9faa6cfd8c05390213e1d1b95c0162bc50552cad75bffa5f",
|
"sha256:5b9062d5c0812335c75434bf17ce33d7a20ecfedaa0733faec7379868eb4068a",
|
||||||
"sha256:a8fb8151eb9d12204c9f1784c0da920476077609fa0a70f2468001e3a4258484"
|
"sha256:fcd5b3baeeb7fc19b3486ff6d10543099d40ae1f5c9196eae695d1cde1b2f784"
|
||||||
],
|
],
|
||||||
"version": "==5.0.1"
|
"version": "==5.0.2"
|
||||||
},
|
},
|
||||||
"asgiref": {
|
"asgiref": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
|
"sha256:a5098bc870b80e7b872bff60bb363c7f2c2c89078759f6c47b53ff8c525a152e",
|
||||||
"sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
|
"sha256:cd88907ecaec59d78e4ac00ea665b03e571cb37e3a0e37b3702af1a9e86c365a"
|
||||||
],
|
],
|
||||||
"version": "==3.2.10"
|
"version": "==3.3.0"
|
||||||
},
|
},
|
||||||
"async-timeout": {
|
"async-timeout": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -46,10 +46,10 @@
|
|||||||
},
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594",
|
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
|
||||||
"sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"
|
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
|
||||||
],
|
],
|
||||||
"version": "==20.2.0"
|
"version": "==20.3.0"
|
||||||
},
|
},
|
||||||
"autobahn": {
|
"autobahn": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -74,18 +74,18 @@
|
|||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1627f97e050be59cfef839481acc73eba4b29e475a067f374a493e6b7f25601e",
|
"sha256:23d2575b7bd01c4e7153f283c1d1c12d329dabf78a6279d4192f2e752bb67b1a",
|
||||||
"sha256:8aafa1ec72451cf70fe6d8c7e86b1a83d2e195d4dda95e5bf21e40132a38c309"
|
"sha256:cb3f4c2f2576153b845e5b4f325d54a04f4ca00c156f2063965432bfa5d714dd"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.15.15"
|
"version": "==1.16.13"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3b9179edbba61c96f5d1eaa4328c9cda686bd461e102c5878c4880479c24e268",
|
"sha256:1b1b4cf5efd552ecc7f1ce0fc674d5fba56857db5ffe6394ee76edd1a568d7a6",
|
||||||
"sha256:f59437ff69d260faa876a2bb7d76debcbbb3b1a497e9ff49550a1a5501679720"
|
"sha256:b3b4b8fa33620f015c52e426a92e7db21b5e667ed4785c5fbc484ebfdb2b5153"
|
||||||
],
|
],
|
||||||
"version": "==1.18.15"
|
"version": "==1.19.13"
|
||||||
},
|
},
|
||||||
"cachetools": {
|
"cachetools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -96,18 +96,18 @@
|
|||||||
},
|
},
|
||||||
"celery": {
|
"celery": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:313930fddde703d8e37029a304bf91429cd11aeef63c57de6daca9d958e1f255",
|
"sha256:012c814967fe89e3f5d2cf49df2dba3de5f29253a7f4f2270e8fce6b901b4ebf",
|
||||||
"sha256:72138dc3887f68dc58e1a2397e477256f80f1894c69fa4337f8ed70be460375b"
|
"sha256:930c3acd55349d028c4e7104a7d377729cbcca19d9fce470c17172d9e7f9a8b6"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==5.0.0"
|
"version": "==5.0.2"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
|
"sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd",
|
||||||
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
|
"sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"
|
||||||
],
|
],
|
||||||
"version": "==2020.6.20"
|
"version": "==2020.11.8"
|
||||||
},
|
},
|
||||||
"cffi": {
|
"cffi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -152,19 +152,19 @@
|
|||||||
},
|
},
|
||||||
"channels": {
|
"channels": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:08e756406d7165cb32f6fc3090c0643f41ca9f7e0f7fada0b31194662f20f414",
|
"sha256:5cdd9c6b9ee663cdf1bbb00de7cdab885a3c418f9d32a29f04b09498828020f6",
|
||||||
"sha256:80a5ad1962ae039a3dcc0a5cb5212413e66e2f11ad9e9db8004834436daf3400"
|
"sha256:b02e150b48704ec3607d4168402ac5c26138dd183fcdb7f2aeb965e6e19fd558"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.4.0"
|
"version": "==3.0.1"
|
||||||
},
|
},
|
||||||
"channels-redis": {
|
"channels-redis": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3ce9832b64a2d7f950dd11e4f0dca784de7cbee99e95a3c345a1460c8878b682",
|
"sha256:18d63f6462a58011740dc8eeb57ea4b31ec220eb551cb71b27de9c6779a549de",
|
||||||
"sha256:41ee0af352d3b6b31a6b613985b51dc5695d2da60688c38e6caa0a1772735a9f"
|
"sha256:2fb31a63b05373f6402da2e6a91a22b9e66eb8b56626c6bfc93e156c734c5ae6"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.1.0"
|
"version": "==3.2.0"
|
||||||
},
|
},
|
||||||
"chardet": {
|
"chardet": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -216,27 +216,30 @@
|
|||||||
},
|
},
|
||||||
"cryptography": {
|
"cryptography": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:091d31c42f444c6f519485ed528d8b451d1a0c7bf30e8ca583a0cac44b8a0df6",
|
"sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538",
|
||||||
"sha256:18452582a3c85b96014b45686af264563e3e5d99d226589f057ace56196ec78b",
|
"sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f",
|
||||||
"sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5",
|
"sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77",
|
||||||
"sha256:1e4014639d3d73fbc5ceff206049c5a9a849cefd106a49fa7aaaa25cc0ce35cf",
|
"sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b",
|
||||||
"sha256:22e91636a51170df0ae4dcbd250d318fd28c9f491c4e50b625a49964b24fe46e",
|
"sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33",
|
||||||
"sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b",
|
"sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e",
|
||||||
"sha256:651448cd2e3a6bc2bb76c3663785133c40d5e1a8c1a9c5429e4354201c6024ae",
|
"sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb",
|
||||||
"sha256:726086c17f94747cedbee6efa77e99ae170caebeb1116353c6cf0ab67ea6829b",
|
"sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e",
|
||||||
"sha256:844a76bc04472e5135b909da6aed84360f522ff5dfa47f93e3dd2a0b84a89fa0",
|
"sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7",
|
||||||
"sha256:88c881dd5a147e08d1bdcf2315c04972381d026cdb803325c03fe2b4a8ed858b",
|
"sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297",
|
||||||
"sha256:96c080ae7118c10fcbe6229ab43eb8b090fccd31a09ef55f83f690d1ef619a1d",
|
"sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d",
|
||||||
"sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229",
|
"sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7",
|
||||||
"sha256:bb1f0281887d89617b4c68e8db9a2c42b9efebf2702a3c5bf70599421a8623e3",
|
"sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b",
|
||||||
"sha256:c447cf087cf2dbddc1add6987bbe2f767ed5317adb2d08af940db517dd704365",
|
"sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7",
|
||||||
"sha256:c4fd17d92e9d55b84707f4fd09992081ba872d1a0c610c109c18e062e06a2e55",
|
"sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4",
|
||||||
"sha256:d0d5aeaedd29be304848f1c5059074a740fa9f6f26b84c5b63e8b29e73dfc270",
|
"sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8",
|
||||||
"sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e",
|
"sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b",
|
||||||
"sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785",
|
"sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851",
|
||||||
"sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0"
|
"sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13",
|
||||||
|
"sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b",
|
||||||
|
"sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3",
|
||||||
|
"sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df"
|
||||||
],
|
],
|
||||||
"version": "==2.9.2"
|
"version": "==3.2.1"
|
||||||
},
|
},
|
||||||
"dacite": {
|
"dacite": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -248,10 +251,10 @@
|
|||||||
},
|
},
|
||||||
"daphne": {
|
"daphne": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1ca46d7419103958bbc9576fb7ba3b25b053006e22058bc97084ee1a7d44f4ba",
|
"sha256:60856f7efa0b1e1b969efa074e8698bd09de4713ecc06e6a4d19d04c66c4a3bd",
|
||||||
"sha256:aa64840015709bbc9daa3c4464a4a4d437937d6cda10a9b51e913eb319272553"
|
"sha256:b43e70d74ff832a634ff6c92badd208824e4530e08b340116517e5aad0aca774"
|
||||||
],
|
],
|
||||||
"version": "==2.5.0"
|
"version": "==3.0.0"
|
||||||
},
|
},
|
||||||
"defusedxml": {
|
"defusedxml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -263,11 +266,11 @@
|
|||||||
},
|
},
|
||||||
"django": {
|
"django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a2127ad0150ec6966655bedf15dbbff9697cc86d61653db2da1afa506c0b04cc",
|
"sha256:14a4b7cd77297fba516fc0d92444cc2e2e388aa9de32d7a68d4a83d58f5a4927",
|
||||||
"sha256:c93c28ccf1d094cbd00d860e83128a39e45d2c571d3b54361713aaaf9a94cac4"
|
"sha256:14b87775ffedab2ef6299b73343d1b4b41e5d4e2aa58c6581f114dbec01e3f8f"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.1.2"
|
"version": "==3.1.3"
|
||||||
},
|
},
|
||||||
"django-cors-middleware": {
|
"django-cors-middleware": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -310,11 +313,11 @@
|
|||||||
},
|
},
|
||||||
"django-otp": {
|
"django-otp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2fb1c8dbd7e7ae76a65b63d89d3d8c3e1105a48bc29830b81c6e417a89380658",
|
"sha256:8ba5ab9bd2738c7321376c349d7cce49cf4404e79f6804e0a3cc462a91728e18",
|
||||||
"sha256:fef1f2de9a52bc37e16211b98b4323e5b34fa24739116fbe3d1ff018c17ebea8"
|
"sha256:f523fb9dec420f28a29d3e2ad72ac06f64588956ed4f2b5b430d8e957ebb8287"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.0.1"
|
"version": "==1.0.2"
|
||||||
},
|
},
|
||||||
"django-prometheus": {
|
"django-prometheus": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -349,11 +352,10 @@
|
|||||||
},
|
},
|
||||||
"djangorestframework": {
|
"djangorestframework": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6dd02d5a4bd2516fb93f80360673bf540c3b6641fec8766b1da2870a5aa00b32",
|
"sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7"
|
||||||
"sha256:8b1ac62c581dbc5799b03e535854b92fc4053ecfe74bad3f9c05782063d4196b"
|
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.11.1"
|
"version": "==3.12.2"
|
||||||
},
|
},
|
||||||
"djangorestframework-guardian": {
|
"djangorestframework-guardian": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -371,13 +373,13 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.3.1"
|
"version": "==4.3.1"
|
||||||
},
|
},
|
||||||
"drf-yasg": {
|
"drf-yasg2": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5572e9d5baab9f6b49318169df9789f7399d0e3c7bdac8fdb8dfccf1d5d2b1ca",
|
"sha256:7037a8041eb5d1073fa504a284fc889685f93d0bfd008a963db1b366db786734",
|
||||||
"sha256:7d7af27ad16e18507e9392b2afd6b218fbffc432ec8dbea053099a2241e184ff"
|
"sha256:75e661ca5cf15eb44fcfab408c7b864f87c20794f564aa08b3a31817a857f19d"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.17.1"
|
"version": "==1.19.4"
|
||||||
},
|
},
|
||||||
"eight": {
|
"eight": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -402,10 +404,10 @@
|
|||||||
},
|
},
|
||||||
"google-auth": {
|
"google-auth": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:712dd7d140a9a1ea218e5688c7fcb04af71b431a29ec9ce433e384c60e387b98",
|
"sha256:5176db85f1e7e837a646cd9cede72c3c404ccf2e3373d9ee14b2db88febad440",
|
||||||
"sha256:9c0f71789438d703f77b94aad4ea545afaec9a65f10e6cc1bc8b89ce242244bb"
|
"sha256:b728625ff5dfce8f9e56a499c8a4eb51443a67f20f6d28b67d5774c310ec4b6b"
|
||||||
],
|
],
|
||||||
"version": "==1.22.1"
|
"version": "==1.23.0"
|
||||||
},
|
},
|
||||||
"gunicorn": {
|
"gunicorn": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -555,11 +557,11 @@
|
|||||||
},
|
},
|
||||||
"kubernetes": {
|
"kubernetes": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1a2472f8b01bc6aa87e3a34781f859bded5a5c8ff791a53d889a8bd6cc550430",
|
"sha256:72f095a1cd593401ff26b3b8d71749340394ca6d8413770ea28ce18efd5bcf4c",
|
||||||
"sha256:4af81201520977139a143f96123fb789fa351879df37f122916b9b6ed050bbaf"
|
"sha256:9a339a32d6c79e6461cb6050c3662cb4e33058b508d8d34ee5d5206add395828"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==11.0.0"
|
"version": "==12.0.0"
|
||||||
},
|
},
|
||||||
"ldap3": {
|
"ldap3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -571,40 +573,46 @@
|
|||||||
},
|
},
|
||||||
"lxml": {
|
"lxml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:05a444b207901a68a6526948c7cc8f9fe6d6f24c70781488e32fd74ff5996e3f",
|
"sha256:098fb713b31050463751dcc694878e1d39f316b86366fb9fe3fbbe5396ac9fab",
|
||||||
"sha256:08fc93257dcfe9542c0a6883a25ba4971d78297f63d7a5a26ffa34861ca78730",
|
"sha256:0e89f5d422988c65e6936e4ec0fe54d6f73f3128c80eb7ecc3b87f595523607b",
|
||||||
"sha256:107781b213cf7201ec3806555657ccda67b1fccc4261fb889ef7fc56976db81f",
|
"sha256:189ad47203e846a7a4951c17694d845b6ade7917c47c64b29b86526eefc3adf5",
|
||||||
"sha256:121b665b04083a1e85ff1f5243d4a93aa1aaba281bc12ea334d5a187278ceaf1",
|
"sha256:1d87936cb5801c557f3e981c9c193861264c01209cb3ad0964a16310ca1b3301",
|
||||||
"sha256:1fa21263c3aba2b76fd7c45713d4428dbcc7644d73dcf0650e9d344e433741b3",
|
"sha256:211b3bcf5da70c2d4b84d09232534ad1d78320762e2c59dedc73bf01cb1fc45b",
|
||||||
"sha256:2b30aa2bcff8e958cd85d907d5109820b01ac511eae5b460803430a7404e34d7",
|
"sha256:2358809cc64394617f2719147a58ae26dac9e21bae772b45cfb80baa26bfca5d",
|
||||||
"sha256:4b4a111bcf4b9c948e020fd207f915c24a6de3f1adc7682a2d92660eb4e84f1a",
|
"sha256:23c83112b4dada0b75789d73f949dbb4e8f29a0a3511647024a398ebd023347b",
|
||||||
"sha256:5591c4164755778e29e69b86e425880f852464a21c7bb53c7ea453bbe2633bbe",
|
"sha256:24e811118aab6abe3ce23ff0d7d38932329c513f9cef849d3ee88b0f848f2aa9",
|
||||||
"sha256:59daa84aef650b11bccd18f99f64bfe44b9f14a08a28259959d33676554065a1",
|
"sha256:2d5896ddf5389560257bbe89317ca7bcb4e54a02b53a3e572e1ce4226512b51b",
|
||||||
"sha256:5a9c8d11aa2c8f8b6043d845927a51eb9102eb558e3f936df494e96393f5fd3e",
|
"sha256:2d6571c48328be4304aee031d2d5046cbc8aed5740c654575613c5a4f5a11311",
|
||||||
"sha256:5dd20538a60c4cc9a077d3b715bb42307239fcd25ef1ca7286775f95e9e9a46d",
|
"sha256:2e311a10f3e85250910a615fe194839a04a0f6bc4e8e5bb5cac221344e3a7891",
|
||||||
"sha256:74f48ec98430e06c1fa8949b49ebdd8d27ceb9df8d3d1c92e1fdc2773f003f20",
|
"sha256:302160eb6e9764168e01d8c9ec6becddeb87776e81d3fcb0d97954dd51d48e0a",
|
||||||
"sha256:786aad2aa20de3dbff21aab86b2fb6a7be68064cbbc0219bde414d3a30aa47ae",
|
"sha256:3a7a380bfecc551cfd67d6e8ad9faa91289173bdf12e9cfafbd2bdec0d7b1ec1",
|
||||||
"sha256:7ad7906e098ccd30d8f7068030a0b16668ab8aa5cda6fcd5146d8d20cbaa71b5",
|
"sha256:3d9b2b72eb0dbbdb0e276403873ecfae870599c83ba22cadff2db58541e72856",
|
||||||
"sha256:80a38b188d20c0524fe8959c8ce770a8fdf0e617c6912d23fc97c68301bb9aba",
|
"sha256:475325e037fdf068e0c2140b818518cf6bc4aa72435c407a798b2db9f8e90810",
|
||||||
"sha256:8f0ec6b9b3832e0bd1d57af41f9238ea7709bbd7271f639024f2fc9d3bb01293",
|
"sha256:4b7572145054330c8e324a72d808c8c8fbe12be33368db28c39a255ad5f7fb51",
|
||||||
"sha256:92282c83547a9add85ad658143c76a64a8d339028926d7dc1998ca029c88ea6a",
|
"sha256:4fff34721b628cce9eb4538cf9a73d02e0f3da4f35a515773cce6f5fe413b360",
|
||||||
"sha256:94150231f1e90c9595ccc80d7d2006c61f90a5995db82bccbca7944fd457f0f6",
|
"sha256:56eff8c6fb7bc4bcca395fdff494c52712b7a57486e4fbde34c31bb9da4c6cc4",
|
||||||
"sha256:9dc9006dcc47e00a8a6a029eb035c8f696ad38e40a27d073a003d7d1443f5d88",
|
"sha256:573b2f5496c7e9f4985de70b9bbb4719ffd293d5565513e04ac20e42e6e5583f",
|
||||||
"sha256:a76979f728dd845655026ab991df25d26379a1a8fc1e9e68e25c7eda43004bed",
|
"sha256:7ecaef52fd9b9535ae5f01a1dd2651f6608e4ec9dc136fc4dfe7ebe3c3ddb230",
|
||||||
"sha256:aa8eba3db3d8761db161003e2d0586608092e217151d7458206e243be5a43843",
|
"sha256:803a80d72d1f693aa448566be46ffd70882d1ad8fc689a2e22afe63035eb998a",
|
||||||
"sha256:bea760a63ce9bba566c23f726d72b3c0250e2fa2569909e2d83cda1534c79443",
|
"sha256:8862d1c2c020cb7a03b421a9a7b4fe046a208db30994fc8ff68c627a7915987f",
|
||||||
"sha256:c3f511a3c58676147c277eff0224c061dd5a6a8e1373572ac817ac6324f1b1e0",
|
"sha256:9b06690224258db5cd39a84e993882a6874676f5de582da57f3df3a82ead9174",
|
||||||
"sha256:c9d317efde4bafbc1561509bfa8a23c5cab66c44d49ab5b63ff690f5159b2304",
|
"sha256:a71400b90b3599eb7bf241f947932e18a066907bf84617d80817998cee81e4bf",
|
||||||
"sha256:cc411ad324a4486b142c41d9b2b6a722c534096963688d879ea6fa8a35028258",
|
"sha256:bb252f802f91f59767dcc559744e91efa9df532240a502befd874b54571417bd",
|
||||||
"sha256:cdc13a1682b2a6241080745b1953719e7fe0850b40a5c71ca574f090a1391df6",
|
"sha256:be1ebf9cc25ab5399501c9046a7dcdaa9e911802ed0e12b7d620cd4bbf0518b3",
|
||||||
"sha256:cfd7c5dd3c35c19cec59c63df9571c67c6d6e5c92e0fe63517920e97f61106d1",
|
"sha256:be7c65e34d1b50ab7093b90427cbc488260e4b3a38ef2435d65b62e9fa3d798a",
|
||||||
"sha256:e1cacf4796b20865789083252186ce9dc6cc59eca0c2e79cca332bdff24ac481",
|
"sha256:c0dac835c1a22621ffa5e5f999d57359c790c52bbd1c687fe514ae6924f65ef5",
|
||||||
"sha256:e70d4e467e243455492f5de463b72151cc400710ac03a0678206a5f27e79ddef",
|
"sha256:c152b2e93b639d1f36ec5a8ca24cde4a8eefb2b6b83668fcd8e83a67badcb367",
|
||||||
"sha256:ecc930ae559ea8a43377e8b60ca6f8d61ac532fc57efb915d899de4a67928efd",
|
"sha256:d182eada8ea0de61a45a526aa0ae4bcd222f9673424e65315c35820291ff299c",
|
||||||
"sha256:f161af26f596131b63b236372e4ce40f3167c1b5b5d459b29d2514bd8c9dc9ee"
|
"sha256:d18331ea905a41ae71596502bd4c9a2998902328bbabd29e3d0f5f8569fabad1",
|
||||||
|
"sha256:d20d32cbb31d731def4b1502294ca2ee99f9249b63bc80e03e67e8f8e126dea8",
|
||||||
|
"sha256:d4ad7fd3269281cb471ad6c7bafca372e69789540d16e3755dd717e9e5c9d82f",
|
||||||
|
"sha256:d6f8c23f65a4bfe4300b85f1f40f6c32569822d08901db3b6454ab785d9117cc",
|
||||||
|
"sha256:d84d741c6e35c9f3e7406cb7c4c2e08474c2a6441d59322a00dcae65aac6315d",
|
||||||
|
"sha256:e65c221b2115a91035b55a593b6eb94aa1206fa3ab374f47c6dc10d364583ff9",
|
||||||
|
"sha256:f98b6f256be6cec8dd308a8563976ddaff0bdc18b730720f6f4bee927ffe926f"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.5.2"
|
"version": "==4.6.1"
|
||||||
},
|
},
|
||||||
"markupsafe": {
|
"markupsafe": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -691,23 +699,26 @@
|
|||||||
},
|
},
|
||||||
"prompt-toolkit": {
|
"prompt-toolkit": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:822f4605f28f7d2ba6b0b09a31e25e140871e96364d1d377667b547bb3bf4489",
|
"sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c",
|
||||||
"sha256:83074ee28ad4ba6af190593d4d4c607ff525272a504eb159199b6dd9f950c950"
|
"sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"
|
||||||
],
|
],
|
||||||
"version": "==3.0.7"
|
"version": "==3.0.8"
|
||||||
},
|
},
|
||||||
"psycopg2-binary": {
|
"psycopg2-binary": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c",
|
"sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c",
|
||||||
"sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67",
|
"sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67",
|
||||||
"sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0",
|
"sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0",
|
||||||
|
"sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6",
|
||||||
"sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db",
|
"sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db",
|
||||||
"sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94",
|
"sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94",
|
||||||
"sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52",
|
"sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52",
|
||||||
|
"sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056",
|
||||||
"sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b",
|
"sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b",
|
||||||
"sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd",
|
"sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd",
|
||||||
"sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550",
|
"sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550",
|
||||||
"sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679",
|
"sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679",
|
||||||
|
"sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83",
|
||||||
"sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77",
|
"sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77",
|
||||||
"sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2",
|
"sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2",
|
||||||
"sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77",
|
"sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77",
|
||||||
@ -757,84 +768,84 @@
|
|||||||
},
|
},
|
||||||
"pycryptodome": {
|
"pycryptodome": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:02e51e1d5828d58f154896ddfd003e2e7584869c275e5acbe290443575370fba",
|
"sha256:19cb674df6c74a14b8b408aa30ba8a89bd1c01e23505100fb45f930fbf0ed0d9",
|
||||||
"sha256:03d5cca8618620f45fd40f827423f82b86b3a202c8d44108601b0f5f56b04299",
|
"sha256:1cfdb92dca388e27e732caa72a1cc624520fe93752a665c3b6cd8f1a91b34916",
|
||||||
"sha256:0e24171cf01021bc5dc17d6a9d4f33a048f09d62cc3f62541e95ef104588bda4",
|
"sha256:27397aee992af69d07502126561d851ba3845aa808f0e55c71ad0efa264dd7d4",
|
||||||
"sha256:132a56abba24e2e06a479d8e5db7a48271a73a215f605017bbd476d31f8e71c1",
|
"sha256:28f75e58d02019a7edc7d4135203d2501dfc47256d175c72c9798f9a129a49a7",
|
||||||
"sha256:1e655746f539421d923fd48df8f6f40b3443d80b75532501c0085b64afed9df5",
|
"sha256:2a68df525b387201a43b27b879ce8c08948a430e883a756d6c9e3acdaa7d7bd8",
|
||||||
"sha256:2b998dc45ef5f4e5cf5248a6edfcd8d8e9fb5e35df8e4259b13a1b10eda7b16b",
|
"sha256:411745c6dce4eff918906eebcde78771d44795d747e194462abb120d2e537cd9",
|
||||||
"sha256:360955eece2cd0fa694a708d10303c6abd7b39614fa2547b6bd245da76198beb",
|
"sha256:46e96aeb8a9ca8b1edf9b1fd0af4bf6afcf3f1ca7fa35529f5d60b98f3e4e959",
|
||||||
"sha256:39ef9fb52d6ec7728fce1f1693cb99d60ce302aeebd59bcedea70ca3203fda60",
|
"sha256:4ed27951b0a17afd287299e2206a339b5b6d12de9321e1a1575261ef9c4a851b",
|
||||||
"sha256:4350a42028240c344ee855f032c7d4ad6ff4f813bfbe7121547b7dc579ecc876",
|
"sha256:50826b49fbca348a61529693b0031cdb782c39060fb9dca5ac5dff858159dc5a",
|
||||||
"sha256:50348edd283afdccddc0938cdc674484533912ba8a99a27c7bfebb75030aa856",
|
"sha256:5598dc6c9dbfe882904e54584322893eff185b98960bbe2cdaaa20e8a437b6e5",
|
||||||
"sha256:54bdedd28476dea8a3cd86cb67c0df1f0e3d71cae8022354b0f879c41a3d27b2",
|
"sha256:5c3c4865730dfb0263f822b966d6d58429d8b1e560d1ddae37685fd9e7c63161",
|
||||||
"sha256:55eb61aca2c883db770999f50d091ff7c14016f2769ad7bca3d9b75d1d7c1b68",
|
"sha256:5f19e6ef750f677d924d9c7141f54bade3cd56695bbfd8a9ef15d0378557dfe4",
|
||||||
"sha256:6276478ada411aca97c0d5104916354b3d740d368407912722bd4d11aa9ee4c2",
|
"sha256:60febcf5baf70c566d9d9351c47fbd8321da9a4edf2eff45c4c31c86164ca794",
|
||||||
"sha256:663f8de2b3df2e744d6e1610506e0ea4e213bde906795953c1e82279c169f0a7",
|
"sha256:62c488a21c253dadc9f731a32f0ac61e4e436d81a1ea6f7d1d9146ed4d20d6bd",
|
||||||
"sha256:67dcad1b8b201308586a8ca2ffe89df1e4f731d5a4cdd0610cc4ea790351c739",
|
"sha256:6d3baaf82681cfb1a842f1c8f77beac791ceedd99af911e4f5fabec32bae2259",
|
||||||
"sha256:709b9f144d23e290b9863121d1ace14a72e01f66ea9c903fbdc690520dfdfcf0",
|
"sha256:6e4227849e4231a3f5b35ea5bdedf9a82b3883500e5624f00a19156e9a9ef861",
|
||||||
"sha256:8063a712fba642f78d3c506b0896846601b6de7f5c3d534e388ad0cc07f5a149",
|
"sha256:6e89bb3826e6f84501e8e3b205c22595d0c5492c2f271cbb9ee1c48eb1866645",
|
||||||
"sha256:80d57177a0b7c14d4594c62bbb47fe2f6309ad3b0a34348a291d570925c97a82",
|
"sha256:70d807d11d508433daf96244ec1c64e55039e8a35931fc5ea9eee94dbe3cb6b5",
|
||||||
"sha256:87006cf0d81505408f1ae4f55cf8a5d95a8e029a4793360720ae17c6500f7ecc",
|
"sha256:76b1a34d74bb2c91bce460cdc74d1347592045627a955e9a252554481c17c52f",
|
||||||
"sha256:9f62d21bc693f3d7d444f17ed2ad7a913b4c37c15cd807895d013c39c0517dfd",
|
"sha256:7798e73225a699651888489fbb1dbc565e03a509942a8ce6194bbe6fb582a41f",
|
||||||
"sha256:a207231a52426de3ff20f5608f0687261a3329d97a036c51f7d4c606a6f30c23",
|
"sha256:834b790bbb6bd18956f625af4004d9c15eed12d5186d8e57851454ae76d52215",
|
||||||
"sha256:abc2e126c9490e58a36a0f83516479e781d83adfb134576a5cbe5c6af2a3e93c",
|
"sha256:843e5f10ecdf9d307032b8b91afe9da1d6ed5bb89d0bbec5c8dcb4ba44008e11",
|
||||||
"sha256:b56638d58a3a4be13229c6a815cd448f9e3ce40c00880a5398471b42ee86f50e",
|
"sha256:8f9f84059039b672a5a705b3c5aa21747867bacc30a72e28bf0d147cc8ef85ed",
|
||||||
"sha256:bcd5b8416e73e4b0d48afba3704d8c826414764dafaed7a1a93c442188d90ccc",
|
"sha256:9000877383e2189dafd1b2fc68c6c726eca9a3cfb6d68148fbb72ccf651959b6",
|
||||||
"sha256:bec2bcdf7c9ce7f04d718e51887f3b05dc5c1cfaf5d2c2e9065ecddd1b2f6c9a",
|
"sha256:910e202a557e1131b1c1b3f17a63914d57aac55cf9fb9b51644962841c3995c4",
|
||||||
"sha256:c8bf40cf6e281a4378e25846924327e728a887e8bf0ee83b2604a0f4b61692e8",
|
"sha256:946399d15eccebafc8ce0257fc4caffe383c75e6b0633509bd011e357368306c",
|
||||||
"sha256:cecbf67e81d6144a50dc615629772859463b2e4f815d0c082fa421db362f040e",
|
"sha256:a199e9ca46fc6e999e5f47fce342af4b56c7de85fae893c69ab6aa17531fb1e1",
|
||||||
"sha256:d8074c8448cfd0705dfa71ca333277fce9786d0b9cac75d120545de6253f996a",
|
"sha256:a3d8a9efa213be8232c59cdc6b65600276508e375e0a119d710826248fd18d37",
|
||||||
"sha256:dd302b6ae3965afeb5ef1b0d92486f986c0e65183cd7835973f0b593800590e6",
|
"sha256:a4599c0ca0fc027c780c1c45ed996d5bef03e571470b7b1c7171ec1e1a90914c",
|
||||||
"sha256:de6e1cd75677423ff64712c337521e62e3a7a4fc84caabbd93207752e831a85a",
|
"sha256:b4e6b269a8ddaede774e5c3adbef6bf452ee144e6db8a716d23694953348cd86",
|
||||||
"sha256:ef39c98d9b8c0736d91937d193653e47c3b19ddf4fc3bccdc5e09aaa4b0c5d21",
|
"sha256:b68794fba45bdb367eeb71249c26d23e61167510a1d0c3d6cf0f2f14636e62ee",
|
||||||
"sha256:f2e045224074d5664dc9cbabbf4f4d4d46f1ee90f24780e3a9a668fd096ff17f",
|
"sha256:d7ec2bd8f57c559dd24e71891c51c25266a8deb66fc5f02cc97c7fb593d1780a",
|
||||||
"sha256:f521178e5a991ffd04182ed08f552daca1affcb826aeda0e1945cd989a9d4345",
|
"sha256:e15bde67ccb7d4417f627dd16ffe2f5a4c2941ce5278444e884cb26d73ecbc61",
|
||||||
"sha256:f78a68c2c820e4731e510a2df3eef0322f24fde1781ced970bf497b6c7d92982",
|
"sha256:eb01f9997e4d6a8ec8a1ad1f676ba5a362781ff64e8189fe2985258ba9cb9706",
|
||||||
"sha256:fbe65d5cfe04ff2f7684160d50f5118bdefb01e3af4718eeb618bfed40f19d94"
|
"sha256:faa682c404c218e8788c3126c9a4b8fbcc54dc245b5b6e8ea5b46f3b63bd0c84"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.9.8"
|
"version": "==3.9.9"
|
||||||
},
|
},
|
||||||
"pycryptodomex": {
|
"pycryptodomex": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:06f5a458624c9b0e04c0086c7f84bcc578567dab0ddc816e0476b3057b18339f",
|
"sha256:15c03ffdac17731b126880622823d30d0a3cc7203cd219e6b9814140a44e7fab",
|
||||||
"sha256:1714675fb4ac29a26ced38ca22eb8ffd923ac851b7a6140563863194d7158422",
|
"sha256:20fb7f4efc494016eab1bc2f555bc0a12dd5ca61f35c95df8061818ffb2c20a3",
|
||||||
"sha256:17272d06e4b2f6455ee2cbe93e8eb50d9450a5dc6223d06862ee1ea5d1235861",
|
"sha256:28ee3bcb4d609aea3040cad995a8e2c9c6dc57c12183dadd69e53880c35333b9",
|
||||||
"sha256:2199708ebeed4b82eb45b10e1754292677f5a0df7d627ee91ea01290b9bab7e6",
|
"sha256:305e3c46f20d019cd57543c255e7ba49e432e275d7c0de8913b6dbe57a851bc8",
|
||||||
"sha256:2275a663c9e744ee4eace816ef2d446b3060554c5773a92fbc79b05bf47debda",
|
"sha256:3547b87b16aad6afb28c9b3a9cd870e11b5e7b5ac649b74265258d96d8de1130",
|
||||||
"sha256:2710fc8d83b3352b370db932b3710033b9d630b970ff5aaa3e7458b5336e3b32",
|
"sha256:3642252d7bfc4403a42050e18ba748bedebd5a998a8cba89665a4f42aea4c380",
|
||||||
"sha256:35b9c9177a9fe7288b19dd41554c9c8ca1063deb426dd5a02e7e2a7416b6bd11",
|
"sha256:404faa3e518f8bea516aae2aac47d4d960397199a15b4bd6f66cad97825469a0",
|
||||||
"sha256:3b23d63030819b7d9ac7db9360305fd1241e6870ca5b7e8d59fee4db4674a490",
|
"sha256:42669638e4f7937b7141044a2fbd1019caca62bd2cdd8b535f731426ab07bde1",
|
||||||
"sha256:3caa32cf807422adf33c10c88c22e9e2e08b9d9d042f12e1e25fe23113dd618f",
|
"sha256:4632d55a140b28e20be3cd7a3057af52fb747298ff0fd3290d4e9f245b5004ba",
|
||||||
"sha256:48cc2cfc251f04a6142badeb666d1ff49ca6fdfc303fd72579f62b768aaa52b9",
|
"sha256:4a88c9383d273bdce3afc216020282c9c5c39ec0bd9462b1a206af6afa377cf0",
|
||||||
"sha256:4ae6379350a09339109e9b6f419bb2c3f03d3e441f4b0f5b8ca699d47cc9ff7e",
|
"sha256:4ce1fc1e6d2fd2d6dc197607153327989a128c093e0e94dca63408f506622c3e",
|
||||||
"sha256:4e0b27697fa1621c6d3d3b4edeec723c2e841285de6a8d378c1962da77b349be",
|
"sha256:55cf4e99b3ba0122dee570dc7661b97bf35c16aab3e2ccb5070709d282a1c7ab",
|
||||||
"sha256:58e19560814dabf5d788b95a13f6b98279cf41a49b1e49ee6cf6c79a57adb4c9",
|
"sha256:5e486cab2dfcfaec934dd4f5d5837f4a9428b690f4d92a3b020fd31d1497ca64",
|
||||||
"sha256:8044eae59301dd392fbb4a7c5d64e1aea8ef0be2540549807ecbe703d6233d68",
|
"sha256:65ec88c8271448d2ea109d35c1f297b09b872c57214ab7e832e413090d3469a9",
|
||||||
"sha256:85c108b42e47d4073344ff61d4e019f1d95bb7725ca0fe87d0a2deb237c10e49",
|
"sha256:6c95a3361ce70068cf69526a58751f73ddac5ba27a3c2379b057efa2f5338c8c",
|
||||||
"sha256:89be1bf55e50116fe7e493a7c0c483099770dd7f81b87ac8d04a43b1a203e259",
|
"sha256:73240335f4a1baf12880ebac6df66ab4d3a9212db9f3efe809c36a27280d16f8",
|
||||||
"sha256:8fcdda24dddf47f716400d54fc7f75cadaaba1dd47cc127e59d752c9c0fc3c48",
|
"sha256:7651211e15109ac0058a49159265d9f6e6423c8a81c65434d3c56d708417a05b",
|
||||||
"sha256:914fbb18e29c54585e6aa39d300385f90d0fa3b3cc02ed829b08f95c1acf60c2",
|
"sha256:7b5b7c5896f8172ea0beb283f7f9428e0ab88ec248ce0a5b8c98d73e26267d51",
|
||||||
"sha256:93a75d1acd54efed314b82c952b39eac96ce98d241ad7431547442e5c56138aa",
|
"sha256:836fe39282e75311ce4c38468be148f7fac0df3d461c5de58c5ff1ddb8966bac",
|
||||||
"sha256:9fd758e5e2fe02d57860b85da34a1a1e7037155c4eadc2326fc7af02f9cae214",
|
"sha256:871852044f55295449fbf225538c2c4118525093c32f0a6c43c91bed0452d7e3",
|
||||||
"sha256:a2bc4e1a2e6ca3a18b2e0be6131a23af76fecb37990c159df6edc7da6df913e3",
|
"sha256:892e93f3e7e10c751d6c17fa0dc422f7984cfd5eb6690011f9264dc73e2775fc",
|
||||||
"sha256:a2ee8ba99d33e1a434fcd27d7d0aa7964163efeee0730fe2efc9d60edae1fc71",
|
"sha256:934e460c5058346c6f1d62fdf3db5680fbdfbfd212722d24d8277bf47cd9ebdc",
|
||||||
"sha256:b2d756620078570d3f940c84bc94dd30aa362b795cce8b2723300a8800b87f1c",
|
"sha256:9736f3f3e1761024200637a080a4f922f5298ad5d780e10dbb5634fe8c65b34c",
|
||||||
"sha256:c0d085c8187a1e4d3402f626c9e438b5861151ab132d8761d9c5ce6491a87761",
|
"sha256:a1d38a96da57e6103423a446079ead600b450cf0f8ebf56a231895abf77e7ffc",
|
||||||
"sha256:c315262e26d54a9684e323e37ac9254f481d57fcc4fd94002992460898ef5c04",
|
"sha256:a385fceaa0cdb97f0098f1c1e9ec0b46cc09186ddf60ec23538e871b1dddb6dc",
|
||||||
"sha256:c990f2c58f7c67688e9e86e6557ed05952669ff6f1343e77b459007d85f7df00",
|
"sha256:a7cf1c14e47027d9fb9d26aa62e5d603994227bd635e58a8df4b1d2d1b6a8ed7",
|
||||||
"sha256:ccbbec59bf4b74226170c54476da5780c9176bae084878fc94d9a2c841218e34",
|
"sha256:a9aac1a30b00b5038d3d8e48248f3b58ea15c827b67325c0d18a447552e30fc8",
|
||||||
"sha256:dc2bed32c7b138f1331794e454a953360c8cedf3ee62ae31f063822da6007489",
|
"sha256:b696876ee583d15310be57311e90e153a84b7913ac93e6b99675c0c9867926d0",
|
||||||
"sha256:ddb1ae2891c8cb83a25da87a3e00111a9654fc5f0b70f18879c41aece45d6182",
|
"sha256:bef9e9d39393dc7baec39ba4bac6c73826a4db02114cdeade2552a9d6afa16e2",
|
||||||
"sha256:e070a1f91202ed34c396be5ea842b886f6fa2b90d2db437dc9fb35a26c80c060",
|
"sha256:c885fe4d5f26ce8ca20c97d02e88f5fdd92c01e1cc771ad0951b21e1641faf6d",
|
||||||
"sha256:e42860fbe1292668b682f6dabd225fbe2a7a4fa1632f0c39881c019e93dea594",
|
"sha256:d2d1388595cb5d27d9220d5cbaff4f37c6ec696a25882eb06d224d241e6e93fb",
|
||||||
"sha256:e4e1c486bf226822c8dceac81d0ec59c0a2399dbd1b9e04f03c3efa3605db677",
|
"sha256:d2e853e0f9535e693fade97768cf7293f3febabecc5feb1e9b2ffdfe1044ab96",
|
||||||
"sha256:ea4d4b58f9bc34e224ef4b4604a6be03d72ef1f8c486391f970205f6733dbc46",
|
"sha256:d62fbab185a6b01c5469eda9f0795f3d1a5bba24f5a5813f362e4b73a3c4dc70",
|
||||||
"sha256:f5bd6891380e0fb5467251daf22525644fdf6afd9ae8bc2fe065c78ea1882e0d",
|
"sha256:f20a62397e09704049ce9007bea4f6bad965ba9336a760c6f4ef1b4192e12d6d",
|
||||||
"sha256:f60b3484ce4be04f5da3777c51c5140d3fe21cdd6674f2b6568f41c8130bcdeb"
|
"sha256:f81f7311250d9480e36dec819127897ae772e7e8de07abfabe931b8566770b8e"
|
||||||
],
|
],
|
||||||
"version": "==3.9.8"
|
"version": "==3.9.9"
|
||||||
},
|
},
|
||||||
"pyhamcrest": {
|
"pyhamcrest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -879,17 +890,17 @@
|
|||||||
},
|
},
|
||||||
"python-dotenv": {
|
"python-dotenv": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:8c10c99a1b25d9a68058a1ad6f90381a62ba68230ca93966882a4dbc3bc9c33d",
|
"sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e",
|
||||||
"sha256:c10863aee750ad720f4f43436565e4c1698798d763b63234fb5021b6c616e423"
|
"sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"
|
||||||
],
|
],
|
||||||
"version": "==0.14.0"
|
"version": "==0.15.0"
|
||||||
},
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
|
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
|
||||||
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
|
"sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
|
||||||
],
|
],
|
||||||
"version": "==2020.1"
|
"version": "==2020.4"
|
||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -974,6 +985,8 @@
|
|||||||
"sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c",
|
"sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c",
|
||||||
"sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988",
|
"sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988",
|
||||||
"sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f",
|
"sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f",
|
||||||
|
"sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5",
|
||||||
|
"sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a",
|
||||||
"sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1",
|
"sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1",
|
||||||
"sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2",
|
"sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2",
|
||||||
"sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"
|
"sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"
|
||||||
@ -990,11 +1003,11 @@
|
|||||||
},
|
},
|
||||||
"sentry-sdk": {
|
"sentry-sdk": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1d91a0059d2d8bb980bec169578035c2f2d4b93cd8a4fb5b85c81904d33e221a",
|
"sha256:17b725df2258354ccb39618ae4ead29651aa92c01a92acf72f98efe06ee2e45a",
|
||||||
"sha256:6222cf623e404c3e62b8e0e81c6db866ac2d12a663b7c1f7963350e3f397522a"
|
"sha256:9040539485226708b5cad0401d76628fba4eed9154bf301c50579767afe344fd"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.18.0"
|
"version": "==0.19.2"
|
||||||
},
|
},
|
||||||
"service-identity": {
|
"service-identity": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1006,11 +1019,11 @@
|
|||||||
},
|
},
|
||||||
"signxml": {
|
"signxml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4c996153153c9b1eb7ff40cf624722946f8c2ab059febfa641e54cd59725acd9",
|
"sha256:b70e151d10d99cbc74a50a3344f508ee481fe3c376d61cd1cae850912d303d19",
|
||||||
"sha256:d116c283f2c940bc2b4edf011330107ba02f197650a4878466987e04142d43b1"
|
"sha256:bab03a6823c9a5b225d1e6266ce66b5d08c4ebfb42029fdb5d3e588b8128c86d"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.8.0"
|
"version": "==2.8.1"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1092,23 +1105,23 @@
|
|||||||
"secure"
|
"secure"
|
||||||
],
|
],
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
|
"sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
|
||||||
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
|
"sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": null,
|
"markers": null,
|
||||||
"version": "==1.25.10"
|
"version": "==1.25.11"
|
||||||
},
|
},
|
||||||
"uvicorn": {
|
"uvicorn": {
|
||||||
"extras": [
|
"extras": [
|
||||||
"standard"
|
"standard"
|
||||||
],
|
],
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a461e76406088f448f36323f5ac774d50e5a552b6ccb54e4fca8d83ef614a7c2",
|
"sha256:8ff7495c74b8286a341526ff9efa3988ebab9a4b2f561c7438c3cb420992d7dd",
|
||||||
"sha256:d06a25caa8dc680ad92eb3ec67363f5281c092059613a1cc0100acba37fc0f45"
|
"sha256:e5dbed4a8a44c7b04376021021d63798d6a7bcfae9c654a0b153577b93854fba"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.12.1"
|
"version": "==0.12.2"
|
||||||
},
|
},
|
||||||
"uvloop": {
|
"uvloop": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1181,48 +1194,60 @@
|
|||||||
},
|
},
|
||||||
"zope.interface": {
|
"zope.interface": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:040f833694496065147e76581c0bf32b229a8b8c5eda120a0293afb008222387",
|
"sha256:05a97ba92c1c7c26f25c9f671aa1ef85ffead6cdad13770e5b689cf983adc7e1",
|
||||||
"sha256:11198b44e4a3d8c7a80cc20bbdd65522258a4d82fe467cd310c9fcce8ffe2ed2",
|
"sha256:07d61722dd7d85547b7c6b0f5486b4338001fab349f2ac5cabc0b7182eb3425d",
|
||||||
"sha256:121a9dccfe0c34be9c33b2c28225f0284f9b8e090580ffdff26c38fa16c7ffe1",
|
"sha256:0a990dcc97806e5980bbb54b2e46b9cde9e48932d8e6984daf71ef1745516123",
|
||||||
"sha256:15f3082575e7e19581a80b866664f843719b647a7f7189c811ba7f9ab3309f83",
|
"sha256:150e8bcb7253a34a4535aeea3de36c0bb3b1a6a47a183a95d65a194b3e07f232",
|
||||||
"sha256:1d73d8986f948525536956ddd902e8a587a6846ebf4492117db16daba2865ddf",
|
"sha256:1743bcfe45af8846b775086471c28258f4c6e9ee8ef37484de4495f15a98b549",
|
||||||
"sha256:208e82f73b242275b8566ac07a25158e7b21fa2f14e642a7881048430612d1a6",
|
"sha256:1b5f6c8fff4ed32aa2dd43e84061bc8346f32d3ba6ad6e58f088fe109608f102",
|
||||||
"sha256:2557833df892558123d791d6ff80ac4a2a0351f69c7421c7d5f0c07db72c8865",
|
"sha256:21e49123f375703cf824214939d39df0af62c47d122d955b2a8d9153ea08cfd5",
|
||||||
"sha256:25ea6906f9987d42546329d06f9750e69f0ee62307a2e7092955ed0758e64f09",
|
"sha256:21f579134a47083ffb5ddd1307f0405c91aa8b61ad4be6fd5af0171474fe0c45",
|
||||||
"sha256:2c867914f7608674a555ac8daf20265644ac7be709e1da7d818089eebdfe544e",
|
"sha256:27c267dc38a0f0079e96a2945ee65786d38ef111e413c702fbaaacbab6361d00",
|
||||||
"sha256:2eadac20711a795d3bb7a2bfc87c04091cb5274d9c3281b43088a1227099b662",
|
"sha256:299bde0ab9e5c4a92f01a152b7fbabb460f31343f1416f9b7b983167ab1e33bc",
|
||||||
"sha256:37999d5ebd5d7bcd32438b725ca3470df05a7de8b1e9c0395bef24296b31ca99",
|
"sha256:2ab88d8f228f803fcb8cb7d222c579d13dab2d3622c51e8cf321280da01102a7",
|
||||||
"sha256:3ae8946d51789779f76e4fa326fd6676d8c19c1c3b4c4c5e9342807185264875",
|
"sha256:2ced4c35061eea623bc84c7711eedce8ecc3c2c51cd9c6afa6290df3bae9e104",
|
||||||
"sha256:5636cd7e60583b1608044ae4405e91575399430e66a5e1812f4bf30bcc55864e",
|
"sha256:2dcab01c660983ba5e5a612e0c935141ccbee67d2e2e14b833e01c2354bd8034",
|
||||||
"sha256:570e637cb6509998555f7e4af13006d89fad6c09cfc5c4795855385391063e4b",
|
"sha256:32546af61a9a9b141ca38d971aa6eb9800450fa6620ce6323cc30eec447861f3",
|
||||||
"sha256:590a40447ff3803c44050ce3c17c3958f11ca028dae3eacdd7b96775184394fa",
|
"sha256:32b40a4c46d199827d79c86bb8cb88b1bbb764f127876f2cb6f3a47f63dbada3",
|
||||||
"sha256:5aab51b9c1af1b8a84f40aa49ffe1684d41810b18d6c3e94aa50194e0a563f01",
|
"sha256:3cc94c69f6bd48ed86e8e24f358cb75095c8129827df1298518ab860115269a4",
|
||||||
"sha256:5ffe4e0753393bcbcfc9a58133ed3d3a584634cc7cc2e667f8e3e6fbcbb2155d",
|
"sha256:42b278ac0989d6f5cf58d7e0828ea6b5951464e3cf2ff229dd09a96cb6ba0c86",
|
||||||
"sha256:663982381bd428a275a841009e52983cc69c471a4979ce01344fadbf72cf353d",
|
"sha256:495b63fd0302f282ee6c1e6ea0f1c12cb3d1a49c8292d27287f01845ff252a96",
|
||||||
"sha256:6d06bf8e24dd6c473c4fbd8e16a83bd2e6d74add6ba25169043deb46d497b211",
|
"sha256:4af87cdc0d4b14e600e6d3d09793dce3b7171348a094ba818e2a68ae7ee67546",
|
||||||
"sha256:6e5b9a4bf133cf1887b4a04c21c10ca9f548114f19c83957b2820d5c84254940",
|
"sha256:4b94df9f2fdde7b9314321bab8448e6ad5a23b80542dcab53e329527d4099dcb",
|
||||||
"sha256:70a2aed9615645bbe9d82c0f52bc7e676d2c0f8a63933d68418e0cb307f30536",
|
"sha256:4c48ddb63e2b20fba4c6a2bf81b4d49e99b6d4587fb67a6cd33a2c1f003af3e3",
|
||||||
"sha256:7750746421c4395e3d2cc3d805919f4f57bb9f2a9a0ccd955566a9341050a1b4",
|
"sha256:4df9afd17bd5477e9f8c8b6bb8507e18dd0f8b4efe73bb99729ff203279e9e3b",
|
||||||
"sha256:7fc8708bc996e50fc7a9a2ad394e1f015348e389da26789fa6916630237143d7",
|
"sha256:518950fe6a5d56f94ba125107895f938a4f34f704c658986eae8255edb41163b",
|
||||||
"sha256:91abd2f080065a7c007540f6bbd93ef7bdbbffa6df4a4cfab3892d8623b83c98",
|
"sha256:538298e4e113ccb8b41658d5a4b605bebe75e46a30ceca22a5a289cf02c80bec",
|
||||||
"sha256:988f8b2281f3d95c66c01bdb141cefef1cc97db0d473c25c3fe2927ef00293b9",
|
"sha256:55465121e72e208a7b69b53de791402affe6165083b2ea71b892728bd19ba9ae",
|
||||||
"sha256:9f56121d8a676802044584e6cc41250bbcde069d8adf725b9b817a6b0fd87f09",
|
"sha256:588384d70a0f19b47409cfdb10e0c27c20e4293b74fc891df3d8eb47782b8b3e",
|
||||||
"sha256:a0f51536ce6e817a7aa25b0dca8b62feb210d4dc22cabfe8d1a92d47979372cd",
|
"sha256:6278c080d4afffc9016e14325f8734456831124e8c12caa754fd544435c08386",
|
||||||
"sha256:a1cdd7390d7f66ddcebf545203ca3728c4890d605f9f2697bc8e31437906e8e7",
|
"sha256:64ea6c221aeee4796860405e1aedec63424cda4202a7ad27a5066876db5b0fd2",
|
||||||
"sha256:b10eb4d0a77609679bf5f23708e20b1cd461a1643bd8ea42b1ca4149b1a5406c",
|
"sha256:681dbb33e2b40262b33fd383bae63c36d33fd79fa1a8e4092945430744ffd34a",
|
||||||
"sha256:b274ac8e511b55ffb62e8292316bd2baa80c10e9fe811b1aa5ce81da6b6697d8",
|
"sha256:6936aa9da390402d646a32a6a38d5409c2d2afb2950f045a7d02ab25a4e7d08d",
|
||||||
"sha256:c75b502af2c83fcfa2ee9c2257c1ba5806634a91a50db6129ff70e67c42c7e7b",
|
"sha256:778d0ec38bbd288b150a3ae363c8ffd88d2207a756842495e9bffd8a8afbc89a",
|
||||||
"sha256:c9c8e53a5472b77f6a391b515c771105011f4b40740ce53af8428d1c8ca20004",
|
"sha256:8251f06a77985a2729a8bdbefbae79ee78567dddc3acbd499b87e705ca59fe24",
|
||||||
"sha256:d867998a56c5133b9d31992beb699892e33b72150a8bf40f86cb52b8c606c83f",
|
"sha256:83b4aa5344cce005a9cff5d0321b2e318e871cc1dfc793b66c32dd4f59e9770d",
|
||||||
"sha256:eb566cab630ec176b2d6115ed08b2cf4d921b47caa7f02cca1b4a9525223ee94",
|
"sha256:844fad925ac5c2ad4faaceb3b2520ad016b5280105c6e16e79838cf951903a7b",
|
||||||
"sha256:f61e6b95b414431ffe9dc460928fe9f351095fde074e2c2f5c6dda7b67a2192d",
|
"sha256:8ceb3667dd13b8133f2e4d637b5b00f240f066448e2aa89a41f4c2d78a26ce50",
|
||||||
"sha256:f718675fd071bcce4f7cbf9250cbaaf64e2e91ef1b0b32a1af596e7412647556",
|
"sha256:92dc0fb79675882d0b6138be4bf0cec7ea7c7eede60aaca78303d8e8dbdaa523",
|
||||||
"sha256:f9d4bfbd015e4b80dbad11c97049975f94592a6a0440e903ee647309f6252a1f",
|
"sha256:9789bd945e9f5bd026ed3f5b453d640befb8b1fc33a779c1fe8d3eb21fe3fb4a",
|
||||||
"sha256:fae50fc12a5e8541f6f1cc4ed744ca8f76a9543876cf63f618fb0e6aca8f8375",
|
"sha256:a2b6d6eb693bc2fc6c484f2e5d93bd0b0da803fa77bf974f160533e555e4d095",
|
||||||
"sha256:fcf9c8edda7f7b2fd78069e97f4197815df5e871ec47b0f22580d330c6dec561",
|
"sha256:aab9f1e34d810feb00bf841993552b8fcc6ae71d473c505381627143d0018a6a",
|
||||||
"sha256:fdedce3bc5360bd29d4bb90396e8d4d3c09af49bc0383909fe84c7233c5ee675"
|
"sha256:abb61afd84f23099ac6099d804cdba9bd3b902aaaded3ffff47e490b0a495520",
|
||||||
|
"sha256:adf9ee115ae8ff8b6da4b854b4152f253b390ba64407a22d75456fe07dcbda65",
|
||||||
|
"sha256:aedc6c672b351afe6dfe17ff83ee5e7eb6ed44718f879a9328a68bdb20b57e11",
|
||||||
|
"sha256:b7a00ecb1434f8183395fac5366a21ee73d14900082ca37cf74993cf46baa56c",
|
||||||
|
"sha256:ba32f4a91c1cb7314c429b03afbf87b1fff4fb1c8db32260e7310104bd77f0c7",
|
||||||
|
"sha256:cbd0f2cbd8689861209cd89141371d3a22a11613304d1f0736492590aa0ab332",
|
||||||
|
"sha256:e4bc372b953bf6cec65a8d48482ba574f6e051621d157cf224227dbb55486b1e",
|
||||||
|
"sha256:eccac3d9aadc68e994b6d228cb0c8919fc47a5350d85a1b4d3d81d1e98baf40c",
|
||||||
|
"sha256:efd550b3da28195746bb43bd1d815058181a7ca6d9d6aa89dd37f5eefe2cacb7",
|
||||||
|
"sha256:efef581c8ba4d990770875e1a2218e856849d32ada2680e53aebc5d154a17e20",
|
||||||
|
"sha256:f057897711a630a0b7a6a03f1acf379b6ba25d37dc5dc217a97191984ba7f2fc",
|
||||||
|
"sha256:f37d45fab14ffef9d33a0dc3bc59ce0c5313e2253323312d47739192da94f5fd",
|
||||||
|
"sha256:f44906f70205d456d503105023041f1e63aece7623b31c390a0103db4de17537"
|
||||||
],
|
],
|
||||||
"version": "==5.1.2"
|
"version": "==5.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
@ -1235,10 +1260,10 @@
|
|||||||
},
|
},
|
||||||
"asgiref": {
|
"asgiref": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
|
"sha256:a5098bc870b80e7b872bff60bb363c7f2c2c89078759f6c47b53ff8c525a152e",
|
||||||
"sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
|
"sha256:cd88907ecaec59d78e4ac00ea665b03e571cb37e3a0e37b3702af1a9e86c365a"
|
||||||
],
|
],
|
||||||
"version": "==3.2.10"
|
"version": "==3.3.0"
|
||||||
},
|
},
|
||||||
"astroid": {
|
"astroid": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1249,10 +1274,10 @@
|
|||||||
},
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594",
|
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
|
||||||
"sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"
|
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
|
||||||
],
|
],
|
||||||
"version": "==20.2.0"
|
"version": "==20.3.0"
|
||||||
},
|
},
|
||||||
"autopep8": {
|
"autopep8": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1300,11 +1325,11 @@
|
|||||||
},
|
},
|
||||||
"colorama": {
|
"colorama": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
|
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||||
"sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
|
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.4.3"
|
"version": "==0.4.4"
|
||||||
},
|
},
|
||||||
"coverage": {
|
"coverage": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1348,11 +1373,11 @@
|
|||||||
},
|
},
|
||||||
"django": {
|
"django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a2127ad0150ec6966655bedf15dbbff9697cc86d61653db2da1afa506c0b04cc",
|
"sha256:14a4b7cd77297fba516fc0d92444cc2e2e388aa9de32d7a68d4a83d58f5a4927",
|
||||||
"sha256:c93c28ccf1d094cbd00d860e83128a39e45d2c571d3b54361713aaaf9a94cac4"
|
"sha256:14b87775ffedab2ef6299b73343d1b4b41e5d4e2aa58c6581f114dbec01e3f8f"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.1.2"
|
"version": "==3.1.3"
|
||||||
},
|
},
|
||||||
"django-debug-toolbar": {
|
"django-debug-toolbar": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1392,17 +1417,17 @@
|
|||||||
},
|
},
|
||||||
"gitpython": {
|
"gitpython": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:138016d519bf4dd55b22c682c904ed2fd0235c3612b2f8f65ce218ff358deed8",
|
"sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b",
|
||||||
"sha256:a03f728b49ce9597a6655793207c6ab0da55519368ff5961e4a74ae475b9fa8e"
|
"sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8"
|
||||||
],
|
],
|
||||||
"version": "==3.1.9"
|
"version": "==3.1.11"
|
||||||
},
|
},
|
||||||
"iniconfig": {
|
"iniconfig": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437",
|
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
|
||||||
"sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"
|
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
|
||||||
],
|
],
|
||||||
"version": "==1.0.1"
|
"version": "==1.1.1"
|
||||||
},
|
},
|
||||||
"isort": {
|
"isort": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1461,17 +1486,17 @@
|
|||||||
},
|
},
|
||||||
"pathspec": {
|
"pathspec": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0",
|
"sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd",
|
||||||
"sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"
|
"sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"
|
||||||
],
|
],
|
||||||
"version": "==0.8.0"
|
"version": "==0.8.1"
|
||||||
},
|
},
|
||||||
"pbr": {
|
"pbr": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea",
|
"sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9",
|
||||||
"sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15"
|
"sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"
|
||||||
],
|
],
|
||||||
"version": "==5.5.0"
|
"version": "==5.5.1"
|
||||||
},
|
},
|
||||||
"pep8-naming": {
|
"pep8-naming": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1566,26 +1591,26 @@
|
|||||||
},
|
},
|
||||||
"pytest": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7a8190790c17d79a11f847fba0b004ee9a8122582ebff4729a082c109e81a4c9",
|
"sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe",
|
||||||
"sha256:8f593023c1a0f916110285b6efd7f99db07d59546e3d8c36fc60e2ab05d3be92"
|
"sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==6.1.1"
|
"version": "==6.1.2"
|
||||||
},
|
},
|
||||||
"pytest-django": {
|
"pytest-django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4de6dbd077ed8606616958f77655fed0d5e3ee45159475671c7fa67596c6dba6",
|
"sha256:10e384e6b8912ded92db64c58be8139d9ae23fb8361e5fc139d8e4f8fc601bc2",
|
||||||
"sha256:c33e3d3da14d8409b125d825d4e74da17bb252191bf6fc3da6856e27a8b73ea4"
|
"sha256:26f02c16d36fd4c8672390deebe3413678d89f30720c16efb8b2a6bf63b9041f"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.10.0"
|
"version": "==4.1.0"
|
||||||
},
|
},
|
||||||
"pytz": {
|
"pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
|
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
|
||||||
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
|
"sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
|
||||||
],
|
],
|
||||||
"version": "==2020.1"
|
"version": "==2020.4"
|
||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1606,35 +1631,51 @@
|
|||||||
},
|
},
|
||||||
"regex": {
|
"regex": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:088afc8c63e7bd187a3c70a94b9e50ab3f17e1d3f52a32750b5b77dbe99ef5ef",
|
"sha256:03855ee22980c3e4863dc84c42d6d2901133362db5daf4c36b710dd895d78f0a",
|
||||||
"sha256:1fe0a41437bbd06063aa184c34804efa886bcc128222e9916310c92cd54c3b4c",
|
"sha256:06b52815d4ad38d6524666e0d50fe9173533c9cc145a5779b89733284e6f688f",
|
||||||
"sha256:3d20024a70b97b4f9546696cbf2fd30bae5f42229fbddf8661261b1eaff0deb7",
|
"sha256:11116d424734fe356d8777f89d625f0df783251ada95d6261b4c36ad27a394bb",
|
||||||
"sha256:41bb65f54bba392643557e617316d0d899ed5b4946dccee1cb6696152b29844b",
|
"sha256:119e0355dbdd4cf593b17f2fc5dbd4aec2b8899d0057e4957ba92f941f704bf5",
|
||||||
"sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c",
|
"sha256:127a9e0c0d91af572fbb9e56d00a504dbd4c65e574ddda3d45b55722462210de",
|
||||||
"sha256:4707f3695b34335afdfb09be3802c87fa0bc27030471dbc082f815f23688bc63",
|
"sha256:1ec66700a10e3c75f1f92cbde36cca0d3aaee4c73dfa26699495a3a30b09093c",
|
||||||
"sha256:49f23ebd5ac073765ecbcf046edc10d63dcab2f4ae2bce160982cb30df0c0302",
|
"sha256:227a8d2e5282c2b8346e7f68aa759e0331a0b4a890b55a5cfbb28bd0261b84c0",
|
||||||
"sha256:5533a959a1748a5c042a6da71fe9267a908e21eded7a4f373efd23a2cbdb0ecc",
|
"sha256:2564def9ce0710d510b1fc7e5178ce2d20f75571f788b5197b3c8134c366f50c",
|
||||||
"sha256:5d892a4f1c999834eaa3c32bc9e8b976c5825116cde553928c4c8e7e48ebda67",
|
"sha256:297116e79074ec2a2f885d22db00ce6e88b15f75162c5e8b38f66ea734e73c64",
|
||||||
"sha256:5f18875ac23d9aa2f060838e8b79093e8bb2313dbaaa9f54c6d8e52a5df097be",
|
"sha256:2dc522e25e57e88b4980d2bdd334825dbf6fa55f28a922fc3bfa60cc09e5ef53",
|
||||||
"sha256:60b0e9e6dc45683e569ec37c55ac20c582973841927a85f2d8a7d20ee80216ab",
|
"sha256:3a5f08039eee9ea195a89e180c5762bfb55258bfb9abb61a20d3abee3b37fd12",
|
||||||
"sha256:816064fc915796ea1f26966163f6845de5af78923dfcecf6551e095f00983650",
|
"sha256:3dfca201fa6b326239e1bccb00b915e058707028809b8ecc0cf6819ad233a740",
|
||||||
"sha256:84cada8effefe9a9f53f9b0d2ba9b7b6f5edf8d2155f9fdbe34616e06ececf81",
|
"sha256:49461446b783945597c4076aea3f49aee4b4ce922bd241e4fcf62a3e7c61794c",
|
||||||
"sha256:84e9407db1b2eb368b7ecc283121b5e592c9aaedbe8c78b1a2f1102eb2e21d19",
|
"sha256:4afa350f162551cf402bfa3cd8302165c8e03e689c897d185f16a167328cc6dd",
|
||||||
"sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637",
|
"sha256:4b5a9bcb56cc146c3932c648603b24514447eafa6ce9295234767bf92f69b504",
|
||||||
"sha256:9a02d0ae31d35e1ec12a4ea4d4cca990800f66a917d0fb997b20fbc13f5321fc",
|
"sha256:52e83a5f28acd621ba8e71c2b816f6541af7144b69cc5859d17da76c436a5427",
|
||||||
"sha256:9bc13e0d20b97ffb07821aa3e113f9998e84994fe4d159ffa3d3a9d1b805043b",
|
"sha256:625116aca6c4b57c56ea3d70369cacc4d62fead4930f8329d242e4fe7a58ce4b",
|
||||||
"sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d",
|
"sha256:654c1635f2313d0843028487db2191530bca45af61ca85d0b16555c399625b0e",
|
||||||
"sha256:ae91972f8ac958039920ef6e8769277c084971a142ce2b660691793ae44aae6b",
|
"sha256:8092a5a06ad9a7a247f2a76ace121183dc4e1a84c259cf9c2ce3bbb69fac3582",
|
||||||
"sha256:c570f6fa14b9c4c8a4924aaad354652366577b4f98213cf76305067144f7b100",
|
"sha256:832339223b9ce56b7b15168e691ae654d345ac1635eeb367ade9ecfe0e66bee0",
|
||||||
"sha256:c9443124c67b1515e4fe0bb0aa18df640965e1030f468a2a5dc2589b26d130ad",
|
"sha256:8ca9dca965bd86ea3631b975d63b0693566d3cc347e55786d5514988b6f5b84c",
|
||||||
"sha256:d23a18037313714fb3bb5a94434d3151ee4300bae631894b1ac08111abeaa4a3",
|
"sha256:96f99219dddb33e235a37283306834700b63170d7bb2a1ee17e41c6d589c8eb9",
|
||||||
"sha256:eaf548d117b6737df379fdd53bdde4f08870e66d7ea653e230477f071f861121",
|
"sha256:9b6305295b6591e45f069d3553c54d50cc47629eb5c218aac99e0f7fafbf90a1",
|
||||||
"sha256:ebbe29186a3d9b0c591e71b7393f1ae08c83cb2d8e517d2a822b8f7ec99dfd8b",
|
"sha256:a62162be05edf64f819925ea88d09d18b09bebf20971b363ce0c24e8b4aa14c0",
|
||||||
"sha256:eda4771e0ace7f67f58bc5b560e27fb20f32a148cbc993b0c3835970935c2707",
|
"sha256:aacc8623ffe7999a97935eeabbd24b1ae701d08ea8f874a6ff050e93c3e658cf",
|
||||||
"sha256:f1b3afc574a3db3b25c89161059d857bd4909a1269b0b3cb3c904677c8c4a3f7",
|
"sha256:b45bab9f224de276b7bc916f6306b86283f6aa8afe7ed4133423efb42015a898",
|
||||||
"sha256:f2388013e68e750eaa16ccbea62d4130180c26abb1d8e5d584b9baf69672b30f"
|
"sha256:b88fa3b8a3469f22b4f13d045d9bd3eda797aa4e406fde0a2644bc92bbdd4bdd",
|
||||||
|
"sha256:b8a686a6c98872007aa41fdbb2e86dc03b287d951ff4a7f1da77fb7f14113e4d",
|
||||||
|
"sha256:bd904c0dec29bbd0769887a816657491721d5f545c29e30fd9d7a1a275dc80ab",
|
||||||
|
"sha256:bf4f896c42c63d1f22039ad57de2644c72587756c0cfb3cc3b7530cfe228277f",
|
||||||
|
"sha256:c13d311a4c4a8d671f5860317eb5f09591fbe8259676b86a85769423b544451e",
|
||||||
|
"sha256:c2c6c56ee97485a127555c9595c069201b5161de9d05495fbe2132b5ac104786",
|
||||||
|
"sha256:c32c91a0f1ac779cbd73e62430de3d3502bbc45ffe5bb6c376015acfa848144b",
|
||||||
|
"sha256:c3466a84fce42c2016113101018a9981804097bacbab029c2d5b4fcb224b89de",
|
||||||
|
"sha256:c454ad88e56e80e44f824ef8366bb7e4c3def12999151fd5c0ea76a18fe9aa3e",
|
||||||
|
"sha256:c8a2b7ccff330ae4c460aff36626f911f918555660cc28163417cb84ffb25789",
|
||||||
|
"sha256:cb905f3d2e290a8b8f1579d3984f2cfa7c3a29cc7cba608540ceeed18513f520",
|
||||||
|
"sha256:cfcf28ed4ce9ced47b9b9670a4f0d3d3c0e4d4779ad4dadb1ad468b097f808aa",
|
||||||
|
"sha256:dd3e6547ecf842a29cf25123fbf8d2461c53c8d37aa20d87ecee130c89b7079b",
|
||||||
|
"sha256:de7fd57765398d141949946c84f3590a68cf5887dac3fc52388df0639b01eda4",
|
||||||
|
"sha256:ea37320877d56a7f0a1e6a625d892cf963aa7f570013499f5b8d5ab8402b5625",
|
||||||
|
"sha256:f1fce1e4929157b2afeb4bb7069204d4370bab9f4fc03ca1fbec8bd601f8c87d",
|
||||||
|
"sha256:f43109822df2d3faac7aad79613f5f02e4eab0fc8ad7932d2e70e2a83bd49c26"
|
||||||
],
|
],
|
||||||
"version": "==2020.9.27"
|
"version": "==2020.10.28"
|
||||||
},
|
},
|
||||||
"requirements-detector": {
|
"requirements-detector": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1693,33 +1734,42 @@
|
|||||||
},
|
},
|
||||||
"toml": {
|
"toml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
|
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||||
"sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
|
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||||
],
|
],
|
||||||
"version": "==0.10.1"
|
"version": "==0.10.2"
|
||||||
},
|
},
|
||||||
"typed-ast": {
|
"typed-ast": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
|
"sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
|
||||||
"sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
|
"sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
|
||||||
|
"sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d",
|
||||||
"sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
|
"sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
|
||||||
"sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
|
"sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
|
||||||
"sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
|
"sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
|
||||||
|
"sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c",
|
||||||
"sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
|
"sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
|
||||||
"sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
|
"sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
|
||||||
"sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
|
"sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
|
||||||
"sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
|
"sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
|
||||||
"sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
|
"sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
|
||||||
"sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
|
"sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
|
||||||
|
"sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d",
|
||||||
"sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
|
"sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
|
||||||
"sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
|
"sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
|
||||||
|
"sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c",
|
||||||
"sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
|
"sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
|
||||||
|
"sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395",
|
||||||
"sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
|
"sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
|
||||||
"sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
|
"sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
|
||||||
"sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
|
"sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
|
||||||
"sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
|
"sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
|
||||||
"sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
|
"sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
|
||||||
|
"sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072",
|
||||||
|
"sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298",
|
||||||
|
"sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91",
|
||||||
"sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
|
"sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
|
||||||
|
"sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f",
|
||||||
"sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
|
"sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
|
||||||
],
|
],
|
||||||
"version": "==1.4.1"
|
"version": "==1.4.1"
|
||||||
@ -1737,12 +1787,12 @@
|
|||||||
"secure"
|
"secure"
|
||||||
],
|
],
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
|
"sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
|
||||||
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
|
"sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": null,
|
"markers": null,
|
||||||
"version": "==1.25.10"
|
"version": "==1.25.11"
|
||||||
},
|
},
|
||||||
"wrapt": {
|
"wrapt": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|||||||
@ -6,8 +6,9 @@ As passbook is currently in a pre-stable, only the latest "stable" version is su
|
|||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| -------- | ------------------ |
|
| -------- | ------------------ |
|
||||||
| 0.9.x | :white_check_mark: |
|
|
||||||
| 0.10.x | :white_check_mark: |
|
| 0.10.x | :white_check_mark: |
|
||||||
|
| 0.11.x | :white_check_mark: |
|
||||||
|
| 0.12.x | :white_check_mark: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
|||||||
@ -89,7 +89,7 @@ stages:
|
|||||||
versionSpec: '3.8'
|
versionSpec: '3.8'
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
inputs:
|
inputs:
|
||||||
script: npm install -g pyright@1.1.75
|
script: npm install -g pyright@1.1.79
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
inputs:
|
inputs:
|
||||||
script: |
|
script: |
|
||||||
@ -169,6 +169,13 @@ stages:
|
|||||||
dockerComposeFile: 'scripts/ci.docker-compose.yml'
|
dockerComposeFile: 'scripts/ci.docker-compose.yml'
|
||||||
action: 'Run services'
|
action: 'Run services'
|
||||||
buildImages: false
|
buildImages: false
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: Install K3d and prepare
|
||||||
|
inputs:
|
||||||
|
script: |
|
||||||
|
wget -q -O - https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash
|
||||||
|
k3d cluster create
|
||||||
|
k3d kubeconfig write -o ~/.kube/config --overwrite
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
inputs:
|
inputs:
|
||||||
script: |
|
script: |
|
||||||
@ -178,6 +185,7 @@ stages:
|
|||||||
displayName: Run full test suite
|
displayName: Run full test suite
|
||||||
inputs:
|
inputs:
|
||||||
script: |
|
script: |
|
||||||
|
export PB_TEST_K8S=true
|
||||||
pipenv run coverage run ./manage.py test passbook -v 3
|
pipenv run coverage run ./manage.py test passbook -v 3
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
inputs:
|
inputs:
|
||||||
@ -203,6 +211,13 @@ stages:
|
|||||||
dockerComposeFile: 'scripts/ci.docker-compose.yml'
|
dockerComposeFile: 'scripts/ci.docker-compose.yml'
|
||||||
action: 'Run services'
|
action: 'Run services'
|
||||||
buildImages: false
|
buildImages: false
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: Install K3d and prepare
|
||||||
|
inputs:
|
||||||
|
script: |
|
||||||
|
wget -q -O - https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash
|
||||||
|
k3d cluster create
|
||||||
|
k3d kubeconfig write -o ~/.kube/config --overwrite
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
inputs:
|
inputs:
|
||||||
script: |
|
script: |
|
||||||
@ -225,6 +240,7 @@ stages:
|
|||||||
displayName: Run full test suite
|
displayName: Run full test suite
|
||||||
inputs:
|
inputs:
|
||||||
script: |
|
script: |
|
||||||
|
export PB_TEST_K8S=true
|
||||||
pipenv run coverage run ./manage.py test e2e -v 3 --failfast
|
pipenv run coverage run ./manage.py test e2e -v 3 --failfast
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
condition: always()
|
condition: always()
|
||||||
@ -232,6 +248,7 @@ stages:
|
|||||||
inputs:
|
inputs:
|
||||||
script: |
|
script: |
|
||||||
docker stop $(docker ps -aq)
|
docker stop $(docker ps -aq)
|
||||||
|
docker container prune -f
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
displayName: Prepare unittests and coverage for upload
|
displayName: Prepare unittests and coverage for upload
|
||||||
inputs:
|
inputs:
|
||||||
|
|||||||
@ -12,18 +12,14 @@ services:
|
|||||||
- POSTGRES_PASSWORD=${PG_PASS:-thisisnotagoodpassword}
|
- POSTGRES_PASSWORD=${PG_PASS:-thisisnotagoodpassword}
|
||||||
- POSTGRES_USER=passbook
|
- POSTGRES_USER=passbook
|
||||||
- POSTGRES_DB=passbook
|
- POSTGRES_DB=passbook
|
||||||
labels:
|
|
||||||
- traefik.enable=false
|
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
redis:
|
redis:
|
||||||
image: redis
|
image: redis
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
labels:
|
|
||||||
- traefik.enable=false
|
|
||||||
server:
|
server:
|
||||||
image: beryju/passbook:${PASSBOOK_TAG:-0.11.0-stable}
|
image: beryju/passbook:${PASSBOOK_TAG:-0.12.9-stable}
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
PASSBOOK_REDIS__HOST: redis
|
PASSBOOK_REDIS__HOST: redis
|
||||||
@ -34,38 +30,50 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
labels:
|
labels:
|
||||||
- traefik.port=8000
|
traefik.enable: 'true'
|
||||||
- traefik.docker.network=internal
|
traefik.docker.network: internal
|
||||||
- traefik.frontend.rule=PathPrefix:/
|
traefik.http.routers.app-router.rule: PathPrefix(`/`)
|
||||||
|
traefik.http.routers.app-router.service: app-service
|
||||||
|
traefik.http.routers.app-router.tls: 'true'
|
||||||
|
traefik.http.services.app-service.loadbalancer.healthcheck.hostname: passbook-healthcheck-host
|
||||||
|
traefik.http.services.app-service.loadbalancer.server.port: '8000'
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
worker:
|
worker:
|
||||||
image: beryju/passbook:${PASSBOOK_TAG:-0.11.0-stable}
|
image: beryju/passbook:${PASSBOOK_TAG:-0.12.9-stable}
|
||||||
command: worker
|
command: worker
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
labels:
|
|
||||||
- traefik.enable=false
|
|
||||||
environment:
|
environment:
|
||||||
PASSBOOK_REDIS__HOST: redis
|
PASSBOOK_REDIS__HOST: redis
|
||||||
PASSBOOK_POSTGRESQL__HOST: postgresql
|
PASSBOOK_POSTGRESQL__HOST: postgresql
|
||||||
PASSBOOK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
PASSBOOK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||||
volumes:
|
volumes:
|
||||||
- ./backups:/backups
|
- ./backups:/backups
|
||||||
- /var/run/docker.socket:/var/run/docker.socket
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
static:
|
static:
|
||||||
image: beryju/passbook-static:${PASSBOOK_TAG:-0.11.0-stable}
|
image: beryju/passbook-static:${PASSBOOK_TAG:-0.12.9-stable}
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
labels:
|
labels:
|
||||||
- traefik.frontend.rule=PathPrefix:/static, /robots.txt, /favicon.ico
|
traefik.enable: 'true'
|
||||||
- traefik.port=80
|
traefik.docker.network: internal
|
||||||
- traefik.docker.network=internal
|
traefik.http.routers.static-router.rule: PathPrefix(`/static`, `/robots.txt`, `/favicon.ico`)
|
||||||
|
traefik.http.routers.static-router.tls: 'true'
|
||||||
|
traefik.http.routers.static-router.service: static-service
|
||||||
|
traefik.http.services.static-service.loadbalancer.healthcheck.path: /
|
||||||
|
traefik.http.services.static-service.loadbalancer.server.port: '80'
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:1.7
|
image: traefik:2.3
|
||||||
command: --api --docker --defaultentrypoints=https --entryPoints='Name:http Address::80 Redirect.EntryPoint:https' --entryPoints='Name:https Address::443 TLS'
|
command:
|
||||||
|
- "--log.format=json"
|
||||||
|
- "--api.insecure=true"
|
||||||
|
- "--providers.docker=true"
|
||||||
|
- "--providers.docker.exposedbydefault=false"
|
||||||
|
- "--entrypoints.http.address=:80"
|
||||||
|
- "--entrypoints.https.address=:443"
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@ -117,7 +117,7 @@
|
|||||||
},
|
},
|
||||||
"model": "passbook_stages_user_login.userloginstage",
|
"model": "passbook_stages_user_login.userloginstage",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"session_duration": 0
|
"session_duration": "seconds=-1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -136,7 +136,7 @@
|
|||||||
},
|
},
|
||||||
"model": "passbook_stages_user_login.userloginstage",
|
"model": "passbook_stages_user_login.userloginstage",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"session_duration": 0
|
"session_duration": "seconds=-1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
"model": "passbook_stages_user_login.userloginstage",
|
"model": "passbook_stages_user_login.userloginstage",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"session_duration": 0
|
"session_duration": "seconds=-1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
"model": "passbook_stages_user_login.userloginstage",
|
"model": "passbook_stages_user_login.userloginstage",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"session_duration": 0
|
"session_duration": "seconds=-1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -95,7 +95,8 @@
|
|||||||
},
|
},
|
||||||
"model": "passbook_flows.flowstagebinding",
|
"model": "passbook_flows.flowstagebinding",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"re_evaluate_policies": false
|
"evaluate_on_plan": false,
|
||||||
|
"re_evaluate_policies": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -101,7 +101,7 @@
|
|||||||
{
|
{
|
||||||
"identifiers": {
|
"identifiers": {
|
||||||
"pk": "975d5502-1e22-4d10-b560-fbc5bd70ff4d",
|
"pk": "975d5502-1e22-4d10-b560-fbc5bd70ff4d",
|
||||||
"name": "default-password-change-prompt"
|
"name": "Change your password"
|
||||||
},
|
},
|
||||||
"model": "passbook_stages_prompt.promptstage",
|
"model": "passbook_stages_prompt.promptstage",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
@ -118,7 +118,7 @@
|
|||||||
},
|
},
|
||||||
"model": "passbook_stages_user_login.userloginstage",
|
"model": "passbook_stages_user_login.userloginstage",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"session_duration": 0
|
"session_duration": "seconds=-1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 342 KiB After Width: | Height: | Size: 373 KiB |
@ -13,7 +13,7 @@ Download the latest `docker-compose.yml` from [here](https://raw.githubuserconte
|
|||||||
|
|
||||||
To optionally enable error-reporting, run `echo PASSBOOK_ERROR_REPORTING__ENABLED=true >> .env`
|
To optionally enable error-reporting, run `echo PASSBOOK_ERROR_REPORTING__ENABLED=true >> .env`
|
||||||
|
|
||||||
To optionally deploy a different version run `echo PASSBOOK_TAG=0.11.0-stable >> .env`
|
To optionally deploy a different version run `echo PASSBOOK_TAG=0.12.9-stable >> .env`
|
||||||
|
|
||||||
If this is a fresh passbook install run the following commands to generate a password:
|
If this is a fresh passbook install run the following commands to generate a password:
|
||||||
|
|
||||||
|
|||||||
@ -11,29 +11,30 @@ This installation automatically applies database migrations on startup. After th
|
|||||||
image:
|
image:
|
||||||
name: beryju/passbook
|
name: beryju/passbook
|
||||||
name_static: beryju/passbook-static
|
name_static: beryju/passbook-static
|
||||||
tag: 0.11.0-stable
|
tag: 0.12.9-stable
|
||||||
|
|
||||||
nameOverride: ""
|
|
||||||
|
|
||||||
serverReplicas: 1
|
serverReplicas: 1
|
||||||
workerReplicas: 1
|
workerReplicas: 1
|
||||||
|
|
||||||
|
# Enable the Kubernetes integration which lets passbook deploy outposts into kubernetes
|
||||||
|
kubernetesIntegration: true
|
||||||
|
|
||||||
config:
|
config:
|
||||||
# Optionally specify fixed secret_key, otherwise generated automatically
|
# Optionally specify fixed secret_key, otherwise generated automatically
|
||||||
# secret_key: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
|
# secretKey: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
|
||||||
# Enable error reporting
|
# Enable error reporting
|
||||||
error_reporting:
|
errorReporting:
|
||||||
enabled: false
|
enabled: false
|
||||||
environment: customer
|
environment: customer
|
||||||
send_pii: false
|
sendPii: false
|
||||||
# Log level used by web and worker
|
# Log level used by web and worker
|
||||||
# Can be either debug, info, warning, error
|
# Can be either debug, info, warning, error
|
||||||
log_level: warning
|
logLevel: warning
|
||||||
|
|
||||||
# Enable Database Backups to S3
|
# Enable Database Backups to S3
|
||||||
# backup:
|
# backup:
|
||||||
# access_key: access-key
|
# accessKey: access-key
|
||||||
# secret_key: secret-key
|
# secretKey: secret-key
|
||||||
# bucket: s3-bucket
|
# bucket: s3-bucket
|
||||||
# region: eu-central-1
|
# region: eu-central-1
|
||||||
# host: s3-host
|
# host: s3-host
|
||||||
@ -42,7 +43,6 @@ ingress:
|
|||||||
annotations: {}
|
annotations: {}
|
||||||
# kubernetes.io/ingress.class: nginx
|
# kubernetes.io/ingress.class: nginx
|
||||||
# kubernetes.io/tls-acme: "true"
|
# kubernetes.io/tls-acme: "true"
|
||||||
path: /
|
|
||||||
hosts:
|
hosts:
|
||||||
- passbook.k8s.local
|
- passbook.k8s.local
|
||||||
tls: []
|
tls: []
|
||||||
|
|||||||
@ -34,7 +34,8 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto https;
|
proxy_set_header X-Forwarded-Proto https;
|
||||||
proxy_set_header X-Forwarded-Port 443;
|
proxy_set_header X-Forwarded-Port 443;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header Host $http_host;
|
# This needs to be set inside the location block, very important.
|
||||||
|
proxy_set_header Host $host;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
}
|
}
|
||||||
|
|||||||
59
docs/integrations/services/home-assistant/index.md
Normal file
59
docs/integrations/services/home-assistant/index.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Home-Assistant Integration
|
||||||
|
|
||||||
|
## What is Home-Assistant
|
||||||
|
|
||||||
|
From https://www.home-assistant.io/
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
Open source home automation that puts local control and privacy first. Powered by a worldwide community of tinkerers and DIY enthusiasts. Perfect to run on a Raspberry Pi or a local server.
|
||||||
|
|
||||||
|
|
||||||
|
## Preparation
|
||||||
|
|
||||||
|
The following placeholders will be used:
|
||||||
|
|
||||||
|
- `hass.company` is the FQDN of the Home-Assistant install.
|
||||||
|
- `passbook.company` is the FQDN of the passbook install.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
This setup uses https://github.com/BeryJu/hass-auth-header and the passbook proxy for authentication. When this [PR](https://github.com/home-assistant/core/pull/32926) is merged, this will no longer be necessary.
|
||||||
|
|
||||||
|
## Home-Assistant
|
||||||
|
|
||||||
|
This guide requires https://github.com/BeryJu/hass-auth-header, which can be installed as described in the Readme.
|
||||||
|
|
||||||
|
Afterwards, make sure the `trusted_proxies` setting contains the IP(s) of the Host(s) passbook is running on.
|
||||||
|
|
||||||
|
With the default Header of `X-Forwarded-Preferred-Username` matching is done on a username basis, so your Name in Home-Assistant and your username in passbook have to match.
|
||||||
|
|
||||||
|
If this is not the case, you can simply add an additional header for your user, which contains the Home-Assistant Name and authenticate based on that.
|
||||||
|
|
||||||
|
For example add this to your user's properties and set the Header to `X-pb-hass-user`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
additionalHeaders:
|
||||||
|
X-pb-hass-user: some other name
|
||||||
|
```
|
||||||
|
|
||||||
|
## passbook
|
||||||
|
|
||||||
|
Create a Proxy Provider with the following values
|
||||||
|
|
||||||
|
- Internal host
|
||||||
|
|
||||||
|
If Home-Assistant is running in docker, and you're deploying the passbook proxy on the same host, set the value to `http://homeassistant:8123`, where Home-Assistant is the name of your container.
|
||||||
|
|
||||||
|
If Home-Assistant is running on a different server than where you are deploying the passbook proxy, set the value to `http://hass.company:8123`.
|
||||||
|
|
||||||
|
- External host
|
||||||
|
|
||||||
|
Set this to the external URL you will be accessing Home-Assistant from.
|
||||||
|
|
||||||
|
Create an application in passbook and select the provider you've created above.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Create an outpost deployment for the provider you've created above, as described [here](../../../outposts/outposts.md). Deploy this Outpost either on the same host or a different host that can access Home-Assistant.
|
||||||
|
|
||||||
|
The outpost will connect to passbook and configure itself.
|
||||||
@ -18,7 +18,7 @@ The following placeholders will be used:
|
|||||||
- `sonarr.company` is the FQDN of the Sonarr install.
|
- `sonarr.company` is the FQDN of the Sonarr install.
|
||||||
- `passbook.company` is the FQDN of the passbook install.
|
- `passbook.company` is the FQDN of the passbook install.
|
||||||
|
|
||||||
Create an application in passbook. Create a Proxy Provider with the following values
|
Create a Proxy Provider with the following values
|
||||||
|
|
||||||
- Internal host
|
- Internal host
|
||||||
|
|
||||||
@ -30,6 +30,8 @@ Create an application in passbook. Create a Proxy Provider with the following va
|
|||||||
|
|
||||||
Set this to the external URL you will be accessing Sonarr from.
|
Set this to the external URL you will be accessing Sonarr from.
|
||||||
|
|
||||||
|
Create an application in passbook and select the provider you've created above.
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
Create an outpost deployment for the provider you've created above, as described [here](../../../outposts/outposts.md). Deploy this Outpost either on the same host or a different host that can access Sonarr.
|
Create an outpost deployment for the provider you've created above, as described [here](../../../outposts/outposts.md). Deploy this Outpost either on the same host or a different host that can access Sonarr.
|
||||||
|
|||||||
@ -16,6 +16,10 @@ From https://en.wikipedia.org/wiki/VCenter
|
|||||||
|
|
||||||
This requires VMware vCenter 7.0.0 or newer.
|
This requires VMware vCenter 7.0.0 or newer.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
It seems that the vCenter still needs to be joined to the Active Directory Domain, otherwise group membership does not work correctly. We're working on a fix for this, for the meantime your vCenter should be part of your Domain.
|
||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
|
|
||||||
The following placeholders will be used:
|
The following placeholders will be used:
|
||||||
|
|||||||
BIN
docs/integrations/sources/active-directory/01_user_create.png
Normal file
BIN
docs/integrations/sources/active-directory/01_user_create.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
docs/integrations/sources/active-directory/02_delegate.png
Normal file
BIN
docs/integrations/sources/active-directory/02_delegate.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
docs/integrations/sources/active-directory/03_pb_status.png
Normal file
BIN
docs/integrations/sources/active-directory/03_pb_status.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
55
docs/integrations/sources/active-directory/index.md
Normal file
55
docs/integrations/sources/active-directory/index.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Active Directory Integration
|
||||||
|
|
||||||
|
## Preparation
|
||||||
|
|
||||||
|
The following placeholders will be used:
|
||||||
|
|
||||||
|
- `ad.company` is the Name of the Active Directory domain.
|
||||||
|
- `passbook.company` is the FQDN of the passbook install.
|
||||||
|
|
||||||
|
## Active Directory Setup
|
||||||
|
|
||||||
|
1. Open Active Directory Users and Computers
|
||||||
|
|
||||||
|
2. Create a user in Active Directory, matching your naming scheme
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. Give the User a password, generated using for example `pwgen 64 1`.
|
||||||
|
|
||||||
|
4. Open the Delegation of Control Wizard by right-clicking the domain.
|
||||||
|
|
||||||
|
5. Select the passbook service user you've just created.
|
||||||
|
|
||||||
|
6. Ensure the "Reset user password and force password change at next logon" Option is checked.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## passbook Setup
|
||||||
|
|
||||||
|
In passbook, create a new LDAP Source in Administration -> Sources.
|
||||||
|
|
||||||
|
Use these settings:
|
||||||
|
|
||||||
|
- Server URI: `ldap://ad.company`
|
||||||
|
|
||||||
|
For passbook to be able to write passwords back to Active Directory, make sure to use `ldaps://`
|
||||||
|
|
||||||
|
- Bind CN: `<name of your service user>@ad.company`
|
||||||
|
- Bind Password: The password you've given the user above
|
||||||
|
- Base DN: The base DN which you want passbook to sync
|
||||||
|
- Property Mappings: Select all and click the right arrow
|
||||||
|
|
||||||
|
The other settings might need to be adjusted based on the setup of your domain.
|
||||||
|
|
||||||
|
- Addition User/Group DN: Additional DN which is *prepended* to your Base DN for user synchronization.
|
||||||
|
- Addition Group DN: Additional DN which is *prepended* to your Base DN for group synchronization.
|
||||||
|
- User object filter: Which objects should be considered users.
|
||||||
|
- Group object filter: Which objects should be considered groups.
|
||||||
|
- User group membership field: Which user field saves the group membership
|
||||||
|
- Object uniqueness field: A user field which contains a unique Identifier
|
||||||
|
- Sync parent group: If enabled, all synchronized groups will be given this group as a parent.
|
||||||
|
|
||||||
|
After you save the source, a synchronization will start in the background. When its done, you cen see the summary on the System Tasks page.
|
||||||
|
|
||||||
|

|
||||||
@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
### Backup
|
### Backup
|
||||||
|
|
||||||
|
!!! notice
|
||||||
|
|
||||||
|
Local backups are **enabled** by default, and will be run daily at 00:00
|
||||||
|
|
||||||
Local backups can be created by running the following command in your passbook installation directory
|
Local backups can be created by running the following command in your passbook installation directory
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -14,15 +18,6 @@ docker-compose run --rm worker backup
|
|||||||
|
|
||||||
This will dump the current database into the `./backups` folder. By defaults, the last 10 Backups are kept.
|
This will dump the current database into the `./backups` folder. By defaults, the last 10 Backups are kept.
|
||||||
|
|
||||||
To schedule these backups, use the following snippet in a crontab
|
|
||||||
|
|
||||||
```
|
|
||||||
0 0 * * * bash -c "cd <passbook install location> && docker-compose run --rm worker backup" >/dev/null
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! notice
|
|
||||||
|
|
||||||
passbook does support automatic backups on a schedule, however this is currently not recommended, as there is no way to monitor these scheduled tasks.
|
|
||||||
|
|
||||||
### Restore
|
### Restore
|
||||||
|
|
||||||
@ -42,11 +37,7 @@ After you've restored the backup, it is recommended to restart all services with
|
|||||||
|
|
||||||
### S3 Configuration
|
### S3 Configuration
|
||||||
|
|
||||||
!!! notice
|
#### Preparation
|
||||||
|
|
||||||
To trigger backups with S3 enabled, use the same commands as above.
|
|
||||||
|
|
||||||
#### S3 Preparation
|
|
||||||
|
|
||||||
passbook expects the bucket you select to already exist. The IAM User given to passbook should have the following permissions
|
passbook expects the bucket you select to already exist. The IAM User given to passbook should have the following permissions
|
||||||
|
|
||||||
@ -101,11 +92,11 @@ Simply enable these options in your values.yaml file
|
|||||||
```yaml
|
```yaml
|
||||||
# Enable Database Backups to S3
|
# Enable Database Backups to S3
|
||||||
backup:
|
backup:
|
||||||
access_key: access-key
|
accessKey: access-key
|
||||||
secret_key: secret-key
|
secretKey: secret-key
|
||||||
bucket: s3-bucket
|
bucket: s3-bucket
|
||||||
region: eu-central-1
|
region: eu-central-1
|
||||||
host: s3-host
|
host: s3-host
|
||||||
```
|
```
|
||||||
|
|
||||||
Afterwards, run a `helm upgrade` to update the ConfigMap. Because passbook-scheduled backups are not recommended currently, a Kubernetes CronJob is created that runs the backup daily.
|
Afterwards, run a `helm upgrade` to update the ConfigMap. Backups are done automatically as above, at 00:00 every day.
|
||||||
|
|||||||
@ -26,7 +26,11 @@ return False
|
|||||||
- `request.obj`: A Django Model instance. This is only set if the policy is ran against an object.
|
- `request.obj`: A Django Model instance. This is only set if the policy is ran against an object.
|
||||||
- `request.context`: A dictionary with dynamic data. This depends on the origin of the execution.
|
- `request.context`: A dictionary with dynamic data. This depends on the origin of the execution.
|
||||||
- `pb_is_sso_flow`: Boolean which is true if request was initiated by authenticating through an external provider.
|
- `pb_is_sso_flow`: Boolean which is true if request was initiated by authenticating through an external provider.
|
||||||
- `pb_client_ip`: Client's IP Address or '255.255.255.255' if no IP Address could be extracted. Can be [compared](../expressions/index.md#comparing-ip-addresses)
|
- `pb_client_ip`: Client's IP Address or 255.255.255.255 if no IP Address could be extracted. Can be [compared](../expressions/index.md#comparing-ip-addresses), for example
|
||||||
|
|
||||||
|
```python
|
||||||
|
return pb_client_ip in ip_network('10.0.0.0/24')
|
||||||
|
```
|
||||||
|
|
||||||
Additionally, when the policy is executed from a flow, every variable from the flow's current context is accessible under the `context` object.
|
Additionally, when the policy is executed from a flow, every variable from the flow's current context is accessible under the `context` object.
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,14 @@ The Proxy these extra headers to the application:
|
|||||||
|
|
||||||
Header Name | Value
|
Header Name | Value
|
||||||
-------------|-------
|
-------------|-------
|
||||||
X-Auth-Request-User | The user's unique identifier
|
X-Forwarded-User | The user's unique identifier (**not the username**)
|
||||||
X-Auth-Request-Email | The user's email address
|
X-Forwarded-Email | The user's email address
|
||||||
X-Auth-Request-Preferred-Username | The user's username
|
X-Forwarded-Preferred-Username | The user's username
|
||||||
|
X-Auth-Username | The user's username
|
||||||
|
|
||||||
|
Additionally, you can add more custom headers using `additionalHeaders` in the User or Group Properties, for example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
additionalHeaders:
|
||||||
|
X-additional-header: bar
|
||||||
|
```
|
||||||
|
|||||||
20
docs/upgrading/to-0.11.md
Normal file
20
docs/upgrading/to-0.11.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Upgrading to 0.11
|
||||||
|
|
||||||
|
This update brings these headline features:
|
||||||
|
|
||||||
|
- Add Backup and Restore, currently only externally schedulable, documented [here](https://passbook.beryju.org/maintenance/backups/)
|
||||||
|
- New Admin Dashboard with more metrics and Charts
|
||||||
|
|
||||||
|
Shows successful and failed logins from the last 24 hours, as well as the most used applications
|
||||||
|
- Add search to all table views
|
||||||
|
- Outpost now supports a Docker Controller, which installs the Outpost on the same host as passbook, updates and manages it
|
||||||
|
- Add Token Identifier
|
||||||
|
|
||||||
|
Tokens now have an identifier which is used to reference to them, so the Primary key is not shown in URLs
|
||||||
|
- `core/applications/list` API now shows applications the user has access to via policies
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
This upgrade can be done as with minor upgrades, the only external change is the new docker-compose file, which enabled the Docker Integration for Outposts. To use this feature, please download the latest docker-compose from [here](https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml).
|
||||||
|
|
||||||
|
Afterwards, you can simply run `docker-compose up -d` and then the normal upgrade command of `docker-compose run --rm server migrate`.
|
||||||
63
docs/upgrading/to-0.12.md
Normal file
63
docs/upgrading/to-0.12.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# Upgrading to 0.12
|
||||||
|
|
||||||
|
This update brings these headline features:
|
||||||
|
|
||||||
|
- Rewrite Outpost state Logic, which now supports multiple concurrent Outpost instances.
|
||||||
|
- Add Kubernetes Integration for Outposts, which deploys and maintains Outposts with High Availability in a Kubernetes Cluster
|
||||||
|
- Add System Task Overview to see all background tasks, their status, the log output, and retry them
|
||||||
|
- Alerts now disappear automatically
|
||||||
|
- Audit Logs are now searchable
|
||||||
|
- Users can now create their own Tokens to access the API
|
||||||
|
- docker-compose deployment now uses traefik 2.3
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
- Fix high CPU Usage of the proxy when Websocket connections fail
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
### docker-compose
|
||||||
|
|
||||||
|
Docker-compose users should download the latest docker-compose file from [here](https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml). This includes the new traefik 2.3.
|
||||||
|
|
||||||
|
Afterwards, you can simply run `docker-compose up -d` and then the normal upgrade command of `docker-compose run --rm server migrate`.
|
||||||
|
|
||||||
|
### Kubernetes
|
||||||
|
|
||||||
|
For Kubernetes users, there are some changes to the helm values.
|
||||||
|
|
||||||
|
The values change from
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
config:
|
||||||
|
# Optionally specify fixed secret_key, otherwise generated automatically
|
||||||
|
# secret_key: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
|
||||||
|
# Enable error reporting
|
||||||
|
error_reporting:
|
||||||
|
enabled: false
|
||||||
|
environment: customer
|
||||||
|
send_pii: false
|
||||||
|
# Log level used by web and worker
|
||||||
|
# Can be either debug, info, warning, error
|
||||||
|
log_level: warning
|
||||||
|
```
|
||||||
|
|
||||||
|
to
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
config:
|
||||||
|
# Optionally specify fixed secret_key, otherwise generated automatically
|
||||||
|
# secretKey: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
|
||||||
|
# Enable error reporting
|
||||||
|
errorReporting:
|
||||||
|
enabled: false
|
||||||
|
environment: customer
|
||||||
|
sendPii: false
|
||||||
|
# Log level used by web and worker
|
||||||
|
# Can be either debug, info, warning, error
|
||||||
|
logLevel: warning
|
||||||
|
```
|
||||||
|
|
||||||
|
in order to be consistent with the rest of the settings.
|
||||||
|
|
||||||
|
There is also a new setting called `kubernetesIntegration`, which controls the Kubernetes integration for passbook. When enabled (the default), a Service Account is created, which allows passbook to deploy and update Outposts.
|
||||||
@ -8,7 +8,7 @@ from docker.types import Healthcheck
|
|||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
from e2e.utils import USER, SeleniumTestCase
|
from e2e.utils import USER, SeleniumTestCase, retry
|
||||||
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
|
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
|
||||||
from passbook.stages.email.models import EmailStage, EmailTemplates
|
from passbook.stages.email.models import EmailStage, EmailTemplates
|
||||||
from passbook.stages.identification.models import IdentificationStage
|
from passbook.stages.identification.models import IdentificationStage
|
||||||
@ -23,7 +23,7 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||||||
|
|
||||||
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
||||||
return {
|
return {
|
||||||
"image": "mailhog/mailhog:v1.0.1",
|
"image": "docker.beryju.org/proxy/mailhog/mailhog:v1.0.1",
|
||||||
"detach": True,
|
"detach": True,
|
||||||
"network_mode": "host",
|
"network_mode": "host",
|
||||||
"auto_remove": True,
|
"auto_remove": True,
|
||||||
@ -34,6 +34,7 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_enroll_2_step(self):
|
def test_enroll_2_step(self):
|
||||||
"""Test 2-step enroll flow"""
|
"""Test 2-step enroll flow"""
|
||||||
# First stage fields
|
# First stage fields
|
||||||
@ -119,6 +120,7 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||||||
"foo@bar.baz",
|
"foo@bar.baz",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
@override_settings(EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend")
|
@override_settings(EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend")
|
||||||
def test_enroll_email(self):
|
def test_enroll_email(self):
|
||||||
"""Test enroll with Email verification"""
|
"""Test enroll with Email verification"""
|
||||||
|
|||||||
@ -5,13 +5,14 @@ from unittest.case import skipUnless
|
|||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.common.keys import Keys
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
from e2e.utils import USER, SeleniumTestCase
|
from e2e.utils import USER, SeleniumTestCase, retry
|
||||||
|
|
||||||
|
|
||||||
@skipUnless(platform.startswith("linux"), "requires local docker")
|
@skipUnless(platform.startswith("linux"), "requires local docker")
|
||||||
class TestFlowsLogin(SeleniumTestCase):
|
class TestFlowsLogin(SeleniumTestCase):
|
||||||
"""test default login flow"""
|
"""test default login flow"""
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_login(self):
|
def test_login(self):
|
||||||
"""test default login flow"""
|
"""test default login flow"""
|
||||||
self.driver.get(f"{self.live_server_url}/flows/default-authentication-flow/")
|
self.driver.get(f"{self.live_server_url}/flows/default-authentication-flow/")
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from selenium.webdriver.common.by import By
|
|||||||
from selenium.webdriver.common.keys import Keys
|
from selenium.webdriver.common.keys import Keys
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
from e2e.utils import USER, SeleniumTestCase
|
from e2e.utils import USER, SeleniumTestCase, retry
|
||||||
from passbook.flows.models import Flow, FlowStageBinding
|
from passbook.flows.models import Flow, FlowStageBinding
|
||||||
from passbook.stages.otp_validate.models import OTPValidateStage
|
from passbook.stages.otp_validate.models import OTPValidateStage
|
||||||
|
|
||||||
@ -21,6 +21,7 @@ from passbook.stages.otp_validate.models import OTPValidateStage
|
|||||||
class TestFlowsOTP(SeleniumTestCase):
|
class TestFlowsOTP(SeleniumTestCase):
|
||||||
"""test flow with otp stages"""
|
"""test flow with otp stages"""
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_otp_validate(self):
|
def test_otp_validate(self):
|
||||||
"""test flow with otp stages"""
|
"""test flow with otp stages"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
@ -52,6 +53,7 @@ class TestFlowsOTP(SeleniumTestCase):
|
|||||||
USER().username,
|
USER().username,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_otp_totp_setup(self):
|
def test_otp_totp_setup(self):
|
||||||
"""test TOTP Setup stage"""
|
"""test TOTP Setup stage"""
|
||||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
||||||
@ -98,6 +100,7 @@ class TestFlowsOTP(SeleniumTestCase):
|
|||||||
|
|
||||||
self.assertTrue(TOTPDevice.objects.filter(user=USER(), confirmed=True).exists())
|
self.assertTrue(TOTPDevice.objects.filter(user=USER(), confirmed=True).exists())
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_otp_static_setup(self):
|
def test_otp_static_setup(self):
|
||||||
"""test Static OTP Setup stage"""
|
"""test Static OTP Setup stage"""
|
||||||
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
flow: Flow = Flow.objects.get(slug="default-authentication-flow")
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from unittest.case import skipUnless
|
|||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.common.keys import Keys
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
from e2e.utils import USER, SeleniumTestCase
|
from e2e.utils import USER, SeleniumTestCase, retry
|
||||||
from passbook.core.models import User
|
from passbook.core.models import User
|
||||||
from passbook.flows.models import Flow, FlowDesignation
|
from passbook.flows.models import Flow, FlowDesignation
|
||||||
from passbook.providers.oauth2.generators import generate_client_secret
|
from passbook.providers.oauth2.generators import generate_client_secret
|
||||||
@ -16,6 +16,7 @@ from passbook.stages.password.models import PasswordStage
|
|||||||
class TestFlowsStageSetup(SeleniumTestCase):
|
class TestFlowsStageSetup(SeleniumTestCase):
|
||||||
"""test stage setup flows"""
|
"""test stage setup flows"""
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_password_change(self):
|
def test_password_change(self):
|
||||||
"""test password change flow"""
|
"""test password change flow"""
|
||||||
# Ensure that password stage has change_flow set
|
# Ensure that password stage has change_flow set
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from selenium.webdriver.common.by import By
|
|||||||
from selenium.webdriver.common.keys import Keys
|
from selenium.webdriver.common.keys import Keys
|
||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
|
|
||||||
from e2e.utils import USER, SeleniumTestCase
|
from e2e.utils import USER, SeleniumTestCase, retry
|
||||||
from passbook.core.models import Application
|
from passbook.core.models import Application
|
||||||
from passbook.flows.models import Flow
|
from passbook.flows.models import Flow
|
||||||
from passbook.policies.expression.models import ExpressionPolicy
|
from passbook.policies.expression.models import ExpressionPolicy
|
||||||
@ -33,7 +33,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||||||
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
||||||
"""Setup client grafana container which we test OAuth against"""
|
"""Setup client grafana container which we test OAuth against"""
|
||||||
return {
|
return {
|
||||||
"image": "grafana/grafana:7.1.0",
|
"image": "docker.beryju.org/proxy/grafana/grafana:7.1.0",
|
||||||
"detach": True,
|
"detach": True,
|
||||||
"network_mode": "host",
|
"network_mode": "host",
|
||||||
"auto_remove": True,
|
"auto_remove": True,
|
||||||
@ -61,6 +61,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_authorization_consent_implied(self):
|
def test_authorization_consent_implied(self):
|
||||||
"""test OAuth Provider flow (default authorization flow with implied consent)"""
|
"""test OAuth Provider flow (default authorization flow with implied consent)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
@ -115,6 +116,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||||||
USER().username,
|
USER().username,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_authorization_consent_explicit(self):
|
def test_authorization_consent_explicit(self):
|
||||||
"""test OAuth Provider flow (default authorization flow with explicit consent)"""
|
"""test OAuth Provider flow (default authorization flow with explicit consent)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
@ -184,6 +186,7 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
|||||||
USER().username,
|
USER().username,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_denied(self):
|
def test_denied(self):
|
||||||
"""test OAuth Provider flow (default authorization flow, denied)"""
|
"""test OAuth Provider flow (default authorization flow, denied)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from selenium.webdriver.common.keys import Keys
|
|||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from e2e.utils import USER, SeleniumTestCase
|
from e2e.utils import USER, SeleniumTestCase, retry
|
||||||
from passbook.core.models import Application
|
from passbook.core.models import Application
|
||||||
from passbook.crypto.models import CertificateKeyPair
|
from passbook.crypto.models import CertificateKeyPair
|
||||||
from passbook.flows.models import Flow
|
from passbook.flows.models import Flow
|
||||||
@ -47,7 +47,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||||||
|
|
||||||
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
||||||
return {
|
return {
|
||||||
"image": "grafana/grafana:7.1.0",
|
"image": "docker.beryju.org/proxy/grafana/grafana:7.1.0",
|
||||||
"detach": True,
|
"detach": True,
|
||||||
"network_mode": "host",
|
"network_mode": "host",
|
||||||
"auto_remove": True,
|
"auto_remove": True,
|
||||||
@ -80,6 +80,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_redirect_uri_error(self):
|
def test_redirect_uri_error(self):
|
||||||
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
@ -122,6 +123,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||||||
"Redirect URI Error",
|
"Redirect URI Error",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_authorization_consent_implied(self):
|
def test_authorization_consent_implied(self):
|
||||||
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
@ -183,6 +185,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||||||
USER().email,
|
USER().email,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_authorization_logout(self):
|
def test_authorization_logout(self):
|
||||||
"""test OpenID Provider flow with logout"""
|
"""test OpenID Provider flow with logout"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
@ -252,6 +255,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||||||
)
|
)
|
||||||
self.driver.find_element(By.ID, "logout").click()
|
self.driver.find_element(By.ID, "logout").click()
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_authorization_consent_explicit(self):
|
def test_authorization_consent_explicit(self):
|
||||||
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
@ -325,6 +329,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||||||
USER().email,
|
USER().email,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_authorization_denied(self):
|
def test_authorization_denied(self):
|
||||||
"""test OpenID Provider flow (default authorization with access deny)"""
|
"""test OpenID Provider flow (default authorization with access deny)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from selenium.webdriver.common.keys import Keys
|
|||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from e2e.utils import USER, SeleniumTestCase
|
from e2e.utils import USER, SeleniumTestCase, retry
|
||||||
from passbook.core.models import Application
|
from passbook.core.models import Application
|
||||||
from passbook.crypto.models import CertificateKeyPair
|
from passbook.crypto.models import CertificateKeyPair
|
||||||
from passbook.flows.models import Flow
|
from passbook.flows.models import Flow
|
||||||
@ -53,7 +53,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||||||
client: DockerClient = from_env()
|
client: DockerClient = from_env()
|
||||||
client.images.pull("beryju/oidc-test-client")
|
client.images.pull("beryju/oidc-test-client")
|
||||||
container = client.containers.run(
|
container = client.containers.run(
|
||||||
image="beryju/oidc-test-client",
|
image="docker.beryju.org/proxy/beryju/oidc-test-client",
|
||||||
detach=True,
|
detach=True,
|
||||||
network_mode="host",
|
network_mode="host",
|
||||||
auto_remove=True,
|
auto_remove=True,
|
||||||
@ -76,6 +76,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||||||
LOGGER.info("Container failed healthcheck")
|
LOGGER.info("Container failed healthcheck")
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_redirect_uri_error(self):
|
def test_redirect_uri_error(self):
|
||||||
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
"""test OpenID Provider flow (invalid redirect URI, check error message)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
@ -119,6 +120,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||||||
"Redirect URI Error",
|
"Redirect URI Error",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_authorization_consent_implied(self):
|
def test_authorization_consent_implied(self):
|
||||||
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
"""test OpenID Provider flow (default authorization flow with implied consent)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
@ -169,6 +171,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||||||
self.assertEqual(body["IDTokenClaims"]["email"], USER().email)
|
self.assertEqual(body["IDTokenClaims"]["email"], USER().email)
|
||||||
self.assertEqual(body["UserInfo"]["email"], USER().email)
|
self.assertEqual(body["UserInfo"]["email"], USER().email)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_authorization_consent_explicit(self):
|
def test_authorization_consent_explicit(self):
|
||||||
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
"""test OpenID Provider flow (default authorization flow with explicit consent)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
@ -229,6 +232,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||||||
self.assertEqual(body["IDTokenClaims"]["email"], USER().email)
|
self.assertEqual(body["IDTokenClaims"]["email"], USER().email)
|
||||||
self.assertEqual(body["UserInfo"]["email"], USER().email)
|
self.assertEqual(body["UserInfo"]["email"], USER().email)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_authorization_denied(self):
|
def test_authorization_denied(self):
|
||||||
"""test OpenID Provider flow (default authorization with access deny)"""
|
"""test OpenID Provider flow (default authorization with access deny)"""
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
"""Proxy and Outpost e2e tests"""
|
"""Proxy and Outpost e2e tests"""
|
||||||
|
from dataclasses import asdict
|
||||||
from sys import platform
|
from sys import platform
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
@ -10,11 +11,16 @@ from docker.models.containers import Container
|
|||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.common.keys import Keys
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
from e2e.utils import USER, SeleniumTestCase
|
from e2e.utils import USER, SeleniumTestCase, retry
|
||||||
from passbook import __version__
|
from passbook import __version__
|
||||||
from passbook.core.models import Application
|
from passbook.core.models import Application
|
||||||
from passbook.flows.models import Flow
|
from passbook.flows.models import Flow
|
||||||
from passbook.outposts.models import Outpost, OutpostDeploymentType, OutpostType
|
from passbook.outposts.models import (
|
||||||
|
DockerServiceConnection,
|
||||||
|
Outpost,
|
||||||
|
OutpostConfig,
|
||||||
|
OutpostType,
|
||||||
|
)
|
||||||
from passbook.providers.proxy.models import ProxyProvider
|
from passbook.providers.proxy.models import ProxyProvider
|
||||||
|
|
||||||
|
|
||||||
@ -30,7 +36,7 @@ class TestProviderProxy(SeleniumTestCase):
|
|||||||
|
|
||||||
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
||||||
return {
|
return {
|
||||||
"image": "traefik/whoami:latest",
|
"image": "docker.beryju.org/proxy/traefik/whoami:latest",
|
||||||
"detach": True,
|
"detach": True,
|
||||||
"network_mode": "host",
|
"network_mode": "host",
|
||||||
"auto_remove": True,
|
"auto_remove": True,
|
||||||
@ -46,11 +52,12 @@ class TestProviderProxy(SeleniumTestCase):
|
|||||||
auto_remove=True,
|
auto_remove=True,
|
||||||
environment={
|
environment={
|
||||||
"PASSBOOK_HOST": self.live_server_url,
|
"PASSBOOK_HOST": self.live_server_url,
|
||||||
"PASSBOOK_TOKEN": outpost.token.token_uuid.hex,
|
"PASSBOOK_TOKEN": outpost.token.key,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return container
|
return container
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_proxy_simple(self):
|
def test_proxy_simple(self):
|
||||||
"""Test simple outpost setup with single provider"""
|
"""Test simple outpost setup with single provider"""
|
||||||
proxy: ProxyProvider = ProxyProvider.objects.create(
|
proxy: ProxyProvider = ProxyProvider.objects.create(
|
||||||
@ -69,7 +76,6 @@ class TestProviderProxy(SeleniumTestCase):
|
|||||||
outpost: Outpost = Outpost.objects.create(
|
outpost: Outpost = Outpost.objects.create(
|
||||||
name="proxy_outpost",
|
name="proxy_outpost",
|
||||||
type=OutpostType.PROXY,
|
type=OutpostType.PROXY,
|
||||||
deployment_type=OutpostDeploymentType.CUSTOM,
|
|
||||||
)
|
)
|
||||||
outpost.providers.add(proxy)
|
outpost.providers.add(proxy)
|
||||||
outpost.save()
|
outpost.save()
|
||||||
@ -79,7 +85,9 @@ class TestProviderProxy(SeleniumTestCase):
|
|||||||
# Wait until outpost healthcheck succeeds
|
# Wait until outpost healthcheck succeeds
|
||||||
healthcheck_retries = 0
|
healthcheck_retries = 0
|
||||||
while healthcheck_retries < 50:
|
while healthcheck_retries < 50:
|
||||||
if outpost.deployment_health:
|
if len(outpost.state) > 0:
|
||||||
|
state = outpost.state[0]
|
||||||
|
if state.last_seen:
|
||||||
break
|
break
|
||||||
healthcheck_retries += 1
|
healthcheck_retries += 1
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
@ -102,27 +110,7 @@ class TestProviderProxy(SeleniumTestCase):
|
|||||||
class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
||||||
"""Test Proxy connectivity over websockets"""
|
"""Test Proxy connectivity over websockets"""
|
||||||
|
|
||||||
proxy_container: Container
|
@retry()
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
|
||||||
self.proxy_container.kill()
|
|
||||||
super().tearDown()
|
|
||||||
|
|
||||||
def start_proxy(self, outpost: Outpost) -> Container:
|
|
||||||
"""Start proxy container based on outpost created"""
|
|
||||||
client: DockerClient = from_env()
|
|
||||||
container = client.containers.run(
|
|
||||||
image=f"beryju/passbook-proxy:{__version__}",
|
|
||||||
detach=True,
|
|
||||||
network_mode="host",
|
|
||||||
auto_remove=True,
|
|
||||||
environment={
|
|
||||||
"PASSBOOK_HOST": self.live_server_url,
|
|
||||||
"PASSBOOK_TOKEN": outpost.token.token_uuid.hex,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return container
|
|
||||||
|
|
||||||
def test_proxy_connectivity(self):
|
def test_proxy_connectivity(self):
|
||||||
"""Test proxy connectivity over websocket"""
|
"""Test proxy connectivity over websocket"""
|
||||||
SeleniumTestCase().apply_default_data()
|
SeleniumTestCase().apply_default_data()
|
||||||
@ -139,23 +127,31 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
|||||||
proxy.save()
|
proxy.save()
|
||||||
# we need to create an application to actually access the proxy
|
# we need to create an application to actually access the proxy
|
||||||
Application.objects.create(name="proxy", slug="proxy", provider=proxy)
|
Application.objects.create(name="proxy", slug="proxy", provider=proxy)
|
||||||
|
service_connection = DockerServiceConnection.objects.get(local=True)
|
||||||
outpost: Outpost = Outpost.objects.create(
|
outpost: Outpost = Outpost.objects.create(
|
||||||
name="proxy_outpost",
|
name="proxy_outpost",
|
||||||
type=OutpostType.PROXY,
|
type=OutpostType.PROXY,
|
||||||
deployment_type=OutpostDeploymentType.CUSTOM,
|
service_connection=service_connection,
|
||||||
|
_config=asdict(
|
||||||
|
OutpostConfig(passbook_host=self.live_server_url, log_level="debug")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
outpost.providers.add(proxy)
|
outpost.providers.add(proxy)
|
||||||
outpost.save()
|
outpost.save()
|
||||||
|
|
||||||
self.proxy_container = self.start_proxy(outpost)
|
|
||||||
|
|
||||||
# Wait until outpost healthcheck succeeds
|
# Wait until outpost healthcheck succeeds
|
||||||
healthcheck_retries = 0
|
healthcheck_retries = 0
|
||||||
while healthcheck_retries < 50:
|
while healthcheck_retries < 50:
|
||||||
if outpost.deployment_health:
|
if len(outpost.state) > 0:
|
||||||
|
state = outpost.state[0]
|
||||||
|
if state.last_seen and state.version:
|
||||||
break
|
break
|
||||||
healthcheck_retries += 1
|
healthcheck_retries += 1
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
|
|
||||||
self.assertIsNotNone(outpost.deployment_health)
|
state = outpost.state
|
||||||
self.assertEqual(outpost.deployment_version.get("version"), __version__)
|
self.assertTrue(len(state), 1)
|
||||||
|
self.assertEqual(state[0].version, __version__)
|
||||||
|
|
||||||
|
# Make sure to delete the outpost to remove the container
|
||||||
|
outpost.delete()
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from selenium.webdriver.common.keys import Keys
|
|||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from e2e.utils import USER, SeleniumTestCase
|
from e2e.utils import USER, SeleniumTestCase, retry
|
||||||
from passbook.core.models import Application
|
from passbook.core.models import Application
|
||||||
from passbook.crypto.models import CertificateKeyPair
|
from passbook.crypto.models import CertificateKeyPair
|
||||||
from passbook.flows.models import Flow
|
from passbook.flows.models import Flow
|
||||||
@ -38,7 +38,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
client: DockerClient = from_env()
|
client: DockerClient = from_env()
|
||||||
client.images.pull("beryju/oidc-test-client")
|
client.images.pull("beryju/oidc-test-client")
|
||||||
container = client.containers.run(
|
container = client.containers.run(
|
||||||
image="beryju/saml-test-sp",
|
image="docker.beryju.org/proxy/beryju/saml-test-sp",
|
||||||
detach=True,
|
detach=True,
|
||||||
network_mode="host",
|
network_mode="host",
|
||||||
auto_remove=True,
|
auto_remove=True,
|
||||||
@ -66,6 +66,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
LOGGER.info("Container failed healthcheck")
|
LOGGER.info("Container failed healthcheck")
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_sp_initiated_implicit(self):
|
def test_sp_initiated_implicit(self):
|
||||||
"""test SAML Provider flow SP-initiated flow (implicit consent)"""
|
"""test SAML Provider flow SP-initiated flow (implicit consent)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
@ -105,6 +106,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
self.assertEqual(body["attr"]["mail"], [USER().email])
|
self.assertEqual(body["attr"]["mail"], [USER().email])
|
||||||
self.assertEqual(body["attr"]["uid"], [str(USER().pk)])
|
self.assertEqual(body["attr"]["uid"], [str(USER().pk)])
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_sp_initiated_explicit(self):
|
def test_sp_initiated_explicit(self):
|
||||||
"""test SAML Provider flow SP-initiated flow (explicit consent)"""
|
"""test SAML Provider flow SP-initiated flow (explicit consent)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
@ -150,6 +152,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
self.assertEqual(body["attr"]["mail"], [USER().email])
|
self.assertEqual(body["attr"]["mail"], [USER().email])
|
||||||
self.assertEqual(body["attr"]["uid"], [str(USER().pk)])
|
self.assertEqual(body["attr"]["uid"], [str(USER().pk)])
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_idp_initiated_implicit(self):
|
def test_idp_initiated_implicit(self):
|
||||||
"""test SAML Provider flow IdP-initiated flow (implicit consent)"""
|
"""test SAML Provider flow IdP-initiated flow (implicit consent)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
@ -195,6 +198,7 @@ class TestProviderSAML(SeleniumTestCase):
|
|||||||
self.assertEqual(body["attr"]["mail"], [USER().email])
|
self.assertEqual(body["attr"]["mail"], [USER().email])
|
||||||
self.assertEqual(body["attr"]["uid"], [str(USER().pk)])
|
self.assertEqual(body["attr"]["uid"], [str(USER().pk)])
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_sp_initiated_denied(self):
|
def test_sp_initiated_denied(self):
|
||||||
"""test SAML Provider flow SP-initiated flow (Policy denies access)"""
|
"""test SAML Provider flow SP-initiated flow (Policy denies access)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
|||||||
@ -14,7 +14,7 @@ from selenium.webdriver.support import expected_conditions as ec
|
|||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
from yaml import safe_dump
|
from yaml import safe_dump
|
||||||
|
|
||||||
from e2e.utils import SeleniumTestCase
|
from e2e.utils import SeleniumTestCase, retry
|
||||||
from passbook.flows.models import Flow
|
from passbook.flows.models import Flow
|
||||||
from passbook.providers.oauth2.generators import (
|
from passbook.providers.oauth2.generators import (
|
||||||
generate_client_id,
|
generate_client_id,
|
||||||
@ -106,6 +106,7 @@ class TestSourceOAuth2(SeleniumTestCase):
|
|||||||
consumer_secret=self.client_secret,
|
consumer_secret=self.client_secret,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_oauth_enroll(self):
|
def test_oauth_enroll(self):
|
||||||
"""test OAuth Source With With OIDC"""
|
"""test OAuth Source With With OIDC"""
|
||||||
self.create_objects()
|
self.create_objects()
|
||||||
@ -159,6 +160,7 @@ class TestSourceOAuth2(SeleniumTestCase):
|
|||||||
"admin@example.com",
|
"admin@example.com",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
@override_settings(SESSION_COOKIE_SAMESITE="strict")
|
@override_settings(SESSION_COOKIE_SAMESITE="strict")
|
||||||
def test_oauth_samesite_strict(self):
|
def test_oauth_samesite_strict(self):
|
||||||
"""test OAuth Source With SameSite set to strict
|
"""test OAuth Source With SameSite set to strict
|
||||||
@ -195,6 +197,7 @@ class TestSourceOAuth2(SeleniumTestCase):
|
|||||||
"Authentication Failed.",
|
"Authentication Failed.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_oauth_enroll_auth(self):
|
def test_oauth_enroll_auth(self):
|
||||||
"""test OAuth Source With With OIDC (enroll and authenticate again)"""
|
"""test OAuth Source With With OIDC (enroll and authenticate again)"""
|
||||||
self.test_oauth_enroll()
|
self.test_oauth_enroll()
|
||||||
@ -255,7 +258,7 @@ class TestSourceOAuth1(SeleniumTestCase):
|
|||||||
|
|
||||||
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
||||||
return {
|
return {
|
||||||
"image": "beryju/oauth1-test-server",
|
"image": "docker.beryju.org/proxy/beryju/oauth1-test-server",
|
||||||
"detach": True,
|
"detach": True,
|
||||||
"network_mode": "host",
|
"network_mode": "host",
|
||||||
"auto_remove": True,
|
"auto_remove": True,
|
||||||
@ -291,6 +294,7 @@ class TestSourceOAuth1(SeleniumTestCase):
|
|||||||
consumer_secret=self.client_secret,
|
consumer_secret=self.client_secret,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_oauth_enroll(self):
|
def test_oauth_enroll(self):
|
||||||
"""test OAuth Source With With OIDC"""
|
"""test OAuth Source With With OIDC"""
|
||||||
self.create_objects()
|
self.create_objects()
|
||||||
@ -317,6 +321,7 @@ class TestSourceOAuth1(SeleniumTestCase):
|
|||||||
self.driver.find_element(By.CSS_SELECTOR, "[name='confirm']").click()
|
self.driver.find_element(By.CSS_SELECTOR, "[name='confirm']").click()
|
||||||
|
|
||||||
# Wait until we've loaded the user info page
|
# Wait until we've loaded the user info page
|
||||||
|
sleep(2)
|
||||||
self.wait.until(ec.presence_of_element_located((By.ID, "user-settings")))
|
self.wait.until(ec.presence_of_element_located((By.ID, "user-settings")))
|
||||||
self.driver.get(self.url("passbook_core:user-settings"))
|
self.driver.get(self.url("passbook_core:user-settings"))
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from selenium.webdriver.common.keys import Keys
|
|||||||
from selenium.webdriver.support import expected_conditions as ec
|
from selenium.webdriver.support import expected_conditions as ec
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from e2e.utils import SeleniumTestCase
|
from e2e.utils import SeleniumTestCase, retry
|
||||||
from passbook.crypto.models import CertificateKeyPair
|
from passbook.crypto.models import CertificateKeyPair
|
||||||
from passbook.flows.models import Flow
|
from passbook.flows.models import Flow
|
||||||
from passbook.sources.saml.models import SAMLBindingTypes, SAMLSource
|
from passbook.sources.saml.models import SAMLBindingTypes, SAMLSource
|
||||||
@ -75,7 +75,7 @@ class TestSourceSAML(SeleniumTestCase):
|
|||||||
|
|
||||||
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
||||||
return {
|
return {
|
||||||
"image": "kristophjunge/test-saml-idp:1.15",
|
"image": "docker.beryju.org/proxy/kristophjunge/test-saml-idp:1.15",
|
||||||
"detach": True,
|
"detach": True,
|
||||||
"network_mode": "host",
|
"network_mode": "host",
|
||||||
"auto_remove": True,
|
"auto_remove": True,
|
||||||
@ -92,6 +92,7 @@ class TestSourceSAML(SeleniumTestCase):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_idp_redirect(self):
|
def test_idp_redirect(self):
|
||||||
"""test SAML Source With redirect binding"""
|
"""test SAML Source With redirect binding"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
@ -141,6 +142,7 @@ class TestSourceSAML(SeleniumTestCase):
|
|||||||
self.driver.find_element(By.ID, "id_username").get_attribute("value"), ""
|
self.driver.find_element(By.ID, "id_username").get_attribute("value"), ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_idp_post(self):
|
def test_idp_post(self):
|
||||||
"""test SAML Source With post binding"""
|
"""test SAML Source With post binding"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
@ -192,6 +194,7 @@ class TestSourceSAML(SeleniumTestCase):
|
|||||||
self.driver.find_element(By.ID, "id_username").get_attribute("value"), ""
|
self.driver.find_element(By.ID, "id_username").get_attribute("value"), ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@retry()
|
||||||
def test_idp_post_auto(self):
|
def test_idp_post_auto(self):
|
||||||
"""test SAML Source With post binding (auto redirect)"""
|
"""test SAML Source With post binding (auto redirect)"""
|
||||||
# Bootstrap all needed objects
|
# Bootstrap all needed objects
|
||||||
|
|||||||
43
e2e/utils.py
43
e2e/utils.py
@ -1,19 +1,22 @@
|
|||||||
"""passbook e2e testing utilities"""
|
"""passbook e2e testing utilities"""
|
||||||
|
from functools import wraps
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from importlib.util import module_from_spec, spec_from_file_location
|
from importlib.util import module_from_spec, spec_from_file_location
|
||||||
from inspect import getmembers, isfunction
|
from inspect import getmembers, isfunction
|
||||||
from os import environ, makedirs
|
from os import environ, makedirs
|
||||||
from time import sleep, time
|
from time import sleep, time
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Callable, Dict, Optional
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
||||||
from django.db import connection, transaction
|
from django.db import connection, transaction
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
from django.shortcuts import reverse
|
from django.shortcuts import reverse
|
||||||
|
from django.test.testcases import TransactionTestCase
|
||||||
from docker import DockerClient, from_env
|
from docker import DockerClient, from_env
|
||||||
from docker.models.containers import Container
|
from docker.models.containers import Container
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
|
from selenium.common.exceptions import NoSuchElementException, TimeoutException
|
||||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||||
from selenium.webdriver.remote.webdriver import WebDriver
|
from selenium.webdriver.remote.webdriver import WebDriver
|
||||||
from selenium.webdriver.support.ui import WebDriverWait
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
@ -123,3 +126,41 @@ class SeleniumTestCase(StaticLiveServerTestCase):
|
|||||||
func(apps, schema_editor)
|
func(apps, schema_editor)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def retry(max_retires=3, exceptions=None):
|
||||||
|
"""Retry test multiple times. Default to catching Selenium Timeout Exception"""
|
||||||
|
|
||||||
|
if not exceptions:
|
||||||
|
exceptions = [TimeoutException, NoSuchElementException]
|
||||||
|
|
||||||
|
logger = get_logger()
|
||||||
|
|
||||||
|
def retry_actual(func: Callable):
|
||||||
|
"""Retry test multiple times"""
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(self: TransactionTestCase, *args, **kwargs):
|
||||||
|
"""Run test again if we're below max_retries, including tearDown and
|
||||||
|
setUp. Otherwise raise the error"""
|
||||||
|
nonlocal count
|
||||||
|
try:
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
# pylint: disable=catching-non-exception
|
||||||
|
except tuple(exceptions) as exc:
|
||||||
|
count += 1
|
||||||
|
if count > max_retires:
|
||||||
|
logger.debug("Exceeded retry count", exc=exc, test=self)
|
||||||
|
# pylint: disable=raising-non-exception
|
||||||
|
raise exc
|
||||||
|
logger.debug("Retrying on error", exc=exc, test=self)
|
||||||
|
self.tearDown()
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
self._post_teardown()
|
||||||
|
self.setUp()
|
||||||
|
return wrapper(self, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return retry_actual
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
appVersion: "0.11.0-stable"
|
description: passbook is an open-source Identity Provider focused on flexibility and versatility. You can use passbook in an existing environment to add support for new protocols. passbook is also a great solution for implementing signup/recovery/etc in your application, so you don't have to deal with it.
|
||||||
description: A Helm chart for passbook.
|
|
||||||
name: passbook
|
name: passbook
|
||||||
version: "0.11.0-stable"
|
home: https://passbook.beryju.org
|
||||||
icon: https://github.com/BeryJu/passbook/blob/master/docs/images/logo.svg
|
sources:
|
||||||
|
- https://github.com/BeryJu/passbook
|
||||||
|
version: "0.12.9-stable"
|
||||||
|
icon: https://raw.githubusercontent.com/BeryJu/passbook/master/docs/images/logo.svg
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: postgresql
|
- name: postgresql
|
||||||
version: 9.4.1
|
version: 9.4.1
|
||||||
|
|||||||
28
helm/README.md
Normal file
28
helm/README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# passbook Helm Chart
|
||||||
|
|
||||||
|
| Name | Default | Description |
|
||||||
|
|-----------------------------------|-------------------------|-------------|
|
||||||
|
| image.name | beryju/passbook | Image used to run the passbook server and worker |
|
||||||
|
| image.name_static | beryju/passbook-static | Image used to run the passbook static server (CSS and JS Files) |
|
||||||
|
| image.tag | 0.12.5-stable | Image tag |
|
||||||
|
| serverReplicas | 1 | Replicas for the Server deployment |
|
||||||
|
| workerReplicas | 1 | Replicas for the Worker deployment |
|
||||||
|
| kubernetesIntegration | true | Enable/disable the Kubernetes integration for passbook. This will create a service account for passbook to create and update outposts in passbook |
|
||||||
|
| config.secretKey | | Secret key used to sign session cookies, generate with `pwgen 50 1` for example. |
|
||||||
|
| config.errorReporting.enabled | false | Enable/disable error reporting |
|
||||||
|
| config.errorReporting.environment | customer | Environment sent with the error reporting |
|
||||||
|
| config.errorReporting.sendPii | false | Whether to send Personally-identifiable data with the error reporting |
|
||||||
|
| config.logLevel | warning | Log level of passbook |
|
||||||
|
| backup.accessKey | | Optionally enable S3 Backup, Access Key |
|
||||||
|
| backup.secretKey | | Optionally enable S3 Backup, Secret Key |
|
||||||
|
| backup.bucket | | Optionally enable S3 Backup, Bucket |
|
||||||
|
| backup.region | | Optionally enable S3 Backup, Region |
|
||||||
|
| backup.host | | Optionally enable S3 Backup, to custom Endpoint like minio |
|
||||||
|
| ingress.annotations | {} | Annotations for the ingress object |
|
||||||
|
| ingress.hosts | [passbook.k8s.local] | Hosts which the ingress will match |
|
||||||
|
| ingress.tls | [] | TLS Configuration, same as Ingress objects |
|
||||||
|
| install.postgresql | true | Enables/disables the packaged PostgreSQL Chart
|
||||||
|
| install.redis | true | Enables/disables the packaged Redis Chart
|
||||||
|
| postgresql.postgresqlPassword | | Password used for PostgreSQL, generated automatically.
|
||||||
|
|
||||||
|
For more info, see https://passbook.beryju.org/ and https://passbook.beryju.org/installation/kubernetes/
|
||||||
@ -3,7 +3,7 @@
|
|||||||
Expand the name of the chart.
|
Expand the name of the chart.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "passbook.name" -}}
|
{{- define "passbook.name" -}}
|
||||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
{{- default .Chart.Name | trunc 63 | trimSuffix "-" -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
@ -12,17 +12,13 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this
|
|||||||
If release name contains chart name it will be used as a full name.
|
If release name contains chart name it will be used as a full name.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "passbook.fullname" -}}
|
{{- define "passbook.fullname" -}}
|
||||||
{{- if .Values.fullnameOverride -}}
|
{{- $name := default .Chart.Name -}}
|
||||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
|
||||||
{{- else -}}
|
|
||||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
|
||||||
{{- if contains $name .Release.Name -}}
|
{{- if contains $name .Release.Name -}}
|
||||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Create chart name and version as used by the chart label.
|
Create chart name and version as used by the chart label.
|
||||||
|
|||||||
@ -7,14 +7,14 @@ data:
|
|||||||
POSTGRESQL__NAME: "{{ .Values.postgresql.postgresqlDatabase }}"
|
POSTGRESQL__NAME: "{{ .Values.postgresql.postgresqlDatabase }}"
|
||||||
POSTGRESQL__USER: "{{ .Values.postgresql.postgresqlUsername }}"
|
POSTGRESQL__USER: "{{ .Values.postgresql.postgresqlUsername }}"
|
||||||
{{- if .Values.backup }}
|
{{- if .Values.backup }}
|
||||||
POSTGRESQL__S3_BACKUP__ACCESS_KEY: "{{ .Values.backup.access_key }}"
|
POSTGRESQL__S3_BACKUP__ACCESS_KEY: "{{ .Values.backup.accessKey }}"
|
||||||
POSTGRESQL__S3_BACKUP__SECRET_KEY: "{{ .Values.backup.secret_key }}"
|
POSTGRESQL__S3_BACKUP__SECRET_KEY: "{{ .Values.backup.secretKey }}"
|
||||||
POSTGRESQL__S3_BACKUP__BUCKET: "{{ .Values.backup.bucket }}"
|
POSTGRESQL__S3_BACKUP__BUCKET: "{{ .Values.backup.bucket }}"
|
||||||
POSTGRESQL__S3_BACKUP__REGION: "{{ .Values.backup.region }}"
|
POSTGRESQL__S3_BACKUP__REGION: "{{ .Values.backup.region }}"
|
||||||
POSTGRESQL__S3_BACKUP__HOST: "{{ .Values.backup.host }}"
|
POSTGRESQL__S3_BACKUP__HOST: "{{ .Values.backup.host }}"
|
||||||
{{- end}}
|
{{- end}}
|
||||||
REDIS__HOST: "{{ .Release.Name }}-redis-master"
|
REDIS__HOST: "{{ .Release.Name }}-redis-master"
|
||||||
ERROR_REPORTING__ENABLED: "{{ .Values.config.error_reporting.enabled }}"
|
ERROR_REPORTING__ENABLED: "{{ .Values.config.errorReporting.enabled }}"
|
||||||
ERROR_REPORTING__ENVIRONMENT: "{{ .Values.config.error_reporting.environment }}"
|
ERROR_REPORTING__ENVIRONMENT: "{{ .Values.config.errorReporting.environment }}"
|
||||||
ERROR_REPORTING__SEND_PII: "{{ .Values.config.error_reporting.send_pii }}"
|
ERROR_REPORTING__SEND_PII: "{{ .Values.config.errorReporting.sendPii }}"
|
||||||
LOG_LEVEL: "{{ .Values.config.log_level }}"
|
LOG_LEVEL: "{{ .Values.config.logLevel }}"
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
{{- if .Values.backup }}
|
|
||||||
apiVersion: batch/v1beta1
|
|
||||||
kind: CronJob
|
|
||||||
metadata:
|
|
||||||
name: {{ include "passbook.fullname" . }}-backup
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: {{ include "passbook.name" . }}
|
|
||||||
helm.sh/chart: {{ include "passbook.chart" . }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
||||||
spec:
|
|
||||||
schedule: "0 0 * * *"
|
|
||||||
jobTemplate:
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
restartPolicy: Never
|
|
||||||
containers:
|
|
||||||
- name: {{ .Chart.Name }}
|
|
||||||
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
|
|
||||||
args: [server]
|
|
||||||
envFrom:
|
|
||||||
- configMapRef:
|
|
||||||
name: {{ include "passbook.fullname" . }}-config
|
|
||||||
prefix: PASSBOOK_
|
|
||||||
env:
|
|
||||||
- name: PASSBOOK_SECRET_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: "{{ include "passbook.fullname" . }}-secret-key"
|
|
||||||
key: "secret_key"
|
|
||||||
- name: PASSBOOK_REDIS__PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: "{{ .Release.Name }}-redis"
|
|
||||||
key: "redis-password"
|
|
||||||
- name: PASSBOOK_POSTGRESQL__PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: "{{ .Release.Name }}-postgresql"
|
|
||||||
key: "postgresql-password"
|
|
||||||
{{- end}}
|
|
||||||
@ -5,8 +5,8 @@ metadata:
|
|||||||
name: {{ include "passbook.fullname" . }}-secret-key
|
name: {{ include "passbook.fullname" . }}-secret-key
|
||||||
data:
|
data:
|
||||||
monitoring_username: bW9uaXRvcg== # monitor in base64
|
monitoring_username: bW9uaXRvcg== # monitor in base64
|
||||||
{{- if .Values.config.secret_key }}
|
{{- if .Values.config.secretKey }}
|
||||||
secret_key: {{ .Values.config.secret_key | b64enc | quote }}
|
secret_key: {{ .Values.config.secretKey | b64enc | quote }}
|
||||||
{{- else }}
|
{{- else }}
|
||||||
secret_key: {{ randAlphaNum 50 | b64enc | quote}}
|
secret_key: {{ randAlphaNum 50 | b64enc | quote}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
64
helm/templates/service-account.yaml
Normal file
64
helm/templates/service-account.yaml
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{{- if .Values.kubernetesIntegration }}
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: {{ include "passbook.fullname" . }}-sa-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
- services
|
||||||
|
verbs:
|
||||||
|
- "get"
|
||||||
|
- "create"
|
||||||
|
- "delete"
|
||||||
|
- "read"
|
||||||
|
- "patch"
|
||||||
|
- apiGroups:
|
||||||
|
- "extensions"
|
||||||
|
- "apps"
|
||||||
|
resources:
|
||||||
|
- "deployments"
|
||||||
|
verbs:
|
||||||
|
- "get"
|
||||||
|
- "create"
|
||||||
|
- "delete"
|
||||||
|
- "read"
|
||||||
|
- "patch"
|
||||||
|
- apiGroups:
|
||||||
|
- "extensions"
|
||||||
|
- "networking.k8s.io"
|
||||||
|
resources:
|
||||||
|
- "ingresses"
|
||||||
|
verbs:
|
||||||
|
- "get"
|
||||||
|
- "create"
|
||||||
|
- "delete"
|
||||||
|
- "read"
|
||||||
|
- "patch"
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- namespaces
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "passbook.fullname" . }}-sa
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: {{ include "passbook.fullname" . }}-sa-role-binding
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: {{ include "passbook.fullname" . }}-sa-role
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: {{ include "passbook.fullname" . }}-sa
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
{{- end }}
|
||||||
@ -100,14 +100,14 @@ spec:
|
|||||||
port: http
|
port: http
|
||||||
httpHeaders:
|
httpHeaders:
|
||||||
- name: Host
|
- name: Host
|
||||||
value: kubernetes-healthcheck-host
|
value: passbook-healthcheck-host
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /
|
||||||
port: http
|
port: http
|
||||||
httpHeaders:
|
httpHeaders:
|
||||||
- name: Host
|
- name: Host
|
||||||
value: kubernetes-healthcheck-host
|
value: passbook-healthcheck-host
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
|
|||||||
@ -22,6 +22,9 @@ spec:
|
|||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
k8s.passbook.beryju.org/component: worker
|
k8s.passbook.beryju.org/component: worker
|
||||||
spec:
|
spec:
|
||||||
|
{{- if .Values.kubernetesIntegration }}
|
||||||
|
serviceAccountName: {{ include "passbook.fullname" . }}-sa
|
||||||
|
{{- end }}
|
||||||
affinity:
|
affinity:
|
||||||
podAntiAffinity:
|
podAntiAffinity:
|
||||||
preferredDuringSchedulingIgnoredDuringExecution:
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
|||||||
21
helm/values.test.yaml
Normal file
21
helm/values.test.yaml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
image:
|
||||||
|
tag: gh-master
|
||||||
|
|
||||||
|
serverReplicas: 1
|
||||||
|
workerReplicas: 1
|
||||||
|
|
||||||
|
config:
|
||||||
|
# Log level used by web and worker
|
||||||
|
# Can be either debug, info, warning, error
|
||||||
|
logLevel: debug
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
hosts:
|
||||||
|
- passbook.127.0.0.1.nip.io
|
||||||
|
|
||||||
|
# These values influence the bundled postgresql and redis charts, but are also used by passbook to connect
|
||||||
|
postgresql:
|
||||||
|
postgresqlPassword: EK-5jnKfjrGRm<77
|
||||||
|
|
||||||
|
redis:
|
||||||
|
password: password
|
||||||
@ -4,29 +4,30 @@
|
|||||||
image:
|
image:
|
||||||
name: beryju/passbook
|
name: beryju/passbook
|
||||||
name_static: beryju/passbook-static
|
name_static: beryju/passbook-static
|
||||||
tag: 0.11.0-stable
|
tag: 0.12.9-stable
|
||||||
|
|
||||||
nameOverride: ""
|
|
||||||
|
|
||||||
serverReplicas: 1
|
serverReplicas: 1
|
||||||
workerReplicas: 1
|
workerReplicas: 1
|
||||||
|
|
||||||
|
# Enable the Kubernetes integration which lets passbook deploy outposts into kubernetes
|
||||||
|
kubernetesIntegration: true
|
||||||
|
|
||||||
config:
|
config:
|
||||||
# Optionally specify fixed secret_key, otherwise generated automatically
|
# Optionally specify fixed secret_key, otherwise generated automatically
|
||||||
# secret_key: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
|
# secretKey: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
|
||||||
# Enable error reporting
|
# Enable error reporting
|
||||||
error_reporting:
|
errorReporting:
|
||||||
enabled: false
|
enabled: false
|
||||||
environment: customer
|
environment: customer
|
||||||
send_pii: false
|
sendPii: false
|
||||||
# Log level used by web and worker
|
# Log level used by web and worker
|
||||||
# Can be either debug, info, warning, error
|
# Can be either debug, info, warning, error
|
||||||
log_level: warning
|
logLevel: warning
|
||||||
|
|
||||||
# Enable Database Backups to S3
|
# Enable Database Backups to S3
|
||||||
# backup:
|
# backup:
|
||||||
# access_key: access-key
|
# accessKey: access-key
|
||||||
# secret_key: secret-key
|
# secretKey: secret-key
|
||||||
# bucket: s3-bucket
|
# bucket: s3-bucket
|
||||||
# region: eu-central-1
|
# region: eu-central-1
|
||||||
# host: s3-host
|
# host: s3-host
|
||||||
@ -35,7 +36,6 @@ ingress:
|
|||||||
annotations: {}
|
annotations: {}
|
||||||
# kubernetes.io/ingress.class: nginx
|
# kubernetes.io/ingress.class: nginx
|
||||||
# kubernetes.io/tls-acme: "true"
|
# kubernetes.io/tls-acme: "true"
|
||||||
path: /
|
|
||||||
hosts:
|
hosts:
|
||||||
- passbook.k8s.local
|
- passbook.k8s.local
|
||||||
tls: []
|
tls: []
|
||||||
@ -59,7 +59,5 @@ redis:
|
|||||||
cluster:
|
cluster:
|
||||||
enabled: false
|
enabled: false
|
||||||
master:
|
master:
|
||||||
persistence:
|
|
||||||
enabled: false
|
|
||||||
# https://stackoverflow.com/a/59189742
|
# https://stackoverflow.com/a/59189742
|
||||||
disableCommands: []
|
disableCommands: []
|
||||||
|
|||||||
@ -4,7 +4,7 @@ printf '{"event": "Bootstrap completed", "level": "info", "logger": "bootstrap",
|
|||||||
if [[ "$1" == "server" ]]; then
|
if [[ "$1" == "server" ]]; then
|
||||||
gunicorn -c /lifecycle/gunicorn.conf.py passbook.root.asgi:application
|
gunicorn -c /lifecycle/gunicorn.conf.py passbook.root.asgi:application
|
||||||
elif [[ "$1" == "worker" ]]; then
|
elif [[ "$1" == "worker" ]]; then
|
||||||
celery -A passbook.root.celery worker --autoscale 10,3 -E -B -s /tmp/celerybeat-schedule -Q passbook,passbook_scheduled
|
celery -A passbook.root.celery worker --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q passbook,passbook_scheduled
|
||||||
elif [[ "$1" == "migrate" ]]; then
|
elif [[ "$1" == "migrate" ]]; then
|
||||||
# Run system migrations first, run normal migrations after
|
# Run system migrations first, run normal migrations after
|
||||||
python -m lifecycle.migrate
|
python -m lifecycle.migrate
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
"""Gunicorn config"""
|
"""Gunicorn config"""
|
||||||
|
import warnings
|
||||||
from multiprocessing import cpu_count
|
from multiprocessing import cpu_count
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -49,3 +50,5 @@ if Path("/var/run/secrets/kubernetes.io").exists():
|
|||||||
else:
|
else:
|
||||||
worker = cpu_count() * 2 + 1
|
worker = cpu_count() * 2 + 1
|
||||||
threads = 4
|
threads = 4
|
||||||
|
|
||||||
|
warnings.simplefilter("once")
|
||||||
|
|||||||
@ -47,7 +47,9 @@ if __name__ == "__main__":
|
|||||||
# pyright: reportGeneralTypeIssues=false
|
# pyright: reportGeneralTypeIssues=false
|
||||||
spec.loader.exec_module(mod)
|
spec.loader.exec_module(mod)
|
||||||
|
|
||||||
for _, sub in getmembers(mod, isclass):
|
for name, sub in getmembers(mod, isclass):
|
||||||
|
if name != "Migration":
|
||||||
|
continue
|
||||||
migration = sub(curr, conn)
|
migration = sub(curr, conn)
|
||||||
if migration.needs_migration():
|
if migration.needs_migration():
|
||||||
LOGGER.info("Migration needs to be applied", migration=sub)
|
LOGGER.info("Migration needs to be applied", migration=sub)
|
||||||
|
|||||||
@ -25,7 +25,7 @@ delete from django_migrations where app = 'passbook_stages_password' and
|
|||||||
name = '0002_passwordstage_change_flow';"""
|
name = '0002_passwordstage_change_flow';"""
|
||||||
|
|
||||||
|
|
||||||
class To010Migration(BaseMigration):
|
class Migration(BaseMigration):
|
||||||
def needs_migration(self) -> bool:
|
def needs_migration(self) -> bool:
|
||||||
self.cur.execute(
|
self.cur.execute(
|
||||||
"select * from information_schema.tables where table_name='oidc_provider_client'"
|
"select * from information_schema.tables where table_name='oidc_provider_client'"
|
||||||
|
|||||||
@ -47,6 +47,8 @@ nav:
|
|||||||
- Overview: policies/index.md
|
- Overview: policies/index.md
|
||||||
- Expression: policies/expression.md
|
- Expression: policies/expression.md
|
||||||
- Integrations:
|
- Integrations:
|
||||||
|
- as Source:
|
||||||
|
- Active Directory: integrations/sources/active-directory/index.md
|
||||||
- as Provider:
|
- as Provider:
|
||||||
- Amazon Web Services: integrations/services/aws/index.md
|
- Amazon Web Services: integrations/services/aws/index.md
|
||||||
- GitLab: integrations/services/gitlab/index.md
|
- GitLab: integrations/services/gitlab/index.md
|
||||||
@ -63,6 +65,8 @@ nav:
|
|||||||
- Upgrading:
|
- Upgrading:
|
||||||
- to 0.9: upgrading/to-0.9.md
|
- to 0.9: upgrading/to-0.9.md
|
||||||
- to 0.10: upgrading/to-0.10.md
|
- to 0.10: upgrading/to-0.10.md
|
||||||
|
- to 0.11: upgrading/to-0.11.md
|
||||||
|
- to 0.12: upgrading/to-0.12.md
|
||||||
- Troubleshooting:
|
- Troubleshooting:
|
||||||
- Access problems: troubleshooting/access.md
|
- Access problems: troubleshooting/access.md
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
"""passbook"""
|
"""passbook"""
|
||||||
__version__ = "0.11.0-stable"
|
__version__ = "0.12.9-stable"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"""passbook administration overview"""
|
"""passbook administration overview"""
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.http import response
|
from django.http import response
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg2.utils import swagger_auto_schema
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import SerializerMethodField
|
||||||
from rest_framework.permissions import IsAdminUser
|
from rest_framework.permissions import IsAdminUser
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from django.db.models.fields import DurationField
|
|||||||
from django.db.models.functions import ExtractHour
|
from django.db.models.functions import ExtractHour
|
||||||
from django.http import response
|
from django.http import response
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg2.utils import swagger_auto_schema
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import SerializerMethodField
|
||||||
from rest_framework.permissions import IsAdminUser
|
from rest_framework.permissions import IsAdminUser
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|||||||
72
passbook/admin/api/tasks.py
Normal file
72
passbook/admin/api/tasks.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
"""Tasks API"""
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.http.response import Http404
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from drf_yasg2.utils import swagger_auto_schema
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.fields import CharField, DateTimeField, IntegerField, ListField
|
||||||
|
from rest_framework.permissions import IsAdminUser
|
||||||
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.serializers import Serializer
|
||||||
|
from rest_framework.viewsets import ViewSet
|
||||||
|
|
||||||
|
from passbook.lib.tasks import TaskInfo
|
||||||
|
|
||||||
|
|
||||||
|
class TaskSerializer(Serializer):
|
||||||
|
"""Serialize TaskInfo and TaskResult"""
|
||||||
|
|
||||||
|
task_name = CharField()
|
||||||
|
task_description = CharField()
|
||||||
|
task_finish_timestamp = DateTimeField(source="finish_timestamp")
|
||||||
|
|
||||||
|
status = IntegerField(source="result.status.value")
|
||||||
|
messages = ListField(source="result.messages")
|
||||||
|
|
||||||
|
def create(self, request: Request) -> Response:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def update(self, request: Request) -> Response:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class TaskViewSet(ViewSet):
|
||||||
|
"""Read-only view set that returns all background tasks"""
|
||||||
|
|
||||||
|
permission_classes = [IsAdminUser]
|
||||||
|
|
||||||
|
@swagger_auto_schema(responses={200: TaskSerializer(many=True)})
|
||||||
|
def list(self, request: Request) -> Response:
|
||||||
|
"""List current messages and pass into Serializer"""
|
||||||
|
return Response(TaskSerializer(TaskInfo.all().values(), many=True).data)
|
||||||
|
|
||||||
|
@action(detail=True, methods=["post"])
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
def retry(self, request: Request, pk=None) -> Response:
|
||||||
|
"""Retry task"""
|
||||||
|
task = TaskInfo.by_name(pk)
|
||||||
|
if not task:
|
||||||
|
raise Http404
|
||||||
|
try:
|
||||||
|
task_module = import_module(task.task_call_module)
|
||||||
|
task_func = getattr(task_module, task.task_call_func)
|
||||||
|
task_func.delay(*task.task_call_args, **task.task_call_kwargs)
|
||||||
|
messages.success(
|
||||||
|
self.request,
|
||||||
|
_(
|
||||||
|
"Successfully re-scheduled Task %(name)s!"
|
||||||
|
% {"name": task.task_name}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"successful": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except ImportError:
|
||||||
|
# if we get an import error, the module path has probably changed
|
||||||
|
task.delete()
|
||||||
|
return Response({"successful": False})
|
||||||
@ -3,6 +3,7 @@ from django.core.cache import cache
|
|||||||
from requests import RequestException, get
|
from requests import RequestException, get
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
|
from passbook.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
|
||||||
from passbook.root.celery import CELERY_APP
|
from passbook.root.celery import CELERY_APP
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
@ -10,8 +11,8 @@ VERSION_CACHE_KEY = "passbook_latest_version"
|
|||||||
VERSION_CACHE_TIMEOUT = 2 * 60 * 60 # 2 hours
|
VERSION_CACHE_TIMEOUT = 2 * 60 * 60 # 2 hours
|
||||||
|
|
||||||
|
|
||||||
@CELERY_APP.task()
|
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||||
def update_latest_version():
|
def update_latest_version(self: MonitoredTask):
|
||||||
"""Update latest version info"""
|
"""Update latest version info"""
|
||||||
try:
|
try:
|
||||||
data = get(
|
data = get(
|
||||||
@ -19,5 +20,11 @@ def update_latest_version():
|
|||||||
).json()
|
).json()
|
||||||
tag_name = data.get("tag_name")
|
tag_name = data.get("tag_name")
|
||||||
cache.set(VERSION_CACHE_KEY, tag_name.split("/")[1], VERSION_CACHE_TIMEOUT)
|
cache.set(VERSION_CACHE_KEY, tag_name.split("/")[1], VERSION_CACHE_TIMEOUT)
|
||||||
except (RequestException, IndexError):
|
self.set_status(
|
||||||
|
TaskResult(
|
||||||
|
TaskResultStatus.SUCCESSFUL, ["Successfully updated latest Version"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except (RequestException, IndexError) as exc:
|
||||||
cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT)
|
cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT)
|
||||||
|
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||||
|
|||||||
@ -46,12 +46,29 @@
|
|||||||
{% trans 'Providers' %}
|
{% trans 'Providers' %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="pf-c-nav__item pf-m-expanded">
|
||||||
|
<a href="#" class="pf-c-nav__link" aria-expanded="true">{% trans 'Outposts' %}
|
||||||
|
<span class="pf-c-nav__toggle">
|
||||||
|
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<section class="pf-c-nav__subnav">
|
||||||
|
<ul class="pf-c-nav__simple-list">
|
||||||
<li class="pf-c-nav__item">
|
<li class="pf-c-nav__item">
|
||||||
<a href="{% url 'passbook_admin:outposts' %}"
|
<a href="{% url 'passbook_admin:outposts' %}"
|
||||||
class="pf-c-nav__link {% is_active 'passbook_admin:outposts' 'passbook_admin:outpost-create' 'passbook_admin:outpost-update' 'passbook_admin:outpost-delete' %}">
|
class="pf-c-nav__link {% is_active 'passbook_admin:outposts' 'passbook_admin:outpost-create' 'passbook_admin:outpost-update' 'passbook_admin:outpost-delete' %}">
|
||||||
{% trans 'Outposts' %}
|
{% trans 'Outposts' %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="pf-c-nav__item">
|
||||||
|
<a href="{% url 'passbook_admin:outpost-service-connections' %}"
|
||||||
|
class="pf-c-nav__link {% is_active 'passbook_admin:outpost-service-connections' 'passbook_admin:outpost-service-connections-create' 'passbook_admin:outpost-service-connections-update' 'passbook_admin:outpost-service-connections-delete' %}">
|
||||||
|
{% trans 'Service Connections' %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</li>
|
||||||
<li class="pf-c-nav__item">
|
<li class="pf-c-nav__item">
|
||||||
<a href="{% url 'passbook_admin:property-mappings' %}"
|
<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' %}">
|
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' %}">
|
||||||
@ -146,6 +163,12 @@
|
|||||||
{% trans 'Groups' %}
|
{% trans 'Groups' %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="pf-c-nav__item">
|
||||||
|
<a href="{% url 'passbook_admin:tasks' %}"
|
||||||
|
class="pf-c-nav__link {% is_active 'passbook_admin:tasks' %}">
|
||||||
|
{% trans 'System Tasks' %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -48,28 +48,41 @@
|
|||||||
{{ outpost.providers.all.select_subclasses|join:", " }}
|
{{ outpost.providers.all.select_subclasses|join:", " }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
{% with states=outpost.state %}
|
||||||
|
{% if states|length > 0 %}
|
||||||
<td role="cell">
|
<td role="cell">
|
||||||
{% with health=outpost.deployment_health %}
|
{% for state in states %}
|
||||||
{% if health %}
|
<div>
|
||||||
<i class="fas fa-check pf-m-success"></i> {{ health|naturaltime }}
|
{% if state.last_seen %}
|
||||||
|
<i class="fas fa-check pf-m-success"></i> {{ state.last_seen|naturaltime }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="fas fa-times pf-m-danger"></i> Unhealthy
|
<i class="fas fa-times pf-m-danger"></i> {% trans 'Unhealthy' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td role="cell">
|
<td role="cell">
|
||||||
<span>
|
{% for state in states %}
|
||||||
{% with ver=outpost.deployment_version %}
|
<div>
|
||||||
{% if not ver.version %}
|
{% if not state.version %}
|
||||||
<i class="fas fa-question-circle"></i>
|
<i class="fas fa-question-circle"></i>
|
||||||
{% elif ver.outdated %}
|
{% elif state.version_outdated %}
|
||||||
<i class="fas fa-times pf-m-danger"></i> {% blocktrans with is=ver.version should=ver.should %}{{ is }}, should be {{ should }}{% endblocktrans %}
|
<i class="fas fa-times pf-m-danger"></i> {% blocktrans with is=state.version should=state.version_should %}{{ is }}, should be {{ should }}{% endblocktrans %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="fas fa-check pf-m-success"></i> {{ ver.version }}
|
<i class="fas fa-check pf-m-success"></i> {{ state.version }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
{% else %}
|
||||||
|
<td role="cell">
|
||||||
|
<i class="fas fa-question-circle"></i>
|
||||||
|
</td>
|
||||||
|
<td role="cell">
|
||||||
|
<i class="fas fa-question-circle"></i>
|
||||||
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:outpost-update' pk=outpost.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
|
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:outpost-update' pk=outpost.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
|
||||||
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:outpost-delete' pk=outpost.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
|
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:outpost-delete' pk=outpost.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
|
||||||
|
|||||||
@ -0,0 +1,135 @@
|
|||||||
|
{% extends "administration/base.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load humanize %}
|
||||||
|
{% load passbook_utils %}
|
||||||
|
{% load admin_reflection %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="pf-c-page__main-section pf-m-light">
|
||||||
|
<div class="pf-c-content">
|
||||||
|
<h1>
|
||||||
|
<i class="pf-icon-integration"></i>
|
||||||
|
{% trans 'Outpost Service-Connections' %}
|
||||||
|
</h1>
|
||||||
|
<p>{% trans "Outpost Service-Connections define how passbook connects to external platforms to manage and deploy Outposts." %}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||||
|
<div class="pf-c-card">
|
||||||
|
{% if object_list %}
|
||||||
|
<div class="pf-c-toolbar">
|
||||||
|
<div class="pf-c-toolbar__content">
|
||||||
|
{% include 'partials/toolbar_search.html' %}
|
||||||
|
<div class="pf-c-toolbar__bulk-select">
|
||||||
|
<div class="pf-c-dropdown">
|
||||||
|
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
|
||||||
|
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
|
||||||
|
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="pf-c-dropdown__menu" hidden>
|
||||||
|
{% for type, name in types.items %}
|
||||||
|
<li>
|
||||||
|
<a class="pf-c-dropdown__menu-item" href="{% url 'passbook_admin:outpost-service-connection-create' %}?type={{ type }}&back={{ request.get_full_path }}">
|
||||||
|
{{ name|verbose_name }}<br>
|
||||||
|
<small>
|
||||||
|
{{ name|doc }}
|
||||||
|
</small>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include 'partials/pagination.html' %}
|
||||||
|
</div>
|
||||||
|
</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 'Type' %}</th>
|
||||||
|
<th role="columnheader" scope="col">{% trans 'Local?' %}</th>
|
||||||
|
<th role="columnheader" scope="col">{% trans 'Status' %}</th>
|
||||||
|
<th role="cell"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody role="rowgroup">
|
||||||
|
{% for sc in object_list %}
|
||||||
|
<tr role="row">
|
||||||
|
<th role="columnheader">
|
||||||
|
<span>{{ sc.name }}</span>
|
||||||
|
</th>
|
||||||
|
<td role="cell">
|
||||||
|
<span>
|
||||||
|
{{ sc|verbose_name }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td role="cell">
|
||||||
|
<span>
|
||||||
|
{{ sc.local|yesno:"Yes,No" }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td role="cell">
|
||||||
|
<span>
|
||||||
|
{% if sc.state.healthy %}
|
||||||
|
<i class="fas fa-check pf-m-success"></i> {{ sc.state.version }}
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-times pf-m-danger"></i> {% trans 'Unhealthy' %}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:outpost-service-connection-update' pk=sc.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
|
||||||
|
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:outpost-service-connection-delete' pk=sc.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="pf-c-pagination pf-m-bottom">
|
||||||
|
{% include 'partials/pagination.html' %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="pf-c-toolbar">
|
||||||
|
<div class="pf-c-toolbar__content">
|
||||||
|
{% include 'partials/toolbar_search.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pf-c-empty-state">
|
||||||
|
<div class="pf-c-empty-state__content">
|
||||||
|
<i class="fas fa-map-marker pf-c-empty-state__icon" aria-hidden="true"></i>
|
||||||
|
<h1 class="pf-c-title pf-m-lg">
|
||||||
|
{% trans 'No Outpost Service Connections.' %}
|
||||||
|
</h1>
|
||||||
|
<div class="pf-c-empty-state__body">
|
||||||
|
{% if request.GET.search != "" %}
|
||||||
|
{% trans "Your search query doesn't match any outposts." %}
|
||||||
|
{% else %}
|
||||||
|
{% trans 'Currently no service connections exist. Click the button below to create one.' %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="pf-c-dropdown">
|
||||||
|
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
|
||||||
|
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
|
||||||
|
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="pf-c-dropdown__menu" hidden>
|
||||||
|
{% for type, name in types.items %}
|
||||||
|
<li>
|
||||||
|
<a class="pf-c-dropdown__menu-item" href="{% url 'passbook_admin:outpost-service-connection-create' %}?type={{ type }}&back={{ request.get_full_path }}">
|
||||||
|
{{ name|verbose_name }}<br>
|
||||||
|
<small>
|
||||||
|
{{ name|doc }}
|
||||||
|
</small>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
@ -56,19 +56,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
|
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-plugged"></i> {% trans 'Providers' %}
|
<i class="pf-icon pf-icon-plugged"></i> {% trans 'Providers' %}
|
||||||
</div>
|
</div>
|
||||||
|
<a href="{% url 'passbook_admin:providers' %}">
|
||||||
|
<i class="fa fa-external-link-alt"> </i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
{% if providers_without_application.exists %}
|
{% if providers_without_application.exists %}
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
<i class="fa fa-exclamation-triangle"></i> {{ provider_count }}
|
<i class="fa fa-exclamation-triangle"></i> {{ provider_count }}
|
||||||
</p>
|
</p>
|
||||||
<p>{% trans 'Warning: At least one Provider has no application assigned.' %}</p>
|
<p>{% trans 'Warning: At least one Provider has no application assigned.' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
<i class="fa fa-check-circle"></i> {{ provider_count }}
|
<i class="fa fa-check-circle"></i> {{ provider_count }}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -76,19 +79,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
|
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-infrastructure"></i> {% trans 'Policies' %}
|
<i class="pf-icon pf-icon-infrastructure"></i> {% trans 'Policies' %}
|
||||||
</div>
|
</div>
|
||||||
|
<a href="{% url 'passbook_admin:policies' %}">
|
||||||
|
<i class="fa fa-external-link-alt"> </i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
{% if policies_without_binding %}
|
{% if policies_without_binding %}
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
<i class="fa fa-exclamation-triangle"></i> {{ policy_count }}
|
<i class="fa fa-exclamation-triangle"></i> {{ policy_count }}
|
||||||
</p>
|
</p>
|
||||||
<p>{% trans 'Policies without binding exist.' %}</p>
|
<p>{% trans 'Policies without binding exist.' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
<i class="fa fa-check-circle"></i> {{ policy_count }}
|
<i class="fa fa-check-circle"></i> {{ policy_count }}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -96,26 +102,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
|
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-user"></i> {% trans 'Users' %}
|
<i class="pf-icon pf-icon-user"></i> {% trans 'Users' %}
|
||||||
</div>
|
</div>
|
||||||
|
<a href="{% url 'passbook_admin:users' %}">
|
||||||
|
<i class="fa fa-external-link-alt"> </i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
<i class="fa fa-check-circle"></i> {{ user_count }}
|
<i class="fa fa-check-circle"></i> {{ user_count }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
|
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-bundle"></i> {% trans 'Version' %}
|
<i class="pf-icon pf-icon-bundle"></i> {% trans 'Version' %}
|
||||||
</div>
|
</div>
|
||||||
|
<a href="https://github.com/BeryJu/passbook/releases" target="_blank">
|
||||||
|
<i class="fa fa-external-link-alt"> </i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
{% if version >= version_latest %}
|
{% if version >= version_latest %}
|
||||||
<i class="fa fa-check-circle"></i> {{ version }}
|
<i class="fa fa-check-circle"></i> {{ version }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -142,13 +154,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<fetch-fill-slot class="pf-c-card__body" url="{% url 'passbook_api:admin_overview-list' %}" key="worker_count">
|
<fetch-fill-slot class="pf-c-card__body" url="{% url 'passbook_api:admin_overview-list' %}" key="worker_count">
|
||||||
<div slot="value < 1">
|
<div slot="value < 1">
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
<i class="fa fa-exclamation-triangle"></i> <span data-value></span>
|
<i class="fa fa-exclamation-triangle"></i> <span data-value></span>
|
||||||
</p>
|
</p>
|
||||||
<p>{% trans 'No workers connected.' %}</p>
|
<p>{% trans 'No workers connected.' %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div slot="value >= 1">
|
<div slot="value >= 1">
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
<i class="fa fa-check-circle"></i> <span data-value></span>
|
<i class="fa fa-check-circle"></i> <span data-value></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -162,74 +174,104 @@
|
|||||||
</fetch-fill-slot>
|
</fetch-fill-slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-hoverable pf-m-compact" data-target="modal" data-modal="clearCacheModalRoot">
|
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Policies' %}
|
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Policies' %}
|
||||||
</div>
|
</div>
|
||||||
|
<a data-target="modal" data-modal="clearPolicyCache">
|
||||||
|
<i class="fa fa-trash"> </i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
{% if cached_policies < 1 %}
|
{% if cached_policies < 1 %}
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
<i class="fa fa-exclamation-triangle"></i> {{ cached_policies }}
|
<i class="fa fa-exclamation-triangle"></i> {{ cached_policies }}
|
||||||
</p>
|
</p>
|
||||||
<p>{% trans 'No policies cached. Users may experience slow response times.' %}</p>
|
<p>{% trans 'No policies cached. Users may experience slow response times.' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
<i class="fa fa-check-circle"></i> {{ cached_policies }}
|
<i class="fa fa-check-circle"></i> {{ cached_policies }}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
|
|
||||||
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
|
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Flows' %}
|
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Flows' %}
|
||||||
</div>
|
</div>
|
||||||
|
<a data-target="modal" data-modal="clearFlowCache">
|
||||||
|
<i class="fa fa-trash"> </i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
{% if cached_flows < 1 %}
|
{% if cached_flows < 1 %}
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
<span class="fa fa-exclamation-triangle"></span> {{ cached_flows }}
|
<span class="fa fa-exclamation-triangle"></span> {{ cached_flows }}
|
||||||
</p>
|
</p>
|
||||||
<p>{% trans 'No flows cached.' %}</p>
|
<p>{% trans 'No flows cached.' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="aggregate-status">
|
<p class="pb-aggregate-card">
|
||||||
<i class="fa fa-check-circle"></i> {{ cached_flows }}
|
<i class="fa fa-check-circle"></i> {{ cached_flows }}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-backdrop" id="clearCacheModalRoot" hidden>
|
</section>
|
||||||
|
|
||||||
|
<div class="pf-c-backdrop" id="clearPolicyCache" hidden>
|
||||||
<div class="pf-l-bullseye">
|
<div class="pf-l-bullseye">
|
||||||
<div class="pf-c-modal-box pf-m-sm" role="dialog">
|
<div class="pf-c-modal-box pf-m-sm" role="dialog">
|
||||||
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
||||||
<i class="fas fa-times" aria-hidden="true"></i>
|
<i class="fas fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="pf-c-modal-box__header">
|
<div class="pf-c-modal-box__header">
|
||||||
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Cache' %}?</h1>
|
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Policy Cache' %}?</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-modal-box__body" id="modal-description">
|
<div class="pf-c-modal-box__body" id="modal-description">
|
||||||
<form method="post" id="clearForm">
|
<form method="post" id="clear_policies">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="clear">
|
<input type="hidden" name="clear_policies">
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans %}
|
{% blocktrans %}
|
||||||
Are you sure you want to clear the cache? This includes all user sessions and all cached Policy results.
|
Are you sure you want to clear the policy cache? This will cause all policies to be re-evaluated on their next usage.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
<h3>
|
|
||||||
{% blocktrans %}
|
|
||||||
This will also log you out.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</h3>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<footer class="pf-c-modal-box__footer pf-m-align-left">
|
<footer class="pf-c-modal-box__footer pf-m-align-left">
|
||||||
<button form="clearForm" class="pf-c-button pf-m-primary" type="submit">{% trans 'Clear' %}</button>
|
<button form="clear_policies" class="pf-c-button pf-m-primary" type="submit">{% trans 'Clear' %}</button>
|
||||||
|
<button data-modal-close class="pf-c-button pf-m-link" type="button">{% trans 'Cancel' %}</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pf-c-backdrop" id="clearFlowCache" hidden>
|
||||||
|
<div class="pf-l-bullseye">
|
||||||
|
<div class="pf-c-modal-box pf-m-sm" role="dialog">
|
||||||
|
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
||||||
|
<i class="fas fa-times" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<div class="pf-c-modal-box__header">
|
||||||
|
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Flow Cache' %}?</h1>
|
||||||
|
</div>
|
||||||
|
<div class="pf-c-modal-box__body" id="modal-description">
|
||||||
|
<form method="post" id="clear_flows">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="clear_flows">
|
||||||
|
<p>
|
||||||
|
{% blocktrans %}
|
||||||
|
Are you sure you want to clear the flow cache? This will cause all flows to be re-evaluated on their next usage.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<footer class="pf-c-modal-box__footer pf-m-align-left">
|
||||||
|
<button form="clear_flows" class="pf-c-button pf-m-primary" type="submit">{% trans 'Clear' %}</button>
|
||||||
<button data-modal-close class="pf-c-button pf-m-link" type="button">{% trans 'Cancel' %}</button>
|
<button data-modal-close class="pf-c-button pf-m-link" type="button">{% trans 'Cancel' %}</button>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
@ -274,7 +316,6 @@ fetch("{% url 'passbook_api:admin_metrics-list' %}").then(r => r.json()).then(r
|
|||||||
const date = new Date();
|
const date = new Date();
|
||||||
const delta = (date - values[index].value);
|
const delta = (date - values[index].value);
|
||||||
const ago = Math.round(delta / 1000 / 3600);
|
const ago = Math.round(delta / 1000 / 3600);
|
||||||
console.log(ago);
|
|
||||||
return `${ago} Hours ago`;
|
return `${ago} Hours ago`;
|
||||||
},
|
},
|
||||||
autoSkip: true,
|
autoSkip: true,
|
||||||
@ -282,9 +323,6 @@ fetch("{% url 'passbook_api:admin_metrics-list' %}").then(r => r.json()).then(r
|
|||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
yAxes: [{
|
yAxes: [{
|
||||||
ticks: {
|
|
||||||
stepSize: 1
|
|
||||||
},
|
|
||||||
stacked: true,
|
stacked: true,
|
||||||
gridLines: {
|
gridLines: {
|
||||||
color: "rgba(0, 0, 0, 0)",
|
color: "rgba(0, 0, 0, 0)",
|
||||||
|
|||||||
77
passbook/admin/templates/administration/task/list.html
Normal file
77
passbook/admin/templates/administration/task/list.html
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
{% extends "administration/base.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load humanize %}
|
||||||
|
{% load passbook_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-automation"></i>
|
||||||
|
{% trans 'System Tasks' %}
|
||||||
|
</h1>
|
||||||
|
<p>{% trans "Long-running operations which passbook executes in the background." %}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||||
|
<div class="pf-c-card">
|
||||||
|
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
|
||||||
|
<thead>
|
||||||
|
<tr role="row">
|
||||||
|
<th role="columnheader" scope="col">{% trans 'Identifier' %}</th>
|
||||||
|
<th role="columnheader" scope="col">{% trans 'Description' %}</th>
|
||||||
|
<th role="columnheader" scope="col">{% trans 'Last Run' %}</th>
|
||||||
|
<th role="columnheader" scope="col">{% trans 'Status' %}</th>
|
||||||
|
<th role="columnheader" scope="col">{% trans 'Messages' %}</th>
|
||||||
|
<th role="cell"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody role="rowgroup">
|
||||||
|
{% for task in object_list %}
|
||||||
|
<tr role="row">
|
||||||
|
<th role="columnheader">
|
||||||
|
<pre>{{ task.task_name }}</pre>
|
||||||
|
</th>
|
||||||
|
<td role="cell">
|
||||||
|
<span>
|
||||||
|
{{ task.task_description }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td role="cell">
|
||||||
|
<span>
|
||||||
|
{{ task.finish_timestamp|naturaltime }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td role="cell">
|
||||||
|
<span>
|
||||||
|
{% if task.result.status == task_successful %}
|
||||||
|
<i class="fas fa-check pf-m-success"></i> {% trans 'Successful' %}
|
||||||
|
{% elif task.result.status == task_warning %}
|
||||||
|
<i class="fas fa-exclamation-triangle pf-m-warning"></i> {% trans 'Warning' %}
|
||||||
|
{% elif task.result.status == task_error %}
|
||||||
|
<i class="fas fa-times pf-m-danger"></i> {% trans 'Error' %}
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-question-circle"></i> {% trans 'Unknown' %}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% for message in task.result.messages %}
|
||||||
|
<div>
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button is="action-button" class="pf-c-button pf-m-primary" url="{% url 'passbook_api:admin_system_tasks-retry' pk=task.task_name %}">
|
||||||
|
{% trans 'Retry Task' %}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
42
passbook/admin/templates/administration/user/disable.html
Normal file
42
passbook/admin/templates/administration/user/disable.html
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{% extends "administration/base.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load passbook_utils %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="pf-c-page__main-section pf-m-light">
|
||||||
|
<div class="pf-c-content">
|
||||||
|
{% block above_form %}
|
||||||
|
<h1>
|
||||||
|
{% blocktrans with object_type=object|verbose_name %}
|
||||||
|
Disable {{ object_type }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
</h1>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="pf-c-page__main-section">
|
||||||
|
<div class="pf-l-stack">
|
||||||
|
<div class="pf-l-stack__item">
|
||||||
|
<div class="pf-c-card">
|
||||||
|
<div class="pf-c-card__body">
|
||||||
|
<form action="" method="post" class="pf-c-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
{% blocktrans with object_type=object|verbose_name name=object %}
|
||||||
|
Are you sure you want to disable {{ object_type }} "{{ object }}"?
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<div class="pf-c-form__group pf-m-action">
|
||||||
|
<div class="pf-c-form__actions">
|
||||||
|
<input class="pf-c-button pf-m-danger" type="submit" value="{% trans 'Disable' %}" />
|
||||||
|
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
@ -54,7 +54,11 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:user-update' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
|
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:user-update' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
|
||||||
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:user-delete' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
|
{% if user.is_active %}
|
||||||
|
<a class="pf-c-button pf-m-warning" href="{% url 'passbook_admin:user-disable' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Disable' %}</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="pf-c-button pf-m-primary" href="{% url 'passbook_admin:user-enable' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Enable' %}</a>
|
||||||
|
{% endif %}
|
||||||
<a class="pf-c-button pf-m-tertiary" href="{% url 'passbook_admin:user-password-reset' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Reset Password' %}</a>
|
<a class="pf-c-button pf-m-tertiary" href="{% url 'passbook_admin:user-password-reset' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Reset Password' %}</a>
|
||||||
<a class="pf-c-button pf-m-tertiary" href="{% url 'passbook_core:impersonate-init' user_id=user.pk %}">{% trans 'Impersonate' %}</a>
|
<a class="pf-c-button pf-m-tertiary" href="{% url 'passbook_core:impersonate-init' user_id=user.pk %}">{% trans 'Impersonate' %}</a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{% extends "administration/base.html" %}
|
{% extends container_template|default:"administration/base.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load passbook_utils %}
|
{% load passbook_utils %}
|
||||||
|
|||||||
@ -7,16 +7,18 @@ from passbook.admin.views import (
|
|||||||
flows,
|
flows,
|
||||||
groups,
|
groups,
|
||||||
outposts,
|
outposts,
|
||||||
|
outposts_service_connections,
|
||||||
overview,
|
overview,
|
||||||
policies,
|
policies,
|
||||||
policies_bindings,
|
policies_bindings,
|
||||||
property_mapping,
|
property_mappings,
|
||||||
providers,
|
providers,
|
||||||
sources,
|
sources,
|
||||||
stages,
|
stages,
|
||||||
stages_bindings,
|
stages_bindings,
|
||||||
stages_invitations,
|
stages_invitations,
|
||||||
stages_prompts,
|
stages_prompts,
|
||||||
|
tasks,
|
||||||
tokens,
|
tokens,
|
||||||
users,
|
users,
|
||||||
)
|
)
|
||||||
@ -224,22 +226,22 @@ urlpatterns = [
|
|||||||
# Property Mappings
|
# Property Mappings
|
||||||
path(
|
path(
|
||||||
"property-mappings/",
|
"property-mappings/",
|
||||||
property_mapping.PropertyMappingListView.as_view(),
|
property_mappings.PropertyMappingListView.as_view(),
|
||||||
name="property-mappings",
|
name="property-mappings",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"property-mappings/create/",
|
"property-mappings/create/",
|
||||||
property_mapping.PropertyMappingCreateView.as_view(),
|
property_mappings.PropertyMappingCreateView.as_view(),
|
||||||
name="property-mapping-create",
|
name="property-mapping-create",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"property-mappings/<uuid:pk>/update/",
|
"property-mappings/<uuid:pk>/update/",
|
||||||
property_mapping.PropertyMappingUpdateView.as_view(),
|
property_mappings.PropertyMappingUpdateView.as_view(),
|
||||||
name="property-mapping-update",
|
name="property-mapping-update",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"property-mappings/<uuid:pk>/delete/",
|
"property-mappings/<uuid:pk>/delete/",
|
||||||
property_mapping.PropertyMappingDeleteView.as_view(),
|
property_mappings.PropertyMappingDeleteView.as_view(),
|
||||||
name="property-mapping-delete",
|
name="property-mapping-delete",
|
||||||
),
|
),
|
||||||
# Users
|
# Users
|
||||||
@ -247,6 +249,10 @@ urlpatterns = [
|
|||||||
path("users/create/", users.UserCreateView.as_view(), name="user-create"),
|
path("users/create/", users.UserCreateView.as_view(), name="user-create"),
|
||||||
path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"),
|
path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"),
|
||||||
path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"),
|
path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"),
|
||||||
|
path(
|
||||||
|
"users/<int:pk>/disable/", users.UserDisableView.as_view(), name="user-disable"
|
||||||
|
),
|
||||||
|
path("users/<int:pk>/enable/", users.UserEnableView.as_view(), name="user-enable"),
|
||||||
path(
|
path(
|
||||||
"users/<int:pk>/reset/",
|
"users/<int:pk>/reset/",
|
||||||
users.UserPasswordResetView.as_view(),
|
users.UserPasswordResetView.as_view(),
|
||||||
@ -307,4 +313,31 @@ urlpatterns = [
|
|||||||
outposts.OutpostDeleteView.as_view(),
|
outposts.OutpostDeleteView.as_view(),
|
||||||
name="outpost-delete",
|
name="outpost-delete",
|
||||||
),
|
),
|
||||||
|
# Outpost Service Connections
|
||||||
|
path(
|
||||||
|
"outposts/service_connections/",
|
||||||
|
outposts_service_connections.OutpostServiceConnectionListView.as_view(),
|
||||||
|
name="outpost-service-connections",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"outposts/service_connections/create/",
|
||||||
|
outposts_service_connections.OutpostServiceConnectionCreateView.as_view(),
|
||||||
|
name="outpost-service-connection-create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"outposts/service_connections/<uuid:pk>/update/",
|
||||||
|
outposts_service_connections.OutpostServiceConnectionUpdateView.as_view(),
|
||||||
|
name="outpost-service-connection-update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"outposts/service_connections/<uuid:pk>/delete/",
|
||||||
|
outposts_service_connections.OutpostServiceConnectionDeleteView.as_view(),
|
||||||
|
name="outpost-service-connection-delete",
|
||||||
|
),
|
||||||
|
# Tasks
|
||||||
|
path(
|
||||||
|
"tasks/",
|
||||||
|
tasks.TaskListView.as_view(),
|
||||||
|
name="tasks",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
83
passbook/admin/views/outposts_service_connections.py
Normal file
83
passbook/admin/views/outposts_service_connections.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
"""passbook OutpostServiceConnection administration"""
|
||||||
|
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 gettext as _
|
||||||
|
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||||
|
|
||||||
|
from passbook.admin.views.utils import (
|
||||||
|
BackSuccessUrlMixin,
|
||||||
|
DeleteMessageView,
|
||||||
|
InheritanceCreateView,
|
||||||
|
InheritanceListView,
|
||||||
|
InheritanceUpdateView,
|
||||||
|
SearchListMixin,
|
||||||
|
UserPaginateListMixin,
|
||||||
|
)
|
||||||
|
from passbook.outposts.models import OutpostServiceConnection
|
||||||
|
|
||||||
|
|
||||||
|
class OutpostServiceConnectionListView(
|
||||||
|
LoginRequiredMixin,
|
||||||
|
PermissionListMixin,
|
||||||
|
UserPaginateListMixin,
|
||||||
|
SearchListMixin,
|
||||||
|
InheritanceListView,
|
||||||
|
):
|
||||||
|
"""Show list of all outpost-service-connections"""
|
||||||
|
|
||||||
|
model = OutpostServiceConnection
|
||||||
|
permission_required = "passbook_outposts.add_outpostserviceconnection"
|
||||||
|
template_name = "administration/outpost_service_connection/list.html"
|
||||||
|
ordering = "pk"
|
||||||
|
search_fields = ["pk", "name"]
|
||||||
|
|
||||||
|
|
||||||
|
class OutpostServiceConnectionCreateView(
|
||||||
|
SuccessMessageMixin,
|
||||||
|
BackSuccessUrlMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
DjangoPermissionRequiredMixin,
|
||||||
|
InheritanceCreateView,
|
||||||
|
):
|
||||||
|
"""Create new OutpostServiceConnection"""
|
||||||
|
|
||||||
|
model = OutpostServiceConnection
|
||||||
|
permission_required = "passbook_outposts.add_outpostserviceconnection"
|
||||||
|
|
||||||
|
template_name = "generic/create.html"
|
||||||
|
success_url = reverse_lazy("passbook_admin:outpost-service-connections")
|
||||||
|
success_message = _("Successfully created OutpostServiceConnection")
|
||||||
|
|
||||||
|
|
||||||
|
class OutpostServiceConnectionUpdateView(
|
||||||
|
SuccessMessageMixin,
|
||||||
|
BackSuccessUrlMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
PermissionRequiredMixin,
|
||||||
|
InheritanceUpdateView,
|
||||||
|
):
|
||||||
|
"""Update outpostserviceconnection"""
|
||||||
|
|
||||||
|
model = OutpostServiceConnection
|
||||||
|
permission_required = "passbook_outposts.change_outpostserviceconnection"
|
||||||
|
|
||||||
|
template_name = "generic/update.html"
|
||||||
|
success_url = reverse_lazy("passbook_admin:outpost-service-connections")
|
||||||
|
success_message = _("Successfully updated OutpostServiceConnection")
|
||||||
|
|
||||||
|
|
||||||
|
class OutpostServiceConnectionDeleteView(
|
||||||
|
LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageView
|
||||||
|
):
|
||||||
|
"""Delete outpostserviceconnection"""
|
||||||
|
|
||||||
|
model = OutpostServiceConnection
|
||||||
|
permission_required = "passbook_outposts.delete_outpostserviceconnection"
|
||||||
|
|
||||||
|
template_name = "generic/delete.html"
|
||||||
|
success_url = reverse_lazy("passbook_admin:outpost-service-connections")
|
||||||
|
success_message = _("Successfully deleted OutpostServiceConnection")
|
||||||
@ -5,9 +5,9 @@ from django.conf import settings
|
|||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.db.models.fields.json import KeyTextTransform
|
from django.db.models.fields.json import KeyTextTransform
|
||||||
from django.shortcuts import redirect, reverse
|
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
from packaging.version import LegacyVersion, Version, parse
|
from packaging.version import LegacyVersion, Version, parse
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook import __version__
|
from passbook import __version__
|
||||||
from passbook.admin.mixins import AdminRequiredMixin
|
from passbook.admin.mixins import AdminRequiredMixin
|
||||||
@ -16,6 +16,8 @@ from passbook.audit.models import Event, EventAction
|
|||||||
from passbook.core.models import Provider, User
|
from passbook.core.models import Provider, User
|
||||||
from passbook.policies.models import Policy
|
from passbook.policies.models import Policy
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
||||||
"""Overview View"""
|
"""Overview View"""
|
||||||
@ -24,9 +26,14 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
|||||||
|
|
||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs):
|
||||||
"""Handle post (clear cache from modal)"""
|
"""Handle post (clear cache from modal)"""
|
||||||
if "clear" in self.request.POST:
|
if "clear_policies" in self.request.POST:
|
||||||
cache.clear()
|
keys = cache.keys("policy_*")
|
||||||
return redirect(reverse("passbook_flows:default-authentication"))
|
cache.delete_many(keys)
|
||||||
|
LOGGER.debug("Cleared Policy cache", keys=len(keys))
|
||||||
|
if "clear_flows" in self.request.POST:
|
||||||
|
keys = cache.keys("flow_*")
|
||||||
|
cache.delete_many(keys)
|
||||||
|
LOGGER.debug("Cleared flow cache", keys=len(keys))
|
||||||
return self.get(*args, **kwargs)
|
return self.get(*args, **kwargs)
|
||||||
|
|
||||||
def get_latest_version(self) -> Union[LegacyVersion, Version]:
|
def get_latest_version(self) -> Union[LegacyVersion, Version]:
|
||||||
|
|||||||
@ -32,8 +32,8 @@ class ProviderListView(
|
|||||||
model = Provider
|
model = Provider
|
||||||
permission_required = "passbook_core.add_provider"
|
permission_required = "passbook_core.add_provider"
|
||||||
template_name = "administration/provider/list.html"
|
template_name = "administration/provider/list.html"
|
||||||
ordering = "id"
|
ordering = "pk"
|
||||||
search_fields = ["id", "name"]
|
search_fields = ["pk", "name"]
|
||||||
|
|
||||||
|
|
||||||
class ProviderCreateView(
|
class ProviderCreateView(
|
||||||
|
|||||||
23
passbook/admin/views/tasks.py
Normal file
23
passbook/admin/views/tasks.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"""passbook Tasks List"""
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
|
|
||||||
|
from passbook.admin.mixins import AdminRequiredMixin
|
||||||
|
from passbook.lib.tasks import TaskInfo, TaskResultStatus
|
||||||
|
|
||||||
|
|
||||||
|
class TaskListView(AdminRequiredMixin, TemplateView):
|
||||||
|
"""Show list of all background tasks"""
|
||||||
|
|
||||||
|
template_name = "administration/task/list.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
||||||
|
kwargs = super().get_context_data(**kwargs)
|
||||||
|
kwargs["object_list"] = sorted(
|
||||||
|
TaskInfo.all().values(), key=lambda x: x.task_name
|
||||||
|
)
|
||||||
|
kwargs["task_successful"] = TaskResultStatus.SUCCESSFUL
|
||||||
|
kwargs["task_warning"] = TaskResultStatus.WARNING
|
||||||
|
kwargs["task_error"] = TaskResultStatus.ERROR
|
||||||
|
return kwargs
|
||||||
@ -6,6 +6,7 @@ from django.contrib.auth.mixins import (
|
|||||||
)
|
)
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.http.response import HttpResponseRedirect
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
@ -98,6 +99,53 @@ class UserDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessageV
|
|||||||
success_message = _("Successfully deleted User")
|
success_message = _("Successfully deleted User")
|
||||||
|
|
||||||
|
|
||||||
|
class UserDisableView(
|
||||||
|
LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DeleteMessageView
|
||||||
|
):
|
||||||
|
"""Disable user"""
|
||||||
|
|
||||||
|
object: User
|
||||||
|
|
||||||
|
model = User
|
||||||
|
permission_required = "passbook_core.update_user"
|
||||||
|
|
||||||
|
# By default the object's name is user which is used by other checks
|
||||||
|
context_object_name = "object"
|
||||||
|
template_name = "administration/user/disable.html"
|
||||||
|
success_url = reverse_lazy("passbook_admin:users")
|
||||||
|
success_message = _("Successfully disabled User")
|
||||||
|
|
||||||
|
def delete(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
self.object: User = self.get_object()
|
||||||
|
success_url = self.get_success_url()
|
||||||
|
self.object.is_active = False
|
||||||
|
self.object.save()
|
||||||
|
return HttpResponseRedirect(success_url)
|
||||||
|
|
||||||
|
|
||||||
|
class UserEnableView(
|
||||||
|
LoginRequiredMixin, PermissionRequiredMixin, BackSuccessUrlMixin, DetailView
|
||||||
|
):
|
||||||
|
"""Enable user"""
|
||||||
|
|
||||||
|
object: User
|
||||||
|
|
||||||
|
model = User
|
||||||
|
permission_required = "passbook_core.update_user"
|
||||||
|
|
||||||
|
# By default the object's name is user which is used by other checks
|
||||||
|
context_object_name = "object"
|
||||||
|
success_url = reverse_lazy("passbook_admin:users")
|
||||||
|
success_message = _("Successfully enabled User")
|
||||||
|
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs):
|
||||||
|
self.object: User = self.get_object()
|
||||||
|
success_url = self.get_success_url()
|
||||||
|
self.object.is_active = True
|
||||||
|
self.object.save()
|
||||||
|
return HttpResponseRedirect(success_url)
|
||||||
|
|
||||||
|
|
||||||
class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||||
"""Get Password reset link for user"""
|
"""Get Password reset link for user"""
|
||||||
|
|
||||||
@ -110,7 +158,7 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
|
|||||||
token, _ = Token.objects.get_or_create(
|
token, _ = Token.objects.get_or_create(
|
||||||
identifier="password-reset-temp", user=self.object
|
identifier="password-reset-temp", user=self.object
|
||||||
)
|
)
|
||||||
querystring = urlencode({"token": token.token_uuid})
|
querystring = urlencode({"token": token.key})
|
||||||
link = request.build_absolute_uri(
|
link = request.build_absolute_uri(
|
||||||
reverse("passbook_flows:default-recovery") + f"?{querystring}"
|
reverse("passbook_flows:default-recovery") + f"?{querystring}"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,43 +1,57 @@
|
|||||||
"""API Authentication"""
|
"""API Authentication"""
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
from typing import Any, Tuple, Union
|
from typing import Any, Optional, Tuple, Union
|
||||||
|
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
from rest_framework import HTTP_HEADER_ENCODING, exceptions
|
|
||||||
from rest_framework.authentication import BaseAuthentication, get_authorization_header
|
from rest_framework.authentication import BaseAuthentication, get_authorization_header
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Token, TokenIntents, User
|
from passbook.core.models import Token, TokenIntents, User
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
def token_from_header(raw_header: bytes) -> Optional[Token]:
|
||||||
|
"""raw_header in the Format of `Basic dGVzdDp0ZXN0`"""
|
||||||
|
auth_credentials = raw_header.decode()
|
||||||
|
# Accept headers with Type format and without
|
||||||
|
if " " in auth_credentials:
|
||||||
|
auth_type, auth_credentials = auth_credentials.split()
|
||||||
|
if auth_type.lower() != "basic":
|
||||||
|
LOGGER.debug(
|
||||||
|
"Unsupported authentication type, denying", type=auth_type.lower()
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
auth_credentials = b64decode(auth_credentials.encode()).decode()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return None
|
||||||
|
# Accept credentials with username and without
|
||||||
|
if ":" in auth_credentials:
|
||||||
|
_, password = auth_credentials.split(":")
|
||||||
|
else:
|
||||||
|
password = auth_credentials
|
||||||
|
if password == "":
|
||||||
|
return None
|
||||||
|
tokens = Token.filter_not_expired(key=password, intent=TokenIntents.INTENT_API)
|
||||||
|
if not tokens.exists():
|
||||||
|
LOGGER.debug("Token not found")
|
||||||
|
return None
|
||||||
|
return tokens.first()
|
||||||
|
|
||||||
|
|
||||||
class PassbookTokenAuthentication(BaseAuthentication):
|
class PassbookTokenAuthentication(BaseAuthentication):
|
||||||
"""Token-based authentication using HTTP Basic authentication"""
|
"""Token-based authentication using HTTP Basic authentication"""
|
||||||
|
|
||||||
def authenticate(self, request: Request) -> Union[Tuple[User, Any], None]:
|
def authenticate(self, request: Request) -> Union[Tuple[User, Any], None]:
|
||||||
"""Token-based authentication using HTTP Basic authentication"""
|
"""Token-based authentication using HTTP Basic authentication"""
|
||||||
auth = get_authorization_header(request).split()
|
auth = get_authorization_header(request)
|
||||||
|
|
||||||
if not auth or auth[0].lower() != b"basic":
|
token = token_from_header(auth)
|
||||||
|
if not token:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if len(auth) == 1:
|
return (token.user, None)
|
||||||
msg = _("Invalid basic header. No credentials provided.")
|
|
||||||
raise exceptions.AuthenticationFailed(msg)
|
|
||||||
if len(auth) > 2:
|
|
||||||
msg = _(
|
|
||||||
"Invalid basic header. Credentials string should not contain spaces."
|
|
||||||
)
|
|
||||||
raise exceptions.AuthenticationFailed(msg)
|
|
||||||
|
|
||||||
header_data = b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(":")
|
|
||||||
|
|
||||||
tokens = Token.filter_not_expired(
|
|
||||||
token_uuid=header_data[2], intent=TokenIntents.INTENT_API
|
|
||||||
)
|
|
||||||
if not tokens.exists():
|
|
||||||
raise exceptions.AuthenticationFailed(_("Invalid token."))
|
|
||||||
|
|
||||||
return (tokens.first().user, None)
|
|
||||||
|
|
||||||
def authenticate_header(self, request: Request) -> str:
|
def authenticate_header(self, request: Request) -> str:
|
||||||
return 'Basic realm="passbook"'
|
return 'Basic realm="passbook"'
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
"""core messages API"""
|
"""core messages API"""
|
||||||
from django.contrib.messages import get_messages
|
from django.contrib.messages import get_messages
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg2.utils import swagger_auto_schema
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
"""api v2 urls"""
|
"""api v2 urls"""
|
||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
from drf_yasg import openapi
|
from drf_yasg2 import openapi
|
||||||
from drf_yasg.views import get_schema_view
|
from drf_yasg2.views import get_schema_view
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
|
|
||||||
from passbook.admin.api.overview import AdministrationOverviewViewSet
|
from passbook.admin.api.overview import AdministrationOverviewViewSet
|
||||||
from passbook.admin.api.overview_metrics import AdministrationMetricsViewSet
|
from passbook.admin.api.overview_metrics import AdministrationMetricsViewSet
|
||||||
|
from passbook.admin.api.tasks import TaskViewSet
|
||||||
from passbook.api.v2.messages import MessagesViewSet
|
from passbook.api.v2.messages import MessagesViewSet
|
||||||
from passbook.audit.api import EventViewSet
|
from passbook.audit.api import EventViewSet
|
||||||
from passbook.core.api.applications import ApplicationViewSet
|
from passbook.core.api.applications import ApplicationViewSet
|
||||||
@ -18,7 +19,11 @@ from passbook.core.api.tokens import TokenViewSet
|
|||||||
from passbook.core.api.users import UserViewSet
|
from passbook.core.api.users import UserViewSet
|
||||||
from passbook.crypto.api import CertificateKeyPairViewSet
|
from passbook.crypto.api import CertificateKeyPairViewSet
|
||||||
from passbook.flows.api import FlowStageBindingViewSet, FlowViewSet, StageViewSet
|
from passbook.flows.api import FlowStageBindingViewSet, FlowViewSet, StageViewSet
|
||||||
from passbook.outposts.api import OutpostViewSet
|
from passbook.outposts.api import (
|
||||||
|
DockerServiceConnectionViewSet,
|
||||||
|
KubernetesServiceConnectionViewSet,
|
||||||
|
OutpostViewSet,
|
||||||
|
)
|
||||||
from passbook.policies.api import PolicyBindingViewSet, PolicyViewSet
|
from passbook.policies.api import PolicyBindingViewSet, PolicyViewSet
|
||||||
from passbook.policies.dummy.api import DummyPolicyViewSet
|
from passbook.policies.dummy.api import DummyPolicyViewSet
|
||||||
from passbook.policies.expiry.api import PasswordExpiryPolicyViewSet
|
from passbook.policies.expiry.api import PasswordExpiryPolicyViewSet
|
||||||
@ -28,7 +33,7 @@ from passbook.policies.hibp.api import HaveIBeenPwendPolicyViewSet
|
|||||||
from passbook.policies.password.api import PasswordPolicyViewSet
|
from passbook.policies.password.api import PasswordPolicyViewSet
|
||||||
from passbook.policies.reputation.api import ReputationPolicyViewSet
|
from passbook.policies.reputation.api import ReputationPolicyViewSet
|
||||||
from passbook.providers.oauth2.api import OAuth2ProviderViewSet, ScopeMappingViewSet
|
from passbook.providers.oauth2.api import OAuth2ProviderViewSet, ScopeMappingViewSet
|
||||||
from passbook.providers.proxy.api import OutpostConfigViewSet, ProxyProviderViewSet
|
from passbook.providers.proxy.api import ProxyOutpostConfigViewSet, ProxyProviderViewSet
|
||||||
from passbook.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProviderViewSet
|
from passbook.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProviderViewSet
|
||||||
from passbook.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
|
from passbook.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
|
||||||
from passbook.sources.oauth.api import OAuthSourceViewSet
|
from passbook.sources.oauth.api import OAuthSourceViewSet
|
||||||
@ -57,6 +62,7 @@ router.register(
|
|||||||
"admin/overview", AdministrationOverviewViewSet, basename="admin_overview"
|
"admin/overview", AdministrationOverviewViewSet, basename="admin_overview"
|
||||||
)
|
)
|
||||||
router.register("admin/metrics", AdministrationMetricsViewSet, basename="admin_metrics")
|
router.register("admin/metrics", AdministrationMetricsViewSet, basename="admin_metrics")
|
||||||
|
router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks")
|
||||||
|
|
||||||
router.register("core/applications", ApplicationViewSet)
|
router.register("core/applications", ApplicationViewSet)
|
||||||
router.register("core/groups", GroupViewSet)
|
router.register("core/groups", GroupViewSet)
|
||||||
@ -64,7 +70,14 @@ router.register("core/users", UserViewSet)
|
|||||||
router.register("core/tokens", TokenViewSet)
|
router.register("core/tokens", TokenViewSet)
|
||||||
|
|
||||||
router.register("outposts/outposts", OutpostViewSet)
|
router.register("outposts/outposts", OutpostViewSet)
|
||||||
router.register("outposts/proxy", OutpostConfigViewSet)
|
router.register("outposts/service_connections/docker", DockerServiceConnectionViewSet)
|
||||||
|
router.register(
|
||||||
|
"outposts/service_connections/kubernetes", KubernetesServiceConnectionViewSet
|
||||||
|
)
|
||||||
|
router.register("outposts/proxy", ProxyOutpostConfigViewSet)
|
||||||
|
|
||||||
|
router.register("flows/instances", FlowViewSet)
|
||||||
|
router.register("flows/bindings", FlowStageBindingViewSet)
|
||||||
|
|
||||||
router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet)
|
router.register("crypto/certificatekeypairs", CertificateKeyPairViewSet)
|
||||||
|
|
||||||
@ -112,9 +125,6 @@ router.register("stages/user_login", UserLoginStageViewSet)
|
|||||||
router.register("stages/user_logout", UserLogoutStageViewSet)
|
router.register("stages/user_logout", UserLogoutStageViewSet)
|
||||||
router.register("stages/user_write", UserWriteStageViewSet)
|
router.register("stages/user_write", UserWriteStageViewSet)
|
||||||
|
|
||||||
router.register("flows/instances", FlowViewSet)
|
|
||||||
router.register("flows/bindings", FlowStageBindingViewSet)
|
|
||||||
|
|
||||||
router.register("stages/dummy", DummyStageViewSet)
|
router.register("stages/dummy", DummyStageViewSet)
|
||||||
router.register("policies/dummy", DummyPolicyViewSet)
|
router.register("policies/dummy", DummyPolicyViewSet)
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,6 @@ class EventSerializer(ModelSerializer):
|
|||||||
"pk",
|
"pk",
|
||||||
"user",
|
"user",
|
||||||
"action",
|
"action",
|
||||||
"date",
|
|
||||||
"app",
|
"app",
|
||||||
"context",
|
"context",
|
||||||
"client_ip",
|
"client_ip",
|
||||||
|
|||||||
42
passbook/audit/migrations/0006_auto_20201017_2024.py
Normal file
42
passbook/audit/migrations/0006_auto_20201017_2024.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-10-17 20:24
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("passbook_audit", "0005_auto_20201005_2139"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="event",
|
||||||
|
name="date",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="event",
|
||||||
|
name="action",
|
||||||
|
field=models.TextField(
|
||||||
|
choices=[
|
||||||
|
("login", "Login"),
|
||||||
|
("login_failed", "Login Failed"),
|
||||||
|
("logout", "Logout"),
|
||||||
|
("user_write", "User Write"),
|
||||||
|
("suspicious_request", "Suspicious Request"),
|
||||||
|
("password_set", "Password Set"),
|
||||||
|
("token_view", "Token View"),
|
||||||
|
("invitation_created", "Invite Created"),
|
||||||
|
("invitation_used", "Invite Used"),
|
||||||
|
("authorize_application", "Authorize Application"),
|
||||||
|
("source_linked", "Source Linked"),
|
||||||
|
("impersonation_started", "Impersonation Started"),
|
||||||
|
("impersonation_ended", "Impersonation Ended"),
|
||||||
|
("model_created", "Model Created"),
|
||||||
|
("model_updated", "Model Updated"),
|
||||||
|
("model_deleted", "Model Deleted"),
|
||||||
|
("custom_", "Custom Prefix"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -100,6 +100,8 @@ class EventAction(models.TextChoices):
|
|||||||
SUSPICIOUS_REQUEST = "suspicious_request"
|
SUSPICIOUS_REQUEST = "suspicious_request"
|
||||||
PASSWORD_SET = "password_set" # noqa # nosec
|
PASSWORD_SET = "password_set" # noqa # nosec
|
||||||
|
|
||||||
|
TOKEN_VIEW = "token_view"
|
||||||
|
|
||||||
INVITE_CREATED = "invitation_created"
|
INVITE_CREATED = "invitation_created"
|
||||||
INVITE_USED = "invitation_used"
|
INVITE_USED = "invitation_used"
|
||||||
|
|
||||||
@ -122,7 +124,6 @@ class Event(models.Model):
|
|||||||
event_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
event_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||||
user = models.JSONField(default=dict)
|
user = models.JSONField(default=dict)
|
||||||
action = models.TextField(choices=EventAction.choices)
|
action = models.TextField(choices=EventAction.choices)
|
||||||
date = models.DateTimeField(auto_now_add=True)
|
|
||||||
app = models.TextField()
|
app = models.TextField()
|
||||||
context = models.JSONField(default=dict, blank=True)
|
context = models.JSONField(default=dict, blank=True)
|
||||||
client_ip = models.GenericIPAddressField(null=True)
|
client_ip = models.GenericIPAddressField(null=True)
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
<div class="pf-c-card">
|
<div class="pf-c-card">
|
||||||
<div class="pf-c-toolbar">
|
<div class="pf-c-toolbar">
|
||||||
<div class="pf-c-toolbar__content">
|
<div class="pf-c-toolbar__content">
|
||||||
|
{% include 'partials/toolbar_search.html' %}
|
||||||
{% include 'partials/pagination.html' %}
|
{% include 'partials/pagination.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,12 +3,16 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
|||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
from guardian.mixins import PermissionListMixin
|
from guardian.mixins import PermissionListMixin
|
||||||
|
|
||||||
from passbook.admin.views.utils import UserPaginateListMixin
|
from passbook.admin.views.utils import SearchListMixin, UserPaginateListMixin
|
||||||
from passbook.audit.models import Event
|
from passbook.audit.models import Event
|
||||||
|
|
||||||
|
|
||||||
class EventListView(
|
class EventListView(
|
||||||
PermissionListMixin, LoginRequiredMixin, UserPaginateListMixin, ListView
|
PermissionListMixin,
|
||||||
|
LoginRequiredMixin,
|
||||||
|
SearchListMixin,
|
||||||
|
UserPaginateListMixin,
|
||||||
|
ListView,
|
||||||
):
|
):
|
||||||
"""Show list of all invitations"""
|
"""Show list of all invitations"""
|
||||||
|
|
||||||
@ -16,3 +20,11 @@ class EventListView(
|
|||||||
template_name = "audit/list.html"
|
template_name = "audit/list.html"
|
||||||
permission_required = "passbook_audit.view_event"
|
permission_required = "passbook_audit.view_event"
|
||||||
ordering = "-created"
|
ordering = "-created"
|
||||||
|
|
||||||
|
search_fields = [
|
||||||
|
"user",
|
||||||
|
"action",
|
||||||
|
"app",
|
||||||
|
"context",
|
||||||
|
"client_ip",
|
||||||
|
]
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
"""Tokens API Viewset"""
|
"""Tokens API Viewset"""
|
||||||
|
from django.http.response import Http404
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import ModelSerializer
|
from rest_framework.serializers import ModelSerializer
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from passbook.audit.models import Event, EventAction
|
||||||
from passbook.core.models import Token
|
from passbook.core.models import Token
|
||||||
|
|
||||||
|
|
||||||
@ -17,6 +22,16 @@ class TokenSerializer(ModelSerializer):
|
|||||||
class TokenViewSet(ModelViewSet):
|
class TokenViewSet(ModelViewSet):
|
||||||
"""Token Viewset"""
|
"""Token Viewset"""
|
||||||
|
|
||||||
queryset = Token.objects.all()
|
|
||||||
lookup_field = "identifier"
|
lookup_field = "identifier"
|
||||||
|
queryset = Token.filter_not_expired()
|
||||||
serializer_class = TokenSerializer
|
serializer_class = TokenSerializer
|
||||||
|
|
||||||
|
@action(detail=True)
|
||||||
|
def view_key(self, request: Request, identifier: str) -> Response:
|
||||||
|
"""Return token key and log access"""
|
||||||
|
tokens = Token.filter_not_expired(identifier=identifier)
|
||||||
|
if not tokens.exists():
|
||||||
|
raise Http404
|
||||||
|
token = tokens.first()
|
||||||
|
Event.new(EventAction.TOKEN_VIEW, token=token).from_http(request)
|
||||||
|
return Response({"key": token.key})
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
"""Channels base classes"""
|
"""Channels base classes"""
|
||||||
from channels.generic.websocket import JsonWebsocketConsumer
|
from channels.generic.websocket import JsonWebsocketConsumer
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Token, TokenIntents, User
|
from passbook.api.auth import token_from_header
|
||||||
|
from passbook.core.models import User
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
@ -18,20 +18,15 @@ class AuthJsonConsumer(JsonWebsocketConsumer):
|
|||||||
if b"authorization" not in headers:
|
if b"authorization" not in headers:
|
||||||
LOGGER.warning("WS Request without authorization header")
|
LOGGER.warning("WS Request without authorization header")
|
||||||
self.close()
|
self.close()
|
||||||
|
return False
|
||||||
|
|
||||||
token = headers[b"authorization"]
|
raw_header = headers[b"authorization"]
|
||||||
try:
|
|
||||||
token_uuid = token.decode("utf-8")
|
token = token_from_header(raw_header)
|
||||||
tokens = Token.filter_not_expired(
|
if not token:
|
||||||
token_uuid=token_uuid, intent=TokenIntents.INTENT_API
|
LOGGER.warning("Failed to authenticate")
|
||||||
)
|
|
||||||
if not tokens.exists():
|
|
||||||
LOGGER.warning("WS Request with invalid token")
|
|
||||||
self.close()
|
self.close()
|
||||||
return False
|
return False
|
||||||
except ValidationError:
|
|
||||||
LOGGER.warning("WS Invalid UUID")
|
self.user = token.user
|
||||||
self.close()
|
|
||||||
return False
|
|
||||||
self.user = tokens.first().user
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
22
passbook/core/forms/token.py
Normal file
22
passbook/core/forms/token.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""Core user token form"""
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from passbook.core.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
class UserTokenForm(forms.ModelForm):
|
||||||
|
"""Token form, for tokens created by endusers"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
model = Token
|
||||||
|
fields = [
|
||||||
|
"identifier",
|
||||||
|
"expires",
|
||||||
|
"expiring",
|
||||||
|
"description",
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
"identifier": forms.TextInput(),
|
||||||
|
"description": forms.TextInput(),
|
||||||
|
}
|
||||||
50
passbook/core/migrations/0014_auto_20201018_1158.py
Normal file
50
passbook/core/migrations/0014_auto_20201018_1158.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-10-18 11:58
|
||||||
|
from django.apps.registry import Apps
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
import passbook.core.models
|
||||||
|
|
||||||
|
|
||||||
|
def set_default_token_key(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
Token = apps.get_model("passbook_core", "Token")
|
||||||
|
|
||||||
|
for token in Token.objects.using(db_alias).all():
|
||||||
|
token.key = token.pk.hex
|
||||||
|
token.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("passbook_core", "0013_auto_20201003_2132"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="token",
|
||||||
|
name="key",
|
||||||
|
field=models.TextField(default=passbook.core.models.default_token_key),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="token",
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="token",
|
||||||
|
name="identifier",
|
||||||
|
field=models.SlugField(max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="token",
|
||||||
|
index=models.Index(fields=["key"], name="passbook_co_key_e45007_idx"),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="token",
|
||||||
|
index=models.Index(
|
||||||
|
fields=["identifier"], name="passbook_co_identif_1a34a8_idx"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(set_default_token_key),
|
||||||
|
]
|
||||||
@ -32,6 +32,11 @@ def default_token_duration():
|
|||||||
return now() + timedelta(minutes=30)
|
return now() + timedelta(minutes=30)
|
||||||
|
|
||||||
|
|
||||||
|
def default_token_key():
|
||||||
|
"""Default token key"""
|
||||||
|
return uuid4().hex
|
||||||
|
|
||||||
|
|
||||||
class Group(models.Model):
|
class Group(models.Model):
|
||||||
"""Custom Group model which supports a basic hierarchy"""
|
"""Custom Group model which supports a basic hierarchy"""
|
||||||
|
|
||||||
@ -274,10 +279,8 @@ class ExpiringModel(models.Model):
|
|||||||
def filter_not_expired(cls, **kwargs) -> QuerySet:
|
def filter_not_expired(cls, **kwargs) -> QuerySet:
|
||||||
"""Filer for tokens which are not expired yet or are not expiring,
|
"""Filer for tokens which are not expired yet or are not expiring,
|
||||||
and match filters in `kwargs`"""
|
and match filters in `kwargs`"""
|
||||||
query = Q(**kwargs)
|
expired = Q(expires__lt=now(), expiring=True)
|
||||||
query_not_expired_yet = Q(expires__lt=now(), expiring=True)
|
return cls.objects.exclude(expired).filter(**kwargs)
|
||||||
query_not_expiring = Q(expiring=False)
|
|
||||||
return cls.objects.filter(query & (query_not_expired_yet | query_not_expiring))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_expired(self) -> bool:
|
def is_expired(self) -> bool:
|
||||||
@ -298,6 +301,7 @@ class TokenIntents(models.TextChoices):
|
|||||||
# Allow access to API
|
# Allow access to API
|
||||||
INTENT_API = "api"
|
INTENT_API = "api"
|
||||||
|
|
||||||
|
# Recovery use for the recovery app
|
||||||
INTENT_RECOVERY = "recovery"
|
INTENT_RECOVERY = "recovery"
|
||||||
|
|
||||||
|
|
||||||
@ -305,7 +309,8 @@ class Token(ExpiringModel):
|
|||||||
"""Token used to authenticate the User for API Access or confirm another Stage like Email."""
|
"""Token used to authenticate the User for API Access or confirm another Stage like Email."""
|
||||||
|
|
||||||
token_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
token_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||||
identifier = models.TextField()
|
identifier = models.SlugField(max_length=255)
|
||||||
|
key = models.TextField(default=default_token_key)
|
||||||
intent = models.TextField(
|
intent = models.TextField(
|
||||||
choices=TokenIntents.choices, default=TokenIntents.INTENT_VERIFICATION
|
choices=TokenIntents.choices, default=TokenIntents.INTENT_VERIFICATION
|
||||||
)
|
)
|
||||||
@ -313,13 +318,19 @@ class Token(ExpiringModel):
|
|||||||
description = models.TextField(default="", blank=True)
|
description = models.TextField(default="", blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Token {self.identifier} (expires={self.expires})"
|
description = f"{self.identifier}"
|
||||||
|
if self.expiring:
|
||||||
|
description += f" (expires={self.expires})"
|
||||||
|
return description
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
||||||
verbose_name = _("Token")
|
verbose_name = _("Token")
|
||||||
verbose_name_plural = _("Tokens")
|
verbose_name_plural = _("Tokens")
|
||||||
unique_together = (("identifier", "user"),)
|
indexes = [
|
||||||
|
models.Index(fields=["identifier"]),
|
||||||
|
models.Index(fields=["key"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class PropertyMapping(models.Model):
|
class PropertyMapping(models.Model):
|
||||||
|
|||||||
@ -1,16 +1,26 @@
|
|||||||
"""passbook core tasks"""
|
"""passbook core tasks"""
|
||||||
|
from datetime import datetime
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
from boto3.exceptions import Boto3Error
|
||||||
|
from botocore.exceptions import BotoCoreError, ClientError
|
||||||
|
from dbbackup.db.exceptions import CommandConnectorError
|
||||||
|
from django.contrib.humanize.templatetags.humanize import naturaltime
|
||||||
|
from django.core import management
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import ExpiringModel
|
from passbook.core.models import ExpiringModel
|
||||||
|
from passbook.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
|
||||||
from passbook.root.celery import CELERY_APP
|
from passbook.root.celery import CELERY_APP
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
@CELERY_APP.task()
|
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||||
def clean_expired_models():
|
def clean_expired_models(self: MonitoredTask):
|
||||||
"""Remove expired objects"""
|
"""Remove expired objects"""
|
||||||
|
messages = []
|
||||||
for cls in ExpiringModel.__subclasses__():
|
for cls in ExpiringModel.__subclasses__():
|
||||||
cls: ExpiringModel
|
cls: ExpiringModel
|
||||||
amount, _ = (
|
amount, _ = (
|
||||||
@ -20,3 +30,34 @@ def clean_expired_models():
|
|||||||
.delete()
|
.delete()
|
||||||
)
|
)
|
||||||
LOGGER.debug("Deleted expired models", model=cls, amount=amount)
|
LOGGER.debug("Deleted expired models", model=cls, amount=amount)
|
||||||
|
messages.append(f"Deleted {amount} expired {cls._meta.verbose_name_plural}")
|
||||||
|
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages))
|
||||||
|
|
||||||
|
|
||||||
|
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||||
|
def backup_database(self: MonitoredTask): # pragma: no cover
|
||||||
|
"""Database backup"""
|
||||||
|
self.result_timeout_hours = 25
|
||||||
|
try:
|
||||||
|
start = datetime.now()
|
||||||
|
out = StringIO()
|
||||||
|
management.call_command("dbbackup", quiet=True, stdout=out)
|
||||||
|
self.set_status(
|
||||||
|
TaskResult(
|
||||||
|
TaskResultStatus.SUCCESSFUL,
|
||||||
|
[
|
||||||
|
f"Successfully finished database backup {naturaltime(start)}",
|
||||||
|
out.getvalue(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
LOGGER.info("Successfully backed up database.")
|
||||||
|
except (
|
||||||
|
IOError,
|
||||||
|
BotoCoreError,
|
||||||
|
ClientError,
|
||||||
|
Boto3Error,
|
||||||
|
PermissionError,
|
||||||
|
CommandConnectorError,
|
||||||
|
) as exc:
|
||||||
|
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
{% load passbook_utils %}
|
{% load passbook_utils %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% include 'partials/messages.html' %}
|
<pb-messages url="{% url 'passbook_api:messages-list' %}"></pb-messages>
|
||||||
<div class="pf-c-page" id="page-default-nav-example">
|
<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>
|
<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">
|
<header role="banner" class="pf-c-page__header ws-page-header">
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<a href="{% url 'passbook_core:overview' %}" class="pf-c-page__header-brand-link">
|
<a href="{% url 'passbook_core:overview' %}" class="pf-c-page__header-brand-link">
|
||||||
<div class="pf-c-brand pb-brand">
|
<div class="pf-c-brand pb-brand">
|
||||||
<img src="{{ config.passbook.branding.logo }}" alt="passbook icon">
|
<img src="{{ config.passbook.branding.logo }}" style="width: 100px;" alt="passbook icon">
|
||||||
{% if config.passbook.branding.title_show %}
|
{% if config.passbook.branding.title_show %}
|
||||||
<small><small>{{ config.passbook.branding.title }}</small></small>
|
<small><small>{{ config.passbook.branding.title }}</small></small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -53,7 +53,7 @@
|
|||||||
{{ user.username }}
|
{{ user.username }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<img class="pf-c-avatar" src="{% gravatar user.email %}" alt="">
|
<img class="pf-c-avatar" src="{% avatar user %}" alt="">
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{% extends "administration/base.html" %}
|
{% extends container_template|default:"administration/base.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load passbook_utils %}
|
{% load passbook_utils %}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% include 'partials/messages.html' %}
|
<pb-messages url="{% url 'passbook_api:messages-list' %}"></pb-messages>
|
||||||
|
|
||||||
<header class="pf-c-login__main-header">
|
<header class="pf-c-login__main-header">
|
||||||
<h1 class="pf-c-title pf-m-3xl">
|
<h1 class="pf-c-title pf-m-3xl">
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
</filter>
|
</filter>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
{% include 'partials/messages.html' %}
|
<pb-messages url="{% url 'passbook_api:messages-list' %}"></pb-messages>
|
||||||
<div class="pf-c-login">
|
<div class="pf-c-login">
|
||||||
<div class="pf-c-login__container">
|
<div class="pf-c-login__container">
|
||||||
<header class="pf-c-login__header">
|
<header class="pf-c-login__header">
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<div class="pf-c-form__group">
|
<div class="pf-c-form__group">
|
||||||
<div class="form-control-static">
|
<div class="form-control-static">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<img class="pf-c-avatar" src="{% gravatar user.email %}" alt="">
|
<img class="pf-c-avatar" src="{% avatar user %}" alt="">
|
||||||
{{ user.username }}
|
{{ user.username }}
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
<ul class="pf-c-alert-group pf-m-toast">
|
|
||||||
{% for msg in messages %}
|
|
||||||
<li class="pf-c-alert-group__item">
|
|
||||||
<div class="pf-c-alert pf-m-{{ msg.level_tag }} {% if msg.level_tag == 'error' %}pf-m-danger{% endif %}">
|
|
||||||
<div class="pf-c-alert__icon">
|
|
||||||
{% if msg.level_tag == 'error' %}
|
|
||||||
<i class="fas fa-exclamation-circle"></i>
|
|
||||||
{% elif msg.level_tag == 'warning' %}
|
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
|
||||||
{% elif msg.level_tag == 'success' %}
|
|
||||||
<i class="fas fa-check-circle"></i>
|
|
||||||
{% elif msg.level_tag == 'info' %}
|
|
||||||
<i class="fas fa-info"></i>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<h4 class="pf-c-alert__title">
|
|
||||||
{{ msg.message|safe }}
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user