Compare commits
23 Commits
version/0.
...
version/0.
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d51295db2 | |||
| 3bbded3555 | |||
| b3262e2a82 | |||
| 40614a65fc | |||
| 3cf558d594 | |||
| 812cc0d2f1 | |||
| e21ed92848 | |||
| 5184c4b7ef | |||
| 2c07859b68 | |||
| ae6304c05e | |||
| 501683e3cb | |||
| cc8afa8706 | |||
| 17a9e02bc0 | |||
| 6a669992a8 | |||
| 7ea5c22b6c | |||
| b11d6a5891 | |||
| 49830367a7 | |||
| e69ca5a229 | |||
| a57d21f5e8 | |||
| c7026407c6 | |||
| 69eecd6b60 | |||
| 810f10edfe | |||
| 1c57128f11 |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.10.0-stable
|
current_version = 0.10.1-stable
|
||||||
tag = True
|
tag = True
|
||||||
commit = True
|
commit = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
[run]
|
[run]
|
||||||
source = passbook
|
source = passbook
|
||||||
|
relative_files = true
|
||||||
omit =
|
omit =
|
||||||
*/asgi.py
|
*/asgi.py
|
||||||
manage.py
|
manage.py
|
||||||
|
|||||||
17
.github/workflows/release.yml
vendored
17
.github/workflows/release.yml
vendored
@ -18,11 +18,11 @@ jobs:
|
|||||||
- name: Building Docker Image
|
- name: Building Docker Image
|
||||||
run: docker build
|
run: docker build
|
||||||
--no-cache
|
--no-cache
|
||||||
-t beryju/passbook:0.10.0-stable
|
-t beryju/passbook:0.10.1-stable
|
||||||
-t beryju/passbook:latest
|
-t beryju/passbook:latest
|
||||||
-f Dockerfile .
|
-f Dockerfile .
|
||||||
- name: Push Docker Container to Registry (versioned)
|
- name: Push Docker Container to Registry (versioned)
|
||||||
run: docker push beryju/passbook:0.10.0-stable
|
run: docker push beryju/passbook:0.10.1-stable
|
||||||
- name: Push Docker Container to Registry (latest)
|
- name: Push Docker Container to Registry (latest)
|
||||||
run: docker push beryju/passbook:latest
|
run: docker push beryju/passbook:latest
|
||||||
build-proxy:
|
build-proxy:
|
||||||
@ -48,11 +48,11 @@ jobs:
|
|||||||
cd proxy
|
cd proxy
|
||||||
docker build \
|
docker build \
|
||||||
--no-cache \
|
--no-cache \
|
||||||
-t beryju/passbook-proxy:0.10.0-stable \
|
-t beryju/passbook-proxy:0.10.1-stable \
|
||||||
-t beryju/passbook-proxy:latest \
|
-t beryju/passbook-proxy:latest \
|
||||||
-f Dockerfile .
|
-f Dockerfile .
|
||||||
- name: Push Docker Container to Registry (versioned)
|
- name: Push Docker Container to Registry (versioned)
|
||||||
run: docker push beryju/passbook-proxy:0.10.0-stable
|
run: docker push beryju/passbook-proxy:0.10.1-stable
|
||||||
- name: Push Docker Container to Registry (latest)
|
- name: Push Docker Container to Registry (latest)
|
||||||
run: docker push beryju/passbook-proxy:latest
|
run: docker push beryju/passbook-proxy:latest
|
||||||
build-static:
|
build-static:
|
||||||
@ -77,11 +77,11 @@ jobs:
|
|||||||
run: docker build
|
run: docker build
|
||||||
--no-cache
|
--no-cache
|
||||||
--network=$(docker network ls | grep github | awk '{print $1}')
|
--network=$(docker network ls | grep github | awk '{print $1}')
|
||||||
-t beryju/passbook-static:0.10.0-stable
|
-t beryju/passbook-static:0.10.1-stable
|
||||||
-t beryju/passbook-static:latest
|
-t beryju/passbook-static:latest
|
||||||
-f static.Dockerfile .
|
-f static.Dockerfile .
|
||||||
- name: Push Docker Container to Registry (versioned)
|
- name: Push Docker Container to Registry (versioned)
|
||||||
run: docker push beryju/passbook-static:0.10.0-stable
|
run: docker push beryju/passbook-static:0.10.1-stable
|
||||||
- name: Push Docker Container to Registry (latest)
|
- name: Push Docker Container to Registry (latest)
|
||||||
run: docker push beryju/passbook-static:latest
|
run: docker push beryju/passbook-static:latest
|
||||||
test-release:
|
test-release:
|
||||||
@ -93,6 +93,9 @@ jobs:
|
|||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- name: Run test suite in final docker images
|
- name: Run test suite in final docker images
|
||||||
run: |
|
run: |
|
||||||
|
sudo apt-get install -y pwgen
|
||||||
|
echo "PG_PASS=$(pwgen 40 1)" >> .env
|
||||||
|
echo "PASSBOOK_SECRET_KEY=$(pwgen 50 1)" >> .env
|
||||||
docker-compose pull -q
|
docker-compose pull -q
|
||||||
docker-compose up --no-start
|
docker-compose up --no-start
|
||||||
docker-compose start postgresql redis
|
docker-compose start postgresql redis
|
||||||
@ -111,5 +114,5 @@ jobs:
|
|||||||
SENTRY_PROJECT: passbook
|
SENTRY_PROJECT: passbook
|
||||||
SENTRY_URL: https://sentry.beryju.org
|
SENTRY_URL: https://sentry.beryju.org
|
||||||
with:
|
with:
|
||||||
tagName: 0.10.0-stable
|
tagName: 0.10.1-stable
|
||||||
environment: beryjuorg-prod
|
environment: beryjuorg-prod
|
||||||
|
|||||||
5
.github/workflows/tag.yml
vendored
5
.github/workflows/tag.yml
vendored
@ -13,7 +13,10 @@ jobs:
|
|||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- name: Pre-release test
|
- name: Pre-release test
|
||||||
run: |
|
run: |
|
||||||
export PASSBOOK_TAG=latest
|
sudo apt-get install -y pwgen
|
||||||
|
echo "PASSBOOK_TAG=latest" >> .env
|
||||||
|
echo "PG_PASS=$(pwgen 40 1)" >> .env
|
||||||
|
echo "PASSBOOK_SECRET_KEY=$(pwgen 50 1)" >> .env
|
||||||
docker-compose pull -q
|
docker-compose pull -q
|
||||||
docker build \
|
docker build \
|
||||||
--no-cache \
|
--no-cache \
|
||||||
|
|||||||
2
Makefile
2
Makefile
@ -1,7 +1,7 @@
|
|||||||
all: lint-fix lint coverage gen
|
all: lint-fix lint coverage gen
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
coverage run --concurrency=multiprocessing manage.py test --failfast
|
coverage run --concurrency=multiprocessing manage.py test --failfast -v 3
|
||||||
coverage combine
|
coverage combine
|
||||||
coverage html
|
coverage html
|
||||||
coverage report
|
coverage report
|
||||||
|
|||||||
86
Pipfile.lock
generated
86
Pipfile.lock
generated
@ -74,11 +74,11 @@
|
|||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:20edd03ae4c4e141b0d8a9a9afc773af4345d54b68202b6aa502956b57b18b3f",
|
"sha256:79e95f428c485ea817969a78e77a311d2ec4d82e0955639d6126189c990ddad3",
|
||||||
"sha256:b596a80181fecd775ccc009286400f4d785136f250967895cb34beeeef65eb1f"
|
"sha256:d8ca27ee13deeb1a9e79f2fe5f923effa60947ed49bbdfbc2a9f5790aef64217"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.14.59"
|
"version": "==1.14.60"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -327,11 +327,11 @@
|
|||||||
},
|
},
|
||||||
"django-storages": {
|
"django-storages": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1e37da57678e6cf1e9914f84099a305323e4e1f261afe54fdb703cae7aa6fbc3",
|
"sha256:12de8fb2605b9b57bfaf54b075280d7cbb3b3ee1ca4bc9b9add147af87fe3a2c",
|
||||||
"sha256:36ed8dab33d761954498189592ce005920095fcbc02dab4184eb51393c370991"
|
"sha256:652275ab7844538c462b62810276c0244866f345878256a9e0e86f5b1283ae18"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.10"
|
"version": "==1.10.1"
|
||||||
},
|
},
|
||||||
"djangorestframework": {
|
"djangorestframework": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -835,9 +835,9 @@
|
|||||||
},
|
},
|
||||||
"pyrsistent": {
|
"pyrsistent": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:27515d2d5db0629c7dadf6fbe76973eb56f098c1b01d36de42eb69220d2c19e4"
|
"sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"
|
||||||
],
|
],
|
||||||
"version": "==0.17.2"
|
"version": "==0.17.3"
|
||||||
},
|
},
|
||||||
"python-dateutil": {
|
"python-dateutil": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -1269,43 +1269,43 @@
|
|||||||
},
|
},
|
||||||
"coverage": {
|
"coverage": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb",
|
"sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516",
|
||||||
"sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3",
|
"sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259",
|
||||||
"sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716",
|
"sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9",
|
||||||
"sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034",
|
"sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097",
|
||||||
"sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3",
|
"sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0",
|
||||||
"sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8",
|
"sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f",
|
||||||
"sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0",
|
"sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7",
|
||||||
"sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f",
|
"sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c",
|
||||||
"sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4",
|
"sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5",
|
||||||
"sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962",
|
"sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7",
|
||||||
"sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d",
|
"sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729",
|
||||||
"sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b",
|
"sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978",
|
||||||
"sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4",
|
"sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9",
|
||||||
"sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3",
|
"sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f",
|
||||||
"sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258",
|
"sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9",
|
||||||
"sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59",
|
"sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822",
|
||||||
"sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01",
|
"sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418",
|
||||||
"sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd",
|
"sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82",
|
||||||
"sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b",
|
"sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f",
|
||||||
"sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d",
|
"sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d",
|
||||||
"sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89",
|
"sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221",
|
||||||
"sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd",
|
"sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4",
|
||||||
"sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b",
|
"sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21",
|
||||||
"sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d",
|
"sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709",
|
||||||
"sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46",
|
"sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54",
|
||||||
"sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546",
|
"sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d",
|
||||||
"sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082",
|
"sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270",
|
||||||
"sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b",
|
"sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24",
|
||||||
"sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4",
|
"sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751",
|
||||||
"sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8",
|
"sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a",
|
||||||
"sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811",
|
"sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237",
|
||||||
"sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd",
|
"sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7",
|
||||||
"sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651",
|
"sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636",
|
||||||
"sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"
|
"sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==5.2.1"
|
"version": "==5.3"
|
||||||
},
|
},
|
||||||
"django": {
|
"django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|||||||
@ -20,7 +20,7 @@ wget https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml
|
|||||||
# Optionally enable Error-reporting
|
# Optionally enable Error-reporting
|
||||||
# export PASSBOOK_ERROR_REPORTING=true
|
# export PASSBOOK_ERROR_REPORTING=true
|
||||||
# Optionally deploy a different version
|
# Optionally deploy a different version
|
||||||
# export PASSBOOK_TAG=0.10.0-stable
|
# export PASSBOOK_TAG=0.10.1-stable
|
||||||
# If this is a productive installation, set a different PostgreSQL Password
|
# If this is a productive installation, set a different PostgreSQL Password
|
||||||
# export PG_PASS=$(pwgen 40 1)
|
# export PG_PASS=$(pwgen 40 1)
|
||||||
docker-compose pull
|
docker-compose pull
|
||||||
|
|||||||
@ -139,7 +139,7 @@ stages:
|
|||||||
displayName: Run full test suite
|
displayName: Run full test suite
|
||||||
inputs:
|
inputs:
|
||||||
script: |
|
script: |
|
||||||
pipenv run coverage run ./manage.py test passbook
|
pipenv run coverage run ./manage.py test passbook -v 3
|
||||||
mkdir output-unittest
|
mkdir output-unittest
|
||||||
mv unittest.xml output-unittest/unittest.xml
|
mv unittest.xml output-unittest/unittest.xml
|
||||||
mv .coverage output-unittest/coverage
|
mv .coverage output-unittest/coverage
|
||||||
@ -181,7 +181,7 @@ stages:
|
|||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
displayName: Run full test suite
|
displayName: Run full test suite
|
||||||
inputs:
|
inputs:
|
||||||
script: pipenv run coverage run ./manage.py test e2e
|
script: pipenv run coverage run ./manage.py test e2e -v 3
|
||||||
- task: CmdLine@2
|
- task: CmdLine@2
|
||||||
displayName: Prepare unittests and coverage for upload
|
displayName: Prepare unittests and coverage for upload
|
||||||
inputs:
|
inputs:
|
||||||
@ -225,11 +225,9 @@ stages:
|
|||||||
script: |
|
script: |
|
||||||
sudo pip install -U wheel pipenv
|
sudo pip install -U wheel pipenv
|
||||||
pipenv install --dev
|
pipenv install --dev
|
||||||
find .
|
|
||||||
pipenv run coverage combine coverage-e2e/coverage coverage-unittest/coverage
|
pipenv run coverage combine coverage-e2e/coverage coverage-unittest/coverage
|
||||||
pipenv run coverage xml
|
pipenv run coverage xml
|
||||||
pipenv run coverage html
|
pipenv run coverage html
|
||||||
find .
|
|
||||||
- task: PublishCodeCoverageResults@1
|
- task: PublishCodeCoverageResults@1
|
||||||
inputs:
|
inputs:
|
||||||
codeCoverageTool: 'Cobertura'
|
codeCoverageTool: 'Cobertura'
|
||||||
|
|||||||
@ -14,6 +14,8 @@ services:
|
|||||||
- POSTGRES_DB=passbook
|
- POSTGRES_DB=passbook
|
||||||
labels:
|
labels:
|
||||||
- traefik.enable=false
|
- traefik.enable=false
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
redis:
|
redis:
|
||||||
image: redis
|
image: redis
|
||||||
networks:
|
networks:
|
||||||
@ -21,13 +23,12 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
- traefik.enable=false
|
- traefik.enable=false
|
||||||
server:
|
server:
|
||||||
image: beryju/passbook:${PASSBOOK_TAG:-0.10.0-stable}
|
image: beryju/passbook:${PASSBOOK_TAG:-0.10.1-stable}
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
PASSBOOK_REDIS__HOST: redis
|
PASSBOOK_REDIS__HOST: redis
|
||||||
PASSBOOK_ERROR_REPORTING: ${PASSBOOK_ERROR_REPORTING:-false}
|
|
||||||
PASSBOOK_POSTGRESQL__HOST: postgresql
|
PASSBOOK_POSTGRESQL__HOST: postgresql
|
||||||
PASSBOOK_POSTGRESQL__PASSWORD: ${PG_PASS:-thisisnotagoodpassword}
|
PASSBOOK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||||
PASSBOOK_LOG_LEVEL: debug
|
PASSBOOK_LOG_LEVEL: debug
|
||||||
ports:
|
ports:
|
||||||
- 8000
|
- 8000
|
||||||
@ -37,8 +38,10 @@ services:
|
|||||||
- traefik.port=8000
|
- traefik.port=8000
|
||||||
- traefik.docker.network=internal
|
- traefik.docker.network=internal
|
||||||
- traefik.frontend.rule=PathPrefix:/
|
- traefik.frontend.rule=PathPrefix:/
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
worker:
|
worker:
|
||||||
image: beryju/passbook:${PASSBOOK_TAG:-0.10.0-stable}
|
image: beryju/passbook:${PASSBOOK_TAG:-0.10.1-stable}
|
||||||
command: worker
|
command: worker
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
@ -46,12 +49,13 @@ services:
|
|||||||
- traefik.enable=false
|
- traefik.enable=false
|
||||||
environment:
|
environment:
|
||||||
PASSBOOK_REDIS__HOST: redis
|
PASSBOOK_REDIS__HOST: redis
|
||||||
PASSBOOK_ERROR_REPORTING: ${PASSBOOK_ERROR_REPORTING:-false}
|
|
||||||
PASSBOOK_POSTGRESQL__HOST: postgresql
|
PASSBOOK_POSTGRESQL__HOST: postgresql
|
||||||
PASSBOOK_POSTGRESQL__PASSWORD: ${PG_PASS:-thisisnotagoodpassword}
|
PASSBOOK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||||
PASSBOOK_LOG_LEVEL: debug
|
PASSBOOK_LOG_LEVEL: debug
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
static:
|
static:
|
||||||
image: beryju/passbook-static:${PASSBOOK_TAG:-0.10.0-stable}
|
image: beryju/passbook-static:${PASSBOOK_TAG:-0.10.1-stable}
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
@ -11,14 +11,21 @@ This installation method is for test-setups and small-scale productive setups.
|
|||||||
|
|
||||||
Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml). Place it in a directory of your choice.
|
Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml). Place it in a directory of your choice.
|
||||||
|
|
||||||
|
To optionally enable error-reporting, run `echo PASSBOOK_ERROR_REPORTING=true >> .env`
|
||||||
|
|
||||||
|
To optionally deploy a different version run `echo PASSBOOK_TAG=0.10.1-stable >> .env`
|
||||||
|
|
||||||
|
If this is a fresh passbook install run the following commands to generate a password:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo apt-get install -y pwgen
|
||||||
|
echo "PG_PASS=$(pwgen 40 1)" >> .env
|
||||||
|
echo "PASSBOOK_SECRET_KEY=$(pwgen 50 1)" >> .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Afterwards, run these commands to finish
|
||||||
|
|
||||||
```
|
```
|
||||||
wget https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml
|
|
||||||
# Optionally enable Error-reporting
|
|
||||||
# export PASSBOOK_ERROR_REPORTING=true
|
|
||||||
# Optionally deploy a different version
|
|
||||||
# export PASSBOOK_TAG=0.10.0-stable
|
|
||||||
# If this is a productive installation, set a different PostgreSQL Password
|
|
||||||
# export PG_PASS=$(pwgen 40 1)
|
|
||||||
docker-compose pull
|
docker-compose pull
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
docker-compose run --rm server migrate
|
docker-compose run --rm server migrate
|
||||||
|
|||||||
@ -11,7 +11,7 @@ This installation automatically applies database migrations on startup. After th
|
|||||||
image:
|
image:
|
||||||
name: beryju/passbook
|
name: beryju/passbook
|
||||||
name_static: beryju/passbook-static
|
name_static: beryju/passbook-static
|
||||||
tag: 0.10.0-stable
|
tag: 0.10.1-stable
|
||||||
|
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,8 @@ 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.
|
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
|
```yaml
|
||||||
api_version: v1
|
apiVersion: v1
|
||||||
kind: secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/instance: test
|
app.kubernetes.io/instance: test
|
||||||
@ -14,65 +14,14 @@ metadata:
|
|||||||
app.kubernetes.io/name: passbook-proxy
|
app.kubernetes.io/name: passbook-proxy
|
||||||
app.kubernetes.io/version: 0.10.0
|
app.kubernetes.io/version: 0.10.0
|
||||||
name: passbook-outpost-api
|
name: passbook-outpost-api
|
||||||
string_data:
|
stringData:
|
||||||
passbook_host: '__PASSBOOK_URL__'
|
passbook_host: '__PASSBOOK_URL__'
|
||||||
passbook_host_insecure: 'true'
|
passbook_host_insecure: 'true'
|
||||||
token: '__PASSBOOK_TOKEN__'
|
token: '__PASSBOOK_TOKEN__'
|
||||||
type: Opaque
|
type: Opaque
|
||||||
---
|
---
|
||||||
api_version: apps/v1
|
apiVersion: v1
|
||||||
kind: deployment
|
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:
|
|
||||||
selector:
|
|
||||||
match_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
|
|
||||||
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
|
|
||||||
value_from:
|
|
||||||
secret_key_ref:
|
|
||||||
key: passbook_host
|
|
||||||
name: passbook-outpost-api
|
|
||||||
- name: PASSBOOK_TOKEN
|
|
||||||
value_from:
|
|
||||||
secret_key_ref:
|
|
||||||
key: token
|
|
||||||
name: passbook-outpost-api
|
|
||||||
- name: PASSBOOK_INSECURE
|
|
||||||
value_from:
|
|
||||||
secret_key_ref:
|
|
||||||
key: passbook_host_insecure
|
|
||||||
name: passbook-outpost-api
|
|
||||||
image: beryju/passbook-proxy:0.10.0
|
|
||||||
name: proxy
|
|
||||||
ports:
|
|
||||||
- containerPort: 4180
|
|
||||||
name: http
|
|
||||||
protocol: TCP
|
|
||||||
- containerPort: 4443
|
|
||||||
name: http
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
api_version: v1
|
|
||||||
kind: service
|
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/instance: test
|
app.kubernetes.io/instance: test
|
||||||
@ -96,4 +45,55 @@ spec:
|
|||||||
app.kubernetes.io/name: passbook-proxy
|
app.kubernetes.io/name: passbook-proxy
|
||||||
app.kubernetes.io/version: 0.10.0
|
app.kubernetes.io/version: 0.10.0
|
||||||
type: ClusterIP
|
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
|
||||||
```
|
```
|
||||||
|
|||||||
11
docs/troubleshooting/access.md
Normal file
11
docs/troubleshooting/access.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Troubleshooting access problems
|
||||||
|
|
||||||
|
## I get an access denied error when trying to access an application.
|
||||||
|
|
||||||
|
If your user is a superuser, or has the attribute `passbook_user_debug` set to true:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Afterwards, try to access the application again. You will now see a message explaining which policy denied you access:
|
||||||
|
|
||||||
|

|
||||||
BIN
docs/troubleshooting/access_denied_message.png
Normal file
BIN
docs/troubleshooting/access_denied_message.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
BIN
docs/troubleshooting/passbook_user_debug.png
Normal file
BIN
docs/troubleshooting/passbook_user_debug.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@ -1,8 +1,8 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
appVersion: "0.10.0-stable"
|
appVersion: "0.10.1-stable"
|
||||||
description: A Helm chart for passbook.
|
description: A Helm chart for passbook.
|
||||||
name: passbook
|
name: passbook
|
||||||
version: "0.10.0-stable"
|
version: "0.10.1-stable"
|
||||||
icon: https://github.com/BeryJu/passbook/blob/master/docs/images/logo.svg
|
icon: https://github.com/BeryJu/passbook/blob/master/docs/images/logo.svg
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: postgresql
|
- name: postgresql
|
||||||
|
|||||||
@ -22,10 +22,27 @@ spec:
|
|||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
k8s.passbook.beryju.org/component: web
|
k8s.passbook.beryju.org/component: web
|
||||||
spec:
|
spec:
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app.kubernetes.io/name
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- {{ include "passbook.name" . }}
|
||||||
|
- key: app.kubernetes.io/instance
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- {{ .Release.Name }}
|
||||||
|
- key: k8s.passbook.beryju.org/component
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- web
|
||||||
|
topologyKey: "kubernetes.io/hostname"
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: passbook-database-migrations
|
- name: passbook-database-migrations
|
||||||
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
|
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
|
||||||
imagePullPolicy: Always
|
|
||||||
args: [migrate]
|
args: [migrate]
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
@ -50,7 +67,6 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: {{ .Chart.Name }}
|
- name: {{ .Chart.Name }}
|
||||||
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
|
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
|
||||||
imagePullPolicy: Always
|
|
||||||
args: [server]
|
args: [server]
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
|
|||||||
@ -22,6 +22,24 @@ spec:
|
|||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
k8s.passbook.beryju.org/component: worker
|
k8s.passbook.beryju.org/component: worker
|
||||||
spec:
|
spec:
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: app.kubernetes.io/name
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- {{ include "passbook.name" . }}
|
||||||
|
- key: app.kubernetes.io/instance
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- {{ .Release.Name }}
|
||||||
|
- key: k8s.passbook.beryju.org/component
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- worker
|
||||||
|
topologyKey: "kubernetes.io/hostname"
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Chart.Name }}
|
- name: {{ .Chart.Name }}
|
||||||
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
|
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
image:
|
image:
|
||||||
name: beryju/passbook
|
name: beryju/passbook
|
||||||
name_static: beryju/passbook-static
|
name_static: beryju/passbook-static
|
||||||
tag: 0.10.0-stable
|
tag: 0.10.1-stable
|
||||||
|
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
|
|
||||||
|
|||||||
@ -55,6 +55,8 @@ nav:
|
|||||||
- Upgrading:
|
- Upgrading:
|
||||||
- to 0.9: upgrading/to-0.9.md
|
- to 0.9: upgrading/to-0.9.md
|
||||||
- to 0.10: upgrading/to-0.10.md
|
- to 0.10: upgrading/to-0.10.md
|
||||||
|
- Troubleshooting:
|
||||||
|
- Access problems: troubleshooting/access.md
|
||||||
|
|
||||||
repo_name: "BeryJu/passbook"
|
repo_name: "BeryJu/passbook"
|
||||||
repo_url: https://github.com/BeryJu/passbook
|
repo_url: https://github.com/BeryJu/passbook
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
"""passbook"""
|
"""passbook"""
|
||||||
__version__ = "0.10.0-stable"
|
__version__ = "0.10.1-stable"
|
||||||
|
|||||||
@ -55,7 +55,7 @@
|
|||||||
<th role="columnheader">
|
<th role="columnheader">
|
||||||
<div>
|
<div>
|
||||||
<div>{{ policy.name }}</div>
|
<div>{{ policy.name }}</div>
|
||||||
{% if not policy.bindings.exists %}
|
{% if not policy.bindings.exists and not policy.promptstage_set.exists %}
|
||||||
<i class="pf-icon pf-icon-warning-triangle"></i>
|
<i class="pf-icon pf-icon-warning-triangle"></i>
|
||||||
<small>{% trans 'Warning: Policy is not assigned.' %}</small>
|
<small>{% trans 'Warning: Policy is not assigned.' %}</small>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@ -58,7 +58,7 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
|||||||
application=None
|
application=None
|
||||||
)
|
)
|
||||||
kwargs["policies_without_binding"] = len(
|
kwargs["policies_without_binding"] = len(
|
||||||
Policy.objects.filter(bindings__isnull=True)
|
Policy.objects.filter(bindings__isnull=True, promptstage__isnull=True)
|
||||||
)
|
)
|
||||||
kwargs["cached_policies"] = len(cache.keys("policy_*"))
|
kwargs["cached_policies"] = len(cache.keys("policy_*"))
|
||||||
kwargs["cached_flows"] = len(cache.keys("flow_*"))
|
kwargs["cached_flows"] = len(cache.keys("flow_*"))
|
||||||
|
|||||||
@ -29,7 +29,16 @@ class ApplicationForm(forms.ModelForm):
|
|||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
"name": forms.TextInput(),
|
"name": forms.TextInput(),
|
||||||
"meta_launch_url": forms.TextInput(),
|
"meta_launch_url": forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": _(
|
||||||
|
(
|
||||||
|
"If left empty, passbook will try to extract the launch URL "
|
||||||
|
"based on the selected provider."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
"meta_icon_url": forms.TextInput(),
|
"meta_icon_url": forms.TextInput(),
|
||||||
"meta_publisher": forms.TextInput(),
|
"meta_publisher": forms.TextInput(),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ from passbook.lib.models import CreatedUpdatedModel
|
|||||||
from passbook.policies.models import PolicyBindingModel
|
from passbook.policies.models import PolicyBindingModel
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
PASSBOOK_USER_DEBUG = "passbook_user_debug"
|
||||||
|
|
||||||
|
|
||||||
def default_token_duration():
|
def default_token_duration():
|
||||||
@ -92,6 +93,12 @@ class Provider(models.Model):
|
|||||||
|
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def launch_url(self) -> Optional[str]:
|
||||||
|
"""URL to this provider and initiate authorization for the user.
|
||||||
|
Can return None for providers that are not URL-based"""
|
||||||
|
return None
|
||||||
|
|
||||||
def form(self) -> Type[ModelForm]:
|
def form(self) -> Type[ModelForm]:
|
||||||
"""Return Form class used to edit this object"""
|
"""Return Form class used to edit this object"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -119,6 +126,14 @@ class Application(PolicyBindingModel):
|
|||||||
meta_description = models.TextField(default="", blank=True)
|
meta_description = models.TextField(default="", blank=True)
|
||||||
meta_publisher = models.TextField(default="", blank=True)
|
meta_publisher = models.TextField(default="", blank=True)
|
||||||
|
|
||||||
|
def get_launch_url(self) -> Optional[str]:
|
||||||
|
"""Get launch URL if set, otherwise attempt to get launch URL based on provider."""
|
||||||
|
if self.meta_launch_url:
|
||||||
|
return self.meta_launch_url
|
||||||
|
if self.provider:
|
||||||
|
return self.provider.launch_url
|
||||||
|
return None
|
||||||
|
|
||||||
def get_provider(self) -> Optional[Provider]:
|
def get_provider(self) -> Optional[Provider]:
|
||||||
"""Get casted provider instance"""
|
"""Get casted provider instance"""
|
||||||
if not self.provider:
|
if not self.provider:
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<link rel="preload" href="{% static 'passbook/fonts/DINEngschriftStd.woff2' %}" as="font" type="font/woff2">
|
<link rel="preload" href="{% static 'passbook/fonts/DINEngschriftStd.woff2' %}" as="font" type="font/woff2" crossorigin>
|
||||||
<link rel="preload" href="{% static 'passbook/fonts/DINEngschriftStd.woff' %}" as="font" type="font/woff">
|
<link rel="preload" href="{% static 'passbook/fonts/DINEngschriftStd.woff' %}" as="font" type="font/woff" crossorigin>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
<title>{% block title %}{% trans title|default:config.passbook.branding.title %}{% endblock %}</title>
|
<title>{% block title %}{% trans title|default:config.passbook.branding.title %}{% endblock %}</title>
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
{% extends 'login/base_full.html' %}
|
|
||||||
|
|
||||||
{% load static %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load passbook_utils %}
|
|
||||||
|
|
||||||
{% block card_title %}
|
|
||||||
{% trans 'Permission denied' %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
{% trans 'Permission denied' %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block card %}
|
|
||||||
<form method="POST" class="pf-c-form">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% include 'partials/form.html' %}
|
|
||||||
<div class="pf-c-form__group">
|
|
||||||
<p>
|
|
||||||
<i class="pf-icon pf-icon-error-circle-o"></i>
|
|
||||||
{% trans 'Access denied' %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% if 'back' in request.GET %}
|
|
||||||
<a href="{% back %}" class="btn btn-primary btn-block btn-lg">{% trans 'Back' %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@ -24,7 +24,7 @@
|
|||||||
{% if applications %}
|
{% if applications %}
|
||||||
<div class="pf-l-gallery pf-m-gutter">
|
<div class="pf-l-gallery pf-m-gutter">
|
||||||
{% for app in applications %}
|
{% for app in applications %}
|
||||||
<a href="{{ app.meta_launch_url }}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
<a href="{{ app.get_launch_url }}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
{% if not app.meta_icon_url %}
|
{% if not app.meta_icon_url %}
|
||||||
<i class="pf-icon pf-icon-arrow"></i>
|
<i class="pf-icon pf-icon-arrow"></i>
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
"""passbook util view tests"""
|
|
||||||
import string
|
|
||||||
from random import SystemRandom
|
|
||||||
|
|
||||||
from django.test import RequestFactory, TestCase
|
|
||||||
|
|
||||||
from passbook.core.models import User
|
|
||||||
from passbook.core.views.utils import PermissionDeniedView
|
|
||||||
|
|
||||||
|
|
||||||
class TestUtilViews(TestCase):
|
|
||||||
"""Test Utility Views"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.user = User.objects.create_superuser(
|
|
||||||
username="unittest user",
|
|
||||||
email="unittest@example.com",
|
|
||||||
password="".join(
|
|
||||||
SystemRandom().choice(string.ascii_uppercase + string.digits)
|
|
||||||
for _ in range(8)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.factory = RequestFactory()
|
|
||||||
|
|
||||||
def test_permission_denied_view(self):
|
|
||||||
"""Test PermissionDeniedView"""
|
|
||||||
request = self.factory.get("something")
|
|
||||||
request.user = self.user
|
|
||||||
response = PermissionDeniedView.as_view()(request)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
"""passbook core utils view"""
|
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
from django.views.generic import TemplateView
|
|
||||||
|
|
||||||
|
|
||||||
class PermissionDeniedView(TemplateView):
|
|
||||||
"""Generic Permission denied view"""
|
|
||||||
|
|
||||||
template_name = "login/denied.html"
|
|
||||||
title = _("Permission denied.")
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
kwargs["title"] = self.title
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
|
|
||||||
class FlowNonApplicableException(BaseException):
|
class FlowNonApplicableException(BaseException):
|
||||||
"""Exception raised when a Flow does not apply to a user."""
|
"""Flow does not apply to current user (denied by policy)."""
|
||||||
|
|
||||||
|
|
||||||
class EmptyFlowException(BaseException):
|
class EmptyFlowException(BaseException):
|
||||||
"""Exception raised when a Flow Plan is empty"""
|
"""Flow has no stages."""
|
||||||
|
|||||||
57
passbook/flows/templates/flows/denied.html
Normal file
57
passbook/flows/templates/flows/denied.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{% extends 'login/base_full.html' %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load passbook_utils %}
|
||||||
|
|
||||||
|
{% block card_title %}
|
||||||
|
{% trans 'Permission denied' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans 'Permission denied' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block card %}
|
||||||
|
<form method="POST" class="pf-c-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include 'partials/form.html' %}
|
||||||
|
<div class="pf-c-form__group">
|
||||||
|
<p>
|
||||||
|
<i class="pf-icon pf-icon-error-circle-o"></i>
|
||||||
|
{% trans 'Access denied' %}
|
||||||
|
</p>
|
||||||
|
{% if error %}
|
||||||
|
<hr>
|
||||||
|
<p>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if policy_result %}
|
||||||
|
<hr>
|
||||||
|
<em>
|
||||||
|
{% trans 'Explanation:' %}
|
||||||
|
</em>
|
||||||
|
<ul class="pf-c-list">
|
||||||
|
{% for source_result in policy_result.source_results %}
|
||||||
|
<li>
|
||||||
|
{% blocktrans with name=source_result.source_policy.name result=source_result.passing %}
|
||||||
|
Policy '{{ name }}' returned result '{{ result }}'
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% if source_result.messages %}
|
||||||
|
<ul class="pf-c-list">
|
||||||
|
{% for message in source_result.messages %}
|
||||||
|
<li>{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if 'back' in request.GET %}
|
||||||
|
<a href="{% back %}" class="btn btn-primary btn-block btn-lg">{% trans 'Back' %}</a>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@ -5,7 +5,6 @@ from django.shortcuts import reverse
|
|||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
from passbook.flows.exceptions import EmptyFlowException, FlowNonApplicableException
|
|
||||||
from passbook.flows.markers import ReevaluateMarker, StageMarker
|
from passbook.flows.markers import ReevaluateMarker, StageMarker
|
||||||
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
|
from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
|
||||||
from passbook.flows.planner import FlowPlan
|
from passbook.flows.planner import FlowPlan
|
||||||
@ -48,8 +47,8 @@ class TestFlowExecutor(TestCase):
|
|||||||
"passbook_flows:flow-executor", kwargs={"flow_slug": flow.slug}
|
"passbook_flows:flow-executor", kwargs={"flow_slug": flow.slug}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(cancel_mock.call_count, 1)
|
self.assertEqual(cancel_mock.call_count, 2)
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
"passbook.policies.engine.PolicyEngine.result", POLICY_RETURN_FALSE,
|
"passbook.policies.engine.PolicyEngine.result", POLICY_RETURN_FALSE,
|
||||||
@ -66,8 +65,11 @@ class TestFlowExecutor(TestCase):
|
|||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("passbook_flows:flow-executor", kwargs={"flow_slug": flow.slug}),
|
reverse("passbook_flows:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertInHTML(FlowNonApplicableException.__doc__, response.rendered_content)
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{"type": "redirect", "to": reverse("passbook_flows:denied")},
|
||||||
|
)
|
||||||
|
|
||||||
def test_invalid_empty_flow(self):
|
def test_invalid_empty_flow(self):
|
||||||
"""Tests that an empty flow returns the correct error message"""
|
"""Tests that an empty flow returns the correct error message"""
|
||||||
@ -81,8 +83,11 @@ class TestFlowExecutor(TestCase):
|
|||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("passbook_flows:flow-executor", kwargs={"flow_slug": flow.slug}),
|
reverse("passbook_flows:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertInHTML(EmptyFlowException.__doc__, response.rendered_content)
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content),
|
||||||
|
{"type": "redirect", "to": reverse("passbook_flows:denied")},
|
||||||
|
)
|
||||||
|
|
||||||
def test_invalid_flow_redirect(self):
|
def test_invalid_flow_redirect(self):
|
||||||
"""Tests that an invalid flow still redirects"""
|
"""Tests that an invalid flow still redirects"""
|
||||||
|
|||||||
@ -12,18 +12,18 @@ from django.http import (
|
|||||||
from django.shortcuts import get_object_or_404, redirect, render, reverse
|
from django.shortcuts import get_object_or_404, redirect, render, reverse
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||||
from django.views.generic import TemplateView, View
|
from django.views.generic import TemplateView, View
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.audit.models import cleanse_dict
|
from passbook.audit.models import cleanse_dict
|
||||||
from passbook.core.views.utils import PermissionDeniedView
|
from passbook.core.models import PASSBOOK_USER_DEBUG
|
||||||
from passbook.flows.exceptions import EmptyFlowException, FlowNonApplicableException
|
from passbook.flows.exceptions import EmptyFlowException, FlowNonApplicableException
|
||||||
from passbook.flows.models import Flow, FlowDesignation, Stage
|
from passbook.flows.models import Flow, FlowDesignation, Stage
|
||||||
from passbook.flows.planner import FlowPlan, FlowPlanner
|
from passbook.flows.planner import FlowPlan, FlowPlanner
|
||||||
from passbook.lib.utils.reflection import class_to_path
|
from passbook.lib.utils.reflection import class_to_path
|
||||||
from passbook.lib.utils.urls import is_url_absolute, redirect_with_qs
|
from passbook.lib.utils.urls import is_url_absolute, redirect_with_qs
|
||||||
from passbook.lib.views import bad_request_message
|
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
# Argument used to redirect user after login
|
# Argument used to redirect user after login
|
||||||
@ -31,6 +31,8 @@ NEXT_ARG_NAME = "next"
|
|||||||
SESSION_KEY_PLAN = "passbook_flows_plan"
|
SESSION_KEY_PLAN = "passbook_flows_plan"
|
||||||
SESSION_KEY_APPLICATION_PRE = "passbook_flows_application_pre"
|
SESSION_KEY_APPLICATION_PRE = "passbook_flows_application_pre"
|
||||||
SESSION_KEY_GET = "passbook_flows_get"
|
SESSION_KEY_GET = "passbook_flows_get"
|
||||||
|
SESSION_KEY_DENIED_ERROR = "passbook_flows_denied_error"
|
||||||
|
SESSION_KEY_DENIED_POLICY_RESULT = "passbook_flows_denied_policy_result"
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(xframe_options_sameorigin, name="dispatch")
|
@method_decorator(xframe_options_sameorigin, name="dispatch")
|
||||||
@ -54,7 +56,9 @@ class FlowExecutorView(View):
|
|||||||
LOGGER.debug("f(exec): Redirecting to next on fail")
|
LOGGER.debug("f(exec): Redirecting to next on fail")
|
||||||
return redirect(self.request.GET.get(NEXT_ARG_NAME))
|
return redirect(self.request.GET.get(NEXT_ARG_NAME))
|
||||||
message = exc.__doc__ if exc.__doc__ else str(exc)
|
message = exc.__doc__ if exc.__doc__ else str(exc)
|
||||||
return bad_request_message(self.request, message)
|
return to_stage_response(
|
||||||
|
self.request, self.stage_invalid(error_message=message)
|
||||||
|
)
|
||||||
|
|
||||||
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
|
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
|
||||||
# Early check if theres an active Plan for the current session
|
# Early check if theres an active Plan for the current session
|
||||||
@ -193,11 +197,19 @@ class FlowExecutorView(View):
|
|||||||
)
|
)
|
||||||
return self._flow_done()
|
return self._flow_done()
|
||||||
|
|
||||||
def stage_invalid(self) -> HttpResponse:
|
def stage_invalid(self, error_message: Optional[str] = None) -> HttpResponse:
|
||||||
"""Callback used stage when data is correct but a policy denies access
|
"""Callback used stage when data is correct but a policy denies access
|
||||||
or the user account is disabled."""
|
or the user account is disabled.
|
||||||
|
|
||||||
|
Optionally, an exception can be passed, which will be shown if the current user
|
||||||
|
is a superuser."""
|
||||||
LOGGER.debug("f(exec): Stage invalid", flow_slug=self.flow.slug)
|
LOGGER.debug("f(exec): Stage invalid", flow_slug=self.flow.slug)
|
||||||
self.cancel()
|
self.cancel()
|
||||||
|
if self.request.user and self.request.user.is_authenticated:
|
||||||
|
if self.request.user.is_superuser or self.request.user.attributes.get(
|
||||||
|
PASSBOOK_USER_DEBUG, False
|
||||||
|
):
|
||||||
|
self.request.session[SESSION_KEY_DENIED_ERROR] = error_message
|
||||||
return redirect_with_qs("passbook_flows:denied", self.request.GET)
|
return redirect_with_qs("passbook_flows:denied", self.request.GET)
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
@ -212,9 +224,22 @@ class FlowExecutorView(View):
|
|||||||
del self.request.session[key]
|
del self.request.session[key]
|
||||||
|
|
||||||
|
|
||||||
class FlowPermissionDeniedView(PermissionDeniedView):
|
class FlowPermissionDeniedView(TemplateView):
|
||||||
"""User could not be authenticated"""
|
"""User could not be authenticated"""
|
||||||
|
|
||||||
|
template_name = "flows/denied.html"
|
||||||
|
title = _("Permission denied.")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
kwargs["title"] = self.title
|
||||||
|
if SESSION_KEY_DENIED_ERROR in self.request.session:
|
||||||
|
kwargs["error"] = self.request.session[SESSION_KEY_DENIED_ERROR]
|
||||||
|
if SESSION_KEY_DENIED_POLICY_RESULT in self.request.session:
|
||||||
|
kwargs["policy_result"] = self.request.session[
|
||||||
|
SESSION_KEY_DENIED_POLICY_RESULT
|
||||||
|
]
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class FlowExecutorShellView(TemplateView):
|
class FlowExecutorShellView(TemplateView):
|
||||||
"""Executor Shell view, loads a dummy card with a spinner
|
"""Executor Shell view, loads a dummy card with a spinner
|
||||||
|
|||||||
@ -10,6 +10,7 @@ redis:
|
|||||||
password: ''
|
password: ''
|
||||||
cache_db: 0
|
cache_db: 0
|
||||||
message_queue_db: 1
|
message_queue_db: 1
|
||||||
|
ws_db: 2
|
||||||
|
|
||||||
debug: false
|
debug: false
|
||||||
log_level: info
|
log_level: info
|
||||||
|
|||||||
@ -27,12 +27,12 @@ class CreateAssignPermView(CreateView):
|
|||||||
|
|
||||||
|
|
||||||
def bad_request_message(
|
def bad_request_message(
|
||||||
request: HttpRequest, message: str, title="Bad Request"
|
request: HttpRequest,
|
||||||
|
message: str,
|
||||||
|
title="Bad Request",
|
||||||
|
template="error/generic.html",
|
||||||
) -> TemplateResponse:
|
) -> TemplateResponse:
|
||||||
"""Return generic error page with message, with status code set to 400"""
|
"""Return generic error page with message, with status code set to 400"""
|
||||||
return TemplateResponse(
|
return TemplateResponse(
|
||||||
request,
|
request, template, {"message": message, "card_title": _(title)}, status=400,
|
||||||
"error/generic.html",
|
|
||||||
{"message": message, "card_title": _(title)},
|
|
||||||
status=400,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@ -7,9 +7,10 @@ from uuid import uuid4
|
|||||||
from dacite import from_dict
|
from dacite import from_dict
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from django.db.models.base import Model
|
from django.db.models.base import Model
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from guardian.models import UserObjectPermission
|
||||||
from guardian.shortcuts import assign_perm
|
from guardian.shortcuts import assign_perm
|
||||||
|
|
||||||
from passbook.core.models import Provider, Token, TokenIntents, User
|
from passbook.core.models import Provider, Token, TokenIntents, User
|
||||||
@ -104,24 +105,24 @@ class Outpost(models.Model):
|
|||||||
return datetime.fromtimestamp(value)
|
return datetime.fromtimestamp(value)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _create_user(self) -> User:
|
|
||||||
"""Create user and assign permissions for all required objects"""
|
|
||||||
user: User = User.objects.create(username=f"pb-outpost-{self.uuid.hex}")
|
|
||||||
user.set_unusable_password()
|
|
||||||
user.save()
|
|
||||||
for model in self.get_required_objects():
|
|
||||||
assign_perm(
|
|
||||||
f"{model._meta.app_label}.view_{model._meta.model_name}", user, model
|
|
||||||
)
|
|
||||||
return user
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user(self) -> User:
|
def user(self) -> User:
|
||||||
"""Get/create user with access to all required objects"""
|
"""Get/create user with access to all required objects"""
|
||||||
user = User.objects.filter(username=f"pb-outpost-{self.uuid.hex}")
|
users = User.objects.filter(username=f"pb-outpost-{self.uuid.hex}")
|
||||||
if user.exists():
|
if not users.exists():
|
||||||
return user.first()
|
user: User = User.objects.create(username=f"pb-outpost-{self.uuid.hex}")
|
||||||
return self._create_user()
|
user.set_unusable_password()
|
||||||
|
user.save()
|
||||||
|
else:
|
||||||
|
user = users.first()
|
||||||
|
# To ensure the user only has the correct permissions, we delete all of them and re-add
|
||||||
|
# the ones the user needs
|
||||||
|
with transaction.atomic():
|
||||||
|
UserObjectPermission.objects.filter(user=user).delete()
|
||||||
|
for model in self.get_required_objects():
|
||||||
|
code_name = f"{model._meta.app_label}.view_{model._meta.model_name}"
|
||||||
|
assign_perm(code_name, user, model)
|
||||||
|
return user
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def token(self) -> Token:
|
def token(self) -> Token:
|
||||||
|
|||||||
@ -41,7 +41,7 @@ def outpost_send_update(model_class: str, model_pk: Any):
|
|||||||
"""Send outpost update to all registered outposts, irregardless to which passbook
|
"""Send outpost update to all registered outposts, irregardless to which passbook
|
||||||
instance they are connected"""
|
instance they are connected"""
|
||||||
model = path_to_class(model_class)
|
model = path_to_class(model_class)
|
||||||
outpost_model: OutpostModel = model.objects.get(model_pk)
|
outpost_model: OutpostModel = model.objects.get(pk=model_pk)
|
||||||
for outpost in outpost_model.outpost_set.all():
|
for outpost in outpost_model.outpost_set.all():
|
||||||
channel_layer = get_channel_layer()
|
channel_layer = get_channel_layer()
|
||||||
for channel in outpost.channels:
|
for channel in outpost.channels:
|
||||||
|
|||||||
60
passbook/outposts/tests.py
Normal file
60
passbook/outposts/tests.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
"""outpost tests"""
|
||||||
|
from django.test import TestCase
|
||||||
|
from guardian.models import UserObjectPermission
|
||||||
|
|
||||||
|
from passbook.crypto.models import CertificateKeyPair
|
||||||
|
from passbook.flows.models import Flow
|
||||||
|
from passbook.outposts.models import Outpost, OutpostDeploymentType, OutpostType
|
||||||
|
from passbook.providers.proxy.models import ProxyProvider
|
||||||
|
|
||||||
|
|
||||||
|
class OutpostTests(TestCase):
|
||||||
|
"""Outpost Tests"""
|
||||||
|
|
||||||
|
def test_service_account_permissions(self):
|
||||||
|
"""Test that the service account has correct permissions"""
|
||||||
|
provider: ProxyProvider = ProxyProvider.objects.create(
|
||||||
|
name="test",
|
||||||
|
internal_host="http://localhost",
|
||||||
|
external_host="http://localhost",
|
||||||
|
authorization_flow=Flow.objects.first(),
|
||||||
|
)
|
||||||
|
outpost: Outpost = Outpost.objects.create(
|
||||||
|
name="test",
|
||||||
|
type=OutpostType.PROXY,
|
||||||
|
deployment_type=OutpostDeploymentType.CUSTOM,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Before we add a provider, the user should only have access to the outpost
|
||||||
|
permissions = UserObjectPermission.objects.filter(user=outpost.user)
|
||||||
|
self.assertEqual(len(permissions), 1)
|
||||||
|
self.assertEqual(permissions[0].object_pk, str(outpost.pk))
|
||||||
|
|
||||||
|
# We add a provider, user should only have access to outpost and provider
|
||||||
|
outpost.providers.add(provider)
|
||||||
|
outpost.save()
|
||||||
|
permissions = UserObjectPermission.objects.filter(user=outpost.user).order_by(
|
||||||
|
"content_type__model"
|
||||||
|
)
|
||||||
|
self.assertEqual(len(permissions), 2)
|
||||||
|
self.assertEqual(permissions[0].object_pk, str(outpost.pk))
|
||||||
|
self.assertEqual(permissions[1].object_pk, str(provider.pk))
|
||||||
|
|
||||||
|
# Provider requires a certificate-key-pair, user should have permissions for it
|
||||||
|
keypair = CertificateKeyPair.objects.first()
|
||||||
|
provider.certificate = keypair
|
||||||
|
provider.save()
|
||||||
|
permissions = UserObjectPermission.objects.filter(user=outpost.user).order_by(
|
||||||
|
"content_type__model"
|
||||||
|
)
|
||||||
|
self.assertEqual(len(permissions), 3)
|
||||||
|
self.assertEqual(permissions[0].object_pk, str(keypair.pk))
|
||||||
|
self.assertEqual(permissions[1].object_pk, str(outpost.pk))
|
||||||
|
self.assertEqual(permissions[2].object_pk, str(provider.pk))
|
||||||
|
|
||||||
|
# Remove provider from outpost, user should only have access to outpost
|
||||||
|
outpost.providers.remove(provider)
|
||||||
|
outpost.save()
|
||||||
|
permissions = UserObjectPermission.objects.filter(user=outpost.user)
|
||||||
|
self.assertEqual(len(permissions), 1)
|
||||||
|
self.assertEqual(permissions[0].object_pk, str(outpost.pk))
|
||||||
@ -68,7 +68,7 @@ class PolicyEngine:
|
|||||||
|
|
||||||
def _check_policy_type(self, policy: Policy):
|
def _check_policy_type(self, policy: Policy):
|
||||||
"""Check policy type, make sure it's not the root class as that has no logic implemented"""
|
"""Check policy type, make sure it's not the root class as that has no logic implemented"""
|
||||||
# policy_type = type(policy)
|
# pyright: reportGeneralTypeIssues=false
|
||||||
if policy.__class__ == Policy:
|
if policy.__class__ == Policy:
|
||||||
raise TypeError(f"Policy '{policy}' is root type")
|
raise TypeError(f"Policy '{policy}' is root type")
|
||||||
|
|
||||||
@ -109,19 +109,25 @@ class PolicyEngine:
|
|||||||
@property
|
@property
|
||||||
def result(self) -> PolicyResult:
|
def result(self) -> PolicyResult:
|
||||||
"""Get policy-checking result"""
|
"""Get policy-checking result"""
|
||||||
messages: List[str] = []
|
|
||||||
process_results: List[PolicyResult] = [
|
process_results: List[PolicyResult] = [
|
||||||
x.result for x in self.__processes if x.result
|
x.result for x in self.__processes if x.result
|
||||||
]
|
]
|
||||||
|
final_result = PolicyResult(False)
|
||||||
|
final_result.messages = []
|
||||||
|
final_result.source_results = list(process_results + self.__cached_policies)
|
||||||
for result in process_results + self.__cached_policies:
|
for result in process_results + self.__cached_policies:
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
"P_ENG: result", passing=result.passing, messages=result.messages
|
"P_ENG: result", passing=result.passing, messages=result.messages
|
||||||
)
|
)
|
||||||
if result.messages:
|
if result.messages:
|
||||||
messages += result.messages
|
final_result.messages.extend(result.messages)
|
||||||
if not result.passing:
|
if not result.passing:
|
||||||
return PolicyResult(False, *messages)
|
final_result.messages = tuple(final_result.messages)
|
||||||
return PolicyResult(True, *messages)
|
final_result.passing = False
|
||||||
|
return final_result
|
||||||
|
final_result.messages = tuple(final_result.messages)
|
||||||
|
final_result.passing = True
|
||||||
|
return final_result
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def passing(self) -> bool:
|
def passing(self) -> bool:
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<label for="" class="pf-c-form__label"></label>
|
<label for="" class="pf-c-form__label"></label>
|
||||||
<div class="c-form__horizontal-group">
|
<div class="c-form__horizontal-group">
|
||||||
<p>
|
<p>
|
||||||
Expression using Python. See <a href="https://passbook.beryju.org/policies/expression/">here</a> for a list of all variables.
|
Expression using Python. See <a target="_blank" href="https://passbook.beryju.org/policies/expression/">here</a> for a list of all variables.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,7 +10,10 @@ from django.utils.translation import gettext as _
|
|||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Application, Provider, User
|
from passbook.core.models import Application, Provider, User
|
||||||
from passbook.flows.views import SESSION_KEY_APPLICATION_PRE
|
from passbook.flows.views import (
|
||||||
|
SESSION_KEY_APPLICATION_PRE,
|
||||||
|
SESSION_KEY_DENIED_POLICY_RESULT,
|
||||||
|
)
|
||||||
from passbook.policies.engine import PolicyEngine
|
from passbook.policies.engine import PolicyEngine
|
||||||
from passbook.policies.types import PolicyResult
|
from passbook.policies.types import PolicyResult
|
||||||
|
|
||||||
@ -36,8 +39,12 @@ class PolicyAccessMixin(BaseMixin, AccessMixin):
|
|||||||
self.get_redirect_field_name(),
|
self.get_redirect_field_name(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_no_permission_authorized(self) -> HttpResponse:
|
def handle_no_permission_authenticated(
|
||||||
"""Function called when user has no permissions but is authorized"""
|
self, result: Optional[PolicyResult] = None
|
||||||
|
) -> HttpResponse:
|
||||||
|
"""Function called when user has no permissions but is authenticated"""
|
||||||
|
if result:
|
||||||
|
self.request.session[SESSION_KEY_DENIED_POLICY_RESULT] = result
|
||||||
# TODO: Remove this URL and render the view instead
|
# TODO: Remove this URL and render the view instead
|
||||||
return redirect("passbook_flows:denied")
|
return redirect("passbook_flows:denied")
|
||||||
|
|
||||||
|
|||||||
@ -63,6 +63,7 @@ class PolicyProcess(Process):
|
|||||||
except PolicyException as exc:
|
except PolicyException as exc:
|
||||||
LOGGER.debug("P_ENG(proc): error", exc=exc)
|
LOGGER.debug("P_ENG(proc): error", exc=exc)
|
||||||
policy_result = PolicyResult(False, str(exc))
|
policy_result = PolicyResult(False, str(exc))
|
||||||
|
policy_result.source_policy = self.binding.policy
|
||||||
# Invert result if policy.negate is set
|
# Invert result if policy.negate is set
|
||||||
if self.binding.negate:
|
if self.binding.negate:
|
||||||
policy_result.passing = not policy_result.passing
|
policy_result.passing = not policy_result.passing
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
"""policy structures"""
|
"""policy structures"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Dict, Optional, Tuple
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from passbook.core.models import User
|
from passbook.core.models import User
|
||||||
|
from passbook.policies.models import Policy
|
||||||
|
|
||||||
|
|
||||||
class PolicyRequest:
|
class PolicyRequest:
|
||||||
@ -34,9 +35,14 @@ class PolicyResult:
|
|||||||
passing: bool
|
passing: bool
|
||||||
messages: Tuple[str, ...]
|
messages: Tuple[str, ...]
|
||||||
|
|
||||||
|
source_policy: Optional[Policy]
|
||||||
|
source_results: Optional[List["PolicyResult"]]
|
||||||
|
|
||||||
def __init__(self, passing: bool, *messages: str):
|
def __init__(self, passing: bool, *messages: str):
|
||||||
self.passing = passing
|
self.passing = passing
|
||||||
self.messages = messages
|
self.messages = messages
|
||||||
|
self.source_policy = None
|
||||||
|
self.source_results = []
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import time
|
|||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from typing import Any, Dict, List, Optional, Type
|
from typing import Any, Dict, List, Optional, Type
|
||||||
|
from urllib.parse import urlparse
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -230,6 +231,16 @@ class OAuth2Provider(Provider):
|
|||||||
except Provider.application.RelatedObjectDoesNotExist:
|
except Provider.application.RelatedObjectDoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def launch_url(self) -> Optional[str]:
|
||||||
|
"""Guess launch_url based on first redirect_uri"""
|
||||||
|
if not self.redirect_uris:
|
||||||
|
return None
|
||||||
|
main_url = self.redirect_uris[0]
|
||||||
|
launch_url = urlparse(main_url)
|
||||||
|
launch_url.path = ""
|
||||||
|
return launch_url.geturl()
|
||||||
|
|
||||||
def form(self) -> Type[ModelForm]:
|
def form(self) -> Type[ModelForm]:
|
||||||
from passbook.providers.oauth2.forms import OAuth2ProviderForm
|
from passbook.providers.oauth2.forms import OAuth2ProviderForm
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from django.utils import timezone
|
|||||||
from django.views import View
|
from django.views import View
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Application, Token
|
from passbook.core.models import Application
|
||||||
from passbook.flows.models import in_memory_stage
|
from passbook.flows.models import in_memory_stage
|
||||||
from passbook.flows.planner import (
|
from passbook.flows.planner import (
|
||||||
PLAN_CONTEXT_APPLICATION,
|
PLAN_CONTEXT_APPLICATION,
|
||||||
@ -95,7 +95,7 @@ class OAuthAuthorizationParams:
|
|||||||
elif response_type in [
|
elif response_type in [
|
||||||
ResponseTypes.ID_TOKEN,
|
ResponseTypes.ID_TOKEN,
|
||||||
ResponseTypes.ID_TOKEN_TOKEN,
|
ResponseTypes.ID_TOKEN_TOKEN,
|
||||||
ResponseTypes.TOKEN,
|
ResponseTypes.CODE_TOKEN,
|
||||||
]:
|
]:
|
||||||
grant_type = GrantTypes.IMPLICIT
|
grant_type = GrantTypes.IMPLICIT
|
||||||
elif response_type in [
|
elif response_type in [
|
||||||
@ -220,9 +220,11 @@ class OAuthFulfillmentStage(StageView):
|
|||||||
)
|
)
|
||||||
return redirect(self.create_response_uri())
|
return redirect(self.create_response_uri())
|
||||||
except (ClientIdError, RedirectUriError) as error:
|
except (ClientIdError, RedirectUriError) as error:
|
||||||
|
self.executor.stage_invalid()
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
return bad_request_message(request, error.description, title=error.error)
|
return bad_request_message(request, error.description, title=error.error)
|
||||||
except AuthorizeError as error:
|
except AuthorizeError as error:
|
||||||
|
self.executor.stage_invalid()
|
||||||
uri = error.create_uri(self.params.redirect_uri, self.params.state)
|
uri = error.create_uri(self.params.redirect_uri, self.params.state)
|
||||||
return redirect(uri)
|
return redirect(uri)
|
||||||
|
|
||||||
@ -248,28 +250,26 @@ class OAuthFulfillmentStage(StageView):
|
|||||||
str(self.params.state) if self.params.state else ""
|
str(self.params.state) if self.params.state else ""
|
||||||
]
|
]
|
||||||
elif self.params.grant_type in [GrantTypes.IMPLICIT, GrantTypes.HYBRID]:
|
elif self.params.grant_type in [GrantTypes.IMPLICIT, GrantTypes.HYBRID]:
|
||||||
token: Token = self.provider.create_token(
|
token = self.provider.create_refresh_token(
|
||||||
user=self.request.user, scope=self.params.scope,
|
user=self.request.user, scope=self.params.scope,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if response_type must include access_token in the response.
|
# Check if response_type must include access_token in the response.
|
||||||
if self.params.response_type in [
|
if self.params.response_type in [
|
||||||
ResponseTypes.id_token_token,
|
ResponseTypes.ID_TOKEN_TOKEN,
|
||||||
ResponseTypes.code_id_token_token,
|
ResponseTypes.CODE_ID_TOKEN_TOKEN,
|
||||||
ResponseTypes.token,
|
ResponseTypes.ID_TOKEN,
|
||||||
ResponseTypes.code_token,
|
ResponseTypes.CODE_TOKEN,
|
||||||
]:
|
]:
|
||||||
query_fragment["access_token"] = token.access_token
|
query_fragment["access_token"] = token.access_token
|
||||||
|
|
||||||
# We don't need id_token if it's an OAuth2 request.
|
# We don't need id_token if it's an OAuth2 request.
|
||||||
if SCOPE_OPENID in self.params.scope:
|
if SCOPE_OPENID in self.params.scope:
|
||||||
id_token = token.create_id_token(
|
id_token = token.create_id_token(
|
||||||
user=self.request.user,
|
user=self.request.user, request=self.request,
|
||||||
request=self.request,
|
|
||||||
scope=self.params.scope,
|
|
||||||
)
|
)
|
||||||
id_token.nonce = self.params.nonce
|
id_token.nonce = self.params.nonce
|
||||||
id_token.scope = self.params.scope
|
|
||||||
# Include at_hash when access_token is being returned.
|
# Include at_hash when access_token is being returned.
|
||||||
if "access_token" in query_fragment:
|
if "access_token" in query_fragment:
|
||||||
id_token.at_hash = token.at_hash
|
id_token.at_hash = token.at_hash
|
||||||
@ -283,8 +283,6 @@ class OAuthFulfillmentStage(StageView):
|
|||||||
]:
|
]:
|
||||||
query_fragment["id_token"] = id_token.encode(self.provider)
|
query_fragment["id_token"] = id_token.encode(self.provider)
|
||||||
token.id_token = id_token
|
token.id_token = id_token
|
||||||
else:
|
|
||||||
token.id_token = {}
|
|
||||||
|
|
||||||
# Store the token.
|
# Store the token.
|
||||||
token.save()
|
token.save()
|
||||||
@ -325,7 +323,7 @@ class AuthorizationFlowInitView(PolicyAccessMixin, View):
|
|||||||
try:
|
try:
|
||||||
application = self.provider_to_application(provider)
|
application = self.provider_to_application(provider)
|
||||||
except Application.DoesNotExist:
|
except Application.DoesNotExist:
|
||||||
return self.handle_no_permission_authorized()
|
return self.handle_no_permission_authenticated()
|
||||||
# Check if user is unauthenticated, so we pass the application
|
# Check if user is unauthenticated, so we pass the application
|
||||||
# for the identification stage
|
# for the identification stage
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
@ -333,7 +331,7 @@ class AuthorizationFlowInitView(PolicyAccessMixin, View):
|
|||||||
# Check permissions
|
# Check permissions
|
||||||
result = self.user_has_access(application)
|
result = self.user_has_access(application)
|
||||||
if not result.passing:
|
if not result.passing:
|
||||||
return self.handle_no_permission_authorized()
|
return self.handle_no_permission_authenticated(result)
|
||||||
# TODO: End block
|
# TODO: End block
|
||||||
# Extract params so we can save them in the plan context
|
# Extract params so we can save them in the plan context
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 3.1.1 on 2020-09-14 15: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_proxy", "0004_auto_20200913_1947"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="proxyprovider",
|
||||||
|
name="certificate",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="passbook_crypto.certificatekeypair",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -50,7 +50,7 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
|
|||||||
cookie_secret = models.TextField(default=get_cookie_secret)
|
cookie_secret = models.TextField(default=get_cookie_secret)
|
||||||
|
|
||||||
certificate = models.ForeignKey(
|
certificate = models.ForeignKey(
|
||||||
CertificateKeyPair, on_delete=models.SET_NULL, null=True
|
CertificateKeyPair, on_delete=models.SET_NULL, null=True, blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def form(self) -> Type[ModelForm]:
|
def form(self) -> Type[ModelForm]:
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
"""passbook saml_idp Models"""
|
"""passbook saml_idp Models"""
|
||||||
from typing import Optional, Type
|
from typing import Optional, Type
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
@ -102,6 +103,13 @@ class SAMLProvider(Provider):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def launch_url(self) -> Optional[str]:
|
||||||
|
"""Guess launch_url based on acs URL"""
|
||||||
|
launch_url = urlparse(self.acs_url)
|
||||||
|
launch_url.path = ""
|
||||||
|
return launch_url.geturl()
|
||||||
|
|
||||||
def form(self) -> Type[ModelForm]:
|
def form(self) -> Type[ModelForm]:
|
||||||
from passbook.providers.saml.forms import SAMLProviderForm
|
from passbook.providers.saml.forms import SAMLProviderForm
|
||||||
|
|
||||||
|
|||||||
@ -62,8 +62,9 @@ class SAMLSSOView(PolicyAccessMixin, View):
|
|||||||
)
|
)
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return self.handle_no_permission(self.application)
|
return self.handle_no_permission(self.application)
|
||||||
if not self.user_has_access(self.application).passing:
|
has_access = self.user_has_access(self.application)
|
||||||
return self.handle_no_permission_authorized()
|
if not has_access.passing:
|
||||||
|
return self.handle_no_permission_authenticated(has_access)
|
||||||
# Call the method handler, which checks the SAML Request
|
# Call the method handler, which checks the SAML Request
|
||||||
method_response = super().dispatch(request, *args, application_slug, **kwargs)
|
method_response = super().dispatch(request, *args, application_slug, **kwargs)
|
||||||
if method_response:
|
if method_response:
|
||||||
|
|||||||
@ -193,7 +193,12 @@ ASGI_APPLICATION = "passbook.root.routing.application"
|
|||||||
CHANNEL_LAYERS = {
|
CHANNEL_LAYERS = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
||||||
"CONFIG": {"hosts": [(CONFIG.y("redis.host"), 6379)]},
|
"CONFIG": {
|
||||||
|
"hosts": [
|
||||||
|
f"redis://:{CONFIG.y('redis.password')}@{CONFIG.y('redis.host')}:6379"
|
||||||
|
f"/{CONFIG.y('redis.ws_db')}"
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user