Compare commits

...

31 Commits

Author SHA1 Message Date
8a105cf5a0 release: 0.12.11-stable 2020-11-16 00:21:56 +01:00
9e384df79e static: adjust sizing of icon in navbar 2020-11-15 22:57:33 +01:00
c0bfd32d39 root: update remaining paths for static files 2020-11-15 22:46:14 +01:00
7be680cbe5 Migrate to Docusaurus (#329)
* docs: initial migration to docusaurus

* website: add custom font, update blurbs and icons

* website: update splash

* root: update links to docs

* flows: use .pbflow extension so docusaurus doesn't mangle the files

* e2e: workaround prospector

* Squashed commit of the following:

commit 1248585dca
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Sun Nov 15 20:46:53 2020 +0100

    e2e: attempt to fix prospector error again

commit 1319c480c4
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Sun Nov 15 20:41:35 2020 +0100

    ci: install previous python version for upgrade testing

* web: update accent colours and format

* website: format markdown files

* website: fix colours for text

* website: switch to temporary accent colour to improve readability

* flows: fix path for TestTransferDocs

* flows: fix formatting of tests
2020-11-15 22:42:02 +01:00
93bf8eaa82 root: fix useless supressions 2020-11-15 21:02:15 +01:00
1248585dca e2e: attempt to fix prospector error again 2020-11-15 20:46:53 +01:00
1319c480c4 ci: install previous python version for upgrade testing 2020-11-15 20:41:35 +01:00
1911e8e3a9 e2e: fix linting error 2020-11-15 19:31:42 +01:00
4198c5363f proxy: bump dependencies 2020-11-15 16:35:46 +01:00
207aae15a8 root: add missing libraries for static docker container 2020-11-15 16:17:25 +01:00
50531b8a36 root: upgrade to python3.9 2020-11-15 16:15:01 +01:00
e5e4824920 */saml: fully migrate to xmlsec, remove signxml dependency 2020-11-15 15:20:56 +01:00
085247e2dc ci: fix apt install for libxmlsec1 2020-11-15 01:05:20 +01:00
f766594ab0 build(deps): bump urllib3 from 1.25.11 to 1.26.2 (#327)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.11 to 1.26.2.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/master/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.25.11...1.26.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-13 17:42:44 +01:00
d1e469e282 build(deps): bump boto3 from 1.16.16 to 1.16.17 (#328)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.16 to 1.16.17.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.16.16...1.16.17)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-13 11:44:14 +01:00
79e4500827 build(deps): bump sentry-sdk from 0.19.2 to 0.19.3 (#326)
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 0.19.2 to 0.19.3.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGES.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/0.19.2...0.19.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-13 06:55:47 +01:00
42702fa96a root: fix missing libxmlsec1-dev pkg-config packages 2020-11-12 19:38:58 +01:00
9deb3ad80f sources/saml: make signature and digest of SAML Source configurable 2020-11-12 12:02:57 +01:00
9877ef99c4 */saml: fix creation and validation of detached signatures 2020-11-12 11:59:07 +01:00
c304b40e1b providers/saml: improve verification for detached signatures 2020-11-12 11:58:51 +01:00
f0e6d6f417 root: fix asgi import order 2020-11-12 11:58:51 +01:00
54de5c981e providers/saml: fix signatures being required 2020-11-12 11:58:51 +01:00
a446775fe2 build(deps): bump boto3 from 1.16.15 to 1.16.16 (#325)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.15 to 1.16.16.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.16.15...1.16.16)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-12 11:57:23 +01:00
7393d8720b new release: 0.12.10-stable 2020-11-11 14:54:29 +01:00
287cb72d6f root: fix websockets not working correctly 2020-11-11 14:51:26 +01:00
c5eff4bdd6 outposts: fix selection of outpost's service connection not showing name 2020-11-11 14:34:43 +01:00
e9a33ed8ab root: fix exclusion in dockerignore 2020-11-11 14:24:43 +01:00
875173a86e outposts: fix migration error 2020-11-11 14:10:15 +01:00
df7642b365 build(deps): bump boto3 from 1.16.14 to 1.16.15 (#324)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.14 to 1.16.15.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.16.14...1.16.15)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-11 09:54:19 +01:00
3bc1c0aa8b build(deps): bump channels from 3.0.1 to 3.0.2 (#322)
Bumps [channels](https://github.com/django/channels) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/django/channels/releases)
- [Changelog](https://github.com/django/channels/blob/master/CHANGELOG.txt)
- [Commits](https://github.com/django/channels/compare/3.0.1...3.0.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jens L <jens@beryju.org>
2020-11-10 23:41:29 +01:00
8951f5695e build(deps): bump boto3 from 1.16.13 to 1.16.14 (#323)
Bumps [boto3](https://github.com/boto/boto3) from 1.16.13 to 1.16.14.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.16.13...1.16.14)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-11-10 21:50:42 +01:00
183 changed files with 16703 additions and 1391 deletions

View File

@ -1,10 +1,10 @@
[bumpversion]
current_version = 0.12.9-stable
current_version = 0.12.11-stable
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
serialize = {major}.{minor}.{patch}-{release}
message = new release: {new_version}
message = release: {new_version}
tag_name = version/{new_version}
[bumpversion:part:release]
@ -15,9 +15,9 @@ values =
beta
stable
[bumpversion:file:docs/installation/docker-compose.md]
[bumpversion:file:website/docs/installation/docker-compose.md]
[bumpversion:file:docs/installation/kubernetes.md]
[bumpversion:file:website/docs/installation/kubernetes.md]
[bumpversion:file:docker-compose.yml]

View File

@ -6,7 +6,7 @@ omit =
manage.py
*/migrations/*
*/apps.py
docs/
website/
[report]
sort = Cover

View File

@ -3,4 +3,4 @@ helm
passbook-ui
static
*.env.yml
node_modules/
**/node_modules

View File

@ -18,11 +18,11 @@ jobs:
- name: Building Docker Image
run: docker build
--no-cache
-t beryju/passbook:0.12.9-stable
-t beryju/passbook:0.12.11-stable
-t beryju/passbook:latest
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook:0.12.9-stable
run: docker push beryju/passbook:0.12.11-stable
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook:latest
build-proxy:
@ -48,11 +48,11 @@ jobs:
cd proxy
docker build \
--no-cache \
-t beryju/passbook-proxy:0.12.9-stable \
-t beryju/passbook-proxy:0.12.11-stable \
-t beryju/passbook-proxy:latest \
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-proxy:0.12.9-stable
run: docker push beryju/passbook-proxy:0.12.11-stable
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-proxy:latest
build-static:
@ -77,11 +77,11 @@ jobs:
run: docker build
--no-cache
--network=$(docker network ls | grep github | awk '{print $1}')
-t beryju/passbook-static:0.12.9-stable
-t beryju/passbook-static:0.12.11-stable
-t beryju/passbook-static:latest
-f static.Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-static:0.12.9-stable
run: docker push beryju/passbook-static:0.12.11-stable
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-static:latest
test-release:
@ -114,5 +114,5 @@ jobs:
SENTRY_PROJECT: passbook
SENTRY_URL: https://sentry.beryju.org
with:
tagName: 0.12.9-stable
tagName: 0.12.11-stable
environment: beryjuorg-prod

1
.gitignore vendored
View File

@ -199,3 +199,4 @@ local.env.yml
# Selenium Screenshots
selenium_screenshots/**
backups/

View File

@ -9,3 +9,4 @@ ignore-paths:
uses:
- django
- celery

View File

@ -1,16 +1,29 @@
[MASTER]
disable=arguments-differ,no-self-use,fixme,locally-disabled,too-many-ancestors,too-few-public-methods,import-outside-toplevel,bad-continuation,signature-differs,similarities,cyclic-import
disable =
arguments-differ,
no-self-use,
fixme,
locally-disabled,
too-many-ancestors,
too-few-public-methods,
import-outside-toplevel,
bad-continuation,
signature-differs,
similarities,
cyclic-import,
protected-access,
unsubscriptable-object # remove when pylint is upgraded to 2.6
load-plugins=pylint_django,pylint.extensions.bad_builtin
extension-pkg-whitelist=lxml
extension-pkg-whitelist=lxml,xmlsec
# Allow constants to be shorter than normal (and lowercase, for settings.py)
const-rgx=[a-zA-Z0-9_]{1,40}$
ignored-modules=django-otp
generated-members=xmlsec.constants.*,xmlsec.tree.*,xmlsec.template.*
ignore=migrations
max-attributes=12
jobs=12
max-branches=20

View File

@ -1,4 +1,4 @@
FROM python:3.8-slim-buster as locker
FROM python:3.9-slim-buster as locker
COPY ./Pipfile /app/
COPY ./Pipfile.lock /app/
@ -9,7 +9,7 @@ RUN pip install pipenv && \
pipenv lock -r > requirements.txt && \
pipenv lock -rd > requirements-dev.txt
FROM python:3.8-slim-buster
FROM python:3.9-slim-buster
WORKDIR /
COPY --from=locker /app/requirements.txt /
@ -20,7 +20,7 @@ RUN apt-get update && \
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
echo "deb http://apt.postgresql.org/pub/repos/apt buster-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \
apt-get update && \
apt-get install -y --no-install-recommends postgresql-client-12 postgresql-client-11 build-essential && \
apt-get install -y --no-install-recommends postgresql-client-12 postgresql-client-11 build-essential libxmlsec1-dev pkg-config && \
apt-get clean && \
pip install -r /requirements.txt --no-cache-dir && \
apt-get remove --purge -y build-essential && \
@ -41,5 +41,6 @@ COPY ./manage.py /
COPY ./lifecycle/ /lifecycle
USER passbook
STOPSIGNAL SIGINT
ENTRYPOINT [ "/lifecycle/bootstrap.sh" ]

View File

@ -35,7 +35,6 @@ qrcode = "*"
requests-oauthlib = "*"
sentry-sdk = "*"
service_identity = "*"
signxml = "*"
structlog = "*"
swagger-spec-validator = "*"
urllib3 = {extras = ["secure"],version = "*"}
@ -44,9 +43,10 @@ channels = "*"
channels-redis = "*"
kubernetes = "*"
docker = "*"
xmlsec = "*"
[requires]
python_version = "3.8"
python_version = "3.9"
[dev-packages]
autopep8 = "*"

379
Pipfile.lock generated
View File

@ -1,11 +1,11 @@
{
"_meta": {
"hash": {
"sha256": "d1a9883d864e25f18e34b298b72b58db333a037571c7a20cefb7ba7a4037a434"
"sha256": "53ad00e394a5f2e2462837c4ceff837d1e593469af9505726048bed72ce0b81a"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
"python_version": "3.9"
},
"sources": [
{
@ -28,20 +28,23 @@
"sha256:5b9062d5c0812335c75434bf17ce33d7a20ecfedaa0733faec7379868eb4068a",
"sha256:fcd5b3baeeb7fc19b3486ff6d10543099d40ae1f5c9196eae695d1cde1b2f784"
],
"markers": "python_version >= '3.6'",
"version": "==5.0.2"
},
"asgiref": {
"hashes": [
"sha256:a5098bc870b80e7b872bff60bb363c7f2c2c89078759f6c47b53ff8c525a152e",
"sha256:cd88907ecaec59d78e4ac00ea665b03e571cb37e3a0e37b3702af1a9e86c365a"
"sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17",
"sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"
],
"version": "==3.3.0"
"markers": "python_version >= '3.5'",
"version": "==3.3.1"
},
"async-timeout": {
"hashes": [
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
],
"markers": "python_full_version >= '3.5.3'",
"version": "==3.0.1"
},
"attrs": {
@ -49,6 +52,7 @@
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.3.0"
},
"autobahn": {
@ -56,6 +60,7 @@
"sha256:24ce276d313e84d68241c3aef30d484f352b90a40168981b3640312c821df77b",
"sha256:86bbce30cdd407137c57670993a8f9bfdfe3f8e994b889181d85e844d5aa8dfb"
],
"markers": "python_version >= '3.5'",
"version": "==20.7.1"
},
"automat": {
@ -74,24 +79,25 @@
},
"boto3": {
"hashes": [
"sha256:23d2575b7bd01c4e7153f283c1d1c12d329dabf78a6279d4192f2e752bb67b1a",
"sha256:cb3f4c2f2576153b845e5b4f325d54a04f4ca00c156f2063965432bfa5d714dd"
"sha256:51c419d890ae216b9b031be31f3182739dc3deb5b64351f286bffca2818ddb35",
"sha256:d70d21ea137d786e84124639a62be42f92f4b09472ebfb761156057c92dc5366"
],
"index": "pypi",
"version": "==1.16.13"
"version": "==1.16.18"
},
"botocore": {
"hashes": [
"sha256:1b1b4cf5efd552ecc7f1ce0fc674d5fba56857db5ffe6394ee76edd1a568d7a6",
"sha256:b3b4b8fa33620f015c52e426a92e7db21b5e667ed4785c5fbc484ebfdb2b5153"
"sha256:288d43e85f12e3c1d6a0535a585a182ca04e8c6e742ebaaf15357a0e3b37ca7a",
"sha256:bba18b5c4eef3eb2dc39b1b1f8959ba01ac27e7e12e413e281b0fb242990c0f5"
],
"version": "==1.19.13"
"version": "==1.19.18"
},
"cachetools": {
"hashes": [
"sha256:513d4ff98dd27f85743a8dc0e92f55ddb1b49e060c2d5961512855cda2c01a98",
"sha256:bbaa39c3dede00175df2dc2b03d0cf18dd2d32a7de7beb68072d13043c9edb20"
],
"markers": "python_version ~= '3.5'",
"version": "==4.1.1"
},
"celery": {
@ -152,11 +158,11 @@
},
"channels": {
"hashes": [
"sha256:5cdd9c6b9ee663cdf1bbb00de7cdab885a3c418f9d32a29f04b09498828020f6",
"sha256:b02e150b48704ec3607d4168402ac5c26138dd183fcdb7f2aeb965e6e19fd558"
"sha256:74db79c9eca616be69d38013b22083ab5d3f9ccda1ab5e69096b1bb7da2d9b18",
"sha256:f50a6e79757a64c1e45e95e144a2ac5f1e99ee44a0718ab182c501f5e5abd268"
],
"index": "pypi",
"version": "==3.0.1"
"version": "==3.0.2"
},
"channels-redis": {
"hashes": [
@ -178,6 +184,7 @@
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2"
},
"click-didyoumean": {
@ -251,10 +258,11 @@
},
"daphne": {
"hashes": [
"sha256:60856f7efa0b1e1b969efa074e8698bd09de4713ecc06e6a4d19d04c66c4a3bd",
"sha256:b43e70d74ff832a634ff6c92badd208824e4530e08b340116517e5aad0aca774"
"sha256:0052c9887600c57054a5867d4b0240159fa009faa3bcf6a1627271d9cdcb005a",
"sha256:c22b692707f514de9013651ecb687f2abe4f35cf6fe292ece634e9f1737bc7e3"
],
"version": "==3.0.0"
"markers": "python_version >= '3.6'",
"version": "==3.0.1"
},
"defusedxml": {
"hashes": [
@ -381,13 +389,6 @@
"index": "pypi",
"version": "==1.19.4"
},
"eight": {
"hashes": [
"sha256:0a7f0e7725f2a478a97676cf9c49266d95f922f8ed621ec314eeccb333927dc2",
"sha256:d148aa1fac6cafb5ff806ff634914b05e3f9357aa8dbd82cd7908821d7f93f43"
],
"version": "==1.0.0"
},
"facebook-sdk": {
"hashes": [
"sha256:2e987b3e0f466a6f4ee77b935eb023dba1384134f004a2af21f1cfff7fe0806e",
@ -400,6 +401,7 @@
"hashes": [
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.18.2"
},
"google-auth": {
@ -407,6 +409,7 @@
"sha256:5176db85f1e7e837a646cd9cede72c3c404ccf2e3373d9ee14b2db88febad440",
"sha256:b728625ff5dfce8f9e56a499c8a4eb51443a67f20f6d28b67d5774c310ec4b6b"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.23.0"
},
"gunicorn": {
@ -473,6 +476,7 @@
"sha256:e64be68255234bb489a574c4f2f8df7029c98c81ec4d160d6cd836e7f0679390",
"sha256:e82d6b930e02e80e5109b678c663a9ed210680ded81c1abaf54635d88d1da298"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.0"
},
"httptools": {
@ -518,6 +522,7 @@
"sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417",
"sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"
],
"markers": "python_version >= '3.5'",
"version": "==0.5.1"
},
"itypes": {
@ -532,6 +537,7 @@
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.11.2"
},
"jmespath": {
@ -539,6 +545,7 @@
"sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9",
"sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.0"
},
"jsonschema": {
@ -553,20 +560,24 @@
"sha256:6dc509178ac4269b0e66ab4881f70a2035c33d3a622e20585f965986a5182006",
"sha256:f4965fba0a4718d47d470beeb5d6446e3357a62402b16c510b6a2f251e05ac3c"
],
"markers": "python_version >= '3.6'",
"version": "==5.0.2"
},
"kubernetes": {
"hashes": [
"sha256:72f095a1cd593401ff26b3b8d71749340394ca6d8413770ea28ce18efd5bcf4c",
"sha256:9a339a32d6c79e6461cb6050c3662cb4e33058b508d8d34ee5d5206add395828"
"sha256:23c85d8571df8f56e773f1a413bc081537536dc47e2b5e8dc2e6262edb2c57ca",
"sha256:ec52ea01d52e2ec3da255992f7e859f3a76f2bdb51cf65ba8cd71dfc309d8daa"
],
"index": "pypi",
"version": "==12.0.0"
"version": "==12.0.1"
},
"ldap3": {
"hashes": [
"sha256:10bdd23b612e942ce90ea4dbc744dfd88735949833e46c5467a2dcf68e60f469",
"sha256:37d633e20fa360c302b1263c96fe932d40622d0119f1bddcb829b03462eeeeb7",
"sha256:7c3738570766f5e5e74a56fade15470f339d5c436d821cf476ef27da0a4de8b0"
"sha256:7c3738570766f5e5e74a56fade15470f339d5c436d821cf476ef27da0a4de8b0",
"sha256:8f59a7b5399555b22db06f153daa76c77ded2dd84bc0f0ffe5b0b33901b6eac4",
"sha256:bed71c6ce2f70a00a330eed0c8370664c065239d45bcbe1b82517b6f6eed7f25"
],
"index": "pypi",
"version": "==2.8.1"
@ -650,6 +661,7 @@
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.1"
},
"msgpack": {
@ -680,6 +692,7 @@
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
"sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.1.0"
},
"packaging": {
@ -702,60 +715,83 @@
"sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c",
"sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"
],
"markers": "python_full_version >= '3.6.1'",
"version": "==3.0.8"
},
"psycopg2-binary": {
"hashes": [
"sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c",
"sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67",
"sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0",
"sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1",
"sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6",
"sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db",
"sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94",
"sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52",
"sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4",
"sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056",
"sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b",
"sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd",
"sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550",
"sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679",
"sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94",
"sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83",
"sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77",
"sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2",
"sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77",
"sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2",
"sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550",
"sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c",
"sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd",
"sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859",
"sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1",
"sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25",
"sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152",
"sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf",
"sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f",
"sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729",
"sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71",
"sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66",
"sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4",
"sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449",
"sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da",
"sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a",
"sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52",
"sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71",
"sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679",
"sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2",
"sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c",
"sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2",
"sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152",
"sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db",
"sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a",
"sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0",
"sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729",
"sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb",
"sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77",
"sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd",
"sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f",
"sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77",
"sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4",
"sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5"
"sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5",
"sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf",
"sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67",
"sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da",
"sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859",
"sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25",
"sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449"
],
"index": "pypi",
"version": "==2.8.6"
},
"pyasn1": {
"hashes": [
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
],
"version": "==0.4.8"
},
"pyasn1-modules": {
"hashes": [
"sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
"sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
"sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
"sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
"sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
"sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb",
"sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
"sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
"sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
"sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
"sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"
],
"version": "==0.2.8"
},
@ -764,6 +800,7 @@
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.20"
},
"pycryptodome": {
@ -845,6 +882,7 @@
"sha256:f20a62397e09704049ce9007bea4f6bad965ba9336a760c6f4ef1b4192e12d6d",
"sha256:f81f7311250d9480e36dec819127897ae772e7e8de07abfabe931b8566770b8e"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.9.9"
},
"pyhamcrest": {
@ -852,6 +890,7 @@
"sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316",
"sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29"
],
"markers": "python_version >= '3.5'",
"version": "==2.0.2"
},
"pyjwkest": {
@ -873,12 +912,14 @@
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pyrsistent": {
"hashes": [
"sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"
],
"markers": "python_version >= '3.5'",
"version": "==0.17.3"
},
"python-dateutil": {
@ -886,6 +927,7 @@
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1"
},
"python-dotenv": {
@ -932,19 +974,22 @@
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
"sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==3.5.3"
},
"requests": {
"hashes": [
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
],
"version": "==2.24.0"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.0"
},
"requests-oauthlib": {
"hashes": [
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
"sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
],
"index": "pypi",
"version": "==1.3.0"
@ -964,36 +1009,6 @@
],
"version": "==0.16.12"
},
"ruamel.yaml.clib": {
"hashes": [
"sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b",
"sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91",
"sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc",
"sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7",
"sha256:30dca9bbcbb1cc858717438218d11eafb78666759e5094dd767468c0d577a7e7",
"sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6",
"sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6",
"sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0",
"sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62",
"sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99",
"sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5",
"sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026",
"sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2",
"sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1",
"sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b",
"sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e",
"sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c",
"sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988",
"sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f",
"sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5",
"sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a",
"sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1",
"sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2",
"sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"
],
"markers": "platform_python_implementation == 'CPython' and python_version < '3.9'",
"version": "==0.2.2"
},
"s3transfer": {
"hashes": [
"sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13",
@ -1003,11 +1018,11 @@
},
"sentry-sdk": {
"hashes": [
"sha256:17b725df2258354ccb39618ae4ead29651aa92c01a92acf72f98efe06ee2e45a",
"sha256:9040539485226708b5cad0401d76628fba4eed9154bf301c50579767afe344fd"
"sha256:81d7a5d8ca0b13a16666e8280127b004565aa988bfeec6481e98a8601804b215",
"sha256:fd48f627945511c140546939b4d73815be4860cd1d2b9149577d7f6563e7bd60"
],
"index": "pypi",
"version": "==0.19.2"
"version": "==0.19.3"
},
"service-identity": {
"hashes": [
@ -1017,19 +1032,12 @@
"index": "pypi",
"version": "==18.1.0"
},
"signxml": {
"hashes": [
"sha256:b70e151d10d99cbc74a50a3344f508ee481fe3c376d61cd1cae850912d303d19",
"sha256:bab03a6823c9a5b225d1e6266ce66b5d08c4ebfb42029fdb5d3e588b8128c86d"
],
"index": "pypi",
"version": "==2.8.1"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"sqlparse": {
@ -1037,6 +1045,7 @@
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
],
"markers": "python_version >= '3.5'",
"version": "==0.4.1"
},
"structlog": {
@ -1084,6 +1093,7 @@
"sha256:f058bd0168271de4dcdc39845b52dd0a4a2fecf5f1246335f13f5e96eaebb467",
"sha256:f3c19e5bd42bbe4bf345704ad7c326c74d3fd7a1b3844987853bef180be638d4"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==20.3.0"
},
"txaio": {
@ -1091,6 +1101,7 @@
"sha256:17938f2bca4a9cabce61346758e482ca4e600160cbc28e861493eac74a19539d",
"sha256:38a469daf93c37e5527cb062653d6393ae11663147c42fab7ddc3f6d00d434ae"
],
"markers": "python_version >= '3.5'",
"version": "==20.4.1"
},
"uritemplate": {
@ -1098,6 +1109,7 @@
"sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f",
"sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.0.1"
},
"urllib3": {
@ -1105,12 +1117,11 @@
"secure"
],
"hashes": [
"sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
"sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
],
"index": "pypi",
"markers": null,
"version": "==1.25.11"
"version": "==1.26.2"
},
"uvicorn": {
"extras": [
@ -1142,6 +1153,7 @@
"sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30",
"sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"
],
"markers": "python_version >= '3.6'",
"version": "==5.0.0"
},
"watchgod": {
@ -1192,6 +1204,25 @@
],
"version": "==8.1"
},
"xmlsec": {
"hashes": [
"sha256:252f79ed4482d6eefcca62c3bfc99b8d95c07abd846262d854a207ec4d67fac5",
"sha256:31884dc97cc34cf1681a0f239f613969e61f9a01f4c2d2a62e53d68216fe42d6",
"sha256:32a669dfe447bccecdb4ef79221c0452ce6dad919f3a75daf512792141a54dac",
"sha256:3d13d7b6cb921dbc4d60d00ad00081a038df73a1e69f5bcc3695deb1bf2093b0",
"sha256:5e2f263a21fd146859911479ec35e40a57f519e650f56c775f91367d2a1b6e15",
"sha256:61076be98da4c7cf842a78aa3f129a5039f2ba4992e02480eefe78028d317698",
"sha256:69d7f965d6b74b3266f7baa99a0377d9c76acbf26c615b4ee8d2cbe17bf85528",
"sha256:6d8bb24c3a4db398011f394e29b58cd34c9c26d76b772c5d418d8579df127234",
"sha256:6d9d46d1f6b4985023469a1e334cb35c7c8fc6bd9d8b65ca52b923a7a6869c2a",
"sha256:8a7ffdc4f7f760253aa4dd8d2037358eb33915ca1dcf1c2422b19fcf0ab68506",
"sha256:927fc5755bb93dc09275bd5d818811e016290c194012d63f8e6f86b7ece3e468",
"sha256:dcaa084c3700f775eba09d81a1432444f82d9ad6270320c56c1a733d71cceb3a",
"sha256:f59698cc0366395ca79b48b080674973541aae290670c57d88f05d939a4c00da"
],
"index": "pypi",
"version": "==1.3.9"
},
"zope.interface": {
"hashes": [
"sha256:05a97ba92c1c7c26f25c9f671aa1ef85ffead6cdad13770e5b689cf983adc7e1",
@ -1247,6 +1278,7 @@
"sha256:f37d45fab14ffef9d33a0dc3bc59ce0c5313e2253323312d47739192da94f5fd",
"sha256:f44906f70205d456d503105023041f1e63aece7623b31c390a0103db4de17537"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==5.2.0"
}
},
@ -1260,16 +1292,18 @@
},
"asgiref": {
"hashes": [
"sha256:a5098bc870b80e7b872bff60bb363c7f2c2c89078759f6c47b53ff8c525a152e",
"sha256:cd88907ecaec59d78e4ac00ea665b03e571cb37e3a0e37b3702af1a9e86c365a"
"sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17",
"sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"
],
"version": "==3.3.0"
"markers": "python_version >= '3.5'",
"version": "==3.3.1"
},
"astroid": {
"hashes": [
"sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1",
"sha256:d8506842a3faf734b81599c8b98dcc423de863adcc1999248480b18bd31a0f38"
],
"markers": "python_version >= '3.5'",
"version": "==2.4.1"
},
"attrs": {
@ -1277,6 +1311,7 @@
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==20.3.0"
},
"autopep8": {
@ -1306,6 +1341,7 @@
"sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410",
"sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.1"
},
"bumpversion": {
@ -1321,6 +1357,7 @@
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2"
},
"colorama": {
@ -1399,6 +1436,7 @@
"sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839",
"sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.8.4"
},
"flake8-polyfill": {
@ -1413,6 +1451,7 @@
"sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac",
"sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"
],
"markers": "python_version >= '3.4'",
"version": "==4.0.5"
},
"gitpython": {
@ -1420,6 +1459,7 @@
"sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b",
"sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8"
],
"markers": "python_version >= '3.4'",
"version": "==3.1.11"
},
"iniconfig": {
@ -1434,6 +1474,7 @@
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==4.3.21"
},
"lazy-object-proxy": {
@ -1460,6 +1501,7 @@
"sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
"sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.4.3"
},
"mccabe": {
@ -1496,6 +1538,7 @@
"sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9",
"sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"
],
"markers": "python_version >= '2.6'",
"version": "==5.5.1"
},
"pep8-naming": {
@ -1510,20 +1553,22 @@
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1"
},
"prospector": {
"hashes": [
"sha256:43e5e187c027336b0e4c4aa6a82d66d3b923b5ec5b51968126132e32f9d14a2f"
"sha256:700d7918d93d73035a2a58fb18c6be0b609a0481fc6e0908843fa856b89e52c6"
],
"index": "pypi",
"version": "==1.3.0"
"version": "==1.3.1"
},
"py": {
"hashes": [
"sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
"sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.9.0"
},
"pycodestyle": {
@ -1531,6 +1576,7 @@
"sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
"sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.6.0"
},
"pydocstyle": {
@ -1538,6 +1584,7 @@
"sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325",
"sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"
],
"markers": "python_version >= '3.5'",
"version": "==5.1.1"
},
"pyflakes": {
@ -1545,15 +1592,16 @@
"sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
"sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.2.0"
},
"pylint": {
"hashes": [
"sha256:b95e31850f3af163c2283ed40432f053acbc8fc6eba6a069cb518d9dbf71848c",
"sha256:dd506acce0427e9e08fb87274bcaa953d38b50a58207170dbf5b36cf3e16957b"
"sha256:7dd78437f2d8d019717dbf287772d0b2dbdfd13fc016aa7faa08d67bccc46adc",
"sha256:d0ece7d223fe422088b0e8f13fa0a1e8eb745ebffcb8ed53d3e95394b6101a1c"
],
"index": "pypi",
"version": "==2.5.2"
"version": "==2.5.3"
},
"pylint-celery": {
"hashes": [
@ -1563,11 +1611,11 @@
},
"pylint-django": {
"hashes": [
"sha256:06a64331c498a3f049ba669dc0c174b92209e164198d43e589b1096ee616d5f8",
"sha256:3d3436ba8d0fae576ae2db160e33a8f2746a101fda4463f2b3ff3a8b6fccec38"
"sha256:b7756844dba0cecd3471056a1ef4154439defedaba38bf3ced9f848d2bf6297c",
"sha256:ca32277c77878dd3c2d9e75f3f3f7f0c0712f053f10ff1b946cdc27367a6c911"
],
"index": "pypi",
"version": "==2.0.15"
"version": "==2.1.0"
},
"pylint-flask": {
"hashes": [
@ -1587,6 +1635,7 @@
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pytest": {
@ -1631,51 +1680,49 @@
},
"regex": {
"hashes": [
"sha256:03855ee22980c3e4863dc84c42d6d2901133362db5daf4c36b710dd895d78f0a",
"sha256:06b52815d4ad38d6524666e0d50fe9173533c9cc145a5779b89733284e6f688f",
"sha256:11116d424734fe356d8777f89d625f0df783251ada95d6261b4c36ad27a394bb",
"sha256:119e0355dbdd4cf593b17f2fc5dbd4aec2b8899d0057e4957ba92f941f704bf5",
"sha256:127a9e0c0d91af572fbb9e56d00a504dbd4c65e574ddda3d45b55722462210de",
"sha256:1ec66700a10e3c75f1f92cbde36cca0d3aaee4c73dfa26699495a3a30b09093c",
"sha256:227a8d2e5282c2b8346e7f68aa759e0331a0b4a890b55a5cfbb28bd0261b84c0",
"sha256:2564def9ce0710d510b1fc7e5178ce2d20f75571f788b5197b3c8134c366f50c",
"sha256:297116e79074ec2a2f885d22db00ce6e88b15f75162c5e8b38f66ea734e73c64",
"sha256:2dc522e25e57e88b4980d2bdd334825dbf6fa55f28a922fc3bfa60cc09e5ef53",
"sha256:3a5f08039eee9ea195a89e180c5762bfb55258bfb9abb61a20d3abee3b37fd12",
"sha256:3dfca201fa6b326239e1bccb00b915e058707028809b8ecc0cf6819ad233a740",
"sha256:49461446b783945597c4076aea3f49aee4b4ce922bd241e4fcf62a3e7c61794c",
"sha256:4afa350f162551cf402bfa3cd8302165c8e03e689c897d185f16a167328cc6dd",
"sha256:4b5a9bcb56cc146c3932c648603b24514447eafa6ce9295234767bf92f69b504",
"sha256:52e83a5f28acd621ba8e71c2b816f6541af7144b69cc5859d17da76c436a5427",
"sha256:625116aca6c4b57c56ea3d70369cacc4d62fead4930f8329d242e4fe7a58ce4b",
"sha256:654c1635f2313d0843028487db2191530bca45af61ca85d0b16555c399625b0e",
"sha256:8092a5a06ad9a7a247f2a76ace121183dc4e1a84c259cf9c2ce3bbb69fac3582",
"sha256:832339223b9ce56b7b15168e691ae654d345ac1635eeb367ade9ecfe0e66bee0",
"sha256:8ca9dca965bd86ea3631b975d63b0693566d3cc347e55786d5514988b6f5b84c",
"sha256:96f99219dddb33e235a37283306834700b63170d7bb2a1ee17e41c6d589c8eb9",
"sha256:9b6305295b6591e45f069d3553c54d50cc47629eb5c218aac99e0f7fafbf90a1",
"sha256:a62162be05edf64f819925ea88d09d18b09bebf20971b363ce0c24e8b4aa14c0",
"sha256:aacc8623ffe7999a97935eeabbd24b1ae701d08ea8f874a6ff050e93c3e658cf",
"sha256:b45bab9f224de276b7bc916f6306b86283f6aa8afe7ed4133423efb42015a898",
"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"
"sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538",
"sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4",
"sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc",
"sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa",
"sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444",
"sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1",
"sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af",
"sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8",
"sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9",
"sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88",
"sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba",
"sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364",
"sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e",
"sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7",
"sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0",
"sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31",
"sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683",
"sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee",
"sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b",
"sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884",
"sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c",
"sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e",
"sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562",
"sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85",
"sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c",
"sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6",
"sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d",
"sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b",
"sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70",
"sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b",
"sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b",
"sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f",
"sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0",
"sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5",
"sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5",
"sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f",
"sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e",
"sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512",
"sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d",
"sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917",
"sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"
],
"version": "==2020.10.28"
"version": "==2020.11.13"
},
"requirements-detector": {
"hashes": [
@ -1702,6 +1749,7 @@
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"smmap": {
@ -1709,6 +1757,7 @@
"sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4",
"sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.0.4"
},
"snowballstemmer": {
@ -1723,6 +1772,7 @@
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
],
"markers": "python_version >= '3.5'",
"version": "==0.4.1"
},
"stevedore": {
@ -1730,6 +1780,7 @@
"sha256:5e1ab03eaae06ef6ce23859402de785f08d97780ed774948ef16c4652c41bc62",
"sha256:f845868b3a3a77a2489d226568abe7328b5c2d4f6a011cc759dfa99144a521f0"
],
"markers": "python_version >= '3.6'",
"version": "==3.2.2"
},
"toml": {
@ -1737,6 +1788,7 @@
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
"typed-ast": {
@ -1787,12 +1839,11 @@
"secure"
],
"hashes": [
"sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
"sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
],
"index": "pypi",
"markers": null,
"version": "==1.25.11"
"version": "==1.26.2"
},
"wrapt": {
"hashes": [

View File

@ -1,4 +1,4 @@
<img src="docs/images/logo.svg" height="50" alt="passbook logo"><img src="docs/images/brand_inverted.svg" height="50" alt="passbook">
<img src="website/static/img/logo.svg" height="50" alt="passbook logo"><img src="website/static/img/brand_inverted.svg" height="50" alt="passbook">
[![CI Build status](https://img.shields.io/azure-devops/build/beryjuorg/passbook/1?style=flat-square)](https://dev.azure.com/beryjuorg/passbook/_build?definitionId=1)
![Tests](https://img.shields.io/azure-devops/tests/beryjuorg/passbook/1?compact_message&style=flat-square)
@ -13,18 +13,18 @@ passbook is an open-source Identity Provider focused on flexibility and versatil
## Installation
For small/test setups it is recommended to use docker-compose, see the [documentation](https://passbook.beryju.org/installation/docker-compose/)
For small/test setups it is recommended to use docker-compose, see the [documentation](https://passbook.beryju.org/website/docs/installation/docker-compose/)
For bigger setups, there is a Helm Chart in the `helm/` directory. This is documented [here](https://passbook.beryju.org//installation/kubernetes/)
For bigger setups, there is a Helm Chart in the `helm/` directory. This is documented [here](https://passbook.beryju.org/website/docs/installation/kubernetes/)
## Screenshots
![](docs/images/screen_apps.png)
![](docs/images/screen_admin.png)
![](website/static/img/screen_apps.png)
![](website/static/img/screen_admin.png)
## Development
To develop on passbook, you need a system with Python 3.7+ (3.8 is recommended). passbook uses [pipenv](https://pipenv.pypa.io/en/latest/) for managing dependencies.
To develop on passbook, you need a system with Python 3.8+ (3.9 is recommended). passbook uses [pipenv](https://pipenv.pypa.io/en/latest/) for managing dependencies.
To get started, run

View File

@ -22,10 +22,11 @@ stages:
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
versionSpec: '3.9'
- task: CmdLine@2
inputs:
script: |
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
@ -37,10 +38,11 @@ stages:
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
versionSpec: '3.9'
- task: CmdLine@2
inputs:
script: |
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
@ -52,10 +54,11 @@ stages:
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
versionSpec: '3.9'
- task: CmdLine@2
inputs:
script: |
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
pipenv install --dev prospector --skip-lock
@ -68,10 +71,11 @@ stages:
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
versionSpec: '3.9'
- task: CmdLine@2
inputs:
script: |
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
@ -86,13 +90,14 @@ stages:
version: '12.x'
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
versionSpec: '3.9'
- task: CmdLine@2
inputs:
script: npm install -g pyright@1.1.79
- task: CmdLine@2
inputs:
script: |
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
@ -106,7 +111,7 @@ stages:
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
versionSpec: '3.9'
- task: DockerCompose@0
displayName: Run services
inputs:
@ -116,6 +121,7 @@ stages:
- task: CmdLine@2
inputs:
script: |
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
@ -128,6 +134,9 @@ stages:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
- task: DockerCompose@0
displayName: Run services
inputs:
@ -139,6 +148,7 @@ stages:
inputs:
script: |
git checkout $(git describe --abbrev=0 --match 'version/*')
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
@ -162,7 +172,7 @@ stages:
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
versionSpec: '3.9'
- task: DockerCompose@0
displayName: Run services
inputs:
@ -179,6 +189,7 @@ stages:
- task: CmdLine@2
inputs:
script: |
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: CmdLine@2
@ -204,7 +215,7 @@ stages:
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
versionSpec: '3.9'
- task: DockerCompose@0
displayName: Run services
inputs:
@ -221,6 +232,7 @@ stages:
- task: CmdLine@2
inputs:
script: |
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
- task: DockerCompose@0
@ -286,10 +298,11 @@ stages:
path: "coverage-unittest/"
- task: UsePythonVersion@0
inputs:
versionSpec: '3.8'
versionSpec: '3.9'
- task: CmdLine@2
inputs:
script: |
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
pipenv run coverage combine coverage-e2e/coverage coverage-unittest/coverage

View File

@ -19,7 +19,7 @@ services:
networks:
- internal
server:
image: beryju/passbook:${PASSBOOK_TAG:-0.12.9-stable}
image: beryju/passbook:${PASSBOOK_TAG:-0.12.11-stable}
command: server
environment:
PASSBOOK_REDIS__HOST: redis
@ -40,7 +40,7 @@ services:
env_file:
- .env
worker:
image: beryju/passbook:${PASSBOOK_TAG:-0.12.9-stable}
image: beryju/passbook:${PASSBOOK_TAG:-0.12.11-stable}
command: worker
networks:
- internal
@ -54,7 +54,7 @@ services:
env_file:
- .env
static:
image: beryju/passbook-static:${PASSBOOK_TAG:-0.12.9-stable}
image: beryju/passbook-static:${PASSBOOK_TAG:-0.12.11-stable}
networks:
- internal
labels:

View File

@ -1,26 +0,0 @@
# Passbook User Object
The User object has the following attributes:
- `username`: User's username.
- `email` User's email.
- `name` User's display name.
- `is_staff` Boolean field if user is staff.
- `is_active` Boolean field if user is active.
- `date_joined` Date user joined/was created.
- `password_change_date` Date password was last changed.
- `attributes` Dynamic attributes.
- `pb_groups` This is a queryset of all the user's groups.
You can do additional filtering like `user.pb_groups.filter(name__startswith='test')`, see [here](https://docs.djangoproject.com/en/3.1/ref/models/querysets/#id4)
To get the name of all groups, you can do `[group.name for group in user.pb_groups.all()]`
## Examples
List all the User's group names:
```python
for group in user.pb_groups.all():
yield group.name
```

View File

@ -1,7 +0,0 @@
# OTP Stage
This stage offers a generic Time-based One-time Password authentication step.
You can optionally enforce this step, which will force every user without OTP setup to configure it.
This stage uses a 6-digit Code with a 30 second time-drift. This is currently not changeable.

View File

@ -1,8 +0,0 @@
# User Delete Stage
!!! danger
This stage deletes the `pending_user` without any confirmation. You have to make sure the user is aware of this.
This stage is intended for an unenrollment flow. It deletes the currently pending user.
The pending user is also removed from the current session.

View File

@ -1,73 +0,0 @@
# Kubernetes
For a mid to high-load installation, Kubernetes is recommended. passbook is installed using a helm-chart.
This installation automatically applies database migrations on startup. After the installation is done, you can use `pbadmin` as username and password.
```yaml
###################################
# Values directly affecting passbook
###################################
image:
name: beryju/passbook
name_static: beryju/passbook-static
tag: 0.12.9-stable
serverReplicas: 1
workerReplicas: 1
# Enable the Kubernetes integration which lets passbook deploy outposts into kubernetes
kubernetesIntegration: true
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
# Enable Database Backups to S3
# backup:
# accessKey: access-key
# secretKey: secret-key
# bucket: s3-bucket
# region: eu-central-1
# host: s3-host
ingress:
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- passbook.k8s.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - passbook.k8s.local
###################################
# Values controlling dependencies
###################################
install:
postgresql: true
redis: true
# These values influence the bundled postgresql and redis charts, but are also used by passbook to connect
postgresql:
postgresqlDatabase: passbook
redis:
cluster:
enabled: false
master:
persistence:
enabled: false
# https://stackoverflow.com/a/59189742
disableCommands: []
```

View File

@ -1,75 +0,0 @@
# Ansible Tower / AWX Integration
## What is Tower
From https://docs.ansible.com/ansible/2.5/reference_appendices/tower.html
!!! note ""
Ansible Tower (formerly AWX) is a web-based solution that makes Ansible even more easy to use for IT teams of all kinds. Its designed to be the hub for all of your automation tasks.
Tower allows you to control access to who can access what, even allowing sharing of SSH credentials without someone being able to transfer those credentials. Inventory can be graphically managed or synced with a wide variety of cloud sources. It logs all of your jobs, integrates well with LDAP, and has an amazing browsable REST API. Command line tools are available for easy integration with Jenkins as well. Provisioning callbacks provide great support for autoscaling topologies.
!!! note
AWX is the open-source version of Tower. The term "AWX" will be used interchangeably throughout this document.
## Preparation
The following placeholders will be used:
- `awx.company` is the FQDN of the AWX/Tower install.
- `passbook.company` is the FQDN of the passbook install.
Create an application in passbook and note the slug, as this will be used later. Create a SAML provider with the following parameters:
- ACS URL: `https://awx.company/sso/complete/saml/`
- Audience: `awx`
- Service Provider Binding: Post
- Issuer: `https://awx.company/sso/metadata/saml/`
You can of course use a custom signing certificate, and adjust durations.
## AWX Configuration
Navigate to `https://awx.company/#/settings/auth` to configure SAML. Set the Field `SAML SERVICE PROVIDER ENTITY ID` to `awx`.
For the fields `SAML SERVICE PROVIDER PUBLIC CERTIFICATE` and `SAML SERVICE PROVIDER PRIVATE KEY`, you can either use custom certificates, or use the self-signed pair generated by passbook.
Provide metadata in the `SAML Service Provider Organization Info` field:
```json
{
"en-US": {
"name": "passbook",
"url": "https://passbook.company",
"displayname": "passbook"
}
}
```
Provide metadata in the `SAML Service Provider Technical Contact` and `SAML Service Provider Technical Contact` fields:
```json
{
"givenName": "Admin Name",
"emailAddress": "admin@company"
}
```
In the `SAML Enabled Identity Providers` paste the following configuration:
```json
{
"passbook": {
"attr_username": "urn:oid:2.16.840.1.113730.3.1.241",
"attr_user_permanent_id": "urn:oid:0.9.2342.19200300.100.1.1",
"x509cert": "MIIDEjCCAfqgAwIBAgIRAJZ9pOZ1g0xjiHtQAAejsMEwDQYJKoZIhvcNAQELBQAwMDEuMCwGA1UEAwwlcGFzc2Jvb2sgU2VsZi1zaWduZWQgU0FNTCBDZXJ0aWZpY2F0ZTAeFw0xOTEyMjYyMDEwNDFaFw0yMDEyMjYyMDEwNDFaMFkxLjAsBgNVBAMMJXBhc3Nib29rIFNlbGYtc2lnbmVkIFNBTUwgQ2VydGlmaWNhdGUxETAPBgNVBAoMCHBhc3Nib29rMRQwEgYDVQQLDAtTZWxmLXNpZ25lZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO/ktBYZkY9xAijF4acvzX6Q1K8KoIZeyde8fVgcWBz4L5FgDQ4/dni4k2YAcPdwteGL4nKVzetUzjbRCBUNuO6lqU4J4WNNX4Xg4Ir7XLRoAQeo+omTPBdpJ1p02HjtN5jT01umN3bK2yto1e37CJhK6WJiaXqRewPxh4lI4aqdj3BhFkJ3I3r2qxaWOAXQ6X7fg3w/ny7QP53//ouZo7hSLY3GIcRKgvdjjVM3OW5C3WLpOq5Dez5GWVJ17aeFCfGQ8bwFKde6qfYqyGcU9xHB36TtVHB9hSFP/tUFhkiSOxtsrYwCgCyXm4UTSpP+wiNyjKfFw7qGLBvA2hGTNw8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAh9PeAqPRQk1/SSygIFADZBi08O/DPCshFwEHvJATIcTzcDD8UGAjXh+H5OlkDyX7KyrcaNvYaafCUo63A+WprdtdY5Ty6SBEwTYyiQyQfwM9BfK+imCoif1Ai7xAelD7p9lNazWq7JU+H/Ep7U7Q7LvpxAbK0JArt+IWTb2NcMb3OWE1r0gFbs44O1l6W9UbJTbyLMzbGbe5i+NHlgnwPwuhtRMh0NUYabGHKcHbhwyFhfGAQv2dAp5KF1E5gu6ZzCiFePzc0FrqXQyb2zpFYcJHXquiqaOeG7cZxRHYcjrl10Vxzki64XVA9BpdELgKSnupDGUEJsRUt3WVOmvZuA==",
"url": "https://passbook.company/application/saml/awx/login/",
"attr_last_name": "User.LastName",
"entity_id": "https://awx.company/sso/metadata/saml/",
"attr_email": "urn:oid:0.9.2342.19200300.100.1.3",
"attr_first_name": "urn:oid:2.5.4.3"
}
}
```
`x509cert` is the certificate configured in passbook. Remove the `--BEGIN CERTIFICATE--` and `--END CERTIFICATE--` headers, then enter the cert as one non-breaking string.

View File

@ -1,83 +0,0 @@
# VMware vCenter Integration
## What is vCenter
From https://en.wikipedia.org/wiki/VCenter
!!! note ""
vCenter Server is the centralized management utility for VMware, and is used to manage virtual machines, multiple ESXi hosts, and all dependent components from a single centralized location. VMware vMotion and svMotion require the use of vCenter and ESXi hosts.
!!! warning
This requires passbook 0.10.3 or newer.
!!! warning
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
The following placeholders will be used:
- `vcenter.company` is the FQDN of the vCenter server.
- `passbook.company` is the FQDN of the passbook install.
Since vCenter only allows OpenID-Connect in combination with Active Directory, it is recommended to have passbook sync with the same Active Directory.
### Step 1
Under *Property Mappings*, create a *Scope Mapping*. Give it a name like "OIDC-Scope-VMware-vCenter". Set the scope name to `openid` and the expression to the following
```python
return {
"domain": "<your active directory domain>",
}
```
### Step 2
!!! note
If your Active Directory Schema is the same as your Email address schema, skip to Step 3.
Under *Sources*, click *Edit* and ensure that "Autogenerated Active Directory Mapping: userPrincipalName -> attributes.upn" has been added to your source.
### Step 3
Under *Providers*, create an OAuth2/OpenID Provider with these settings:
- Client Type: Confidential
- Response Type: code (ADFS Compatibility Mode, sends id_token as access_token)
- JWT Algorithm: RS256
- Redirect URI: `https://vcenter.company/ui/login/oauth2/authcode`
- Post Logout Redirect URIs: `https://vcenter.company/ui/login`
- Sub Mode: If your Email address Schema matches your UPN, select "Based on the User's Email...", otherwise select "Based on the User's UPN...".
- Scopes: Select the Scope Mapping you've created in Step 1
![](./passbook_setup.png)
### Step 4
Create an application which uses this provider. Optionally apply access restrictions to the application.
Set the Launch URL to `https://vcenter.company/ui/login/oauth2`. This will skip vCenter's User Prompt and directly log you in.
## vCenter Setup
Login as local Administrator account (most likely ends with vsphere.local). Using the Menu in the Navigation bar, navigate to *Administration -> Single Sing-on -> Configuration*.
Click on *Change Identity Provider* in the top-right corner.
In the wizard, select "Microsoft ADFS" and click Next.
Fill in the Client Identifier and Shared Secret from the Provider in passbook. For the OpenID Address, click on *View Setup URLs* in passbook, and copy the OpenID Configuration URL.
On the next page, fill in your Active Directory Connection Details. These should be similar to what you have set in passbook.
![](./vcenter_post_setup.png)
If your vCenter was already setup with LDAP beforehand, your Role assignments will continue to work.

View File

@ -1,20 +0,0 @@
# Outpost deployment in docker-compose
To deploy an outpost with docker-compose, use this snippet in your docker-compose file.
You can also run the outpost in a separate docker-compose project, you just have to ensure that the outpost container can reach your application container.
```yaml
version: '3.5'
services:
passbook_proxy:
image: beryju/passbook-proxy:0.10.0-stable
ports:
- 4180:4180
- 4443:4443
environment:
PASSBOOK_HOST: https://your-passbook.tld
PASSBOOK_INSECURE: 'false'
PASSBOOK_TOKEN: token-generated-by-passbook
```

View File

@ -1,99 +0,0 @@
# Outpost deployment on Kubernetes
Use the following manifest, replacing all values surrounded with `__`.
Afterwards, configure the proxy provider to connect to `<service name>.<namespace>.svc.cluster.local`, and update your Ingress to connect to the `passbook-outpost` service.
```yaml
apiVersion: v1
kind: Secret
metadata:
labels:
app.kubernetes.io/instance: test
app.kubernetes.io/managed-by: passbook.beryju.org
app.kubernetes.io/name: passbook-proxy
app.kubernetes.io/version: 0.10.0
name: passbook-outpost-api
stringData:
passbook_host: '__PASSBOOK_URL__'
passbook_host_insecure: 'true'
token: '__PASSBOOK_TOKEN__'
type: Opaque
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/instance: test
app.kubernetes.io/managed-by: passbook.beryju.org
app.kubernetes.io/name: passbook-proxy
app.kubernetes.io/version: 0.10.0
name: passbook-outpost
spec:
ports:
- name: http
port: 4180
protocol: TCP
targetPort: http
- name: https
port: 4443
protocol: TCP
targetPort: https
selector:
app.kubernetes.io/instance: test
app.kubernetes.io/managed-by: passbook.beryju.org
app.kubernetes.io/name: passbook-proxy
app.kubernetes.io/version: 0.10.0
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/instance: test
app.kubernetes.io/managed-by: passbook.beryju.org
app.kubernetes.io/name: passbook-proxy
app.kubernetes.io/version: 0.10.0
name: passbook-outpost
spec:
selector:
matchLabels:
app.kubernetes.io/instance: test
app.kubernetes.io/managed-by: passbook.beryju.org
app.kubernetes.io/name: passbook-proxy
app.kubernetes.io/version: 0.10.0
template:
metadata:
labels:
app.kubernetes.io/instance: test
app.kubernetes.io/managed-by: passbook.beryju.org
app.kubernetes.io/name: passbook-proxy
app.kubernetes.io/version: 0.10.0
spec:
containers:
- env:
- name: PASSBOOK_HOST
valueFrom:
secretKeyRef:
key: passbook_host
name: passbook-outpost-api
- name: PASSBOOK_TOKEN
valueFrom:
secretKeyRef:
key: token
name: passbook-outpost-api
- name: PASSBOOK_INSECURE
valueFrom:
secretKeyRef:
key: passbook_host_insecure
name: passbook-outpost-api
image: beryju/passbook-proxy:0.10.0-stable
name: proxy
ports:
- containerPort: 4180
name: http
protocol: TCP
- containerPort: 4443
name: https
protocol: TCP
```

View File

@ -1,14 +0,0 @@
# Outposts
An outpost is a single deployment of a passbook component, which can be deployed in a completely separate environment. Currently, only the Proxy Provider is supported as outpost.
![](outposts.png)
Upon creation, a service account and a token is generated. The service account only has permissions to read the outpost and provider configuration. This token is used by the Outpost to connect to passbook.
To deploy an outpost, see: <a name="deploy">
- [Kubernetes](deploy-kubernetes.md)
- [docker-compose](deploy-docker-compose.md)
In future versions, this snippet will be automatically generated. You will also be able to deploy an outpost directly into a kubernetes cluster.

View File

@ -1,41 +0,0 @@
# Expression Policies
!!! notice
These variables are available in addition to the common variables/functions defined in [**Expressions**](../expressions/index.md)
The passing of the policy is determined by the return value of the code. Use `return True` to pass a policy and `return False` to fail it.
### Available Functions
#### `pb_message(message: str)`
Add a message, visible by the end user. This can be used to show the reason why they were denied.
Example:
```python
pb_message("Access denied")
return False
```
### Context variables
- `request`: A PolicyRequest object, which has the following properties:
- `request.user`: The current user, against which the policy is applied. ([ref](../expressions/reference/user-object.md))
- `request.http_request`: The Django HTTP Request. ([ref](https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects))
- `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.
- `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), 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.
This includes the following:
- `prompt_data`: Data which has been saved from a prompt stage or an external source.
- `application`: The application the user is in the process of authorizing.
- `pending_user`: The currently pending user

View File

@ -1,12 +0,0 @@
# Property Mapping Expressions
The property mapping should return a value that is expected by the Provider/Source. Supported types are documented in the individual Provider/Source. Returning `None` is always accepted and would simply skip the mapping for which `None` was returned.
!!! notice
These variables are available in addition to the common variables/functions defined in [**Expressions**](../expressions/index.md)
### Context Variables
- `user`: The current user. This may be `None` if there is no contextual user. ([ref](../expressions/reference/user-object.md))
- `request`: The current request. This may be `None` if there is no contextual request. ([ref](https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects))
- Other arbitrary arguments given by the provider, this is documented on the Provider/Source.

View File

@ -1,31 +0,0 @@
# OAuth2 Provider
This provider supports both generic OAuth2 as well as OpenID Connect
Scopes can be configured using Scope Mappings, a type of [Property Mappings](../property-mappings/index.md#scope-mapping).
Endpoint | URL
---------|---
Authorization | `/application/o/authorize/`
Token | `/application/o/token/`
User Info | `/application/o/userinfo/`
End Session | `/application/o/end-session/`
Introspect | `/application/o/end-session/`
JWKS | `/application/o/<application slug>/jwks/`
OpenID Configuration | `/application/o/<application slug>/.well-known/openid-configuration`
## GitHub Compatibility
This provider also exposes a GitHub-compatible endpoint. This endpoint can be used by applications, which support authenticating against GitHub Enterprise, but not generic OpenID Connect.
To use any of the GitHub Compatibility scopes, you have to use the GitHub Compatibility Endpoints.
Endpoint | URL
---------|---
Authorization | `/login/oauth/authorize`
Token | `/login/oauth/access_token`
User Info | `/user`
User Teams Info | `/user/teams`
To access the user's email address, a scope of `user:email` is required. To access their groups, `read:org` is required. Because these scopes are handled by a different endpoint, they are not customisable as a Scope Mapping.

View File

@ -1,24 +0,0 @@
# Proxy Provider
!!! info
This provider is to be used in conjunction with [Outposts](../outposts/outposts.md)
This provider protects applications, which have no built-in support for OAuth2 or SAML. This is done by running a lightweight Reverse Proxy in front of the application, which authenticates the requests.
passbook Proxy is based on [oauth2_proxy](https://github.com/oauth2-proxy/oauth2-proxy), but has been integrated more tightly with passbook.
The Proxy these extra headers to the application:
Header Name | Value
-------------|-------
X-Forwarded-User | The user's unique identifier (**not the username**)
X-Forwarded-Email | The user's email address
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
```

View File

@ -1,12 +0,0 @@
# SAML Provider
This provider allows you to integrate enterprise software using the SAML2 Protocol. It supports signed requests and uses [Property Mappings](../property-mappings/index.md#saml-property-mapping) to determine which fields are exposed and what values they return. This makes it possible to expose vendor-specific fields.
Default fields are exposed through auto-generated Property Mappings, which are prefixed with "Autogenerated".
Endpoint | URL
---------|---
SSO (Redirect binding) | `/application/saml/<application slug>/sso/binding/redirect/`
SSO (POST binding) | `/application/saml/<application slug>/sso/binding/post/`
IdP-initiated login | `/application/saml/<application slug>/sso/binding/init/`
Metadata Download | `/application/saml/<application slug>/metadata/`

View File

@ -1,2 +0,0 @@
mkdocs
mkdocs-material

View File

@ -1 +0,0 @@
3.7

View File

@ -1,73 +0,0 @@
# Upgrading to 0.10
This update brings a lot of big features, such as:
- New OAuth2/OpenID Provider
This new provider merges both OAuth2 and OpenID. It is based on the codebase of the old provider, which has been simplified and cleaned from the ground up. Support for Property Mappings has also been added. Because of this change, OpenID and OAuth2 Providers will have to be re-created.
- Proxy Provider
Due to this new OAuth2 Provider, the Application Gateway Provider, now simply called "Proxy Provider" has been revamped as well. The new passbook Proxy integrates more tightly with passbook via the new Outposts system. The new proxy also supports multiple applications per proxy instance, can configure TLS based on passbook Keypairs, and more.
See [Proxy](../providers/proxy.md)
- Outpost System
This is a new Object type, currently used only by the Proxy Provider. It manages the creation and permissions of service accounts, which are used by the outposts to communicate with passbook.
See [Outposts](../outposts/outposts.md)
- Flow Import/Export
Flows can now be imported and exported. This feature can be used as a backup system, or to share complex flows with other people. Example flows have also been added to the documentation to help you get going with passbook.
## Under the hood
- passbook now runs on Django 3.1 and Channels with complete ASGI enabled
- uwsgi has been replaced with Gunicorn and uvicorn
- Elastic APM has been replaced with Sentry Performance metrics
- Flow title is now configurable separately from the name
- All logging output is now json
## Upgrading
### docker-compose
The docker-compose file has been updated, please download the latest from `https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml`.
By default, the new compose file uses a fixed version to prevent unintended updates.
Before updating the file, stop all containers. Then download the file, pull the new containers and start the database.
```
docker-compose down
docker-compose pull
docker-compose up --no-start
docker-compose start redis postgrseql
docker-compose run --rm server migrate
docker-compose up -d
```
### Helm
A few options have changed:
- `error_reporting` was changed from a simple boolean to a dictionary:
```yaml
error_reporting:
enabled: false
environment: customer
send_pii: false
```
- The `apm` and `monitoring` blocks have been removed.
- `serverReplicas` and `workerReplicas` have been added
### Upgrading
This upgrade only applies if you are upgrading from a running 0.9 instance. Passbook detects this on startup, and automatically executes this upgrade.
Because this upgrade brings the new OAuth2 Provider, the old providers will be lost in the process. Make sure to take note of the providers you want to bring over.
Another side-effect of this upgrade is the change of OAuth2 URLs, see [here](../providers/oauth2.md).

View File

@ -1,20 +0,0 @@
# 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`.

View File

@ -1,63 +0,0 @@
# 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.

View File

@ -9,7 +9,7 @@ curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo npm install -g yarn
# Setup python
sudo apt install -y python3.8 python3-pip
sudo apt install -y python3.9 python3-pip libxmlsec1-dev pkg-config
# Setup docker
sudo pip3 install pipenv

View File

@ -156,8 +156,7 @@ def retry(max_retires=3, exceptions=None):
raise exc
logger.debug("Retrying on error", exc=exc, test=self)
self.tearDown()
# pylint: disable=protected-access
self._post_teardown()
self._post_teardown() # noqa
self.setUp()
return wrapper(self, *args, **kwargs)

View File

@ -4,8 +4,8 @@ name: passbook
home: https://passbook.beryju.org
sources:
- https://github.com/BeryJu/passbook
version: "0.12.9-stable"
icon: https://raw.githubusercontent.com/BeryJu/passbook/master/docs/images/logo.svg
version: "0.12.11-stable"
icon: https://raw.githubusercontent.com/BeryJu/passbook/master/website/static/img/logo.svg
dependencies:
- name: postgresql
version: 9.4.1

View File

@ -25,4 +25,4 @@
| 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/
For more info, see https://passbook.beryju.org/ and https://passbook.beryju.org/docs/installation/kubernetes/

View File

@ -4,7 +4,7 @@
image:
name: beryju/passbook
name_static: beryju/passbook-static
tag: 0.12.9-stable
tag: 0.12.11-stable
serverReplicas: 1
workerReplicas: 1

View File

@ -1,4 +1,5 @@
"""Gunicorn config"""
import os
import warnings
from multiprocessing import cpu_count
from pathlib import Path
@ -14,6 +15,8 @@ worker_class = "uvicorn.workers.UvicornWorker"
# Docker containers don't have /tmp as tmpfs
worker_tmp_dir = "/dev/shm"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings")
logconfig_dict = {
"version": 1,
"disable_existing_loggers": False,

View File

@ -1,95 +0,0 @@
site_name: passbook Docs
site_url: https://passbook.beryju.org/
copyright: "Copyright &copy; 2019 - 2020 BeryJu.org"
nav:
- Home: index.md
- Terminology: terminology.md
- Installation:
- docker-compose: installation/docker-compose.md
- Kubernetes: installation/kubernetes.md
- Reverse Proxy: installation/reverse-proxy.md
- Flows:
Overview: flow/flows.md
Examples: flow/examples/examples.md
- Stages:
- Captcha Stage: flow/stages/captcha/index.md
- Dummy Stage: flow/stages/dummy/index.md
- Email Stage: flow/stages/email/index.md
- Identification Stage: flow/stages/identification/index.md
- Invitation Stage: flow/stages/invitation/index.md
- OTP Stage: flow/stages/otp/index.md
- Password Stage: flow/stages/password/index.md
- Prompt Stage: flow/stages/prompt/index.md
- Prompt Stage Validation: flow/stages/prompt/validation.md
- User Delete Stage: flow/stages/user_delete.md
- User Login Stage: flow/stages/user_login.md
- User Logout Stage: flow/stages/user_logout.md
- User Write Stage: flow/stages/user_write.md
- Sources: sources.md
- Providers:
- OAuth2: providers/oauth2.md
- SAML: providers/saml.md
- Proxy: providers/proxy.md
- Outposts:
- Overview: outposts/outposts.md
- Upgrading: outposts/upgrading.md
- Deploy on docker-compose: outposts/deploy-docker-compose.md
- Deploy on Kubernetes: outposts/deploy-kubernetes.md
- Expressions:
- Overview: expressions/index.md
- Reference:
- User Object: expressions/reference/user-object.md
- Property Mappings:
- Overview: property-mappings/index.md
- Expressions: property-mappings/expression.md
- Policies:
- Overview: policies/index.md
- Expression: policies/expression.md
- Integrations:
- as Source:
- Active Directory: integrations/sources/active-directory/index.md
- as Provider:
- Amazon Web Services: integrations/services/aws/index.md
- GitLab: integrations/services/gitlab/index.md
- Rancher: integrations/services/rancher/index.md
- Harbor: integrations/services/harbor/index.md
- Sentry: integrations/services/sentry/index.md
- Ansible Tower/AWX: integrations/services/tower-awx/index.md
- VMware vCenter: integrations/services/vmware-vcenter/index.md
- Ubuntu Landscape: integrations/services/ubuntu-landscape/index.md
- Sonarr: integrations/services/sonarr/index.md
- Tautulli: integrations/services/tautulli/index.md
- Maintenance:
- Backups: maintenance/backups/index.md
- Upgrading:
- to 0.9: upgrading/to-0.9.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:
- Access problems: troubleshooting/access.md
repo_name: "BeryJu/passbook"
repo_url: https://github.com/BeryJu/passbook
theme:
name: material
logo: images/logo.svg
favicon: images/logo.svg
palette:
scheme: slate
primary: white
markdown_extensions:
- toc:
permalink: "¶"
- admonition
- codehilite
- pymdownx.betterem:
smart_enable: all
- pymdownx.inlinehilite
- pymdownx.magiclink
- attr_list
plugins:
- search

View File

@ -1,2 +1,2 @@
"""passbook"""
__version__ = "0.12.9-stable"
__version__ = "0.12.11-stable"

View File

@ -147,5 +147,5 @@ class FlowExportView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
flow: Flow = self.get_object()
exporter = FlowExporter(flow)
response = JsonResponse(exporter.export(), encoder=DataclassEncoder, safe=False)
response["Content-Disposition"] = f'attachment; filename="{flow.slug}.json"'
response["Content-Disposition"] = f'attachment; filename="{flow.slug}.pbflow"'
return response

View File

@ -1,6 +1,7 @@
"""Flow and Stage forms"""
from django import forms
from django.core.validators import FileExtensionValidator
from django.forms import ValidationError
from django.utils.translation import gettext_lazy as _
@ -62,7 +63,9 @@ class FlowStageBindingForm(forms.ModelForm):
class FlowImportForm(forms.Form):
"""Form used for flow importing"""
flow = forms.FileField()
flow = forms.FileField(
validators=[FileExtensionValidator(allowed_extensions=["pbflow"])]
)
def clean_flow(self):
"""Check if the flow is valid and rewind the file to the start"""

View File

@ -12,7 +12,7 @@ class TestTransferDocs(TransactionTestCase):
"""Empty class, test methods are added dynamically"""
def generic_view_tester(file_name: str) -> Callable:
def pbflow_tester(file_name: str) -> Callable:
"""This is used instead of subTest for better visibility"""
def tester(self: TestTransferDocs):
@ -24,8 +24,6 @@ def generic_view_tester(file_name: str) -> Callable:
return tester
for flow_file in glob("docs/flow/examples/*.json"):
for flow_file in glob("website/static/flows/*.pbflow"):
method_name = Path(flow_file).stem.replace("-", "_").replace(".", "_")
setattr(
TestTransferDocs, f"test_flow_{method_name}", generic_view_tester(flow_file)
)
setattr(TestTransferDocs, f"test_flow_{method_name}", pbflow_tester(flow_file))

View File

@ -30,6 +30,6 @@ passbook:
# Optionally add links to the footer on the login page
footer_links:
- name: Documentation
href: https://passbook.beryju.org/docs/
- name: passbook Website
href: https://passbook.beryju.org/
# - name: test
# href: https://test

View File

@ -27,7 +27,7 @@ class BaseEvaluator:
def __init__(self):
# update passbook/policies/expression/templates/policy/expression/form.html
# update docs/policies/expression/index.md
# update website/docs/policies/expression.md
self._globals = {
"regex_match": BaseEvaluator.expr_filter_regex_match,
"regex_replace": BaseEvaluator.expr_filter_regex_replace,

View File

@ -1,5 +1,5 @@
"""passbook sentry integration"""
from aioredis.errors import ReplyError
from aioredis.errors import ConnectionClosedError, ReplyError
from billiard.exceptions import WorkerLostError
from botocore.client import ClientError
from celery.exceptions import CeleryError
@ -40,6 +40,7 @@ def before_send(event, hint):
RedisError,
ResponseError,
ReplyError,
ConnectionClosedError,
# websocket errors
ChannelFull,
WebSocketException,

View File

@ -8,6 +8,7 @@ from passbook.outposts.models import (
DockerServiceConnection,
KubernetesServiceConnection,
Outpost,
OutpostServiceConnection,
)
from passbook.providers.proxy.models import ProxyProvider
@ -18,6 +19,9 @@ class OutpostForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["providers"].queryset = ProxyProvider.objects.all()
self.fields[
"service_connection"
].queryset = OutpostServiceConnection.objects.select_subclasses()
class Meta:

View File

@ -25,8 +25,8 @@ def migrate_to_service_connection(apps: Apps, schema_editor: BaseDatabaseSchemaE
# Ensure that local connection have been created
PassbookOutpostConfig.init_local_connection(None)
docker = DockerServiceConnection.objects.filter(local=True)
k8s = KubernetesServiceConnection.objects.filter(local=True)
docker = DockerServiceConnection.objects.filter(local=True).first()
k8s = KubernetesServiceConnection.objects.filter(local=True).first()
try:
for outpost in (

View File

@ -12,7 +12,7 @@
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Outpost Deployment Info' %}</h1>
</div>
<div class="pf-c-modal-box__body" id="modal-description">
<p><a href="https://passbook.beryju.org/outposts/outposts/#deploy">{% trans 'View deployment documentation' %}</a></p>
<p><a href="https://passbook.beryju.org/docs/outposts/outposts/#deploy">{% trans 'View deployment documentation' %}</a></p>
<form class="pf-c-form">
<div class="pf-c-form__group">
<label class="pf-c-form__label" for="help-text-simple-form-name">

View File

@ -32,7 +32,7 @@ class PolicyEvaluator(BaseEvaluator):
def set_policy_request(self, request: PolicyRequest):
"""Update context based on policy request (if http request is given, update that too)"""
# update docs/policies/expression/index.md
# update website/docs/policies/expression.md
self._context["pb_is_sso_flow"] = request.context.get(PLAN_CONTEXT_SSO, False)
if request.http_request:
self.set_http_request(request.http_request)
@ -41,7 +41,7 @@ class PolicyEvaluator(BaseEvaluator):
def set_http_request(self, request: HttpRequest):
"""Update context based on http request"""
# update docs/policies/expression/index.md
# update website/docs/policies/expression.md
self._context["pb_client_ip"] = ip_address(
get_client_ip(request) or "255.255.255.255"
)

View File

@ -108,7 +108,6 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
@swagger_serializer_method(serializer_or_field=OpenIDConnectConfigurationSerializer)
def get_oidc_configuration(self, obj: ProxyProvider):
"""Embed OpenID Connect provider information"""
# pylint: disable=protected-access
return ProviderInfoView(request=self.context["request"]._request).get_info(obj)

View File

@ -24,7 +24,6 @@ class SAMLProviderSerializer(ModelSerializer):
"digest_algorithm",
"signature_algorithm",
"signing_kp",
"require_signing",
"verification_kp",
]

View File

@ -39,7 +39,6 @@ class SAMLProviderForm(forms.ModelForm):
"assertion_valid_not_on_or_after",
"session_valid_not_on_or_after",
"digest_algorithm",
"require_signing",
"signature_algorithm",
"signing_kp",
"verification_kp",

View File

@ -0,0 +1,71 @@
# Generated by Django 3.1.3 on 2020-11-12 10:36
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_crypto", "0002_create_self_signed_kp"),
("passbook_providers_saml", "0007_samlprovider_verification_kp"),
]
operations = [
migrations.RemoveField(
model_name="samlprovider",
name="require_signing",
),
migrations.AlterField(
model_name="samlprovider",
name="audience",
field=models.TextField(
default="",
help_text="Value of the audience restriction field of the asseration.",
),
),
migrations.AlterField(
model_name="samlprovider",
name="issuer",
field=models.TextField(
default="passbook", help_text="Also known as EntityID"
),
),
migrations.AlterField(
model_name="samlprovider",
name="signing_kp",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Keypair used to sign outgoing Responses going to the Service Provider.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="passbook_crypto.certificatekeypair",
verbose_name="Signing Keypair",
),
),
migrations.AlterField(
model_name="samlprovider",
name="sp_binding",
field=models.TextField(
choices=[("redirect", "Redirect"), ("post", "Post")],
default="redirect",
help_text="This determines how passbook sends the response back to the Service Provider.",
verbose_name="Service Provider Binding",
),
),
migrations.AlterField(
model_name="samlprovider",
name="verification_kp",
field=models.ForeignKey(
blank=True,
default=None,
help_text="When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="passbook_crypto.certificatekeypair",
verbose_name="Verification Certificate",
),
),
]

View File

@ -0,0 +1,69 @@
# Generated by Django 3.1.3 on 2020-11-12 20:16
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from passbook.sources.saml.processors import constants
def update_algorithms(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
SAMLProvider = apps.get_model("passbook_providers_saml", "SAMLProvider")
signature_translation_map = {
"rsa-sha1": constants.RSA_SHA1,
"rsa-sha256": constants.RSA_SHA256,
"ecdsa-sha256": constants.RSA_SHA256,
"dsa-sha1": constants.DSA_SHA1,
}
digest_translation_map = {
"sha1": constants.SHA1,
"sha256": constants.SHA256,
}
for source in SAMLProvider.objects.all():
source.signature_algorithm = signature_translation_map.get(
source.signature_algorithm, constants.RSA_SHA256
)
source.digest_algorithm = digest_translation_map.get(
source.digest_algorithm, constants.SHA256
)
source.save()
class Migration(migrations.Migration):
dependencies = [
("passbook_providers_saml", "0008_auto_20201112_1036"),
]
operations = [
migrations.AlterField(
model_name="samlprovider",
name="digest_algorithm",
field=models.CharField(
choices=[
(constants.SHA1, "SHA1"),
(constants.SHA256, "SHA256"),
(constants.SHA384, "SHA384"),
(constants.SHA512, "SHA512"),
],
default=constants.SHA256,
max_length=50,
),
),
migrations.AlterField(
model_name="samlprovider",
name="signature_algorithm",
field=models.CharField(
choices=[
(constants.RSA_SHA1, "RSA-SHA1"),
(constants.RSA_SHA256, "RSA-SHA256"),
(constants.RSA_SHA384, "RSA-SHA384"),
(constants.RSA_SHA512, "RSA-SHA512"),
(constants.DSA_SHA1, "DSA-SHA1"),
],
default=constants.RSA_SHA256,
max_length=50,
),
),
]

View File

@ -13,6 +13,17 @@ from passbook.core.models import PropertyMapping, Provider
from passbook.crypto.models import CertificateKeyPair
from passbook.lib.utils.template import render_to_string
from passbook.lib.utils.time import timedelta_string_validator
from passbook.sources.saml.processors.constants import (
DSA_SHA1,
RSA_SHA1,
RSA_SHA256,
RSA_SHA384,
RSA_SHA512,
SHA1,
SHA256,
SHA384,
SHA512,
)
LOGGER = get_logger()
@ -28,12 +39,21 @@ class SAMLProvider(Provider):
"""SAML 2.0 Endpoint for applications which support SAML."""
acs_url = models.URLField(verbose_name=_("ACS URL"))
audience = models.TextField(default="")
issuer = models.TextField(help_text=_("Also known as EntityID"))
audience = models.TextField(
default="",
help_text=_("Value of the audience restriction field of the asseration."),
)
issuer = models.TextField(help_text=_("Also known as EntityID"), default="passbook")
sp_binding = models.TextField(
choices=SAMLBindings.choices,
default=SAMLBindings.REDIRECT,
verbose_name=_("Service Prodier Binding"),
verbose_name=_("Service Provider Binding"),
help_text=_(
(
"This determines how passbook sends the "
"response back to the Service Provider."
)
),
)
assertion_valid_not_before = models.TextField(
@ -71,48 +91,52 @@ class SAMLProvider(Provider):
digest_algorithm = models.CharField(
max_length=50,
choices=(
("sha1", _("SHA1")),
("sha256", _("SHA256")),
(SHA1, _("SHA1")),
(SHA256, _("SHA256")),
(SHA384, _("SHA384")),
(SHA512, _("SHA512")),
),
default="sha256",
default=SHA256,
)
signature_algorithm = models.CharField(
max_length=50,
choices=(
("rsa-sha1", _("RSA-SHA1")),
("rsa-sha256", _("RSA-SHA256")),
("ecdsa-sha256", _("ECDSA-SHA256")),
("dsa-sha1", _("DSA-SHA1")),
(RSA_SHA1, _("RSA-SHA1")),
(RSA_SHA256, _("RSA-SHA256")),
(RSA_SHA384, _("RSA-SHA384")),
(RSA_SHA512, _("RSA-SHA512")),
(DSA_SHA1, _("DSA-SHA1")),
),
default="rsa-sha256",
default=RSA_SHA256,
)
verification_kp = models.ForeignKey(
CertificateKeyPair,
default=None,
null=True,
help_text=_("If selected, incoming assertion's Signatures will be validated."),
blank=True,
help_text=_(
(
"When selected, incoming assertion's Signatures will be validated against this "
"certificate. To allow unsigned Requests, leave on default."
)
),
on_delete=models.SET_NULL,
verbose_name=_("Verification Keypair"),
verbose_name=_("Verification Certificate"),
related_name="+",
)
signing_kp = models.ForeignKey(
CertificateKeyPair,
default=None,
null=True,
help_text=_("Singing is enabled upon selection of a Key Pair."),
blank=True,
help_text=_(
"Keypair used to sign outgoing Responses going to the Service Provider."
),
on_delete=models.SET_NULL,
verbose_name=_("Signing Keypair"),
)
require_signing = models.BooleanField(
default=False,
help_text=_(
"Require Requests to be signed by an X509 Certificate. "
"Must match the Certificate selected in `Singing Keypair`."
),
)
@property
def launch_url(self) -> Optional[str]:
"""Guess launch_url based on acs URL"""

View File

@ -2,10 +2,10 @@
from hashlib import sha256
from types import GeneratorType
import xmlsec
from django.http import HttpRequest
from lxml import etree # nosec
from lxml.etree import Element, SubElement # nosec
from signxml import XMLSigner, XMLVerifier, strip_pem_header
from structlog import get_logger
from passbook.core.exceptions import PropertyMappingExpressionException
@ -16,14 +16,15 @@ from passbook.providers.saml.utils import get_random_id
from passbook.providers.saml.utils.time import get_time_string
from passbook.sources.saml.exceptions import UnsupportedNameIDFormat
from passbook.sources.saml.processors.constants import (
DIGEST_ALGORITHM_TRANSLATION_MAP,
NS_MAP,
NS_SAML_ASSERTION,
NS_SAML_PROTOCOL,
NS_SIGNATURE,
SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT,
SAML_NAME_ID_FORMAT_X509,
SIGN_ALGORITHM_TRANSFORM_MAP,
)
LOGGER = get_logger()
@ -186,12 +187,16 @@ class AssertionProcessor:
assertion.append(self.get_issuer())
if self.provider.signing_kp:
# We need a placeholder signature as SAML requires the signature to be between
# Issuer and subject
signature_placeholder = SubElement(
assertion, f"{{{NS_SIGNATURE}}}Signature", nsmap=NS_MAP
sign_algorithm_transform = SIGN_ALGORITHM_TRANSFORM_MAP.get(
self.provider.signature_algorithm, xmlsec.constants.TransformRsaSha1
)
signature_placeholder.attrib["Id"] = "placeholder"
signature = xmlsec.template.create(
assertion,
xmlsec.constants.TransformExclC14N,
sign_algorithm_transform,
ns="ds", # type: ignore
)
assertion.append(signature)
assertion.append(self.get_assertion_subject())
assertion.append(self.get_assertion_conditions())
@ -223,20 +228,36 @@ class AssertionProcessor:
"""Build string XML Response and sign if signing is enabled."""
root_response = self.get_response()
if self.provider.signing_kp:
signer = XMLSigner(
c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#",
signature_algorithm=self.provider.signature_algorithm,
digest_algorithm=self.provider.digest_algorithm,
digest_algorithm_transform = DIGEST_ALGORITHM_TRANSLATION_MAP.get(
self.provider.digest_algorithm, xmlsec.constants.TransformSha1
)
x509_data = strip_pem_header(
self.provider.signing_kp.certificate_data
).replace("\n", "")
signed = signer.sign(
root_response,
key=self.provider.signing_kp.private_key,
cert=[x509_data],
reference_uri=self._assertion_id,
assertion = root_response.xpath("//saml:Assertion", namespaces=NS_MAP)[0]
xmlsec.tree.add_ids(assertion, ["ID"])
signature_node = xmlsec.tree.find_node(
assertion, xmlsec.constants.NodeSignature
)
XMLVerifier().verify(signed, x509_cert=x509_data)
return etree.tostring(signed).decode("utf-8") # nosec
ref = xmlsec.template.add_reference(
signature_node,
digest_algorithm_transform,
uri="#" + self._assertion_id,
)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformEnveloped)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformExclC14N)
key_info = xmlsec.template.ensure_key_info(signature_node)
xmlsec.template.add_x509_data(key_info)
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
self.provider.signing_kp.key_data,
xmlsec.constants.KeyDataFormatPem,
None,
)
key.load_cert_from_memory(
self.provider.signing_kp.certificate_data,
xmlsec.constants.KeyDataFormatCertPem,
)
ctx.key = key
ctx.sign(signature_node)
return etree.tostring(root_response).decode("utf-8") # nosec

View File

@ -4,9 +4,9 @@ from typing import Iterator, Optional
from django.http import HttpRequest
from django.shortcuts import reverse
from lxml.etree import Element, SubElement, tostring # nosec
from signxml.util import strip_pem_header
from passbook.providers.saml.models import SAMLProvider
from passbook.providers.saml.utils.encoding import strip_pem_header
from passbook.sources.saml.processors.constants import (
NS_MAP,
NS_SAML_METADATA,
@ -42,7 +42,7 @@ class MetadataProcessor:
)
x509_certificate.text = strip_pem_header(
self.provider.signing_kp.certificate_data.replace("\r", "")
).replace("\n", "")
)
return key_descriptor
return None

View File

@ -4,22 +4,33 @@ from dataclasses import dataclass
from typing import Optional
from urllib.parse import quote_plus
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
import xmlsec
from defusedxml import ElementTree
from signxml import XMLVerifier
from lxml import etree # nosec
from structlog import get_logger
from passbook.providers.saml.exceptions import CannotHandleAssertion
from passbook.providers.saml.models import SAMLProvider
from passbook.providers.saml.utils.encoding import decode_base64_and_inflate
from passbook.sources.saml.processors.constants import (
DSA_SHA1,
NS_MAP,
NS_SAML_PROTOCOL,
RSA_SHA1,
RSA_SHA256,
RSA_SHA384,
RSA_SHA512,
SAML_NAME_ID_FORMAT_EMAIL,
)
LOGGER = get_logger()
ERROR_SIGNATURE_REQUIRED_BUT_ABSENT = (
"Verification Certificate configured, but request is not signed."
)
ERROR_SIGNATURE_EXISTS_BUT_NO_VERIFIER = (
"Provider does not have a Validation Certificate configured."
)
ERROR_FAILED_TO_VERIFY = "Failed to verify signature"
@dataclass
@ -67,16 +78,38 @@ class AuthNRequestParser:
def parse(self, saml_request: str, relay_state: Optional[str]) -> AuthNRequest:
"""Validate and parse raw request with enveloped signautre."""
decoded_xml = decode_base64_and_inflate(saml_request)
decoded_xml = b64decode(saml_request.encode()).decode()
verifier = self.provider.verification_kp
root = etree.fromstring(decoded_xml) # nosec
xmlsec.tree.add_ids(root, ["ID"])
signature_nodes = root.xpath(
"/samlp:AuthnRequest/ds:Signature", namespaces=NS_MAP
)
if len(signature_nodes) != 1:
raise CannotHandleAssertion(ERROR_SIGNATURE_REQUIRED_BUT_ABSENT)
signature_node = signature_nodes[0]
if verifier and signature_node is None:
raise CannotHandleAssertion(ERROR_SIGNATURE_REQUIRED_BUT_ABSENT)
if signature_node is not None:
if not verifier:
raise CannotHandleAssertion(ERROR_SIGNATURE_EXISTS_BUT_NO_VERIFIER)
if self.provider.verification_kp:
try:
XMLVerifier().verify(
decoded_xml,
x509_cert=self.provider.verification_kp.certificate_data,
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
verifier.certificate_data,
xmlsec.constants.KeyDataFormatCertPem,
None,
)
except InvalidSignature as exc:
raise CannotHandleAssertion("Failed to verify signature") from exc
ctx.key = key
ctx.verify(signature_node)
except xmlsec.VerificationError as exc:
raise CannotHandleAssertion(ERROR_FAILED_TO_VERIFY) from exc
return self._parse_xml(decoded_xml, relay_state)
@ -90,31 +123,45 @@ class AuthNRequestParser:
"""Validate and parse raw request with detached signature"""
decoded_xml = decode_base64_and_inflate(saml_request)
verifier = self.provider.verification_kp
if verifier and not (signature and sig_alg):
raise CannotHandleAssertion(ERROR_SIGNATURE_REQUIRED_BUT_ABSENT)
if signature and sig_alg:
# if sig_alg == "http://www.w3.org/2000/09/xmldsig#rsa-sha1":
sig_hash = hashes.SHA1() # nosec
if not verifier:
raise CannotHandleAssertion(ERROR_SIGNATURE_EXISTS_BUT_NO_VERIFIER)
querystring = f"SAMLRequest={quote_plus(saml_request)}&"
if relay_state is not None:
querystring += f"RelayState={quote_plus(relay_state)}&"
querystring += f"SigAlg={sig_alg}"
querystring += f"SigAlg={quote_plus(sig_alg)}"
dsig_ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
verifier.certificate_data, xmlsec.constants.KeyDataFormatCertPem, None
)
dsig_ctx.key = key
sign_algorithm_transform_map = {
DSA_SHA1: xmlsec.constants.TransformDsaSha1,
RSA_SHA1: xmlsec.constants.TransformRsaSha1,
RSA_SHA256: xmlsec.constants.TransformRsaSha256,
RSA_SHA384: xmlsec.constants.TransformRsaSha384,
RSA_SHA512: xmlsec.constants.TransformRsaSha512,
}
sign_algorithm_transform = sign_algorithm_transform_map.get(
sig_alg, xmlsec.constants.TransformRsaSha1
)
if not self.provider.verification_kp:
raise CannotHandleAssertion(
"Provider does not have a Validation Certificate configured."
)
public_key = self.provider.verification_kp.private_key.public_key()
try:
public_key.verify(
dsig_ctx.verify_binary(
querystring.encode("utf-8"),
sign_algorithm_transform,
b64decode(signature),
querystring.encode(),
padding.PSS(
mgf=padding.MGF1(sig_hash), salt_length=padding.PSS.MAX_LENGTH
),
sig_hash,
)
except InvalidSignature as exc:
raise CannotHandleAssertion("Failed to verify signature") from exc
except xmlsec.VerificationError as exc:
raise CannotHandleAssertion(ERROR_FAILED_TO_VERIFY) from exc
return self._parse_xml(decoded_xml, relay_state)
def idp_initiated(self) -> AuthNRequest:

View File

@ -1,4 +1,6 @@
"""Test AuthN Request generator and parser"""
from base64 import b64encode
from django.contrib.sessions.middleware import SessionMiddleware
from django.http.request import HttpRequest, QueryDict
from django.test import RequestFactory, TestCase
@ -6,18 +8,55 @@ from guardian.utils import get_anonymous_user
from passbook.crypto.models import CertificateKeyPair
from passbook.flows.models import Flow
from passbook.providers.saml.models import SAMLProvider
from passbook.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from passbook.providers.saml.processors.assertion import AssertionProcessor
from passbook.providers.saml.processors.request_parser import AuthNRequestParser
from passbook.providers.saml.utils.encoding import deflate_and_base64_encode
from passbook.sources.saml.exceptions import MismatchedRequestID
from passbook.sources.saml.models import SAMLSource
from passbook.sources.saml.processors.constants import SAML_NAME_ID_FORMAT_EMAIL
from passbook.sources.saml.processors.request import (
SESSION_REQUEST_ID,
RequestProcessor,
)
from passbook.sources.saml.processors.response import ResponseProcessor
REDIRECT_REQUEST = (
"fZLNbsIwEIRfJfIdbKeFgEUipXAoEm0jSHvopTLJplhK7NTr9Oft6yRUKhekPdk73+yOdoWyqVuRdu6k9/DRAbrgu6k1iu"
"EjJp3VwkhUKLRsAIUrxCF92IlwykRrjTOFqUmQIoJ1yui10dg1YA9gP1UBz/tdTE7OtSgo5WzKQzYditGeP8GW9rSQZk+H"
"nAQbb6+07EGj7EI1j8SCeaVs21oVQ9dAoRqcf6OIhh6VLpV+pxZKZaFwlATbTUzeyqKazaqiDCO5WEQwZzKCagkwr8obWc"
"qjb0PsYKvRSe1iErKQTTj3lYdc3HLBl68kyM4L340u19M5j4LiPs+zybjgC1gclvMNJFn104vB2P5L/TpW/kVNkqvBrug/"
"+mjVikeP224y4/P7CdK6Nl9rC9JBTDihySi5vIbkFw=="
)
REDIRECT_SIGNATURE = (
"UlOe1BItHVHM+io6rUZAenIqfibm7hM6wr9I1rcP5kPJ4N8cbkyqmAMh5LD2lUq3PDERJfjdO/oOKnvJmbD2y9MOObyR2d"
"7Udv62KERrA0qM917Q+w8wrLX7w2nHY96EDvkXD4iAomR5EE9dHRuubDy7uRv2syEevc0gfoLi7W/5vp96vJgsaSqxnTp+"
"QiYq49KyWyMtxRULF2yd+vYDnHCDME73mNSULEHfwCU71dvbKpnFaej78q7wS20gUk6ysOOXXtvDHbiVcpUb/9oyDgNAxU"
"jVvPdh96AhBFj2HCuGZhP0CGotafTciu6YlsiwUpuBkIYgZmNWYa3FR9LS4Q=="
)
REDIRECT_SIG_ALG = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
REDIRECT_RELAY_STATE = (
"ss:mem:7a054b4af44f34f89dd2d973f383c250b6b076e7f06cfa8276008a6504eaf3c7"
)
REDIRECT_CERT = """-----BEGIN CERTIFICATE-----
MIIDCDCCAfCgAwIBAgIRAM5s+bhOHk4ChSpPkGSh0NswDQYJKoZIhvcNAQELBQAw
KzEpMCcGA1UEAwwgcGFzc2Jvb2sgU2VsZi1zaWduZWQgQ2VydGlmaWNhdGUwHhcN
MjAxMTA3MjAzNDIxWhcNMjExMTA4MjAzNDIxWjBUMSkwJwYDVQQDDCBwYXNzYm9v
ayBTZWxmLXNpZ25lZCBDZXJ0aWZpY2F0ZTERMA8GA1UECgwIcGFzc2Jvb2sxFDAS
BgNVBAsMC1NlbGYtc2lnbmVkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAuh+Bv6a/ogpic72X/sq86YiLzVjixnGqjc4wpsPPP00GX8jUAZJL4Tjo+sYK
IU2DF2/azlVqjkbLho4rGuuc8YkbFXBEXPYc5h3bseO2vk6sbbbWKV0mro1VFhBh
T59hBORuMMefmQdhFzsRNOGklIptQdg0quD8ET3+/uNfIT98S2ruZdYteFls46Sa
MokZFYVD6pWEYV4P2MKVAFqJX9bqBW0LfCCfFqHAOJjUZj9dtleg86d2WfedUOG2
LK0iLrydjhThbI0GUDhv0jWYkRlv04fdJ1WSRANYA3gBOnyw+Iigh2xNnYbVZMXT
I0BupIJ4UoODMc4QpD2GYJ6oGwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCCEF3e
Y99KxEBSR4H4/TvKbnh4QtHswOf7MaGdjtrld7l4u4Hc4NEklNdDn1XLKhZwnq3Z
LRsRlJutDzZ18SRmAJPXPbka7z7D+LA1mbNQElOgiKyQHD9rIJSBr6X5SM9As3CR
7QUsb8dg7kc+Jn7WuLZIEVxxMtekt0buWEdMJiklF0tCS3LNsP083FaQk/H1K0z6
3PWP26EFdwir3RyTKLY5CBLjKrUAo9O1l/WBVFYbdetnipbGGu5f6nk6nnxbwLLI
Dm52Vkq+xFDDUq9IqIoYvLaE86MDvtpMQEx65tIGU19vUf3fL/+sSfdRZ1HDzP4d
qNAZMq1DqpibfCBg
-----END CERTIFICATE-----"""
def dummy_get_response(request: HttpRequest): # pragma: no cover
"""Dummy get_response for SessionMiddleware"""
@ -28,18 +67,21 @@ class TestAuthNRequest(TestCase):
"""Test AuthN Request generator and parser"""
def setUp(self):
self.provider = SAMLProvider.objects.create(
cert = CertificateKeyPair.objects.first()
self.provider: SAMLProvider = SAMLProvider.objects.create(
authorization_flow=Flow.objects.get(
slug="default-provider-authorization-implicit-consent"
),
acs_url="http://testserver/source/saml/provider/acs/",
signing_kp=CertificateKeyPair.objects.first(),
verification_kp=CertificateKeyPair.objects.first(),
signing_kp=cert,
verification_kp=cert,
)
self.provider.property_mappings.set(SAMLPropertyMapping.objects.all())
self.provider.save()
self.source = SAMLSource.objects.create(
slug="provider",
issuer="passbook",
signing_kp=CertificateKeyPair.objects.first(),
signing_kp=cert,
)
self.factory = RequestFactory()
@ -56,14 +98,15 @@ class TestAuthNRequest(TestCase):
request = request_proc.build_auth_n()
# Now we check the ID and signature
parsed_request = AuthNRequestParser(self.provider).parse(
deflate_and_base64_encode(request), "test_state"
b64encode(request.encode()).decode(), "test_state"
)
self.assertEqual(parsed_request.id, request_proc.request_id)
self.assertEqual(parsed_request.relay_state, "test_state")
def test_signed_valid_detached(self):
"""Test generated AuthNRequest with valid signature (detached)"""
def test_request_full_signed(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = self.factory.get("/")
http_request.user = get_anonymous_user()
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
@ -71,13 +114,22 @@ class TestAuthNRequest(TestCase):
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
params = request_proc.build_auth_n_detached()
# Now we check the ID and signature
parsed_request = AuthNRequestParser(self.provider).parse_detached(
params["SAMLRequest"], "test_state", params["Signature"], params["SigAlg"]
request = request_proc.build_auth_n()
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
self.assertEqual(parsed_request.id, request_proc.request_id)
self.assertEqual(parsed_request.relay_state, "test_state")
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
response = response_proc.build_response()
# Now parse the response (source)
http_request.POST = QueryDict(mutable=True)
http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
response_parser = ResponseProcessor(self.source)
response_parser.parse(http_request)
def test_request_id_invalid(self):
"""Test generated AuthNRequest with invalid request ID"""
@ -98,7 +150,7 @@ class TestAuthNRequest(TestCase):
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
deflate_and_base64_encode(request), "test_state"
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
@ -106,9 +158,54 @@ class TestAuthNRequest(TestCase):
# Now parse the response (source)
http_request.POST = QueryDict(mutable=True)
http_request.POST["SAMLResponse"] = deflate_and_base64_encode(response)
http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
response_parser = ResponseProcessor(self.source)
with self.assertRaises(MismatchedRequestID):
response_parser.parse(http_request)
def test_signed_valid_detached(self):
"""Test generated AuthNRequest with valid signature (detached)"""
http_request = self.factory.get("/")
middleware = SessionMiddleware(dummy_get_response)
middleware.process_request(http_request)
http_request.session.save()
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
params = request_proc.build_auth_n_detached()
# Now we check the ID and signature
parsed_request = AuthNRequestParser(self.provider).parse_detached(
params["SAMLRequest"],
params["RelayState"],
params["Signature"],
params["SigAlg"],
)
self.assertEqual(parsed_request.id, request_proc.request_id)
self.assertEqual(parsed_request.relay_state, "test_state")
def test_signed_detached_static(self):
"""Test request with detached signature,
taken from https://www.samltool.com/generic_sso_req.php"""
static_keypair = CertificateKeyPair.objects.create(
name="samltool", certificate_data=REDIRECT_CERT
)
provider = SAMLProvider(
name="samltool",
authorization_flow=Flow.objects.get(
slug="default-provider-authorization-implicit-consent"
),
acs_url="https://10.120.20.200/saml-sp/SAML2/POST",
audience="https://10.120.20.200/saml-sp/SAML2/POST",
issuer="https://10.120.20.200/saml-sp/SAML2/POST",
signing_kp=static_keypair,
verification_kp=static_keypair,
)
parsed_request = AuthNRequestParser(provider).parse_detached(
REDIRECT_REQUEST, REDIRECT_RELAY_STATE, REDIRECT_SIGNATURE, REDIRECT_SIG_ALG
)
self.assertEqual(parsed_request.id, "_dcf55fcd27a887e60a7ef9ee6fd3adab")
self.assertEqual(parsed_request.name_id_policy, SAML_NAME_ID_FORMAT_EMAIL)
self.assertEqual(parsed_request.relay_state, REDIRECT_RELAY_STATE)

View File

@ -2,6 +2,9 @@
import base64
import zlib
PEM_HEADER = "-----BEGIN CERTIFICATE-----"
PEM_FOOTER = "-----END CERTIFICATE-----"
def decode_base64_and_inflate(encoded: str, encoding="utf-8") -> str:
"""Base64 decode and ZLib decompress b64string"""
@ -22,3 +25,8 @@ def deflate_and_base64_encode(inflated: str, encoding="utf-8"):
def nice64(src: str) -> str:
"""Returns src base64-encoded and formatted nicely for our XML. """
return base64.b64encode(src.encode()).decode("utf-8").replace("\n", "")
def strip_pem_header(cert: str) -> str:
"""Remove PEM Headers"""
return cert.replace(PEM_HEADER, "").replace(PEM_FOOTER, "").replace("\n", "")

View File

@ -127,7 +127,7 @@ class SAMLSSOBindingPOSTView(SAMLSSOView):
def check_saml_request(self) -> Optional[HttpRequest]:
"""Handle POST bindings"""
if REQUEST_KEY_SAML_REQUEST not in self.request.POST:
LOGGER.info("handle_saml_request: SAML payload missing")
LOGGER.info("check_saml_request: SAML payload missing")
return bad_request_message(
self.request, "The SAML request payload is missing."
)

View File

@ -6,23 +6,27 @@ It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""
import os
import typing
from time import time
from typing import Any, ByteString, Dict
import django
from asgiref.compatibility import guarantee_single_callable
from channels.routing import ProtocolTypeRouter, URLRouter
from defusedxml import defuse_stdlib
from django.core.asgi import get_asgi_application
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
from structlog import get_logger
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings")
# DJANGO_SETTINGS_MODULE is set in gunicorn.conf.py
defuse_stdlib()
django.setup()
# pylint: disable=wrong-import-position
from passbook.root import websocket # noqa # isort:skip
# See https://github.com/encode/starlette/blob/master/starlette/types.py
Scope = typing.MutableMapping[str, typing.Any]
Message = typing.MutableMapping[str, typing.Any]
@ -129,5 +133,14 @@ class ASGILogger:
application = ASGILogger(
guarantee_single_callable(SentryAsgiMiddleware(get_asgi_application()))
guarantee_single_callable(
SentryAsgiMiddleware(
ProtocolTypeRouter(
{
"http": get_asgi_application(),
"websocket": URLRouter(websocket.websocket_urlpatterns),
}
)
)
)
)

View File

@ -1,14 +0,0 @@
"""root Websocket URLS"""
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from passbook.outposts.channels import OutpostConsumer
application = ProtocolTypeRouter(
{
# (http->django views is added by default)
"websocket": URLRouter(
[path("ws/outpost/<uuid:pk>/", OutpostConsumer.as_asgi())]
),
}
)

View File

@ -208,7 +208,7 @@ TEMPLATES = [
},
]
ASGI_APPLICATION = "passbook.root.routing.application"
ASGI_APPLICATION = "passbook.root.asgi.application"
CHANNEL_LAYERS = {
"default": {

View File

@ -0,0 +1,6 @@
"""root Websocket URLS"""
from django.urls import path
from passbook.outposts.channels import OutpostConsumer
websocket_urlpatterns = [path("ws/outpost/<uuid:pk>/", OutpostConsumer.as_asgi())]

View File

@ -19,8 +19,10 @@ class SAMLSourceSerializer(ModelSerializer):
"allow_idp_initiated",
"name_id_policy",
"binding_type",
"temporary_user_delete_after",
"signing_kp",
"digest_algorithm",
"signature_algorithm",
"temporary_user_delete_after",
]

View File

@ -12,3 +12,7 @@ class UnsupportedNameIDFormat(SentryIgnoredException):
class MismatchedRequestID(SentryIgnoredException):
"""Exception raised when the returned request ID doesn't match the saved ID."""
class InvalidSignature(SentryIgnoredException):
"""Signature of XML Object is either missing or invalid"""

View File

@ -35,8 +35,10 @@ class SAMLSourceForm(forms.ModelForm):
"binding_type",
"name_id_policy",
"allow_idp_initiated",
"temporary_user_delete_after",
"signing_kp",
"digest_algorithm",
"signature_algorithm",
"temporary_user_delete_after",
]
widgets = {
"name": forms.TextInput(),

View File

@ -0,0 +1,51 @@
# Generated by Django 3.1.3 on 2020-11-12 10:55
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_crypto", "0002_create_self_signed_kp"),
("passbook_sources_saml", "0006_samlsource_allow_idp_initiated"),
]
operations = [
migrations.AddField(
model_name="samlsource",
name="digest_algorithm",
field=models.CharField(
choices=[("sha1", "SHA1"), ("sha256", "SHA256")],
default="sha256",
max_length=50,
),
),
migrations.AddField(
model_name="samlsource",
name="signature_algorithm",
field=models.CharField(
choices=[
("rsa-sha1", "RSA-SHA1"),
("rsa-sha256", "RSA-SHA256"),
("ecdsa-sha256", "ECDSA-SHA256"),
("dsa-sha1", "DSA-SHA1"),
],
default="rsa-sha256",
max_length=50,
),
),
migrations.AlterField(
model_name="samlsource",
name="signing_kp",
field=models.ForeignKey(
blank=True,
default=None,
help_text="Keypair which is used to sign outgoing requests. Leave empty to disable signing.",
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="passbook_crypto.certificatekeypair",
verbose_name="Singing Keypair",
),
),
]

View File

@ -0,0 +1,70 @@
# Generated by Django 3.1.3 on 2020-11-12 20:16
from django.apps.registry import Apps
from django.db import migrations, models
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from passbook.sources.saml.processors import constants
def update_algorithms(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
SAMLSource = apps.get_model("passbook_sources_saml", "SAMLSource")
signature_translation_map = {
"rsa-sha1": constants.RSA_SHA1,
"rsa-sha256": constants.RSA_SHA256,
"ecdsa-sha256": constants.RSA_SHA256,
"dsa-sha1": constants.DSA_SHA1,
}
digest_translation_map = {
"sha1": constants.SHA1,
"sha256": constants.SHA256,
}
for source in SAMLSource.objects.all():
source.signature_algorithm = signature_translation_map.get(
source.signature_algorithm, constants.RSA_SHA256
)
source.digest_algorithm = digest_translation_map.get(
source.digest_algorithm, constants.SHA256
)
source.save()
class Migration(migrations.Migration):
dependencies = [
("passbook_sources_saml", "0007_auto_20201112_1055"),
]
operations = [
migrations.AlterField(
model_name="samlsource",
name="signature_algorithm",
field=models.CharField(
choices=[
(constants.RSA_SHA1, "RSA-SHA1"),
(constants.RSA_SHA256, "RSA-SHA256"),
(constants.RSA_SHA384, "RSA-SHA384"),
(constants.RSA_SHA512, "RSA-SHA512"),
(constants.DSA_SHA1, "DSA-SHA1"),
],
default=constants.RSA_SHA256,
max_length=50,
),
),
migrations.AlterField(
model_name="samlsource",
name="digest_algorithm",
field=models.CharField(
choices=[
(constants.SHA1, "SHA1"),
(constants.SHA256, "SHA256"),
(constants.SHA384, "SHA384"),
(constants.SHA512, "SHA512"),
],
default=constants.SHA256,
max_length=50,
),
),
migrations.RunPython(update_algorithms),
]

View File

@ -13,11 +13,20 @@ from passbook.core.types import UILoginButton
from passbook.crypto.models import CertificateKeyPair
from passbook.lib.utils.time import timedelta_string_validator
from passbook.sources.saml.processors.constants import (
DSA_SHA1,
RSA_SHA1,
RSA_SHA256,
RSA_SHA384,
RSA_SHA512,
SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT,
SAML_NAME_ID_FORMAT_WINDOWS,
SAML_NAME_ID_FORMAT_X509,
SHA1,
SHA256,
SHA384,
SHA512,
)
@ -96,11 +105,36 @@ class SAMLSource(Source):
signing_kp = models.ForeignKey(
CertificateKeyPair,
default=None,
blank=True,
null=True,
verbose_name=_("Singing Keypair"),
help_text=_(
"Certificate Key Pair of the IdP which Assertion's Signature is validated against."
"Keypair which is used to sign outgoing requests. Leave empty to disable signing."
),
on_delete=models.PROTECT,
on_delete=models.SET_DEFAULT,
)
digest_algorithm = models.CharField(
max_length=50,
choices=(
(SHA1, _("SHA1")),
(SHA256, _("SHA256")),
(SHA384, _("SHA384")),
(SHA512, _("SHA512")),
),
default=SHA256,
)
signature_algorithm = models.CharField(
max_length=50,
choices=(
(RSA_SHA1, _("RSA-SHA1")),
(RSA_SHA256, _("RSA-SHA256")),
(RSA_SHA384, _("RSA-SHA384")),
(RSA_SHA512, _("RSA-SHA512")),
(DSA_SHA1, _("DSA-SHA1")),
),
default=RSA_SHA256,
)
@property

View File

@ -1,4 +1,6 @@
"""SAML Source processor constants"""
import xmlsec
NS_SAML_PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
NS_SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
NS_SAML_METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
@ -21,3 +23,29 @@ SAML_NAME_ID_FORMAT_TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:trans
SAML_BINDING_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
SAML_BINDING_REDIRECT = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
SHA512 = "http://www.w3.org/2001/04/xmlenc#sha512"
SIGN_ALGORITHM_TRANSFORM_MAP = {
DSA_SHA1: xmlsec.constants.TransformDsaSha1,
RSA_SHA1: xmlsec.constants.TransformRsaSha1,
RSA_SHA256: xmlsec.constants.TransformRsaSha256,
RSA_SHA384: xmlsec.constants.TransformRsaSha384,
RSA_SHA512: xmlsec.constants.TransformRsaSha512,
}
DIGEST_ALGORITHM_TRANSLATION_MAP = {
SHA1: xmlsec.constants.TransformSha1,
SHA256: xmlsec.constants.TransformSha256,
SHA384: xmlsec.constants.TransformSha384,
SHA512: xmlsec.constants.TransformSha512,
}

View File

@ -3,8 +3,8 @@ from typing import Iterator, Optional
from django.http import HttpRequest
from lxml.etree import Element, SubElement, tostring # nosec
from signxml.util import strip_pem_header
from passbook.providers.saml.utils.encoding import strip_pem_header
from passbook.sources.saml.models import SAMLSource
from passbook.sources.saml.processors.constants import (
NS_MAP,

View File

@ -3,21 +3,21 @@ from base64 import b64encode
from typing import Dict
from urllib.parse import quote_plus
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
import xmlsec
from django.http import HttpRequest
from lxml import etree # nosec
from lxml.etree import Element # nosec
from signxml import XMLSigner
from passbook.providers.saml.utils import get_random_id
from passbook.providers.saml.utils.encoding import deflate_and_base64_encode
from passbook.providers.saml.utils.time import get_time_string
from passbook.sources.saml.models import SAMLSource
from passbook.sources.saml.processors.constants import (
DIGEST_ALGORITHM_TRANSLATION_MAP,
NS_MAP,
NS_SAML_ASSERTION,
NS_SAML_PROTOCOL,
SIGN_ALGORITHM_TRANSFORM_MAP,
)
SESSION_REQUEST_ID = "passbook_source_saml_request_id"
@ -51,7 +51,7 @@ class RequestProcessor:
def get_name_id_policy(self) -> Element:
"""Get NameID Policy Element"""
name_id_policy = Element(f"{{{NS_SAML_PROTOCOL}}}NameIDPolicy")
name_id_policy.text = self.source.name_id_policy
name_id_policy.attrib["Format"] = self.source.name_id_policy
return name_id_policy
def get_auth_n(self) -> Element:
@ -67,6 +67,19 @@ class RequestProcessor:
auth_n_request.attrib["Version"] = "2.0"
# Create issuer object
auth_n_request.append(self.get_issuer())
if self.source.signing_kp:
sign_algorithm_transform = SIGN_ALGORITHM_TRANSFORM_MAP.get(
self.source.signature_algorithm, xmlsec.constants.TransformRsaSha1
)
signature = xmlsec.template.create(
auth_n_request,
xmlsec.constants.TransformExclC14N,
sign_algorithm_transform,
ns="ds", # type: ignore
)
auth_n_request.append(signature)
# Create NameID Policy Object
auth_n_request.append(self.get_name_id_policy())
return auth_n_request
@ -77,12 +90,38 @@ class RequestProcessor:
auth_n_request = self.get_auth_n()
if self.source.signing_kp:
signed_request = XMLSigner().sign(
auth_n_request,
cert=self.source.signing_kp.certificate_data,
key=self.source.signing_kp.key_data,
xmlsec.tree.add_ids(auth_n_request, ["ID"])
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
self.source.signing_kp.key_data, xmlsec.constants.KeyDataFormatPem, None
)
return etree.tostring(signed_request).decode()
key.load_cert_from_memory(
self.source.signing_kp.certificate_data,
xmlsec.constants.KeyDataFormatCertPem,
)
ctx.key = key
digest_algorithm_transform = DIGEST_ALGORITHM_TRANSLATION_MAP.get(
self.source.digest_algorithm, xmlsec.constants.TransformSha1
)
signature_node = xmlsec.tree.find_node(
auth_n_request, xmlsec.constants.NodeSignature
)
ref = xmlsec.template.add_reference(
signature_node,
digest_algorithm_transform,
uri="#" + auth_n_request.attrib["ID"],
)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformEnveloped)
xmlsec.template.add_transform(ref, xmlsec.constants.TransformExclC14N)
key_info = xmlsec.template.ensure_key_info(signature_node)
xmlsec.template.add_x509_data(key_info)
ctx.sign(signature_node)
return etree.tostring(auth_n_request).decode()
@ -103,22 +142,31 @@ class RequestProcessor:
response_dict["RelayState"] = self.relay_state
if self.source.signing_kp:
sig_alg = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
sig_hash = hashes.SHA1() # nosec
sign_algorithm_transform = SIGN_ALGORITHM_TRANSFORM_MAP.get(
self.source.signature_algorithm, xmlsec.constants.TransformRsaSha1
)
# Create the full querystring in the correct order to be signed
querystring = f"SAMLRequest={quote_plus(saml_request)}&"
if self.relay_state != "":
querystring += f"RelayState={quote_plus(self.relay_state)}&"
querystring += f"SigAlg={sig_alg}"
if "RelayState" in response_dict:
querystring += f"RelayState={quote_plus(response_dict['RelayState'])}&"
querystring += f"SigAlg={quote_plus(self.source.signature_algorithm)}"
signature = self.source.signing_kp.private_key.sign(
querystring.encode(),
padding.PSS(
mgf=padding.MGF1(sig_hash), salt_length=padding.PSS.MAX_LENGTH
),
sig_hash,
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
self.source.signing_kp.key_data, xmlsec.constants.KeyDataFormatPem, None
)
key.load_cert_from_memory(
self.source.signing_kp.certificate_data,
xmlsec.constants.KeyDataFormatPem,
)
ctx.key = key
signature = ctx.sign_binary(
querystring.encode("utf-8"), sign_algorithm_transform
)
response_dict["SigAlg"] = sig_alg
response_dict["Signature"] = b64encode(signature).decode()
response_dict["SigAlg"] = self.source.signature_algorithm
return response_dict

View File

@ -1,11 +1,12 @@
"""passbook saml source processor"""
from typing import TYPE_CHECKING, Dict
from base64 import b64decode
from typing import TYPE_CHECKING, Any, Dict
from defusedxml import ElementTree
import xmlsec
from defusedxml.lxml import fromstring
from django.core.cache import cache
from django.core.exceptions import SuspiciousOperation
from django.http import HttpRequest, HttpResponse
from signxml import XMLVerifier
from structlog import get_logger
from passbook.core.models import User
@ -18,14 +19,15 @@ from passbook.flows.planner import (
from passbook.flows.views import SESSION_KEY_PLAN
from passbook.lib.utils.urls import redirect_with_qs
from passbook.policies.utils import delete_none_keys
from passbook.providers.saml.utils.encoding import decode_base64_and_inflate
from passbook.sources.saml.exceptions import (
InvalidSignature,
MismatchedRequestID,
MissingSAMLResponse,
UnsupportedNameIDFormat,
)
from passbook.sources.saml.models import SAMLSource
from passbook.sources.saml.processors.constants import (
NS_MAP,
SAML_NAME_ID_FORMAT_EMAIL,
SAML_NAME_ID_FORMAT_PERSISTENT,
SAML_NAME_ID_FORMAT_TRANSIENT,
@ -49,7 +51,7 @@ class ResponseProcessor:
_source: SAMLSource
_root: "Element"
_root: Any
_root_xml: str
def __init__(self, source: SAMLSource):
@ -61,20 +63,36 @@ class ResponseProcessor:
raw_response = request.POST.get("SAMLResponse", None)
if not raw_response:
raise MissingSAMLResponse("Request does not contain 'SAMLResponse'")
# relay_state = request.POST.get('RelayState', None)
# Check if response is compressed, b64 decode it
self._root_xml = decode_base64_and_inflate(raw_response)
self._root = ElementTree.fromstring(self._root_xml)
self._root_xml = b64decode(raw_response.encode()).decode()
self._root = fromstring(self._root_xml)
self._verify_signed()
if self._source.signing_kp:
self._verify_signed()
self._verify_request_id(request)
def _verify_signed(self):
"""Verify SAML Response's Signature"""
verifier = XMLVerifier()
verifier.verify(
self._root_xml, x509_cert=self._source.signing_kp.certificate_data
signature_nodes = self._root.xpath(
"/samlp:Response/saml:Assertion/ds:Signature", namespaces=NS_MAP
)
if len(signature_nodes) != 1:
raise InvalidSignature()
signature_node = signature_nodes[0]
xmlsec.tree.add_ids(self._root, ["ID"])
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_memory(
self._source.signing_kp.certificate_data,
xmlsec.constants.KeyDataFormatCertPem,
)
ctx.key = key
ctx.set_enabled_key_data([xmlsec.constants.KeyDataX509])
try:
ctx.verify(signature_node)
except (xmlsec.InternalError, xmlsec.VerificationError) as exc:
raise InvalidSignature from exc
LOGGER.debug("Successfully verified signautre")
def _verify_request_id(self, request: HttpRequest):

View File

@ -8,7 +8,7 @@ from django.utils.http import urlencode
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from signxml import InvalidSignature
from xmlsec import VerificationError
from passbook.lib.views import bad_request_message
from passbook.providers.saml.utils.encoding import nice64
@ -77,7 +77,7 @@ class ACSView(View):
processor.parse(request)
except MissingSAMLResponse as exc:
return bad_request_message(request, str(exc))
except InvalidSignature as exc:
except VerificationError as exc:
return bad_request_message(request, str(exc))
try:

View File

@ -112,7 +112,6 @@ class PasswordStageView(FormView, StageView):
# No user was found -> invalid credentials
LOGGER.debug("Invalid credentials")
# Manually inject error into form
# pylint: disable=protected-access
errors = form._errors.setdefault("password", ErrorList())
errors.append(_("Invalid password"))
return self.form_invalid(form)

View File

@ -22,7 +22,7 @@
}
.pb-brand > img {
max-height: 76px;
max-height: 68px;
margin-right: .5em;
}

View File

@ -34,11 +34,12 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1 // indirect
go.mongodb.org/mongo-driver v1.4.3 // indirect
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de // indirect
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 // indirect
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba // indirect
golang.org/x/text v0.3.4 // indirect
golang.org/x/tools v0.0.0-20201102043006-b53d4cbd60a6 // indirect
golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
)

View File

@ -664,6 +664,8 @@ go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS
go.mongodb.org/mongo-driver v1.3.5/go.mod h1:Ual6Gkco7ZGQw8wE1t4tLnvBsf6yVSM60qW6TgOeJ5c=
go.mongodb.org/mongo-driver v1.4.2 h1:WlnEglfTg/PfPq4WXs2Vkl/5ICC6hoG8+r+LraPmGk4=
go.mongodb.org/mongo-driver v1.4.2/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.4.3 h1:moga+uhicpVshTyaqY9L23E6QqwcHRUv1sqyOsoyOO8=
go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@ -773,6 +775,8 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@ -845,6 +849,8 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba h1:xmhUJGQGbxlod18iJGqVEp9cHIPLl7QiX2aA3to708s=
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -913,8 +919,8 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f h1:33yHANSyO/TeglgY9rBhUpX43wtonTXoFOsMRtNB6qE=
golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201102043006-b53d4cbd60a6 h1:vTr2e3iWbC27MMR83IzSZeEKQSzJAhwDM+Ld5YSaHA0=
golang.org/x/tools v0.0.0-20201102043006-b53d4cbd60a6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b h1:Ych5r0Z6MLML1fgf5hTg9p5bV56Xqx9xv9hLgMBATWs=
golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -1,3 +1,3 @@
package pkg
const VERSION = "0.12.9-stable"
const VERSION = "0.12.11-stable"

View File

@ -1,7 +1,7 @@
#!/bin/bash -xe
wget -q -O - https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash
VERSION=3.8.5
VERSION=3.9.0
wget https://www.python.org/ftp/python/$VERSION/Python-$VERSION.tgz
tar xvzf Python-$VERSION.tgz
@ -12,5 +12,5 @@ make -j 8
sudo make altinstall
touch $HOME/_work/_tool/Python/$VERSION/x64.complete
ln -s $HOME/_work/_tool/Python/3.8.5/x64 $HOME/_work/_tool/Python/3/x64
ln -s $HOME/_work/_tool/Python/3.8.5/x64 $HOME/_work/_tool/Python/3.8/x64
ln -s $HOME/_work/_tool/Python/3.9.5/x64 $HOME/_work/_tool/Python/3/x64
ln -s $HOME/_work/_tool/Python/3.9.5/x64 $HOME/_work/_tool/Python/3.9/x64

View File

@ -1,4 +1,4 @@
FROM python:3.8-slim-buster as locker
FROM python:3.9-slim-buster as locker
COPY ./Pipfile /app/
COPY ./Pipfile.lock /app/
@ -9,7 +9,7 @@ RUN pip install pipenv && \
pipenv lock -r > requirements.txt && \
pipenv lock -rd > requirements-dev.txt
FROM python:3.8-slim-buster as static-build
FROM python:3.9-slim-buster as static-build
COPY --from=locker /app/requirements.txt /app/
COPY --from=locker /app/requirements-dev.txt /app/
@ -17,7 +17,7 @@ COPY --from=locker /app/requirements-dev.txt /app/
WORKDIR /app/
RUN apt-get update && \
apt-get install -y --no-install-recommends postgresql-client-11 build-essential && \
apt-get install -y --no-install-recommends postgresql-client-11 build-essential libxmlsec1-dev pkg-config && \
rm -rf /var/lib/apt/ && \
pip install -r requirements.txt --no-cache-dir && \
apt-get remove --purge -y build-essential && \

View File

@ -7431,7 +7431,6 @@ definitions:
required:
- name
- acs_url
- issuer
type: object
properties:
pk:
@ -7450,6 +7449,7 @@ definitions:
minLength: 1
audience:
title: Audience
description: Value of the audience restriction field of the asseration.
type: string
minLength: 1
issuer:
@ -7485,30 +7485,30 @@ definitions:
title: Digest algorithm
type: string
enum:
- sha1
- sha256
- http://www.w3.org/2000/09/xmldsig#sha1
- http://www.w3.org/2001/04/xmlenc#sha256
- http://www.w3.org/2001/04/xmldsig-more#sha384
- http://www.w3.org/2001/04/xmlenc#sha512
signature_algorithm:
title: Signature algorithm
type: string
enum:
- rsa-sha1
- rsa-sha256
- ecdsa-sha256
- dsa-sha1
- http://www.w3.org/2000/09/xmldsig#rsa-sha1
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
- http://www.w3.org/2000/09/xmldsig#dsa-sha1
signing_kp:
title: Signing Keypair
description: Singing is enabled upon selection of a Key Pair.
description: Keypair used to sign outgoing Responses going to the Service
Provider.
type: string
format: uuid
x-nullable: true
require_signing:
title: Require signing
description: Require Requests to be signed by an X509 Certificate. Must match
the Certificate selected in `Singing Keypair`.
type: boolean
verification_kp:
title: Verification Keypair
description: If selected, incoming assertion's Signatures will be validated.
title: Verification Certificate
description: When selected, incoming assertion's Signatures will be validated
against this certificate. To allow unsigned Requests, leave on default.
type: string
format: uuid
x-nullable: true
@ -7782,7 +7782,6 @@ definitions:
- name
- slug
- sso_url
- signing_kp
type: object
properties:
name:
@ -7854,6 +7853,30 @@ definitions:
- REDIRECT
- POST
- POST_AUTO
signing_kp:
title: Singing Keypair
description: Keypair which is used to sign outgoing requests. Leave empty
to disable signing.
type: string
format: uuid
x-nullable: true
digest_algorithm:
title: Digest algorithm
type: string
enum:
- http://www.w3.org/2000/09/xmldsig#sha1
- http://www.w3.org/2001/04/xmlenc#sha256
- http://www.w3.org/2001/04/xmldsig-more#sha384
- http://www.w3.org/2001/04/xmlenc#sha512
signature_algorithm:
title: Signature algorithm
type: string
enum:
- http://www.w3.org/2000/09/xmldsig#rsa-sha1
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
- http://www.w3.org/2000/09/xmldsig#dsa-sha1
temporary_user_delete_after:
title: Delete temporary users after
description: "Time offset when temporary users should be deleted. This only\
@ -7861,12 +7884,6 @@ definitions:
\ log out manually. (Format: hours=1;minutes=2;seconds=3)."
type: string
minLength: 1
signing_kp:
title: Singing Keypair
description: Certificate Key Pair of the IdP which Assertion's Signature is
validated against.
type: string
format: uuid
Stage:
description: Stage Serializer
required:

20
website/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

4
website/.prettierignore Normal file
View File

@ -0,0 +1,4 @@
# Ignore artifacts:
build
coverage
.docusaurus

1
website/.prettierrc.json Normal file
View File

@ -0,0 +1 @@
{}

33
website/README.md Normal file
View File

@ -0,0 +1,33 @@
# Website
This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.
## Installation
```console
yarn install
```
## Local Development
```console
yarn start
```
This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
## Build
```console
yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
## Deployment
```console
GIT_USER=<Your GitHub username> USE_SSH=true yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.

3
website/babel.config.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
};

View File

@ -1,16 +1,19 @@
# Expressions
---
title: Expressions
---
Expressions allow you to write custom logic using Python code.
Expressions are used in different places throughout passbook, and can do different things.
!!! info
These functions/objects are available wherever expressions are used. For more specific information, see [Expression Policies](../policies/expression.md) and [Property Mappings](../property-mappings/expression.md)
:::info
These functions/objects are available wherever expressions are used. For more specific information, see [Expression Policies](../policies/expression.md) and [Property Mappings](../property-mappings/expression.md)
:::
## Global objects
- `pb_logger`: structlog BoundLogger. ([ref](https://www.structlog.org/en/stable/api.html#structlog.BoundLogger))
- `requests`: requests Session object. ([ref](https://requests.readthedocs.io/en/master/user/advanced/))
- `pb_logger`: structlog BoundLogger. ([ref](https://www.structlog.org/en/stable/api.html#structlog.BoundLogger))
- `requests`: requests Session object. ([ref](https://requests.readthedocs.io/en/master/user/advanced/))
## Generally available functions

View File

@ -0,0 +1,29 @@
---
title: User Object
---
The User object has the following attributes:
- `username`: User's username.
- `email` User's email.
- `name` User's display name.
- `is_staff` Boolean field if user is staff.
- `is_active` Boolean field if user is active.
- `date_joined` Date user joined/was created.
- `password_change_date` Date password was last changed.
- `attributes` Dynamic attributes.
- `group_attributes` Merged attributes of all groups the user is member of and the user's own attributes.
- `pb_groups` This is a queryset of all the user's groups.
You can do additional filtering like `user.pb_groups.filter(name__startswith='test')`, see [here](https://docs.djangoproject.com/en/3.1/ref/models/querysets/#id4)
To get the name of all groups, you can do `[group.name for group in user.pb_groups.all()]`
## Examples
List all the User's group names:
```python
for group in user.pb_groups.all():
yield group.name
```

View File

@ -1,18 +1,20 @@
# Example Flows
!!! info
You can apply theses flows multiple times to stay updated, however this will discard all changes you've made.
---
title: Example Flows
---
:::info
You can apply theses flows multiple times to stay updated, however this will discard all changes you've made.
:::
## Enrollment (2 Stage)
Flow: right-click [here](enrollment-2-stage.json) and save the file.
Flow: right-click [here](/flows/enrollment-2-stage.pbflow) and save the file.
Sign-up flow for new users, which prompts them for their username, email, password and name. No verification is done. Users are also immediately logged on after this flow.
## Enrollment with email verification
Flow: right-click [here](enrollment-email-verification.json) and save the file.
Flow: right-click [here](/flows/enrollment-email-verification.pbflow) and save the file.
Same flow as above, with an extra email verification stage.
@ -20,13 +22,13 @@ You'll probably have to adjust the Email stage and set your connection details.
## Two-factor Login
Flow: right-click [here](login-2fa.json) and save the file.
Flow: right-click [here](/flows/login-2fa.pbflow) and save the file.
Login flow which follows the default pattern (username/email, then password), but also checks for the user's OTP token, if they have one configured
## Login with conditional Captcha
Flow: right-click [here](login-conditional-captcha.json) and save the file.
Flow: right-click [here](/flows/login-conditional-captcha.pbflow) and save the file.
Login flow which conditionally shows the users a captcha, based on the reputation of their IP and Username.
@ -34,16 +36,16 @@ By default, the captcha test keys are used. You can get a proper key [here](http
## Recovery with email verification
Flow: right-click [here](recovery-email-verification.json) and save the file.
Flow: right-click [here](/flows/recovery-email-verification.pbflow) and save the file.
Recovery flow, the user is sent an email after they've identified themselves. After they click on the link in the email, they are prompted for a new password and immediately logged on.
## User deletion
Flow: right-click [here](unenrollment.json) and save the file.
Flow: right-click [here](/flows/unenrollment.pbflow) and save the file.
Flow for users to delete their account,
!!! warning
This is done without any warning.
:::warning
This is done without any warning.
:::

View File

@ -1,4 +1,6 @@
# Flows
---
title: Flows
---
Flows are a method of describing a sequence of stages. A stage represents a single verification or logic step. They are used to authenticate users, enroll them, and more.

View File

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

View File

@ -1,4 +1,6 @@
# Captcha stage
---
title: Captcha stage
---
This stage adds a form of verification using [Google's ReCaptcha](https://www.google.com/recaptcha/intro/v3.html).

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -1,4 +1,6 @@
# Dummy stage
---
title: Dummy stage
---
This stage is used for development and has no function. It presents the user with a form which requires a single confirmation.

View File

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -1,4 +1,6 @@
# Email
---
title: Email stage
---
This stage can be used for email verification. passbook's background worker will send an email using the specified connection details. When an email can't be delivered, delivery is automatically retried periodically.

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