Compare commits
	
		
			27 Commits
		
	
	
		
			version/0.
			...
			version/0.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 04a5428148 | |||
| 73b173b92a | |||
| 7cbf20a71c | |||
| 7a98e6d92b | |||
| 49e915f98b | |||
| 3aa2f1e892 | |||
| bc4b7ef44d | |||
| 9400b01a55 | |||
| e57da71dcf | |||
| 7268afaaf9 | |||
| 205183445c | |||
| a08bdfdbcd | |||
| e6c47fee26 | |||
| a5629c5155 | |||
| 41689fe3ce | |||
| 8e84208e2c | |||
| 32a48fa07a | |||
| 773a9c0692 | |||
| 8808e3afe0 | |||
| ecea85f8ca | |||
| 5dfa141e35 | |||
| 447e81d0b8 | |||
| e138076e1d | |||
| 721d133dc3 | |||
| 75b687ecbe | |||
| bdd1863177 | |||
| e5b85e8e6a | 
@ -1,5 +1,5 @@
 | 
			
		||||
[bumpversion]
 | 
			
		||||
current_version = 0.7.13-beta
 | 
			
		||||
current_version = 0.7.16-beta
 | 
			
		||||
tag = True
 | 
			
		||||
commit = True
 | 
			
		||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@ -56,7 +56,7 @@ jobs:
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            ${{ runner.os }}-pipenv-
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: pip install -U pip pipenv && pipenv install --dev
 | 
			
		||||
        run: pip install -U pip pipenv && pipenv install --dev && pipenv install --dev prospector --skip-lock
 | 
			
		||||
      - name: Lint with prospector
 | 
			
		||||
        run: pipenv run prospector
 | 
			
		||||
  bandit:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@ -16,11 +16,11 @@ jobs:
 | 
			
		||||
      - name: Building Docker Image
 | 
			
		||||
        run: docker build
 | 
			
		||||
          --no-cache
 | 
			
		||||
          -t beryju/passbook:0.7.13-beta
 | 
			
		||||
          -t beryju/passbook:0.7.16-beta
 | 
			
		||||
          -t beryju/passbook:latest
 | 
			
		||||
          -f Dockerfile .
 | 
			
		||||
      - name: Push Docker Container to Registry (versioned)
 | 
			
		||||
        run: docker push beryju/passbook:0.7.13-beta
 | 
			
		||||
        run: docker push beryju/passbook:0.7.16-beta
 | 
			
		||||
      - 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.7.13-beta \
 | 
			
		||||
          -t beryju/passbook-gatekeeper:0.7.16-beta \
 | 
			
		||||
          -t beryju/passbook-gatekeeper:latest \
 | 
			
		||||
          -f Dockerfile .
 | 
			
		||||
      - name: Push Docker Container to Registry (versioned)
 | 
			
		||||
        run: docker push beryju/passbook-gatekeeper:0.7.13-beta
 | 
			
		||||
        run: docker push beryju/passbook-gatekeeper:0.7.16-beta
 | 
			
		||||
      - 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.7.13-beta
 | 
			
		||||
          -t beryju/passbook-static:0.7.16-beta
 | 
			
		||||
          -t beryju/passbook-static:latest
 | 
			
		||||
          -f static.Dockerfile .
 | 
			
		||||
      - name: Push Docker Container to Registry (versioned)
 | 
			
		||||
        run: docker push beryju/passbook-static:0.7.13-beta
 | 
			
		||||
        run: docker push beryju/passbook-static:0.7.16-beta
 | 
			
		||||
      - name: Push Docker Container to Registry (latest)
 | 
			
		||||
        run: docker push beryju/passbook-static:latest
 | 
			
		||||
  test-release:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Pipfile
									
									
									
									
									
								
							@ -40,6 +40,7 @@ signxml = "*"
 | 
			
		||||
structlog = "*"
 | 
			
		||||
swagger-spec-validator = "*"
 | 
			
		||||
urllib3 = {extras = ["secure"],version = "*"}
 | 
			
		||||
jinja2 = "*"
 | 
			
		||||
 | 
			
		||||
[requires]
 | 
			
		||||
python_version = "3.8"
 | 
			
		||||
@ -51,7 +52,6 @@ bumpversion = "*"
 | 
			
		||||
colorama = "*"
 | 
			
		||||
coverage = "*"
 | 
			
		||||
django-debug-toolbar = "*"
 | 
			
		||||
prospector = "*"
 | 
			
		||||
pylint = "*"
 | 
			
		||||
pylint-django = "*"
 | 
			
		||||
unittest-xml-reporting = "*"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										324
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										324
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
    "_meta": {
 | 
			
		||||
        "hash": {
 | 
			
		||||
            "sha256": "3693ac70f66ada5c8c8784e6e2d5c2f0ee75219e7141dde1075347234366e314"
 | 
			
		||||
            "sha256": "c9232d99b062ee14eda43d176481f16da78ce53f912c844db3f33f1b3e8a47b7"
 | 
			
		||||
        },
 | 
			
		||||
        "pipfile-spec": 6,
 | 
			
		||||
        "requires": {
 | 
			
		||||
@ -23,6 +23,13 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.5.2"
 | 
			
		||||
        },
 | 
			
		||||
        "asgiref": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
 | 
			
		||||
                "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.2.3"
 | 
			
		||||
        },
 | 
			
		||||
        "asn1crypto": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:5a215cb8dc12f892244e3a113fe05397ee23c5c4ca7a69cd6e69811755efc42d",
 | 
			
		||||
