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]
|
||||
current_version = 0.9.0-pre5
|
||||
current_version = 0.9.0-pre6
|
||||
tag = True
|
||||
commit = True
|
||||
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
|
||||
run: docker build
|
||||
--no-cache
|
||||
-t beryju/passbook:0.9.0-pre5
|
||||
-t beryju/passbook:0.9.0-pre6
|
||||
-t beryju/passbook:latest
|
||||
-f Dockerfile .
|
||||
- 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)
|
||||
run: docker push beryju/passbook:latest
|
||||
build-gatekeeper:
|
||||
@ -37,11 +37,11 @@ jobs:
|
||||
cd gatekeeper
|
||||
docker build \
|
||||
--no-cache \
|
||||
-t beryju/passbook-gatekeeper:0.9.0-pre5 \
|
||||
-t beryju/passbook-gatekeeper:0.9.0-pre6 \
|
||||
-t beryju/passbook-gatekeeper:latest \
|
||||
-f Dockerfile .
|
||||
- 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)
|
||||
run: docker push beryju/passbook-gatekeeper:latest
|
||||
build-static:
|
||||
@ -66,11 +66,11 @@ jobs:
|
||||
run: docker build
|
||||
--no-cache
|
||||
--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
|
||||
-f static.Dockerfile .
|
||||
- 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)
|
||||
run: docker push beryju/passbook-static:latest
|
||||
test-release:
|
||||
@ -86,3 +86,19 @@ jobs:
|
||||
docker-compose up --no-start
|
||||
docker-compose start postgresql redis
|
||||
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 = "*"
|
||||
urllib3 = {extras = ["secure"],version = "*"}
|
||||
facebook-sdk = "*"
|
||||
elastic-apm = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.8"
|
||||
|
||||
113
Pipfile.lock
generated
113
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "fd0192b73c01aaffb90716ce7b6d4e5be9adb8788d3ebd58e54ccd6f85d9b71b"
|
||||
"sha256": "f90d79a67bbd689ca4e7ccbfd528e4ed45078e848c36d84e53ff9c6b2a1e92ed"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@ -46,18 +46,18 @@
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:c2a223f4b48782e8b160b2130265e2a66081df111f630a5a384d6909e29a5aa9",
|
||||
"sha256:ce5a4ab6af9e993d1864209cbbb6f4812f65fbc57ad6b95e5967d8bf38b1dcfb"
|
||||
"sha256:ae57df1fbad7e29954a160d77cbf650d6562eb0d304c1206afa71d914e771a66",
|
||||
"sha256:cbe618d61cb8f75cd9495ea36e69bad7c8984eb11f02ad247be4c9a2eb7eb647"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.14.16"
|
||||
"version": "==1.14.17"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:99d995ef99cf77458a661f3fc64e0c3a4ce77ca30facfdf0472f44b2953dd856",
|
||||
"sha256:fe0c4f7cd6b67eff3b7cb8dff6709a65d6fca10b7b7449a493b2036915e98b4c"
|
||||
"sha256:5528c04c360019c24f2706ce82872c9ab767a8c581beffdfdaf006cce7499cac",
|
||||
"sha256:d65b5574dad8c221344496352245828d9ffecaa0868199eb04ccd2eb2ff09133"
|
||||
],
|
||||
"version": "==1.17.16"
|
||||
"version": "==1.17.17"
|
||||
},
|
||||
"celery": {
|
||||
"hashes": [
|
||||
@ -306,6 +306,38 @@
|
||||
],
|
||||
"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": {
|
||||
"hashes": [
|
||||
"sha256:2e987b3e0f466a6f4ee77b935eb023dba1384134f004a2af21f1cfff7fe0806e",
|
||||
@ -945,40 +977,43 @@
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a",
|
||||
"sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355",
|
||||
"sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65",
|
||||
"sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7",
|
||||
"sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9",
|
||||
"sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1",
|
||||
"sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0",
|
||||
"sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55",
|
||||
"sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c",
|
||||
"sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6",
|
||||
"sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef",
|
||||
"sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019",
|
||||
"sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e",
|
||||
"sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0",
|
||||
"sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf",
|
||||
"sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24",
|
||||
"sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2",
|
||||
"sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c",
|
||||
"sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4",
|
||||
"sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0",
|
||||
"sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd",
|
||||
"sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04",
|
||||
"sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e",
|
||||
"sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730",
|
||||
"sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2",
|
||||
"sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768",
|
||||
"sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796",
|
||||
"sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7",
|
||||
"sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a",
|
||||
"sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489",
|
||||
"sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"
|
||||
"sha256:0fc4e0d91350d6f43ef6a61f64a48e917637e1dcfcba4b4b7d543c628ef82c2d",
|
||||
"sha256:10f2a618a6e75adf64329f828a6a5b40244c1c50f5ef4ce4109e904e69c71bd2",
|
||||
"sha256:12eaccd86d9a373aea59869bc9cfa0ab6ba8b1477752110cb4c10d165474f703",
|
||||
"sha256:1874bdc943654ba46d28f179c1846f5710eda3aeb265ff029e0ac2b52daae404",
|
||||
"sha256:1dcebae667b73fd4aa69237e6afb39abc2f27520f2358590c1b13dd90e32abe7",
|
||||
"sha256:1e58fca3d9ec1a423f1b7f2aa34af4f733cbfa9020c8fe39ca451b6071237405",
|
||||
"sha256:214eb2110217f2636a9329bc766507ab71a3a06a8ea30cdeebb47c24dce5972d",
|
||||
"sha256:25fe74b5b2f1b4abb11e103bb7984daca8f8292683957d0738cd692f6a7cc64c",
|
||||
"sha256:32ecee61a43be509b91a526819717d5e5650e009a8d5eda8631a59c721d5f3b6",
|
||||
"sha256:3740b796015b889e46c260ff18b84683fa2e30f0f75a171fb10d2bf9fb91fc70",
|
||||
"sha256:3b2c34690f613525672697910894b60d15800ac7e779fbd0fccf532486c1ba40",
|
||||
"sha256:41d88736c42f4a22c494c32cc48a05828236e37c991bd9760f8923415e3169e4",
|
||||
"sha256:42fa45a29f1059eda4d3c7b509589cc0343cd6bbf083d6118216830cd1a51613",
|
||||
"sha256:4bb385a747e6ae8a65290b3df60d6c8a692a5599dc66c9fa3520e667886f2e10",
|
||||
"sha256:509294f3e76d3f26b35083973fbc952e01e1727656d979b11182f273f08aa80b",
|
||||
"sha256:5c74c5b6045969b07c9fb36b665c9cac84d6c174a809fc1b21bdc06c7836d9a0",
|
||||
"sha256:60a3d36297b65c7f78329b80120f72947140f45b5c7a017ea730f9112b40f2ec",
|
||||
"sha256:6f91b4492c5cde83bfe462f5b2b997cdf96a138f7c58b1140f05de5751623cf1",
|
||||
"sha256:7403675df5e27745571aba1c957c7da2dacb537c21e14007ec3a417bf31f7f3d",
|
||||
"sha256:87bdc8135b8ee739840eee19b184804e5d57f518578ffc797f5afa2c3c297913",
|
||||
"sha256:8a3decd12e7934d0254939e2bf434bf04a5890c5bf91a982685021786a08087e",
|
||||
"sha256:9702e2cb1c6dec01fb8e1a64c015817c0800a6eca287552c47a5ee0ebddccf62",
|
||||
"sha256:a4d511012beb967a39580ba7d2549edf1e6865a33e5fe51e4dce550522b3ac0e",
|
||||
"sha256:bbb387811f7a18bdc61a2ea3d102be0c7e239b0db9c83be7bfa50f095db5b92a",
|
||||
"sha256:bfcc811883699ed49afc58b1ed9f80428a18eb9166422bce3c31a53dba00fd1d",
|
||||
"sha256:c32aa13cc3fe86b0f744dfe35a7f879ee33ac0a560684fef0f3e1580352b818f",
|
||||
"sha256:ca63dae130a2e788f2b249200f01d7fa240f24da0596501d387a50e57aa7075e",
|
||||
"sha256:d54d7ea74cc00482a2410d63bf10aa34ebe1c49ac50779652106c867f9986d6b",
|
||||
"sha256:d67599521dff98ec8c34cd9652cbcfe16ed076a2209625fca9dc7419b6370e5c",
|
||||
"sha256:d82db1b9a92cb5c67661ca6616bdca6ff931deceebb98eecbd328812dab52032",
|
||||
"sha256:d9ad0a988ae20face62520785ec3595a5e64f35a21762a57d115dae0b8fb894a",
|
||||
"sha256:ebf2431b2d457ae5217f3a1179533c456f3272ded16f8ed0b32961a6d90e38ee",
|
||||
"sha256:ed9a21502e9223f563e071759f769c3d6a2e1ba5328c31e86830368e8d78bc9c",
|
||||
"sha256:f50632ef2d749f541ca8e6c07c9928a37f87505ce3a9f20c8446ad310f1aa87b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.1"
|
||||
"version": "==5.2"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
|
||||
@ -194,15 +194,13 @@ stages:
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: cd gatekeeper
|
||||
- task: Docker@2
|
||||
inputs:
|
||||
containerRegistry: 'dockerhub'
|
||||
repository: 'beryju/passbook-gatekeeper'
|
||||
command: 'buildAndPush'
|
||||
Dockerfile: 'Dockerfile'
|
||||
Dockerfile: 'gatekeeper/Dockerfile'
|
||||
buildContext: 'gatekeeper/'
|
||||
tags: 'gh-$(Build.SourceBranchName)'
|
||||
- job: build_static
|
||||
pool:
|
||||
|
||||
@ -22,6 +22,11 @@ config:
|
||||
# Log level used by web and worker
|
||||
# Can be either debug, info, warning, error
|
||||
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 requires the CoreOS Prometheus Operator.
|
||||
|
||||
@ -6,13 +6,13 @@ To export data from your old instance, run this command:
|
||||
|
||||
- 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
|
||||
```
|
||||
|
||||
- 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
|
||||
```
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
apiVersion: v1
|
||||
appVersion: "0.9.0-pre5"
|
||||
appVersion: "0.9.0-pre6"
|
||||
description: A Helm chart for 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
|
||||
|
||||
@ -21,3 +21,7 @@ data:
|
||||
message_queue_db: 1
|
||||
error_reporting: {{ .Values.config.error_reporting }}
|
||||
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.
|
||||
# Declare variables to be passed into your templates.
|
||||
image:
|
||||
tag: 0.9.0-pre5
|
||||
tag: 0.9.0-pre6
|
||||
|
||||
nameOverride: ""
|
||||
|
||||
@ -14,6 +14,11 @@ config:
|
||||
# Log level used by web and worker
|
||||
# Can be either debug, info, warning, error
|
||||
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 requires the CoreOS Prometheus Operator.
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
"""passbook"""
|
||||
__version__ = "0.9.0-pre5"
|
||||
__version__ = "0.9.0-pre6"
|
||||
|
||||
@ -10,29 +10,33 @@
|
||||
</section>
|
||||
<section class="pf-c-page__main-section">
|
||||
<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-main">
|
||||
<i class="pf-icon pf-icon-applications"></i> {% trans 'Applications' %}
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</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-main">
|
||||
<i class="pf-icon pf-icon-middleware"></i> {% trans 'Sources' %}
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</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-main">
|
||||
<i class="pf-icon pf-icon-plugged"></i> {% trans 'Providers' %}
|
||||
@ -40,15 +44,19 @@
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
{% 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>
|
||||
{% 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 %}
|
||||
</div>
|
||||
</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-main">
|
||||
<i class="pf-icon pf-icon-plugged"></i> {% trans 'Stages' %}
|
||||
@ -56,26 +64,32 @@
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
{% if stage_count < 1 %}
|
||||
<p class="aggregate-status">
|
||||
<i class="pficon-error-circle-o"></i> {{ stage_count }}
|
||||
</p>
|
||||
<p>{% trans 'No Stages configured. No Users will be able to login.' %}"></p>
|
||||
{% 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 %}
|
||||
</div>
|
||||
</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-main">
|
||||
<i class="pf-icon pf-icon-topology"></i> {% trans 'Flows' %}
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</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-main">
|
||||
<i class="pf-icon pf-icon-infrastructure"></i> {% trans 'Policies' %}
|
||||
@ -83,58 +97,71 @@
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
{% 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>
|
||||
{% 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 %}
|
||||
</div>
|
||||
</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-main">
|
||||
<i class="pf-icon pf-icon-migration"></i> {% trans 'Invitation' %}
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</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-main">
|
||||
<i class="pf-icon pf-icon-user"></i> {% trans 'Users' %}
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</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-main">
|
||||
<i class="pf-icon pf-icon-bundle"></i> {% trans 'Version' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<p class="aggregate-status">
|
||||
{% if version >= version_latest %}
|
||||
<i class="pf-icon pf-icon-ok"></i>
|
||||
{% blocktrans with version=version %}
|
||||
{{ version }} (Up-to-date!)
|
||||
<i class="fa fa-check-circle"></i> {{ version }}
|
||||
{% else %}
|
||||
<i class="fa fa-exclamation-triangle"></i> {{ version }}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if version >= version_latest %}
|
||||
{% blocktrans %}
|
||||
Up-to-date!
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
<i class="pf-icon pf-icon-warning-triangle"></i>
|
||||
{% blocktrans with version=version latest=version_latest %}
|
||||
{{ version }} ({{ latest }} is available!)
|
||||
{% blocktrans with latest=version_latest %}
|
||||
{{ latest }} is available!
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</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-main">
|
||||
<i class="pf-icon pf-icon-server"></i> {% trans 'Workers' %}
|
||||
@ -142,15 +169,19 @@
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
{% 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>
|
||||
{% 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 %}
|
||||
</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-main">
|
||||
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Policies' %}
|
||||
@ -158,13 +189,37 @@
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
{% 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>
|
||||
{% 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 %}
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
<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">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="pf-c-modal-box__header">
|
||||
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Cache' %}?</h1>
|
||||
</div>
|
||||
<div class="pf-c-modal-box__body" id="modal-description">
|
||||
<form method="post" id="clearForm">
|
||||
{% csrf_token %}
|
||||
|
||||
@ -69,12 +69,11 @@
|
||||
<div class="pf-c-empty-state__content">
|
||||
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
|
||||
<h1 class="pf-c-title pf-m-lg">
|
||||
{% trans 'No Applications.' %}
|
||||
{% trans 'No Tokens.' %}
|
||||
</h1>
|
||||
<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>
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block action %}
|
||||
{% blocktrans with type=form|form_verbose_name|title %}
|
||||
{% blocktrans with type=form|form_verbose_name %}
|
||||
Update {{ type }}
|
||||
{% endblocktrans %}
|
||||
{% endblock %}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
"""passbook administration overview"""
|
||||
from functools import lru_cache
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.shortcuts import redirect, reverse
|
||||
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.stages.invitation.models import Invitation
|
||||
|
||||
VERSION_CACHE_KEY = "passbook_latest_version"
|
||||
|
||||
|
||||
@lru_cache
|
||||
def latest_version() -> Version:
|
||||
"""Get latest release from GitHub, cached"""
|
||||
if not cache.get(VERSION_CACHE_KEY):
|
||||
try:
|
||||
data = get(
|
||||
"https://api.github.com/repos/beryju/passbook/releases/latest"
|
||||
).json()
|
||||
tag_name = data.get("tag_name")
|
||||
return parse(tag_name.split("/")[1])
|
||||
except RequestException:
|
||||
return parse("0.0.0")
|
||||
cache.set(VERSION_CACHE_KEY, tag_name.split("/")[1], 30)
|
||||
except (RequestException, IndexError):
|
||||
cache.set(VERSION_CACHE_KEY, "0.0.0", 30)
|
||||
return parse(cache.get(VERSION_CACHE_KEY))
|
||||
|
||||
|
||||
class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
||||
@ -60,4 +61,5 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
|
||||
Policy.objects.filter(bindings__isnull=True)
|
||||
)
|
||||
kwargs["cached_policies"] = len(cache.keys("policy_*"))
|
||||
kwargs["cached_flows"] = len(cache.keys("flow_*"))
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
@ -11,10 +11,10 @@ from django.views.generic import ListView
|
||||
from guardian.mixins import PermissionListMixin, PermissionRequiredMixin
|
||||
|
||||
from passbook.admin.views.utils import DeleteMessageView
|
||||
from passbook.core.signals import invitation_created
|
||||
from passbook.lib.views import CreateAssignPermView
|
||||
from passbook.stages.invitation.forms import InvitationForm
|
||||
from passbook.stages.invitation.models import Invitation
|
||||
from passbook.stages.invitation.signals import invitation_created
|
||||
|
||||
|
||||
class InvitationListView(LoginRequiredMixin, PermissionListMixin, ListView):
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"""passbook audit signal listener"""
|
||||
from typing import Dict
|
||||
from threading import Thread
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from django.contrib.auth.signals import (
|
||||
user_logged_in,
|
||||
@ -11,21 +12,54 @@ from django.http import HttpRequest
|
||||
|
||||
from passbook.audit.models import Event, EventAction
|
||||
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)
|
||||
# pylint: disable=unused-argument
|
||||
def on_user_logged_in(sender, request: HttpRequest, user: User, **_):
|
||||
"""Log successful login"""
|
||||
Event.new(EventAction.LOGIN).from_http(request)
|
||||
thread = EventNewThread(EventAction.LOGIN, request)
|
||||
thread.user = user
|
||||
thread.run()
|
||||
|
||||
|
||||
@receiver(user_logged_out)
|
||||
# pylint: disable=unused-argument
|
||||
def on_user_logged_out(sender, request: HttpRequest, user: User, **_):
|
||||
"""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)
|
||||
@ -34,29 +68,25 @@ def on_user_login_failed(
|
||||
sender, credentials: Dict[str, str], request: HttpRequest, **_
|
||||
):
|
||||
"""Failed Login"""
|
||||
Event.new(EventAction.LOGIN_FAILED, **credentials).from_http(request)
|
||||
|
||||
|
||||
@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)
|
||||
thread = EventNewThread(EventAction.LOGIN_FAILED, request, **credentials)
|
||||
thread.run()
|
||||
|
||||
|
||||
@receiver(invitation_created)
|
||||
# pylint: disable=unused-argument
|
||||
def on_invitation_created(sender, request: HttpRequest, invitation, **_):
|
||||
def on_invitation_created(sender, request: HttpRequest, invitation: Invitation, **_):
|
||||
"""Log Invitation creation"""
|
||||
Event.new(
|
||||
EventAction.INVITE_CREATED, invitation_uuid=invitation.uuid.hex
|
||||
).from_http(request)
|
||||
thread = EventNewThread(
|
||||
EventAction.INVITE_CREATED, request, invitation_uuid=invitation.invite_uuid.hex
|
||||
)
|
||||
thread.run()
|
||||
|
||||
|
||||
@receiver(invitation_used)
|
||||
# pylint: disable=unused-argument
|
||||
def on_invitation_used(sender, request: HttpRequest, invitation, **_):
|
||||
def on_invitation_used(sender, request: HttpRequest, invitation: Invitation, **_):
|
||||
"""Log Invitation usage"""
|
||||
Event.new(EventAction.INVITE_USED, invitation_uuid=invitation.uuid.hex).from_http(
|
||||
request
|
||||
thread = EventNewThread(
|
||||
EventAction.INVITE_USED, request, invitation_uuid=invitation.invite_uuid.hex
|
||||
)
|
||||
thread.run()
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
"""passbook core signals"""
|
||||
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"])
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
<ul class="pf-c-nav__list">
|
||||
{% for source in user_sources_loc %}
|
||||
<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 %}">
|
||||
{{ source.name }}
|
||||
</a>
|
||||
|
||||
@ -56,7 +56,9 @@ class CertificateKeyPair(CreatedUpdatedModel):
|
||||
@property
|
||||
def fingerprint(self) -> str:
|
||||
"""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:
|
||||
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.http import HttpRequest
|
||||
from elasticapm import capture_span
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import User
|
||||
@ -88,6 +89,7 @@ class FlowPlanner:
|
||||
self.allow_empty_flows = False
|
||||
self.flow = flow
|
||||
|
||||
@capture_span(name="FlowPlanner", span_type="flow.planner.plan")
|
||||
def plan(
|
||||
self, request: HttpRequest, default_context: Optional[Dict[str, Any]] = None
|
||||
) -> FlowPlan:
|
||||
@ -127,6 +129,7 @@ class FlowPlanner:
|
||||
raise EmptyFlowException()
|
||||
return plan
|
||||
|
||||
@capture_span(name="FlowPlanner", span_type="flow.planner.build_plan")
|
||||
def _build_plan(
|
||||
self,
|
||||
user: User,
|
||||
|
||||
@ -4,6 +4,7 @@ from textwrap import indent
|
||||
from typing import Any, Dict, Iterable, Optional
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from elasticapm import capture_span
|
||||
from requests import Session
|
||||
from structlog import get_logger
|
||||
|
||||
@ -68,6 +69,7 @@ class BaseEvaluator:
|
||||
full_expression += f"\nresult = handler({handler_signature})"
|
||||
return full_expression
|
||||
|
||||
@capture_span(name="BaseEvaluator", span_type="lib.evaluator.evaluate")
|
||||
def evaluate(self, expression_source: str) -> Any:
|
||||
"""Parse and evaluate expression. If the syntax is incorrect, a SyntaxError is raised.
|
||||
If any exception is raised during execution, it is raised.
|
||||
|
||||
@ -35,7 +35,7 @@ def before_send(event, hint):
|
||||
SentryIgnoredException,
|
||||
)
|
||||
if "exc_info" in hint:
|
||||
_exc_type, exc_value, _ = hint["exc_info"]
|
||||
_, exc_value, _ = hint["exc_info"]
|
||||
if isinstance(exc_value, ignored_classes):
|
||||
LOGGER.info("Supressing error %r", exc_value)
|
||||
return None
|
||||
|
||||
@ -23,4 +23,4 @@ def get_client_ip(request: Optional[HttpRequest]) -> Optional[str]:
|
||||
Returns none if no IP Could be found"""
|
||||
if request:
|
||||
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.http import HttpRequest
|
||||
from elasticapm import capture_span
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import User
|
||||
@ -69,6 +70,7 @@ class PolicyEngine:
|
||||
if policy.__class__ == Policy:
|
||||
raise TypeError(f"Policy '{policy}' is root type")
|
||||
|
||||
@capture_span(name="PolicyEngine", span_type="policy.engine.build")
|
||||
def build(self) -> "PolicyEngine":
|
||||
"""Build task group"""
|
||||
for binding in self._iter_bindings():
|
||||
|
||||
@ -4,6 +4,7 @@ from multiprocessing.connection import Connection
|
||||
from typing import Optional
|
||||
|
||||
from django.core.cache import cache
|
||||
from elasticapm import capture_span
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.policies.exceptions import PolicyException
|
||||
@ -44,6 +45,7 @@ class PolicyProcess(Process):
|
||||
if connection:
|
||||
self.connection = connection
|
||||
|
||||
@capture_span(name="PolicyEngine", span_type="policy.process.execute")
|
||||
def execute(self) -> PolicyResult:
|
||||
"""Run actual policy, returns result"""
|
||||
LOGGER.debug(
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
"""passbook reputation request policy"""
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
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.types import PolicyRequest, PolicyResult
|
||||
|
||||
CACHE_KEY_IP_PREFIX = "passbook_reputation_ip_"
|
||||
CACHE_KEY_USER_PREFIX = "passbook_reputation_user_"
|
||||
|
||||
|
||||
class ReputationPolicy(Policy):
|
||||
"""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"
|
||||
|
||||
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
|
||||
if self.check_ip:
|
||||
ip_scores = IPReputation.objects.filter(
|
||||
ip=remote_ip, score__lte=self.threshold
|
||||
)
|
||||
passing = passing and ip_scores.exists()
|
||||
score = cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0)
|
||||
passing = passing and score <= self.threshold
|
||||
if self.check_username:
|
||||
user_scores = UserReputation.objects.filter(
|
||||
user=request.user, score__lte=self.threshold
|
||||
)
|
||||
passing = passing and user_scores.exists()
|
||||
score = cache.get_or_set(CACHE_KEY_USER_PREFIX + request.user.username, 0)
|
||||
passing = passing and score <= self.threshold
|
||||
return PolicyResult(passing)
|
||||
|
||||
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"""
|
||||
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.http import HttpRequest
|
||||
from structlog import get_logger
|
||||
|
||||
from passbook.core.models import User
|
||||
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()
|
||||
|
||||
|
||||
def update_score(request, username, amount):
|
||||
def update_score(request: HttpRequest, username: str, amount: int):
|
||||
"""Update score for IP and User"""
|
||||
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
|
||||
ip_score.save()
|
||||
LOGGER.debug("Updated score", amount=amount, for_ip=remote_ip)
|
||||
user = User.objects.filter(username=username)
|
||||
if not user.exists():
|
||||
return
|
||||
user_score, _ = UserReputation.objects.update_or_create(user=user.first())
|
||||
user_score.score += amount
|
||||
user_score.save()
|
||||
LOGGER.debug("Updated score", amount=amount, for_user=username)
|
||||
remote_ip = get_client_ip(request) or "255.255.255.255"
|
||||
|
||||
# We only update the cache here, as its faster than writing to the DB
|
||||
cache.get_or_set(CACHE_KEY_IP_PREFIX + remote_ip, 0)
|
||||
cache.incr(CACHE_KEY_IP_PREFIX + remote_ip, amount)
|
||||
|
||||
cache.get_or_set(CACHE_KEY_USER_PREFIX + username, 0)
|
||||
cache.incr(CACHE_KEY_USER_PREFIX + username, amount)
|
||||
|
||||
LOGGER.debug("Updated score", amount=amount, for_user=username, for_ip=remote_ip)
|
||||
|
||||
|
||||
@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
|
||||
|
||||
if isinstance(instance, Policy):
|
||||
LOGGER.debug("Invalidating policy cache", policy=instance)
|
||||
total = 0
|
||||
for binding in PolicyBinding.objects.filter(policy=instance):
|
||||
prefix = (
|
||||
@ -23,4 +22,4 @@ def invalidate_policy_cache(sender, instance, **_):
|
||||
keys = cache.keys(prefix)
|
||||
total += len(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
|
||||
|
||||
|
||||
class PolicyTestEngine(TestCase):
|
||||
class TestPolicyEngine(TestCase):
|
||||
"""PolicyEngine tests"""
|
||||
|
||||
def setUp(self):
|
||||
@ -21,7 +21,9 @@
|
||||
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="pf-c-modal-box__header">
|
||||
<h1 class="pf-c-title pf-m-2xl">{% trans 'Setup with docker-compose' %}</h1>
|
||||
</div>
|
||||
<div class="pf-c-modal-box__body">
|
||||
{% 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>
|
||||
@ -39,7 +41,9 @@
|
||||
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="pf-c-modal-box__header">
|
||||
<h1 class="pf-c-title pf-m-2xl">{% trans 'Setup with Kubernetes' %}</h1>
|
||||
</div>
|
||||
<div class="pf-c-modal-box__body">
|
||||
<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>
|
||||
|
||||
@ -7,7 +7,9 @@
|
||||
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="pf-c-modal-box__header">
|
||||
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Setup URLs' %}</h1>
|
||||
</div>
|
||||
<div class="pf-c-modal-box__body" id="modal-description">
|
||||
<form class="pf-c-form">
|
||||
<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">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="pf-c-modal-box__header">
|
||||
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Setup URLs' %}</h1>
|
||||
</div>
|
||||
<div class="pf-c-modal-box__body" id="modal-description">
|
||||
<form class="pf-c-form">
|
||||
<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">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="pf-c-modal-box__header">
|
||||
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Metadata' %}</h1>
|
||||
</div>
|
||||
<div class="pf-c-modal-box__body" id="modal-description">
|
||||
<form method="post">
|
||||
<textarea class="codemirror" readonly data-cm-mode="xml">{{ metadata }}</textarea>
|
||||
|
||||
@ -131,7 +131,10 @@ REST_FRAMEWORK = {
|
||||
"rest_framework.filters.OrderingFilter",
|
||||
"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": (
|
||||
"passbook.api.auth.PassbookTokenAuthentication",
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
@ -278,6 +281,19 @@ if not DEBUG and _ERROR_REPORTING:
|
||||
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)
|
||||
# https://docs.djangoproject.com/en/2.1/howto/static-files/
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ AUTHENTICATION_BACKENDS = [
|
||||
]
|
||||
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"sync": {
|
||||
"sources_ldap_sync": {
|
||||
"task": "passbook.sources.ldap.tasks.sync",
|
||||
"schedule": crontab(minute=0), # Run every hour
|
||||
}
|
||||
|
||||
@ -1,22 +1,29 @@
|
||||
{% extends "user/base.html" %}
|
||||
|
||||
{% load passbook_utils %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page %}
|
||||
<div class="pf-c-card">
|
||||
<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 class="pf-c-card__body">
|
||||
{% if connections.exists %}
|
||||
<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' %}
|
||||
</a>
|
||||
{% else %}
|
||||
<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' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% 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.stages.invitation.models import Invitation, InvitationStage
|
||||
from passbook.stages.invitation.signals import invitation_used
|
||||
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||
|
||||
INVITATION_TOKEN_KEY = "token"
|
||||
@ -25,4 +26,5 @@ class InvitationStageView(StageView):
|
||||
invite: Invitation = get_object_or_404(Invitation, pk=token)
|
||||
self.executor.plan.context[PLAN_CONTEXT_PROMPT] = invite.fixed_data
|
||||
self.executor.plan.context[INVITATION_IN_EFFECT] = True
|
||||
invitation_used.send(sender=self, request=request, invitation=invite)
|
||||
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.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||
from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||
from passbook.stages.user_write.signals import user_write
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -49,6 +50,7 @@ class UserWriteStageView(StageView):
|
||||
else:
|
||||
user.attributes[key] = value
|
||||
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
|
||||
if any(["password" in x for x in data.keys()]):
|
||||
update_session_auth_hash(self.request, user)
|
||||
|
||||
@ -241,3 +241,9 @@ input[data-is-monospace] {
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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