Compare commits

...

26 Commits

Author SHA1 Message Date
d7481c9de7 new release: 0.7.13-beta 2020-02-14 15:35:05 +01:00
571373866e providers/saml: some more cleanup, fix get_time_string when called without argument 2020-02-14 15:34:24 +01:00
e36d7928e4 providers/saml: big cleanup, simplify base processor
add New fields for
 - assertion_valid_not_before
 - assertion_valid_not_on_or_after
 - session_valid_not_on_or_after
allow flexible time durations for these fields
fall back to Provider's ACS if none is specified in AuthNRequest
2020-02-14 15:19:48 +01:00
2be026dd44 global: fix import order 2020-02-14 15:17:40 +01:00
d5b9de3569 Merge pull request #3 from BeryJu/dependabot/pip/django-2.2.10
build(deps): bump django from 2.2.9 to 2.2.10
2020-02-12 09:31:13 +01:00
e22620b0ec build(deps): bump django from 2.2.9 to 2.2.10
Bumps [django](https://github.com/django/django) from 2.2.9 to 2.2.10.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/2.2.9...2.2.10)

Signed-off-by: dependabot[bot] <support@github.com>
2020-02-12 03:20:09 +00:00
ba74a3213d *: upgrade python 3.7 to 3.8 2020-01-19 21:03:01 +01:00
d9ecb7070d core: add more prometheus metrics 2020-01-19 21:01:26 +01:00
fc4a46bd9c root: fix credential variables overwriting each other 2020-01-17 11:16:23 +01:00
78301b7bab docs: fix site_url 2020-01-17 10:55:20 +01:00
7bf7bde856 root: fix prometheus path in ServiceMonitor, return WWW-Authenticate header so basic auth is sent 2020-01-17 10:55:11 +01:00
9bdff14403 providers/app_gw: fix wrong UPSTREAM parameter 2020-01-03 09:15:07 +01:00
f124314eab new release: 0.7.12-beta 2020-01-02 20:22:44 +01:00
684e4ffdcf providers/app_gw: fix formatting 2020-01-02 20:22:36 +01:00
d9ff5c69c8 providers/app_gw: fix assignment of response_types 2020-01-02 20:20:10 +01:00
8142e3df45 providers/oidc: fix application property of wrong object being used 2020-01-02 20:19:53 +01:00
73920899de static: use current pixie image 2020-01-02 20:09:30 +01:00
13666965a7 actions: fix build over gatekeeper 2020-01-02 16:55:30 +01:00
86f16e2781 providers/oidc: fix incorrectly sorted imports 2020-01-02 16:42:52 +01:00
2ed8e72c62 new release: 0.7.11-beta 2020-01-02 16:38:11 +01:00
edeed18ae8 providers/oidc: fix error when using with app_gw 2020-01-02 16:38:01 +01:00
d24133d8a2 core: fix _redirect_with_qs appending an array to the URL 2020-01-02 16:14:56 +01:00
b9733e56aa providers/app_gw: fix passbook domain being empty 2020-01-02 16:09:17 +01:00
cd34413914 providers/app_gw: separate host field into external_ and internal_ 2020-01-02 16:09:04 +01:00
c3a4a76d43 providers/app_gw: fix Client's response_type not being set 2020-01-02 16:06:32 +01:00
a59a29b256 actions: also build gatekeeper on release 2020-01-02 15:55:39 +01:00
45 changed files with 863 additions and 714 deletions

View File

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

View File

@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
python-version: '3.7'
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
python-version: '3.7'
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
@ -48,7 +48,7 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
python-version: '3.7'
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
@ -65,7 +65,7 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
python-version: '3.7'
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
@ -100,7 +100,7 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
python-version: '3.7'
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
@ -134,7 +134,7 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
python-version: '3.7'
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/

View File

@ -16,13 +16,34 @@ jobs:
- name: Building Docker Image
run: docker build
--no-cache
-t beryju/passbook:0.7.10-beta
-t beryju/passbook:0.7.13-beta
-t beryju/passbook:latest
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook:0.7.10-beta
run: docker push beryju/passbook:0.7.13-beta
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook:latest
build-gatekeeper:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Docker Login Registry
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- name: Building Docker Image
run: |
cd gatekeeper
docker build \
--no-cache \
-t beryju/passbook-gatekeeper:0.7.13-beta \
-t beryju/passbook-gatekeeper:latest \
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-gatekeeper:0.7.13-beta
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-gatekeeper:latest
build-static:
runs-on: ubuntu-latest
services:
@ -45,11 +66,11 @@ jobs:
run: docker build
--no-cache
--network=$(docker network ls | grep github | awk '{print $1}')
-t beryju/passbook-static:0.7.10-beta
-t beryju/passbook-static:0.7.13-beta
-t beryju/passbook-static:latest
-f static.Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-static:0.7.10-beta
run: docker push beryju/passbook-static:0.7.13-beta
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-static:latest
test-release:

View File

@ -1,4 +1,4 @@
FROM python:3.7-slim-buster as locker
FROM python:3.8-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.7-slim-buster
FROM python:3.8-slim-buster
COPY --from=locker /app/requirements.txt /app/
COPY --from=locker /app/requirements-dev.txt /app/

View File

@ -42,7 +42,7 @@ swagger-spec-validator = "*"
urllib3 = {extras = ["secure"],version = "*"}
[requires]
python_version = "3.7"
python_version = "3.8"
[dev-packages]
autopep8 = "*"

480
Pipfile.lock generated
View File

@ -1,11 +1,11 @@
{
"_meta": {
"hash": {
"sha256": "138816efaba5be0b175cfd5b5e6a0b58e5ba551567f0efb441740344da3986d8"
"sha256": "3693ac70f66ada5c8c8784e6e2d5c2f0ee75219e7141dde1075347234366e314"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
"python_version": "3.8"
},
"sources": [
{
@ -25,10 +25,10 @@
},
"asn1crypto": {
"hashes": [
"sha256:7bb1cc02a5620b3d72da4ba070bda2f44f0e61b44dee910a302eddff802b6fb5",
"sha256:87620880a477123e01177a1f73d0f327210b43a3cdbd714efcd2fa49a8d7b384"
"sha256:5a215cb8dc12f892244e3a113fe05397ee23c5c4ca7a69cd6e69811755efc42d",
"sha256:831d2710d3274c8a74befdddaf9f17fcbf6e350534565074818722d6d615b315"
],
"version": "==1.2.0"
"version": "==1.3.0"
},
"attrs": {
"hashes": [
@ -39,25 +39,25 @@
},
"billiard": {
"hashes": [
"sha256:01afcb4e7c4fd6480940cfbd4d9edc19d7a7509d6ada533984d0d0f49901ec82",
"sha256:b8809c74f648dfe69b973c8e660bcec00603758c9db8ba89d7719f88d5f01f26"
"sha256:26fd494dc3251f8ce1f5559744f18aeed427fdaf29a75d7baae26752a5d3816f",
"sha256:f4e09366653aa3cb3ae8ed16423f9ba1665ff426f087bcdbbed86bf3664fe02c"
],
"version": "==3.6.1.0"
"version": "==3.6.2.0"
},
"boto3": {
"hashes": [
"sha256:982823e7c992d27e5954c81db93238ffc42c7a1210d863b4f5e048fdc088040e",
"sha256:f05ee90a738c2f1ec8088121030229f26ef6a809fb9a1338de2118fd088dd99a"
"sha256:7aaccacc199cd633b5ac14f0d544c9d592c53275a79cf4d230a9f66215331665",
"sha256:ca36aabfa5e7fa9ee8f84f82c3faffb4ffe078923f935f572f70dc49154ef6a0"
],
"index": "pypi",
"version": "==1.10.45"
"version": "==1.11.5"
},
"botocore": {
"hashes": [
"sha256:88ee646f7a0fe6a418681c6f119a590fae23d8439c48c2aec6878f7f89430b1f",
"sha256:f48ba1ef04b25323c1d27fa6399795baa0ca9d316911b87be4d33acda5cef07c"
"sha256:2538065f9f5023eae3607e78fc5d8c595c9173ec02bc92410652078617c44ac1",
"sha256:554231b1690c8521e05a41e50184e43d62941fdf9351e658aea894649b879985"
],
"version": "==1.13.45"
"version": "==1.14.15"
},
"celery": {
"hashes": [
@ -76,41 +76,36 @@
},
"cffi": {
"hashes": [
"sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42",
"sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04",
"sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5",
"sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54",
"sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba",
"sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57",
"sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396",
"sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12",
"sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97",
"sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43",
"sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db",
"sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3",
"sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b",
"sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579",
"sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346",
"sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159",
"sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652",
"sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e",
"sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a",
"sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506",
"sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f",
"sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d",
"sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c",
"sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20",
"sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858",
"sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc",
"sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a",
"sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3",
"sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e",
"sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410",
"sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25",
"sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b",
"sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d"
"sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff",
"sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b",
"sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac",
"sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0",
"sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384",
"sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26",
"sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6",
"sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b",
"sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e",
"sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd",
"sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2",
"sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66",
"sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc",
"sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8",
"sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55",
"sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4",
"sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5",
"sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d",
"sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78",
"sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa",
"sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793",
"sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f",
"sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a",
"sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f",
"sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30",
"sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f",
"sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3",
"sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"
],
"version": "==1.13.2"
"version": "==1.14.0"
},
"chardet": {
"hashes": [
@ -169,11 +164,11 @@
},
"django": {
"hashes": [
"sha256:662a1ff78792e3fd77f16f71b1f31149489434de4b62a74895bd5d6534e635a5",
"sha256:687c37153486cf26c3fdcbdd177ef16de38dc3463f094b5f9c9955d91f277b14"
"sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a",
"sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038"
],
"index": "pypi",
"version": "==2.2.9"
"version": "==2.2.10"
},
"django-cors-middleware": {
"hashes": [
@ -326,14 +321,6 @@
],
"version": "==2.8"
},
"importlib-metadata": {
"hashes": [
"sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45",
"sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"
],
"markers": "python_version < '3.8'",
"version": "==1.3.0"
},
"inflection": {
"hashes": [
"sha256:18ea7fb7a7d152853386523def08736aa8c32636b047ade55f7578c4edeb16ca"
@ -348,10 +335,10 @@
},
"jinja2": {
"hashes": [
"sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f",
"sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"
"sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484",
"sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668"
],
"version": "==2.10.3"
"version": "==3.0.0a1"
},
"jmespath": {
"hashes": [
@ -421,13 +408,16 @@
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
@ -444,17 +434,12 @@
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
],
"version": "==1.1.1"
},
"more-itertools": {
"hashes": [
"sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d",
"sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"
],
"version": "==8.0.2"
},
"oauthlib": {
"hashes": [
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
@ -465,11 +450,11 @@
},
"packaging": {
"hashes": [
"sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47",
"sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"
"sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb",
"sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8"
],
"index": "pypi",
"version": "==19.2"
"version": "==20.0"
},
"prometheus-client": {
"hashes": [
@ -524,10 +509,10 @@
},
"pyasn1-modules": {
"hashes": [
"sha256:0c35a52e00b672f832e5846826f1fb7507907f7d52fba6faa9e3c4cbe874fe4b",
"sha256:b6ada4f840fe51abf5a6bd545b45bf537bea62221fa0dde2e8a553ed9f06a4e3"
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"
],
"version": "==0.2.7"
"version": "==0.2.8"
},
"pycparser": {
"hashes": [
@ -575,40 +560,38 @@
},
"pycryptodomex": {
"hashes": [
"sha256:0943b65fb41b7403a9def6214061fdd9ab9afd0bbc581e553c72eebe60bded36",
"sha256:0a1dbb5c4d975a4ea568fb7686550aa225d94023191fb0cca8747dc5b5d77857",
"sha256:0f43f1608518347fdcb9c8f443fa5cabedd33f94188b13e4196a3a7ba90d169c",
"sha256:11ce5fec5990e34e3981ed14897ba601c83957b577d77d395f1f8f878a179f98",
"sha256:17a09e38fdc91e4857cf5a7ce82f3c0b229c3977490f2146513e366923fc256b",
"sha256:22d970cee5c096b9123415e183ae03702b2cd4d3ba3f0ced25c4e1aba3967167",
"sha256:2a1793efcbae3a2264c5e0e492a2629eb10d895d6e5f17dbbd00eb8b489c6bda",
"sha256:30a8a148a0fe482cec1aaf942bbd0ade56ec197c14fe058b2a94318c57e1f991",
"sha256:32fbbaf964c5184d3f3e349085b0536dd28184b02e2b014fc900f58bbc126339",
"sha256:347d67faee36d449dc9632da411cc318df52959079062627f1243001b10dc227",
"sha256:45f4b4e5461a041518baabc52340c249b60833aa84cea6377dc8016a2b33c666",
"sha256:4717daec0035034b002d31c42e55431c970e3e38a78211f43990e1b7eaf19e28",
"sha256:51a1ac9e7dda81da444fed8be558a60ec88dfc73b2aa4b0efa310e87acb75838",
"sha256:53e9dcc8f14783f6300b70da325a50ac1b0a3dbaee323bd9dc3f71d409c197a1",
"sha256:5519a2ed776e193688b7ddb61ab709303f6eb7d1237081e298283c72acc44271",
"sha256:583450e8e80a0885c453211ed2bd69ceea634d8c904f23ff8687f677fe810e95",
"sha256:60f862bd2a07133585a4fc2ce2b1a8ec24746b07ac44307d22ef2b767cb03435",
"sha256:612091f1d3c84e723bec7cb855cf77576e646045744794c9a3f75ba80737762f",
"sha256:629a87b87c8203b8789ccefc7f2f2faecd2daaeb56bdd0b4e44cd89565f2db07",
"sha256:6e56ec4c8938fb388b6f250ddd5e21c15e8f25a76e0ad0e2abae9afee09e67b4",
"sha256:8e8092651844a11ec7fa534395f3dfe99256ce4edca06f128efc9d770d6e1dc1",
"sha256:8f5f260629876603e08f3ce95c8ccd9b6b83bf9a921c41409046796267f7adc5",
"sha256:9a6b74f38613f54c56bd759b411a352258f47489bbefd1d57c930a291498b35b",
"sha256:a5a13ebb52c4cd065fb673d8c94f39f30823428a4de19e1f3f828b63a8882d1e",
"sha256:a77ca778a476829876a3a70ae880073379160e4a465d057e3c4e1c79acdf1b8a",
"sha256:a9f7be3d19f79429c2118fd61bc2ec4fa095e93b56fb3a5f3009822402c4380f",
"sha256:dc15a467c4f9e4b43748ba2f97aea66f67812bfd581818284c47cadc81d4caec",
"sha256:e13cdeea23059f7577c230fd580d2c8178e67ebe10e360041abe86c33c316f1c",
"sha256:e45b85c8521bca6bdfaf57e4987743ade53e9f03529dd3adbc9524094c6d55c4",
"sha256:e87f17867b260f57c88487f943eb4d46c90532652bb37046e764842c3b66cbb1",
"sha256:ee40a5b156f6c1192bc3082e9d73d0479904433cdda83110546cd67f5a15a5be",
"sha256:ef63ffde3b267043579af8830fc97fc3b9b8a526a24e3ba23af9989d4e9e689a"
"sha256:04646e40ef5788bad6d415e52862ffcdf2ac2d888ba4a5c82d5cb44607a042f7",
"sha256:132f1e5fa84921f25695a313a6d4988847dfaee7fb1fd0d1fbe03ef678836f58",
"sha256:17ad1ebaa00806305d34550fe5d3c776e38a27b8a2678dfb7871ef0209d64e46",
"sha256:27736fa02a2d3502e1ca4b150457e56ce3b98f132462f540073498884e5f8975",
"sha256:38050b3fd86c74c6c79e40bbe824bec6431c3e4e36f6080ed544673ba2dc133a",
"sha256:3b9306b360bddbc8e098b16eab7adacf49389d212db9c3739588ab840a1ca868",
"sha256:466e36ba74a7e725625e717fad3f36e0b9293c247b7d0439c66528026ef2834f",
"sha256:4f77360b23a21db32a4c35dacffac33dc30ac6a5a77162a34e99ab11ab631516",
"sha256:5002388178845683c330a02f4faeddfe7cd477b87824987cca4718fa0c4f2085",
"sha256:51be76756abfc1ddc97e1e2e3c38f4e62fb940161162368308ea9e5919e86c34",
"sha256:544628ae67d61c31c28a60e621dadd738b303c5266492355d5ebdb6e7dd1e78f",
"sha256:6ff9d4a06bc40211eee05cd88436740d698a01233f4aaff9eb70d8a90e578966",
"sha256:718329c6ca60260f1c27b8392e372dd51e4e691f7dcb88adc53eb3b76af6363c",
"sha256:918bc5a0170fe8ed7b72f202245b34f84a1997f5ca1520b9c7db71126e5acd62",
"sha256:a8ea72adde0d010f89abece5f024b1be95a5c52472e9a57b3ac7d59aee3c8238",
"sha256:a979d2c7bcc67282b7ec2600db384c63d37d74e250edb99168483605a380bf62",
"sha256:b350f9ad09b692aed57e669fc3f8cf918557fae9f0229c6ce9286a6fe8c1b60f",
"sha256:be838abc8557a21a60d453c5a4e64c738966b8a0b7d7f8f97eb8bb44041ca452",
"sha256:bfa99692d3c8f994c5850cc8a894cba001abd76d34069a8bfaad173dd46387d6",
"sha256:c021b66f5b3c4ea0c45422ec3241bfea4a16651e1ee5459a136639d0716ccb3c",
"sha256:c7babb64484080057a24c74a82dbf7997904b1710b74caf62e261610f989b437",
"sha256:c96b7762b601dc8a58d7712235c3c152868116f58a7ffa40dcd1c6f6cd97405e",
"sha256:d67b6e0bae0777a2c6c83275fbd7cbf53cd5f23c2028f908b0f7d996466e5b15",
"sha256:e15f39fcfb949cfd5536cc9647daba942b1a99b67e4d7211e3bdbcedbc2f823c",
"sha256:e380448f1e39736f6230ec284cd6d771956ad802d6ce5bc56947a2481080cac1",
"sha256:e5236f2171b21e704d1854fd809a7228eb22e29c894af31459e41986e6a53f87",
"sha256:ea7b48ce8dbbc86ebadcfe56ebc10d413bdd12c9a5ff0b9147a41993f12b80b3",
"sha256:f39f5b58d8fe348ed604bb44a89ca93b26130c275db2b249f718f1538cb70500",
"sha256:f545f776e45f74c41329e4020463fdd4d0cd0a7501bdf9e50251dafe7bd959a9",
"sha256:f667ac7ae29c19530f199854635f1a97e73d0bfd24163e0db6bdba7dba04eb9f"
],
"version": "==3.9.4"
"version": "==3.9.6"
},
"pyjwkest": {
"hashes": [
@ -632,16 +615,15 @@
},
"pyrsistent": {
"hashes": [
"sha256:f3b280d030afb652f79d67c5586157c5c1355c9a58dfc7940566e28d28f3df1b"
"sha256:cdc7b5e3ed77bed61270a47d35434a30617b9becdf2478af76ad2c6ade307280"
],
"version": "==0.15.6"
"version": "==0.15.7"
},
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"markers": "python_version >= '2.7'",
"version": "==2.8.1"
},
"pytz": {
@ -685,20 +667,20 @@
},
"pyyaml": {
"hashes": [
"sha256:21a8e19e2007a4047ffabbd8f0ee32c0dabae3b7f4b6c645110ae53e7714b470",
"sha256:74ad685bfb065f4bdd36d24aa97092f04bcbb1179b5ffdd3d5f994023fb8c292",
"sha256:79c3ba1da22e61c2a71aaa382c57518ab492278c8974c40187b900b50f3e0282",
"sha256:94ad913ab3fd967d14ecffda8182d7d0e1f7dd919b352773c492ec51890d3224",
"sha256:998db501e3a627c3e5678d6505f0e182d1529545df289db036cdc717f35d8058",
"sha256:9b69d4645bff5820713e8912bc61c4277dc127a6f8c197b52b6436503c42600f",
"sha256:9da13b536533518343a04f3c6564782ec8a13c705310b26b4832d77fa4d92a47",
"sha256:a76159f13b47fb44fb2acac8fef798a1940dd31b4acec6f4560bd11b2d92d31b",
"sha256:a9e9175c1e47a089a2b45d9e2afc6aae1f1f725538c32eec761894a42ba1227f",
"sha256:ea51ce7b96646ecd3bb12c2702e570c2bd7dd4d9f146db7fa83c5008ede35f66",
"sha256:ffbaaa05de60fc444eda3f6300d1af27d965b09b67f1fb4ebcc88dd0fb4ab1b4"
"sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
"sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
"sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
"sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
"sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
"sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
"sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
"sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
"sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
"sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
"sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
],
"index": "pypi",
"version": "==5.3b1"
"version": "==5.3"
},
"qrcode": {
"hashes": [
@ -710,10 +692,10 @@
},
"redis": {
"hashes": [
"sha256:3613daad9ce5951e426f460deddd5caf469e08a3af633e9578fc77d362becf62",
"sha256:8d0fc278d3f5e1249967cba2eb4a5632d19e45ce5c09442b8422d15ee2c22cc2"
"sha256:0dcfb335921b88a850d461dc255ff4708294943322bd55de6cfd68972490ca1f",
"sha256:b205cffd05ebfd0a468db74f0eedbff8df1a7bfc47521516ade4692991bb0833"
],
"version": "==3.3.11"
"version": "==3.4.1"
},
"requests": {
"hashes": [
@ -732,10 +714,10 @@
},
"ruamel.yaml": {
"hashes": [
"sha256:0db639b1b2742dae666c6fc009b8d1931ef15c9276ef31c0673cc6dcf766cf40",
"sha256:412a6f5cfdc0525dee6a27c08f5415c7fd832a7afcb7a0ed7319628aed23d408"
"sha256:ee3264b83c3309b4ae7978afa185da6a1d278e3abc9fb942f1a0b57c622092f8",
"sha256:fd16843ff0ba45fa5e1ea9ea7038428b4a46f2c39deea9aa67f9eaa34823dc11"
],
"version": "==0.16.5"
"version": "==0.16.9"
},
"ruamel.yaml.clib": {
"hashes": [
@ -759,23 +741,23 @@
"sha256:ed5b3698a2bb241b7f5cbbe277eaa7fe48b07a58784fba4f75224fd066d253ad",
"sha256:f9dcc1ae73f36e8059589b601e8e4776b9976effd76c21ad6a855a74318efd6e"
],
"markers": "platform_python_implementation == 'CPython' and python_version < '3.8'",
"markers": "platform_python_implementation == 'CPython' and python_version < '3.9'",
"version": "==0.2.0"
},
"s3transfer": {
"hashes": [
"sha256:6efc926738a3cd576c2a79725fed9afde92378aa5c6a957e3af010cb019fac9d",
"sha256:b780f2411b824cb541dbcd2c713d0cb61c7d1bcadae204cdddda2b35cef493ba"
"sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13",
"sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db"
],
"version": "==0.2.1"
"version": "==0.3.3"
},
"sentry-sdk": {
"hashes": [
"sha256:05285942901d38c7ce2498aba50d8e87b361fc603281a5902dda98f3f8c5e145",
"sha256:c6b919623e488134a728f16326c6f0bcdab7e3f59e7f4c472a90eea4d6d8fe82"
"sha256:8e2d38dc58dc992280487e553ec3d97a424e4d179f4fad802ef3b08f64ccf4d8",
"sha256:9b59e155229ea7d46a52b5c025d8c3c6d591e9dd9bb5f5f47310b2bb430038a8"
],
"index": "pypi",
"version": "==0.13.5"
"version": "==0.14.0"
},
"service-identity": {
"hashes": [
@ -795,10 +777,10 @@
},
"six": {
"hashes": [
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
],
"version": "==1.13.0"
"version": "==1.14.0"
},
"sqlparse": {
"hashes": [
@ -848,13 +830,6 @@
"sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"
],
"version": "==1.3.0"
},
"zipp": {
"hashes": [
"sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e",
"sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"
],
"version": "==0.6.0"
}
},
"develop": {
@ -934,48 +909,48 @@
},
"coverage": {
"hashes": [
"sha256:0101888bd1592a20ccadae081ba10e8b204d20235d18d05c6f7d5e904a38fc10",
"sha256:04b961862334687549eb91cd5178a6fbe977ad365bddc7c60f2227f2f9880cf4",
"sha256:1ca43dbd739c0fc30b0a3637a003a0d2c7edc1dd618359d58cc1e211742f8bd1",
"sha256:1cbb88b34187bdb841f2599770b7e6ff8e259dc3bb64fc7893acf44998acf5f8",
"sha256:232f0b52a5b978288f0bbc282a6c03fe48cd19a04202df44309919c142b3bb9c",
"sha256:24bcfa86fd9ce86b73a8368383c39d919c497a06eebb888b6f0c12f13e920b1a",
"sha256:25b8f60b5c7da71e64c18888f3067d5b6f1334b9681876b2fb41eea26de881ae",
"sha256:2714160a63da18aed9340c70ed514973971ee7e665e6b336917ff4cca81a25b1",
"sha256:2ca2cd5264e84b2cafc73f0045437f70c6378c0d7dbcddc9ee3fe192c1e29e5d",
"sha256:2cc707fc9aad2592fc686d63ef72dc0031fc98b6fb921d2f5395d9ab84fbc3ef",
"sha256:348630edea485f4228233c2f310a598abf8afa5f8c716c02a9698089687b6085",
"sha256:40fbfd6b044c9db13aeec1daf5887d322c710d811f944011757526ef6e323fd9",
"sha256:46c9c6a1d1190c0b75ec7c0f339088309952b82ae8d67a79ff1319eb4e749b96",
"sha256:591506e088901bdc25620c37aec885e82cc896528f28c57e113751e3471fc314",
"sha256:5ac71bba1e07eab403b082c4428f868c1c9e26a21041436b4905c4c3d4e49b08",
"sha256:5f622f19abda4e934938e24f1d67599249abc201844933a6f01aaa8663094489",
"sha256:65bead1ac8c8930cf92a1ccaedcce19a57298547d5d1db5c9d4d068a0675c38b",
"sha256:7362a7f829feda10c7265b553455de596b83d1623b3d436b6d3c51c688c57bf6",
"sha256:7f2675750c50151f806070ec11258edf4c328340916c53bac0adbc465abd6b1e",
"sha256:960d7f42277391e8b1c0b0ae427a214e1b31a1278de6b73f8807b20c2e913bba",
"sha256:a50b0888d8a021a3342d36a6086501e30de7d840ab68fca44913e97d14487dc1",
"sha256:b7dbc5e8c39ea3ad3db22715f1b5401cd698a621218680c6daf42c2f9d36e205",
"sha256:bb3d29df5d07d5399d58a394d0ef50adf303ab4fbf66dfd25b9ef258effcb692",
"sha256:c0fff2733f7c2950f58a4fd09b5db257b00c6fec57bf3f68c5bae004d804b407",
"sha256:c792d3707a86c01c02607ae74364854220fb3e82735f631cd0a345dea6b4cee5",
"sha256:c90bda74e16bcd03861b09b1d37c0a4158feda5d5a036bb2d6e58de6ff65793e",
"sha256:cfce79ce41cc1a1dc7fc85bb41eeeb32d34a4cf39a645c717c0550287e30ff06",
"sha256:eeafb646f374988c22c8e6da5ab9fb81367ecfe81c70c292623373d2a021b1a1",
"sha256:f425f50a6dd807cb9043d15a4fcfba3b5874a54d9587ccbb748899f70dc18c47",
"sha256:fcd4459fe35a400b8f416bc57906862693c9f88b66dc925e7f2a933e77f6b18b",
"sha256:ff3936dd5feaefb4f91c8c1f50a06c588b5dc69fba4f7d9c79a6617ad80bb7df"
"sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3",
"sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c",
"sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0",
"sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477",
"sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a",
"sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf",
"sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691",
"sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73",
"sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987",
"sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894",
"sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e",
"sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef",
"sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf",
"sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68",
"sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8",
"sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954",
"sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2",
"sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40",
"sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc",
"sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc",
"sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e",
"sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d",
"sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f",
"sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc",
"sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301",
"sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea",
"sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb",
"sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af",
"sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52",
"sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37",
"sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0"
],
"index": "pypi",
"version": "==5.0.1"
"version": "==5.0.3"
},
"django": {
"hashes": [
"sha256:662a1ff78792e3fd77f16f71b1f31149489434de4b62a74895bd5d6534e635a5",
"sha256:687c37153486cf26c3fdcbdd177ef16de38dc3463f094b5f9c9955d91f277b14"
"sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a",
"sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038"
],
"index": "pypi",
"version": "==2.2.9"
"version": "==2.2.10"
},
"django-debug-toolbar": {
"hashes": [
@ -987,9 +962,10 @@
},
"dodgy": {
"hashes": [
"sha256:65e13cf878d7aff129f1461c13cb5fd1bb6dfe66bb5327e09379c3877763280c"
"sha256:28323cbfc9352139fdd3d316fa17f325cc0e9ac74438cbba51d70f9b48f86c3a",
"sha256:51f54c0fd886fa3854387f354b19f429d38c04f984f38bc572558b703c0542a6"
],
"version": "==0.1.9"
"version": "==0.2.1"
},
"gitdb2": {
"hashes": [
@ -1000,10 +976,10 @@
},
"gitpython": {
"hashes": [
"sha256:9c2398ffc3dcb3c40b27324b316f08a4f93ad646d5a6328cafbb871aa79f5e42",
"sha256:c155c6a2653593ccb300462f6ef533583a913e17857cfef8fc617c246b6dc245"
"sha256:99c77677f31f255e130f3fed4c8e0eebb35f1a09df98ff965fff6774f71688cf",
"sha256:99cd0403cecd8a13b95d2e045b9fcaa7837137fcc5ec3105f2c413305d82c143"
],
"version": "==3.0.5"
"version": "==3.0.7"
},
"isort": {
"hashes": [
@ -1082,10 +1058,10 @@
},
"pydocstyle": {
"hashes": [
"sha256:4167fe954b8f27ebbbef2fbcf73c6e8ad1e7bb31488fce44a69fdfc4b0cd0fae",
"sha256:a0de36e549125d0a16a72a8c8c6c9ba267750656e72e466e994c222f1b6e92cb"
"sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586",
"sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"
],
"version": "==5.0.1"
"version": "==5.0.2"
},
"pyflakes": {
"hashes": [
@ -1138,46 +1114,46 @@
},
"pyyaml": {
"hashes": [
"sha256:21a8e19e2007a4047ffabbd8f0ee32c0dabae3b7f4b6c645110ae53e7714b470",
"sha256:74ad685bfb065f4bdd36d24aa97092f04bcbb1179b5ffdd3d5f994023fb8c292",
"sha256:79c3ba1da22e61c2a71aaa382c57518ab492278c8974c40187b900b50f3e0282",
"sha256:94ad913ab3fd967d14ecffda8182d7d0e1f7dd919b352773c492ec51890d3224",
"sha256:998db501e3a627c3e5678d6505f0e182d1529545df289db036cdc717f35d8058",
"sha256:9b69d4645bff5820713e8912bc61c4277dc127a6f8c197b52b6436503c42600f",
"sha256:9da13b536533518343a04f3c6564782ec8a13c705310b26b4832d77fa4d92a47",
"sha256:a76159f13b47fb44fb2acac8fef798a1940dd31b4acec6f4560bd11b2d92d31b",
"sha256:a9e9175c1e47a089a2b45d9e2afc6aae1f1f725538c32eec761894a42ba1227f",
"sha256:ea51ce7b96646ecd3bb12c2702e570c2bd7dd4d9f146db7fa83c5008ede35f66",
"sha256:ffbaaa05de60fc444eda3f6300d1af27d965b09b67f1fb4ebcc88dd0fb4ab1b4"
"sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
"sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
"sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
"sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
"sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
"sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
"sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
"sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
"sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
"sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
"sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
],
"index": "pypi",
"version": "==5.3b1"
"version": "==5.3"
},
"regex": {
"hashes": [
"sha256:032fdcc03406e1a6485ec09b826eac78732943840c4b29e503b789716f051d8d",
"sha256:0e6cf1e747f383f52a0964452658c04300a9a01e8a89c55ea22813931b580aa8",
"sha256:106e25a841921d8259dcef2a42786caae35bc750fb996f830065b3dfaa67b77e",
"sha256:1768cf42a78a11dae63152685e7a1d90af7a8d71d2d4f6d2387edea53a9e0588",
"sha256:27d1bd20d334f50b7ef078eba0f0756a640fd25f5f1708d3b5bed18a5d6bced9",
"sha256:29b20f66f2e044aafba86ecf10a84e611b4667643c42baa004247f5dfef4f90b",
"sha256:4850c78b53acf664a6578bba0e9ebeaf2807bb476c14ec7e0f936f2015133cae",
"sha256:57eacd38a5ec40ed7b19a968a9d01c0d977bda55664210be713e750dd7b33540",
"sha256:724eb24b92fc5fdc1501a1b4df44a68b9c1dda171c8ef8736799e903fb100f63",
"sha256:77ae8d926f38700432807ba293d768ba9e7652df0cbe76df2843b12f80f68885",
"sha256:78b3712ec529b2a71731fbb10b907b54d9c53a17ca589b42a578bc1e9a2c82ea",
"sha256:7bbbdbada3078dc360d4692a9b28479f569db7fc7f304b668787afc9feb38ec8",
"sha256:8d9ef7f6c403e35e73b7fc3cde9f6decdc43b1cb2ff8d058c53b9084bfcb553e",
"sha256:a83049eb717ae828ced9cf607845929efcb086a001fc8af93ff15c50012a5716",
"sha256:adc35d38952e688535980ae2109cad3a109520033642e759f987cf47fe278aa1",
"sha256:c29a77ad4463f71a506515d9ec3a899ed026b4b015bf43245c919ff36275444b",
"sha256:cfd31b3300fefa5eecb2fe596c6dee1b91b3a05ece9d5cfd2631afebf6c6fadd",
"sha256:d3ee0b035816e0520fac928de31b6572106f0d75597f6fa3206969a02baba06f",
"sha256:d508875793efdf6bab3d47850df8f40d4040ae9928d9d80864c1768d6aeaf8e3",
"sha256:ef0b828a7e22e58e06a1cceddba7b4665c6af8afeb22a0d8083001330572c147",
"sha256:faad39fdbe2c2ccda9846cd21581063086330efafa47d87afea4073a08128656"
"sha256:07b39bf943d3d2fe63d46281d8504f8df0ff3fe4c57e13d1656737950e53e525",
"sha256:0932941cdfb3afcbc26cc3bcf7c3f3d73d5a9b9c56955d432dbf8bbc147d4c5b",
"sha256:0e182d2f097ea8549a249040922fa2b92ae28be4be4895933e369a525ba36576",
"sha256:10671601ee06cf4dc1bc0b4805309040bb34c9af423c12c379c83d7895622bb5",
"sha256:23e2c2c0ff50f44877f64780b815b8fd2e003cda9ce817a7fd00dea5600c84a0",
"sha256:26ff99c980f53b3191d8931b199b29d6787c059f2e029b2b0c694343b1708c35",
"sha256:27429b8d74ba683484a06b260b7bb00f312e7c757792628ea251afdbf1434003",
"sha256:3e77409b678b21a056415da3a56abfd7c3ad03da71f3051bbcdb68cf44d3c34d",
"sha256:4e8f02d3d72ca94efc8396f8036c0d3bcc812aefc28ec70f35bb888c74a25161",
"sha256:4eae742636aec40cf7ab98171ab9400393360b97e8f9da67b1867a9ee0889b26",
"sha256:6a6ae17bf8f2d82d1e8858a47757ce389b880083c4ff2498dba17c56e6c103b9",
"sha256:6a6ba91b94427cd49cd27764679024b14a96874e0dc638ae6bdd4b1a3ce97be1",
"sha256:7bcd322935377abcc79bfe5b63c44abd0b29387f267791d566bbb566edfdd146",
"sha256:98b8ed7bb2155e2cbb8b76f627b2fd12cf4b22ab6e14873e8641f266e0fb6d8f",
"sha256:bd25bb7980917e4e70ccccd7e3b5740614f1c408a642c245019cff9d7d1b6149",
"sha256:d0f424328f9822b0323b3b6f2e4b9c90960b24743d220763c7f07071e0778351",
"sha256:d58e4606da2a41659c84baeb3cfa2e4c87a74cec89a1e7c56bee4b956f9d7461",
"sha256:e3cd21cc2840ca67de0bbe4071f79f031c81418deb544ceda93ad75ca1ee9f7b",
"sha256:e6c02171d62ed6972ca8631f6f34fa3281d51db8b326ee397b9c83093a6b7242",
"sha256:e7c7661f7276507bce416eaae22040fd91ca471b5b33c13f8ff21137ed6f248c",
"sha256:ecc6de77df3ef68fee966bb8cb4e067e84d4d1f397d0ef6fce46913663540d77"
],
"version": "==2019.12.20"
"version": "==2020.1.8"
},
"requirements-detector": {
"hashes": [
@ -1193,10 +1169,10 @@
},
"six": {
"hashes": [
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
],
"version": "==1.13.0"
"version": "==1.14.0"
},
"smmap2": {
"hashes": [
@ -1221,10 +1197,10 @@
},
"stevedore": {
"hashes": [
"sha256:01d9f4beecf0fbd070ddb18e5efb10567801ba7ef3ddab0074f54e3cd4e91730",
"sha256:e0739f9739a681c7a1fda76a102b65295e96a144ccdb552f2ae03c5f0abe8a14"
"sha256:18afaf1d623af5950cc0f7e75e70f917784c73b652a34a12d90b309451b5500b",
"sha256:a4e7dc759fb0f2e3e2f7d8ffe2358c19d45b9b8297f393ef1256858d82f69c9b"
],
"version": "==1.31.0"
"version": "==1.32.0"
},
"toml": {
"hashes": [
@ -1235,29 +1211,29 @@
},
"typed-ast": {
"hashes": [
"sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161",
"sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e",
"sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e",
"sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0",
"sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c",
"sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47",
"sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631",
"sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4",
"sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34",
"sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b",
"sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2",
"sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e",
"sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a",
"sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233",
"sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1",
"sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36",
"sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d",
"sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a",
"sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66",
"sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"
"sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
"sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
"sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
"sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
"sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
"sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
"sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
"sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
"sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
"sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
"sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
"sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
"sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
"sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
"sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
"sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
"sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
"sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
"sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
"sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
"sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
],
"markers": "implementation_name == 'cpython' and python_version < '3.8'",
"version": "==1.4.0"
"version": "==1.4.1"
},
"unittest-xml-reporting": {
"hashes": [

View File

@ -1,5 +1,7 @@
# passbook
![](https://github.com/BeryJu/passbook/workflows/passbook-ci/badge.svg)
## Quick instance
```

View File

@ -1,4 +1,4 @@
FROM python:3.7-slim-buster as builder
FROM python:3.8-slim-buster as builder
WORKDIR /mkdocs

View File

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

View File

@ -18,6 +18,7 @@ spec:
name: {{ include "passbook.fullname" . }}-secret-key
key: monitoring_username
port: http
path: /metrics/
interval: 10s
selector:
matchLabels:

View File

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

View File

@ -1,5 +1,5 @@
site_name: passbook Docs
site_url: https://docs.passbook.beryju.org
site_url: https://beryju.github.io/passbook
copyright: "Copyright &copy; 2019 - 2020 BeryJu.org"
nav:

View File

@ -1,2 +1,2 @@
"""passbook"""
__version__ = "0.7.10-beta"
__version__ = "0.7.13-beta"

View File

@ -1,14 +1,14 @@
"""passbook audit models"""
from enum import Enum
from uuid import UUID
from inspect import getmodule, stack
from typing import Optional, Dict, Any
from typing import Any, Dict, Optional
from uuid import UUID
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import JSONField
from django.core.exceptions import ValidationError
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.http import HttpRequest
from django.utils.translation import gettext as _

View File

@ -11,6 +11,7 @@ from django.db import models
from django.urls import reverse_lazy
from django.utils.timezone import now
from django.utils.translation import gettext as _
from django_prometheus.models import ExportModelOperationsMixin
from guardian.mixins import GuardianUserMixin
from model_utils.managers import InheritanceManager
from structlog import get_logger
@ -28,7 +29,7 @@ def default_nonce_duration():
return now() + timedelta(hours=4)
class Group(UUIDModel):
class Group(ExportModelOperationsMixin("group"), UUIDModel):
"""Custom Group model which supports a basic hierarchy"""
name = models.CharField(_("name"), max_length=80)
@ -49,7 +50,7 @@ class Group(UUIDModel):
unique_together = (("name", "parent",),)
class User(GuardianUserMixin, AbstractUser):
class User(ExportModelOperationsMixin("user"), GuardianUserMixin, AbstractUser):
"""Custom User model to allow easier adding o f user-based settings"""
uuid = models.UUIDField(default=uuid4, editable=False)
@ -72,7 +73,7 @@ class User(GuardianUserMixin, AbstractUser):
permissions = (("reset_user_password", "Reset Password"),)
class Provider(models.Model):
class Provider(ExportModelOperationsMixin("provider"), models.Model):
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
property_mappings = models.ManyToManyField(
@ -107,7 +108,7 @@ class UserSettings:
self.view_name = view_name
class Factor(PolicyModel):
class Factor(ExportModelOperationsMixin("factor"), PolicyModel):
"""Authentication factor, multiple instances of the same Factor can be used"""
name = models.TextField()
@ -128,7 +129,7 @@ class Factor(PolicyModel):
return f"Factor {self.slug}"
class Application(PolicyModel):
class Application(ExportModelOperationsMixin("application"), PolicyModel):
"""Every Application which uses passbook for authentication/identification/authorization
needs an Application record. Other authentication types can subclass this Model to
add custom fields and other properties"""
@ -154,7 +155,7 @@ class Application(PolicyModel):
return self.name
class Source(PolicyModel):
class Source(ExportModelOperationsMixin("source"), PolicyModel):
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
name = models.TextField()
@ -199,7 +200,7 @@ class UserSourceConnection(CreatedUpdatedModel):
unique_together = (("user", "source"),)
class Policy(UUIDModel, CreatedUpdatedModel):
class Policy(ExportModelOperationsMixin("policy"), UUIDModel, CreatedUpdatedModel):
"""Policies which specify if a user is authorized to use an Application. Can be overridden by
other types to add other fields, more logic, etc."""
@ -241,7 +242,7 @@ class DebugPolicy(Policy):
verbose_name_plural = _("Debug Policies")
class Invitation(UUIDModel):
class Invitation(ExportModelOperationsMixin("invitation"), UUIDModel):
"""Single-use invitation link"""
created_by = models.ForeignKey("User", on_delete=models.CASCADE)
@ -266,7 +267,7 @@ class Invitation(UUIDModel):
verbose_name_plural = _("Invitations")
class Nonce(UUIDModel):
class Nonce(ExportModelOperationsMixin("nonce"), UUIDModel):
"""One-time link for password resets/sign-up-confirmations"""
expires = models.DateTimeField(default=default_nonce_duration)

View File

@ -23,7 +23,7 @@ def _redirect_with_qs(view, get_query_set=None):
"""Wrapper to redirect whilst keeping GET Parameters"""
target = reverse(view)
if get_query_set:
target += "?" + urlencode(get_query_set)
target += "?" + urlencode(get_query_set.items())
return redirect(target)

View File

@ -3,9 +3,9 @@ from hashlib import md5
from urllib.parse import urlencode
from django import template
from django.template import Context
from django.apps import apps
from django.db.models import Model
from django.template import Context
from django.utils.html import escape
from django.utils.translation import ugettext as _

View File

@ -20,6 +20,5 @@ class CreateAssignPermView(CreateView):
self.object._meta.app_label,
self.object._meta.model_name,
)
print(full_permission)
assign_perm(full_permission, self.request.user, self.object)
return response

View File

@ -1,7 +1,7 @@
"""passbook Application Security Gateway Forms"""
from django import forms
from oauth2_provider.generators import generate_client_id, generate_client_secret
from oidc_provider.models import Client
from oidc_provider.models import Client, ResponseType
from passbook.providers.app_gw.models import ApplicationGatewayProvider
@ -16,9 +16,14 @@ class ApplicationGatewayProviderForm(forms.ModelForm):
client_id=generate_client_id(), client_secret=generate_client_secret()
)
self.instance.client.name = self.instance.name
self.instance.client.response_types.set(
[ResponseType.objects.get_by_natural_key("code")]
)
self.instance.client.redirect_uris = [
f"http://{self.instance.host}/oauth2/callback",
f"https://{self.instance.host}/oauth2/callback",
f"http://{self.instance.external_host}/oauth2/callback",
f"https://{self.instance.external_host}/oauth2/callback",
f"http://{self.instance.internal_host}/oauth2/callback",
f"https://{self.instance.internal_host}/oauth2/callback",
]
self.instance.client.scope = ["openid", "email"]
self.instance.client.save()
@ -27,8 +32,9 @@ class ApplicationGatewayProviderForm(forms.ModelForm):
class Meta:
model = ApplicationGatewayProvider
fields = ["name", "host"]
fields = ["name", "internal_host", "external_host"]
widgets = {
"name": forms.TextInput(),
"host": forms.TextInput(),
"internal_host": forms.TextInput(),
"external_host": forms.TextInput(),
}

View File

@ -0,0 +1,24 @@
# Generated by Django 2.2.9 on 2020-01-02 15:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_providers_app_gw", "0003_applicationgatewayprovider"),
]
operations = [
migrations.RenameField(
model_name="applicationgatewayprovider",
old_name="host",
new_name="external_host",
),
migrations.AddField(
model_name="applicationgatewayprovider",
name="internal_host",
field=models.TextField(default=""),
preserve_default=False,
),
]

View File

@ -14,7 +14,8 @@ class ApplicationGatewayProvider(Provider):
"""This provider uses oauth2_proxy with the OIDC Provider."""
name = models.TextField()
host = models.TextField()
internal_host = models.TextField()
external_host = models.TextField()
client = models.ForeignKey(Client, on_delete=models.CASCADE)

View File

@ -40,10 +40,10 @@ services:
environment:
OAUTH2_PROXY_CLIENT_ID: {{ provider.client.client_id }}
OAUTH2_PROXY_CLIENT_SECRET: {{ provider.client.client_secret }}
OAUTH2_PROXY_REDIRECT_URL: https://{{ provider.host }}/oauth2/callback
OAUTH2_PROXY_OIDC_ISSUER_URL: https://{{ request.META.host }}/application/oidc
OAUTH2_PROXY_REDIRECT_URL: https://{{ provider.external_host }}/oauth2/callback
OAUTH2_PROXY_OIDC_ISSUER_URL: https://{{ request.META.HTTP_HOST }}/application/oidc
OAUTH2_PROXY_COOKIE_SECRET: {{ cookie_secret }}
OAUTH2_PROXY_UPSTREAM: http://{{ provider.host }}</textarea>
OAUTH2_PROXY_UPSTREAMS: http://{{ provider.internal_host }}</textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">{% trans 'Close' %}</button>

View File

@ -1,21 +1,38 @@
"""OIDC Permission checking"""
from typing import Optional
from django.contrib import messages
from django.db.models.deletion import Collector
from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect
from oidc_provider.models import Client
from structlog import get_logger
from passbook.audit.models import Event, EventAction
from passbook.core.models import Application
from passbook.core.models import Application, Provider, User
from passbook.policies.engine import PolicyEngine
LOGGER = get_logger()
def check_permissions(request, user, client):
def check_permissions(
request: HttpRequest, user: User, client: Client
) -> Optional[HttpResponse]:
"""Check permissions, used for
https://django-oidc-provider.readthedocs.io/en/latest/
sections/settings.html#oidc-after-userlogin-hook"""
try:
application = client.openidprovider.application
# because oidc_provider is also used by app_gw, we can't be
# sure an OpenIDPRovider instance exists. hence we look through all related models
# and choose the one that inherits from Provider, which is guaranteed to
# have the application property
collector = Collector(using="default")
collector.collect([client])
for _, related in collector.data.items():
related_object = next(iter(related))
if isinstance(related_object, Provider):
application = related_object.application
break
except Application.DoesNotExist:
return redirect("passbook_providers_oauth:oauth2-permission-denied")
LOGGER.debug(

View File

@ -1,336 +0,0 @@
"""Basic SAML Processor"""
import time
import uuid
from defusedxml import ElementTree
from structlog import get_logger
from passbook.providers.saml import exceptions, utils, xml_render
MINUTES = 60
HOURS = 60 * MINUTES
def get_random_id():
"""Random hex id"""
# It is very important that these random IDs NOT start with a number.
random_id = "_" + uuid.uuid4().hex
return random_id
def get_time_string(delta=0):
"""Get Data formatted in SAML format"""
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(time.time() + delta))
# Design note: I've tried to make this easy to sub-class and override
# just the bits you need to override. I've made use of object properties,
# so that your sub-classes have access to all information: use wisely.
# Formatting note: These methods are alphabetized.
# pylint: disable=too-many-instance-attributes
class Processor:
"""Base SAML 2.0 AuthnRequest to Response Processor.
Sub-classes should provide Service Provider-specific functionality."""
is_idp_initiated = False
_audience = ""
_assertion_params = None
_assertion_xml = None
_assertion_id = None
_django_request = None
_relay_state = None
_request = None
_request_id = None
_request_xml = None
_request_params = None
_response_id = None
_response_xml = None
_response_params = None
_saml_request = None
_saml_response = None
_session_index = None
_subject = None
_subject_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
_system_params = {}
@property
def dotted_path(self):
"""Return a dotted path to this class"""
return "{module}.{class_name}".format(
module=self.__module__, class_name=self.__class__.__name__
)
def __init__(self, remote):
self.name = remote.name
self._remote = remote
self._logger = get_logger()
self._system_params["ISSUER"] = self._remote.issuer
self._logger.debug("processor configured")
def _build_assertion(self):
"""Builds _assertion_params."""
self._determine_assertion_id()
self._determine_audience()
self._determine_subject()
self._determine_session_index()
self._assertion_params = {
"ASSERTION_ID": self._assertion_id,
"ASSERTION_SIGNATURE": "", # it's unsigned
"AUDIENCE": self._audience,
"AUTH_INSTANT": get_time_string(),
"ISSUE_INSTANT": get_time_string(),
"NOT_BEFORE": get_time_string(-1 * HOURS), # TODO: Make these settings.
"NOT_ON_OR_AFTER": get_time_string(86400 * MINUTES),
"SESSION_INDEX": self._session_index,
"SESSION_NOT_ON_OR_AFTER": get_time_string(8 * HOURS),
"SP_NAME_QUALIFIER": self._audience,
"SUBJECT": self._subject,
"SUBJECT_FORMAT": self._subject_format,
}
self._assertion_params.update(self._system_params)
self._assertion_params.update(self._request_params)
def _build_response(self):
"""Builds _response_params."""
self._determine_response_id()
self._response_params = {
"ASSERTION": self._assertion_xml,
"ISSUE_INSTANT": get_time_string(),
"RESPONSE_ID": self._response_id,
"RESPONSE_SIGNATURE": "", # initially unsigned
}
self._response_params.update(self._system_params)
self._response_params.update(self._request_params)
def _decode_request(self):
"""Decodes _request_xml from _saml_request."""
self._request_xml = utils.decode_base64_and_inflate(self._saml_request).decode(
"utf-8"
)
self._logger.debug("SAML request decoded")
def _determine_assertion_id(self):
"""Determines the _assertion_id."""
self._assertion_id = get_random_id()
def _determine_audience(self):
"""Determines the _audience."""
self._audience = self._remote.audience
self._logger.info("determined audience")
def _determine_response_id(self):
"""Determines _response_id."""
self._response_id = get_random_id()
def _determine_session_index(self):
self._session_index = self._django_request.session.session_key
def _determine_subject(self):
"""Determines _subject and _subject_type for Assertion Subject."""
self._subject = self._django_request.user.email
def _encode_response(self):
"""Encodes _response_xml to _encoded_xml."""
self._saml_response = utils.nice64(str.encode(self._response_xml))
def _extract_saml_request(self):
"""Retrieves the _saml_request AuthnRequest from the _django_request."""
self._saml_request = self._django_request.session["SAMLRequest"]
self._relay_state = self._django_request.session["RelayState"]
def _format_assertion(self):
"""Formats _assertion_params as _assertion_xml."""
# https://commons.lbl.gov/display/IDMgmt/Attribute+Definitions
self._assertion_params["ATTRIBUTES"] = [
{
"FriendlyName": "eduPersonPrincipalName",
"Name": "urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
"Value": self._django_request.user.email,
},
{
"FriendlyName": "cn",
"Name": "urn:oid:2.5.4.3",
"Value": self._django_request.user.name,
},
{
"FriendlyName": "mail",
"Name": "urn:oid:0.9.2342.19200300.100.1.3",
"Value": self._django_request.user.email,
},
{
"FriendlyName": "displayName",
"Name": "urn:oid:2.16.840.1.113730.3.1.241",
"Value": self._django_request.user.username,
},
{
"FriendlyName": "uid",
"Name": "urn:oid:0.9.2342.19200300.100.1.1",
"Value": self._django_request.user.pk,
},
]
from passbook.providers.saml.models import SAMLPropertyMapping
for mapping in self._remote.property_mappings.all().select_subclasses():
if isinstance(mapping, SAMLPropertyMapping):
mapping_payload = {
"Name": mapping.saml_name,
"ValueArray": [],
"FriendlyName": mapping.friendly_name,
}
for value in mapping.values:
mapping_payload["ValueArray"].append(
value.format(
user=self._django_request.user, request=self._django_request
)
)
self._assertion_params["ATTRIBUTES"].append(mapping_payload)
self._assertion_xml = xml_render.get_assertion_xml(
"saml/xml/assertions/generic.xml", self._assertion_params, signed=True
)
def _format_response(self):
"""Formats _response_params as _response_xml."""
assertion_id = self._assertion_params["ASSERTION_ID"]
self._response_xml = xml_render.get_response_xml(
self._response_params, saml_provider=self._remote, assertion_id=assertion_id
)
def _get_django_response_params(self):
"""Returns a dictionary of parameters for the response template."""
return {
"acs_url": self._request_params["ACS_URL"],
"saml_response": self._saml_response,
"relay_state": self._relay_state,
"autosubmit": self._remote.application.skip_authorization,
}
def _parse_request(self):
"""Parses various parameters from _request_xml into _request_params."""
# Minimal test to verify that it's not binarily encoded still:
if not str(self._request_xml.strip()).startswith("<"):
raise Exception(
"RequestXML is not valid XML; "
"it may need to be decoded or decompressed."
)
root = ElementTree.fromstring(self._request_xml)
params = {}
params["ACS_URL"] = root.attrib["AssertionConsumerServiceURL"]
params["REQUEST_ID"] = root.attrib["ID"]
params["DESTINATION"] = root.attrib.get("Destination", "")
params["PROVIDER_NAME"] = root.attrib.get("ProviderName", "")
self._request_params = params
def _reset(self, django_request, sp_config=None):
"""Initialize (and reset) object properties, so we don't risk carrying
over anything from the last authentication.
If provided, use sp_config throughout; otherwise, it will be set in
_validate_request(). """
self._assertion_params = sp_config
self._assertion_xml = sp_config
self._assertion_id = sp_config
self._django_request = django_request
self._relay_state = sp_config
self._request = sp_config
self._request_id = sp_config
self._request_xml = sp_config
self._request_params = sp_config
self._response_id = sp_config
self._response_xml = sp_config
self._response_params = sp_config
self._saml_request = sp_config
self._saml_response = sp_config
self._session_index = sp_config
self._subject = sp_config
self._subject_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
self._system_params = {"ISSUER": self._remote.issuer}
def _validate_request(self):
"""
Validates the SAML request against the SP configuration of this
processor. Sub-classes should override this and raise a
`CannotHandleAssertion` exception if the validation fails.
Raises:
CannotHandleAssertion: if the ACS URL specified in the SAML request
doesn't match the one specified in the processor config.
"""
request_acs_url = self._request_params["ACS_URL"]
if self._remote.acs_url != request_acs_url:
msg = "couldn't find ACS url '{}' in SAML2IDP_REMOTES " "setting.".format(
request_acs_url
)
self._logger.info(msg)
raise exceptions.CannotHandleAssertion(msg)
def _validate_user(self):
"""Validates the User. Sub-classes should override this and
throw an CannotHandleAssertion Exception if the validation does not succeed."""
def can_handle(self, request):
"""Returns true if this processor can handle this request."""
self._reset(request)
# Read the request.
try:
self._extract_saml_request()
except Exception as exc:
msg = "can't find SAML request in user session: %s" % exc
self._logger.info(msg)
raise exceptions.CannotHandleAssertion(msg)
try:
self._decode_request()
except Exception as exc:
msg = "can't decode SAML request: %s" % exc
self._logger.info(msg)
raise exceptions.CannotHandleAssertion(msg)
try:
self._parse_request()
except Exception as exc:
msg = "can't parse SAML request: %s" % exc
self._logger.info(msg)
raise exceptions.CannotHandleAssertion(msg)
self._validate_request()
return True
def generate_response(self):
"""Processes request and returns template variables suitable for a response."""
# Build the assertion and response.
# Only call can_handle if SP initiated Request, otherwise we have no Request
if not self.is_idp_initiated:
self.can_handle(self._django_request)
self._validate_user()
self._build_assertion()
self._format_assertion()
self._build_response()
self._format_response()
self._encode_response()
# Return proper template params.
return self._get_django_response_params()
def init_deep_link(self, request, url):
"""Initialize this Processor to make an IdP-initiated call to the SP's
deep-linked URL."""
self._reset(request)
acs_url = self._remote.acs_url
# NOTE: The following request params are made up. Some are blank,
# because they comes over in the AuthnRequest, but we don't have an
# AuthnRequest in this case:
# - Destination: Should be this IdP's SSO endpoint URL. Not used in the response?
# - ProviderName: According to the spec, this is optional.
self._request_params = {
"ACS_URL": acs_url,
"DESTINATION": "",
"PROVIDER_NAME": "",
}
self._relay_state = url

View File

@ -10,7 +10,7 @@ from passbook.providers.saml.models import (
SAMLProvider,
get_provider_choices,
)
from passbook.providers.saml.utils import CertificateBuilder
from passbook.providers.saml.utils.cert import CertificateBuilder
class SAMLProviderForm(forms.ModelForm):
@ -32,12 +32,14 @@ class SAMLProviderForm(forms.ModelForm):
model = SAMLProvider
fields = [
"name",
"property_mappings",
"processor_path",
"acs_url",
"audience",
"processor_path",
"issuer",
"assertion_valid_for",
"assertion_valid_not_before",
"assertion_valid_not_on_or_after",
"session_valid_not_on_or_after",
"property_mappings",
"signing",
"signing_cert",
"signing_key",
@ -50,6 +52,9 @@ class SAMLProviderForm(forms.ModelForm):
"name": forms.TextInput(),
"audience": forms.TextInput(),
"issuer": forms.TextInput(),
"assertion_valid_not_before": forms.TextInput(),
"assertion_valid_not_on_or_after": forms.TextInput(),
"session_valid_not_on_or_after": forms.TextInput(),
"property_mappings": FilteredSelectMultiple(_("Property Mappings"), False),
}

View File

@ -0,0 +1,61 @@
# Generated by Django 2.2.9 on 2020-02-14 13:54
from django.db import migrations, models
import passbook.providers.saml.utils.time
def migrate_valid_for(apps, schema_editor):
"""Migrate from single number standing for minutes to 'minutes=3'"""
SAMLProvider = apps.get_model("passbook_providers_saml", "SAMLProvider")
db_alias = schema_editor.connection.alias
for provider in SAMLProvider.objects.using(db_alias).all():
provider.assertion_valid_not_on_or_after = (
f"minutes={provider.assertion_valid_for}"
)
provider.save()
class Migration(migrations.Migration):
dependencies = [
("passbook_providers_saml", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="samlprovider",
name="assertion_valid_not_before",
field=models.TextField(
default="minutes=5",
help_text="Assertion valid not before current time - this value (Format: hours=1;minutes=2;seconds=3).",
validators=[
passbook.providers.saml.utils.time.timedelta_string_validator
],
),
),
migrations.AddField(
model_name="samlprovider",
name="assertion_valid_not_on_or_after",
field=models.TextField(
default="minutes=5",
help_text="Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).",
validators=[
passbook.providers.saml.utils.time.timedelta_string_validator
],
),
),
migrations.RunPython(migrate_valid_for),
migrations.RemoveField(model_name="samlprovider", name="assertion_valid_for",),
migrations.AddField(
model_name="samlprovider",
name="session_valid_not_on_or_after",
field=models.TextField(
default="minutes=86400",
help_text="Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).",
validators=[
passbook.providers.saml.utils.time.timedelta_string_validator
],
),
),
]

View File

@ -7,7 +7,8 @@ from structlog import get_logger
from passbook.core.models import PropertyMapping, Provider
from passbook.lib.utils.reflection import class_to_path, path_to_class
from passbook.providers.saml.base import Processor
from passbook.providers.saml.processors.base import Processor
from passbook.providers.saml.utils.time import timedelta_string_validator
LOGGER = get_logger()
@ -16,11 +17,44 @@ class SAMLProvider(Provider):
"""Model to save information about a Remote SAML Endpoint"""
name = models.TextField()
processor_path = models.CharField(max_length=255, choices=[])
acs_url = models.URLField()
audience = models.TextField(default="")
processor_path = models.CharField(max_length=255, choices=[])
issuer = models.TextField()
assertion_valid_for = models.IntegerField(default=86400)
assertion_valid_not_before = models.TextField(
default="minutes=5",
validators=[timedelta_string_validator],
help_text=_(
(
"Assertion valid not before current time - this value "
"(Format: hours=1;minutes=2;seconds=3)."
)
),
)
assertion_valid_not_on_or_after = models.TextField(
default="minutes=5",
validators=[timedelta_string_validator],
help_text=_(
(
"Assertion not valid on or after current time + this value "
"(Format: hours=1;minutes=2;seconds=3)."
)
),
)
session_valid_not_on_or_after = models.TextField(
default="minutes=86400",
validators=[timedelta_string_validator],
help_text=_(
(
"Session not valid on or after current time + this value "
"(Format: hours=1;minutes=2;seconds=3)."
)
),
)
signing = models.BooleanField(default=True)
signing_cert = models.TextField()
signing_key = models.TextField()
@ -44,7 +78,7 @@ class SAMLProvider(Provider):
return self._processor
def __str__(self):
return "SAML Provider %s" % self.name
return f"SAML Provider {self.name}"
def link_download_metadata(self):
"""Get link to download XML metadata for admin interface"""
@ -73,7 +107,7 @@ class SAMLPropertyMapping(PropertyMapping):
form = "passbook.providers.saml.forms.SAMLPropertyMappingForm"
def __str__(self):
return "SAML Property Mapping %s" % self.saml_name
return f"SAML Property Mapping {self.saml_name}"
class Meta:

View File

@ -0,0 +1,247 @@
"""Basic SAML Processor"""
from typing import TYPE_CHECKING, Dict, List, Union
from defusedxml import ElementTree
from django.http import HttpRequest
from structlog import get_logger
from passbook.providers.saml.exceptions import CannotHandleAssertion
from passbook.providers.saml.utils import get_random_id
from passbook.providers.saml.utils.encoding import decode_base64_and_inflate, nice64
from passbook.providers.saml.utils.time import get_time_string, timedelta_from_string
from passbook.providers.saml.utils.xml_render import get_assertion_xml, get_response_xml
if TYPE_CHECKING:
from passbook.providers.saml.models import SAMLProvider
# pylint: disable=too-many-instance-attributes
class Processor:
"""Base SAML 2.0 AuthnRequest to Response Processor.
Sub-classes should provide Service Provider-specific functionality."""
is_idp_initiated = False
_remote: "SAMLProvider"
_http_request: HttpRequest
_assertion_xml: str
_response_xml: str
_saml_response: str
_relay_state: str
_saml_request: str
_assertion_params: Dict[str, Union[str, List[Dict[str, str]]]]
_request_params: Dict[str, str]
_system_params: Dict[str, str]
_response_params: Dict[str, str]
@property
def subject_format(self) -> str:
"""Get subject Format"""
return "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
def __init__(self, remote: "SAMLProvider"):
self.name = remote.name
self._remote = remote
self._logger = get_logger()
self._system_params = {
"ISSUER": self._remote.issuer,
}
def _build_assertion(self):
"""Builds _assertion_params."""
self._assertion_params = {
"ASSERTION_ID": get_random_id(),
"ASSERTION_SIGNATURE": "", # it's unsigned
"AUDIENCE": self._remote.audience,
"AUTH_INSTANT": get_time_string(),
"ISSUE_INSTANT": get_time_string(),
"NOT_BEFORE": get_time_string(
timedelta_from_string(self._remote.assertion_valid_not_before)
),
"NOT_ON_OR_AFTER": get_time_string(
timedelta_from_string(self._remote.assertion_valid_not_on_or_after)
),
"SESSION_INDEX": self._http_request.session.session_key,
"SESSION_NOT_ON_OR_AFTER": get_time_string(
timedelta_from_string(self._remote.session_valid_not_on_or_after)
),
"SP_NAME_QUALIFIER": self._remote.audience,
"SUBJECT": self._http_request.user.email,
"SUBJECT_FORMAT": self.subject_format,
}
self._assertion_params.update(self._system_params)
self._assertion_params.update(self._request_params)
def _build_response(self):
"""Builds _response_params."""
self._response_params = {
"ASSERTION": self._assertion_xml,
"ISSUE_INSTANT": get_time_string(),
"RESPONSE_ID": get_random_id(),
"RESPONSE_SIGNATURE": "", # initially unsigned
}
self._response_params.update(self._system_params)
self._response_params.update(self._request_params)
def _encode_response(self):
"""Encodes _response_xml to _encoded_xml."""
self._saml_response = nice64(str.encode(self._response_xml))
def _extract_saml_request(self):
"""Retrieves the _saml_request AuthnRequest from the _http_request."""
self._saml_request = self._http_request.session["SAMLRequest"]
self._relay_state = self._http_request.session["RelayState"]
def _format_assertion(self):
"""Formats _assertion_params as _assertion_xml."""
# https://commons.lbl.gov/display/IDMgmt/Attribute+Definitions
self._assertion_params["ATTRIBUTES"] = [
{
"FriendlyName": "eduPersonPrincipalName",
"Name": "urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
"Value": self._http_request.user.email,
},
{
"FriendlyName": "cn",
"Name": "urn:oid:2.5.4.3",
"Value": self._http_request.user.name,
},
{
"FriendlyName": "mail",
"Name": "urn:oid:0.9.2342.19200300.100.1.3",
"Value": self._http_request.user.email,
},
{
"FriendlyName": "displayName",
"Name": "urn:oid:2.16.840.1.113730.3.1.241",
"Value": self._http_request.user.username,
},
{
"FriendlyName": "uid",
"Name": "urn:oid:0.9.2342.19200300.100.1.1",
"Value": self._http_request.user.pk,
},
]
from passbook.providers.saml.models import SAMLPropertyMapping
for mapping in self._remote.property_mappings.all().select_subclasses():
if isinstance(mapping, SAMLPropertyMapping):
mapping_payload = {
"Name": mapping.saml_name,
"ValueArray": [],
"FriendlyName": mapping.friendly_name,
}
for value in mapping.values:
mapping_payload["ValueArray"].append(
value.format(
user=self._http_request.user, request=self._http_request
)
)
self._assertion_params["ATTRIBUTES"].append(mapping_payload)
self._assertion_xml = get_assertion_xml(
"saml/xml/assertions/generic.xml", self._assertion_params, signed=True
)
def _format_response(self):
"""Formats _response_params as _response_xml."""
assertion_id = self._assertion_params["ASSERTION_ID"]
self._response_xml = get_response_xml(
self._response_params, saml_provider=self._remote, assertion_id=assertion_id
)
def _get_django_response_params(self) -> Dict[str, str]:
"""Returns a dictionary of parameters for the response template."""
return {
"acs_url": self._request_params["ACS_URL"],
"saml_response": self._saml_response,
"relay_state": self._relay_state,
"autosubmit": self._remote.application.skip_authorization,
}
def _decode_and_parse_request(self):
"""Parses various parameters from _request_xml into _request_params."""
decoded_xml = decode_base64_and_inflate(self._saml_request).decode("utf-8")
root = ElementTree.fromstring(decoded_xml)
params = {}
params["ACS_URL"] = root.attrib.get(
"AssertionConsumerServiceURL", self._remote.acs_url
)
params["REQUEST_ID"] = root.attrib["ID"]
params["DESTINATION"] = root.attrib.get("Destination", "")
params["PROVIDER_NAME"] = root.attrib.get("ProviderName", "")
self._request_params = params
def _validate_request(self):
"""
Validates the SAML request against the SP configuration of this
processor. Sub-classes should override this and raise a
`CannotHandleAssertion` exception if the validation fails.
Raises:
CannotHandleAssertion: if the ACS URL specified in the SAML request
doesn't match the one specified in the processor config.
"""
request_acs_url = self._request_params["ACS_URL"]
if self._remote.acs_url != request_acs_url:
msg = "couldn't find ACS url '{}' in SAML2IDP_REMOTES " "setting.".format(
request_acs_url
)
self._logger.info(msg)
raise CannotHandleAssertion(msg)
def can_handle(self, request: HttpRequest) -> bool:
"""Returns true if this processor can handle this request."""
self._http_request = request
# Read the request.
try:
self._extract_saml_request()
except Exception as exc:
raise CannotHandleAssertion(
f"can't find SAML request in user session: {exc}"
) from exc
try:
self._decode_and_parse_request()
except Exception as exc:
raise CannotHandleAssertion(f"can't parse SAML request: {exc}") from exc
self._validate_request()
return True
def generate_response(self) -> Dict[str, str]:
"""Processes request and returns template variables suitable for a response."""
# Build the assertion and response.
# Only call can_handle if SP initiated Request, otherwise we have no Request
if not self.is_idp_initiated:
self.can_handle(self._http_request)
self._build_assertion()
self._format_assertion()
self._build_response()
self._format_response()
self._encode_response()
# Return proper template params.
return self._get_django_response_params()
def init_deep_link(self, request: HttpRequest, url: str):
"""Initialize this Processor to make an IdP-initiated call to the SP's
deep-linked URL."""
self._http_request = request
acs_url = self._remote.acs_url
# NOTE: The following request params are made up. Some are blank,
# because they comes over in the AuthnRequest, but we don't have an
# AuthnRequest in this case:
# - Destination: Should be this IdP's SSO endpoint URL. Not used in the response?
# - ProviderName: According to the spec, this is optional.
self._request_params = {
"ACS_URL": acs_url,
"DESTINATION": "",
"PROVIDER_NAME": "",
}
self._relay_state = url

View File

@ -1,7 +1,7 @@
"""Generic Processor"""
from passbook.providers.saml.base import Processor
from passbook.providers.saml.processors.base import Processor
class GenericProcessor(Processor):
"""Generic Response Handler Processor for testing against django-saml2-sp."""
"""Generic SAML2 Processor"""

View File

@ -1,16 +1,14 @@
"""Salesforce Processor"""
from passbook.providers.saml.base import Processor
from passbook.providers.saml.xml_render import get_assertion_xml
from passbook.providers.saml.processors.generic import GenericProcessor
from passbook.providers.saml.utils.xml_render import get_assertion_xml
class SalesForceProcessor(Processor):
class SalesForceProcessor(GenericProcessor):
"""SalesForce.com-specific SAML 2.0 AuthnRequest to Response Handler Processor."""
def _determine_audience(self):
self._audience = "IAMShowcase"
def _format_assertion(self):
super()._format_assertion()
self._assertion_xml = get_assertion_xml(
"saml/xml/assertions/salesforce.xml", self._assertion_params, signed=True
)

View File

@ -0,0 +1,30 @@
"""Test time utils"""
from datetime import timedelta
from django.core.exceptions import ValidationError
from django.test import TestCase
from passbook.providers.saml.utils.time import (
timedelta_from_string,
timedelta_string_validator,
)
class TestTimeUtils(TestCase):
"""Test time-utils"""
def test_valid(self):
"""Test valid expression"""
expr = "hours=3;minutes=1"
expected = timedelta(hours=3, minutes=1)
self.assertEqual(timedelta_from_string(expr), expected)
def test_invalid(self):
"""Test invalid expression"""
with self.assertRaises(ValueError):
timedelta_from_string("foo")
def test_validation(self):
"""Test Django model field validator"""
with self.assertRaises(ValidationError):
timedelta_string_validator("foo")

View File

@ -0,0 +1,18 @@
"""Small helper functions"""
import uuid
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
from django.template.context import Context
def render_xml(request: HttpRequest, template: str, ctx: Context) -> HttpResponse:
"""Render template with content_type application/xml"""
return render(request, template, context=ctx, content_type="application/xml")
def get_random_id() -> str:
"""Random hex id"""
# It is very important that these random IDs NOT start with a number.
random_id = "_" + uuid.uuid4().hex
return random_id

View File

@ -1,8 +1,6 @@
"""Wrappers to de/encode and de/inflate strings"""
import base64
"""Create self-signed certificates"""
import datetime
import uuid
import zlib
from cryptography import x509
from cryptography.hazmat.backends import default_backend
@ -11,24 +9,6 @@ from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
def decode_base64_and_inflate(b64string):
"""Base64 decode and ZLib decompress b64string"""
decoded_data = base64.b64decode(b64string)
return zlib.decompress(decoded_data, -15)
def deflate_and_base64_encode(string_val):
"""Base64 and ZLib Compress b64string"""
zlibbed_str = zlib.compress(string_val)
compressed_string = zlibbed_str[2:-4]
return base64.b64encode(compressed_string)
def nice64(src):
""" Returns src base64-encoded and formatted nicely for our XML. """
return base64.b64encode(src).decode("utf-8").replace("\n", "")
class CertificateBuilder:
"""Build self-signed certificates"""

View File

@ -0,0 +1,21 @@
"""Wrappers to de/encode and de/inflate strings"""
import base64
import zlib
def decode_base64_and_inflate(b64string):
"""Base64 decode and ZLib decompress b64string"""
decoded_data = base64.b64decode(b64string)
return zlib.decompress(decoded_data, -15)
def deflate_and_base64_encode(string_val):
"""Base64 and ZLib Compress b64string"""
zlibbed_str = zlib.compress(string_val)
compressed_string = zlibbed_str[2:-4]
return base64.b64encode(compressed_string)
def nice64(src):
""" Returns src base64-encoded and formatted nicely for our XML. """
return base64.b64encode(src).decode("utf-8").replace("\n", "")

View File

@ -0,0 +1,47 @@
"""Time utilities"""
import datetime
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
ALLOWED_KEYS = (
"days",
"seconds",
"microseconds",
"milliseconds",
"minutes",
"hours",
"weeks",
)
def timedelta_string_validator(value: str):
"""Validator for Django that checks if value can be parsed with `timedelta_from_string`"""
try:
timedelta_from_string(value)
except ValueError as exc:
raise ValidationError(
_("%(value)s is not in the correct format of 'hours=3;minutes=1'."),
params={"value": value},
) from exc
def timedelta_from_string(expr: str) -> datetime.timedelta:
"""Convert a string with the format of 'hours=1;minute=3;seconds=5' to a
`datetime.timedelta` Object with hours = 1, minutes = 3, seconds = 5"""
kwargs = {}
for duration_pair in expr.split(";"):
key, value = duration_pair.split("=")
if key.lower() not in ALLOWED_KEYS:
continue
kwargs[key.lower()] = float(value)
return datetime.timedelta(**kwargs)
def get_time_string(delta: datetime.timedelta = None) -> str:
"""Get Data formatted in SAML format"""
if delta is None:
delta = datetime.timedelta()
now = datetime.datetime.now()
final = now + delta
return final.strftime("%Y-%m-%dT%H:%M:%SZ")

View File

@ -6,7 +6,10 @@ from typing import TYPE_CHECKING
from structlog import get_logger
from passbook.lib.utils.template import render_to_string
from passbook.providers.saml.xml_signing import get_signature_xml, sign_with_signxml
from passbook.providers.saml.utils.xml_signing import (
get_signature_xml,
sign_with_signxml,
)
if TYPE_CHECKING:
from passbook.providers.saml.models import SAMLProvider
@ -60,7 +63,6 @@ def get_assertion_xml(template, parameters, signed=False):
_get_attribute_statement(params)
unsigned = render_to_string(template, params)
# LOGGER.debug('Unsigned: %s', unsigned)
if not signed:
return unsigned
@ -80,13 +82,11 @@ def get_response_xml(parameters, saml_provider: SAMLProvider, assertion_id=""):
raw_response = render_to_string("saml/xml/response.xml", params)
# LOGGER.debug('Unsigned: %s', unsigned)
if not saml_provider.signing:
return raw_response
signature_xml = get_signature_xml()
params["RESPONSE_SIGNATURE"] = signature_xml
# LOGGER.debug("Raw response: %s", raw_response)
signed = sign_with_signxml(
saml_provider.signing_key,

View File

@ -1,9 +1,11 @@
"""passbook SAML IDP Views"""
from typing import Optional
from django.contrib.auth import logout
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.http import HttpResponse, HttpResponseBadRequest
from django.http import HttpResponse, HttpResponseBadRequest, HttpRequest
from django.shortcuts import get_object_or_404, redirect, render, reverse
from django.utils.datastructures import MultiValueDictKeyError
from django.utils.decorators import method_decorator
@ -25,7 +27,7 @@ LOGGER = get_logger()
URL_VALIDATOR = URLValidator(schemes=("http", "https"))
def _generate_response(request, provider: SAMLProvider):
def _generate_response(request: HttpRequest, provider: SAMLProvider):
"""Generate a SAML response using processor_instance and return it in the proper Django
response."""
try:
@ -39,18 +41,13 @@ def _generate_response(request, provider: SAMLProvider):
return render(request, "saml/idp/login.html", ctx)
def render_xml(request, template, ctx):
"""Render template with content_type application/xml"""
return render(request, template, context=ctx, content_type="application/xml")
class AccessRequiredView(AccessMixin, View):
"""Mixin class for Views using a provider instance"""
_provider = None
_provider: Optional[SAMLProvider] = None
@property
def provider(self):
def provider(self) -> SAMLProvider:
"""Get provider instance"""
if not self._provider:
application = get_object_or_404(
@ -59,7 +56,7 @@ class AccessRequiredView(AccessMixin, View):
self._provider = get_object_or_404(SAMLProvider, pk=application.provider_id)
return self._provider
def _has_access(self):
def _has_access(self) -> bool:
"""Check if user has access to application"""
policy_engine = PolicyEngine(
self.provider.application.policies.all(), self.request.user, self.request
@ -92,8 +89,8 @@ class LoginBeginView(AccessRequiredView):
source = request.POST
else:
source = request.GET
# Store these values now, because Django's login cycle won't preserve them.
# Store these values now, because Django's login cycle won't preserve them.
try:
request.session["SAMLRequest"] = source["SAMLRequest"]
except (KeyError, MultiValueDictKeyError):
@ -128,10 +125,9 @@ class LoginProcessView(AccessRequiredView):
Presents a SAML 2.0 Assertion for POSTing back to the Service Provider."""
# pylint: disable=unused-argument
def get(self, request, application):
def get(self, request: HttpRequest, application: str) -> HttpResponse:
"""Handle get request, i.e. render form"""
LOGGER.debug("SAMLLoginProcessView", request=request, method="get")
# Check if user has access
# User access gets checked in dispatch
if self.provider.application.skip_authorization:
ctx = self.provider.processor.generate_response()
# Log Application Authorization
@ -147,16 +143,15 @@ class LoginProcessView(AccessRequiredView):
relay_state=ctx["relay_state"],
)
try:
full_res = _generate_response(request, self.provider)
return full_res
return _generate_response(request, self.provider)
except exceptions.CannotHandleAssertion as exc:
LOGGER.debug(exc)
return HttpResponseBadRequest()
# pylint: disable=unused-argument
def post(self, request, application):
def post(self, request, application: str) -> HttpResponse:
"""Handle post request, return back to ACS"""
LOGGER.debug("SAMLLoginProcessView", request=request, method="post")
# Check if user has access
# User access gets checked in dispatch
if request.POST.get("ACSUrl", None):
# User accepted request
Event.new(
@ -171,10 +166,10 @@ class LoginProcessView(AccessRequiredView):
relay_state=request.POST.get("RelayState"),
)
try:
full_res = _generate_response(request, self.provider)
return full_res
return _generate_response(request, self.provider)
except exceptions.CannotHandleAssertion as exc:
LOGGER.debug(exc)
return HttpResponseBadRequest()
class LogoutView(CSRFExemptMixin, AccessRequiredView):

View File

@ -2,7 +2,7 @@
from base64 import b64encode
from django.conf import settings
from django.http import Http404, HttpRequest, HttpResponse
from django.http import HttpRequest, HttpResponse
from django.views import View
from django_prometheus.exports import ExportToDjangoView
@ -13,11 +13,13 @@ class MetricsView(View):
def get(self, request: HttpRequest) -> HttpResponse:
"""Check for HTTP-Basic auth"""
auth_header = request.META.get("HTTP_AUTHORIZATION", "")
auth_type, _, credentials = auth_header.partition(" ")
auth_type, _, given_credentials = auth_header.partition(" ")
credentials = f"monitor:{settings.SECRET_KEY}"
expected = b64encode(str.encode(credentials)).decode()
if auth_type != "Basic" or credentials != expected:
raise Http404
if auth_type != "Basic" or given_credentials != expected:
response = HttpResponse(status=401)
response["WWW-Authenticate"] = 'Basic realm="passbook-monitoring"'
return response
return ExportToDjangoView(request)

View File

@ -35,7 +35,7 @@ for _passbook_app in get_apps():
urlpatterns += [
# Administration
path("administration/django/", admin.site.urls),
path("metrics", MetricsView.as_view(), name="metrics"),
path("metrics/", MetricsView.as_view(), name="metrics"),
]
if settings.DEBUG:

View File

@ -5,7 +5,7 @@ from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext as _
from passbook.admin.forms.source import SOURCE_FORM_FIELDS
from passbook.providers.saml.utils import CertificateBuilder
from passbook.providers.saml.utils.cert import CertificateBuilder
from passbook.sources.saml.models import SAMLSource

View File

@ -9,9 +9,9 @@ from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from passbook.providers.saml.base import get_random_id, get_time_string
from passbook.providers.saml.utils import nice64
from passbook.providers.saml.views import render_xml
from passbook.providers.saml.utils import get_random_id, render_xml
from passbook.providers.saml.utils.encoding import nice64
from passbook.providers.saml.utils.time import get_time_string
from passbook.sources.saml.models import SAMLSource
from passbook.sources.saml.utils import (
_get_user_from_response,

View File

@ -2,7 +2,7 @@
from structlog import get_logger
from passbook.lib.utils.template import render_to_string
from passbook.providers.saml.xml_signing import get_signature_xml
from passbook.providers.saml.utils.xml_signing import get_signature_xml
LOGGER = get_logger()

View File

@ -1,4 +1,4 @@
FROM python:3.7-slim-buster as locker
FROM python:3.8-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.7-slim-buster as static-build
FROM python:3.8-slim-buster as static-build
COPY --from=locker /app/requirements.txt /app/
COPY --from=locker /app/requirements-dev.txt /app/
@ -34,8 +34,7 @@ ENV PASSBOOK_POSTGRESQL__USER=passbook
ENV PASSBOOK_POSTGRESQL__PASSWORD="EK-5jnKfjrGRm<77"
RUN ./manage.py collectstatic --no-input
FROM docker.beryju.org/pixie/server
FROM beryju/pixie:latest
COPY --from=static-build /app/static /data/static/
COPY --from=static-build /app/static/robots.txt /data/robots.txt
WORKDIR /data
COPY --from=static-build /app/static /web-root/static/
COPY --from=static-build /app/static/robots.txt /web-root/robots.txt