@ -46,18 +53,18 @@
 | 
			
		||||
        },
 | 
			
		||||
        "boto3": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:7aaccacc199cd633b5ac14f0d544c9d592c53275a79cf4d230a9f66215331665",
 | 
			
		||||
                "sha256:ca36aabfa5e7fa9ee8f84f82c3faffb4ffe078923f935f572f70dc49154ef6a0"
 | 
			
		||||
                "sha256:33462a79d57c9c4a215e075472509537d03545f54566fc4f776fb0f4cfa616f6",
 | 
			
		||||
                "sha256:34f9a04f529dc849f0e427782d6f3c6b62f7fb734d8f4859b17e5dee0855323e"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.11.5"
 | 
			
		||||
            "version": "==1.12.0"
 | 
			
		||||
        },
 | 
			
		||||
        "botocore": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:2538065f9f5023eae3607e78fc5d8c595c9173ec02bc92410652078617c44ac1",
 | 
			
		||||
                "sha256:554231b1690c8521e05a41e50184e43d62941fdf9351e658aea894649b879985"
 | 
			
		||||
                "sha256:055da4826f6c9158e4a61549d57a2ce449c27d44ce34ab4c96c7bb7b5c993efc",
 | 
			
		||||
                "sha256:1f7cecfcd38c7cac17b5386014eb04626d1c7559ee8d8ec1526058cd23f6d1d4"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.14.15"
 | 
			
		||||
            "version": "==1.15.0"
 | 
			
		||||
        },
 | 
			
		||||
        "celery": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -164,11 +171,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a",
 | 
			
		||||
                "sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038"
 | 
			
		||||
                "sha256:2f1ba1db8648484dd5c238fb62504777b7ad090c81c5f1fd8d5eb5ec21b5f283",
 | 
			
		||||
                "sha256:c91c91a7ad6ef67a874a4f76f58ba534f9208412692a840e1d125eb5c279cb0a"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.2.10"
 | 
			
		||||
            "version": "==3.0.3"
 | 
			
		||||
        },
 | 
			
		||||
        "django-cors-middleware": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -195,11 +202,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django-guardian": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:8cf4efd67a863eb32beafd4335a38ffb083630f8ab2045212d27f8f9c3abe5a6",
 | 
			
		||||
                "sha256:e638c9a23eeac534bb68b133975539ed8782f733ab6f35c0b23b4c39cd06b1bb"
 | 
			
		||||
                "sha256:8cacf49ebcc1e545f0a8997971eec0fe109f5ed31fc2a569a7bf5615453696e2",
 | 
			
		||||
                "sha256:ac81e88372fdf1795d84ba065550e739b42e9c6d07cdf201cf5bbf9efa7f396c"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.1.0"
 | 
			
		||||
            "version": "==2.2.0"
 | 
			
		||||
        },
 | 
			
		||||
        "django-model-utils": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -225,27 +232,26 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django-otp": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1f16c2b93fe484706ff16ac6f5e64ecc73dd240318c333e0560384ba548d3837",
 | 
			
		||||
                "sha256:cd4975539be478417033561e9832a1a69a583189f680e92a649f412c661f90aa"
 | 
			
		||||
                "sha256:523a87f8d0c52ce9a9f0a5e248c59dab3e85cdda5bbcd106cf49138a8f3f3209",
 | 
			
		||||
                "sha256:ad3206e4a6f461c8968a2366a7cccfb340b93294e604b278aa9ab8b26f1017a1"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.7.5"
 | 
			
		||||
            "version": "==0.8.1"
 | 
			
		||||
        },
 | 
			
		||||
        "django-prometheus": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:f0657d4b887309086b71b55f6aa4a95f967b35fe115128b501f95422c423b12c",
 | 
			
		||||
                "sha256:f645016ae5270ac2025a70788cd2bd636244a0c5705b323cc086994bf828181e"
 | 
			
		||||
                "sha256:362ea45e5ee26bdba85ce978aeb370659ca6bbc0d6bac69868a055179e053bd1",
 | 
			
		||||
                "sha256:facaa677386899303ea26c45552371cc43f476e42a81c081011a49cb5564af0b"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.0.0.dev124"
 | 
			
		||||
            "version": "==2.1.0.dev5"
 | 
			
		||||
        },
 | 
			
		||||
        "django-recaptcha": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3b19b9d972ca802b683eed5fd51ed84b0798f2a52f8d057a912eb7d99cff3779",
 | 
			
		||||
                "sha256:b6ce959cd7c0af7501698fab5f52ca1bcb6d9402cb3b8b42a4c4cb13d89cfbfa"
 | 
			
		||||
                "sha256:567784963fd5400feaf92e8951d8dbbbdb4b4c48a76e225d4baa63a2c9d2cd8c"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.0.5"
 | 
			
		||||
            "version": "==2.0.6"
 | 
			
		||||
        },
 | 
			
		||||
        "django-redis": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -264,11 +270,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django-storages": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0a9b7e620e969fb0797523695329ed223bf540bbfdf6cd163b061fc11dab2d1c",
 | 
			
		||||
                "sha256:9322ab74ba6371e2e0fccc350c741686ade829e43085597b26b07ae8955a0a00"
 | 
			
		||||
                "sha256:3103991c2ee8cef8a2ff096709973ffe7106183d211a79f22cf855f33533d924",
 | 
			
		||||
                "sha256:a59e9923cbce7068792f75344ed7727021ee4ac20f227cf17297d0d03d141e91"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.8"
 | 
			
		||||
            "version": "==1.9.1"
 | 
			
		||||
        },
 | 
			
		||||
        "djangorestframework": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -295,11 +301,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "drf-yasg": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4cfec631880ae527a91ec7cd3241aea2f82189f59e2f089119aa687761afb227",
 | 
			
		||||
                "sha256:504cce09035cf1bace63b84d9d778b772f86bb37d8a71ed6f723346362e633b2"
 | 
			
		||||
                "sha256:5572e9d5baab9f6b49318169df9789f7399d0e3c7bdac8fdb8dfccf1d5d2b1ca",
 | 
			
		||||
                "sha256:7d7af27ad16e18507e9392b2afd6b218fbffc432ec8dbea053099a2241e184ff"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.17.0"
 | 
			
		||||
            "version": "==1.17.1"
 | 
			
		||||
        },
 | 
			
		||||
        "eight": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -338,6 +344,7 @@
 | 
			
		||||
                "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484",
 | 
			
		||||
                "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==3.0.0a1"
 | 
			
		||||
        },
 | 
			
		||||
        "jmespath": {
 | 
			
		||||
@ -372,35 +379,36 @@
 | 
			
		||||
        },
 | 
			
		||||
        "lxml": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:00ac0d64949fef6b3693813fe636a2d56d97a5a49b5bbb86e4cc4cc50ebc9ea2",
 | 
			
		||||
                "sha256:0571e607558665ed42e450d7bf0e2941d542c18e117b1ebbf0ba72f287ad841c",
 | 
			
		||||
                "sha256:0e3f04a7615fdac0be5e18b2406529521d6dbdb0167d2a690ee328bef7807487",
 | 
			
		||||
                "sha256:13cf89be53348d1c17b453867da68704802966c433b2bb4fa1f970daadd2ef70",
 | 
			
		||||
                "sha256:217262fcf6a4c2e1c7cb1efa08bd9ebc432502abc6c255c4abab611e8be0d14d",
 | 
			
		||||
                "sha256:223e544828f1955daaf4cefbb4853bc416b2ec3fd56d4f4204a8b17007c21250",
 | 
			
		||||
                "sha256:277cb61fede2f95b9c61912fefb3d43fbd5f18bf18a14fae4911b67984486f5d",
 | 
			
		||||
                "sha256:3213f753e8ae86c396e0e066866e64c6b04618e85c723b32ecb0909885211f74",
 | 
			
		||||
                "sha256:4690984a4dee1033da0af6df0b7a6bde83f74e1c0c870623797cec77964de34d",
 | 
			
		||||
                "sha256:4fcc472ef87f45c429d3b923b925704aa581f875d65bac80f8ab0c3296a63f78",
 | 
			
		||||
                "sha256:61409bd745a265a742f2693e4600e4dbd45cc1daebe1d5fad6fcb22912d44145",
 | 
			
		||||
                "sha256:678f1963f755c5d9f5f6968dded7b245dd1ece8cf53c1aa9d80e6734a8c7f41d",
 | 
			
		||||
                "sha256:6c6d03549d4e2734133badb9ab1c05d9f0ef4bcd31d83e5d2b4747c85cfa21da",
 | 
			
		||||
                "sha256:6e74d5f4d6ecd6942375c52ffcd35f4318a61a02328f6f1bd79fcb4ffedf969e",
 | 
			
		||||
                "sha256:7b4fc7b1ecc987ca7aaf3f4f0e71bbfbd81aaabf87002558f5bc95da3a865bcd",
 | 
			
		||||
                "sha256:7ed386a40e172ddf44c061ad74881d8622f791d9af0b6f5be20023029129bc85",
 | 
			
		||||
                "sha256:8f54f0924d12c47a382c600c880770b5ebfc96c9fd94cf6f6bdc21caf6163ea7",
 | 
			
		||||
                "sha256:ad9b81351fdc236bda538efa6879315448411a81186c836d4b80d6ca8217cdb9",
 | 
			
		||||
                "sha256:bbd00e21ea17f7bcc58dccd13869d68441b32899e89cf6cfa90d624a9198ce85",
 | 
			
		||||
                "sha256:c3c289762cc09735e2a8f8a49571d0e8b4f57ea831ea11558247b5bdea0ac4db",
 | 
			
		||||
                "sha256:cf4650942de5e5685ad308e22bcafbccfe37c54aa7c0e30cd620c2ee5c93d336",
 | 
			
		||||
                "sha256:cfcbc33c9c59c93776aa41ab02e55c288a042211708b72fdb518221cc803abc8",
 | 
			
		||||
                "sha256:e301055deadfedbd80cf94f2f65ff23126b232b0d1fea28f332ce58137bcdb18",
 | 
			
		||||
                "sha256:ebbfe24df7f7b5c6c7620702496b6419f6a9aa2fd7f005eb731cc80d7b4692b9",
 | 
			
		||||
                "sha256:eff69ddbf3ad86375c344339371168640951c302450c5d3e9936e98d6459db06",
 | 
			
		||||
                "sha256:f6ed60a62c5f1c44e789d2cf14009423cb1646b44a43e40a9cf6a21f077678a1"
 | 
			
		||||
                "sha256:06d4e0bbb1d62e38ae6118406d7cdb4693a3fa34ee3762238bcb96c9e36a93cd",
 | 
			
		||||
                "sha256:0701f7965903a1c3f6f09328c1278ac0eee8f56f244e66af79cb224b7ef3801c",
 | 
			
		||||
                "sha256:1f2c4ec372bf1c4a2c7e4bb20845e8bcf8050365189d86806bad1e3ae473d081",
 | 
			
		||||
                "sha256:4235bc124fdcf611d02047d7034164897ade13046bda967768836629bc62784f",
 | 
			
		||||
                "sha256:5828c7f3e615f3975d48f40d4fe66e8a7b25f16b5e5705ffe1d22e43fb1f6261",
 | 
			
		||||
                "sha256:585c0869f75577ac7a8ff38d08f7aac9033da2c41c11352ebf86a04652758b7a",
 | 
			
		||||
                "sha256:5d467ce9c5d35b3bcc7172c06320dddb275fea6ac2037f72f0a4d7472035cea9",
 | 
			
		||||
                "sha256:63dbc21efd7e822c11d5ddbedbbb08cd11a41e0032e382a0fd59b0b08e405a3a",
 | 
			
		||||
                "sha256:7bc1b221e7867f2e7ff1933165c0cec7153dce93d0cdba6554b42a8beb687bdb",
 | 
			
		||||
                "sha256:8620ce80f50d023d414183bf90cc2576c2837b88e00bea3f33ad2630133bbb60",
 | 
			
		||||
                "sha256:8a0ebda56ebca1a83eb2d1ac266649b80af8dd4b4a3502b2c1e09ac2f88fe128",
 | 
			
		||||
                "sha256:90ed0e36455a81b25b7034038e40880189169c308a3df360861ad74da7b68c1a",
 | 
			
		||||
                "sha256:95e67224815ef86924fbc2b71a9dbd1f7262384bca4bc4793645794ac4200717",
 | 
			
		||||
                "sha256:afdb34b715daf814d1abea0317b6d672476b498472f1e5aacbadc34ebbc26e89",
 | 
			
		||||
                "sha256:b4b2c63cc7963aedd08a5f5a454c9f67251b1ac9e22fd9d72836206c42dc2a72",
 | 
			
		||||
                "sha256:d068f55bda3c2c3fcaec24bd083d9e2eede32c583faf084d6e4b9daaea77dde8",
 | 
			
		||||
                "sha256:d5b3c4b7edd2e770375a01139be11307f04341ec709cf724e0f26ebb1eef12c3",
 | 
			
		||||
                "sha256:deadf4df349d1dcd7b2853a2c8796593cc346600726eff680ed8ed11812382a7",
 | 
			
		||||
                "sha256:df533af6f88080419c5a604d0d63b2c33b1c0c4409aba7d0cb6de305147ea8c8",
 | 
			
		||||
                "sha256:e4aa948eb15018a657702fee0b9db47e908491c64d36b4a90f59a64741516e77",
 | 
			
		||||
                "sha256:e5d842c73e4ef6ed8c1bd77806bf84a7cb535f9c0cf9b2c74d02ebda310070e1",
 | 
			
		||||
                "sha256:ebec08091a22c2be870890913bdadd86fcd8e9f0f22bcb398abd3af914690c15",
 | 
			
		||||
                "sha256:edc15fcfd77395e24543be48871c251f38132bb834d9fdfdad756adb6ea37679",
 | 
			
		||||
                "sha256:f2b74784ed7e0bc2d02bd53e48ad6ba523c9b36c194260b7a5045071abbb1012",
 | 
			
		||||
                "sha256:fa071559f14bd1e92077b1b5f6c22cf09756c6de7139370249eb372854ce51e6",
 | 
			
		||||
                "sha256:fd52e796fee7171c4361d441796b64df1acfceb51f29e545e812f16d023c4bbc",
 | 
			
		||||
                "sha256:fe976a0f1ef09b3638778024ab9fb8cde3118f203364212c198f71341c0715ca"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==4.4.2"
 | 
			
		||||
            "version": "==4.5.0"
 | 
			
		||||
        },
 | 
			
		||||
        "markupsafe": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -450,11 +458,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "packaging": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb",
 | 
			
		||||
                "sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8"
 | 
			
		||||
                "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73",
 | 
			
		||||
                "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==20.0"
 | 
			
		||||
            "version": "==20.1"
 | 
			
		||||
        },
 | 
			
		||||
        "prometheus-client": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -522,41 +530,39 @@
 | 
			
		||||
        },
 | 
			
		||||
        "pycryptodome": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:042ae873baadd0c33b4d699a5c5b976ade3233a979d972f98ca82314632d868c",
 | 
			
		||||
                "sha256:0502876279772b1384b660ccc91563d04490d562799d8e2e06b411e2d81128a9",
 | 
			
		||||
                "sha256:2de33ed0a95855735d5a0fc0c39603314df9e78ee8bbf0baa9692fb46b3b8bbb",
 | 
			
		||||
                "sha256:319e568baf86620b419d53063b18c216abf924875966efdfe06891b987196a45",
 | 
			
		||||
                "sha256:4372ec7518727172e1605c0843cdc5375d4771e447b8148c787b860260aae151",
 | 
			
		||||
                "sha256:48821950ffb9c836858d8fa09d7840b6df52eadd387a3c5acece55cb387743f9",
 | 
			
		||||
                "sha256:4b9533d4166ca07abdd49ce9d516666b1df944997fe135d4b21ac376aa624aff",
 | 
			
		||||
                "sha256:54456cf85130e01674d21fb1ab89ffccacb138a8ade88d72fa2b0ac898d2798b",
 | 
			
		||||
                "sha256:56fdd0e425f1b8fd3a00b6d96351f86226674974814c50534864d0124d48871f",
 | 
			
		||||
                "sha256:57b1b707363490c495ad0eeb38bd1b0e1697c497af25fad78d3a1ebf0477fd5b",
 | 
			
		||||
                "sha256:5c485ed6e9718ebcaa81138fa70ace9c563d202b56a8cee119b4085b023931f5",
 | 
			
		||||
                "sha256:63c103a22cbe9752f6ea9f1a0de129995bad91c4d03a66c67cffcf6ee0c9f1e1",
 | 
			
		||||
                "sha256:68fab8455efcbfe87c5d75015476f9b606227ffe244d57bfd66269451706e899",
 | 
			
		||||
                "sha256:6c2720696b10ae356040e888bde1239b8957fe18885ccf5e7b4e8dec882f0856",
 | 
			
		||||
                "sha256:72166c2ac520a5dbd2d90208b9c279161ec0861662a621892bd52fb6ca13ab91",
 | 
			
		||||
                "sha256:7c52308ac5b834331b2f107a490b2c27de024a229b61df4cdc5c131d563dfe98",
 | 
			
		||||
                "sha256:87d8d85b4792ca5e730fb7a519fbc3ed976c59dcf79c5204589c59afd56b9926",
 | 
			
		||||
                "sha256:896e9b6fd0762aa07b203c993fbbee7a1f1a4674c6886afd7bfa86f3d1be98a8",
 | 
			
		||||
                "sha256:8a799bea3c6617736e914a2e77c409f52893d382f619f088f8a80e2e21f573c1",
 | 
			
		||||
                "sha256:9d9945ac8375d5d8e60bd2a2e1df5882eaa315522eedf3ca868b1546dfa34eba",
 | 
			
		||||
                "sha256:9ef966c727de942de3e41aa8462c4b7b4bca70f19af5a3f99e31376589c11aac",
 | 
			
		||||
                "sha256:a168e73879619b467072509a223282a02c8047d932a48b74fbd498f27224aa04",
 | 
			
		||||
                "sha256:a30f501bbb32e01a49ef9e09ca1260e5ab49bf33a257080ec553e08997acc487",
 | 
			
		||||
                "sha256:a8ca2450394d3699c9f15ef25e8de9a24b401933716a1e39d37fa01f5fe3c58b",
 | 
			
		||||
                "sha256:aec4d42deb836b8fb3ba32f2ba1ef0d33dd3dc9d430b1479ee7a914490d15b5e",
 | 
			
		||||
                "sha256:b4af098f2a50f8d048ab12cabb59456585c0acf43d90ee79782d2d6d0ed59dba",
 | 
			
		||||
                "sha256:b55c60c321ac91945c60a40ac9896ac7a3d432bb3e8c14006dfd82ad5871c331",
 | 
			
		||||
                "sha256:c53348358408d94869059e16fba5ff3bef8c52c25b18421472aba272b9bb450f",
 | 
			
		||||
                "sha256:cbfd97f9e060f0d30245cd29fa267a9a84de9da97559366fca0a3f7655acc63f",
 | 
			
		||||
                "sha256:d3fe3f33ad52bf0c19ee6344b695ba44ffbfa16f3c29ca61116b48d97bd970fb",
 | 
			
		||||
                "sha256:e3a79a30d15d9c7c284a7734036ee8abdb5ca3a6f5774d293cdc9e1358c1dc10",
 | 
			
		||||
                "sha256:eec0689509389f19875f66ae8dedd59f982240cdab31b9f78a8dc266011df93a"
 | 
			
		||||
                "sha256:012ca77c2105600e3c6aef43188101ac1d95052c633a4ae8fbebffab20c25f8a",
 | 
			
		||||
                "sha256:05b4d865710f9a6378d3ada28195ff78e52642d3ecffe6fa9d379d870b9bf29d",
 | 
			
		||||
                "sha256:07daddb98f98f771ba027f8f835bdb675aeb84effe41ed5221f520b267429354",
 | 
			
		||||
                "sha256:09bf05a489fe10f9280a5e0163f195e7b9630cafb15f7d72fb9c8f5eb2afa84f",
 | 
			
		||||
                "sha256:0a8d5f2dbb4bbe830ace54286b829bfa529f0853bedaab6225fcb2e6d1f7e356",
 | 
			
		||||
                "sha256:1259b8ca49662b8a941177357f08147d858595c0042e63ff81e9628e925b5c9d",
 | 
			
		||||
                "sha256:238d8b6dd27bd1a04816a68aa90a739e6dd23b192fcd83b50f9360958bff192a",
 | 
			
		||||
                "sha256:2a57daef18a2022a5e4b6f7376c9ddd0c2d946e4b1f1e59b837f5bf295be7380",
 | 
			
		||||
                "sha256:39e5ca2f66d1eac7abcba5ce1a03370d123dc6085620f1cd532dfee27e650178",
 | 
			
		||||
                "sha256:3d516df693c195b8da3795e381429bd420e87081b7e6c2871c62c9897c812cda",
 | 
			
		||||
                "sha256:3e486c5b7228e864665fc479e9f596b2547b5fe29c6f5c8ed3807784d06faed7",
 | 
			
		||||
                "sha256:5029c46b0d41dfb763c3981c0af68eab029f06fe2b94f2299112fc18cf9e8d6d",
 | 
			
		||||
                "sha256:5817c0b3c263025d851da96b90cbc7e95348008f88b990e90d10683dba376666",
 | 
			
		||||
                "sha256:79320f1fc5c9ca682869087c565bb29ca6f334692e940d7365771e9a94382e12",
 | 
			
		||||
                "sha256:887d08beca6368d3d70dc75126607ad76317a9fd07fe61323d8c3cb42add12b6",
 | 
			
		||||
                "sha256:9163fec630495c10c767991e3f8dab32f4427bfb2dfeaa59bb28fe3e52ba66f2",
 | 
			
		||||
                "sha256:95d324e603c5cec5d89e8595236bbf59ade5fe3a72d100ce61eebb323d598750",
 | 
			
		||||
                "sha256:9927aa8a8cb4af681279b6f28a1dcb14e0eb556c1aea8413a1e27608a8516e0c",
 | 
			
		||||
                "sha256:9948c2d5c5c0ee45ed44cee0e2eba2ce60a03be006ed3074521f3da3be162e72",
 | 
			
		||||
                "sha256:a719bd708207fa219fcbf4c8ebbcbc52846045f78179d00445b429fdabdbc1c4",
 | 
			
		||||
                "sha256:bc22ced26ebc46546798fa0141f4418f1db116dec517f0aeaecec87cf7b2416c",
 | 
			
		||||
                "sha256:c41b7e10b72cef00cd63410f31fe50e72dc3a40eafbd146e288384fbe4208064",
 | 
			
		||||
                "sha256:cdb0ad83a5d6bac986a37fcb7562bcbef0aabae8ea19505bab5cf83c4d18af12",
 | 
			
		||||
                "sha256:d8e480f65ac7105cbc288eec2417dc61eaac6ed6e75595aa15b8c7c77c53a68b",
 | 
			
		||||
                "sha256:da2d581da279bc7408d38e16ff77754f5448c4352f2acfe530a5d14d8fc6934a",
 | 
			
		||||
                "sha256:de61091dd68326b600422cf731eb4810c4c6363f18a65bccd6061784b7454f5b",
 | 
			
		||||
                "sha256:ec7d39589f9cfc2a8b83b1d2fc673441757c99d43283e97b2dd46e0e23730db8",
 | 
			
		||||
                "sha256:f3204006869ab037604b1d9f045c4e84882ddd365e4ee8caa5eb1ff47a59188e",
 | 
			
		||||
                "sha256:f4d2174e168d0eabd1fffaf88b4f62c2b6f30a67b8816f31024b8e48be3e2d75",
 | 
			
		||||
                "sha256:fcff8c9d88d58880f7eda2139c7c444552a38f98a9e77ba5970b6e78f54ac358"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==3.9.4"
 | 
			
		||||
            "version": "==3.9.6"
 | 
			
		||||
        },
 | 
			
		||||
        "pycryptodomex": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -714,10 +720,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "ruamel.yaml": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:ee3264b83c3309b4ae7978afa185da6a1d278e3abc9fb942f1a0b57c622092f8",
 | 
			
		||||
                "sha256:fd16843ff0ba45fa5e1ea9ea7038428b4a46f2c39deea9aa67f9eaa34823dc11"
 | 
			
		||||
                "sha256:0962fd7999e064c4865f96fb1e23079075f4a2a14849bcdc5cdba53a24f9759b",
 | 
			
		||||
                "sha256:099c644a778bf72ffa00524f78dd0b6476bca94a1da344130f4bf3381ce5b954"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.16.9"
 | 
			
		||||
            "version": "==0.16.10"
 | 
			
		||||
        },
 | 
			
		||||
        "ruamel.yaml.clib": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -753,11 +759,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "sentry-sdk": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:8e2d38dc58dc992280487e553ec3d97a424e4d179f4fad802ef3b08f64ccf4d8",
 | 
			
		||||
                "sha256:9b59e155229ea7d46a52b5c025d8c3c6d591e9dd9bb5f5f47310b2bb430038a8"
 | 
			
		||||
                "sha256:b06dd27391fd11fb32f84fe054e6a64736c469514a718a99fb5ce1dff95d6b28",
 | 
			
		||||
                "sha256:e023da07cfbead3868e1e2ba994160517885a32dfd994fc455b118e37989479b"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.14.0"
 | 
			
		||||
            "version": "==0.14.1"
 | 
			
		||||
        },
 | 
			
		||||
        "service-identity": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -791,11 +797,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "structlog": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4287058cf4ce1a59bc5dea290d6386d37f29a37529c9a51cdf7387e51710152b",
 | 
			
		||||
                "sha256:6640e6690fc31d5949bc614c1a630464d3aaa625284aeb7c6e486c3010d73e12"
 | 
			
		||||
                "sha256:7a48375db6274ed1d0ae6123c486472aa1d0890b08d314d2b016f3aa7f35990b",
 | 
			
		||||
                "sha256:8a672be150547a93d90a7d74229a29e765be05bd156a35cdcc527ebf68e9af92"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==19.2.0"
 | 
			
		||||
            "version": "==20.1.0"
 | 
			
		||||
        },
 | 
			
		||||
        "swagger-spec-validator": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -817,12 +823,12 @@
 | 
			
		||||
                "secure"
 | 
			
		||||
            ],
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293",
 | 
			
		||||
                "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"
 | 
			
		||||
                "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
 | 
			
		||||
                "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "markers": null,
 | 
			
		||||
            "version": "==1.25.7"
 | 
			
		||||
            "version": "==1.25.8"
 | 
			
		||||
        },
 | 
			
		||||
        "vine": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -863,10 +869,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "autopep8": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4d8eec30cc81bc5617dbf1218201d770dc35629363547f17577c61683ccfb3ee"
 | 
			
		||||
                "sha256:0f592a0447acea0c2b0a9602be1e4e3d86db52badd2e3c84f0193bfd89fd3a43"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.4.4"
 | 
			
		||||
            "version": "==1.5"
 | 
			
		||||
        },
 | 
			
		||||
        "bandit": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -946,40 +952,33 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a",
 | 
			
		||||
                "sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038"
 | 
			
		||||
                "sha256:2f1ba1db8648484dd5c238fb62504777b7ad090c81c5f1fd8d5eb5ec21b5f283",
 | 
			
		||||
                "sha256:c91c91a7ad6ef67a874a4f76f58ba534f9208412692a840e1d125eb5c279cb0a"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.2.10"
 | 
			
		||||
            "version": "==3.0.3"
 | 
			
		||||
        },
 | 
			
		||||
        "django-debug-toolbar": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:24c157bc6c0e1648e0a6587511ecb1b007a00a354ce716950bff2de12693e7a8",
 | 
			
		||||
                "sha256:77cfba1d6e91b9bc3d36dc7dc74a9bb80be351948db5f880f2562a0cbf20b6c5"
 | 
			
		||||
                "sha256:eabbefe89881bbe4ca7c980ff102e3c35c8e8ad6eb725041f538988f2f39a943",
 | 
			
		||||
                "sha256:ff94725e7aae74b133d0599b9bf89bd4eb8f5d2c964106e61d11750228c8774c"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.1"
 | 
			
		||||
        },
 | 
			
		||||
        "dodgy": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:28323cbfc9352139fdd3d316fa17f325cc0e9ac74438cbba51d70f9b48f86c3a",
 | 
			
		||||
                "sha256:51f54c0fd886fa3854387f354b19f429d38c04f984f38bc572558b703c0542a6"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.2.1"
 | 
			
		||||
            "version": "==2.2"
 | 
			
		||||
        },
 | 
			
		||||
        "gitdb2": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1b6df1433567a51a4a9c1a5a0de977aa351a405cc56d7d35f3388bad1f630350",
 | 
			
		||||
                "sha256:96bbb507d765a7f51eb802554a9cfe194a174582f772e0d89f4e87288c288b7b"
 | 
			
		||||
                "sha256:0375d983fd887d03c8942e81b1b0abc6c320cfb500cd3fe0d9c0eac87fbf2b52",
 | 
			
		||||
                "sha256:b2b3a67090c17dc61f8407ca485e79ae811225ab5ebcd98ac5ee01448e8987b5"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.0.6"
 | 
			
		||||
            "version": "==3.0.2"
 | 
			
		||||
        },
 | 
			
		||||
        "gitpython": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:99c77677f31f255e130f3fed4c8e0eebb35f1a09df98ff965fff6774f71688cf",
 | 
			
		||||
                "sha256:99cd0403cecd8a13b95d2e045b9fcaa7837137fcc5ec3105f2c413305d82c143"
 | 
			
		||||
                "sha256:620b3c729bbc143b498cfea77e302999deedc55faec5b1067086c9ef90e101bc",
 | 
			
		||||
                "sha256:a43a5d88a5bbc3cf32bb5223e4b4e68fd716db5e9996cad6e561bbfee6e5f4af"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.0.7"
 | 
			
		||||
            "version": "==3.0.8"
 | 
			
		||||
        },
 | 
			
		||||
        "isort": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1035,40 +1034,12 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==5.4.4"
 | 
			
		||||
        },
 | 
			
		||||
        "pep8-naming": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1b419fa45b68b61cd8c5daf4e0c96d28915ad14d3d5f35fcc1e7e95324a33a2e",
 | 
			
		||||
                "sha256:4eedfd4c4b05e48796f74f5d8628c068ff788b9c2b08471ad408007fc6450e5a"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.4.1"
 | 
			
		||||
        },
 | 
			
		||||
        "prospector": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:ea910794b53cfefcb5dfb6b4eb0323e42d1a88132e165b85b016cc7f0b6ae635"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.2.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pycodestyle": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83",
 | 
			
		||||
                "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a"
 | 
			
		||||
                "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
 | 
			
		||||
                "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.4.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pydocstyle": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586",
 | 
			
		||||
                "sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==5.0.2"
 | 
			
		||||
        },
 | 
			
		||||
        "pyflakes": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
 | 
			
		||||
                "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.1.1"
 | 
			
		||||
            "version": "==2.5.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1078,25 +1049,13 @@
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.4.4"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint-celery": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:41e32094e7408d15c044178ea828dd524beedbdbe6f83f712c5e35bde1de4beb"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.3"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint-django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:9bdb0e022b19881218a25ffb8ad05e83b83bc5cdbc58e5ee8ffbe99965193f6c",
 | 
			
		||||
                "sha256:9eea6a026eaa5ecfad5fed7a33faf77ef55a43cc78afbcaf2f6ddd071156b3f8"
 | 
			
		||||
                "sha256:440beb814464928aedd2e21196bb6e47a83b63e2cbe886a701ba0f4a64206bbb",
 | 
			
		||||
                "sha256:d5d113605a64cf0e638b707d4cb42106e626f8851bc30a44d5b22bd698ad8483"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.0.12"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint-flask": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:f4d97de2216bf7bfce07c9c08b166e978fe9f2725de2a50a9845a97de7e31517"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.6"
 | 
			
		||||
            "version": "==2.0.13"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint-plugin-utils": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1155,18 +1114,6 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2020.1.8"
 | 
			
		||||
        },
 | 
			
		||||
        "requirements-detector": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:9fbc4b24e8b7c3663aff32e3eba34596848c6b91bd425079b386973bd8d08931"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.6"
 | 
			
		||||
        },
 | 
			
		||||
        "setoptconf": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:5b0b5d8e0077713f5d5152d4f63be6f048d9a1bb66be15d089a11c898c3cf49c"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.2.0"
 | 
			
		||||
        },
 | 
			
		||||
        "six": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
 | 
			
		||||
