Compare commits
29 Commits
version/0.
...
version/0.
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b250b897e | |||
| c6880a0f16 | |||
| beb5ffcbdd | |||
| 0715cac39b | |||
| 41117d873d | |||
| 231e448b1a | |||
| b3b8cd807d | |||
| 9021bbd5de | |||
| 169475ab39 | |||
| c00e01626e | |||
| 05d4a9ef62 | |||
| 17a2ac73e7 | |||
| 6bc6f947dd | |||
| b048a1fb4f | |||
| 363940ee8d | |||
| a64e53479c | |||
| 14fdbe7720 | |||
| f56332c954 | |||
| 21c53c748f | |||
| b12182c1d1 | |||
| d8f27f595a | |||
| b25dc2aaa3 | |||
| 3ec3849e72 | |||
| 2dc1b65718 | |||
| af22f507f4 | |||
| 9958019bf3 | |||
| 02d65972cb | |||
| 24ad893350 | |||
| 9c5792b1e1 |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.9.0-pre5
|
current_version = 0.9.0-pre6
|
||||||
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>.*)
|
||||||
|
|||||||
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@ -16,11 +16,11 @@ jobs:
|
|||||||
- name: Building Docker Image
|
- name: Building Docker Image
|
||||||
run: docker build
|
run: docker build
|
||||||
--no-cache
|
--no-cache
|
||||||
-t beryju/passbook:0.9.0-pre5
|
-t beryju/passbook:0.9.0-pre6
|
||||||
-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.9.0-pre5
|
run: docker push beryju/passbook:0.9.0-pre6
|
||||||
- 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-gatekeeper:
|
build-gatekeeper:
|
||||||
@ -37,11 +37,11 @@ jobs:
|
|||||||
cd gatekeeper
|
cd gatekeeper
|
||||||
docker build \
|
docker build \
|
||||||
--no-cache \
|
--no-cache \
|
||||||
-t beryju/passbook-gatekeeper:0.9.0-pre5 \
|
-t beryju/passbook-gatekeeper:0.9.0-pre6 \
|
||||||
-t beryju/passbook-gatekeeper:latest \
|
-t beryju/passbook-gatekeeper:latest \
|
||||||
-f Dockerfile .
|
-f Dockerfile .
|
||||||
- name: Push Docker Container to Registry (versioned)
|
- name: Push Docker Container to Registry (versioned)
|
||||||
run: docker push beryju/passbook-gatekeeper:0.9.0-pre5
|
run: docker push beryju/passbook-gatekeeper:0.9.0-pre6
|
||||||
- name: Push Docker Container to Registry (latest)
|
- name: Push Docker Container to Registry (latest)
|
||||||
run: docker push beryju/passbook-gatekeeper:latest
|
run: docker push beryju/passbook-gatekeeper:latest
|
||||||
build-static:
|
build-static:
|
||||||
@ -66,11 +66,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.9.0-pre5
|
-t beryju/passbook-static:0.9.0-pre6
|
||||||
-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.9.0-pre5
|
run: docker push beryju/passbook-static:0.9.0-pre6
|
||||||
- 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:
|
||||||
@ -86,3 +86,19 @@ jobs:
|
|||||||
docker-compose up --no-start
|
docker-compose up --no-start
|
||||||
docker-compose start postgresql redis
|
docker-compose start postgresql redis
|
||||||
docker-compose run -u root server bash -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test"
|
docker-compose run -u root server bash -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test"
|
||||||
|
sentry-release:
|
||||||
|
needs:
|
||||||
|
- test-release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Create a Sentry.io release
|
||||||
|
uses: tclindner/sentry-releases-action@v1.2.0
|
||||||
|
env:
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
SENTRY_ORG: beryjuorg
|
||||||
|
SENTRY_PROJECT: passbook
|
||||||
|
SENTRY_URL: https://sentry.beryju.org
|
||||||
|
with:
|
||||||
|
tagName: 0.9.0-pre6
|
||||||
|
environment: production
|
||||||
|
|||||||
1
Pipfile
1
Pipfile
@ -41,6 +41,7 @@ structlog = "*"
|
|||||||
swagger-spec-validator = "*"
|
swagger-spec-validator = "*"
|
||||||
urllib3 = {extras = ["secure"],version = "*"}
|
urllib3 = {extras = ["secure"],version = "*"}
|
||||||
facebook-sdk = "*"
|
facebook-sdk = "*"
|
||||||
|
elastic-apm = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.8"
|
python_version = "3.8"
|
||||||
|
|||||||
113
Pipfile.lock
generated
113
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "fd0192b73c01aaffb90716ce7b6d4e5be9adb8788d3ebd58e54ccd6f85d9b71b"
|
"sha256": "f90d79a67bbd689ca4e7ccbfd528e4ed45078e848c36d84e53ff9c6b2a1e92ed"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -46,18 +46,18 @@
|
|||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:c2a223f4b48782e8b160b2130265e2a66081df111f630a5a384d6909e29a5aa9",
|
"sha256:ae57df1fbad7e29954a160d77cbf650d6562eb0d304c1206afa71d914e771a66",
|
||||||
"sha256:ce5a4ab6af9e993d1864209cbbb6f4812f65fbc57ad6b95e5967d8bf38b1dcfb"
|
"sha256:cbe618d61cb8f75cd9495ea36e69bad7c8984eb11f02ad247be4c9a2eb7eb647"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.14.16"
|
"version": "==1.14.17"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:99d995ef99cf77458a661f3fc64e0c3a4ce77ca30facfdf0472f44b2953dd856",
|
"sha256:5528c04c360019c24f2706ce82872c9ab767a8c581beffdfdaf006cce7499cac",
|
||||||
"sha256:fe0c4f7cd6b67eff3b7cb8dff6709a65d6fca10b7b7449a493b2036915e98b4c"
|
"sha256:d65b5574dad8c221344496352245828d9ffecaa0868199eb04ccd2eb2ff09133"
|
||||||
],
|
],
|
||||||
"version": "==1.17.16"
|
"version": "==1.17.17"
|
||||||
},
|
},
|
||||||
"celery": {
|
"celery": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -306,6 +306,38 @@
|
|||||||
],
|
],
|
||||||
"version": "==1.0.0"
|
"version": "==1.0.0"
|
||||||
},
|
},
|
||||||
|
"elastic-apm": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0ffd86d8449d7b63c6053c5032e09abf398c753214a55de8a4e15cb9b56108d1",
|
||||||
|
"sha256:18006ade25a91a8030b5fcfa825d5c364b13bc5e902b818725341f8c9a00895a",
|
||||||
|
"sha256:1d8335f94660c246d5475ec3b15452cd0f5b51affb2e1d16eb2fbc36380308a8",
|
||||||
|
"sha256:25fea9cb6c99efc229b1449d7fbdda76260404cc74abefcc0cc86b3a5102d99d",
|
||||||
|
"sha256:2841bee5650b736d5ebb199d728a18b415dfed22cb367cd913619a691dfe39e8",
|
||||||
|
"sha256:301d159933f19115b21f92bc1ff7f0073bfea13ca24c6ff34023c23077a08e44",
|
||||||
|
"sha256:4eaaebd088315d7ba2726b21fea06279598cad128be073b28c0462049f093d5a",
|
||||||
|
"sha256:515d027d380df818ec304d4d28121c39069a0d919cb2eb7f8e29019a14d62c2a",
|
||||||
|
"sha256:5ad2b431298567f642d44826be2c557d9aca5761c0240be23f9a52b66833cc93",
|
||||||
|
"sha256:5dbf19570bdf97e169b5901913e9a3e271ff5e10d298a608e214802dca8c9065",
|
||||||
|
"sha256:724ded78cc24d2c7d8bc81642a938d9bfc2dcb8b5bdad1b1da242300f9f4ec73",
|
||||||
|
"sha256:7e859162f4c187defe26fb00c974a128eee2bd8988cd30ccffdcaaeb56cb2248",
|
||||||
|
"sha256:9180ed12b9c12cc794f3b57069d1e7b2a04352af02f8d0bc89c9251231f8660e",
|
||||||
|
"sha256:92f885cc67a9d78e72b174feaa979ddb5188d7cef2b5a7739be740955e07c5ed",
|
||||||
|
"sha256:976eaaf3825df760946f31b5426544fecc4c32fd66e124565ede7151f8152689",
|
||||||
|
"sha256:abafeff08ff285cc03c33e822633c6e25a9434174413f72a5032393e9f95a1e0",
|
||||||
|
"sha256:ad21169ebee7ae35d6c42cd6ac9e7658d6e07bc6a3f34dcc4f0a32e03d736fdc",
|
||||||
|
"sha256:b2b4ff079a20d620d7f87a345d37cf9b7f2bc1c8cc8c9317fb0c3979371f0d41",
|
||||||
|
"sha256:b3b72d26104de89124cca965b234b6b67be4604518e168aedcd52c7229c923e9",
|
||||||
|
"sha256:c4a144ecb0b1570c1f6a285cd6f28f2eda89c0696ed494892e3250bb6fed7909",
|
||||||
|
"sha256:dc368bbac6401fa0c9d7a35429257190759f4f33099783d9e0557ce12d64ca6c",
|
||||||
|
"sha256:e28a81802784ea80d21c294a4ab4e47f658a4031caa5c320147925ab62c6a0d4",
|
||||||
|
"sha256:e7832a5ad503d6cd4a7eaa4cee782ccdf113afa99708e3d005fe9aef539a8222",
|
||||||
|
"sha256:f2674a3aee0c38df82dedade353c944a2f55b215c7d5b0776e1bb89ce87de57c",
|
||||||
|
"sha256:f7b37f65c0ca971038f6b69c7581ea762fbc89d9631107babc04c646898686d2",
|
||||||
|
"sha256:fbd1a68b4cf32298e09652958ec3cf13462a5269408522211cfd3e02b451c3b2"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==5.8.0"
|
||||||
|
},
|
||||||
"facebook-sdk": {
|
"facebook-sdk": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2e987b3e0f466a6f4ee77b935eb023dba1384134f004a2af21f1cfff7fe0806e",
|
"sha256:2e987b3e0f466a6f4ee77b935eb023dba1384134f004a2af21f1cfff7fe0806e",
|
||||||
@ -945,40 +977,43 @@
|
|||||||
},
|
},
|
||||||
"coverage": {
|
"coverage": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a",
|
"sha256:0fc4e0d91350d6f43ef6a61f64a48e917637e1dcfcba4b4b7d543c628ef82c2d",
|
||||||
"sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355",
|
"sha256:10f2a618a6e75adf64329f828a6a5b40244c1c50f5ef4ce4109e904e69c71bd2",
|
||||||
"sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65",
|
"sha256:12eaccd86d9a373aea59869bc9cfa0ab6ba8b1477752110cb4c10d165474f703",
|
||||||
"sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7",
|
"sha256:1874bdc943654ba46d28f179c1846f5710eda3aeb265ff029e0ac2b52daae404",
|
||||||
"sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9",
|
"sha256:1dcebae667b73fd4aa69237e6afb39abc2f27520f2358590c1b13dd90e32abe7",
|
||||||
"sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1",
|
"sha256:1e58fca3d9ec1a423f1b7f2aa34af4f733cbfa9020c8fe39ca451b6071237405",
|
||||||
"sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0",
|
"sha256:214eb2110217f2636a9329bc766507ab71a3a06a8ea30cdeebb47c24dce5972d",
|
||||||
"sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55",
|
"sha256:25fe74b5b2f1b4abb11e103bb7984daca8f8292683957d0738cd692f6a7cc64c",
|
||||||
"sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c",
|
"sha256:32ecee61a43be509b91a526819717d5e5650e009a8d5eda8631a59c721d5f3b6",
|
||||||
"sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6",
|
"sha256:3740b796015b889e46c260ff18b84683fa2e30f0f75a171fb10d2bf9fb91fc70",
|
||||||
"sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef",
|
"sha256:3b2c34690f613525672697910894b60d15800ac7e779fbd0fccf532486c1ba40",
|
||||||
"sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019",
|
"sha256:41d88736c42f4a22c494c32cc48a05828236e37c991bd9760f8923415e3169e4",
|
||||||
"sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e",
|
"sha256:42fa45a29f1059eda4d3c7b509589cc0343cd6bbf083d6118216830cd1a51613",
|
||||||
"sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0",
|
"sha256:4bb385a747e6ae8a65290b3df60d6c8a692a5599dc66c9fa3520e667886f2e10",
|
||||||
"sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf",
|
"sha256:509294f3e76d3f26b35083973fbc952e01e1727656d979b11182f273f08aa80b",
|
||||||
"sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24",
|
"sha256:5c74c5b6045969b07c9fb36b665c9cac84d6c174a809fc1b21bdc06c7836d9a0",
|
||||||
"sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2",
|
"sha256:60a3d36297b65c7f78329b80120f72947140f45b5c7a017ea730f9112b40f2ec",
|
||||||
"sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c",
|
"sha256:6f91b4492c5cde83bfe462f5b2b997cdf96a138f7c58b1140f05de5751623cf1",
|
||||||
"sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4",
|
"sha256:7403675df5e27745571aba1c957c7da2dacb537c21e14007ec3a417bf31f7f3d",
|
||||||
"sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0",
|
"sha256:87bdc8135b8ee739840eee19b184804e5d57f518578ffc797f5afa2c3c297913",
|
||||||
"sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd",
|
"sha256:8a3decd12e7934d0254939e2bf434bf04a5890c5bf91a982685021786a08087e",
|
||||||
"sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04",
|
"sha256:9702e2cb1c6dec01fb8e1a64c015817c0800a6eca287552c47a5ee0ebddccf62",
|
||||||
"sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e",
|
"sha256:a4d511012beb967a39580ba7d2549edf1e6865a33e5fe51e4dce550522b3ac0e",
|
||||||
"sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730",
|
"sha256:bbb387811f7a18bdc61a2ea3d102be0c7e239b0db9c83be7bfa50f095db5b92a",
|
||||||
"sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2",
|
"sha256:bfcc811883699ed49afc58b1ed9f80428a18eb9166422bce3c31a53dba00fd1d",
|
||||||
"sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768",
|
"sha256:c32aa13cc3fe86b0f744dfe35a7f879ee33ac0a560684fef0f3e1580352b818f",
|
||||||
"sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796",
|
"sha256:ca63dae130a2e788f2b249200f01d7fa240f24da0596501d387a50e57aa7075e",
|
||||||
"sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7",
|
"sha256:d54d7ea74cc00482a2410d63bf10aa34ebe1c49ac50779652106c867f9986d6b",
|
||||||
"sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a",
|
"sha256:d67599521dff98ec8c34cd9652cbcfe16ed076a2209625fca9dc7419b6370e5c",
|
||||||
"sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489",
|
"sha256:d82db1b9a92cb5c67661ca6616bdca6ff931deceebb98eecbd328812dab52032",
|
||||||
"sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"
|
"sha256:d9ad0a988ae20face62520785ec3595a5e64f35a21762a57d115dae0b8fb894a",
|
||||||
|
"sha256:ebf2431b2d457ae5217f3a1179533c456f3272ded16f8ed0b32961a6d90e38ee",
|
||||||
|
"sha256:ed9a21502e9223f563e071759f769c3d6a2e1ba5328c31e86830368e8d78bc9c",
|
||||||
|
"sha256:f50632ef2d749f541ca8e6c07c9928a37f87505ce3a9f20c8446ad310f1aa87b"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==5.1"
|
"version": "==5.2"
|
||||||
},
|
},
|
||||||
"cryptography": {
|
"cryptography": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|||||||
@ -194,15 +194,13 @@ stages:
|
|||||||
pool:
|
pool:
|
||||||
vmImage: 'ubuntu-latest'
|
vmImage: 'ubuntu-latest'
|
||||||
steps:
|
steps:
|
||||||
- task: CmdLine@2
|
|
||||||
inputs:
|
|
||||||
script: cd gatekeeper
|
|
||||||
- task: Docker@2
|
- task: Docker@2
|
||||||
inputs:
|
inputs:
|
||||||
containerRegistry: 'dockerhub'
|
containerRegistry: 'dockerhub'
|
||||||
repository: 'beryju/passbook-gatekeeper'
|
repository: 'beryju/passbook-gatekeeper'
|
||||||
command: 'buildAndPush'
|
command: 'buildAndPush'
|
||||||
Dockerfile: 'Dockerfile'
|
Dockerfile: 'gatekeeper/Dockerfile'
|
||||||
|
buildContext: 'gatekeeper/'
|
||||||
tags: 'gh-$(Build.SourceBranchName)'
|
tags: 'gh-$(Build.SourceBranchName)'
|
||||||
- job: build_static
|
- job: build_static
|
||||||
pool:
|
pool:
|
||||||
|
|||||||
@ -22,6 +22,11 @@ config:
|
|||||||
# Log level used by web and worker
|
# Log level used by web and worker
|
||||||
# Can be either debug, info, warning, error
|
# Can be either debug, info, warning, error
|
||||||
log_level: warning
|
log_level: warning
|
||||||
|
# Optionally enable Elastic APM Support
|
||||||
|
apm:
|
||||||
|
enabled: false
|
||||||
|
server_url: ""
|
||||||
|
secret_token: ""
|
||||||
|
|
||||||
# This Helm chart ships with built-in Prometheus ServiceMonitors and Rules.
|
# This Helm chart ships with built-in Prometheus ServiceMonitors and Rules.
|
||||||
# This requires the CoreOS Prometheus Operator.
|
# This requires the CoreOS Prometheus Operator.
|
||||||
|
|||||||
@ -6,13 +6,13 @@ To export data from your old instance, run this command:
|
|||||||
|
|
||||||
- docker-compose
|
- docker-compose
|
||||||
```
|
```
|
||||||
docker-compose exec server ./manage.py dumpdata -o /tmp/passbook_dump.json passbook_core.User passbook_core.Group passbook_crypto.CertificateKeyPair passbook_audit.Event
|
docker-compose exec server ./manage.py dumpdata -o /tmp/passbook_dump.json passbook_core.User passbook_core.Group passbook_crypto.CertificateKeyPair passbook_audit.Event otp_totp.totpdevice otp_static.staticdevice otp_static.statictoken
|
||||||
docker cp passbook_server_1:/tmp/passbook_dump.json passbook_dump.json
|
docker cp passbook_server_1:/tmp/passbook_dump.json passbook_dump.json
|
||||||
```
|
```
|
||||||
|
|
||||||
- kubernetes
|
- kubernetes
|
||||||
```
|
```
|
||||||
kubectl exec -it passbook-web-... -- ./manage.py dumpdata -o /tmp/passbook_dump.json passbook_core.User passbook_core.Group passbook_crypto.CertificateKeyPair passbook_audit.Event
|
kubectl exec -it passbook-web-... -- ./manage.py dumpdata -o /tmp/passbook_dump.json passbook_core.User passbook_core.Group passbook_crypto.CertificateKeyPair passbook_audit.Event otp_totp.totpdevice otp_static.staticdevice otp_static.statictoken
|
||||||
kubectl cp passbook-web-...:/tmp/passbook_dump.json passbook_dump.json
|
kubectl cp passbook-web-...:/tmp/passbook_dump.json passbook_dump.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
appVersion: "0.9.0-pre5"
|
appVersion: "0.9.0-pre6"
|
||||||
description: A Helm chart for passbook.
|
description: A Helm chart for passbook.
|
||||||
name: passbook
|
name: passbook
|
||||||
version: "0.9.0-pre5"
|
version: "0.9.0-pre6"
|
||||||
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png
|
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png
|
||||||
|
|||||||
@ -21,3 +21,7 @@ data:
|
|||||||
message_queue_db: 1
|
message_queue_db: 1
|
||||||
error_reporting: {{ .Values.config.error_reporting }}
|
error_reporting: {{ .Values.config.error_reporting }}
|
||||||
log_level: "{{ .Values.config.log_level }}"
|
log_level: "{{ .Values.config.log_level }}"
|
||||||
|
apm:
|
||||||
|
enabled: {{ .Values.config.apm.enabled }}
|
||||||
|
server_url: "{{ .Values.config.apm.server_url }}"
|
||||||
|
secret_token: "{{ .Values.config.apm.server_token }}"
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
# This is a YAML-formatted file.
|
# This is a YAML-formatted file.
|
||||||
# Declare variables to be passed into your templates.
|
# Declare variables to be passed into your templates.
|
||||||
image:
|
image:
|
||||||
tag: 0.9.0-pre5
|
tag: 0.9.0-pre6
|
||||||
|
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
|
|
||||||
@ -14,6 +14,11 @@ config:
|
|||||||
# Log level used by web and worker
|
# Log level used by web and worker
|
||||||
# Can be either debug, info, warning, error
|
# Can be either debug, info, warning, error
|
||||||
log_level: warning
|
log_level: warning
|
||||||
|
# Optionally enable Elastic APM Support
|
||||||
|
apm:
|
||||||
|
enabled: false
|
||||||
|
server_url: ""
|
||||||
|
secret_token: ""
|
||||||
|
|
||||||
# This Helm chart ships with built-in Prometheus ServiceMonitors and Rules.
|
# This Helm chart ships with built-in Prometheus ServiceMonitors and Rules.
|
||||||
# This requires the CoreOS Prometheus Operator.
|
# This requires the CoreOS Prometheus Operator.
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
"""passbook"""
|
"""passbook"""
|
||||||
__version__ = "0.9.0-pre5"
|
__version__ = "0.9.0-pre6"
|
||||||
|
|||||||
@ -10,29 +10,33 @@
|
|||||||
</section>
|
</section>
|
||||||
<section class="pf-c-page__main-section">
|
<section class="pf-c-page__main-section">
|
||||||
<div class="pf-l-gallery pf-m-gutter">
|
<div class="pf-l-gallery pf-m-gutter">
|
||||||
<a href="{% url 'passbook_admin:applications' %}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
<a href="{% url 'passbook_admin:applications' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-applications"></i> {% trans 'Applications' %}
|
<i class="pf-icon pf-icon-applications"></i> {% trans 'Applications' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<i class="pf-icon pf-icon-ok"></i> {{ application_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-check-circle"></i> {{ application_count }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'passbook_admin:sources' %}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
<a href="{% url 'passbook_admin:sources' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-middleware"></i> {% trans 'Sources' %}
|
<i class="pf-icon pf-icon-middleware"></i> {% trans 'Sources' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<i class="pf-icon pf-icon-ok"></i> {{ source_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-check-circle"></i> {{ source_count }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'passbook_admin:providers' %}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
<a href="{% url 'passbook_admin:providers' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-plugged"></i> {% trans 'Providers' %}
|
<i class="pf-icon pf-icon-plugged"></i> {% trans 'Providers' %}
|
||||||
@ -40,15 +44,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
{% if providers_without_application.exists %}
|
{% if providers_without_application.exists %}
|
||||||
<i class="pf-icon pf-icon-warning-triangle"></i> {{ provider_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-exclamation-triangle"></i> {{ provider_count }}
|
||||||
|
</p>
|
||||||
<p>{% trans 'Warning: At least one Provider has no application assigned.' %}</p>
|
<p>{% trans 'Warning: At least one Provider has no application assigned.' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="pf-icon pf-icon-ok"></i> {{ provider_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-check-circle"></i> {{ provider_count }}
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'passbook_admin:stages' %}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
<a href="{% url 'passbook_admin:stages' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-plugged"></i> {% trans 'Stages' %}
|
<i class="pf-icon pf-icon-plugged"></i> {% trans 'Stages' %}
|
||||||
@ -56,26 +64,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
{% if stage_count < 1 %}
|
{% if stage_count < 1 %}
|
||||||
|
<p class="aggregate-status">
|
||||||
<i class="pficon-error-circle-o"></i> {{ stage_count }}
|
<i class="pficon-error-circle-o"></i> {{ stage_count }}
|
||||||
|
</p>
|
||||||
<p>{% trans 'No Stages configured. No Users will be able to login.' %}"></p>
|
<p>{% trans 'No Stages configured. No Users will be able to login.' %}"></p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="pf-icon pf-icon-ok"></i> {{ stage_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-check-circle"></i> {{ stage_count }}
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'passbook_admin:stages' %}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
<a href="{% url 'passbook_admin:stages' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-topology"></i> {% trans 'Flows' %}
|
<i class="pf-icon pf-icon-topology"></i> {% trans 'Flows' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<i class="pf-icon pf-icon-ok"></i> {{ flow_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-check-circle"></i> {{ flow_count }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'passbook_admin:policies' %}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
<a href="{% url 'passbook_admin:policies' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-infrastructure"></i> {% trans 'Policies' %}
|
<i class="pf-icon pf-icon-infrastructure"></i> {% trans 'Policies' %}
|
||||||
@ -83,58 +97,71 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
{% if policies_without_binding %}
|
{% if policies_without_binding %}
|
||||||
<i class="pf-icon pf-icon-warning-triangle"></i> {{ policy_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-exclamation-triangle"></i> {{ policy_count }}
|
||||||
|
</p>
|
||||||
<p>{% trans 'Policies without binding exist.' %}</p>
|
<p>{% trans 'Policies without binding exist.' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="pf-icon pf-icon-ok"></i> {{ policy_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-check-circle"></i> {{ policy_count }}
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'passbook_admin:stage-invitations' %}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
<a href="{% url 'passbook_admin:stage-invitations' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-migration"></i> {% trans 'Invitation' %}
|
<i class="pf-icon pf-icon-migration"></i> {% trans 'Invitation' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<i class="pf-icon pf-icon-ok"></i> {{ invitation_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-check-circle"></i> {{ invitation_count }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'passbook_admin:users' %}" class="pf-c-card pf-m-hoverable pf-m-compact">
|
<a href="{% url 'passbook_admin:users' %}" class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-user"></i> {% trans 'Users' %}
|
<i class="pf-icon pf-icon-user"></i> {% trans 'Users' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
<i class="pf-icon pf-icon-ok"></i> {{ user_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-check-circle"></i> {{ user_count }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="pf-c-card pf-m-hoverable pf-m-compact">
|
<div class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-bundle"></i> {% trans 'Version' %}
|
<i class="pf-icon pf-icon-bundle"></i> {% trans 'Version' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
|
<p class="aggregate-status">
|
||||||
{% if version >= version_latest %}
|
{% if version >= version_latest %}
|
||||||
<i class="pf-icon pf-icon-ok"></i>
|
<i class="fa fa-check-circle"></i> {{ version }}
|
||||||
{% blocktrans with version=version %}
|
{% else %}
|
||||||
{{ version }} (Up-to-date!)
|
<i class="fa fa-exclamation-triangle"></i> {{ version }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
{% if version >= version_latest %}
|
||||||
|
{% blocktrans %}
|
||||||
|
Up-to-date!
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="pf-icon pf-icon-warning-triangle"></i>
|
{% blocktrans with latest=version_latest %}
|
||||||
{% blocktrans with version=version latest=version_latest %}
|
{{ latest }} is available!
|
||||||
{{ version }} ({{ latest }} is available!)
|
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pf-c-card pf-m-hoverable pf-m-compact">
|
<div class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-server"></i> {% trans 'Workers' %}
|
<i class="pf-icon pf-icon-server"></i> {% trans 'Workers' %}
|
||||||
@ -142,15 +169,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
{% if worker_count < 1 %}
|
{% if worker_count < 1 %}
|
||||||
<i class="pf-icon pf-icon-warning-triangle"></i> {{ worker_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-exclamation-triangle"></i> {{ worker_count }}
|
||||||
|
</p>
|
||||||
<p>{% trans 'No workers connected.' %}</p>
|
<p>{% trans 'No workers connected.' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="pf-icon pf-icon-ok"></i> {{ worker_count }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-check-circle"></i> {{ worker_count }}
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class="pf-c-card pf-m-hoverable pf-m-compact" data-target="modal" data-modal="clearCacheModalRoot">
|
<a class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact" data-target="modal" data-modal="clearCacheModalRoot">
|
||||||
<div class="pf-c-card__header">
|
<div class="pf-c-card__header">
|
||||||
<div class="pf-c-card__header-main">
|
<div class="pf-c-card__header-main">
|
||||||
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Policies' %}
|
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Policies' %}
|
||||||
@ -158,13 +189,37 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
{% if cached_policies < 1 %}
|
{% if cached_policies < 1 %}
|
||||||
<i class="pf-icon pf-icon-warning-triangle"></i> {{ cached_policies }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-exclamation-triangle"></i> {{ cached_policies }}
|
||||||
|
</p>
|
||||||
<p>{% trans 'No policies cached. Users may experience slow response times.' %}</p>
|
<p>{% trans 'No policies cached. Users may experience slow response times.' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="pf-icon pf-icon-ok"></i> {{ cached_policies }}
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-check-circle"></i> {{ cached_policies }}
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<div class="pf-c-card pf-c-card-aggregate pf-m-hoverable pf-m-compact">
|
||||||
|
<div class="pf-c-card__header">
|
||||||
|
<div class="pf-c-card__header-main">
|
||||||
|
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Flows' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pf-c-card__body">
|
||||||
|
{% if cached_flows < 1 %}
|
||||||
|
<p class="aggregate-status">
|
||||||
|
<span class="fa fa-exclamation-triangle"></span> {{ cached_flows }}
|
||||||
|
</p>
|
||||||
|
<p>{% trans 'No flows cached.' %}</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="aggregate-status">
|
||||||
|
<i class="fa fa-check-circle"></i> {{ cached_flows }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-backdrop" id="clearCacheModalRoot" hidden>
|
<div class="pf-c-backdrop" id="clearCacheModalRoot" hidden>
|
||||||
@ -173,7 +228,9 @@
|
|||||||
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
||||||
<i class="fas fa-times" aria-hidden="true"></i>
|
<i class="fas fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="pf-c-modal-box__header">
|
||||||
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Cache' %}?</h1>
|
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Cache' %}?</h1>
|
||||||
|
</div>
|
||||||
<div class="pf-c-modal-box__body" id="modal-description">
|
<div class="pf-c-modal-box__body" id="modal-description">
|
||||||
<form method="post" id="clearForm">
|
<form method="post" id="clearForm">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@ -69,12 +69,11 @@
|
|||||||
<div class="pf-c-empty-state__content">
|
<div class="pf-c-empty-state__content">
|
||||||
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
|
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
|
||||||
<h1 class="pf-c-title pf-m-lg">
|
<h1 class="pf-c-title pf-m-lg">
|
||||||
{% trans 'No Applications.' %}
|
{% trans 'No Tokens.' %}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="pf-c-empty-state__body">
|
<div class="pf-c-empty-state__body">
|
||||||
{% trans 'Currently no applications exist. Click the button below to create one.' %}
|
{% trans 'Currently no tokens exist.' %}
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'passbook_admin:application-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block action %}
|
{% block action %}
|
||||||
{% blocktrans with type=form|form_verbose_name|title %}
|
{% blocktrans with type=form|form_verbose_name %}
|
||||||
Update {{ type }}
|
Update {{ type }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
"""passbook administration overview"""
|
"""passbook administration overview"""
|
||||||
from functools import lru_cache
|
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.shortcuts import redirect, reverse
|
from django.shortcuts import redirect, reverse
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
@ -15,18 +13,21 @@ from passbook.policies.models import Policy
|
|||||||
from passbook.root.celery import CELERY_APP
|
from passbook.root.celery import CELERY_APP
|
||||||
from passbook.stages.invitation.models import Invitation
|
from passbook.stages.invitation.models import Invitation
|
||||||
|
|
||||||
|
VERSION_CACHE_KEY = "passbook_latest_version"
|
||||||
|
|
||||||
|
|
||||||
@lru_cache
|
|
||||||
def latest_version() -> Version:
|
def latest_version() -> Version:
|
||||||
"""Get latest release from GitHub, cached"""
|
"""Get latest release from GitHub, cached"""
|
||||||
|
if not cache.get(VERSION_CACHE_KEY):
|
||||||
try:
|
try:
|
||||||
data = get(
|
data = get(
|
||||||
"https://api.github.com/repos/beryju/passbook/releases/latest"
|
"https://api.github.com/repos/beryju/passbook/releases/latest"
|
||||||
).json()
|
).json()
|
||||||
tag_name = data.get("tag_name")
|
tag_name = data.get("tag_name")
|
||||||
return parse(tag_name.split("/")[1])
|
cache.set(VERSION_CACHE_KEY, tag_name.split("/")[1], 30)
|
||||||
except RequestException:
|
except (RequestException, IndexError):
|
||||||
return parse("0.0.0")
|
cache.set(VERSION_CACHE_KEY, "0.0.0", 30)
|
||||||
|
return parse(cache.get(VERSION_CACHE_KEY))
|
||||||
|
|
||||||
|
|
||||||
class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
||||||
@ -60,4 +61,5 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
|||||||
Policy.objects.filter(bindings__isnull=True)
|
Policy.objects.filter(bindings__isnull=True)
|
||||||
)
|
)
|
||||||
kwargs["cached_policies"] = len(cache.keys("policy_*"))
|
kwargs["cached_policies"] = len(cache.keys("policy_*"))
|
||||||
|
kwargs["cached_flows"] = len(cache.keys("flow_*"))
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|||||||
@ -11,10 +11,10 @@ from django.views.generic import ListView
|
|||||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||||
|
|
||||||
from passbook.admin.views.utils import DeleteMessageView
|
from passbook.admin.views.utils import DeleteMessageView
|
||||||
from passbook.core.signals import invitation_created
|
|
||||||
from passbook.lib.views import CreateAssignPermView
|
from passbook.lib.views import CreateAssignPermView
|
||||||
from passbook.stages.invitation.forms import InvitationForm
|
from passbook.stages.invitation.forms import InvitationForm
|
||||||
from passbook.stages.invitation.models import Invitation
|
from passbook.stages.invitation.models import Invitation
|
||||||
|
from passbook.stages.invitation.signals import invitation_created
|
||||||
|
|
||||||
|
|
||||||
class InvitationListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
class InvitationListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
"""passbook audit signal listener"""
|
"""passbook audit signal listener"""
|
||||||
from typing import Dict
|
from threading import Thread
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from django.contrib.auth.signals import (
|
from django.contrib.auth.signals import (
|
||||||
user_logged_in,
|
user_logged_in,
|
||||||
@ -11,21 +12,54 @@ from django.http import HttpRequest
|
|||||||
|
|
||||||
from passbook.audit.models import Event, EventAction
|
from passbook.audit.models import Event, EventAction
|
||||||
from passbook.core.models import User
|
from passbook.core.models import User
|
||||||
from passbook.core.signals import invitation_created, invitation_used, user_signed_up
|
from passbook.stages.invitation.models import Invitation
|
||||||
|
from passbook.stages.invitation.signals import invitation_created, invitation_used
|
||||||
|
from passbook.stages.user_write.signals import user_write
|
||||||
|
|
||||||
|
|
||||||
|
class EventNewThread(Thread):
|
||||||
|
"""Create Event in background thread"""
|
||||||
|
|
||||||
|
action: EventAction
|
||||||
|
request: HttpRequest
|
||||||
|
kwargs: Dict[str, Any]
|
||||||
|
user: Optional[User] = None
|
||||||
|
|
||||||
|
def __init__(self, action: EventAction, request: HttpRequest, **kwargs):
|
||||||
|
super().__init__()
|
||||||
|
self.action = action
|
||||||
|
self.request = request
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
Event.new(self.action, **self.kwargs).from_http(self.request, user=self.user)
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_logged_in)
|
@receiver(user_logged_in)
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def on_user_logged_in(sender, request: HttpRequest, user: User, **_):
|
def on_user_logged_in(sender, request: HttpRequest, user: User, **_):
|
||||||
"""Log successful login"""
|
"""Log successful login"""
|
||||||
Event.new(EventAction.LOGIN).from_http(request)
|
thread = EventNewThread(EventAction.LOGIN, request)
|
||||||
|
thread.user = user
|
||||||
|
thread.run()
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_logged_out)
|
@receiver(user_logged_out)
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def on_user_logged_out(sender, request: HttpRequest, user: User, **_):
|
def on_user_logged_out(sender, request: HttpRequest, user: User, **_):
|
||||||
"""Log successfully logout"""
|
"""Log successfully logout"""
|
||||||
Event.new(EventAction.LOGOUT).from_http(request)
|
thread = EventNewThread(EventAction.LOGOUT, request)
|
||||||
|
thread.user = user
|
||||||
|
thread.run()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(user_write)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def on_user_write(sender, request: HttpRequest, user: User, data: Dict[str, Any], **_):
|
||||||
|
"""Log User write"""
|
||||||
|
thread = EventNewThread(EventAction.CUSTOM, request, **data)
|
||||||
|
thread.user = user
|
||||||
|
thread.run()
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_login_failed)
|
@receiver(user_login_failed)
|
||||||
@ -34,29 +68,25 @@ def on_user_login_failed(
|
|||||||
sender, credentials: Dict[str, str], request: HttpRequest, **_
|
sender, credentials: Dict[str, str], request: HttpRequest, **_
|
||||||
):
|
):
|
||||||
"""Failed Login"""
|
"""Failed Login"""
|
||||||
Event.new(EventAction.LOGIN_FAILED, **credentials).from_http(request)
|
thread = EventNewThread(EventAction.LOGIN_FAILED, request, **credentials)
|
||||||
|
thread.run()
|
||||||
|
|
||||||
@receiver(user_signed_up)
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def on_user_signed_up(sender, request: HttpRequest, user: User, **_):
|
|
||||||
"""Log successfully signed up"""
|
|
||||||
Event.new(EventAction.SIGN_UP).from_http(request)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(invitation_created)
|
@receiver(invitation_created)
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def on_invitation_created(sender, request: HttpRequest, invitation, **_):
|
def on_invitation_created(sender, request: HttpRequest, invitation: Invitation, **_):
|
||||||
"""Log Invitation creation"""
|
"""Log Invitation creation"""
|
||||||
Event.new(
|
thread = EventNewThread(
|
||||||
EventAction.INVITE_CREATED, invitation_uuid=invitation.uuid.hex
|
EventAction.INVITE_CREATED, request, invitation_uuid=invitation.invite_uuid.hex
|
||||||
).from_http(request)
|
)
|
||||||
|
thread.run()
|
||||||
|
|
||||||
|
|
||||||
@receiver(invitation_used)
|
@receiver(invitation_used)
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def on_invitation_used(sender, request: HttpRequest, invitation, **_):
|
def on_invitation_used(sender, request: HttpRequest, invitation: Invitation, **_):
|
||||||
"""Log Invitation usage"""
|
"""Log Invitation usage"""
|
||||||
Event.new(EventAction.INVITE_USED, invitation_uuid=invitation.uuid.hex).from_http(
|
thread = EventNewThread(
|
||||||
request
|
EventAction.INVITE_USED, request, invitation_uuid=invitation.invite_uuid.hex
|
||||||
)
|
)
|
||||||
|
thread.run()
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
"""passbook core signals"""
|
"""passbook core signals"""
|
||||||
from django.core.signals import Signal
|
from django.core.signals import Signal
|
||||||
|
|
||||||
user_signed_up = Signal(providing_args=["request", "user"])
|
|
||||||
invitation_created = Signal(providing_args=["request", "invitation"])
|
|
||||||
invitation_used = Signal(providing_args=["request", "invitation", "user"])
|
|
||||||
password_changed = Signal(providing_args=["user", "password"])
|
password_changed = Signal(providing_args=["user", "password"])
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
<ul class="pf-c-nav__list">
|
<ul class="pf-c-nav__list">
|
||||||
{% for source in user_sources_loc %}
|
{% for source in user_sources_loc %}
|
||||||
<li class="pf-c-nav__item">
|
<li class="pf-c-nav__item">
|
||||||
<a href="{{ source.view_name }}"
|
<a href="{{ source.url }}"
|
||||||
class="pf-c-nav__link {% if source.url == request.get_full_path %} pf-m-current {% endif %}">
|
class="pf-c-nav__link {% if source.url == request.get_full_path %} pf-m-current {% endif %}">
|
||||||
{{ source.name }}
|
{{ source.name }}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -56,7 +56,9 @@ class CertificateKeyPair(CreatedUpdatedModel):
|
|||||||
@property
|
@property
|
||||||
def fingerprint(self) -> str:
|
def fingerprint(self) -> str:
|
||||||
"""Get SHA256 Fingerprint of certificate_data"""
|
"""Get SHA256 Fingerprint of certificate_data"""
|
||||||
return hexlify(self.certificate.fingerprint(hashes.SHA256())).decode("utf-8")
|
return hexlify(self.certificate.fingerprint(hashes.SHA256()), ":").decode(
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Certificate-Key Pair {self.name} {self.fingerprint}"
|
return f"Certificate-Key Pair {self.name} {self.fingerprint}"
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional
|
|||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
from elasticapm import capture_span
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import User
|
from passbook.core.models import User
|
||||||
@ -88,6 +89,7 @@ class FlowPlanner:
|
|||||||
self.allow_empty_flows = False
|
self.allow_empty_flows = False
|
||||||
self.flow = flow
|
self.flow = flow
|
||||||
|
|
||||||
|
@capture_span(name="FlowPlanner", span_type="flow.planner.plan")
|
||||||
def plan(
|
def plan(
|
||||||
self, request: HttpRequest, default_context: Optional[Dict[str, Any]] = None
|
self, request: HttpRequest, default_context: Optional[Dict[str, Any]] = None
|
||||||
) -> FlowPlan:
|
) -> FlowPlan:
|
||||||
@ -127,6 +129,7 @@ class FlowPlanner:
|
|||||||
raise EmptyFlowException()
|
raise EmptyFlowException()
|
||||||
return plan
|
return plan
|
||||||
|
|
||||||
|
@capture_span(name="FlowPlanner", span_type="flow.planner.build_plan")
|
||||||
def _build_plan(
|
def _build_plan(
|
||||||
self,
|
self,
|
||||||
user: User,
|
user: User,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from textwrap import indent
|
|||||||
from typing import Any, Dict, Iterable, Optional
|
from typing import Any, Dict, Iterable, Optional
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from elasticapm import capture_span
|
||||||
from requests import Session
|
from requests import Session
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
@ -68,6 +69,7 @@ class BaseEvaluator:
|
|||||||
full_expression += f"\nresult = handler({handler_signature})"
|
full_expression += f"\nresult = handler({handler_signature})"
|
||||||
return full_expression
|
return full_expression
|
||||||
|
|
||||||
|
@capture_span(name="BaseEvaluator", span_type="lib.evaluator.evaluate")
|
||||||
def evaluate(self, expression_source: str) -> Any:
|
def evaluate(self, expression_source: str) -> Any:
|
||||||
"""Parse and evaluate expression. If the syntax is incorrect, a SyntaxError is raised.
|
"""Parse and evaluate expression. If the syntax is incorrect, a SyntaxError is raised.
|
||||||
If any exception is raised during execution, it is raised.
|
If any exception is raised during execution, it is raised.
|
||||||
|
|||||||
@ -35,7 +35,7 @@ def before_send(event, hint):
|
|||||||
SentryIgnoredException,
|
SentryIgnoredException,
|
||||||
)
|
)
|
||||||
if "exc_info" in hint:
|
if "exc_info" in hint:
|
||||||
_exc_type, exc_value, _ = hint["exc_info"]
|
_, exc_value, _ = hint["exc_info"]
|
||||||
if isinstance(exc_value, ignored_classes):
|
if isinstance(exc_value, ignored_classes):
|
||||||
LOGGER.info("Supressing error %r", exc_value)
|
LOGGER.info("Supressing error %r", exc_value)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@ -23,4 +23,4 @@ def get_client_ip(request: Optional[HttpRequest]) -> Optional[str]:
|
|||||||
Returns none if no IP Could be found"""
|
Returns none if no IP Could be found"""
|
||||||
if request:
|
if request:
|
||||||
return _get_client_ip_from_meta(request.META)
|
return _get_client_ip_from_meta(request.META)
|
||||||
return ""
|
return None
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from typing import List, Optional
|
|||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
from elasticapm import capture_span
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import User
|
from passbook.core.models import User
|
||||||
@ -69,6 +70,7 @@ class PolicyEngine:
|
|||||||
if policy.__class__ == Policy:
|
if policy.__class__ == Policy:
|
||||||
raise TypeError(f"Policy '{policy}' is root type")
|
raise TypeError(f"Policy '{policy}' is root type")
|
||||||
|
|
||||||
|
@capture_span(name="PolicyEngine", span_type="policy.engine.build")
|
||||||
def build(self) -> "PolicyEngine":
|
def build(self) -> "PolicyEngine":
|
||||||
"""Build task group"""
|
"""Build task group"""
|
||||||
for binding in self._iter_bindings():
|
for binding in self._iter_bindings():
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from multiprocessing.connection import Connection
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from elasticapm import capture_span
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.policies.exceptions import PolicyException
|
from passbook.policies.exceptions import PolicyException
|
||||||
@ -44,6 +45,7 @@ class PolicyProcess(Process):
|
|||||||
if connection:
|
if connection:
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|
||||||
|
@capture_span(name="PolicyEngine", span_type="policy.process.execute")
|
||||||
def execute(self) -> PolicyResult:
|
def execute(self) -> PolicyResult:
|
||||||
"""Run actual policy, returns result"""
|
"""Run actual policy, returns result"""
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
"""passbook reputation request policy"""
|
"""passbook reputation request policy"""
|
||||||
|
from django.core.cache import cache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
@ -7,6 +8,9 @@ from passbook.lib.utils.http import get_client_ip
|
|||||||
from passbook.policies.models import Policy
|
from passbook.policies.models import Policy
|
||||||
from passbook.policies.types import PolicyRequest, PolicyResult
|
from passbook.policies.types import PolicyRequest, PolicyResult
|
||||||
|
|
||||||
|
CACHE_KEY_IP_PREFIX = "passbook_reputation_ip_"
|
||||||
|
CACHE_KEY_USER_PREFIX = "passbook_reputation_user_"
|
||||||
|
|
||||||
|
|
||||||
class ReputationPolicy(Policy):
|
class ReputationPolicy(Policy):
|
||||||
"""Return true if request IP/target username's score is below a certain threshold"""
|
"""Return true if request IP/target username's score is below a certain threshold"""
|
||||||
@ -18,18 +22,14 @@ class ReputationPolicy(Policy):
|
|||||||
form = "passbook.policies.reputation.forms.ReputationPolicyForm"
|
form = "passbook.policies.reputation.forms.ReputationPolicyForm"
|
||||||
|
|
||||||
def passes(self, request: PolicyRequest) -> PolicyResult:
|
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||||
remote_ip = get_client_ip(request.http_request)
|
remote_ip = get_client_ip(request.http_request) or "255.255.255.255"
|
||||||
passing = True
|
passing = True
|
||||||
if self.check_ip:
|
if self.check_ip:
|
||||||
ip_scores = IPReputation.objects.filter(
|
score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0)
|
||||||
ip=remote_ip, score__lte=self.threshold
|
passing = passing and score <= self.threshold
|
||||||
)
|
|
||||||
passing = passing and ip_scores.exists()
|
|
||||||
if self.check_username:
|
if self.check_username:
|
||||||
user_scores = UserReputation.objects.filter(
|
score = cache.get_or_set(CACHE_KEY_USER_PREFIX + request.user.username, 0)
|
||||||
user=request.user, score__lte=self.threshold
|
passing = passing and score <= self.threshold
|
||||||
)
|
|
||||||
passing = passing and user_scores.exists()
|
|
||||||
return PolicyResult(passing)
|
return PolicyResult(passing)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
13
passbook/policies/reputation/settings.py
Normal file
13
passbook/policies/reputation/settings.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
"""Reputation Settings"""
|
||||||
|
from celery.schedules import crontab
|
||||||
|
|
||||||
|
CELERY_BEAT_SCHEDULE = {
|
||||||
|
"policies_reputation_ip_save": {
|
||||||
|
"task": "passbook.policies.reputation.tasks.save_ip_reputation",
|
||||||
|
"schedule": crontab(minute="*/5"),
|
||||||
|
},
|
||||||
|
"policies_reputation_user_save": {
|
||||||
|
"task": "passbook.policies.reputation.tasks.save_user_reputation",
|
||||||
|
"schedule": crontab(minute="*/5"),
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -1,29 +1,31 @@
|
|||||||
"""passbook reputation request signals"""
|
"""passbook reputation request signals"""
|
||||||
from django.contrib.auth.signals import user_logged_in, user_login_failed
|
from django.contrib.auth.signals import user_logged_in, user_login_failed
|
||||||
|
from django.core.cache import cache
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from django.http import HttpRequest
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import User
|
|
||||||
from passbook.lib.utils.http import get_client_ip
|
from passbook.lib.utils.http import get_client_ip
|
||||||
from passbook.policies.reputation.models import IPReputation, UserReputation
|
from passbook.policies.reputation.models import (
|
||||||
|
CACHE_KEY_IP_PREFIX,
|
||||||
|
CACHE_KEY_USER_PREFIX,
|
||||||
|
)
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
def update_score(request, username, amount):
|
def update_score(request: HttpRequest, username: str, amount: int):
|
||||||
"""Update score for IP and User"""
|
"""Update score for IP and User"""
|
||||||
remote_ip = get_client_ip(request) or "255.255.255.255."
|
remote_ip = get_client_ip(request) or "255.255.255.255"
|
||||||
ip_score, _ = IPReputation.objects.update_or_create(ip=remote_ip)
|
|
||||||
ip_score.score += amount
|
# We only update the cache here, as its faster than writing to the DB
|
||||||
ip_score.save()
|
cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0)
|
||||||
LOGGER.debug("Updated score", amount=amount, for_ip=remote_ip)
|
cache.incr(CACHE_KEY_IP_PREFIX + remote_ip, amount)
|
||||||
user = User.objects.filter(username=username)
|
|
||||||
if not user.exists():
|
cache.get_or_set(CACHE_KEY_USER_PREFIX + username, 0)
|
||||||
return
|
cache.incr(CACHE_KEY_USER_PREFIX + username, amount)
|
||||||
user_score, _ = UserReputation.objects.update_or_create(user=user.first())
|
|
||||||
user_score.score += amount
|
LOGGER.debug("Updated score", amount=amount, for_user=username, for_ip=remote_ip)
|
||||||
user_score.save()
|
|
||||||
LOGGER.debug("Updated score", amount=amount, for_user=username)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_login_failed)
|
@receiver(user_login_failed)
|
||||||
|
|||||||
46
passbook/policies/reputation/tasks.py
Normal file
46
passbook/policies/reputation/tasks.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
"""Reputation tasks"""
|
||||||
|
from django.core.cache import cache
|
||||||
|
from structlog import get_logger
|
||||||
|
|
||||||
|
from passbook.core.models import User
|
||||||
|
from passbook.policies.reputation.models import IPReputation, UserReputation
|
||||||
|
from passbook.policies.reputation.signals import (
|
||||||
|
CACHE_KEY_IP_PREFIX,
|
||||||
|
CACHE_KEY_USER_PREFIX,
|
||||||
|
)
|
||||||
|
from passbook.root.celery import CELERY_APP
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
@CELERY_APP.task()
|
||||||
|
def save_ip_reputation():
|
||||||
|
"""Save currently cached reputation to database"""
|
||||||
|
keys = cache.keys(CACHE_KEY_IP_PREFIX + "*")
|
||||||
|
objects_to_update = []
|
||||||
|
for key in keys:
|
||||||
|
score = cache.get(key)
|
||||||
|
remote_ip = key.replace(CACHE_KEY_IP_PREFIX, "")
|
||||||
|
print(remote_ip)
|
||||||
|
rep, _ = IPReputation.objects.get_or_create(ip=remote_ip)
|
||||||
|
rep.score = score
|
||||||
|
objects_to_update.append(rep)
|
||||||
|
IPReputation.objects.bulk_update(objects_to_update, ["score"])
|
||||||
|
|
||||||
|
|
||||||
|
@CELERY_APP.task()
|
||||||
|
def save_user_reputation():
|
||||||
|
"""Save currently cached reputation to database"""
|
||||||
|
keys = cache.keys(CACHE_KEY_USER_PREFIX + "*")
|
||||||
|
objects_to_update = []
|
||||||
|
for key in keys:
|
||||||
|
score = cache.get(key)
|
||||||
|
username = key.replace(CACHE_KEY_USER_PREFIX, "")
|
||||||
|
users = User.objects.filter(username=username)
|
||||||
|
if not users.exists():
|
||||||
|
LOGGER.info("User in cache does not exist, ignoring", username=username)
|
||||||
|
continue
|
||||||
|
rep, _ = UserReputation.objects.get_or_create(user=users.first())
|
||||||
|
rep.score = score
|
||||||
|
objects_to_update.append(rep)
|
||||||
|
UserReputation.objects.bulk_update(objects_to_update, ["score"])
|
||||||
55
passbook/policies/reputation/tests.py
Normal file
55
passbook/policies/reputation/tests.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
"""test reputation signals and policy"""
|
||||||
|
from django.contrib.auth import authenticate
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from passbook.core.models import User
|
||||||
|
from passbook.policies.reputation.models import (
|
||||||
|
CACHE_KEY_IP_PREFIX,
|
||||||
|
CACHE_KEY_USER_PREFIX,
|
||||||
|
IPReputation,
|
||||||
|
ReputationPolicy,
|
||||||
|
UserReputation,
|
||||||
|
)
|
||||||
|
from passbook.policies.reputation.tasks import save_ip_reputation, save_user_reputation
|
||||||
|
from passbook.policies.types import PolicyRequest
|
||||||
|
|
||||||
|
|
||||||
|
class TestReputationPolicy(TestCase):
|
||||||
|
"""test reputation signals and policy"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.test_ip = "255.255.255.255"
|
||||||
|
self.test_username = "test"
|
||||||
|
cache.delete(CACHE_KEY_IP_PREFIX + self.test_ip)
|
||||||
|
cache.delete(CACHE_KEY_USER_PREFIX + self.test_username)
|
||||||
|
# We need a user for the one-to-one in userreputation
|
||||||
|
self.user = User.objects.create(username=self.test_username)
|
||||||
|
|
||||||
|
def test_ip_reputation(self):
|
||||||
|
"""test IP reputation"""
|
||||||
|
# Trigger negative reputation
|
||||||
|
authenticate(None, username=self.test_username, password=self.test_username)
|
||||||
|
# Test value in cache
|
||||||
|
self.assertEqual(cache.get(CACHE_KEY_IP_PREFIX + self.test_ip), -1)
|
||||||
|
# Save cache and check db values
|
||||||
|
save_ip_reputation()
|
||||||
|
self.assertEqual(IPReputation.objects.get(ip=self.test_ip).score, -1)
|
||||||
|
|
||||||
|
def test_user_reputation(self):
|
||||||
|
"""test User reputation"""
|
||||||
|
# Trigger negative reputation
|
||||||
|
authenticate(None, username=self.test_username, password=self.test_username)
|
||||||
|
# Test value in cache
|
||||||
|
self.assertEqual(cache.get(CACHE_KEY_USER_PREFIX + self.test_username), -1)
|
||||||
|
# Save cache and check db values
|
||||||
|
save_user_reputation()
|
||||||
|
self.assertEqual(UserReputation.objects.get(user=self.user).score, -1)
|
||||||
|
|
||||||
|
def test_policy(self):
|
||||||
|
"""Test Policy"""
|
||||||
|
request = PolicyRequest(user=self.user)
|
||||||
|
policy: ReputationPolicy = ReputationPolicy.objects.create(
|
||||||
|
name="reputation-test", threshold=0
|
||||||
|
)
|
||||||
|
self.assertTrue(policy.passes(request).passing)
|
||||||
@ -14,7 +14,6 @@ def invalidate_policy_cache(sender, instance, **_):
|
|||||||
from passbook.policies.models import Policy, PolicyBinding
|
from passbook.policies.models import Policy, PolicyBinding
|
||||||
|
|
||||||
if isinstance(instance, Policy):
|
if isinstance(instance, Policy):
|
||||||
LOGGER.debug("Invalidating policy cache", policy=instance)
|
|
||||||
total = 0
|
total = 0
|
||||||
for binding in PolicyBinding.objects.filter(policy=instance):
|
for binding in PolicyBinding.objects.filter(policy=instance):
|
||||||
prefix = (
|
prefix = (
|
||||||
@ -23,4 +22,4 @@ def invalidate_policy_cache(sender, instance, **_):
|
|||||||
keys = cache.keys(prefix)
|
keys = cache.keys(prefix)
|
||||||
total += len(keys)
|
total += len(keys)
|
||||||
cache.delete_many(keys)
|
cache.delete_many(keys)
|
||||||
LOGGER.debug("Deleted keys", len=total)
|
LOGGER.debug("Invalidating policy cache", policy=instance, keys=total)
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from passbook.policies.expression.models import ExpressionPolicy
|
|||||||
from passbook.policies.models import Policy, PolicyBinding, PolicyBindingModel
|
from passbook.policies.models import Policy, PolicyBinding, PolicyBindingModel
|
||||||
|
|
||||||
|
|
||||||
class PolicyTestEngine(TestCase):
|
class TestPolicyEngine(TestCase):
|
||||||
"""PolicyEngine tests"""
|
"""PolicyEngine tests"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -21,7 +21,9 @@
|
|||||||
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
||||||
<i class="fas fa-times" aria-hidden="true"></i>
|
<i class="fas fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="pf-c-modal-box__header">
|
||||||
<h1 class="pf-c-title pf-m-2xl">{% trans 'Setup with docker-compose' %}</h1>
|
<h1 class="pf-c-title pf-m-2xl">{% trans 'Setup with docker-compose' %}</h1>
|
||||||
|
</div>
|
||||||
<div class="pf-c-modal-box__body">
|
<div class="pf-c-modal-box__body">
|
||||||
{% trans 'Add the following snippet to your docker-compose file.' %}
|
{% trans 'Add the following snippet to your docker-compose file.' %}
|
||||||
<textarea class="codemirror" readonly data-cm-mode="yaml">{% include 'app_gw/docker-compose.yml' %}</textarea>
|
<textarea class="codemirror" readonly data-cm-mode="yaml">{% include 'app_gw/docker-compose.yml' %}</textarea>
|
||||||
@ -39,7 +41,9 @@
|
|||||||
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
||||||
<i class="fas fa-times" aria-hidden="true"></i>
|
<i class="fas fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="pf-c-modal-box__header">
|
||||||
<h1 class="pf-c-title pf-m-2xl">{% trans 'Setup with Kubernetes' %}</h1>
|
<h1 class="pf-c-title pf-m-2xl">{% trans 'Setup with Kubernetes' %}</h1>
|
||||||
|
</div>
|
||||||
<div class="pf-c-modal-box__body">
|
<div class="pf-c-modal-box__body">
|
||||||
<p>{% trans 'Download the manifest to create the Gatekeeper deployment and service:' %}</p>
|
<p>{% trans 'Download the manifest to create the Gatekeeper deployment and service:' %}</p>
|
||||||
<a href="{% url 'passbook_providers_app_gw:k8s-manifest' provider=provider.pk %}">{% trans 'Here' %}</a>
|
<a href="{% url 'passbook_providers_app_gw:k8s-manifest' provider=provider.pk %}">{% trans 'Here' %}</a>
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
||||||
<i class="fas fa-times" aria-hidden="true"></i>
|
<i class="fas fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="pf-c-modal-box__header">
|
||||||
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Setup URLs' %}</h1>
|
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Setup URLs' %}</h1>
|
||||||
|
</div>
|
||||||
<div class="pf-c-modal-box__body" id="modal-description">
|
<div class="pf-c-modal-box__body" id="modal-description">
|
||||||
<form class="pf-c-form">
|
<form class="pf-c-form">
|
||||||
<div class="pf-c-form__group">
|
<div class="pf-c-form__group">
|
||||||
|
|||||||
@ -8,7 +8,9 @@
|
|||||||
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
||||||
<i class="fas fa-times" aria-hidden="true"></i>
|
<i class="fas fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="pf-c-modal-box__header">
|
||||||
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Setup URLs' %}</h1>
|
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Setup URLs' %}</h1>
|
||||||
|
</div>
|
||||||
<div class="pf-c-modal-box__body" id="modal-description">
|
<div class="pf-c-modal-box__body" id="modal-description">
|
||||||
<form class="pf-c-form">
|
<form class="pf-c-form">
|
||||||
<div class="pf-c-form__group">
|
<div class="pf-c-form__group">
|
||||||
|
|||||||
@ -8,7 +8,9 @@
|
|||||||
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
||||||
<i class="fas fa-times" aria-hidden="true"></i>
|
<i class="fas fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="pf-c-modal-box__header">
|
||||||
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Metadata' %}</h1>
|
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Metadata' %}</h1>
|
||||||
|
</div>
|
||||||
<div class="pf-c-modal-box__body" id="modal-description">
|
<div class="pf-c-modal-box__body" id="modal-description">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<textarea class="codemirror" readonly data-cm-mode="xml">{{ metadata }}</textarea>
|
<textarea class="codemirror" readonly data-cm-mode="xml">{{ metadata }}</textarea>
|
||||||
|
|||||||
@ -131,7 +131,10 @@ REST_FRAMEWORK = {
|
|||||||
"rest_framework.filters.OrderingFilter",
|
"rest_framework.filters.OrderingFilter",
|
||||||
"rest_framework.filters.SearchFilter",
|
"rest_framework.filters.SearchFilter",
|
||||||
],
|
],
|
||||||
"DEFAULT_PERMISSION_CLASSES": ("passbook.api.permissions.CustomObjectPermissions"),
|
"DEFAULT_PERMISSION_CLASSES": (
|
||||||
|
"rest_framework.permissions.DjangoObjectPermissions",
|
||||||
|
"passbook.api.permissions.CustomObjectPermissions",
|
||||||
|
),
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||||
"passbook.api.auth.PassbookTokenAuthentication",
|
"passbook.api.auth.PassbookTokenAuthentication",
|
||||||
"rest_framework.authentication.SessionAuthentication",
|
"rest_framework.authentication.SessionAuthentication",
|
||||||
@ -278,6 +281,19 @@ if not DEBUG and _ERROR_REPORTING:
|
|||||||
release="passbook@%s" % __version__,
|
release="passbook@%s" % __version__,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_APM_ENABLED = CONFIG.y("apm.enabled", True)
|
||||||
|
if _APM_ENABLED:
|
||||||
|
INSTALLED_APPS.append("elasticapm.contrib.django")
|
||||||
|
ELASTIC_APM = {
|
||||||
|
"CLOUD_PROVIDER": False,
|
||||||
|
"DEBUG": DEBUG,
|
||||||
|
"SERVICE_NAME": "passbook",
|
||||||
|
"SERVICE_VERSION": __version__,
|
||||||
|
"SECRET_TOKEN": CONFIG.y("apm.secret_token", ""),
|
||||||
|
"SERVER_URL": CONFIG.y("apm.secret_token", "http://localhost:8200"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/2.1/howto/static-files/
|
# https://docs.djangoproject.com/en/2.1/howto/static-files/
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ AUTHENTICATION_BACKENDS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
CELERY_BEAT_SCHEDULE = {
|
CELERY_BEAT_SCHEDULE = {
|
||||||
"sync": {
|
"sources_ldap_sync": {
|
||||||
"task": "passbook.sources.ldap.tasks.sync",
|
"task": "passbook.sources.ldap.tasks.sync",
|
||||||
"schedule": crontab(minute=0), # Run every hour
|
"schedule": crontab(minute=0), # Run every hour
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,29 @@
|
|||||||
{% extends "user/base.html" %}
|
{% extends "user/base.html" %}
|
||||||
|
|
||||||
|
{% load passbook_utils %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page %}
|
{% block page %}
|
||||||
|
<div class="pf-c-card">
|
||||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||||
<h1>{{ source.name }}</h1>
|
{% blocktrans with source_name=source.name %}
|
||||||
|
Source {{ source_name }}
|
||||||
|
{% endblocktrans %}
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body">
|
<div class="pf-c-card__body">
|
||||||
{% if connections.exists %}
|
{% if connections.exists %}
|
||||||
<p>{% trans 'Connected.' %}</p>
|
<p>{% trans 'Connected.' %}</p>
|
||||||
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_sources_oauth:oauth-client-disconnect' source_slug=source.slug %}">
|
<a class="pf-c-button pf-m-danger"
|
||||||
|
href="{% url 'passbook_sources_oauth:oauth-client-disconnect' source_slug=source.slug %}">
|
||||||
{% trans 'Disconnect' %}
|
{% trans 'Disconnect' %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>Not connected.</p>
|
<p>Not connected.</p>
|
||||||
<a class="pf-c-button pf-m-primary" href="{% url 'passbook_sources_oauth:oauth-client-login' source_slug=source.slug %}">
|
<a class="pf-c-button pf-m-primary"
|
||||||
|
href="{% url 'passbook_sources_oauth:oauth-client-login' source_slug=source.slug %}">
|
||||||
{% trans 'Connect' %}
|
{% trans 'Connect' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
5
passbook/stages/invitation/signals.py
Normal file
5
passbook/stages/invitation/signals.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
"""passbook invitation signals"""
|
||||||
|
from django.core.signals import Signal
|
||||||
|
|
||||||
|
invitation_created = Signal(providing_args=["request", "invitation"])
|
||||||
|
invitation_used = Signal(providing_args=["request", "invitation"])
|
||||||
@ -4,6 +4,7 @@ from django.shortcuts import get_object_or_404
|
|||||||
|
|
||||||
from passbook.flows.stage import StageView
|
from passbook.flows.stage import StageView
|
||||||
from passbook.stages.invitation.models import Invitation, InvitationStage
|
from passbook.stages.invitation.models import Invitation, InvitationStage
|
||||||
|
from passbook.stages.invitation.signals import invitation_used
|
||||||
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||||
|
|
||||||
INVITATION_TOKEN_KEY = "token"
|
INVITATION_TOKEN_KEY = "token"
|
||||||
@ -25,4 +26,5 @@ class InvitationStageView(StageView):
|
|||||||
invite: Invitation = get_object_or_404(Invitation, pk=token)
|
invite: Invitation = get_object_or_404(Invitation, pk=token)
|
||||||
self.executor.plan.context[PLAN_CONTEXT_PROMPT] = invite.fixed_data
|
self.executor.plan.context[PLAN_CONTEXT_PROMPT] = invite.fixed_data
|
||||||
self.executor.plan.context[INVITATION_IN_EFFECT] = True
|
self.executor.plan.context[INVITATION_IN_EFFECT] = True
|
||||||
|
invitation_used.send(sender=self, request=request, invitation=invite)
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
|
|||||||
4
passbook/stages/user_write/signals.py
Normal file
4
passbook/stages/user_write/signals.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
"""passbook user_write signals"""
|
||||||
|
from django.core.signals import Signal
|
||||||
|
|
||||||
|
user_write = Signal(providing_args=["request", "user", "data"])
|
||||||
@ -12,6 +12,7 @@ from passbook.flows.stage import StageView
|
|||||||
from passbook.lib.utils.reflection import class_to_path
|
from passbook.lib.utils.reflection import class_to_path
|
||||||
from passbook.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
from passbook.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||||
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||||
|
from passbook.stages.user_write.signals import user_write
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ class UserWriteStageView(StageView):
|
|||||||
else:
|
else:
|
||||||
user.attributes[key] = value
|
user.attributes[key] = value
|
||||||
user.save()
|
user.save()
|
||||||
|
user_write.send(sender=self, request=request, user=user, data=data)
|
||||||
# Check if the password has been updated, and update the session auth hash
|
# Check if the password has been updated, and update the session auth hash
|
||||||
if any(["password" in x for x in data.keys()]):
|
if any(["password" in x for x in data.keys()]):
|
||||||
update_session_auth_hash(self.request, user)
|
update_session_auth_hash(self.request, user)
|
||||||
|
|||||||
@ -241,3 +241,9 @@ input[data-is-monospace] {
|
|||||||
white-space: nowrap !important;
|
white-space: nowrap !important;
|
||||||
padding: var(--pf-c-table--cell--PaddingTop) var(--pf-c-table--cell--PaddingRight) var(--pf-c-table--cell--PaddingBottom) var(--pf-c-table--cell--PaddingLeft) !important;
|
padding: var(--pf-c-table--cell--PaddingTop) var(--pf-c-table--cell--PaddingRight) var(--pf-c-table--cell--PaddingBottom) var(--pf-c-table--cell--PaddingLeft) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Aggregate Cards */
|
||||||
|
.pf-c-card.pf-c-card-aggregate > .pf-c-card__body > .aggregate-status {
|
||||||
|
font-size: var(--pf-global--icon--FontSize--lg);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user