Compare commits

..

29 Commits

Author SHA1 Message Date
0b250b897e new release: 0.9.0-pre6 2020-07-07 19:14:29 +02:00
c6880a0f16 Merge pull request #117 from BeryJu/apm
Support for Elastic APM
2020-07-07 18:48:40 +02:00
beb5ffcbdd ci: fix gatekeeper dockerfile path 2020-07-07 18:48:24 +02:00
0715cac39b root: remove psutil as we have external monitoring for CPU 2020-07-07 18:24:24 +02:00
41117d873d ci: fix gatekeeper building the wrong image 2020-07-07 18:23:15 +02:00
231e448b1a lib/eval: fix import order 2020-07-07 18:05:38 +02:00
b3b8cd807d root: expose APM settings in helm chart 2020-07-07 17:54:07 +02:00
9021bbd5de root: implement APM support 2020-07-07 17:43:10 +02:00
169475ab39 crypto: add colon seperator for fingerprint 2020-07-07 17:05:31 +02:00
c00e01626e sources/ldap: adjust task schedule name 2020-07-07 17:04:07 +02:00
05d4a9ef62 policies/reputation: rewrite to save score into cache and save into DB via worker 2020-07-07 17:03:57 +02:00
17a2ac73e7 stages/user_write: add signals 2020-07-07 15:49:02 +02:00
6bc6f947dd stages/invitation: move invite signals from core to app 2020-07-07 15:46:13 +02:00
b048a1fb4f ci: notify sentry of new releases 2020-07-07 14:09:28 +02:00
363940ee8d root: fix API requests erroring 2020-07-07 14:02:20 +02:00
a64e53479c Merge pull request #115 from BeryJu/dependabot/pip/boto3-1.14.17
build(deps): bump boto3 from 1.14.16 to 1.14.17
2020-07-07 13:34:53 +02:00
14fdbe7720 Merge pull request #116 from BeryJu/dependabot/pip/coverage-5.2
build(deps-dev): bump coverage from 5.1 to 5.2
2020-07-07 13:34:41 +02:00
f56332c954 Merge branch 'master' into dependabot/pip/boto3-1.14.17 2020-07-07 13:14:07 +02:00
21c53c748f Merge branch 'master' into dependabot/pip/coverage-5.2 2020-07-07 13:13:55 +02:00
b12182c1d1 admin: improve overview layout 2020-07-07 13:13:15 +02:00
d8f27f595a admin: use django cache for admin version (expiry) 2020-07-07 13:12:54 +02:00
b25dc2aaa3 build(deps-dev): bump coverage from 5.1 to 5.2
Bumps [coverage](https://github.com/nedbat/coveragepy) from 5.1 to 5.2.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/coverage-5.1...coverage-5.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-07 05:24:51 +00:00
3ec3849e72 build(deps): bump boto3 from 1.14.16 to 1.14.17
Bumps [boto3](https://github.com/boto/boto3) from 1.14.16 to 1.14.17.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.14.16...1.14.17)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-07-07 05:23:18 +00:00
2dc1b65718 ui: fix modal layout 2020-07-06 20:50:14 +02:00
af22f507f4 sources/oauth: fix template for user settings 2020-07-06 17:48:53 +02:00
9958019bf3 core: fix user's sidebar links for sources 2020-07-06 17:46:41 +02:00
02d65972cb admin: fix submit button on update form 2020-07-06 17:46:30 +02:00
24ad893350 admin: fix token_list template 2020-07-06 17:43:20 +02:00
9c5792b1e1 docs: migrate TOTP and Static OTP devices 2020-07-06 17:42:46 +02:00
46 changed files with 507 additions and 180 deletions

View File

@ -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>.*)

View File

@ -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

View File

@ -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
View File

@ -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": [

View File

@ -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:

View File

@ -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.

View File

@ -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
```

View File

@ -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

View File

@ -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 }}"

View File

@ -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.

View File

@ -1,2 +1,2 @@
"""passbook"""
__version__ = "0.9.0-pre5"
__version__ = "0.9.0-pre6"

View File

@ -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 %}
<i class="pficon-error-circle-o"></i> {{ stage_count }}
<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="fa fa-check-circle"></i> {{ version }}
{% else %}
<i class="fa fa-exclamation-triangle"></i> {{ version }}
{% endif %}
</p>
{% if version >= version_latest %}
<i class="pf-icon pf-icon-ok"></i>
{% blocktrans with version=version %}
{{ version }} (Up-to-date!)
{% 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>
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Cache' %}?</h1>
<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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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"""
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")
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")
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)

View File

@ -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):

View File

@ -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()

View File

@ -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"])

View File

@ -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>

View File

@ -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}"

View File

@ -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,

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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():

View File

@ -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(

View File

@ -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:

View 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"),
},
}

View File

@ -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)

View 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"])

View 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)

View File

@ -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)

View File

@ -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):

View File

@ -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>
<h1 class="pf-c-title pf-m-2xl">{% trans 'Setup with docker-compose' %}</h1>
<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>
<h1 class="pf-c-title pf-m-2xl">{% trans 'Setup with Kubernetes' %}</h1>
<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>

View File

@ -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>
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Setup URLs' %}</h1>
<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">

View File

@ -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>
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Setup URLs' %}</h1>
<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">

View File

@ -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>
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Metadata' %}</h1>
<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>

View File

@ -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/

View File

@ -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
}

View File

@ -1,22 +1,29 @@
{% extends "user/base.html" %}
{% load passbook_utils %}
{% load i18n %}
{% block page %}
<div class="pf-c-card__header pf-c-title pf-m-md">
<h1>{{ source.name }}</h1>
</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 %}">
{% 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 %}">
{% trans 'Connect' %}
</a>
{% endif %}
<div class="pf-c-card">
<div class="pf-c-card__header pf-c-title pf-m-md">
{% 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 %}">
{% 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 %}">
{% trans 'Connect' %}
</a>
{% endif %}
</div>
</div>
{% endblock %}

View 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"])

View File

@ -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()

View File

@ -0,0 +1,4 @@
"""passbook user_write signals"""
from django.core.signals import Signal
user_write = Signal(providing_args=["request", "user", "data"])

View File

@ -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)

View File

@ -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;
}