@ -1181,13 +1128,6 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.0.5"
 | 
			
		||||
        },
 | 
			
		||||
        "snowballstemmer": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
 | 
			
		||||
                "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.0.0"
 | 
			
		||||
        },
 | 
			
		||||
        "sqlparse": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177",
 | 
			
		||||
@ -1237,11 +1177,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "unittest-xml-reporting": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:358bbdaf24a26d904cc1c26ef3078bca7fc81541e0a54c8961693cc96a6f35e0",
 | 
			
		||||
                "sha256:9d28ddf6524cf0ff9293f61bd12e792de298f8561a5c945acea63fb437789e0e"
 | 
			
		||||
                "sha256:6584562cde8226fc79fa29e38903c669a02799074a563bb0b70fcd3a8e87829c",
 | 
			
		||||
                "sha256:dd8046a64dc62f3d30301523a54992e0be75a945194491e0a3b718130cb429e0"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.5.2"
 | 
			
		||||
            "version": "==3.0.1"
 | 
			
		||||
        },
 | 
			
		||||
        "wrapt": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								docs/reference/property-mappings/user-object.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								docs/reference/property-mappings/user-object.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
# Passbook User Object
 | 
			
		||||
 | 
			
		||||
The User object has the following attributes:
 | 
			
		||||
 | 
			
		||||
 - `username`: User's Username
 | 
			
		||||
 - `email` User's E-Mail
 | 
			
		||||
 - `name` User's Display Name
 | 
			
		||||
 - `is_staff` Boolean field if user is staff
 | 
			
		||||
 - `is_active` Boolean field if user is active
 | 
			
		||||
 - `date_joined` Date User joined/was created
 | 
			
		||||
 - `password_change_date` Date Password was last changed
 | 
			
		||||
 - `attributes` Dynamic Attributes
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
 | 
			
		||||
