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 %}
 | 
			
		||||
                <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 %}
 | 
			
		||||
 | 
			
		||||
@ -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"""
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
            <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>
 | 
			
		||||
 | 
			
		||||
@ -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">
 | 
			
		||||
 | 
			
		||||
@ -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">
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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__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 %}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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