List all the User's Group Names
 | 
			
		||||
 | 
			
		||||
```jinja2
 | 
			
		||||
[{% for group in user.groups.all() %}'{{ group.name }}',{% endfor %}]
 | 
			
		||||
```
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
appVersion: "0.7.13-beta"
 | 
			
		||||
appVersion: "0.7.16-beta"
 | 
			
		||||
description: A Helm chart for passbook.
 | 
			
		||||
name: passbook
 | 
			
		||||
version: "0.7.13-beta"
 | 
			
		||||
version: "0.7.16-beta"
 | 
			
		||||
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
# This is a YAML-formatted file.
 | 
			
		||||
# Declare variables to be passed into your templates.
 | 
			
		||||
image:
 | 
			
		||||
  tag: 0.7.13-beta
 | 
			
		||||
  tag: 0.7.16-beta
 | 
			
		||||
 | 
			
		||||
nameOverride: ""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,9 @@ nav:
 | 
			
		||||
          - Rancher: integrations/services/rancher/index.md
 | 
			
		||||
          - Harbor: integrations/services/harbor/index.md
 | 
			
		||||
          - Sentry: integrations/services/sentry/index.md
 | 
			
		||||
  - Reference:
 | 
			
		||||
      - Property Mappings:
 | 
			
		||||
          - User Object: reference/property-mappings/user-object.md
 | 
			
		||||
 | 
			
		||||
repo_name: "BeryJu.org/passbook"
 | 
			
		||||
repo_url: https://github.com/BeryJu/passbook
 | 
			
		||||
 | 
			
		||||
@ -1,2 +1,2 @@
 | 
			
		||||
"""passbook"""
 | 
			
		||||
__version__ = "0.7.13-beta"
 | 
			
		||||
__version__ = "0.7.16-beta"
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
{% extends "generic/form.html" %}
 | 
			
		||||
{% extends base_template|default:"generic/form.html" %}
 | 
			
		||||
 | 
			
		||||
{% load utils %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@
 | 
			
		||||
<link rel="stylesheet" href="{% static 'codemirror/lib/codemirror.css' %}">
 | 
			
		||||
<link rel="stylesheet" href="{% static 'codemirror/theme/monokai.css' %}">
 | 
			
		||||
<script src="{% static 'codemirror/mode/yaml/yaml.js' %}"></script>
 | 
			
		||||
<script src="{% static 'codemirror/mode/jinja2/jinja2.js' %}"></script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
@ -29,21 +30,33 @@
 | 
			
		||||
  <div class="">
 | 
			
		||||
    <form action="" method="post" class="form-horizontal">
 | 
			
		||||
      {% include 'partials/form.html' with form=form %}
 | 
			
		||||
      {% block beneath_form %}
 | 
			
		||||
      {% endblock %}
 | 
			
		||||
      <a class="btn btn-default" href="{% back %}">{% trans "Cancel" %}</a>
 | 
			
		||||
      <input type="submit" class="btn btn-primary" value="{% block action %}{% endblock %}" />
 | 
			
		||||
    </form>
 | 
			
		||||
  </div>
 | 
			
		||||
  {% block beneath_form %}
 | 
			
		||||
  {% endblock %}
 | 
			
		||||
  <script>
 | 
			
		||||
    let attributes = document.getElementsByName('attributes');
 | 
			
		||||
    const attributes = document.getElementsByName('attributes');
 | 
			
		||||
    if (attributes.length > 0) {
 | 
			
		||||
      let myCodeMirror = CodeMirror.fromTextArea(attributes[0], {
 | 
			
		||||
      // https://github.com/codemirror/CodeMirror/issues/5092
 | 
			
		||||
      attributes[0].removeAttribute("required");
 | 
			
		||||
      const attributesCM = CodeMirror.fromTextArea(attributes[0], {
 | 
			
		||||
        mode: 'yaml',
 | 
			
		||||
        theme: 'monokai',
 | 
			
		||||
        lineNumbers: true,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    const expressions = document.getElementsByName('expression');
 | 
			
		||||
    if (expressions.length > 0) {
 | 
			
		||||
      // https://github.com/codemirror/CodeMirror/issues/5092
 | 
			
		||||
      expressions[0].removeAttribute("required");
 | 
			
		||||
      const expressionCM = CodeMirror.fromTextArea(expressions[0], {
 | 
			
		||||
        mode: 'jinja2',
 | 
			
		||||
        theme: 'monokai',
 | 
			
		||||
        lineNumbers: true,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  </script>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
{% extends "generic/form.html" %}
 | 
			
		||||
{% extends base_template|default:"generic/form.html" %}
 | 
			
		||||
 | 
			
		||||
{% load utils %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
@ -66,6 +66,9 @@ class PropertyMappingCreateView(
 | 
			
		||||
            if x.__name__ == property_mapping_type
 | 
			
		||||
        )
 | 
			
		||||
        kwargs["type"] = model._meta.verbose_name
 | 
			
		||||
        form_cls = self.get_form_class()
 | 
			
		||||
        if hasattr(form_cls, "template_name"):
 | 
			
		||||
            kwargs["base_template"] = form_cls.template_name
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
@ -92,6 +95,13 @@ class PropertyMappingUpdateView(
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:property-mappings")
 | 
			
		||||
    success_message = _("Successfully updated Property Mapping")
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs = super().get_context_data(**kwargs)
 | 
			
		||||
        form_cls = self.get_form_class()
 | 
			
		||||
        if hasattr(form_cls, "template_name"):
 | 
			
		||||
            kwargs["base_template"] = form_cls.template_name
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
        form_class_path = self.get_object().form
 | 
			
		||||
        form_class = path_to_class(form_class_path)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								passbook/core/migrations/0006_propertymapping_template.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								passbook/core/migrations/0006_propertymapping_template.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
# Generated by Django 3.0.3 on 2020-02-17 16:15
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("passbook_core", "0005_merge_20191025_2022"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="propertymapping",
 | 
			
		||||
            name="template",
 | 
			
		||||
            field=models.TextField(default=""),
 | 
			
		||||
            preserve_default=False,
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										16
									
								
								passbook/core/migrations/0007_auto_20200217_1934.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								passbook/core/migrations/0007_auto_20200217_1934.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
# Generated by Django 3.0.3 on 2020-02-17 19:34
 | 
			
		||||
 | 
			
		||||
from django.db import migrations
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("passbook_core", "0006_propertymapping_template"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RenameField(
 | 
			
		||||
            model_name="propertymapping", old_name="template", new_name="expression",
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -2,15 +2,17 @@
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
from random import SystemRandom
 | 
			
		||||
from time import sleep
 | 
			
		||||
from typing import Optional
 | 
			
		||||
from typing import Optional, Any
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
 | 
			
		||||
from jinja2.nativetypes import NativeEnvironment
 | 
			
		||||
from django.contrib.auth.models import AbstractUser
 | 
			
		||||
from django.contrib.postgres.fields import JSONField
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
from django.utils.timezone import now
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django_prometheus.models import ExportModelOperationsMixin
 | 
			
		||||
from guardian.mixins import GuardianUserMixin
 | 
			
		||||
from model_utils.managers import InheritanceManager
 | 
			
		||||
@ -22,6 +24,7 @@ from passbook.policies.exceptions import PolicyException
 | 
			
		||||
from passbook.policies.struct import PolicyRequest, PolicyResult
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
NATIVE_ENVIRONMENT = NativeEnvironment()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def default_nonce_duration():
 | 
			
		||||
@ -293,10 +296,16 @@ class PropertyMapping(UUIDModel):
 | 
			
		||||
    """User-defined key -> x mapping which can be used by providers to expose extra data."""
 | 
			
		||||
 | 
			
		||||
    name = models.TextField()
 | 
			
		||||
    expression = models.TextField()
 | 
			
		||||
 | 
			
		||||
    form = ""
 | 
			
		||||
    objects = InheritanceManager()
 | 
			
		||||
 | 
			
		||||
    def evaluate(self, user: User, request: HttpRequest, **kwargs) -> Any:
 | 
			
		||||
        """Evaluate `self.expression` using `**kwargs` as Context."""
 | 
			
		||||
        expression = NATIVE_ENVIRONMENT.from_string(self.expression)
 | 
			
		||||
        return expression.render(user=user, request=request, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"Property Mapping {self.name}"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,6 @@ from urllib.parse import urlparse
 | 
			
		||||
 | 
			
		||||
import yaml
 | 
			
		||||
from django.conf import ImproperlyConfigured
 | 
			
		||||
from django.utils.autoreload import autoreload_started
 | 
			
		||||
from structlog import get_logger
 | 
			
		||||
 | 
			
		||||
SEARCH_PATHS = ["passbook/lib/default.yml", "/etc/passbook/config.yml", "",] + glob(
 | 
			
		||||
@ -142,12 +141,3 @@ class ConfigLoader:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG = ConfigLoader()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def signal_handler(sender, **_):
 | 
			
		||||
    """Add all loaded config files to autoreload watcher"""
 | 
			
		||||
    for path in CONFIG.loaded_file:
 | 
			
		||||
        sender.watch_file(path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
autoreload_started.connect(signal_handler)
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
"""passbook policy engine"""
 | 
			
		||||
from multiprocessing import Pipe
 | 
			
		||||
from multiprocessing import Pipe, set_start_method
 | 
			
		||||
from multiprocessing.connection import Connection
 | 
			
		||||
from typing import List, Optional, Tuple
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,9 @@ from passbook.policies.process import PolicyProcess, cache_key
 | 
			
		||||
from passbook.policies.struct import PolicyRequest, PolicyResult
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
# This is only really needed for macOS, because Python 3.8 changed the default to spawn
 | 
			
		||||
# spawn causes issues with objects that aren't picklable, and also the django setup
 | 
			
		||||
set_start_method("fork")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PolicyProcessInfo:
 | 
			
		||||
@ -36,13 +39,15 @@ class PolicyEngine:
 | 
			
		||||
    policies: List[Policy] = []
 | 
			
		||||
    request: PolicyRequest
 | 
			
		||||
 | 
			
		||||
    __processes: List[PolicyProcessInfo] = []
 | 
			
		||||
    __cached_policies: List[PolicyResult]
 | 
			
		||||
    __processes: List[PolicyProcessInfo]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, policies, user: User, request: HttpRequest = None):
 | 
			
		||||
        self.policies = policies
 | 
			
		||||
        self.request = PolicyRequest(user)
 | 
			
		||||
        if request:
 | 
			
		||||
            self.request.http_request = request
 | 
			
		||||
        self.__cached_policies = []
 | 
			
		||||
        self.__processes = []
 | 
			
		||||
 | 
			
		||||
    def _select_subclasses(self) -> List[Policy]:
 | 
			
		||||
@ -55,13 +60,12 @@ class PolicyEngine:
 | 
			
		||||
 | 
			
		||||
    def build(self) -> "PolicyEngine":
 | 
			
		||||
        """Build task group"""
 | 
			
		||||
        cached_policies = []
 | 
			
		||||
        for policy in self._select_subclasses():
 | 
			
		||||
            cached_policy = cache.get(cache_key(policy, self.request.user), None)
 | 
			
		||||
            if cached_policy and self.use_cache:
 | 
			
		||||
                LOGGER.debug("Taking result from cache", policy=policy)
 | 
			
		||||
                cached_policies.append(cached_policy)
 | 
			
		||||
            else:
 | 
			
		||||
                self.__cached_policies.append(cached_policy)
 | 
			
		||||
                continue
 | 
			
		||||
            LOGGER.debug("Evaluating policy", policy=policy)
 | 
			
		||||
            our_end, task_end = Pipe(False)
 | 
			
		||||
            task = PolicyProcess(policy, self.request, task_end)
 | 
			
		||||
@ -82,13 +86,14 @@ class PolicyEngine:
 | 
			
		||||
    def result(self) -> Tuple[bool, List[str]]:
 | 
			
		||||
        """Get policy-checking result"""
 | 
			
		||||
        messages: List[str] = []
 | 
			
		||||
        for proc_info in self.__processes:
 | 
			
		||||
            LOGGER.debug(
 | 
			
		||||
                "Result", policy=proc_info.policy, passing=proc_info.result.passing
 | 
			
		||||
            )
 | 
			
		||||
            if proc_info.result.messages:
 | 
			
		||||
                messages += proc_info.result.messages
 | 
			
		||||
            if not proc_info.result.passing:
 | 
			
		||||
        process_results: List[PolicyResult] = [
 | 
			
		||||
            x.result for x in self.__processes if x.result
 | 
			
		||||
        ]
 | 
			
		||||
        for result in process_results + self.__cached_policies:
 | 
			
		||||
            LOGGER.debug("result", passing=result.passing)
 | 
			
		||||
            if result.messages:
 | 
			
		||||
                messages += result.messages
 | 
			
		||||
            if not result.passing:
 | 
			
		||||
                return False, messages
 | 
			
		||||
        return True, messages
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -14,12 +14,16 @@ class SAMLProviderSerializer(ModelSerializer):
 | 
			
		||||
        fields = [
 | 
			
		||||
            "pk",
 | 
			
		||||
            "name",
 | 
			
		||||
            "property_mappings",
 | 
			
		||||
            "processor_path",
 | 
			
		||||
            "acs_url",
 | 
			
		||||
            "audience",
 | 
			
		||||
            "processor_path",
 | 
			
		||||
            "issuer",
 | 
			
		||||
            "assertion_valid_for",
 | 
			
		||||
            "assertion_valid_not_before",
 | 
			
		||||
            "assertion_valid_not_on_or_after",
 | 
			
		||||
            "session_valid_not_on_or_after",
 | 
			
		||||
            "property_mappings",
 | 
			
		||||
            "digest_algorithm",
 | 
			
		||||
            "signature_algorithm",
 | 
			
		||||
            "signing",
 | 
			
		||||
            "signing_cert",
 | 
			
		||||
            "signing_key",
 | 
			
		||||
@ -39,7 +43,7 @@ class SAMLPropertyMappingSerializer(ModelSerializer):
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = SAMLPropertyMapping
 | 
			
		||||
        fields = ["pk", "name", "saml_name", "friendly_name", "values"]
 | 
			
		||||
        fields = ["pk", "name", "saml_name", "friendly_name", "expression"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SAMLPropertyMappingViewSet(ModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ from django import forms
 | 
			
		||||
from django.contrib.admin.widgets import FilteredSelectMultiple
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
 | 
			
		||||
from passbook.lib.fields import DynamicArrayField
 | 
			
		||||
from passbook.providers.saml.models import (
 | 
			
		||||
    SAMLPropertyMapping,
 | 
			
		||||
    SAMLProvider,
 | 
			
		||||
@ -40,14 +39,12 @@ class SAMLProviderForm(forms.ModelForm):
 | 
			
		||||
            "assertion_valid_not_on_or_after",
 | 
			
		||||
            "session_valid_not_on_or_after",
 | 
			
		||||
            "property_mappings",
 | 
			
		||||
            "digest_algorithm",
 | 
			
		||||
            "signature_algorithm",
 | 
			
		||||
            "signing",
 | 
			
		||||
            "signing_cert",
 | 
			
		||||
            "signing_key",
 | 
			
		||||
        ]
 | 
			
		||||
        labels = {
 | 
			
		||||
            "acs_url": "ACS URL",
 | 
			
		||||
            "signing_cert": "Singing Certificate",
 | 
			
		||||
        }
 | 
			
		||||
        widgets = {
 | 
			
		||||
            "name": forms.TextInput(),
 | 
			
		||||
            "audience": forms.TextInput(),
 | 
			
		||||
@ -62,16 +59,14 @@ class SAMLProviderForm(forms.ModelForm):
 | 
			
		||||
class SAMLPropertyMappingForm(forms.ModelForm):
 | 
			
		||||
    """SAML Property Mapping form"""
 | 
			
		||||
 | 
			
		||||
    template_name = "saml/idp/property_mapping_form.html"
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = SAMLPropertyMapping
 | 
			
		||||
        fields = ["name", "saml_name", "friendly_name", "values"]
 | 
			
		||||
        fields = ["name", "saml_name", "friendly_name", "expression"]
 | 
			
		||||
        widgets = {
 | 
			
		||||
            "name": forms.TextInput(),
 | 
			
		||||
            "saml_name": forms.TextInput(),
 | 
			
		||||
            "friendly_name": forms.TextInput(),
 | 
			
		||||
        }
 | 
			
		||||
        field_classes = {"values": DynamicArrayField}
 | 
			
		||||
        help_texts = {
 | 
			
		||||
            "values": 'String substitution uses a syntax like "{variable} test}".'
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,38 @@
 | 
			
		||||
# Generated by Django 2.2.9 on 2020-02-16 11:09
 | 
			
		||||
 | 
			
		||||
import django.contrib.postgres.fields
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("passbook_providers_saml", "0002_auto_20200214_1354"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="samlpropertymapping",
 | 
			
		||||
            name="saml_name",
 | 
			
		||||
            field=models.TextField(verbose_name="SAML Name"),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="samlpropertymapping",
 | 
			
		||||
            name="values",
 | 
			
		||||
            field=django.contrib.postgres.fields.ArrayField(
 | 
			
		||||
                base_field=models.TextField(),
 | 
			
		||||
                help_text="This string can contain string substitutions delimited by {}. The following Variables are available: user, request",
 | 
			
		||||
                size=None,
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="samlprovider",
 | 
			
		||||
            name="acs_url",
 | 
			
		||||
            field=models.URLField(verbose_name="ACS URL"),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="samlprovider",
 | 
			
		||||
            name="signing_cert",
 | 
			
		||||
            field=models.TextField(verbose_name="Singing Certificate"),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -0,0 +1,41 @@
 | 
			
		||||
# Generated by Django 3.0.3 on 2020-02-17 15:26
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("passbook_providers_saml", "0003_auto_20200216_1109"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="samlprovider",
 | 
			
		||||
            name="digest_algorithm",
 | 
			
		||||
            field=models.CharField(
 | 
			
		||||
                choices=[("sha1", "SHA1"), ("sha256", "SHA256")],
 | 
			
		||||
                default="sha256",
 | 
			
		||||
                max_length=50,
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="samlprovider",
 | 
			
		||||
            name="signature_algorithm",
 | 
			
		||||
            field=models.CharField(
 | 
			
		||||
                choices=[
 | 
			
		||||
                    ("rsa-sha1", "RSA-SHA1"),
 | 
			
		||||
                    ("rsa-sha256", "RSA-SHA256"),
 | 
			
		||||
                    ("ecdsa-sha256", "ECDSA-SHA256"),
 | 
			
		||||
                    ("dsa-sha1", "DSA-SHA1"),
 | 
			
		||||
                ],
 | 
			
		||||
                default="rsa-sha256",
 | 
			
		||||
                max_length=50,
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="samlprovider",
 | 
			
		||||
            name="processor_path",
 | 
			
		||||
            field=models.CharField(choices=[], max_length=255),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -0,0 +1,76 @@
 | 
			
		||||
# Generated by Django 3.0.3 on 2020-02-17 16:15
 | 
			
		||||
 | 
			
		||||
from django.db import migrations
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def cleanup_old_autogenerated(apps, schema_editor):
 | 
			
		||||
    SAMLPropertyMapping = apps.get_model(
 | 
			
		||||
        "passbook_providers_saml", "SAMLPropertyMapping"
 | 
			
		||||
    )
 | 
			
		||||
    db_alias = schema_editor.connection.alias
 | 
			
		||||
    SAMLPropertyMapping.objects.using(db_alias).filter(
 | 
			
		||||
        name__startswith="Autogenerated"
 | 
			
		||||
    ).delete()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_default_property_mappings(apps, schema_editor):
 | 
			
		||||
    """Create default SAML Property Mappings"""
 | 
			
		||||
    SAMLPropertyMapping = apps.get_model(
 | 
			
		||||
        "passbook_providers_saml", "SAMLPropertyMapping"
 | 
			
		||||
    )
 | 
			
		||||
    db_alias = schema_editor.connection.alias
 | 
			
		||||
    defaults = [
 | 
			
		||||
        {
 | 
			
		||||
            "FriendlyName": "eduPersonPrincipalName",
 | 
			
		||||
            "Name": "urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
 | 
			
		||||
            "Expression": "{{ user.email }}",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "FriendlyName": "cn",
 | 
			
		||||
            "Name": "urn:oid:2.5.4.3",
 | 
			
		||||
            "Expression": "{{ user.name }}",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "FriendlyName": "mail",
 | 
			
		||||
            "Name": "urn:oid:0.9.2342.19200300.100.1.3",
 | 
			
		||||
            "Expression": "{{ user.email }}",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "FriendlyName": "displayName",
 | 
			
		||||
            "Name": "urn:oid:2.16.840.1.113730.3.1.241",
 | 
			
		||||
            "Expression": "{{ user.username }}",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "FriendlyName": "uid",
 | 
			
		||||
            "Name": "urn:oid:0.9.2342.19200300.100.1.1",
 | 
			
		||||
            "Expression": "{{ user.pk }}",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "FriendlyName": "member-of",
 | 
			
		||||
            "Name": "member-of",
 | 
			
		||||
            "Expression": "[{% for group in user.groups.all() %}'{{ group.name }}',{% endfor %}]",
 | 
			
		||||
        },
 | 
			
		||||
    ]
 | 
			
		||||
    for default in defaults:
 | 
			
		||||
        SAMLPropertyMapping.objects.using(db_alias).get_or_create(
 | 
			
		||||
            saml_name=default["Name"],
 | 
			
		||||
            friendly_name=default["FriendlyName"],
 | 
			
		||||
            expression=default["Expression"],
 | 
			
		||||
            defaults={
 | 
			
		||||
                "name": f"Autogenerated SAML Mapping: {default['FriendlyName']} -> {default['Expression']}"
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("passbook_providers_saml", "0004_auto_20200217_1526"),
 | 
			
		||||
        ("passbook_core", "0007_auto_20200217_1934"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RunPython(cleanup_old_autogenerated),
 | 
			
		||||
        migrations.RemoveField(model_name="samlpropertymapping", name="values",),
 | 
			
		||||
        migrations.RunPython(create_default_property_mappings),
 | 
			
		||||
    ]
 | 
			
		||||
@ -1,8 +1,7 @@
 | 
			
		||||
"""passbook saml_idp Models"""
 | 
			
		||||
from django.contrib.postgres.fields import ArrayField
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.shortcuts import reverse
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
from structlog import get_logger
 | 
			
		||||
 | 
			
		||||
from passbook.core.models import PropertyMapping, Provider
 | 
			
		||||
@ -19,7 +18,7 @@ class SAMLProvider(Provider):
 | 
			
		||||
    name = models.TextField()
 | 
			
		||||
    processor_path = models.CharField(max_length=255, choices=[])
 | 
			
		||||
 | 
			
		||||
    acs_url = models.URLField()
 | 
			
		||||
    acs_url = models.URLField(verbose_name=_("ACS URL"))
 | 
			
		||||
    audience = models.TextField(default="")
 | 
			
		||||
    issuer = models.TextField()
 | 
			
		||||
 | 
			
		||||
@ -55,8 +54,24 @@ class SAMLProvider(Provider):
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    digest_algorithm = models.CharField(
 | 
			
		||||
        max_length=50,
 | 
			
		||||
        choices=(("sha1", _("SHA1")), ("sha256", _("SHA256")),),
 | 
			
		||||
        default="sha256",
 | 
			
		||||
    )
 | 
			
		||||
    signature_algorithm = models.CharField(
 | 
			
		||||
        max_length=50,
 | 
			
		||||
        choices=(
 | 
			
		||||
            ("rsa-sha1", _("RSA-SHA1")),
 | 
			
		||||
            ("rsa-sha256", _("RSA-SHA256")),
 | 
			
		||||
            ("ecdsa-sha256", _("ECDSA-SHA256")),
 | 
			
		||||
            ("dsa-sha1", _("DSA-SHA1")),
 | 
			
		||||
        ),
 | 
			
		||||
        default="rsa-sha256",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    signing = models.BooleanField(default=True)
 | 
			
		||||
    signing_cert = models.TextField()
 | 
			
		||||
    signing_cert = models.TextField(verbose_name=_("Singing Certificate"))
 | 
			
		||||
    signing_key = models.TextField()
 | 
			
		||||
 | 
			
		||||
    form = "passbook.providers.saml.forms.SAMLProviderForm"
 | 
			
		||||
@ -100,9 +115,8 @@ class SAMLProvider(Provider):
 | 
			
		||||
class SAMLPropertyMapping(PropertyMapping):
 | 
			
		||||
    """SAML Property mapping, allowing Name/FriendlyName mapping to a list of strings"""
 | 
			
		||||
 | 
			
		||||
    saml_name = models.TextField()
 | 
			
		||||
    saml_name = models.TextField(verbose_name="SAML Name")
 | 
			
		||||
    friendly_name = models.TextField(default=None, blank=True, null=True)
 | 
			
		||||
    values = ArrayField(models.TextField())
 | 
			
		||||
 | 
			
		||||
    form = "passbook.providers.saml.forms.SAMLPropertyMappingForm"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,6 @@ class Processor:
 | 
			
		||||
 | 
			
		||||
    _assertion_params: Dict[str, Union[str, List[Dict[str, str]]]]
 | 
			
		||||
    _request_params: Dict[str, str]
 | 
			
		||||
    _system_params: Dict[str, str]
 | 
			
		||||
    _response_params: Dict[str, str]
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
@ -45,9 +44,6 @@ class Processor:
 | 
			
		||||
        self.name = remote.name
 | 
			
		||||
        self._remote = remote
 | 
			
		||||
        self._logger = get_logger()
 | 
			
		||||
        self._system_params = {
 | 
			
		||||
            "ISSUER": self._remote.issuer,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def _build_assertion(self):
 | 
			
		||||
        """Builds _assertion_params."""
 | 
			
		||||
@ -70,8 +66,8 @@ class Processor:
 | 
			
		||||
            "SP_NAME_QUALIFIER": self._remote.audience,
 | 
			
		||||
            "SUBJECT": self._http_request.user.email,
 | 
			
		||||
            "SUBJECT_FORMAT": self.subject_format,
 | 
			
		||||
            "ISSUER": self._remote.issuer,
 | 
			
		||||
        }
 | 
			
		||||
        self._assertion_params.update(self._system_params)
 | 
			
		||||
        self._assertion_params.update(self._request_params)
 | 
			
		||||
 | 
			
		||||
    def _build_response(self):
 | 
			
		||||
@ -81,8 +77,8 @@ class Processor:
 | 
			
		||||
            "ISSUE_INSTANT": get_time_string(),
 | 
			
		||||
            "RESPONSE_ID": get_random_id(),
 | 
			
		||||
            "RESPONSE_SIGNATURE": "",  # initially unsigned
 | 
			
		||||
            "ISSUER": self._remote.issuer,
 | 
			
		||||
        }
 | 
			
		||||
        self._response_params.update(self._system_params)
 | 
			
		||||
        self._response_params.update(self._request_params)
 | 
			
		||||
 | 
			
		||||
    def _encode_response(self):
 | 
			
		||||
@ -97,49 +93,26 @@ class Processor:
 | 
			
		||||
    def _format_assertion(self):
 | 
			
		||||
        """Formats _assertion_params as _assertion_xml."""
 | 
			
		||||
        # https://commons.lbl.gov/display/IDMgmt/Attribute+Definitions
 | 
			
		||||
        self._assertion_params["ATTRIBUTES"] = [
 | 
			
		||||
            {
 | 
			
		||||
                "FriendlyName": "eduPersonPrincipalName",
 | 
			
		||||
                "Name": "urn:oid:1.3.6.1.4.1.5923.1.1.1.6",
 | 
			
		||||
                "Value": self._http_request.user.email,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "FriendlyName": "cn",
 | 
			
		||||
                "Name": "urn:oid:2.5.4.3",
 | 
			
		||||
                "Value": self._http_request.user.name,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "FriendlyName": "mail",
 | 
			
		||||
                "Name": "urn:oid:0.9.2342.19200300.100.1.3",
 | 
			
		||||
                "Value": self._http_request.user.email,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "FriendlyName": "displayName",
 | 
			
		||||
                "Name": "urn:oid:2.16.840.1.113730.3.1.241",
 | 
			
		||||
                "Value": self._http_request.user.username,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "FriendlyName": "uid",
 | 
			
		||||
                "Name": "urn:oid:0.9.2342.19200300.100.1.1",
 | 
			
		||||
                "Value": self._http_request.user.pk,
 | 
			
		||||
            },
 | 
			
		||||
        ]
 | 
			
		||||
        attributes = []
 | 
			
		||||
        from passbook.providers.saml.models import SAMLPropertyMapping
 | 
			
		||||
 | 
			
		||||
        for mapping in self._remote.property_mappings.all().select_subclasses():
 | 
			
		||||
            if isinstance(mapping, SAMLPropertyMapping):
 | 
			
		||||
                value = mapping.evaluate(
 | 
			
		||||
                    user=self._http_request.user,
 | 
			
		||||
                    request=self._http_request,
 | 
			
		||||
                    provider=self._remote,
 | 
			
		||||
                )
 | 
			
		||||
                mapping_payload = {
 | 
			
		||||
                    "Name": mapping.saml_name,
 | 
			
		||||
                    "ValueArray": [],
 | 
			
		||||
                    "FriendlyName": mapping.friendly_name,
 | 
			
		||||
                }
 | 
			
		||||
                for value in mapping.values:
 | 
			
		||||
                    mapping_payload["ValueArray"].append(
 | 
			
		||||
                        value.format(
 | 
			
		||||
                            user=self._http_request.user, request=self._http_request
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                self._assertion_params["ATTRIBUTES"].append(mapping_payload)
 | 
			
		||||
                if isinstance(value, list):
 | 
			
		||||
                    mapping_payload["ValueArray"] = value
 | 
			
		||||
                else:
 | 
			
		||||
                    mapping_payload["Value"] = value
 | 
			
		||||
                attributes.append(mapping_payload)
 | 
			
		||||
        self._assertion_params["ATTRIBUTES"] = attributes
 | 
			
		||||
        self._assertion_xml = get_assertion_xml(
 | 
			
		||||
            "saml/xml/assertions/generic.xml", self._assertion_params, signed=True
 | 
			
		||||
        )
 | 
			
		||||
@ -188,8 +161,9 @@ class Processor:
 | 
			
		||||
        request_acs_url = self._request_params["ACS_URL"]
 | 
			
		||||
 | 
			
		||||
        if self._remote.acs_url != request_acs_url:
 | 
			
		||||
            msg = "couldn't find ACS url '{}' in SAML2IDP_REMOTES " "setting.".format(
 | 
			
		||||
                request_acs_url
 | 
			
		||||
            msg = (
 | 
			
		||||
                f"ACS URL of {request_acs_url} doesn't match Provider "
 | 
			
		||||
                f"ACS URL of {self._remote.acs_url}."
 | 
			
		||||
            )
 | 
			
		||||
            self._logger.info(msg)
 | 
			
		||||
            raise CannotHandleAssertion(msg)
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
{% extends "generic/form.html" %}
 | 
			
		||||
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block beneath_form %}
 | 
			
		||||
<div class="form-group ">
 | 
			
		||||
    <label class="col-sm-2 control-label" for="friendly_name-2">
 | 
			
		||||
    </label>
 | 
			
		||||
    <div class="col-sm-10">
 | 
			
		||||
        <p>
 | 
			
		||||
            Expression using <a href="https://jinja.palletsprojects.com/en/2.11.x/templates/">Jinja</a>. Following variables are available:
 | 
			
		||||
            <ul>
 | 
			
		||||
                <li><code>user</code>: Passbook User Object (<a href="https://beryju.github.io/passbook/reference/property-mappings/user-object/">Reference</a>)</li>
 | 
			
		||||
                <li><code>request</code>: Django HTTP Request Object (<a href="https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects">Reference</a>) </li>
 | 
			
		||||
                <li><code>provider</code>: Passbook SAML Provider Object (<a href="https://github.com/BeryJu/passbook/blob/master/passbook/providers/saml/models.py#L16">Reference</a>) </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </p>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -6,7 +6,10 @@ import zlib
 | 
			
		||||
def decode_base64_and_inflate(b64string):
 | 
			
		||||
    """Base64 decode and ZLib decompress b64string"""
 | 
			
		||||
    decoded_data = base64.b64decode(b64string)
 | 
			
		||||
    try:
 | 
			
		||||
        return zlib.decompress(decoded_data, -15)
 | 
			
		||||
    except zlib.error:
 | 
			
		||||
        return decoded_data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def deflate_and_base64_encode(string_val):
 | 
			
		||||
 | 
			
		||||
@ -88,10 +88,5 @@ def get_response_xml(parameters, saml_provider: SAMLProvider, assertion_id=""):
 | 
			
		||||
    signature_xml = get_signature_xml()
 | 
			
		||||
    params["RESPONSE_SIGNATURE"] = signature_xml
 | 
			
		||||
 | 
			
		||||
    signed = sign_with_signxml(
 | 
			
		||||
        saml_provider.signing_key,
 | 
			
		||||
        raw_response,
 | 
			
		||||
        saml_provider.signing_cert,
 | 
			
		||||
        reference_uri=assertion_id,
 | 
			
		||||
    )
 | 
			
		||||
    signed = sign_with_signxml(raw_response, saml_provider, reference_uri=assertion_id,)
 | 
			
		||||
    return signed
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,6 @@
 | 
			
		||||
"""Signing code goes here."""
 | 
			
		||||
from typing import TYPE_CHECKING
 | 
			
		||||
 | 
			
		||||
from cryptography.hazmat.backends import default_backend
 | 
			
		||||
from cryptography.hazmat.primitives import serialization
 | 
			
		||||
from lxml import etree  # nosec
 | 
			
		||||
@ -7,25 +9,34 @@ from structlog import get_logger
 | 
			
		||||
 | 
			
		||||
from passbook.lib.utils.template import render_to_string
 | 
			
		||||
 | 
			
		||||
if TYPE_CHECKING:
 | 
			
		||||
    from passbook.providers.saml.models import SAMLProvider
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def sign_with_signxml(private_key, data, cert, reference_uri=None):
 | 
			
		||||
def sign_with_signxml(data: str, provider: "SAMLProvider", reference_uri=None) -> str:
 | 
			
		||||
    """Sign Data with signxml"""
 | 
			
		||||
    key = serialization.load_pem_private_key(
 | 
			
		||||
        str.encode("\n".join([x.strip() for x in private_key.split("\n")])),
 | 
			
		||||
        str.encode("\n".join([x.strip() for x in provider.signing_key.split("\n")])),
 | 
			
		||||
        password=None,
 | 
			
		||||
        backend=default_backend(),
 | 
			
		||||
    )
 | 
			
		||||
    # defused XML is not used here because it messes up XML namespaces
 | 
			
		||||
    # Data is trusted, so lxml is ok
 | 
			
		||||
    root = etree.fromstring(data)  # nosec
 | 
			
		||||
    signer = XMLSigner(c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#")
 | 
			
		||||
    signed = signer.sign(root, key=key, cert=[cert], reference_uri=reference_uri)
 | 
			
		||||
    XMLVerifier().verify(signed, x509_cert=cert)
 | 
			
		||||
    signer = XMLSigner(
 | 
			
		||||
        c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#",
 | 
			
		||||
        signature_algorithm=provider.signature_algorithm,
 | 
			
		||||
        digest_algorithm=provider.digest_algorithm,
 | 
			
		||||
    )
 | 
			
		||||
    signed = signer.sign(
 | 
			
		||||
        root, key=key, cert=[provider.signing_cert], reference_uri=reference_uri
 | 
			
		||||
    )
 | 
			
		||||
    XMLVerifier().verify(signed, x509_cert=provider.signing_cert)
 | 
			
		||||
    return etree.tostring(signed).decode("utf-8")  # nosec
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_signature_xml():
 | 
			
		||||
def get_signature_xml() -> str:
 | 
			
		||||
    """Returns XML Signature for subject."""
 | 
			
		||||
    return render_to_string("saml/xml/signature.xml", {})
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ from django.contrib.auth import logout
 | 
			
		||||
from django.contrib.auth.mixins import AccessMixin
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from django.core.validators import URLValidator
 | 
			
		||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpRequest
 | 
			
		||||
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
 | 
			
		||||
from django.shortcuts import get_object_or_404, redirect, render, reverse
 | 
			
		||||
from django.utils.datastructures import MultiValueDictKeyError
 | 
			
		||||
from django.utils.decorators import method_decorator
 | 
			
		||||
@ -27,7 +27,7 @@ LOGGER = get_logger()
 | 
			
		||||
URL_VALIDATOR = URLValidator(schemes=("http", "https"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _generate_response(request: HttpRequest, provider: SAMLProvider):
 | 
			
		||||
def _generate_response(request: HttpRequest, provider: SAMLProvider) -> HttpResponse:
 | 
			
		||||
    """Generate a SAML response using processor_instance and return it in the proper Django
 | 
			
		||||
    response."""
 | 
			
		||||
    try:
 | 
			
		||||
@ -58,13 +58,16 @@ class AccessRequiredView(AccessMixin, View):
 | 
			
		||||
 | 
			
		||||
    def _has_access(self) -> bool:
 | 
			
		||||
        """Check if user has access to application"""
 | 
			
		||||
        LOGGER.debug(
 | 
			
		||||
            "_has_access", user=self.request.user, app=self.provider.application
 | 
			
		||||
        )
 | 
			
		||||
        policy_engine = PolicyEngine(
 | 
			
		||||
            self.provider.application.policies.all(), self.request.user, self.request
 | 
			
		||||
        )
 | 
			
		||||
        policy_engine.build()
 | 
			
		||||
        return policy_engine.passing
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
    def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
 | 
			
		||||
        if not request.user.is_authenticated:
 | 
			
		||||
            return self.handle_no_permission()
 | 
			
		||||
        if not self._has_access():
 | 
			
		||||
@ -84,7 +87,7 @@ class LoginBeginView(AccessRequiredView):
 | 
			
		||||
    stores it in the session prior to enforcing login."""
 | 
			
		||||
 | 
			
		||||
    @method_decorator(csrf_exempt)
 | 
			
		||||
    def dispatch(self, request, application):
 | 
			
		||||
    def dispatch(self, request: HttpRequest, application: str) -> HttpResponse:
 | 
			
		||||
        if request.method == "POST":
 | 
			
		||||
            source = request.POST
 | 
			
		||||
        else:
 | 
			
		||||
@ -108,7 +111,9 @@ class LoginBeginView(AccessRequiredView):
 | 
			
		||||
class RedirectToSPView(AccessRequiredView):
 | 
			
		||||
    """Return autosubmit form"""
 | 
			
		||||
 | 
			
		||||
    def get(self, request, acs_url, saml_response, relay_state):
 | 
			
		||||
    def get(
 | 
			
		||||
        self, request: HttpRequest, acs_url: str, saml_response: str, relay_state: str
 | 
			
		||||
    ) -> HttpResponse:
 | 
			
		||||
        """Return autosubmit form"""
 | 
			
		||||
        return render(
 | 
			
		||||
            request,
 | 
			
		||||
@ -149,7 +154,7 @@ class LoginProcessView(AccessRequiredView):
 | 
			
		||||
        return HttpResponseBadRequest()
 | 
			
		||||
 | 
			
		||||
    # pylint: disable=unused-argument
 | 
			
		||||
    def post(self, request, application: str) -> HttpResponse:
 | 
			
		||||
    def post(self, request: HttpRequest, application: str) -> HttpResponse:
 | 
			
		||||
        """Handle post request, return back to ACS"""
 | 
			
		||||
        # User access gets checked in dispatch
 | 
			
		||||
        if request.POST.get("ACSUrl", None):
 | 
			
		||||
@ -178,7 +183,7 @@ class LogoutView(CSRFExemptMixin, AccessRequiredView):
 | 
			
		||||
    though it's technically not SAML 2.0)."""
 | 
			
		||||
 | 
			
		||||
    # pylint: disable=unused-argument
 | 
			
		||||
    def get(self, request, application):
 | 
			
		||||
    def get(self, request: HttpRequest, application: str) -> HttpResponse:
 | 
			
		||||
        """Perform logout"""
 | 
			
		||||
        logout(request)
 | 
			
		||||
 | 
			
		||||
@ -199,7 +204,7 @@ class SLOLogout(CSRFExemptMixin, AccessRequiredView):
 | 
			
		||||
    logs out the user and returns a standard logged-out page."""
 | 
			
		||||
 | 
			
		||||
    # pylint: disable=unused-argument
 | 
			
		||||
    def post(self, request, application):
 | 
			
		||||
    def post(self, request: HttpRequest, application: str) -> HttpResponse:
 | 
			
		||||
        """Perform logout"""
 | 
			
		||||
        request.session["SAMLRequest"] = request.POST["SAMLRequest"]
 | 
			
		||||
        # TODO: Parse SAML LogoutRequest from POST data, similar to login_process().
 | 
			
		||||
@ -214,7 +219,7 @@ class SLOLogout(CSRFExemptMixin, AccessRequiredView):
 | 
			
		||||
class DescriptorDownloadView(AccessRequiredView):
 | 
			
		||||
    """Replies with the XML Metadata IDSSODescriptor."""
 | 
			
		||||
 | 
			
		||||
    def get(self, request, application):
 | 
			
		||||
    def get(self, request: HttpRequest, application: str) -> HttpResponse:
 | 
			
		||||
        """Replies with the XML Metadata IDSSODescriptor."""
 | 
			
		||||
        entity_id = self.provider.issuer
 | 
			
		||||
        slo_url = request.build_absolute_uri(
 | 
			
		||||
@ -250,7 +255,7 @@ class InitiateLoginView(AccessRequiredView):
 | 
			
		||||
    """IdP-initiated Login"""
 | 
			
		||||
 | 
			
		||||
    # pylint: disable=unused-argument
 | 
			
		||||
    def get(self, request, application):
 | 
			
		||||
    def get(self, request: HttpRequest, application: str) -> HttpResponse:
 | 
			
		||||
        """Initiates an IdP-initiated link to a simple SP resource/target URL."""
 | 
			
		||||
        self.provider.processor.init_deep_link(request, "")
 | 
			
		||||
        self.provider.processor.is_idp_initiated = True
 | 
			
		||||
 | 
			
		||||
@ -62,7 +62,8 @@ class WSGILogger:
 | 
			
		||||
        if environ.get("QUERY_STRING") != "":
 | 
			
		||||
            query_string = f"?{environ.get('QUERY_STRING')}"
 | 
			
		||||
        self.logger.info(
 | 
			
		||||
            f"{environ.get('PATH_INFO', '')}{query_string}",
 | 
			
		||||
            "request",
 | 
			
		||||
            path=f"{environ.get('PATH_INFO', '')}{query_string}",
 | 
			
		||||
            host=host,
 | 
			
		||||
            method=environ.get("REQUEST_METHOD", ""),
 | 
			
		||||
            protocol=environ.get("SERVER_PROTOCOL", ""),
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@ class LDAPPropertyMappingSerializer(ModelSerializer):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = LDAPPropertyMapping
 | 
			
		||||
        fields = ["pk", "name", "ldap_property", "object_field"]
 | 
			
		||||
        fields = ["pk", "name", "expression", "object_field"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LDAPSourceViewSet(ModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ import ldap3.core.exceptions
 | 
			
		||||
from structlog import get_logger
 | 
			
		||||
 | 
			
		||||
from passbook.core.models import Group, User
 | 
			
		||||
from passbook.sources.ldap.models import LDAPSource
 | 
			
		||||
from passbook.sources.ldap.models import LDAPSource, LDAPPropertyMapping
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
@ -154,7 +154,10 @@ class Connector:
 | 
			
		||||
    ) -> Dict[str, Dict[Any, Any]]:
 | 
			
		||||
        properties = {"attributes": {}}
 | 
			
		||||
        for mapping in self._source.property_mappings.all().select_subclasses():
 | 
			
		||||
            properties[mapping.object_field] = attributes.get(mapping.ldap_property, "")
 | 
			
		||||
            mapping: LDAPPropertyMapping
 | 
			
		||||
            properties[mapping.object_field] = mapping.evaluate(
 | 
			
		||||
                user=None, request=None, ldap=attributes
 | 
			
		||||
            )
 | 
			
		||||
        if self._source.object_uniqueness_field in attributes:
 | 
			
		||||
            properties["attributes"]["ldap_uniq"] = attributes.get(
 | 
			
		||||
                self._source.object_uniqueness_field
 | 
			
		||||
 | 
			
		||||
@ -45,23 +45,17 @@ class LDAPSourceForm(forms.ModelForm):
 | 
			
		||||
            "policies": FilteredSelectMultiple(_("policies"), False),
 | 
			
		||||
            "property_mappings": FilteredSelectMultiple(_("Property Mappings"), False),
 | 
			
		||||
        }
 | 
			
		||||
        labels = {
 | 
			
		||||
            "server_uri": _("Server URI"),
 | 
			
		||||
            "bind_cn": _("Bind CN"),
 | 
			
		||||
            "start_tls": _("Enable Start TLS"),
 | 
			
		||||
            "base_dn": _("Base DN"),
 | 
			
		||||
            "additional_user_dn": _("Addition User DN"),
 | 
			
		||||
            "additional_group_dn": _("Addition Group DN"),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LDAPPropertyMappingForm(forms.ModelForm):
 | 
			
		||||
    """LDAP Property Mapping form"""
 | 
			
		||||
 | 
			
		||||
    template_name = "ldap/property_mapping_form.html"
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = LDAPPropertyMapping
 | 
			
		||||
        fields = ["name", "ldap_property", "object_field"]
 | 
			
		||||
        fields = ["name", "object_field", "expression"]
 | 
			
		||||
        widgets = {
 | 
			
		||||
            "name": forms.TextInput(),
 | 
			
		||||
            "ldap_property": forms.TextInput(),
 | 
			
		||||
 | 
			
		||||
@ -13,8 +13,9 @@ def create_default_ad_property_mappings(apps: Apps, schema_editor):
 | 
			
		||||
        "sAMAccountName": "username",
 | 
			
		||||
        "mail": "email",
 | 
			
		||||
    }
 | 
			
		||||
    db_alias = schema_editor.connection.alias
 | 
			
		||||
    for ldap_property, object_field in mapping.items():
 | 
			
		||||
        LDAPPropertyMapping.objects.get_or_create(
 | 
			
		||||
        LDAPPropertyMapping.objects.using(db_alias).get_or_create(
 | 
			
		||||
            ldap_property=ldap_property,
 | 
			
		||||
            object_field=object_field,
 | 
			
		||||
            defaults={
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										60
									
								
								passbook/sources/ldap/migrations/0006_auto_20200216_1116.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								passbook/sources/ldap/migrations/0006_auto_20200216_1116.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
# Generated by Django 2.2.9 on 2020-02-16 11:16
 | 
			
		||||
 | 
			
		||||
import django.core.validators
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("passbook_sources_ldap", "0005_auto_20191011_1059"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="ldappropertymapping",
 | 
			
		||||
            name="ldap_property",
 | 
			
		||||
            field=models.TextField(verbose_name="LDAP Property"),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="ldapsource",
 | 
			
		||||
            name="additional_group_dn",
 | 
			
		||||
            field=models.TextField(
 | 
			
		||||
                help_text="Prepended to Base DN for Group-queries.",
 | 
			
		||||
                verbose_name="Addition Group DN",
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="ldapsource",
 | 
			
		||||
            name="additional_user_dn",
 | 
			
		||||
            field=models.TextField(
 | 
			
		||||
                help_text="Prepended to Base DN for User-queries.",
 | 
			
		||||
                verbose_name="Addition User DN",
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="ldapsource",
 | 
			
		||||
            name="base_dn",
 | 
			
		||||
            field=models.TextField(verbose_name="Base DN"),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="ldapsource",
 | 
			
		||||
            name="bind_cn",
 | 
			
		||||
            field=models.TextField(verbose_name="Bind CN"),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="ldapsource",
 | 
			
		||||
            name="server_uri",
 | 
			
		||||
            field=models.TextField(
 | 
			
		||||
                validators=[
 | 
			
		||||
                    django.core.validators.URLValidator(schemes=["ldap", "ldaps"])
 | 
			
		||||
                ],
 | 
			
		||||
                verbose_name="Server URI",
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="ldapsource",
 | 
			
		||||
            name="start_tls",
 | 
			
		||||
            field=models.BooleanField(default=False, verbose_name="Enable Start TLS"),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -0,0 +1,46 @@
 | 
			
		||||
# Generated by Django 3.0.3 on 2020-02-17 16:19
 | 
			
		||||
 | 
			
		||||
from django.apps.registry import Apps
 | 
			
		||||
from django.db import migrations
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def cleanup_old_autogenerated(apps, schema_editor):
 | 
			
		||||
    LDAPPropertyMapping = apps.get_model("passbook_sources_ldap", "LDAPPropertyMapping")
 | 
			
		||||
    db_alias = schema_editor.connection.alias
 | 
			
		||||
    LDAPPropertyMapping.objects.using(db_alias).filter(
 | 
			
		||||
        name__startswith="Autogenerated"
 | 
			
		||||
    ).delete()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_default_ad_property_mappings(apps: Apps, schema_editor):
 | 
			
		||||
    LDAPPropertyMapping = apps.get_model("passbook_sources_ldap", "LDAPPropertyMapping")
 | 
			
		||||
    mapping = {
 | 
			
		||||
        "name": "{{ ldap.name }}",
 | 
			
		||||
        "first_name": "{{ ldap.givenName }}",
 | 
			
		||||
        "last_name": "{{ ldap.sn }}",
 | 
			
		||||
        "username": "{{ ldap.sAMAccountName }}",
 | 
			
		||||
        "email": "{{ ldap.mail }}",
 | 
			
		||||
    }
 | 
			
		||||
    db_alias = schema_editor.connection.alias
 | 
			
		||||
    for object_field, expression in mapping.items():
 | 
			
		||||
        LDAPPropertyMapping.objects.using(db_alias).get_or_create(
 | 
			
		||||
            expression=expression,
 | 
			
		||||
            object_field=object_field,
 | 
			
		||||
            defaults={
 | 
			
		||||
                "name": f"Autogenerated LDAP Mapping: {expression} -> {object_field}"
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("passbook_sources_ldap", "0006_auto_20200216_1116"),
 | 
			
		||||
        ("passbook_core", "0007_auto_20200217_1934"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RunPython(cleanup_old_autogenerated),
 | 
			
		||||
        migrations.RemoveField(model_name="ldappropertymapping", name="ldap_property",),
 | 
			
		||||
        migrations.RunPython(create_default_ad_property_mappings),
 | 
			
		||||
    ]
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
 | 
			
		||||
from django.core.validators import URLValidator
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from passbook.core.models import Group, PropertyMapping, Source
 | 
			
		||||
 | 
			
		||||
@ -10,17 +10,22 @@ from passbook.core.models import Group, PropertyMapping, Source
 | 
			
		||||
class LDAPSource(Source):
 | 
			
		||||
    """LDAP Authentication source"""
 | 
			
		||||
 | 
			
		||||
    server_uri = models.TextField(validators=[URLValidator(schemes=["ldap", "ldaps"])])
 | 
			
		||||
    bind_cn = models.TextField()
 | 
			
		||||
    server_uri = models.TextField(
 | 
			
		||||
        validators=[URLValidator(schemes=["ldap", "ldaps"])],
 | 
			
		||||
        verbose_name=_("Server URI"),
 | 
			
		||||
    )
 | 
			
		||||
    bind_cn = models.TextField(verbose_name=_("Bind CN"))
 | 
			
		||||
    bind_password = models.TextField()
 | 
			
		||||
    start_tls = models.BooleanField(default=False)
 | 
			
		||||
    start_tls = models.BooleanField(default=False, verbose_name=_("Enable Start TLS"))
 | 
			
		||||
 | 
			
		||||
    base_dn = models.TextField()
 | 
			
		||||
    base_dn = models.TextField(verbose_name=_("Base DN"))
 | 
			
		||||
    additional_user_dn = models.TextField(
 | 
			
		||||
        help_text=_("Prepended to Base DN for User-queries.")
 | 
			
		||||
        help_text=_("Prepended to Base DN for User-queries."),
 | 
			
		||||
        verbose_name=_("Addition User DN"),
 | 
			
		||||
    )
 | 
			
		||||
    additional_group_dn = models.TextField(
 | 
			
		||||
        help_text=_("Prepended to Base DN for Group-queries.")
 | 
			
		||||
        help_text=_("Prepended to Base DN for Group-queries."),
 | 
			
		||||
        verbose_name=_("Addition Group DN"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    user_object_filter = models.TextField(
 | 
			
		||||
@ -54,13 +59,12 @@ class LDAPSource(Source):
 | 
			
		||||
class LDAPPropertyMapping(PropertyMapping):
 | 
			
		||||
    """Map LDAP Property to User or Group object"""
 | 
			
		||||
 | 
			
		||||
    ldap_property = models.TextField()
 | 
			
		||||
    object_field = models.TextField()
 | 
			
		||||
 | 
			
		||||
    form = "passbook.sources.ldap.forms.LDAPPropertyMappingForm"
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"LDAP Property Mapping {self.ldap_property} -> {self.object_field}"
 | 
			
		||||
        return f"LDAP Property Mapping {self.expression} -> {self.object_field}"
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,18 @@
 | 
			
		||||
{% extends "generic/form.html" %}
 | 
			
		||||
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block beneath_form %}
 | 
			
		||||
<div class="form-group ">
 | 
			
		||||
    <label class="col-sm-2 control-label" for="friendly_name-2">
 | 
			
		||||
    </label>
 | 
			
		||||
    <div class="col-sm-10">
 | 
			
		||||
        <p>
 | 
			
		||||
            Expression using <a href="https://jinja.palletsprojects.com/en/2.11.x/templates/">Jinja</a>. Following variables are available:
 | 
			
		||||
            <ul>
 | 
			
		||||
                <li><code>ldap</code>: A Dictionary of all values retrieved from LDAP.</li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </p>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -38,12 +38,6 @@ class OAuthSourceForm(forms.ModelForm):
 | 
			
		||||
            "provider_type": forms.Select(choices=MANAGER.get_name_tuple()),
 | 
			
		||||
            "policies": FilteredSelectMultiple(_("policies"), False),
 | 
			
		||||
        }
 | 
			
		||||
        labels = {
 | 
			
		||||
            "request_token_url": _("Request Token URL"),
 | 
			
		||||
            "authorization_url": _("Authorization URL"),
 | 
			
		||||
            "access_token_url": _("Access Token URL"),
 | 
			
		||||
            "profile_url": _("Profile URL"),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GitHubOAuthSourceForm(OAuthSourceForm):
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										35
									
								
								passbook/sources/oauth/migrations/0002_auto_20200217_1526.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								passbook/sources/oauth/migrations/0002_auto_20200217_1526.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
# Generated by Django 3.0.3 on 2020-02-17 15:26
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("passbook_sources_oauth", "0001_initial"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="oauthsource",
 | 
			
		||||
            name="access_token_url",
 | 
			
		||||
            field=models.CharField(max_length=255, verbose_name="Access Token URL"),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="oauthsource",
 | 
			
		||||
            name="authorization_url",
 | 
			
		||||
            field=models.CharField(max_length=255, verbose_name="Authorization URL"),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="oauthsource",
 | 
			
		||||
            name="profile_url",
 | 
			
		||||
            field=models.CharField(max_length=255, verbose_name="Profile URL"),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="oauthsource",
 | 
			
		||||
            name="request_token_url",
 | 
			
		||||
            field=models.CharField(
 | 
			
		||||
                blank=True, max_length=255, verbose_name="Request Token URL"
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.urls import reverse, reverse_lazy
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from passbook.core.models import Source, UserSettings, UserSourceConnection
 | 
			
		||||
from passbook.sources.oauth.clients import get_client
 | 
			
		||||
@ -12,10 +12,16 @@ class OAuthSource(Source):
 | 
			
		||||
    """Configuration for OAuth provider."""
 | 
			
		||||
 | 
			
		||||
    provider_type = models.CharField(max_length=255)
 | 
			
		||||
    request_token_url = models.CharField(blank=True, max_length=255)
 | 
			
		||||
    authorization_url = models.CharField(max_length=255)
 | 
			
		||||
    access_token_url = models.CharField(max_length=255)
 | 
			
		||||
    profile_url = models.CharField(max_length=255)
 | 
			
		||||
    request_token_url = models.CharField(
 | 
			
		||||
        blank=True, max_length=255, verbose_name=_("Request Token URL")
 | 
			
		||||
    )
 | 
			
		||||
    authorization_url = models.CharField(
 | 
			
		||||
        max_length=255, verbose_name=_("Authorization URL")
 | 
			
		||||
    )
 | 
			
		||||
    access_token_url = models.CharField(
 | 
			
		||||
        max_length=255, verbose_name=_("Access Token URL")
 | 
			
		||||
    )
 | 
			
		||||
    profile_url = models.CharField(max_length=255, verbose_name=_("Profile URL"))
 | 
			
		||||
    consumer_key = models.TextField()
 | 
			
		||||
    consumer_secret = models.TextField()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -28,11 +28,6 @@ class SAMLSourceForm(forms.ModelForm):
 | 
			
		||||
            "auto_logout",
 | 
			
		||||
            "signing_cert",
 | 
			
		||||
        ]
 | 
			
		||||
        labels = {
 | 
			
		||||
            "entity_id": "Entity ID",
 | 
			
		||||
            "idp_url": "IDP URL",
 | 
			
		||||
            "idp_logout_url": "IDP Logout URL",
 | 
			
		||||
        }
 | 
			
		||||
        widgets = {
 | 
			
		||||
            "name": forms.TextInput(),
 | 
			
		||||
            "policies": FilteredSelectMultiple(_("policies"), False),
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								passbook/sources/saml/migrations/0004_auto_20200217_1526.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								passbook/sources/saml/migrations/0004_auto_20200217_1526.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
# Generated by Django 3.0.3 on 2020-02-17 15:26
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("passbook_sources_saml", "0003_auto_20191107_1550"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="samlsource",
 | 
			
		||||
            name="entity_id",
 | 
			
		||||
            field=models.TextField(blank=True, default=None, verbose_name="Entity ID"),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="samlsource",
 | 
			
		||||
            name="idp_logout_url",
 | 
			
		||||
            field=models.URLField(
 | 
			
		||||
                blank=True, default=None, null=True, verbose_name="IDP Logout URL"
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="samlsource",
 | 
			
		||||
            name="idp_url",
 | 
			
		||||
            field=models.URLField(verbose_name="IDP URL"),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
"""saml sp models"""
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from passbook.core.models import Source
 | 
			
		||||
 | 
			
		||||
@ -9,9 +9,11 @@ from passbook.core.models import Source
 | 
			
		||||
class SAMLSource(Source):
 | 
			
		||||
    """SAML2 Source"""
 | 
			
		||||
 | 
			
		||||
    entity_id = models.TextField(blank=True, default=None)
 | 
			
		||||
    idp_url = models.URLField()
 | 
			
		||||
    idp_logout_url = models.URLField(default=None, blank=True, null=True)
 | 
			
		||||
    entity_id = models.TextField(blank=True, default=None, verbose_name=_("Entity ID"))
 | 
			
		||||
    idp_url = models.URLField(verbose_name=_("IDP URL"))
 | 
			
		||||
    idp_logout_url = models.URLField(
 | 
			
		||||
        default=None, blank=True, null=True, verbose_name=_("IDP Logout URL")
 | 
			
		||||
    )
 | 
			
		||||
    auto_logout = models.BooleanField(default=False)
 | 
			
		||||
    signing_cert = models.TextField()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user