Compare commits
	
		
			30 Commits
		
	
	
		
			version/0.
			...
			version/0.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| dce1edbe53 | |||
| 264d43827a | |||
| 6207226bdf | |||
| ebf33f39c9 | |||
| 696cd1f247 | |||
| b7b3abc462 | |||
| 575739d07c | |||
| 2d7e70eebf | |||
| 387f3c981f | |||
| 865435fb25 | |||
| b10c5306b9 | |||
| 7c706369cd | |||
| 20dd6355c1 | |||
| ba8d5d6e27 | |||
| c448f87027 | |||
| 2b8c70a61f | |||
| 9d7ed9a0ed | |||
| ff69b4affe | |||
| d77afd1ded | |||
| c3909f9196 | |||
| fa55ba5ef0 | |||
| 766518ee0e | |||
| 74b2b26a20 | |||
| 4ebbc6f065 | |||
| 3bd1eadd51 | |||
| 8eb3f0f708 | |||
| 31ea2e7139 | |||
| 323b4b4a5d | |||
| 7b8e1bea92 | |||
| f986dc89ad | 
@ -1,5 +1,5 @@
 | 
			
		||||
[bumpversion]
 | 
			
		||||
current_version = 0.7.5-beta
 | 
			
		||||
current_version = 0.7.10-beta
 | 
			
		||||
tag = True
 | 
			
		||||
commit = True
 | 
			
		||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)
 | 
			
		||||
@ -19,7 +19,7 @@ values =
 | 
			
		||||
 | 
			
		||||
[bumpversion:file:helm/Chart.yaml]
 | 
			
		||||
 | 
			
		||||
[bumpversion:file:.gitlab-ci.yml]
 | 
			
		||||
[bumpversion:file:.github/workflows/release.yml]
 | 
			
		||||
 | 
			
		||||
[bumpversion:file:passbook/__init__.py]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										147
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,147 @@
 | 
			
		||||
name: passbook-ci
 | 
			
		||||
on:
 | 
			
		||||
  - push
 | 
			
		||||
env:
 | 
			
		||||
  POSTGRES_DB: passbook
 | 
			
		||||
  POSTGRES_USER: passbook
 | 
			
		||||
  POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  # Linting
 | 
			
		||||
  pylint:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v1
 | 
			
		||||
      - uses: actions/setup-python@v1
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.local/share/virtualenvs/
 | 
			
		||||
          key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            ${{ runner.os }}-pipenv-
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: pip install -U pip pipenv && pipenv install --dev
 | 
			
		||||
      - name: Lint with pylint
 | 
			
		||||
        run: pipenv run pylint passbook
 | 
			
		||||
  black:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v1
 | 
			
		||||
      - uses: actions/setup-python@v1
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.local/share/virtualenvs/
 | 
			
		||||
          key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            ${{ runner.os }}-pipenv-
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: pip install -U pip pipenv && pipenv install --dev
 | 
			
		||||
      - name: Lint with black
 | 
			
		||||
        run: pipenv run black --check passbook
 | 
			
		||||
  prospector:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v1
 | 
			
		||||
      - uses: actions/setup-python@v1
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.local/share/virtualenvs/
 | 
			
		||||
          key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            ${{ runner.os }}-pipenv-
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: pip install -U pip pipenv && pipenv install --dev
 | 
			
		||||
      - name: Lint with prospector
 | 
			
		||||
        run: pipenv run prospector
 | 
			
		||||
  bandit:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v1
 | 
			
		||||
      - uses: actions/setup-python@v1
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.local/share/virtualenvs/
 | 
			
		||||
          key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            ${{ runner.os }}-pipenv-
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: pip install -U pip pipenv && pipenv install --dev
 | 
			
		||||
      - name: Lint with bandit
 | 
			
		||||
        run: pipenv run bandit -r passbook
 | 
			
		||||
  # Actual CI tests
 | 
			
		||||
  migrations:
 | 
			
		||||
    needs:
 | 
			
		||||
      - pylint
 | 
			
		||||
      - black
 | 
			
		||||
      - prospector
 | 
			
		||||
    services:
 | 
			
		||||
      postgres:
 | 
			
		||||
        image: postgres:latest
 | 
			
		||||
        env:
 | 
			
		||||
          POSTGRES_DB: passbook
 | 
			
		||||
          POSTGRES_USER: passbook
 | 
			
		||||
          POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
 | 
			
		||||
        ports:
 | 
			
		||||
          - 5432:5432
 | 
			
		||||
      redis:
 | 
			
		||||
        image: redis:latest
 | 
			
		||||
        ports:
 | 
			
		||||
          - 6379:6379
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v1
 | 
			
		||||
      - uses: actions/setup-python@v1
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.local/share/virtualenvs/
 | 
			
		||||
          key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            ${{ runner.os }}-pipenv-
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: pip install -U pip pipenv && pipenv install --dev
 | 
			
		||||
      - name: Run migrations
 | 
			
		||||
        run: pipenv run ./manage.py migrate
 | 
			
		||||
  coverage:
 | 
			
		||||
    needs:
 | 
			
		||||
      - pylint
 | 
			
		||||
      - black
 | 
			
		||||
      - prospector
 | 
			
		||||
    services:
 | 
			
		||||
      postgres:
 | 
			
		||||
        image: postgres:latest
 | 
			
		||||
        env:
 | 
			
		||||
          POSTGRES_DB: passbook
 | 
			
		||||
          POSTGRES_USER: passbook
 | 
			
		||||
          POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
 | 
			
		||||
        ports:
 | 
			
		||||
          - 5432:5432
 | 
			
		||||
      redis:
 | 
			
		||||
        image: redis:latest
 | 
			
		||||
        ports:
 | 
			
		||||
          - 6379:6379
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v1
 | 
			
		||||
      - uses: actions/setup-python@v1
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.7'
 | 
			
		||||
      - uses: actions/cache@v1
 | 
			
		||||
        with:
 | 
			
		||||
          path: ~/.local/share/virtualenvs/
 | 
			
		||||
          key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
 | 
			
		||||
          restore-keys: |
 | 
			
		||||
            ${{ runner.os }}-pipenv-
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: pip install -U pip pipenv && pipenv install --dev
 | 
			
		||||
      - name: Run coverage
 | 
			
		||||
        run: pipenv run ./scripts/coverage.sh
 | 
			
		||||
							
								
								
									
										68
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
			
		||||
name: passbook-release
 | 
			
		||||
on:
 | 
			
		||||
  release
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  # Build
 | 
			
		||||
  build-server:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v1
 | 
			
		||||
      - name: Docker Login Registry
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
 | 
			
		||||
        run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
 | 
			
		||||
      - name: Building Docker Image
 | 
			
		||||
        run: docker build
 | 
			
		||||
          --no-cache
 | 
			
		||||
          -t beryju/passbook:0.7.10-beta
 | 
			
		||||
          -t beryju/passbook:latest
 | 
			
		||||
          -f Dockerfile .
 | 
			
		||||
      - name: Push Docker Container to Registry (versioned)
 | 
			
		||||
        run: docker push beryju/passbook:0.7.10-beta
 | 
			
		||||
      - name: Push Docker Container to Registry (latest)
 | 
			
		||||
        run: docker push beryju/passbook:latest
 | 
			
		||||
  build-static:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    services:
 | 
			
		||||
      postgres:
 | 
			
		||||
        image: postgres:latest
 | 
			
		||||
        env:
 | 
			
		||||
          POSTGRES_DB: passbook
 | 
			
		||||
          POSTGRES_USER: passbook
 | 
			
		||||
          POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
 | 
			
		||||
      redis:
 | 
			
		||||
        image: redis:latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v1
 | 
			
		||||
      - name: Docker Login Registry
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
 | 
			
		||||
        run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
 | 
			
		||||
      - name: Building Docker Image
 | 
			
		||||
        run: docker build
 | 
			
		||||
          --no-cache
 | 
			
		||||
          --network=$(docker network ls | grep github | awk '{print $1}')
 | 
			
		||||
          -t beryju/passbook-static:0.7.10-beta
 | 
			
		||||
          -t beryju/passbook-static:latest
 | 
			
		||||
          -f static.Dockerfile .
 | 
			
		||||
      - name: Push Docker Container to Registry (versioned)
 | 
			
		||||
        run: docker push beryju/passbook-static:0.7.10-beta
 | 
			
		||||
      - name: Push Docker Container to Registry (latest)
 | 
			
		||||
        run: docker push beryju/passbook-static:latest
 | 
			
		||||
  test-release:
 | 
			
		||||
    needs:
 | 
			
		||||
      - build-server
 | 
			
		||||
      - build-static
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v1
 | 
			
		||||
      - name: Run test suite in final docker images
 | 
			
		||||
        run: |
 | 
			
		||||
          export PASSBOOK_DOMAIN=localhost
 | 
			
		||||
          docker-compose pull
 | 
			
		||||
          docker-compose up --no-start
 | 
			
		||||
          docker-compose start postgresql redis
 | 
			
		||||
          docker-compose run -u root server bash -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test"
 | 
			
		||||
							
								
								
									
										60
									
								
								.github/workflows/tag.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								.github/workflows/tag.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    tags:
 | 
			
		||||
    - 'version/*'
 | 
			
		||||
 | 
			
		||||
name: passbook-version-tag
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    name: Create Release from Tag
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@master
 | 
			
		||||
      - name: Pre-release test
 | 
			
		||||
        run: |
 | 
			
		||||
          export PASSBOOK_DOMAIN=localhost
 | 
			
		||||
          docker-compose pull
 | 
			
		||||
          docker build \
 | 
			
		||||
            --no-cache \
 | 
			
		||||
            -t beryju/passbook:latest \
 | 
			
		||||
            -f Dockerfile .
 | 
			
		||||
          docker-compose up --no-start
 | 
			
		||||
          docker-compose start postgresql redis
 | 
			
		||||
          docker-compose run -u root server bash -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test"
 | 
			
		||||
      - name: Install Helm
 | 
			
		||||
        run: |
 | 
			
		||||
          apt update && apt install -y curl
 | 
			
		||||
          curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
 | 
			
		||||
      - name: Helm package
 | 
			
		||||
        run: |
 | 
			
		||||
          helm dependency update helm/
 | 
			
		||||
          helm package helm/
 | 
			
		||||
          mv passbook-*.tgz passbook-chart.tgz
 | 
			
		||||
      - name: Extract verison number
 | 
			
		||||
        id: get_version
 | 
			
		||||
        uses: actions/github-script@0.2.0
 | 
			
		||||
        with:
 | 
			
		||||
          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          script: |
 | 
			
		||||
            return context.payload.ref.replace(/\/refs\/tags\/version\//, '');
 | 
			
		||||
      - name: Create Release
 | 
			
		||||
        id: create_release
 | 
			
		||||
        uses: actions/create-release@v1.0.0
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
        with:
 | 
			
		||||
          tag_name: ${{ github.ref }}
 | 
			
		||||
          release_name: Release ${{ steps.get_version.outputs.result }}
 | 
			
		||||
          draft: false
 | 
			
		||||
          prerelease: false
 | 
			
		||||
      - name: Upload packaged Helm Chart
 | 
			
		||||
        id: upload-release-asset
 | 
			
		||||
        uses: actions/upload-release-asset@v1.0.1
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
        with:
 | 
			
		||||
          upload_url: ${{ steps.create_release.outputs.upload_url }}
 | 
			
		||||
          asset_path: ./passbook-chart.tgz
 | 
			
		||||
          asset_name: passbook-chart.tgz
 | 
			
		||||
          asset_content_type: application/gzip
 | 
			
		||||
							
								
								
									
										160
									
								
								.gitlab-ci.yml
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								.gitlab-ci.yml
									
									
									
									
									
								
							@ -1,160 +0,0 @@
 | 
			
		||||
# Global Variables
 | 
			
		||||
stages:
 | 
			
		||||
  - build-base-image
 | 
			
		||||
  - build-dev-image
 | 
			
		||||
  - test
 | 
			
		||||
  - build
 | 
			
		||||
  - package
 | 
			
		||||
  - post-release
 | 
			
		||||
image: docker.beryju.org/passbook/dev:latest
 | 
			
		||||
 | 
			
		||||
variables:
 | 
			
		||||
  POSTGRES_DB: passbook
 | 
			
		||||
  POSTGRES_USER: passbook
 | 
			
		||||
  POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
 | 
			
		||||
 | 
			
		||||
before_script:
 | 
			
		||||
  - pip install pipenv
 | 
			
		||||
  # Ensure all dependencies are installed, even those not included in passbook/dev
 | 
			
		||||
  # According to pipenv docs, -d outputs all packages, however it actually does not
 | 
			
		||||
  - pipenv lock -r > requirements-all.txt
 | 
			
		||||
  - pipenv lock -rd >> requirements-all.txt
 | 
			
		||||
  - pip install -r requirements-all.txt
 | 
			
		||||
 | 
			
		||||
create-base-image:
 | 
			
		||||
  image:
 | 
			
		||||
    name: gcr.io/kaniko-project/executor:debug
 | 
			
		||||
    entrypoint: [""]
 | 
			
		||||
  before_script:
 | 
			
		||||
    - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
 | 
			
		||||
  script:
 | 
			
		||||
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/base.Dockerfile --destination docker.beryju.org/passbook/base:latest
 | 
			
		||||
  stage: build-base-image
 | 
			
		||||
  only:
 | 
			
		||||
    refs:
 | 
			
		||||
      - tags
 | 
			
		||||
      - /^version/.*$/
 | 
			
		||||
 | 
			
		||||
build-dev-image:
 | 
			
		||||
  image:
 | 
			
		||||
    name: gcr.io/kaniko-project/executor:debug
 | 
			
		||||
    entrypoint: [""]
 | 
			
		||||
  before_script:
 | 
			
		||||
    - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
 | 
			
		||||
  script:
 | 
			
		||||
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dev.Dockerfile --destination docker.beryju.org/passbook/dev:latest
 | 
			
		||||
  stage: build-dev-image
 | 
			
		||||
  only:
 | 
			
		||||
    refs:
 | 
			
		||||
      - tags
 | 
			
		||||
      - /^version/.*$/
 | 
			
		||||
 | 
			
		||||
isort:
 | 
			
		||||
  script:
 | 
			
		||||
    - isort -c -sg env
 | 
			
		||||
  stage: test
 | 
			
		||||
  services:
 | 
			
		||||
    - postgres:latest
 | 
			
		||||
    - redis:latest
 | 
			
		||||
migrations:
 | 
			
		||||
  script:
 | 
			
		||||
    - python manage.py migrate
 | 
			
		||||
  stage: test
 | 
			
		||||
  services:
 | 
			
		||||
    - postgres:latest
 | 
			
		||||
    - redis:latest
 | 
			
		||||
prospector:
 | 
			
		||||
  script:
 | 
			
		||||
    - prospector
 | 
			
		||||
  stage: test
 | 
			
		||||
  services:
 | 
			
		||||
    - postgres:latest
 | 
			
		||||
    - redis:latest
 | 
			
		||||
pylint:
 | 
			
		||||
  script:
 | 
			
		||||
    - pylint passbook
 | 
			
		||||
  stage: test
 | 
			
		||||
  services:
 | 
			
		||||
    - postgres:latest
 | 
			
		||||
    - redis:latest
 | 
			
		||||
coverage:
 | 
			
		||||
  script:
 | 
			
		||||
    - ./scripts/coverage.sh
 | 
			
		||||
  stage: test
 | 
			
		||||
  services:
 | 
			
		||||
    - postgres:latest
 | 
			
		||||
    - redis:latest
 | 
			
		||||
 | 
			
		||||
build-passbook-server:
 | 
			
		||||
  stage: build
 | 
			
		||||
  image:
 | 
			
		||||
    name: gcr.io/kaniko-project/executor:debug
 | 
			
		||||
    entrypoint: [""]
 | 
			
		||||
  before_script:
 | 
			
		||||
    - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
 | 
			
		||||
  script:
 | 
			
		||||
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.beryju.org/passbook/server:latest --destination docker.beryju.org/passbook/server:0.7.5-beta
 | 
			
		||||
  only:
 | 
			
		||||
    - tags
 | 
			
		||||
    - /^version/.*$/
 | 
			
		||||
build-docs:
 | 
			
		||||
  stage: build
 | 
			
		||||
  image:
 | 
			
		||||
    name: gcr.io/kaniko-project/executor:debug
 | 
			
		||||
    entrypoint: [""]
 | 
			
		||||
  before_script:
 | 
			
		||||
    - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
 | 
			
		||||
  script:
 | 
			
		||||
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/docs/Dockerfile --destination docker.beryju.org/passbook/docs:latest --destination docker.beryju.org/passbook/docs:0.7.5-beta
 | 
			
		||||
  only:
 | 
			
		||||
    - tags
 | 
			
		||||
    - /^version/.*$/
 | 
			
		||||
build-passbook-static:
 | 
			
		||||
  stage: build
 | 
			
		||||
  image:
 | 
			
		||||
    name: gcr.io/kaniko-project/executor:debug
 | 
			
		||||
    entrypoint: [""]
 | 
			
		||||
  before_script:
 | 
			
		||||
    - echo "{\"auths\":{\"docker.beryju.org\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
 | 
			
		||||
  script:
 | 
			
		||||
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/static.Dockerfile --destination docker.beryju.org/passbook/static:latest --destination docker.beryju.org/passbook/static:0.7.5-beta
 | 
			
		||||
  only:
 | 
			
		||||
    - tags
 | 
			
		||||
    - /^version/.*$/
 | 
			
		||||
  # running collectstatic fully initialises django, hence we need that databases
 | 
			
		||||
  services:
 | 
			
		||||
    - postgres:latest
 | 
			
		||||
    - redis:latest
 | 
			
		||||
 | 
			
		||||
package-helm:
 | 
			
		||||
  image: debian:stretch-slim
 | 
			
		||||
  stage: package
 | 
			
		||||
  before_script:
 | 
			
		||||
    - apt update && apt install -y curl
 | 
			
		||||
    - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
 | 
			
		||||
  script:
 | 
			
		||||
    - helm dependency update helm
 | 
			
		||||
    - helm package helm
 | 
			
		||||
  artifacts:
 | 
			
		||||
    paths:
 | 
			
		||||
      - passbook-*.tgz
 | 
			
		||||
    expire_in: 1 week
 | 
			
		||||
  only:
 | 
			
		||||
    - tags
 | 
			
		||||
    - /^version/.*$/
 | 
			
		||||
 | 
			
		||||
notify-sentry:
 | 
			
		||||
  image: getsentry/sentry-cli
 | 
			
		||||
  stage: post-release
 | 
			
		||||
  variables:
 | 
			
		||||
    SENTRY_URL: https://sentry.beryju.org
 | 
			
		||||
    SENTRY_ORG: beryjuorg
 | 
			
		||||
    SENTRY_PROJECT: passbook
 | 
			
		||||
  before_script:
 | 
			
		||||
    - apk add curl
 | 
			
		||||
  script:
 | 
			
		||||
    - sentry-cli releases new passbook@0.7.5-beta
 | 
			
		||||
    - sentry-cli releases set-commits --auto passbook@0.7.5-beta
 | 
			
		||||
  only:
 | 
			
		||||
    - tags
 | 
			
		||||
    - /^version/.*$/
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
[MASTER]
 | 
			
		||||
 | 
			
		||||
disable=redefined-outer-name,arguments-differ,no-self-use,cyclic-import,fixme,locally-disabled,unpacking-non-sequence,too-many-ancestors,too-many-branches,too-few-public-methods
 | 
			
		||||
disable=redefined-outer-name,arguments-differ,no-self-use,cyclic-import,fixme,locally-disabled,unpacking-non-sequence,too-many-ancestors,too-many-branches,too-few-public-methods,import-outside-toplevel,bad-continuation
 | 
			
		||||
load-plugins=pylint_django,pylint.extensions.bad_builtin
 | 
			
		||||
extension-pkg-whitelist=lxml
 | 
			
		||||
const-rgx=[a-zA-Z0-9_]{1,40}$
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										24
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								Dockerfile
									
									
									
									
									
								
							@ -1,4 +1,26 @@
 | 
			
		||||
FROM docker.beryju.org/passbook/base:latest
 | 
			
		||||
FROM python:3.7-slim-buster as locker
 | 
			
		||||
 | 
			
		||||
COPY ./Pipfile /app/
 | 
			
		||||
COPY ./Pipfile.lock /app/
 | 
			
		||||
 | 
			
		||||
WORKDIR /app/
 | 
			
		||||
 | 
			
		||||
RUN pip install pipenv && \
 | 
			
		||||
    pipenv lock -r > requirements.txt && \
 | 
			
		||||
    pipenv lock -rd > requirements-dev.txt
 | 
			
		||||
 | 
			
		||||
FROM python:3.7-slim-buster
 | 
			
		||||
 | 
			
		||||
COPY --from=locker /app/requirements.txt /app/
 | 
			
		||||
COPY --from=locker /app/requirements-dev.txt /app/
 | 
			
		||||
 | 
			
		||||
WORKDIR /app/
 | 
			
		||||
 | 
			
		||||
RUN apt-get update && \
 | 
			
		||||
    apt-get install -y --no-install-recommends postgresql-client-11 && \
 | 
			
		||||
    rm -rf /var/lib/apt/ && \
 | 
			
		||||
    pip install -r requirements.txt  --no-cache-dir && \
 | 
			
		||||
    adduser --system --no-create-home --uid 1000 --group --home /app passbook
 | 
			
		||||
 | 
			
		||||
COPY ./passbook/ /app/passbook
 | 
			
		||||
COPY ./manage.py /app/
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								Pipfile
									
									
									
									
									
								
							@ -23,7 +23,7 @@ django-rest-framework = "*"
 | 
			
		||||
django-storages = "*"
 | 
			
		||||
djangorestframework-guardian = "*"
 | 
			
		||||
drf-yasg = "*"
 | 
			
		||||
kombu = "==4.5.0"
 | 
			
		||||
kombu = "*"
 | 
			
		||||
ldap3 = "*"
 | 
			
		||||
lxml = "*"
 | 
			
		||||
oauthlib = "*"
 | 
			
		||||
@ -51,8 +51,11 @@ bumpversion = "*"
 | 
			
		||||
colorama = "*"
 | 
			
		||||
coverage = "*"
 | 
			
		||||
django-debug-toolbar = "*"
 | 
			
		||||
isort = "*"
 | 
			
		||||
prospector = "*"
 | 
			
		||||
pylint = "==2.3.1"
 | 
			
		||||
pylint = "*"
 | 
			
		||||
pylint-django = "*"
 | 
			
		||||
unittest-xml-reporting = "*"
 | 
			
		||||
black = "*"
 | 
			
		||||
 | 
			
		||||
[pipenv]
 | 
			
		||||
allow_prereleases = true
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										337
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										337
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
    "_meta": {
 | 
			
		||||
        "hash": {
 | 
			
		||||
            "sha256": "865b57ef5ef326de114d39d8505f60f19b5f7e42a50d988ea3fc9dfc9b9371ec"
 | 
			
		||||
            "sha256": "138816efaba5be0b175cfd5b5e6a0b58e5ba551567f0efb441740344da3986d8"
 | 
			
		||||
        },
 | 
			
		||||
        "pipfile-spec": 6,
 | 
			
		||||
        "requires": {
 | 
			
		||||
@ -46,26 +46,26 @@
 | 
			
		||||
        },
 | 
			
		||||
        "boto3": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:d280f2bf7dc373e8aeab296f81aadefabf8780ff8c8ad27cdc36f8f112ca95ed",
 | 
			
		||||
                "sha256:edbf4636e700c46e49f555ac87ab48b8c385fde604528db15fc5189d5a73dc72"
 | 
			
		||||
                "sha256:982823e7c992d27e5954c81db93238ffc42c7a1210d863b4f5e048fdc088040e",
 | 
			
		||||
                "sha256:f05ee90a738c2f1ec8088121030229f26ef6a809fb9a1338de2118fd088dd99a"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.10.33"
 | 
			
		||||
            "version": "==1.10.45"
 | 
			
		||||
        },
 | 
			
		||||
        "botocore": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4861785b52b0b3f97da91613c31f8e501f12517c9c79482b44efbdb56b69aefc",
 | 
			
		||||
                "sha256:9cc87d7906693c9c8fe862c574a1bebbe22a0475d6991e9b7251bc93cb1954d9"
 | 
			
		||||
                "sha256:88ee646f7a0fe6a418681c6f119a590fae23d8439c48c2aec6878f7f89430b1f",
 | 
			
		||||
                "sha256:f48ba1ef04b25323c1d27fa6399795baa0ca9d316911b87be4d33acda5cef07c"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.13.33"
 | 
			
		||||
            "version": "==1.13.45"
 | 
			
		||||
        },
 | 
			
		||||
        "celery": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9",
 | 
			
		||||
                "sha256:528e56767ae7e43a16cfef24ee1062491f5754368d38fcfffa861cdb9ef219be"
 | 
			
		||||
                "sha256:7c544f37a84a5eadc44cab1aa8c9580dff94636bb81978cdf9bf8012d9ea7d8f",
 | 
			
		||||
                "sha256:d3363bb5df72d74420986a435449f3c3979285941dff57d5d97ecba352a0e3e2"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==4.3.0"
 | 
			
		||||
            "version": "==4.4.0"
 | 
			
		||||
        },
 | 
			
		||||
        "certifi": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -169,19 +169,19 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:a4ad4f6f9c6a4b7af7e2deec8d0cbff28501852e5010d6c2dc695d3d1fae7ca0",
 | 
			
		||||
                "sha256:fa98ec9cc9bf5d72a08ebf3654a9452e761fbb8566e3f80de199cbc15477e891"
 | 
			
		||||
                "sha256:662a1ff78792e3fd77f16f71b1f31149489434de4b62a74895bd5d6534e635a5",
 | 
			
		||||
                "sha256:687c37153486cf26c3fdcbdd177ef16de38dc3463f094b5f9c9955d91f277b14"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.2.8"
 | 
			
		||||
            "version": "==2.2.9"
 | 
			
		||||
        },
 | 
			
		||||
        "django-cors-middleware": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:85904a3401e7bc0c86502ff2b01d726917af3aaa7dafb77799b27ace637e8c92",
 | 
			
		||||
                "sha256:bca8888ed33a94ba5472bde37ed71ec3d08231d6817fd4d799296b016073da95"
 | 
			
		||||
                "sha256:5bbdea85e22909d596e26f6e0dbc174d5521429fa3943ae02a2c6c48e76c88c7",
 | 
			
		||||
                "sha256:856dbe4d7aae65844ccc68acb49c6da7dbf7cbacaf5bcf37019f4c0c60b3be84"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.4.0"
 | 
			
		||||
            "version": "==1.5.0"
 | 
			
		||||
        },
 | 
			
		||||
        "django-dbbackup": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -208,11 +208,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django-model-utils": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3f130a262e45d73e0950d2be76af4bf4ee86804dd60e5f90afc5cd948fcfe760",
 | 
			
		||||
                "sha256:682f58c1de330cedcda58cc85d5232c5b47a9e2cb67bef4541fb43fdaeb18e96"
 | 
			
		||||
                "sha256:9cf882e5b604421b62dbe57ad2b18464dc9c8f963fc3f9831badccae66c1139c",
 | 
			
		||||
                "sha256:adf09e5be15122a7f4e372cb5a6dd512bbf8d78a23a90770ad0983ee9d909061"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==3.2.0"
 | 
			
		||||
            "version": "==4.0.0"
 | 
			
		||||
        },
 | 
			
		||||
        "django-oauth-toolkit": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -230,19 +230,19 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django-otp": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1b6025bbbd2517b7c246828b1d11c83d53567904836ae6d57bc0058f3cd18b50",
 | 
			
		||||
                "sha256:76a698466178ce40473726ffd8c33f68d1c47f27c53f67fa4aeeb6fdde74d37b"
 | 
			
		||||
                "sha256:1f16c2b93fe484706ff16ac6f5e64ecc73dd240318c333e0560384ba548d3837",
 | 
			
		||||
                "sha256:cd4975539be478417033561e9832a1a69a583189f680e92a649f412c661f90aa"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.7.4"
 | 
			
		||||
            "version": "==0.7.5"
 | 
			
		||||
        },
 | 
			
		||||
        "django-prometheus": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:60f331788f9846891e9ea8d7ccd2928b1042e2e99c8d673f97e2b85f5bc20112",
 | 
			
		||||
                "sha256:bb2d4f8acd681fa5787df77e7482391017f0090c70473bccd2aa7cad327800ad"
 | 
			
		||||
                "sha256:f0657d4b887309086b71b55f6aa4a95f967b35fe115128b501f95422c423b12c",
 | 
			
		||||
                "sha256:f645016ae5270ac2025a70788cd2bd636244a0c5705b323cc086994bf828181e"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.1.0"
 | 
			
		||||
            "version": "==2.0.0.dev124"
 | 
			
		||||
        },
 | 
			
		||||
        "django-recaptcha": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -254,11 +254,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django-redis": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:af0b393864e91228dd30d8c85b5c44d670b5524cb161b7f9e41acc98b6e5ace7",
 | 
			
		||||
                "sha256:f46115577063d00a890867c6964ba096057f07cb756e78e0503b89cd18e4e083"
 | 
			
		||||
                "sha256:a5b1e3ffd3198735e6c529d9bdf38ca3fcb3155515249b98dc4d966b8ddf9d2b",
 | 
			
		||||
                "sha256:e1aad4cc5bd743d8d0b13d5cae0cef5410eaace33e83bff5fc3a139ad8db50b4"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==4.10.0"
 | 
			
		||||
            "version": "==4.11.0"
 | 
			
		||||
        },
 | 
			
		||||
        "django-rest-framework": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -277,10 +277,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "djangorestframework": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8",
 | 
			
		||||
                "sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090"
 | 
			
		||||
                "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
 | 
			
		||||
                "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.10.3"
 | 
			
		||||
            "version": "==3.11.0"
 | 
			
		||||
        },
 | 
			
		||||
        "djangorestframework-guardian": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -328,11 +328,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "importlib-metadata": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3a8b2dfd0a2c6a3636e7c016a7e54ae04b997d30e69d5eacdca7a6c2221a1402",
 | 
			
		||||
                "sha256:41e688146d000891f32b1669e8573c57e39e5060e7f5f647aa617cd9a9568278"
 | 
			
		||||
                "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45",
 | 
			
		||||
                "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version < '3.8'",
 | 
			
		||||
            "version": "==1.2.0"
 | 
			
		||||
            "version": "==1.3.0"
 | 
			
		||||
        },
 | 
			
		||||
        "inflection": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -369,11 +369,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "kombu": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:389ba09e03b15b55b1a7371a441c894fd8121d174f5583bbbca032b9ea8c9edd",
 | 
			
		||||
                "sha256:7b92303af381ef02fad6899fd5f5a9a96031d781356cd8e505fa54ae5ddee181"
 | 
			
		||||
                "sha256:2a9e7adff14d046c9996752b2c48b6d9185d0b992106d5160e1a179907a5d4ac",
 | 
			
		||||
                "sha256:67b32ccb6fea030f8799f8fd50dd08e03a4b99464ebc4952d71d8747b1a52ad1"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==4.5.0"
 | 
			
		||||
            "version": "==4.6.7"
 | 
			
		||||
        },
 | 
			
		||||
        "ldap3": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -450,10 +450,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "more-itertools": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:53ff73f186307d9c8ef17a9600309154a6ae27f25579e80af4db8f047ba14bc2",
 | 
			
		||||
                "sha256:a0ea684c39bc4315ba7aae406596ef191fd84f873d2d2751f84d64e81a7a2d45"
 | 
			
		||||
                "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d",
 | 
			
		||||
                "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==8.0.0"
 | 
			
		||||
            "version": "==8.0.2"
 | 
			
		||||
        },
 | 
			
		||||
        "oauthlib": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -625,10 +625,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "pyparsing": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f",
 | 
			
		||||
                "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a"
 | 
			
		||||
                "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
 | 
			
		||||
                "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.4.5"
 | 
			
		||||
            "version": "==2.4.6"
 | 
			
		||||
        },
 | 
			
		||||
        "pyrsistent": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -638,11 +638,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "python-dateutil": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
 | 
			
		||||
                "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
 | 
			
		||||
                "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
 | 
			
		||||
                "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7'",
 | 
			
		||||
            "version": "==2.8.0"
 | 
			
		||||
            "version": "==2.8.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pytz": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -685,20 +685,20 @@
 | 
			
		||||
        },
 | 
			
		||||
        "pyyaml": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
 | 
			
		||||
                "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
 | 
			
		||||
                "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
 | 
			
		||||
                "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
 | 
			
		||||
                "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
 | 
			
		||||
                "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
 | 
			
		||||
                "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
 | 
			
		||||
                "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
 | 
			
		||||
                "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
 | 
			
		||||
                "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
 | 
			
		||||
                "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
 | 
			
		||||
                "sha256:21a8e19e2007a4047ffabbd8f0ee32c0dabae3b7f4b6c645110ae53e7714b470",
 | 
			
		||||
                "sha256:74ad685bfb065f4bdd36d24aa97092f04bcbb1179b5ffdd3d5f994023fb8c292",
 | 
			
		||||
                "sha256:79c3ba1da22e61c2a71aaa382c57518ab492278c8974c40187b900b50f3e0282",
 | 
			
		||||
                "sha256:94ad913ab3fd967d14ecffda8182d7d0e1f7dd919b352773c492ec51890d3224",
 | 
			
		||||
                "sha256:998db501e3a627c3e5678d6505f0e182d1529545df289db036cdc717f35d8058",
 | 
			
		||||
                "sha256:9b69d4645bff5820713e8912bc61c4277dc127a6f8c197b52b6436503c42600f",
 | 
			
		||||
                "sha256:9da13b536533518343a04f3c6564782ec8a13c705310b26b4832d77fa4d92a47",
 | 
			
		||||
                "sha256:a76159f13b47fb44fb2acac8fef798a1940dd31b4acec6f4560bd11b2d92d31b",
 | 
			
		||||
                "sha256:a9e9175c1e47a089a2b45d9e2afc6aae1f1f725538c32eec761894a42ba1227f",
 | 
			
		||||
                "sha256:ea51ce7b96646ecd3bb12c2702e570c2bd7dd4d9f146db7fa83c5008ede35f66",
 | 
			
		||||
                "sha256:ffbaaa05de60fc444eda3f6300d1af27d965b09b67f1fb4ebcc88dd0fb4ab1b4"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==5.2"
 | 
			
		||||
            "version": "==5.3b1"
 | 
			
		||||
        },
 | 
			
		||||
        "qrcode": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -752,6 +752,7 @@
 | 
			
		||||
                "sha256:a0ff786d2a7dbe55f9544b3f6ebbcc495d7e730df92a08434604f6f470b899c5",
 | 
			
		||||
                "sha256:b1b7fcee6aedcdc7e62c3a73f238b3d080c7ba6650cd808bce8d7761ec484070",
 | 
			
		||||
                "sha256:b66832ea8077d9b3f6e311c4a53d06273db5dc2db6e8a908550f3c14d67e718c",
 | 
			
		||||
                "sha256:be018933c2f4ee7de55e7bd7d0d801b3dfb09d21dad0cce8a97995fd3e44be30",
 | 
			
		||||
                "sha256:d0d3ac228c9bbab08134b4004d748cf9f8743504875b3603b3afbb97e3472947",
 | 
			
		||||
                "sha256:d10e9dd744cf85c219bf747c75194b624cc7a94f0c80ead624b06bfa9f61d3bc",
 | 
			
		||||
                "sha256:ea4362548ee0cbc266949d8a441238d9ad3600ca9910c3fe4e82ee3a50706973",
 | 
			
		||||
@ -770,11 +771,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "sentry-sdk": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:a7c2c8d3f53b6b57454830cd6a4b73d272f1ba91952f59e6545b3cf885f3c22f",
 | 
			
		||||
                "sha256:bfc486af718c268cf49ff43d6334ed4db7333ace420240b630acdd8f8a3a8f60"
 | 
			
		||||
                "sha256:05285942901d38c7ce2498aba50d8e87b361fc603281a5902dda98f3f8c5e145",
 | 
			
		||||
                "sha256:c6b919623e488134a728f16326c6f0bcdab7e3f59e7f4c472a90eea4d6d8fe82"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.13.4"
 | 
			
		||||
            "version": "==0.13.5"
 | 
			
		||||
        },
 | 
			
		||||
        "service-identity": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -824,11 +825,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "uritemplate": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:01c69f4fe8ed503b2951bef85d996a9d22434d2431584b5b107b2981ff416fbd",
 | 
			
		||||
                "sha256:1b9c467a940ce9fb9f50df819e8ddd14696f89b9a8cc87ac77952ba416e0a8fd",
 | 
			
		||||
                "sha256:c02643cebe23fc8adb5e6becffe201185bf06c40bda5c0b4028a93f1527d011d"
 | 
			
		||||
                "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f",
 | 
			
		||||
                "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.0.0"
 | 
			
		||||
            "version": "==3.0.1"
 | 
			
		||||
        },
 | 
			
		||||
        "urllib3": {
 | 
			
		||||
            "extras": [
 | 
			
		||||
@ -858,6 +858,13 @@
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "develop": {
 | 
			
		||||
        "appdirs": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
 | 
			
		||||
                "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.4.3"
 | 
			
		||||
        },
 | 
			
		||||
        "asgiref": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
 | 
			
		||||
@ -867,10 +874,17 @@
 | 
			
		||||
        },
 | 
			
		||||
        "astroid": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4",
 | 
			
		||||
                "sha256:b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4"
 | 
			
		||||
                "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a",
 | 
			
		||||
                "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.2.5"
 | 
			
		||||
            "version": "==2.3.3"
 | 
			
		||||
        },
 | 
			
		||||
        "attrs": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
 | 
			
		||||
                "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==19.3.0"
 | 
			
		||||
        },
 | 
			
		||||
        "autopep8": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -887,6 +901,14 @@
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.6.2"
 | 
			
		||||
        },
 | 
			
		||||
        "black": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b",
 | 
			
		||||
                "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==19.10b0"
 | 
			
		||||
        },
 | 
			
		||||
        "bumpversion": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e",
 | 
			
		||||
@ -895,59 +917,65 @@
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.5.3"
 | 
			
		||||
        },
 | 
			
		||||
        "click": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
 | 
			
		||||
                "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==7.0"
 | 
			
		||||
        },
 | 
			
		||||
        "colorama": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d",
 | 
			
		||||
                "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"
 | 
			
		||||
                "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
 | 
			
		||||
                "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.4.1"
 | 
			
		||||
            "version": "==0.4.3"
 | 
			
		||||
        },
 | 
			
		||||
        "coverage": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6",
 | 
			
		||||
                "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650",
 | 
			
		||||
                "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5",
 | 
			
		||||
                "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d",
 | 
			
		||||
                "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351",
 | 
			
		||||
                "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755",
 | 
			
		||||
                "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef",
 | 
			
		||||
                "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca",
 | 
			
		||||
                "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca",
 | 
			
		||||
                "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9",
 | 
			
		||||
                "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc",
 | 
			
		||||
                "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5",
 | 
			
		||||
                "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f",
 | 
			
		||||
                "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe",
 | 
			
		||||
                "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888",
 | 
			
		||||
                "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5",
 | 
			
		||||
                "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce",
 | 
			
		||||
                "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5",
 | 
			
		||||
                "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e",
 | 
			
		||||
                "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e",
 | 
			
		||||
                "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9",
 | 
			
		||||
                "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437",
 | 
			
		||||
                "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1",
 | 
			
		||||
                "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c",
 | 
			
		||||
                "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24",
 | 
			
		||||
                "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47",
 | 
			
		||||
                "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2",
 | 
			
		||||
                "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28",
 | 
			
		||||
                "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c",
 | 
			
		||||
                "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7",
 | 
			
		||||
                "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0",
 | 
			
		||||
                "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"
 | 
			
		||||
                "sha256:0101888bd1592a20ccadae081ba10e8b204d20235d18d05c6f7d5e904a38fc10",
 | 
			
		||||
                "sha256:04b961862334687549eb91cd5178a6fbe977ad365bddc7c60f2227f2f9880cf4",
 | 
			
		||||
                "sha256:1ca43dbd739c0fc30b0a3637a003a0d2c7edc1dd618359d58cc1e211742f8bd1",
 | 
			
		||||
                "sha256:1cbb88b34187bdb841f2599770b7e6ff8e259dc3bb64fc7893acf44998acf5f8",
 | 
			
		||||
                "sha256:232f0b52a5b978288f0bbc282a6c03fe48cd19a04202df44309919c142b3bb9c",
 | 
			
		||||
                "sha256:24bcfa86fd9ce86b73a8368383c39d919c497a06eebb888b6f0c12f13e920b1a",
 | 
			
		||||
                "sha256:25b8f60b5c7da71e64c18888f3067d5b6f1334b9681876b2fb41eea26de881ae",
 | 
			
		||||
                "sha256:2714160a63da18aed9340c70ed514973971ee7e665e6b336917ff4cca81a25b1",
 | 
			
		||||
                "sha256:2ca2cd5264e84b2cafc73f0045437f70c6378c0d7dbcddc9ee3fe192c1e29e5d",
 | 
			
		||||
                "sha256:2cc707fc9aad2592fc686d63ef72dc0031fc98b6fb921d2f5395d9ab84fbc3ef",
 | 
			
		||||
                "sha256:348630edea485f4228233c2f310a598abf8afa5f8c716c02a9698089687b6085",
 | 
			
		||||
                "sha256:40fbfd6b044c9db13aeec1daf5887d322c710d811f944011757526ef6e323fd9",
 | 
			
		||||
                "sha256:46c9c6a1d1190c0b75ec7c0f339088309952b82ae8d67a79ff1319eb4e749b96",
 | 
			
		||||
                "sha256:591506e088901bdc25620c37aec885e82cc896528f28c57e113751e3471fc314",
 | 
			
		||||
                "sha256:5ac71bba1e07eab403b082c4428f868c1c9e26a21041436b4905c4c3d4e49b08",
 | 
			
		||||
                "sha256:5f622f19abda4e934938e24f1d67599249abc201844933a6f01aaa8663094489",
 | 
			
		||||
                "sha256:65bead1ac8c8930cf92a1ccaedcce19a57298547d5d1db5c9d4d068a0675c38b",
 | 
			
		||||
                "sha256:7362a7f829feda10c7265b553455de596b83d1623b3d436b6d3c51c688c57bf6",
 | 
			
		||||
                "sha256:7f2675750c50151f806070ec11258edf4c328340916c53bac0adbc465abd6b1e",
 | 
			
		||||
                "sha256:960d7f42277391e8b1c0b0ae427a214e1b31a1278de6b73f8807b20c2e913bba",
 | 
			
		||||
                "sha256:a50b0888d8a021a3342d36a6086501e30de7d840ab68fca44913e97d14487dc1",
 | 
			
		||||
                "sha256:b7dbc5e8c39ea3ad3db22715f1b5401cd698a621218680c6daf42c2f9d36e205",
 | 
			
		||||
                "sha256:bb3d29df5d07d5399d58a394d0ef50adf303ab4fbf66dfd25b9ef258effcb692",
 | 
			
		||||
                "sha256:c0fff2733f7c2950f58a4fd09b5db257b00c6fec57bf3f68c5bae004d804b407",
 | 
			
		||||
                "sha256:c792d3707a86c01c02607ae74364854220fb3e82735f631cd0a345dea6b4cee5",
 | 
			
		||||
                "sha256:c90bda74e16bcd03861b09b1d37c0a4158feda5d5a036bb2d6e58de6ff65793e",
 | 
			
		||||
                "sha256:cfce79ce41cc1a1dc7fc85bb41eeeb32d34a4cf39a645c717c0550287e30ff06",
 | 
			
		||||
                "sha256:eeafb646f374988c22c8e6da5ab9fb81367ecfe81c70c292623373d2a021b1a1",
 | 
			
		||||
                "sha256:f425f50a6dd807cb9043d15a4fcfba3b5874a54d9587ccbb748899f70dc18c47",
 | 
			
		||||
                "sha256:fcd4459fe35a400b8f416bc57906862693c9f88b66dc925e7f2a933e77f6b18b",
 | 
			
		||||
                "sha256:ff3936dd5feaefb4f91c8c1f50a06c588b5dc69fba4f7d9c79a6617ad80bb7df"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==4.5.4"
 | 
			
		||||
            "version": "==5.0.1"
 | 
			
		||||
        },
 | 
			
		||||
        "django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:a4ad4f6f9c6a4b7af7e2deec8d0cbff28501852e5010d6c2dc695d3d1fae7ca0",
 | 
			
		||||
                "sha256:fa98ec9cc9bf5d72a08ebf3654a9452e761fbb8566e3f80de199cbc15477e891"
 | 
			
		||||
                "sha256:662a1ff78792e3fd77f16f71b1f31149489434de4b62a74895bd5d6534e635a5",
 | 
			
		||||
                "sha256:687c37153486cf26c3fdcbdd177ef16de38dc3463f094b5f9c9955d91f277b14"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.2.8"
 | 
			
		||||
            "version": "==2.2.9"
 | 
			
		||||
        },
 | 
			
		||||
        "django-debug-toolbar": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -982,7 +1010,6 @@
 | 
			
		||||
                "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
 | 
			
		||||
                "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==4.3.21"
 | 
			
		||||
        },
 | 
			
		||||
        "lazy-object-proxy": {
 | 
			
		||||
@ -1018,6 +1045,13 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.6.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pathspec": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424",
 | 
			
		||||
                "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.7.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pbr": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b",
 | 
			
		||||
@ -1034,10 +1068,10 @@
 | 
			
		||||
        },
 | 
			
		||||
        "prospector": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:aba551e53dc1a5a432afa67385eaa81d7b4cf4c162dc1a4d0ee00b3a0712ad90"
 | 
			
		||||
                "sha256:ea910794b53cfefcb5dfb6b4eb0323e42d1a88132e165b85b016cc7f0b6ae635"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.1.7"
 | 
			
		||||
            "version": "==1.2.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pycodestyle": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1048,25 +1082,25 @@
 | 
			
		||||
        },
 | 
			
		||||
        "pydocstyle": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:04c84e034ebb56eb6396c820442b8c4499ac5eb94a3bda88951ac3dc519b6058",
 | 
			
		||||
                "sha256:66aff87ffe34b1e49bff2dd03a88ce6843be2f3346b0c9814410d34987fbab59"
 | 
			
		||||
                "sha256:4167fe954b8f27ebbbef2fbcf73c6e8ad1e7bb31488fce44a69fdfc4b0cd0fae",
 | 
			
		||||
                "sha256:a0de36e549125d0a16a72a8c8c6c9ba267750656e72e466e994c222f1b6e92cb"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==4.0.1"
 | 
			
		||||
            "version": "==5.0.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pyflakes": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
 | 
			
		||||
                "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
 | 
			
		||||
                "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
 | 
			
		||||
                "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.6.0"
 | 
			
		||||
            "version": "==2.1.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09",
 | 
			
		||||
                "sha256:723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1"
 | 
			
		||||
                "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd",
 | 
			
		||||
                "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.3.1"
 | 
			
		||||
            "version": "==2.4.4"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint-celery": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1076,11 +1110,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "pylint-django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:75c69d1ec2275918c37f175976da20e2f1e1e62e067098a685cd263ffa833dfd",
 | 
			
		||||
                "sha256:c7cb6384ea7b33ea77052a5ae07358c10d377807390ef27b2e6ff997303fadb7"
 | 
			
		||||
                "sha256:9bdb0e022b19881218a25ffb8ad05e83b83bc5cdbc58e5ee8ffbe99965193f6c",
 | 
			
		||||
                "sha256:9eea6a026eaa5ecfad5fed7a33faf77ef55a43cc78afbcaf2f6ddd071156b3f8"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==2.0.10"
 | 
			
		||||
            "version": "==2.0.12"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint-flask": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1104,20 +1138,46 @@
 | 
			
		||||
        },
 | 
			
		||||
        "pyyaml": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
 | 
			
		||||
                "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
 | 
			
		||||
                "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
 | 
			
		||||
                "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
 | 
			
		||||
                "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
 | 
			
		||||
                "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
 | 
			
		||||
                "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
 | 
			
		||||
                "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
 | 
			
		||||
                "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
 | 
			
		||||
                "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
 | 
			
		||||
                "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
 | 
			
		||||
                "sha256:21a8e19e2007a4047ffabbd8f0ee32c0dabae3b7f4b6c645110ae53e7714b470",
 | 
			
		||||
                "sha256:74ad685bfb065f4bdd36d24aa97092f04bcbb1179b5ffdd3d5f994023fb8c292",
 | 
			
		||||
                "sha256:79c3ba1da22e61c2a71aaa382c57518ab492278c8974c40187b900b50f3e0282",
 | 
			
		||||
                "sha256:94ad913ab3fd967d14ecffda8182d7d0e1f7dd919b352773c492ec51890d3224",
 | 
			
		||||
                "sha256:998db501e3a627c3e5678d6505f0e182d1529545df289db036cdc717f35d8058",
 | 
			
		||||
                "sha256:9b69d4645bff5820713e8912bc61c4277dc127a6f8c197b52b6436503c42600f",
 | 
			
		||||
                "sha256:9da13b536533518343a04f3c6564782ec8a13c705310b26b4832d77fa4d92a47",
 | 
			
		||||
                "sha256:a76159f13b47fb44fb2acac8fef798a1940dd31b4acec6f4560bd11b2d92d31b",
 | 
			
		||||
                "sha256:a9e9175c1e47a089a2b45d9e2afc6aae1f1f725538c32eec761894a42ba1227f",
 | 
			
		||||
                "sha256:ea51ce7b96646ecd3bb12c2702e570c2bd7dd4d9f146db7fa83c5008ede35f66",
 | 
			
		||||
                "sha256:ffbaaa05de60fc444eda3f6300d1af27d965b09b67f1fb4ebcc88dd0fb4ab1b4"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==5.2"
 | 
			
		||||
            "version": "==5.3b1"
 | 
			
		||||
        },
 | 
			
		||||
        "regex": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:032fdcc03406e1a6485ec09b826eac78732943840c4b29e503b789716f051d8d",
 | 
			
		||||
                "sha256:0e6cf1e747f383f52a0964452658c04300a9a01e8a89c55ea22813931b580aa8",
 | 
			
		||||
                "sha256:106e25a841921d8259dcef2a42786caae35bc750fb996f830065b3dfaa67b77e",
 | 
			
		||||
                "sha256:1768cf42a78a11dae63152685e7a1d90af7a8d71d2d4f6d2387edea53a9e0588",
 | 
			
		||||
                "sha256:27d1bd20d334f50b7ef078eba0f0756a640fd25f5f1708d3b5bed18a5d6bced9",
 | 
			
		||||
                "sha256:29b20f66f2e044aafba86ecf10a84e611b4667643c42baa004247f5dfef4f90b",
 | 
			
		||||
                "sha256:4850c78b53acf664a6578bba0e9ebeaf2807bb476c14ec7e0f936f2015133cae",
 | 
			
		||||
                "sha256:57eacd38a5ec40ed7b19a968a9d01c0d977bda55664210be713e750dd7b33540",
 | 
			
		||||
                "sha256:724eb24b92fc5fdc1501a1b4df44a68b9c1dda171c8ef8736799e903fb100f63",
 | 
			
		||||
                "sha256:77ae8d926f38700432807ba293d768ba9e7652df0cbe76df2843b12f80f68885",
 | 
			
		||||
                "sha256:78b3712ec529b2a71731fbb10b907b54d9c53a17ca589b42a578bc1e9a2c82ea",
 | 
			
		||||
                "sha256:7bbbdbada3078dc360d4692a9b28479f569db7fc7f304b668787afc9feb38ec8",
 | 
			
		||||
                "sha256:8d9ef7f6c403e35e73b7fc3cde9f6decdc43b1cb2ff8d058c53b9084bfcb553e",
 | 
			
		||||
                "sha256:a83049eb717ae828ced9cf607845929efcb086a001fc8af93ff15c50012a5716",
 | 
			
		||||
                "sha256:adc35d38952e688535980ae2109cad3a109520033642e759f987cf47fe278aa1",
 | 
			
		||||
                "sha256:c29a77ad4463f71a506515d9ec3a899ed026b4b015bf43245c919ff36275444b",
 | 
			
		||||
                "sha256:cfd31b3300fefa5eecb2fe596c6dee1b91b3a05ece9d5cfd2631afebf6c6fadd",
 | 
			
		||||
                "sha256:d3ee0b035816e0520fac928de31b6572106f0d75597f6fa3206969a02baba06f",
 | 
			
		||||
                "sha256:d508875793efdf6bab3d47850df8f40d4040ae9928d9d80864c1768d6aeaf8e3",
 | 
			
		||||
                "sha256:ef0b828a7e22e58e06a1cceddba7b4665c6af8afeb22a0d8083001330572c147",
 | 
			
		||||
                "sha256:faad39fdbe2c2ccda9846cd21581063086330efafa47d87afea4073a08128656"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2019.12.20"
 | 
			
		||||
        },
 | 
			
		||||
        "requirements-detector": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1166,6 +1226,13 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.31.0"
 | 
			
		||||
        },
 | 
			
		||||
        "toml": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
 | 
			
		||||
                "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.10.0"
 | 
			
		||||
        },
 | 
			
		||||
        "typed-ast": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161",
 | 
			
		||||
@ -1189,7 +1256,7 @@
 | 
			
		||||
                "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66",
 | 
			
		||||
                "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "implementation_name == 'cpython'",
 | 
			
		||||
            "markers": "implementation_name == 'cpython' and python_version < '3.8'",
 | 
			
		||||
            "version": "==1.4.0"
 | 
			
		||||
        },
 | 
			
		||||
        "unittest-xml-reporting": {
 | 
			
		||||
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
FROM python:3.7-slim-buster as locker
 | 
			
		||||
 | 
			
		||||
COPY ./Pipfile /app/
 | 
			
		||||
COPY ./Pipfile.lock /app/
 | 
			
		||||
 | 
			
		||||
WORKDIR /app/
 | 
			
		||||
 | 
			
		||||
RUN pip install pipenv && \
 | 
			
		||||
    pipenv lock -r > requirements.txt && \
 | 
			
		||||
    pipenv lock -rd > requirements-dev.txt
 | 
			
		||||
 | 
			
		||||
FROM python:3.7-slim-buster
 | 
			
		||||
 | 
			
		||||
COPY --from=locker /app/requirements.txt /app/
 | 
			
		||||
COPY --from=locker /app/requirements-dev.txt /app/
 | 
			
		||||
 | 
			
		||||
WORKDIR /app/
 | 
			
		||||
 | 
			
		||||
RUN apt-get update && \
 | 
			
		||||
    apt-get install -y --no-install-recommends postgresql-client-11 && \
 | 
			
		||||
    rm -rf /var/lib/apt/ && \
 | 
			
		||||
    pip install -r requirements.txt  --no-cache-dir && \
 | 
			
		||||
    adduser --system --no-create-home --uid 1000 --group --home /app passbook
 | 
			
		||||
@ -1,3 +0,0 @@
 | 
			
		||||
FROM docker.beryju.org/passbook/base:latest
 | 
			
		||||
 | 
			
		||||
RUN pip install -r /app/requirements-dev.txt  --no-cache-dir
 | 
			
		||||
@ -21,7 +21,7 @@ services:
 | 
			
		||||
    labels:
 | 
			
		||||
      - traefik.enable=false
 | 
			
		||||
  server:
 | 
			
		||||
    image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest}
 | 
			
		||||
    image: beryju/passbook:${SERVER_TAG:-latest}
 | 
			
		||||
    command:
 | 
			
		||||
      - uwsgi
 | 
			
		||||
      - uwsgi.ini
 | 
			
		||||
@ -40,7 +40,7 @@ services:
 | 
			
		||||
      - traefik.docker.network=internal
 | 
			
		||||
      - traefik.frontend.rule=PathPrefix:/
 | 
			
		||||
  worker:
 | 
			
		||||
    image: docker.beryju.org/passbook/server:${SERVER_TAG:-latest}
 | 
			
		||||
    image: beryju/passbook:${SERVER_TAG:-latest}
 | 
			
		||||
    command:
 | 
			
		||||
      - celery
 | 
			
		||||
      - worker
 | 
			
		||||
@ -60,7 +60,7 @@ services:
 | 
			
		||||
      - PASSBOOK_POSTGRESQL__HOST=postgresql
 | 
			
		||||
      - PASSBOOK_POSTGRESQL__PASSWORD=${PG_PASS:-thisisnotagoodpassword}
 | 
			
		||||
  static:
 | 
			
		||||
    image: docker.beryju.org/passbook/static:latest
 | 
			
		||||
    image: beryju/passbook-static:latest
 | 
			
		||||
    networks:
 | 
			
		||||
      - internal
 | 
			
		||||
    labels:
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ This installation Method is for test-setups and small-scale productive setups.
 | 
			
		||||
 | 
			
		||||
## Install
 | 
			
		||||
 | 
			
		||||
Download the latest `docker-compose.yml` from [here](https://git.beryju.org/BeryJu.org/passbook/raw/master/docker-compose.yml). Place it in a directory of your choice.
 | 
			
		||||
Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml). Place it in a directory of your choice.
 | 
			
		||||
 | 
			
		||||
passbook needs to know it's primary URL to create links in E-Mails and set cookies, so you have to run the following command:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ spec:
 | 
			
		||||
    spec:
 | 
			
		||||
      containers:
 | 
			
		||||
        - name: passbook-docs
 | 
			
		||||
          image: "docker.beryju.org/passbook/docs:latest"
 | 
			
		||||
          image: "beryju/passbook-docs:latest"
 | 
			
		||||
          ports:
 | 
			
		||||
            - name: http
 | 
			
		||||
              containerPort: 80
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
appVersion: "0.7.5-beta"
 | 
			
		||||
appVersion: "0.7.10-beta"
 | 
			
		||||
description: A Helm chart for passbook.
 | 
			
		||||
name: passbook
 | 
			
		||||
version: "0.7.5-beta"
 | 
			
		||||
version: "0.7.10-beta"
 | 
			
		||||
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ spec:
 | 
			
		||||
    spec:
 | 
			
		||||
      containers:
 | 
			
		||||
        - name: {{ .Chart.Name }}-static
 | 
			
		||||
          image: "docker.beryju.org/passbook/static:{{ .Values.image.tag }}"
 | 
			
		||||
          image: "beryju/passbook-static:{{ .Values.image.tag }}"
 | 
			
		||||
          imagePullPolicy: IfNotPresent
 | 
			
		||||
          ports:
 | 
			
		||||
            - name: http
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ spec:
 | 
			
		||||
            name: {{ include "passbook.fullname" . }}-config
 | 
			
		||||
      initContainers:
 | 
			
		||||
        - name: passbook-database-migrations
 | 
			
		||||
          image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
 | 
			
		||||
          image: "beryju/passbook:{{ .Values.image.tag }}"
 | 
			
		||||
          command:
 | 
			
		||||
            - ./manage.py
 | 
			
		||||
          args:
 | 
			
		||||
@ -56,7 +56,7 @@ spec:
 | 
			
		||||
                  key: postgresql-password
 | 
			
		||||
      containers:
 | 
			
		||||
        - name: {{ .Chart.Name }}
 | 
			
		||||
          image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
 | 
			
		||||
          image: "beryju/passbook:{{ .Values.image.tag }}"
 | 
			
		||||
          imagePullPolicy: IfNotPresent
 | 
			
		||||
          command:
 | 
			
		||||
            - uwsgi
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ spec:
 | 
			
		||||
            name: {{ include "passbook.fullname" . }}-config
 | 
			
		||||
      containers:
 | 
			
		||||
        - name: {{ .Chart.Name }}
 | 
			
		||||
          image: "docker.beryju.org/passbook/server:{{ .Values.image.tag }}"
 | 
			
		||||
          image: "beryju/passbook:{{ .Values.image.tag }}"
 | 
			
		||||
          imagePullPolicy: IfNotPresent
 | 
			
		||||
          command:
 | 
			
		||||
            - celery
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
# This is a YAML-formatted file.
 | 
			
		||||
# Declare variables to be passed into your templates.
 | 
			
		||||
image:
 | 
			
		||||
  tag: 0.7.5-beta
 | 
			
		||||
  tag: 0.7.10-beta
 | 
			
		||||
 | 
			
		||||
nameOverride: ""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ nav:
 | 
			
		||||
          - Sentry: integrations/services/sentry/index.md
 | 
			
		||||
 | 
			
		||||
repo_name: "BeryJu.org/passbook"
 | 
			
		||||
repo_url: https://git.beryju.org/BeryJu.org/passbook
 | 
			
		||||
repo_url: https://github.com/BeryJu/passbook
 | 
			
		||||
theme:
 | 
			
		||||
  name: "material"
 | 
			
		||||
  logo: "images/logo.svg"
 | 
			
		||||
 | 
			
		||||
@ -1,2 +1,2 @@
 | 
			
		||||
"""passbook"""
 | 
			
		||||
__version__ = '0.7.5-beta'
 | 
			
		||||
__version__ = "0.7.10-beta"
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ from django.apps import AppConfig
 | 
			
		||||
class PassbookAdminConfig(AppConfig):
 | 
			
		||||
    """passbook admin app config"""
 | 
			
		||||
 | 
			
		||||
    name = 'passbook.admin'
 | 
			
		||||
    label = 'passbook_admin'
 | 
			
		||||
    mountpoint = 'administration/'
 | 
			
		||||
    verbose_name = 'passbook Admin'
 | 
			
		||||
    name = "passbook.admin"
 | 
			
		||||
    label = "passbook_admin"
 | 
			
		||||
    mountpoint = "administration/"
 | 
			
		||||
    verbose_name = "passbook Admin"
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ class YAMLField(forms.CharField):
 | 
			
		||||
    """Django's JSON Field converted to YAML"""
 | 
			
		||||
 | 
			
		||||
    default_error_messages = {
 | 
			
		||||
        'invalid': _("'%(value)s' value must be valid YAML."),
 | 
			
		||||
        "invalid": _("'%(value)s' value must be valid YAML."),
 | 
			
		||||
    }
 | 
			
		||||
    widget = forms.Textarea
 | 
			
		||||
 | 
			
		||||
@ -31,9 +31,7 @@ class YAMLField(forms.CharField):
 | 
			
		||||
            converted = yaml.safe_load(value)
 | 
			
		||||
        except yaml.YAMLError:
 | 
			
		||||
            raise forms.ValidationError(
 | 
			
		||||
                self.error_messages['invalid'],
 | 
			
		||||
                code='invalid',
 | 
			
		||||
                params={'value': value},
 | 
			
		||||
                self.error_messages["invalid"], code="invalid", params={"value": value},
 | 
			
		||||
            )
 | 
			
		||||
        if isinstance(converted, str):
 | 
			
		||||
            return YAMLString(converted)
 | 
			
		||||
 | 
			
		||||
@ -9,29 +9,32 @@ class TagModelForm(forms.ModelForm):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        # Check if we have an instance, load tags otherwise use an empty dict
 | 
			
		||||
        instance = kwargs.get('instance', None)
 | 
			
		||||
        instance = kwargs.get("instance", None)
 | 
			
		||||
        tags = instance.tags if instance else {}
 | 
			
		||||
        # Make sure all predefined tags exist in tags, and set default if they don't
 | 
			
		||||
        predefined_tags = self._meta.model().get_predefined_tags()  # pylint: disable=no-member
 | 
			
		||||
        predefined_tags = (
 | 
			
		||||
            self._meta.model().get_predefined_tags()  # pylint: disable=no-member
 | 
			
		||||
        )
 | 
			
		||||
        for key, value in predefined_tags.items():
 | 
			
		||||
            if key not in tags:
 | 
			
		||||
                tags[key] = value
 | 
			
		||||
        # Format JSON
 | 
			
		||||
        kwargs['initial']['tags'] = tags
 | 
			
		||||
        kwargs["initial"]["tags"] = tags
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def clean_tags(self):
 | 
			
		||||
        """Make sure all required tags are set"""
 | 
			
		||||
        if hasattr(self.instance, 'get_required_keys') and hasattr(self.instance, 'tags'):
 | 
			
		||||
        if hasattr(self.instance, "get_required_keys") and hasattr(
 | 
			
		||||
            self.instance, "tags"
 | 
			
		||||
        ):
 | 
			
		||||
            for key in self.instance.get_required_keys():
 | 
			
		||||
                if key not in self.cleaned_data.get('tags'):
 | 
			
		||||
                if key not in self.cleaned_data.get("tags"):
 | 
			
		||||
                    raise forms.ValidationError("Tag %s missing." % key)
 | 
			
		||||
        return self.cleaned_data.get('tags')
 | 
			
		||||
        return self.cleaned_data.get("tags")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# pylint: disable=too-few-public-methods
 | 
			
		||||
class TagModelFormMeta:
 | 
			
		||||
    """Base Meta class that uses the YAMLField"""
 | 
			
		||||
 | 
			
		||||
    field_classes = {
 | 
			
		||||
        'tags': YAMLField
 | 
			
		||||
    }
 | 
			
		||||
    field_classes = {"tags": YAMLField}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
"""passbook core source form fields"""
 | 
			
		||||
# from django import forms
 | 
			
		||||
 | 
			
		||||
SOURCE_FORM_FIELDS = ['name', 'slug', 'enabled', 'policies']
 | 
			
		||||
SOURCE_SERIALIZER_FIELDS = ['pk', 'name', 'slug', 'enabled', 'policies']
 | 
			
		||||
SOURCE_FORM_FIELDS = ["name", "slug", "enabled", "policies"]
 | 
			
		||||
SOURCE_SERIALIZER_FIELDS = ["pk", "name", "slug", "enabled", "policies"]
 | 
			
		||||
 | 
			
		||||
# class SourceForm(forms.Form)
 | 
			
		||||
 | 
			
		||||
@ -12,10 +12,10 @@ class UserForm(forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = User
 | 
			
		||||
        fields = ['username', 'name', 'email', 'is_staff', 'is_active', 'attributes']
 | 
			
		||||
        fields = ["username", "name", "email", "is_staff", "is_active", "attributes"]
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'name': forms.TextInput,
 | 
			
		||||
            "name": forms.TextInput,
 | 
			
		||||
        }
 | 
			
		||||
        field_classes = {
 | 
			
		||||
            'attributes': YAMLField,
 | 
			
		||||
            "attributes": YAMLField,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -11,15 +11,16 @@ def impersonate(get_response):
 | 
			
		||||
 | 
			
		||||
        # User is superuser and has __impersonate ID set
 | 
			
		||||
        if request.user.is_superuser and "__impersonate" in request.GET:
 | 
			
		||||
            request.session['impersonate_id'] = request.GET["__impersonate"]
 | 
			
		||||
            request.session["impersonate_id"] = request.GET["__impersonate"]
 | 
			
		||||
        # user wants to stop impersonation
 | 
			
		||||
        elif "__unimpersonate" in request.GET and 'impersonate_id' in request.session:
 | 
			
		||||
            del request.session['impersonate_id']
 | 
			
		||||
        elif "__unimpersonate" in request.GET and "impersonate_id" in request.session:
 | 
			
		||||
            del request.session["impersonate_id"]
 | 
			
		||||
 | 
			
		||||
        # Actually impersonate user
 | 
			
		||||
        if request.user.is_superuser and 'impersonate_id' in request.session:
 | 
			
		||||
            request.user = User.objects.get(pk=request.session['impersonate_id'])
 | 
			
		||||
        if request.user.is_superuser and "impersonate_id" in request.session:
 | 
			
		||||
            request.user = User.objects.get(pk=request.session["impersonate_id"])
 | 
			
		||||
 | 
			
		||||
        response = get_response(request)
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
    return middleware
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
"""passbook admin settings"""
 | 
			
		||||
 | 
			
		||||
MIDDLEWARE = [
 | 
			
		||||
    'passbook.admin.middleware.impersonate',
 | 
			
		||||
    "passbook.admin.middleware.impersonate",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -10,10 +10,11 @@ from passbook.lib.utils.template import render_to_string
 | 
			
		||||
register = template.Library()
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag()
 | 
			
		||||
def get_links(model_instance):
 | 
			
		||||
    """Find all link_ methods on an object instance, run them and return as dict"""
 | 
			
		||||
    prefix = 'link_'
 | 
			
		||||
    prefix = "link_"
 | 
			
		||||
    links = {}
 | 
			
		||||
 | 
			
		||||
    if not isinstance(model_instance, Model):
 | 
			
		||||
@ -21,9 +22,11 @@ def get_links(model_instance):
 | 
			
		||||
        return links
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        for name, method in inspect.getmembers(model_instance, predicate=inspect.ismethod):
 | 
			
		||||
        for name, method in inspect.getmembers(
 | 
			
		||||
            model_instance, predicate=inspect.ismethod
 | 
			
		||||
        ):
 | 
			
		||||
            if name.startswith(prefix):
 | 
			
		||||
                human_name = name.replace(prefix, '').replace('_', ' ').capitalize()
 | 
			
		||||
                human_name = name.replace(prefix, "").replace("_", " ").capitalize()
 | 
			
		||||
                link = method()
 | 
			
		||||
                if link:
 | 
			
		||||
                    links[human_name] = link
 | 
			
		||||
@ -36,7 +39,7 @@ def get_links(model_instance):
 | 
			
		||||
@register.simple_tag(takes_context=True)
 | 
			
		||||
def get_htmls(context, model_instance):
 | 
			
		||||
    """Find all html_ methods on an object instance, run them and return as dict"""
 | 
			
		||||
    prefix = 'html_'
 | 
			
		||||
    prefix = "html_"
 | 
			
		||||
    htmls = []
 | 
			
		||||
 | 
			
		||||
    if not isinstance(model_instance, Model):
 | 
			
		||||
@ -44,9 +47,11 @@ def get_htmls(context, model_instance):
 | 
			
		||||
        return htmls
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        for name, method in inspect.getmembers(model_instance, predicate=inspect.ismethod):
 | 
			
		||||
        for name, method in inspect.getmembers(
 | 
			
		||||
            model_instance, predicate=inspect.ismethod
 | 
			
		||||
        ):
 | 
			
		||||
            if name.startswith(prefix):
 | 
			
		||||
                template, _context = method(context.get('request'))
 | 
			
		||||
                template, _context = method(context.get("request"))
 | 
			
		||||
                htmls.append(render_to_string(template, _context))
 | 
			
		||||
    except NotImplementedError:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
@ -1,82 +1,157 @@
 | 
			
		||||
"""passbook URL Configuration"""
 | 
			
		||||
from django.urls import path
 | 
			
		||||
 | 
			
		||||
from passbook.admin.views import (applications, audit, debug, factors, groups,
 | 
			
		||||
                                  invitations, overview, policy,
 | 
			
		||||
                                  property_mapping, providers, sources, users)
 | 
			
		||||
from passbook.admin.views import (
 | 
			
		||||
    applications,
 | 
			
		||||
    audit,
 | 
			
		||||
    debug,
 | 
			
		||||
    factors,
 | 
			
		||||
    groups,
 | 
			
		||||
    invitations,
 | 
			
		||||
    overview,
 | 
			
		||||
    policy,
 | 
			
		||||
    property_mapping,
 | 
			
		||||
    providers,
 | 
			
		||||
    sources,
 | 
			
		||||
    users,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path('', overview.AdministrationOverviewView.as_view(), name='overview'),
 | 
			
		||||
    path("", overview.AdministrationOverviewView.as_view(), name="overview"),
 | 
			
		||||
    # Applications
 | 
			
		||||
    path('applications/', applications.ApplicationListView.as_view(),
 | 
			
		||||
         name='applications'),
 | 
			
		||||
    path('applications/create/', applications.ApplicationCreateView.as_view(),
 | 
			
		||||
         name='application-create'),
 | 
			
		||||
    path('applications/<uuid:pk>/update/',
 | 
			
		||||
         applications.ApplicationUpdateView.as_view(), name='application-update'),
 | 
			
		||||
    path('applications/<uuid:pk>/delete/',
 | 
			
		||||
         applications.ApplicationDeleteView.as_view(), name='application-delete'),
 | 
			
		||||
    path(
 | 
			
		||||
        "applications/", applications.ApplicationListView.as_view(), name="applications"
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "applications/create/",
 | 
			
		||||
        applications.ApplicationCreateView.as_view(),
 | 
			
		||||
        name="application-create",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "applications/<uuid:pk>/update/",
 | 
			
		||||
        applications.ApplicationUpdateView.as_view(),
 | 
			
		||||
        name="application-update",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "applications/<uuid:pk>/delete/",
 | 
			
		||||
        applications.ApplicationDeleteView.as_view(),
 | 
			
		||||
        name="application-delete",
 | 
			
		||||
    ),
 | 
			
		||||
    # Sources
 | 
			
		||||
    path('sources/', sources.SourceListView.as_view(), name='sources'),
 | 
			
		||||
    path('sources/create/', sources.SourceCreateView.as_view(), name='source-create'),
 | 
			
		||||
    path('sources/<uuid:pk>/update/', sources.SourceUpdateView.as_view(), name='source-update'),
 | 
			
		||||
    path('sources/<uuid:pk>/delete/', sources.SourceDeleteView.as_view(), name='source-delete'),
 | 
			
		||||
    path("sources/", sources.SourceListView.as_view(), name="sources"),
 | 
			
		||||
    path("sources/create/", sources.SourceCreateView.as_view(), name="source-create"),
 | 
			
		||||
    path(
 | 
			
		||||
        "sources/<uuid:pk>/update/",
 | 
			
		||||
        sources.SourceUpdateView.as_view(),
 | 
			
		||||
        name="source-update",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "sources/<uuid:pk>/delete/",
 | 
			
		||||
        sources.SourceDeleteView.as_view(),
 | 
			
		||||
        name="source-delete",
 | 
			
		||||
    ),
 | 
			
		||||
    # Policies
 | 
			
		||||
    path('policies/', policy.PolicyListView.as_view(), name='policies'),
 | 
			
		||||
    path('policies/create/', policy.PolicyCreateView.as_view(), name='policy-create'),
 | 
			
		||||
    path('policies/<uuid:pk>/update/', policy.PolicyUpdateView.as_view(), name='policy-update'),
 | 
			
		||||
    path('policies/<uuid:pk>/delete/', policy.PolicyDeleteView.as_view(), name='policy-delete'),
 | 
			
		||||
    path('policies/<uuid:pk>/test/', policy.PolicyTestView.as_view(), name='policy-test'),
 | 
			
		||||
    path("policies/", policy.PolicyListView.as_view(), name="policies"),
 | 
			
		||||
    path("policies/create/", policy.PolicyCreateView.as_view(), name="policy-create"),
 | 
			
		||||
    path(
 | 
			
		||||
        "policies/<uuid:pk>/update/",
 | 
			
		||||
        policy.PolicyUpdateView.as_view(),
 | 
			
		||||
        name="policy-update",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "policies/<uuid:pk>/delete/",
 | 
			
		||||
        policy.PolicyDeleteView.as_view(),
 | 
			
		||||
        name="policy-delete",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "policies/<uuid:pk>/test/", policy.PolicyTestView.as_view(), name="policy-test"
 | 
			
		||||
    ),
 | 
			
		||||
    # Providers
 | 
			
		||||
    path('providers/', providers.ProviderListView.as_view(), name='providers'),
 | 
			
		||||
    path('providers/create/',
 | 
			
		||||
         providers.ProviderCreateView.as_view(), name='provider-create'),
 | 
			
		||||
    path('providers/<int:pk>/update/',
 | 
			
		||||
         providers.ProviderUpdateView.as_view(), name='provider-update'),
 | 
			
		||||
    path('providers/<int:pk>/delete/',
 | 
			
		||||
         providers.ProviderDeleteView.as_view(), name='provider-delete'),
 | 
			
		||||
    path("providers/", providers.ProviderListView.as_view(), name="providers"),
 | 
			
		||||
    path(
 | 
			
		||||
        "providers/create/",
 | 
			
		||||
        providers.ProviderCreateView.as_view(),
 | 
			
		||||
        name="provider-create",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "providers/<int:pk>/update/",
 | 
			
		||||
        providers.ProviderUpdateView.as_view(),
 | 
			
		||||
        name="provider-update",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "providers/<int:pk>/delete/",
 | 
			
		||||
        providers.ProviderDeleteView.as_view(),
 | 
			
		||||
        name="provider-delete",
 | 
			
		||||
    ),
 | 
			
		||||
    # Factors
 | 
			
		||||
    path('factors/', factors.FactorListView.as_view(), name='factors'),
 | 
			
		||||
    path('factors/create/',
 | 
			
		||||
         factors.FactorCreateView.as_view(), name='factor-create'),
 | 
			
		||||
    path('factors/<uuid:pk>/update/',
 | 
			
		||||
         factors.FactorUpdateView.as_view(), name='factor-update'),
 | 
			
		||||
    path('factors/<uuid:pk>/delete/',
 | 
			
		||||
         factors.FactorDeleteView.as_view(), name='factor-delete'),
 | 
			
		||||
    path("factors/", factors.FactorListView.as_view(), name="factors"),
 | 
			
		||||
    path("factors/create/", factors.FactorCreateView.as_view(), name="factor-create"),
 | 
			
		||||
    path(
 | 
			
		||||
        "factors/<uuid:pk>/update/",
 | 
			
		||||
        factors.FactorUpdateView.as_view(),
 | 
			
		||||
        name="factor-update",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "factors/<uuid:pk>/delete/",
 | 
			
		||||
        factors.FactorDeleteView.as_view(),
 | 
			
		||||
        name="factor-delete",
 | 
			
		||||
    ),
 | 
			
		||||
    # Factors
 | 
			
		||||
    path('property-mappings/', property_mapping.PropertyMappingListView.as_view(),
 | 
			
		||||
         name='property-mappings'),
 | 
			
		||||
    path('property-mappings/create/',
 | 
			
		||||
         property_mapping.PropertyMappingCreateView.as_view(), name='property-mapping-create'),
 | 
			
		||||
    path('property-mappings/<uuid:pk>/update/',
 | 
			
		||||
         property_mapping.PropertyMappingUpdateView.as_view(), name='property-mapping-update'),
 | 
			
		||||
    path('property-mappings/<uuid:pk>/delete/',
 | 
			
		||||
         property_mapping.PropertyMappingDeleteView.as_view(), name='property-mapping-delete'),
 | 
			
		||||
    path(
 | 
			
		||||
        "property-mappings/",
 | 
			
		||||
        property_mapping.PropertyMappingListView.as_view(),
 | 
			
		||||
        name="property-mappings",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "property-mappings/create/",
 | 
			
		||||
        property_mapping.PropertyMappingCreateView.as_view(),
 | 
			
		||||
        name="property-mapping-create",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "property-mappings/<uuid:pk>/update/",
 | 
			
		||||
        property_mapping.PropertyMappingUpdateView.as_view(),
 | 
			
		||||
        name="property-mapping-update",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "property-mappings/<uuid:pk>/delete/",
 | 
			
		||||
        property_mapping.PropertyMappingDeleteView.as_view(),
 | 
			
		||||
        name="property-mapping-delete",
 | 
			
		||||
    ),
 | 
			
		||||
    # Invitations
 | 
			
		||||
    path('invitations/', invitations.InvitationListView.as_view(), name='invitations'),
 | 
			
		||||
    path('invitations/create/',
 | 
			
		||||
         invitations.InvitationCreateView.as_view(), name='invitation-create'),
 | 
			
		||||
    path('invitations/<uuid:pk>/delete/',
 | 
			
		||||
         invitations.InvitationDeleteView.as_view(), name='invitation-delete'),
 | 
			
		||||
    path("invitations/", invitations.InvitationListView.as_view(), name="invitations"),
 | 
			
		||||
    path(
 | 
			
		||||
        "invitations/create/",
 | 
			
		||||
        invitations.InvitationCreateView.as_view(),
 | 
			
		||||
        name="invitation-create",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "invitations/<uuid:pk>/delete/",
 | 
			
		||||
        invitations.InvitationDeleteView.as_view(),
 | 
			
		||||
        name="invitation-delete",
 | 
			
		||||
    ),
 | 
			
		||||
    # Users
 | 
			
		||||
    path('users/', users.UserListView.as_view(),
 | 
			
		||||
         name='users'),
 | 
			
		||||
    path('users/create/', users.UserCreateView.as_view(), name='user-create'),
 | 
			
		||||
    path('users/<int:pk>/update/',
 | 
			
		||||
         users.UserUpdateView.as_view(), name='user-update'),
 | 
			
		||||
    path('users/<int:pk>/delete/',
 | 
			
		||||
         users.UserDeleteView.as_view(), name='user-delete'),
 | 
			
		||||
    path('users/<int:pk>/reset/',
 | 
			
		||||
         users.UserPasswordResetView.as_view(), name='user-password-reset'),
 | 
			
		||||
    path("users/", users.UserListView.as_view(), name="users"),
 | 
			
		||||
    path("users/create/", users.UserCreateView.as_view(), name="user-create"),
 | 
			
		||||
    path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"),
 | 
			
		||||
    path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"),
 | 
			
		||||
    path(
 | 
			
		||||
        "users/<int:pk>/reset/",
 | 
			
		||||
        users.UserPasswordResetView.as_view(),
 | 
			
		||||
        name="user-password-reset",
 | 
			
		||||
    ),
 | 
			
		||||
    # Groups
 | 
			
		||||
    path('group/', groups.GroupListView.as_view(), name='group'),
 | 
			
		||||
    path('group/create/', groups.GroupCreateView.as_view(), name='group-create'),
 | 
			
		||||
    path('group/<uuid:pk>/update/', groups.GroupUpdateView.as_view(), name='group-update'),
 | 
			
		||||
    path('group/<uuid:pk>/delete/', groups.GroupDeleteView.as_view(), name='group-delete'),
 | 
			
		||||
    path("group/", groups.GroupListView.as_view(), name="group"),
 | 
			
		||||
    path("group/create/", groups.GroupCreateView.as_view(), name="group-create"),
 | 
			
		||||
    path(
 | 
			
		||||
        "group/<uuid:pk>/update/", groups.GroupUpdateView.as_view(), name="group-update"
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "group/<uuid:pk>/delete/", groups.GroupDeleteView.as_view(), name="group-delete"
 | 
			
		||||
    ),
 | 
			
		||||
    # Audit Log
 | 
			
		||||
    path('audit/', audit.EventListView.as_view(), name='audit-log'),
 | 
			
		||||
    path("audit/", audit.EventListView.as_view(), name="audit-log"),
 | 
			
		||||
    # Groups
 | 
			
		||||
    path('groups/', groups.GroupListView.as_view(), name='groups'),
 | 
			
		||||
    path("groups/", groups.GroupListView.as_view(), name="groups"),
 | 
			
		||||
    # Debug
 | 
			
		||||
    path('debug/request/', debug.DebugRequestView.as_view(), name='debug-request'),
 | 
			
		||||
    path("debug/request/", debug.DebugRequestView.as_view(), name="debug-request"),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
"""passbook Application administration"""
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import \
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import (
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin,
 | 
			
		||||
)
 | 
			
		||||
from django.contrib.messages.views import SuccessMessageMixin
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
from django.utils.translation import ugettext as _
 | 
			
		||||
@ -18,55 +19,61 @@ class ApplicationListView(LoginRequiredMixin, PermissionListMixin, ListView):
 | 
			
		||||
    """Show list of all applications"""
 | 
			
		||||
 | 
			
		||||
    model = Application
 | 
			
		||||
    permission_required = 'passbook_core.view_application'
 | 
			
		||||
    ordering = 'name'
 | 
			
		||||
    permission_required = "passbook_core.view_application"
 | 
			
		||||
    ordering = "name"
 | 
			
		||||
    paginate_by = 40
 | 
			
		||||
    template_name = 'administration/application/list.html'
 | 
			
		||||
    template_name = "administration/application/list.html"
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return super().get_queryset().select_subclasses()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ApplicationCreateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                            DjangoPermissionRequiredMixin, CreateAssignPermView):
 | 
			
		||||
class ApplicationCreateView(
 | 
			
		||||
    SuccessMessageMixin,
 | 
			
		||||
    LoginRequiredMixin,
 | 
			
		||||
    DjangoPermissionRequiredMixin,
 | 
			
		||||
    CreateAssignPermView,
 | 
			
		||||
):
 | 
			
		||||
    """Create new Application"""
 | 
			
		||||
 | 
			
		||||
    model = Application
 | 
			
		||||
    form_class = ApplicationForm
 | 
			
		||||
    permission_required = 'passbook_core.add_application'
 | 
			
		||||
    permission_required = "passbook_core.add_application"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:applications')
 | 
			
		||||
    success_message = _('Successfully created Application')
 | 
			
		||||
    template_name = "generic/create.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:applications")
 | 
			
		||||
    success_message = _("Successfully created Application")
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['type'] = 'Application'
 | 
			
		||||
        kwargs["type"] = "Application"
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ApplicationUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                            PermissionRequiredMixin, UpdateView):
 | 
			
		||||
class ApplicationUpdateView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
 | 
			
		||||
):
 | 
			
		||||
    """Update application"""
 | 
			
		||||
 | 
			
		||||
    model = Application
 | 
			
		||||
    form_class = ApplicationForm
 | 
			
		||||
    permission_required = 'passbook_core.change_application'
 | 
			
		||||
    permission_required = "passbook_core.change_application"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/update.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:applications')
 | 
			
		||||
    success_message = _('Successfully updated Application')
 | 
			
		||||
    template_name = "generic/update.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:applications")
 | 
			
		||||
    success_message = _("Successfully updated Application")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ApplicationDeleteView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                            PermissionRequiredMixin, DeleteView):
 | 
			
		||||
class ApplicationDeleteView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
 | 
			
		||||
):
 | 
			
		||||
    """Delete application"""
 | 
			
		||||
 | 
			
		||||
    model = Application
 | 
			
		||||
    permission_required = 'passbook_core.delete_application'
 | 
			
		||||
    permission_required = "passbook_core.delete_application"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/delete.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:applications')
 | 
			
		||||
    success_message = _('Successfully deleted Application')
 | 
			
		||||
    template_name = "generic/delete.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:applications")
 | 
			
		||||
    success_message = _("Successfully deleted Application")
 | 
			
		||||
 | 
			
		||||
    def delete(self, request, *args, **kwargs):
 | 
			
		||||
        messages.success(self.request, self.success_message)
 | 
			
		||||
 | 
			
		||||
@ -9,10 +9,10 @@ class EventListView(PermissionListMixin, ListView):
 | 
			
		||||
    """Show list of all invitations"""
 | 
			
		||||
 | 
			
		||||
    model = Event
 | 
			
		||||
    template_name = 'administration/audit/list.html'
 | 
			
		||||
    permission_required = 'passbook_audit.view_event'
 | 
			
		||||
    ordering = '-created'
 | 
			
		||||
    template_name = "administration/audit/list.html"
 | 
			
		||||
    permission_required = "passbook_audit.view_event"
 | 
			
		||||
    ordering = "-created"
 | 
			
		||||
    paginate_by = 10
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return Event.objects.all().order_by('-created')
 | 
			
		||||
        return Event.objects.all().order_by("-created")
 | 
			
		||||
 | 
			
		||||
@ -6,10 +6,10 @@ from django.views.generic import TemplateView
 | 
			
		||||
class DebugRequestView(LoginRequiredMixin, TemplateView):
 | 
			
		||||
    """Show debug info about request"""
 | 
			
		||||
 | 
			
		||||
    template_name = 'administration/debug/request.html'
 | 
			
		||||
    template_name = "administration/debug/request.html"
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['request_dict'] = {}
 | 
			
		||||
        kwargs["request_dict"] = {}
 | 
			
		||||
        for key in dir(self.request):
 | 
			
		||||
            kwargs['request_dict'][key] = getattr(self.request, key)
 | 
			
		||||
            kwargs["request_dict"][key] = getattr(self.request, key)
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
"""passbook Factor administration"""
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import \
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import (
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin,
 | 
			
		||||
)
 | 
			
		||||
from django.contrib.messages.views import SuccessMessageMixin
 | 
			
		||||
from django.http import Http404
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
@ -18,62 +19,69 @@ from passbook.lib.views import CreateAssignPermView
 | 
			
		||||
def all_subclasses(cls):
 | 
			
		||||
    """Recursively return all subclassess of cls"""
 | 
			
		||||
    return set(cls.__subclasses__()).union(
 | 
			
		||||
        [s for c in cls.__subclasses__() for s in all_subclasses(c)])
 | 
			
		||||
        [s for c in cls.__subclasses__() for s in all_subclasses(c)]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FactorListView(LoginRequiredMixin, PermissionListMixin, ListView):
 | 
			
		||||
    """Show list of all factors"""
 | 
			
		||||
 | 
			
		||||
    model = Factor
 | 
			
		||||
    template_name = 'administration/factor/list.html'
 | 
			
		||||
    permission_required = 'passbook_core.view_factor'
 | 
			
		||||
    ordering = 'order'
 | 
			
		||||
    template_name = "administration/factor/list.html"
 | 
			
		||||
    permission_required = "passbook_core.view_factor"
 | 
			
		||||
    ordering = "order"
 | 
			
		||||
    paginate_by = 40
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['types'] = {
 | 
			
		||||
            x.__name__: x._meta.verbose_name for x in all_subclasses(Factor)}
 | 
			
		||||
        kwargs["types"] = {
 | 
			
		||||
            x.__name__: x._meta.verbose_name for x in all_subclasses(Factor)
 | 
			
		||||
        }
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return super().get_queryset().select_subclasses()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FactorCreateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                       DjangoPermissionRequiredMixin, CreateAssignPermView):
 | 
			
		||||
class FactorCreateView(
 | 
			
		||||
    SuccessMessageMixin,
 | 
			
		||||
    LoginRequiredMixin,
 | 
			
		||||
    DjangoPermissionRequiredMixin,
 | 
			
		||||
    CreateAssignPermView,
 | 
			
		||||
):
 | 
			
		||||
    """Create new Factor"""
 | 
			
		||||
 | 
			
		||||
    model = Factor
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    permission_required = 'passbook_core.add_factor'
 | 
			
		||||
    template_name = "generic/create.html"
 | 
			
		||||
    permission_required = "passbook_core.add_factor"
 | 
			
		||||
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:factors')
 | 
			
		||||
    success_message = _('Successfully created Factor')
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:factors")
 | 
			
		||||
    success_message = _("Successfully created Factor")
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs = super().get_context_data(**kwargs)
 | 
			
		||||
        factor_type = self.request.GET.get('type')
 | 
			
		||||
        factor_type = self.request.GET.get("type")
 | 
			
		||||
        model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
 | 
			
		||||
        kwargs['type'] = model._meta.verbose_name
 | 
			
		||||
        kwargs["type"] = model._meta.verbose_name
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
        factor_type = self.request.GET.get('type')
 | 
			
		||||
        factor_type = self.request.GET.get("type")
 | 
			
		||||
        model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
 | 
			
		||||
        if not model:
 | 
			
		||||
            raise Http404
 | 
			
		||||
        return path_to_class(model.form)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FactorUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                       PermissionRequiredMixin, UpdateView):
 | 
			
		||||
class FactorUpdateView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
 | 
			
		||||
):
 | 
			
		||||
    """Update factor"""
 | 
			
		||||
 | 
			
		||||
    model = Factor
 | 
			
		||||
    permission_required = 'passbook_core.update_application'
 | 
			
		||||
    template_name = 'generic/update.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:factors')
 | 
			
		||||
    success_message = _('Successfully updated Factor')
 | 
			
		||||
    permission_required = "passbook_core.update_application"
 | 
			
		||||
    template_name = "generic/update.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:factors")
 | 
			
		||||
    success_message = _("Successfully updated Factor")
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
        form_class_path = self.get_object().form
 | 
			
		||||
@ -81,21 +89,26 @@ class FactorUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
        return form_class
 | 
			
		||||
 | 
			
		||||
    def get_object(self, queryset=None):
 | 
			
		||||
        return Factor.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
 | 
			
		||||
        return (
 | 
			
		||||
            Factor.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FactorDeleteView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                       PermissionRequiredMixin, DeleteView):
 | 
			
		||||
class FactorDeleteView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
 | 
			
		||||
):
 | 
			
		||||
    """Delete factor"""
 | 
			
		||||
 | 
			
		||||
    model = Factor
 | 
			
		||||
    template_name = 'generic/delete.html'
 | 
			
		||||
    permission_required = 'passbook_core.delete_factor'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:factors')
 | 
			
		||||
    success_message = _('Successfully deleted Factor')
 | 
			
		||||
    template_name = "generic/delete.html"
 | 
			
		||||
    permission_required = "passbook_core.delete_factor"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:factors")
 | 
			
		||||
    success_message = _("Successfully deleted Factor")
 | 
			
		||||
 | 
			
		||||
    def get_object(self, queryset=None):
 | 
			
		||||
        return Factor.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
 | 
			
		||||
        return (
 | 
			
		||||
            Factor.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def delete(self, request, *args, **kwargs):
 | 
			
		||||
        messages.success(self.request, self.success_message)
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
"""passbook Group administration"""
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import \
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import (
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin,
 | 
			
		||||
)
 | 
			
		||||
from django.contrib.messages.views import SuccessMessageMixin
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
from django.utils.translation import ugettext as _
 | 
			
		||||
@ -18,40 +19,45 @@ class GroupListView(LoginRequiredMixin, PermissionListMixin, ListView):
 | 
			
		||||
    """Show list of all groups"""
 | 
			
		||||
 | 
			
		||||
    model = Group
 | 
			
		||||
    permission_required = 'passbook_core.view_group'
 | 
			
		||||
    ordering = 'name'
 | 
			
		||||
    permission_required = "passbook_core.view_group"
 | 
			
		||||
    ordering = "name"
 | 
			
		||||
    paginate_by = 40
 | 
			
		||||
    template_name = 'administration/group/list.html'
 | 
			
		||||
    template_name = "administration/group/list.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GroupCreateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                      DjangoPermissionRequiredMixin, CreateAssignPermView):
 | 
			
		||||
class GroupCreateView(
 | 
			
		||||
    SuccessMessageMixin,
 | 
			
		||||
    LoginRequiredMixin,
 | 
			
		||||
    DjangoPermissionRequiredMixin,
 | 
			
		||||
    CreateAssignPermView,
 | 
			
		||||
):
 | 
			
		||||
    """Create new Group"""
 | 
			
		||||
 | 
			
		||||
    model = Group
 | 
			
		||||
    form_class = GroupForm
 | 
			
		||||
    permission_required = 'passbook_core.add_group'
 | 
			
		||||
    permission_required = "passbook_core.add_group"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:groups')
 | 
			
		||||
    success_message = _('Successfully created Group')
 | 
			
		||||
    template_name = "generic/create.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:groups")
 | 
			
		||||
    success_message = _("Successfully created Group")
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['type'] = 'Group'
 | 
			
		||||
        kwargs["type"] = "Group"
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GroupUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                      PermissionRequiredMixin, UpdateView):
 | 
			
		||||
class GroupUpdateView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
 | 
			
		||||
):
 | 
			
		||||
    """Update group"""
 | 
			
		||||
 | 
			
		||||
    model = Group
 | 
			
		||||
    form_class = GroupForm
 | 
			
		||||
    permission_required = 'passbook_core.change_group'
 | 
			
		||||
    permission_required = "passbook_core.change_group"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/update.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:groups')
 | 
			
		||||
    success_message = _('Successfully updated Group')
 | 
			
		||||
    template_name = "generic/update.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:groups")
 | 
			
		||||
    success_message = _("Successfully updated Group")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GroupDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView):
 | 
			
		||||
@ -59,9 +65,9 @@ class GroupDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView):
 | 
			
		||||
 | 
			
		||||
    model = Group
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/delete.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:groups')
 | 
			
		||||
    success_message = _('Successfully deleted Group')
 | 
			
		||||
    template_name = "generic/delete.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:groups")
 | 
			
		||||
    success_message = _("Successfully deleted Group")
 | 
			
		||||
 | 
			
		||||
    def delete(self, request, *args, **kwargs):
 | 
			
		||||
        messages.success(self.request, self.success_message)
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
"""passbook Invitation administration"""
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import \
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import (
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin,
 | 
			
		||||
)
 | 
			
		||||
from django.contrib.messages.views import SuccessMessageMixin
 | 
			
		||||
from django.http import HttpResponseRedirect
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
@ -20,47 +21,49 @@ class InvitationListView(LoginRequiredMixin, PermissionListMixin, ListView):
 | 
			
		||||
    """Show list of all invitations"""
 | 
			
		||||
 | 
			
		||||
    model = Invitation
 | 
			
		||||
    permission_required = 'passbook_core.view_invitation'
 | 
			
		||||
    template_name = 'administration/invitation/list.html'
 | 
			
		||||
    permission_required = "passbook_core.view_invitation"
 | 
			
		||||
    template_name = "administration/invitation/list.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvitationCreateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                           DjangoPermissionRequiredMixin, CreateAssignPermView):
 | 
			
		||||
class InvitationCreateView(
 | 
			
		||||
    SuccessMessageMixin,
 | 
			
		||||
    LoginRequiredMixin,
 | 
			
		||||
    DjangoPermissionRequiredMixin,
 | 
			
		||||
    CreateAssignPermView,
 | 
			
		||||
):
 | 
			
		||||
    """Create new Invitation"""
 | 
			
		||||
 | 
			
		||||
    model = Invitation
 | 
			
		||||
    form_class = InvitationForm
 | 
			
		||||
    permission_required = 'passbook_core.add_invitation'
 | 
			
		||||
    permission_required = "passbook_core.add_invitation"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:invitations')
 | 
			
		||||
    success_message = _('Successfully created Invitation')
 | 
			
		||||
    template_name = "generic/create.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:invitations")
 | 
			
		||||
    success_message = _("Successfully created Invitation")
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['type'] = 'Invitation'
 | 
			
		||||
        kwargs["type"] = "Invitation"
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        obj = form.save(commit=False)
 | 
			
		||||
        obj.created_by = self.request.user
 | 
			
		||||
        obj.save()
 | 
			
		||||
        invitation_created.send(
 | 
			
		||||
            sender=self,
 | 
			
		||||
            request=self.request,
 | 
			
		||||
            invitation=obj)
 | 
			
		||||
        invitation_created.send(sender=self, request=self.request, invitation=obj)
 | 
			
		||||
        return HttpResponseRedirect(self.success_url)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvitationDeleteView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                           PermissionRequiredMixin, DeleteView):
 | 
			
		||||
class InvitationDeleteView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
 | 
			
		||||
):
 | 
			
		||||
    """Delete invitation"""
 | 
			
		||||
 | 
			
		||||
    model = Invitation
 | 
			
		||||
    permission_required = 'passbook_core.delete_invitation'
 | 
			
		||||
    permission_required = "passbook_core.delete_invitation"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/delete.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:invitations')
 | 
			
		||||
    success_message = _('Successfully deleted Invitation')
 | 
			
		||||
    template_name = "generic/delete.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:invitations")
 | 
			
		||||
    success_message = _("Successfully deleted Invitation")
 | 
			
		||||
 | 
			
		||||
    def delete(self, request, *args, **kwargs):
 | 
			
		||||
        messages.success(self.request, self.success_message)
 | 
			
		||||
 | 
			
		||||
@ -5,34 +5,45 @@ from django.views.generic import TemplateView
 | 
			
		||||
 | 
			
		||||
from passbook import __version__
 | 
			
		||||
from passbook.admin.mixins import AdminRequiredMixin
 | 
			
		||||
from passbook.core.models import (Application, Factor, Invitation, Policy,
 | 
			
		||||
                                  Provider, Source, User)
 | 
			
		||||
from passbook.core.models import (
 | 
			
		||||
    Application,
 | 
			
		||||
    Factor,
 | 
			
		||||
    Invitation,
 | 
			
		||||
    Policy,
 | 
			
		||||
    Provider,
 | 
			
		||||
    Source,
 | 
			
		||||
    User,
 | 
			
		||||
)
 | 
			
		||||
from passbook.root.celery import CELERY_APP
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
 | 
			
		||||
    """Overview View"""
 | 
			
		||||
 | 
			
		||||
    template_name = 'administration/overview.html'
 | 
			
		||||
    template_name = "administration/overview.html"
 | 
			
		||||
 | 
			
		||||
    def post(self, *args, **kwargs):
 | 
			
		||||
        """Handle post (clear cache from modal)"""
 | 
			
		||||
        if 'clear' in self.request.POST:
 | 
			
		||||
        if "clear" in self.request.POST:
 | 
			
		||||
            cache.clear()
 | 
			
		||||
            return redirect(reverse('passbook_core:auth-login'))
 | 
			
		||||
            return redirect(reverse("passbook_core:auth-login"))
 | 
			
		||||
        return self.get(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['application_count'] = len(Application.objects.all())
 | 
			
		||||
        kwargs['policy_count'] = len(Policy.objects.all())
 | 
			
		||||
        kwargs['user_count'] = len(User.objects.all())
 | 
			
		||||
        kwargs['provider_count'] = len(Provider.objects.all())
 | 
			
		||||
        kwargs['source_count'] = len(Source.objects.all())
 | 
			
		||||
        kwargs['factor_count'] = len(Factor.objects.all())
 | 
			
		||||
        kwargs['invitation_count'] = len(Invitation.objects.all())
 | 
			
		||||
        kwargs['version'] = __version__
 | 
			
		||||
        kwargs['worker_count'] = len(CELERY_APP.control.ping(timeout=0.5))
 | 
			
		||||
        kwargs['providers_without_application'] = Provider.objects.filter(application=None)
 | 
			
		||||
        kwargs['policies_without_attachment'] = len(Policy.objects.filter(policymodel__isnull=True))
 | 
			
		||||
        kwargs['cached_policies'] = len(cache.keys('policy_*'))
 | 
			
		||||
        kwargs["application_count"] = len(Application.objects.all())
 | 
			
		||||
        kwargs["policy_count"] = len(Policy.objects.all())
 | 
			
		||||
        kwargs["user_count"] = len(User.objects.all())
 | 
			
		||||
        kwargs["provider_count"] = len(Provider.objects.all())
 | 
			
		||||
        kwargs["source_count"] = len(Source.objects.all())
 | 
			
		||||
        kwargs["factor_count"] = len(Factor.objects.all())
 | 
			
		||||
        kwargs["invitation_count"] = len(Invitation.objects.all())
 | 
			
		||||
        kwargs["version"] = __version__
 | 
			
		||||
        kwargs["worker_count"] = len(CELERY_APP.control.ping(timeout=0.5))
 | 
			
		||||
        kwargs["providers_without_application"] = Provider.objects.filter(
 | 
			
		||||
            application=None
 | 
			
		||||
        )
 | 
			
		||||
        kwargs["policies_without_attachment"] = len(
 | 
			
		||||
            Policy.objects.filter(policymodel__isnull=True)
 | 
			
		||||
        )
 | 
			
		||||
        kwargs["cached_policies"] = len(cache.keys("policy_*"))
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
"""passbook Policy administration"""
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import \
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import (
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin,
 | 
			
		||||
)
 | 
			
		||||
from django.contrib.messages.views import SuccessMessageMixin
 | 
			
		||||
from django.http import Http404
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
@ -22,49 +23,54 @@ class PolicyListView(LoginRequiredMixin, PermissionListMixin, ListView):
 | 
			
		||||
    """Show list of all policies"""
 | 
			
		||||
 | 
			
		||||
    model = Policy
 | 
			
		||||
    permission_required = 'passbook_core.view_policy'
 | 
			
		||||
    permission_required = "passbook_core.view_policy"
 | 
			
		||||
 | 
			
		||||
    template_name = 'administration/policy/list.html'
 | 
			
		||||
    template_name = "administration/policy/list.html"
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['types'] = {
 | 
			
		||||
            x.__name__: x._meta.verbose_name for x in Policy.__subclasses__()}
 | 
			
		||||
        kwargs["types"] = {
 | 
			
		||||
            x.__name__: x._meta.verbose_name for x in Policy.__subclasses__()
 | 
			
		||||
        }
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return super().get_queryset().order_by('order').select_subclasses()
 | 
			
		||||
        return super().get_queryset().order_by("order").select_subclasses()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PolicyCreateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                       DjangoPermissionRequiredMixin, CreateAssignPermView):
 | 
			
		||||
class PolicyCreateView(
 | 
			
		||||
    SuccessMessageMixin,
 | 
			
		||||
    LoginRequiredMixin,
 | 
			
		||||
    DjangoPermissionRequiredMixin,
 | 
			
		||||
    CreateAssignPermView,
 | 
			
		||||
):
 | 
			
		||||
    """Create new Policy"""
 | 
			
		||||
 | 
			
		||||
    model = Policy
 | 
			
		||||
    permission_required = 'passbook_core.add_policy'
 | 
			
		||||
    permission_required = "passbook_core.add_policy"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:policies')
 | 
			
		||||
    success_message = _('Successfully created Policy')
 | 
			
		||||
    template_name = "generic/create.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:policies")
 | 
			
		||||
    success_message = _("Successfully created Policy")
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
        policy_type = self.request.GET.get('type')
 | 
			
		||||
        model = next(x for x in Policy.__subclasses__()
 | 
			
		||||
                     if x.__name__ == policy_type)
 | 
			
		||||
        policy_type = self.request.GET.get("type")
 | 
			
		||||
        model = next(x for x in Policy.__subclasses__() if x.__name__ == policy_type)
 | 
			
		||||
        if not model:
 | 
			
		||||
            raise Http404
 | 
			
		||||
        return path_to_class(model.form)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PolicyUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                       PermissionRequiredMixin, UpdateView):
 | 
			
		||||
class PolicyUpdateView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
 | 
			
		||||
):
 | 
			
		||||
    """Update policy"""
 | 
			
		||||
 | 
			
		||||
    model = Policy
 | 
			
		||||
    permission_required = 'passbook_core.change_policy'
 | 
			
		||||
    permission_required = "passbook_core.change_policy"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/update.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:policies')
 | 
			
		||||
    success_message = _('Successfully updated Policy')
 | 
			
		||||
    template_name = "generic/update.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:policies")
 | 
			
		||||
    success_message = _("Successfully updated Policy")
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
        form_class_path = self.get_object().form
 | 
			
		||||
@ -72,22 +78,27 @@ class PolicyUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
        return form_class
 | 
			
		||||
 | 
			
		||||
    def get_object(self, queryset=None):
 | 
			
		||||
        return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
 | 
			
		||||
        return (
 | 
			
		||||
            Policy.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PolicyDeleteView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                       PermissionRequiredMixin, DeleteView):
 | 
			
		||||
class PolicyDeleteView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
 | 
			
		||||
):
 | 
			
		||||
    """Delete policy"""
 | 
			
		||||
 | 
			
		||||
    model = Policy
 | 
			
		||||
    permission_required = 'passbook_core.delete_policy'
 | 
			
		||||
    permission_required = "passbook_core.delete_policy"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/delete.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:policies')
 | 
			
		||||
    success_message = _('Successfully deleted Policy')
 | 
			
		||||
    template_name = "generic/delete.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:policies")
 | 
			
		||||
    success_message = _("Successfully deleted Policy")
 | 
			
		||||
 | 
			
		||||
    def get_object(self, queryset=None):
 | 
			
		||||
        return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
 | 
			
		||||
        return (
 | 
			
		||||
            Policy.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def delete(self, request, *args, **kwargs):
 | 
			
		||||
        messages.success(self.request, self.success_message)
 | 
			
		||||
@ -99,15 +110,17 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
 | 
			
		||||
 | 
			
		||||
    model = Policy
 | 
			
		||||
    form_class = PolicyTestForm
 | 
			
		||||
    permission_required = 'passbook_core.view_policy'
 | 
			
		||||
    template_name = 'administration/policy/test.html'
 | 
			
		||||
    permission_required = "passbook_core.view_policy"
 | 
			
		||||
    template_name = "administration/policy/test.html"
 | 
			
		||||
    object = None
 | 
			
		||||
 | 
			
		||||
    def get_object(self, queryset=None):
 | 
			
		||||
        return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
 | 
			
		||||
        return (
 | 
			
		||||
            Policy.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['policy'] = self.get_object()
 | 
			
		||||
        kwargs["policy"] = self.get_object()
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def post(self, *args, **kwargs):
 | 
			
		||||
@ -116,13 +129,13 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
 | 
			
		||||
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        policy = self.get_object()
 | 
			
		||||
        user = form.cleaned_data.get('user')
 | 
			
		||||
        user = form.cleaned_data.get("user")
 | 
			
		||||
        policy_engine = PolicyEngine([policy], user, self.request)
 | 
			
		||||
        policy_engine.use_cache = False
 | 
			
		||||
        policy_engine.build()
 | 
			
		||||
        result = policy_engine.passing
 | 
			
		||||
        if result:
 | 
			
		||||
            messages.success(self.request, _('User successfully passed policy.'))
 | 
			
		||||
            messages.success(self.request, _("User successfully passed policy."))
 | 
			
		||||
        else:
 | 
			
		||||
            messages.error(self.request, _("User didn't pass policy."))
 | 
			
		||||
        return self.render_to_response(self.get_context_data(form=form, result=result))
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
"""passbook PropertyMapping administration"""
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import \
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import (
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin,
 | 
			
		||||
)
 | 
			
		||||
from django.contrib.messages.views import SuccessMessageMixin
 | 
			
		||||
from django.http import Http404
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
@ -18,65 +19,78 @@ from passbook.lib.views import CreateAssignPermView
 | 
			
		||||
def all_subclasses(cls):
 | 
			
		||||
    """Recursively return all subclassess of cls"""
 | 
			
		||||
    return set(cls.__subclasses__()).union(
 | 
			
		||||
        [s for c in cls.__subclasses__() for s in all_subclasses(c)])
 | 
			
		||||
        [s for c in cls.__subclasses__() for s in all_subclasses(c)]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PropertyMappingListView(LoginRequiredMixin, PermissionListMixin, ListView):
 | 
			
		||||
    """Show list of all property_mappings"""
 | 
			
		||||
 | 
			
		||||
    model = PropertyMapping
 | 
			
		||||
    permission_required = 'passbook_core.view_propertymapping'
 | 
			
		||||
    template_name = 'administration/property_mapping/list.html'
 | 
			
		||||
    ordering = 'name'
 | 
			
		||||
    permission_required = "passbook_core.view_propertymapping"
 | 
			
		||||
    template_name = "administration/property_mapping/list.html"
 | 
			
		||||
    ordering = "name"
 | 
			
		||||
    paginate_by = 40
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['types'] = {
 | 
			
		||||
            x.__name__: x._meta.verbose_name for x in all_subclasses(PropertyMapping)}
 | 
			
		||||
        kwargs["types"] = {
 | 
			
		||||
            x.__name__: x._meta.verbose_name for x in all_subclasses(PropertyMapping)
 | 
			
		||||
        }
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return super().get_queryset().select_subclasses()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PropertyMappingCreateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                                DjangoPermissionRequiredMixin, CreateAssignPermView):
 | 
			
		||||
class PropertyMappingCreateView(
 | 
			
		||||
    SuccessMessageMixin,
 | 
			
		||||
    LoginRequiredMixin,
 | 
			
		||||
    DjangoPermissionRequiredMixin,
 | 
			
		||||
    CreateAssignPermView,
 | 
			
		||||
):
 | 
			
		||||
    """Create new PropertyMapping"""
 | 
			
		||||
 | 
			
		||||
    model = PropertyMapping
 | 
			
		||||
    permission_required = 'passbook_core.add_propertymapping'
 | 
			
		||||
    permission_required = "passbook_core.add_propertymapping"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:property-mappings')
 | 
			
		||||
    success_message = _('Successfully created Property Mapping')
 | 
			
		||||
    template_name = "generic/create.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:property-mappings")
 | 
			
		||||
    success_message = _("Successfully created Property Mapping")
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs = super().get_context_data(**kwargs)
 | 
			
		||||
        property_mapping_type = self.request.GET.get('type')
 | 
			
		||||
        model = next(x for x in all_subclasses(PropertyMapping)
 | 
			
		||||
                     if x.__name__ == property_mapping_type)
 | 
			
		||||
        kwargs['type'] = model._meta.verbose_name
 | 
			
		||||
        property_mapping_type = self.request.GET.get("type")
 | 
			
		||||
        model = next(
 | 
			
		||||
            x
 | 
			
		||||
            for x in all_subclasses(PropertyMapping)
 | 
			
		||||
            if x.__name__ == property_mapping_type
 | 
			
		||||
        )
 | 
			
		||||
        kwargs["type"] = model._meta.verbose_name
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
        property_mapping_type = self.request.GET.get('type')
 | 
			
		||||
        model = next(x for x in all_subclasses(PropertyMapping)
 | 
			
		||||
                     if x.__name__ == property_mapping_type)
 | 
			
		||||
        property_mapping_type = self.request.GET.get("type")
 | 
			
		||||
        model = next(
 | 
			
		||||
            x
 | 
			
		||||
            for x in all_subclasses(PropertyMapping)
 | 
			
		||||
            if x.__name__ == property_mapping_type
 | 
			
		||||
        )
 | 
			
		||||
        if not model:
 | 
			
		||||
            raise Http404
 | 
			
		||||
        return path_to_class(model.form)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PropertyMappingUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                                PermissionRequiredMixin, UpdateView):
 | 
			
		||||
class PropertyMappingUpdateView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
 | 
			
		||||
):
 | 
			
		||||
    """Update property_mapping"""
 | 
			
		||||
 | 
			
		||||
    model = PropertyMapping
 | 
			
		||||
    permission_required = 'passbook_core.change_propertymapping'
 | 
			
		||||
    permission_required = "passbook_core.change_propertymapping"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/update.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:property-mappings')
 | 
			
		||||
    success_message = _('Successfully updated Property Mapping')
 | 
			
		||||
    template_name = "generic/update.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:property-mappings")
 | 
			
		||||
    success_message = _("Successfully updated Property Mapping")
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
        form_class_path = self.get_object().form
 | 
			
		||||
@ -84,22 +98,31 @@ class PropertyMappingUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
        return form_class
 | 
			
		||||
 | 
			
		||||
    def get_object(self, queryset=None):
 | 
			
		||||
        return PropertyMapping.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
 | 
			
		||||
        return (
 | 
			
		||||
            PropertyMapping.objects.filter(pk=self.kwargs.get("pk"))
 | 
			
		||||
            .select_subclasses()
 | 
			
		||||
            .first()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PropertyMappingDeleteView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                                PermissionRequiredMixin, DeleteView):
 | 
			
		||||
class PropertyMappingDeleteView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
 | 
			
		||||
):
 | 
			
		||||
    """Delete property_mapping"""
 | 
			
		||||
 | 
			
		||||
    model = PropertyMapping
 | 
			
		||||
    permission_required = 'passbook_core.delete_propertymapping'
 | 
			
		||||
    permission_required = "passbook_core.delete_propertymapping"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/delete.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:property-mappings')
 | 
			
		||||
    success_message = _('Successfully deleted Property Mapping')
 | 
			
		||||
    template_name = "generic/delete.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:property-mappings")
 | 
			
		||||
    success_message = _("Successfully deleted Property Mapping")
 | 
			
		||||
 | 
			
		||||
    def get_object(self, queryset=None):
 | 
			
		||||
        return PropertyMapping.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
 | 
			
		||||
        return (
 | 
			
		||||
            PropertyMapping.objects.filter(pk=self.kwargs.get("pk"))
 | 
			
		||||
            .select_subclasses()
 | 
			
		||||
            .first()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def delete(self, request, *args, **kwargs):
 | 
			
		||||
        messages.success(self.request, self.success_message)
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
"""passbook Provider administration"""
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import \
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import (
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin,
 | 
			
		||||
)
 | 
			
		||||
from django.contrib.messages.views import SuccessMessageMixin
 | 
			
		||||
from django.http import Http404
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
@ -19,48 +20,55 @@ class ProviderListView(LoginRequiredMixin, PermissionListMixin, ListView):
 | 
			
		||||
    """Show list of all providers"""
 | 
			
		||||
 | 
			
		||||
    model = Provider
 | 
			
		||||
    permission_required = 'passbook_core.add_provider'
 | 
			
		||||
    template_name = 'administration/provider/list.html'
 | 
			
		||||
    permission_required = "passbook_core.add_provider"
 | 
			
		||||
    template_name = "administration/provider/list.html"
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['types'] = {
 | 
			
		||||
            x.__name__: x._meta.verbose_name for x in Provider.__subclasses__()}
 | 
			
		||||
        kwargs["types"] = {
 | 
			
		||||
            x.__name__: x._meta.verbose_name for x in Provider.__subclasses__()
 | 
			
		||||
        }
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return super().get_queryset().select_subclasses()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProviderCreateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                         DjangoPermissionRequiredMixin, CreateAssignPermView):
 | 
			
		||||
class ProviderCreateView(
 | 
			
		||||
    SuccessMessageMixin,
 | 
			
		||||
    LoginRequiredMixin,
 | 
			
		||||
    DjangoPermissionRequiredMixin,
 | 
			
		||||
    CreateAssignPermView,
 | 
			
		||||
):
 | 
			
		||||
    """Create new Provider"""
 | 
			
		||||
 | 
			
		||||
    model = Provider
 | 
			
		||||
    permission_required = 'passbook_core.add_provider'
 | 
			
		||||
    permission_required = "passbook_core.add_provider"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:providers')
 | 
			
		||||
    success_message = _('Successfully created Provider')
 | 
			
		||||
    template_name = "generic/create.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:providers")
 | 
			
		||||
    success_message = _("Successfully created Provider")
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
        provider_type = self.request.GET.get('type')
 | 
			
		||||
        model = next(x for x in Provider.__subclasses__()
 | 
			
		||||
                     if x.__name__ == provider_type)
 | 
			
		||||
        provider_type = self.request.GET.get("type")
 | 
			
		||||
        model = next(
 | 
			
		||||
            x for x in Provider.__subclasses__() if x.__name__ == provider_type
 | 
			
		||||
        )
 | 
			
		||||
        if not model:
 | 
			
		||||
            raise Http404
 | 
			
		||||
        return path_to_class(model.form)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProviderUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                         PermissionRequiredMixin, UpdateView):
 | 
			
		||||
class ProviderUpdateView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
 | 
			
		||||
):
 | 
			
		||||
    """Update provider"""
 | 
			
		||||
 | 
			
		||||
    model = Provider
 | 
			
		||||
    permission_required = 'passbook_core.change_provider'
 | 
			
		||||
    permission_required = "passbook_core.change_provider"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/update.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:providers')
 | 
			
		||||
    success_message = _('Successfully updated Provider')
 | 
			
		||||
    template_name = "generic/update.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:providers")
 | 
			
		||||
    success_message = _("Successfully updated Provider")
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
        form_class_path = self.get_object().form
 | 
			
		||||
@ -68,22 +76,31 @@ class ProviderUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
        return form_class
 | 
			
		||||
 | 
			
		||||
    def get_object(self, queryset=None):
 | 
			
		||||
        return Provider.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
 | 
			
		||||
        return (
 | 
			
		||||
            Provider.objects.filter(pk=self.kwargs.get("pk"))
 | 
			
		||||
            .select_subclasses()
 | 
			
		||||
            .first()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProviderDeleteView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                         PermissionRequiredMixin, DeleteView):
 | 
			
		||||
class ProviderDeleteView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
 | 
			
		||||
):
 | 
			
		||||
    """Delete provider"""
 | 
			
		||||
 | 
			
		||||
    model = Provider
 | 
			
		||||
    permission_required = 'passbook_core.delete_provider'
 | 
			
		||||
    permission_required = "passbook_core.delete_provider"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/delete.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:providers')
 | 
			
		||||
    success_message = _('Successfully deleted Provider')
 | 
			
		||||
    template_name = "generic/delete.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:providers")
 | 
			
		||||
    success_message = _("Successfully deleted Provider")
 | 
			
		||||
 | 
			
		||||
    def get_object(self, queryset=None):
 | 
			
		||||
        return Provider.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
 | 
			
		||||
        return (
 | 
			
		||||
            Provider.objects.filter(pk=self.kwargs.get("pk"))
 | 
			
		||||
            .select_subclasses()
 | 
			
		||||
            .first()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def delete(self, request, *args, **kwargs):
 | 
			
		||||
        messages.success(self.request, self.success_message)
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
"""passbook Source administration"""
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import \
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import (
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin,
 | 
			
		||||
)
 | 
			
		||||
from django.contrib.messages.views import SuccessMessageMixin
 | 
			
		||||
from django.http import Http404
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
@ -18,55 +19,63 @@ from passbook.lib.views import CreateAssignPermView
 | 
			
		||||
def all_subclasses(cls):
 | 
			
		||||
    """Recursively return all subclassess of cls"""
 | 
			
		||||
    return set(cls.__subclasses__()).union(
 | 
			
		||||
        [s for c in cls.__subclasses__() for s in all_subclasses(c)])
 | 
			
		||||
        [s for c in cls.__subclasses__() for s in all_subclasses(c)]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SourceListView(LoginRequiredMixin, PermissionListMixin, ListView):
 | 
			
		||||
    """Show list of all sources"""
 | 
			
		||||
 | 
			
		||||
    model = Source
 | 
			
		||||
    permission_required = 'passbook_core.view_source'
 | 
			
		||||
    ordering = 'name'
 | 
			
		||||
    permission_required = "passbook_core.view_source"
 | 
			
		||||
    ordering = "name"
 | 
			
		||||
    paginate_by = 40
 | 
			
		||||
    template_name = 'administration/source/list.html'
 | 
			
		||||
    template_name = "administration/source/list.html"
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['types'] = {
 | 
			
		||||
            x.__name__: x._meta.verbose_name for x in all_subclasses(Source)}
 | 
			
		||||
        kwargs["types"] = {
 | 
			
		||||
            x.__name__: x._meta.verbose_name for x in all_subclasses(Source)
 | 
			
		||||
        }
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return super().get_queryset().select_subclasses()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SourceCreateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                       DjangoPermissionRequiredMixin, CreateAssignPermView):
 | 
			
		||||
class SourceCreateView(
 | 
			
		||||
    SuccessMessageMixin,
 | 
			
		||||
    LoginRequiredMixin,
 | 
			
		||||
    DjangoPermissionRequiredMixin,
 | 
			
		||||
    CreateAssignPermView,
 | 
			
		||||
):
 | 
			
		||||
    """Create new Source"""
 | 
			
		||||
 | 
			
		||||
    model = Source
 | 
			
		||||
    permission_required = 'passbook_core.add_source'
 | 
			
		||||
    permission_required = "passbook_core.add_source"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:sources')
 | 
			
		||||
    success_message = _('Successfully created Source')
 | 
			
		||||
    template_name = "generic/create.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:sources")
 | 
			
		||||
    success_message = _("Successfully created Source")
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
        source_type = self.request.GET.get('type')
 | 
			
		||||
        source_type = self.request.GET.get("type")
 | 
			
		||||
        model = next(x for x in all_subclasses(Source) if x.__name__ == source_type)
 | 
			
		||||
        if not model:
 | 
			
		||||
            raise Http404
 | 
			
		||||
        return path_to_class(model.form)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SourceUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                       PermissionRequiredMixin, UpdateView):
 | 
			
		||||
class SourceUpdateView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
 | 
			
		||||
):
 | 
			
		||||
    """Update source"""
 | 
			
		||||
 | 
			
		||||
    model = Source
 | 
			
		||||
    permission_required = 'passbook_core.change_source'
 | 
			
		||||
    permission_required = "passbook_core.change_source"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/update.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:sources')
 | 
			
		||||
    success_message = _('Successfully updated Source')
 | 
			
		||||
    template_name = "generic/update.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:sources")
 | 
			
		||||
    success_message = _("Successfully updated Source")
 | 
			
		||||
 | 
			
		||||
    def get_form_class(self):
 | 
			
		||||
        form_class_path = self.get_object().form
 | 
			
		||||
@ -74,22 +83,27 @@ class SourceUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
        return form_class
 | 
			
		||||
 | 
			
		||||
    def get_object(self, queryset=None):
 | 
			
		||||
        return Source.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
 | 
			
		||||
        return (
 | 
			
		||||
            Source.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SourceDeleteView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                       PermissionRequiredMixin, DeleteView):
 | 
			
		||||
class SourceDeleteView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
 | 
			
		||||
):
 | 
			
		||||
    """Delete source"""
 | 
			
		||||
 | 
			
		||||
    model = Source
 | 
			
		||||
    permission_required = 'passbook_core.delete_source'
 | 
			
		||||
    permission_required = "passbook_core.delete_source"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/delete.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:sources')
 | 
			
		||||
    success_message = _('Successfully deleted Source')
 | 
			
		||||
    template_name = "generic/delete.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:sources")
 | 
			
		||||
    success_message = _("Successfully deleted Source")
 | 
			
		||||
 | 
			
		||||
    def get_object(self, queryset=None):
 | 
			
		||||
        return Source.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first()
 | 
			
		||||
        return (
 | 
			
		||||
            Source.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def delete(self, request, *args, **kwargs):
 | 
			
		||||
        messages.success(self.request, self.success_message)
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
"""passbook User administration"""
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import \
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin
 | 
			
		||||
from django.contrib.auth.mixins import (
 | 
			
		||||
    PermissionRequiredMixin as DjangoPermissionRequiredMixin,
 | 
			
		||||
)
 | 
			
		||||
from django.contrib.messages.views import SuccessMessageMixin
 | 
			
		||||
from django.shortcuts import redirect
 | 
			
		||||
from django.urls import reverse, reverse_lazy
 | 
			
		||||
@ -19,50 +20,56 @@ class UserListView(LoginRequiredMixin, PermissionListMixin, ListView):
 | 
			
		||||
    """Show list of all users"""
 | 
			
		||||
 | 
			
		||||
    model = User
 | 
			
		||||
    permission_required = 'passbook_core.view_user'
 | 
			
		||||
    ordering = 'username'
 | 
			
		||||
    permission_required = "passbook_core.view_user"
 | 
			
		||||
    ordering = "username"
 | 
			
		||||
    paginate_by = 40
 | 
			
		||||
    template_name = 'administration/user/list.html'
 | 
			
		||||
    template_name = "administration/user/list.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserCreateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                     DjangoPermissionRequiredMixin, CreateAssignPermView):
 | 
			
		||||
class UserCreateView(
 | 
			
		||||
    SuccessMessageMixin,
 | 
			
		||||
    LoginRequiredMixin,
 | 
			
		||||
    DjangoPermissionRequiredMixin,
 | 
			
		||||
    CreateAssignPermView,
 | 
			
		||||
):
 | 
			
		||||
    """Create user"""
 | 
			
		||||
 | 
			
		||||
    model = User
 | 
			
		||||
    form_class = UserForm
 | 
			
		||||
    permission_required = 'passbook_core.add_user'
 | 
			
		||||
    permission_required = "passbook_core.add_user"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/create.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:users')
 | 
			
		||||
    success_message = _('Successfully created User')
 | 
			
		||||
    template_name = "generic/create.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:users")
 | 
			
		||||
    success_message = _("Successfully created User")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserUpdateView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                     PermissionRequiredMixin, UpdateView):
 | 
			
		||||
class UserUpdateView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
 | 
			
		||||
):
 | 
			
		||||
    """Update user"""
 | 
			
		||||
 | 
			
		||||
    model = User
 | 
			
		||||
    form_class = UserForm
 | 
			
		||||
    permission_required = 'passbook_core.change_user'
 | 
			
		||||
    permission_required = "passbook_core.change_user"
 | 
			
		||||
 | 
			
		||||
    # By default the object's name is user which is used by other checks
 | 
			
		||||
    context_object_name = 'object'
 | 
			
		||||
    template_name = 'generic/update.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:users')
 | 
			
		||||
    success_message = _('Successfully updated User')
 | 
			
		||||
    context_object_name = "object"
 | 
			
		||||
    template_name = "generic/update.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:users")
 | 
			
		||||
    success_message = _("Successfully updated User")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserDeleteView(SuccessMessageMixin, LoginRequiredMixin,
 | 
			
		||||
                     PermissionRequiredMixin, DeleteView):
 | 
			
		||||
class UserDeleteView(
 | 
			
		||||
    SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
 | 
			
		||||
):
 | 
			
		||||
    """Delete user"""
 | 
			
		||||
 | 
			
		||||
    model = User
 | 
			
		||||
    permission_required = 'passbook_core.delete_user'
 | 
			
		||||
    permission_required = "passbook_core.delete_user"
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/delete.html'
 | 
			
		||||
    success_url = reverse_lazy('passbook_admin:users')
 | 
			
		||||
    success_message = _('Successfully deleted User')
 | 
			
		||||
    template_name = "generic/delete.html"
 | 
			
		||||
    success_url = reverse_lazy("passbook_admin:users")
 | 
			
		||||
    success_message = _("Successfully deleted User")
 | 
			
		||||
 | 
			
		||||
    def delete(self, request, *args, **kwargs):
 | 
			
		||||
        messages.success(self.request, self.success_message)
 | 
			
		||||
@ -73,13 +80,16 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
 | 
			
		||||
    """Get Password reset link for user"""
 | 
			
		||||
 | 
			
		||||
    model = User
 | 
			
		||||
    permission_required = 'passbook_core.reset_user_password'
 | 
			
		||||
    permission_required = "passbook_core.reset_user_password"
 | 
			
		||||
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
        """Create nonce for user and return link"""
 | 
			
		||||
        super().get(request, *args, **kwargs)
 | 
			
		||||
        nonce = Nonce.objects.create(user=self.object)
 | 
			
		||||
        link = request.build_absolute_uri(reverse(
 | 
			
		||||
            'passbook_core:auth-password-reset', kwargs={'nonce': nonce.uuid}))
 | 
			
		||||
        messages.success(request, _('Password reset link: <pre>%(link)s</pre>' % {'link': link}))
 | 
			
		||||
        return redirect('passbook_admin:users')
 | 
			
		||||
        link = request.build_absolute_uri(
 | 
			
		||||
            reverse("passbook_core:auth-password-reset", kwargs={"nonce": nonce.uuid})
 | 
			
		||||
        )
 | 
			
		||||
        messages.success(
 | 
			
		||||
            request, _("Password reset link: <pre>%(link)s</pre>" % {"link": link})
 | 
			
		||||
        )
 | 
			
		||||
        return redirect("passbook_admin:users")
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ from django.apps import AppConfig
 | 
			
		||||
class PassbookAPIConfig(AppConfig):
 | 
			
		||||
    """passbook API Config"""
 | 
			
		||||
 | 
			
		||||
    name = 'passbook.api'
 | 
			
		||||
    label = 'passbook_api'
 | 
			
		||||
    mountpoint = 'api/'
 | 
			
		||||
    verbose_name = 'passbook API'
 | 
			
		||||
    name = "passbook.api"
 | 
			
		||||
    label = "passbook_api"
 | 
			
		||||
    mountpoint = "api/"
 | 
			
		||||
    verbose_name = "passbook API"
 | 
			
		||||
 | 
			
		||||
@ -9,13 +9,13 @@ class CustomObjectPermissions(DjangoObjectPermissions):
 | 
			
		||||
    """Similar to `DjangoObjectPermissions`, but adding 'view' permissions."""
 | 
			
		||||
 | 
			
		||||
    perms_map = {
 | 
			
		||||
        'GET': ['%(app_label)s.view_%(model_name)s'],
 | 
			
		||||
        'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
 | 
			
		||||
        'HEAD': ['%(app_label)s.view_%(model_name)s'],
 | 
			
		||||
        'POST': ['%(app_label)s.add_%(model_name)s'],
 | 
			
		||||
        'PUT': ['%(app_label)s.change_%(model_name)s'],
 | 
			
		||||
        'PATCH': ['%(app_label)s.change_%(model_name)s'],
 | 
			
		||||
        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
 | 
			
		||||
        "GET": ["%(app_label)s.view_%(model_name)s"],
 | 
			
		||||
        "OPTIONS": ["%(app_label)s.view_%(model_name)s"],
 | 
			
		||||
        "HEAD": ["%(app_label)s.view_%(model_name)s"],
 | 
			
		||||
        "POST": ["%(app_label)s.add_%(model_name)s"],
 | 
			
		||||
        "PUT": ["%(app_label)s.change_%(model_name)s"],
 | 
			
		||||
        "PATCH": ["%(app_label)s.change_%(model_name)s"],
 | 
			
		||||
        "DELETE": ["%(app_label)s.delete_%(model_name)s"],
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,6 @@ from passbook.api.v1.urls import urlpatterns as v1_urls
 | 
			
		||||
from passbook.api.v2.urls import urlpatterns as v2_urls
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path('v1/', include(v1_urls)),
 | 
			
		||||
    path('v2/', include(v2_urls)),
 | 
			
		||||
    path("v1/", include(v1_urls)),
 | 
			
		||||
    path("v2/", include(v2_urls)),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -7,16 +7,16 @@ from oauth2_provider.views.mixins import ScopedResourceMixin
 | 
			
		||||
class OpenIDUserInfoView(ScopedResourceMixin, View):
 | 
			
		||||
    """Passbook v1 OpenID API"""
 | 
			
		||||
 | 
			
		||||
    required_scopes = ['openid:userinfo']
 | 
			
		||||
    required_scopes = ["openid:userinfo"]
 | 
			
		||||
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
    def get(self, request, *_, **__):
 | 
			
		||||
        """Passbook v1 OpenID API"""
 | 
			
		||||
        payload = {
 | 
			
		||||
            'sub': request.user.uuid.int,
 | 
			
		||||
            'name': request.user.get_full_name(),
 | 
			
		||||
            'given_name': request.user.name,
 | 
			
		||||
            'family_name': '',
 | 
			
		||||
            'preferred_username': request.user.username,
 | 
			
		||||
            'email': request.user.email,
 | 
			
		||||
            "sub": request.user.uuid.int,
 | 
			
		||||
            "name": request.user.get_full_name(),
 | 
			
		||||
            "given_name": request.user.name,
 | 
			
		||||
            "family_name": "",
 | 
			
		||||
            "preferred_username": request.user.username,
 | 
			
		||||
            "email": request.user.email,
 | 
			
		||||
        }
 | 
			
		||||
        return JsonResponse(payload)
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,4 @@ from django.urls import path
 | 
			
		||||
 | 
			
		||||
from passbook.api.v1.openid import OpenIDUserInfoView
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path('openid/', OpenIDUserInfoView.as_view(), name='openid')
 | 
			
		||||
]
 | 
			
		||||
urlpatterns = [path("openid/", OpenIDUserInfoView.as_view(), name="openid")]
 | 
			
		||||
 | 
			
		||||
@ -34,70 +34,73 @@ from passbook.policies.webhook.api import WebhookPolicyViewSet
 | 
			
		||||
from passbook.providers.app_gw.api import ApplicationGatewayProviderViewSet
 | 
			
		||||
from passbook.providers.oauth.api import OAuth2ProviderViewSet
 | 
			
		||||
from passbook.providers.oidc.api import OpenIDProviderViewSet
 | 
			
		||||
from passbook.providers.saml.api import (SAMLPropertyMappingViewSet,
 | 
			
		||||
                                         SAMLProviderViewSet)
 | 
			
		||||
from passbook.sources.ldap.api import (LDAPPropertyMappingViewSet,
 | 
			
		||||
                                       LDAPSourceViewSet)
 | 
			
		||||
from passbook.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProviderViewSet
 | 
			
		||||
from passbook.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
 | 
			
		||||
from passbook.sources.oauth.api import OAuthSourceViewSet
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
router = routers.DefaultRouter()
 | 
			
		||||
 | 
			
		||||
for _passbook_app in get_apps():
 | 
			
		||||
    if hasattr(_passbook_app, 'api_mountpoint'):
 | 
			
		||||
    if hasattr(_passbook_app, "api_mountpoint"):
 | 
			
		||||
        for prefix, viewset in _passbook_app.api_mountpoint:
 | 
			
		||||
            router.register(prefix, viewset)
 | 
			
		||||
        LOGGER.debug("Mounted API URLs", app_name=_passbook_app.name)
 | 
			
		||||
 | 
			
		||||
router.register('core/applications', ApplicationViewSet)
 | 
			
		||||
router.register('core/invitations', InvitationViewSet)
 | 
			
		||||
router.register('core/groups', GroupViewSet)
 | 
			
		||||
router.register('core/users', UserViewSet)
 | 
			
		||||
router.register('audit/events', EventViewSet)
 | 
			
		||||
router.register('sources/all', SourceViewSet)
 | 
			
		||||
router.register('sources/ldap', LDAPSourceViewSet)
 | 
			
		||||
router.register('sources/oauth', OAuthSourceViewSet)
 | 
			
		||||
router.register('policies/all', PolicyViewSet)
 | 
			
		||||
router.register('policies/passwordexpiry', PasswordExpiryPolicyViewSet)
 | 
			
		||||
router.register('policies/groupmembership', GroupMembershipPolicyViewSet)
 | 
			
		||||
router.register('policies/haveibeenpwned', HaveIBeenPwendPolicyViewSet)
 | 
			
		||||
router.register('policies/fieldmatcher', FieldMatcherPolicyViewSet)
 | 
			
		||||
router.register('policies/password', PasswordPolicyViewSet)
 | 
			
		||||
router.register('policies/reputation', ReputationPolicyViewSet)
 | 
			
		||||
router.register('policies/ssologin', SSOLoginPolicyViewSet)
 | 
			
		||||
router.register('policies/webhook', WebhookPolicyViewSet)
 | 
			
		||||
router.register('providers/all', ProviderViewSet)
 | 
			
		||||
router.register('providers/applicationgateway', ApplicationGatewayProviderViewSet)
 | 
			
		||||
router.register('providers/oauth', OAuth2ProviderViewSet)
 | 
			
		||||
router.register('providers/openid', OpenIDProviderViewSet)
 | 
			
		||||
router.register('providers/saml', SAMLProviderViewSet)
 | 
			
		||||
router.register('propertymappings/all', PropertyMappingViewSet)
 | 
			
		||||
router.register('propertymappings/ldap', LDAPPropertyMappingViewSet)
 | 
			
		||||
router.register('propertymappings/saml', SAMLPropertyMappingViewSet)
 | 
			
		||||
router.register('factors/all', FactorViewSet)
 | 
			
		||||
router.register('factors/captcha', CaptchaFactorViewSet)
 | 
			
		||||
router.register('factors/dummy', DummyFactorViewSet)
 | 
			
		||||
router.register('factors/email', EmailFactorViewSet)
 | 
			
		||||
router.register('factors/otp', OTPFactorViewSet)
 | 
			
		||||
router.register('factors/password', PasswordFactorViewSet)
 | 
			
		||||
router.register("core/applications", ApplicationViewSet)
 | 
			
		||||
router.register("core/invitations", InvitationViewSet)
 | 
			
		||||
router.register("core/groups", GroupViewSet)
 | 
			
		||||
router.register("core/users", UserViewSet)
 | 
			
		||||
router.register("audit/events", EventViewSet)
 | 
			
		||||
router.register("sources/all", SourceViewSet)
 | 
			
		||||
router.register("sources/ldap", LDAPSourceViewSet)
 | 
			
		||||
router.register("sources/oauth", OAuthSourceViewSet)
 | 
			
		||||
router.register("policies/all", PolicyViewSet)
 | 
			
		||||
router.register("policies/passwordexpiry", PasswordExpiryPolicyViewSet)
 | 
			
		||||
router.register("policies/groupmembership", GroupMembershipPolicyViewSet)
 | 
			
		||||
router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet)
 | 
			
		||||
router.register("policies/fieldmatcher", FieldMatcherPolicyViewSet)
 | 
			
		||||
router.register("policies/password", PasswordPolicyViewSet)
 | 
			
		||||
router.register("policies/reputation", ReputationPolicyViewSet)
 | 
			
		||||
router.register("policies/ssologin", SSOLoginPolicyViewSet)
 | 
			
		||||
router.register("policies/webhook", WebhookPolicyViewSet)
 | 
			
		||||
router.register("providers/all", ProviderViewSet)
 | 
			
		||||
router.register("providers/applicationgateway", ApplicationGatewayProviderViewSet)
 | 
			
		||||
router.register("providers/oauth", OAuth2ProviderViewSet)
 | 
			
		||||
router.register("providers/openid", OpenIDProviderViewSet)
 | 
			
		||||
router.register("providers/saml", SAMLProviderViewSet)
 | 
			
		||||
router.register("propertymappings/all", PropertyMappingViewSet)
 | 
			
		||||
router.register("propertymappings/ldap", LDAPPropertyMappingViewSet)
 | 
			
		||||
router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
 | 
			
		||||
router.register("factors/all", FactorViewSet)
 | 
			
		||||
router.register("factors/captcha", CaptchaFactorViewSet)
 | 
			
		||||
router.register("factors/dummy", DummyFactorViewSet)
 | 
			
		||||
router.register("factors/email", EmailFactorViewSet)
 | 
			
		||||
router.register("factors/otp", OTPFactorViewSet)
 | 
			
		||||
router.register("factors/password", PasswordFactorViewSet)
 | 
			
		||||
 | 
			
		||||
info = openapi.Info(
 | 
			
		||||
    title="passbook API",
 | 
			
		||||
    default_version='v2',
 | 
			
		||||
    default_version="v2",
 | 
			
		||||
    # description="Test description",
 | 
			
		||||
    # terms_of_service="https://www.google.com/policies/terms/",
 | 
			
		||||
    contact=openapi.Contact(email="hello@beryju.org"),
 | 
			
		||||
    license=openapi.License(name="MIT License"),
 | 
			
		||||
)
 | 
			
		||||
SchemaView = get_schema_view(
 | 
			
		||||
    info,
 | 
			
		||||
    public=True,
 | 
			
		||||
    permission_classes=(CustomObjectPermissions,),
 | 
			
		||||
    info, public=True, permission_classes=(CustomObjectPermissions,),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    url(r'^swagger(?P<format>\.json|\.yaml)$',
 | 
			
		||||
        SchemaView.without_ui(cache_timeout=0), name='schema-json'),
 | 
			
		||||
    path('swagger/', SchemaView.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
 | 
			
		||||
    path('redoc/', SchemaView.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
 | 
			
		||||
    url(
 | 
			
		||||
        r"^swagger(?P<format>\.json|\.yaml)$",
 | 
			
		||||
        SchemaView.without_ui(cache_timeout=0),
 | 
			
		||||
        name="schema-json",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "swagger/",
 | 
			
		||||
        SchemaView.with_ui("swagger", cache_timeout=0),
 | 
			
		||||
        name="schema-swagger-ui",
 | 
			
		||||
    ),
 | 
			
		||||
    path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
 | 
			
		||||
] + router.urls
 | 
			
		||||
 | 
			
		||||
@ -2,4 +2,4 @@
 | 
			
		||||
 | 
			
		||||
from passbook.lib.admin import admin_autoregister
 | 
			
		||||
 | 
			
		||||
admin_autoregister('passbook_audit')
 | 
			
		||||
admin_autoregister("passbook_audit")
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,16 @@ class EventSerializer(ModelSerializer):
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = Event
 | 
			
		||||
        fields = ['pk', 'user', 'action', 'date', 'app', 'context', 'request_ip', 'created', ]
 | 
			
		||||
        fields = [
 | 
			
		||||
            "pk",
 | 
			
		||||
            "user",
 | 
			
		||||
            "action",
 | 
			
		||||
            "date",
 | 
			
		||||
            "app",
 | 
			
		||||
            "context",
 | 
			
		||||
            "request_ip",
 | 
			
		||||
            "created",
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventViewSet(ReadOnlyModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -7,10 +7,10 @@ from django.apps import AppConfig
 | 
			
		||||
class PassbookAuditConfig(AppConfig):
 | 
			
		||||
    """passbook audit app"""
 | 
			
		||||
 | 
			
		||||
    name = 'passbook.audit'
 | 
			
		||||
    label = 'passbook_audit'
 | 
			
		||||
    verbose_name = 'passbook Audit'
 | 
			
		||||
    mountpoint = 'audit/'
 | 
			
		||||
    name = "passbook.audit"
 | 
			
		||||
    label = "passbook_audit"
 | 
			
		||||
    verbose_name = "passbook Audit"
 | 
			
		||||
    mountpoint = "audit/"
 | 
			
		||||
 | 
			
		||||
    def ready(self):
 | 
			
		||||
        import_module('passbook.audit.signals')
 | 
			
		||||
        import_module("passbook.audit.signals")
 | 
			
		||||
 | 
			
		||||
@ -18,20 +18,55 @@ class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='AuditEntry',
 | 
			
		||||
            name="AuditEntry",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
 | 
			
		||||
                ('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])),
 | 
			
		||||
                ('date', models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                ('app', models.TextField()),
 | 
			
		||||
                ('context', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
 | 
			
		||||
                ('request_ip', models.GenericIPAddressField()),
 | 
			
		||||
                ('created', models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
 | 
			
		||||
                (
 | 
			
		||||
                    "uuid",
 | 
			
		||||
                    models.UUIDField(
 | 
			
		||||
                        default=uuid.uuid4,
 | 
			
		||||
                        editable=False,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "action",
 | 
			
		||||
                    models.TextField(
 | 
			
		||||
                        choices=[
 | 
			
		||||
                            ("login", "login"),
 | 
			
		||||
                            ("login_failed", "login_failed"),
 | 
			
		||||
                            ("logout", "logout"),
 | 
			
		||||
                            ("authorize_application", "authorize_application"),
 | 
			
		||||
                            ("suspicious_request", "suspicious_request"),
 | 
			
		||||
                            ("sign_up", "sign_up"),
 | 
			
		||||
                            ("password_reset", "password_reset"),
 | 
			
		||||
                            ("invitation_created", "invitation_created"),
 | 
			
		||||
                            ("invitation_used", "invitation_used"),
 | 
			
		||||
                        ]
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("date", models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                ("app", models.TextField()),
 | 
			
		||||
                (
 | 
			
		||||
                    "context",
 | 
			
		||||
                    django.contrib.postgres.fields.jsonb.JSONField(
 | 
			
		||||
                        blank=True, default=dict
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("request_ip", models.GenericIPAddressField()),
 | 
			
		||||
                ("created", models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                (
 | 
			
		||||
                    "user",
 | 
			
		||||
                    models.ForeignKey(
 | 
			
		||||
                        null=True,
 | 
			
		||||
                        on_delete=django.db.models.deletion.SET_NULL,
 | 
			
		||||
                        to=settings.AUTH_USER_MODEL,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'Audit Entry',
 | 
			
		||||
                'verbose_name_plural': 'Audit Entries',
 | 
			
		||||
                "verbose_name": "Audit Entry",
 | 
			
		||||
                "verbose_name_plural": "Audit Entries",
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -8,12 +8,9 @@ class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
			
		||||
        ('passbook_audit', '0001_initial'),
 | 
			
		||||
        ("passbook_audit", "0001_initial"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RenameModel(
 | 
			
		||||
            old_name='AuditEntry',
 | 
			
		||||
            new_name='Event',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RenameModel(old_name="AuditEntry", new_name="Event",),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -8,17 +8,33 @@ import passbook.audit.models
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('passbook_audit', '0002_auto_20191028_0829'),
 | 
			
		||||
        ("passbook_audit", "0002_auto_20191028_0829"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterModelOptions(
 | 
			
		||||
            name='event',
 | 
			
		||||
            options={'verbose_name': 'Audit Event', 'verbose_name_plural': 'Audit Events'},
 | 
			
		||||
            name="event",
 | 
			
		||||
            options={
 | 
			
		||||
                "verbose_name": "Audit Event",
 | 
			
		||||
                "verbose_name_plural": "Audit Events",
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='event',
 | 
			
		||||
            name='action',
 | 
			
		||||
            field=models.TextField(choices=[('LOGIN', 'login'), ('LOGIN_FAILED', 'login_failed'), ('LOGOUT', 'logout'), ('AUTHORIZE_APPLICATION', 'authorize_application'), ('SUSPICIOUS_REQUEST', 'suspicious_request'), ('SIGN_UP', 'sign_up'), ('PASSWORD_RESET', 'password_reset'), ('INVITE_CREATED', 'invitation_created'), ('INVITE_USED', 'invitation_used'), ('CUSTOM', 'custom')]),
 | 
			
		||||
            model_name="event",
 | 
			
		||||
            name="action",
 | 
			
		||||
            field=models.TextField(
 | 
			
		||||
                choices=[
 | 
			
		||||
                    ("LOGIN", "login"),
 | 
			
		||||
                    ("LOGIN_FAILED", "login_failed"),
 | 
			
		||||
                    ("LOGOUT", "logout"),
 | 
			
		||||
                    ("AUTHORIZE_APPLICATION", "authorize_application"),
 | 
			
		||||
                    ("SUSPICIOUS_REQUEST", "suspicious_request"),
 | 
			
		||||
                    ("SIGN_UP", "sign_up"),
 | 
			
		||||
                    ("PASSWORD_RESET", "password_reset"),
 | 
			
		||||
                    ("INVITE_CREATED", "invitation_created"),
 | 
			
		||||
                    ("INVITE_USED", "invitation_used"),
 | 
			
		||||
                    ("CUSTOM", "custom"),
 | 
			
		||||
                ]
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -6,17 +6,14 @@ from django.db import migrations, models
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('passbook_audit', '0003_auto_20191205_1407'),
 | 
			
		||||
        ("passbook_audit", "0003_auto_20191205_1407"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='event',
 | 
			
		||||
            name='request_ip',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(model_name="event", name="request_ip",),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='event',
 | 
			
		||||
            name='client_ip',
 | 
			
		||||
            model_name="event",
 | 
			
		||||
            name="client_ip",
 | 
			
		||||
            field=models.GenericIPAddressField(null=True),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,14 @@
 | 
			
		||||
"""passbook audit models"""
 | 
			
		||||
from enum import Enum
 | 
			
		||||
from uuid import UUID
 | 
			
		||||
from inspect import getmodule, stack
 | 
			
		||||
from typing import Optional
 | 
			
		||||
from typing import Optional, Dict, Any
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.contrib.auth.models import AnonymousUser
 | 
			
		||||
from django.contrib.postgres.fields import JSONField
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from django.contrib.contenttypes.models import ContentType
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
@ -18,19 +20,44 @@ from passbook.lib.utils.http import get_client_ip
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
 | 
			
		||||
    """clean source of all Models that would interfere with the JSONField.
 | 
			
		||||
    Models are replaced with a dictionary of {
 | 
			
		||||
        app: str,
 | 
			
		||||
        name: str,
 | 
			
		||||
        pk: Any
 | 
			
		||||
    }"""
 | 
			
		||||
    for key, value in source.items():
 | 
			
		||||
        if isinstance(value, dict):
 | 
			
		||||
            source[key] = sanitize_dict(value)
 | 
			
		||||
        elif isinstance(value, models.Model):
 | 
			
		||||
            model_content_type = ContentType.objects.get_for_model(value)
 | 
			
		||||
            source[key] = sanitize_dict(
 | 
			
		||||
                {
 | 
			
		||||
                    "app": model_content_type.app_label,
 | 
			
		||||
                    "name": model_content_type.model,
 | 
			
		||||
                    "pk": value.pk,
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        elif isinstance(value, UUID):
 | 
			
		||||
            source[key] = value.hex
 | 
			
		||||
    return source
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventAction(Enum):
 | 
			
		||||
    """All possible actions to save into the audit log"""
 | 
			
		||||
 | 
			
		||||
    LOGIN = 'login'
 | 
			
		||||
    LOGIN_FAILED = 'login_failed'
 | 
			
		||||
    LOGOUT = 'logout'
 | 
			
		||||
    AUTHORIZE_APPLICATION = 'authorize_application'
 | 
			
		||||
    SUSPICIOUS_REQUEST = 'suspicious_request'
 | 
			
		||||
    SIGN_UP = 'sign_up'
 | 
			
		||||
    PASSWORD_RESET = 'password_reset' # noqa # nosec
 | 
			
		||||
    INVITE_CREATED = 'invitation_created'
 | 
			
		||||
    INVITE_USED = 'invitation_used'
 | 
			
		||||
    CUSTOM = 'custom'
 | 
			
		||||
    LOGIN = "login"
 | 
			
		||||
    LOGIN_FAILED = "login_failed"
 | 
			
		||||
    LOGOUT = "logout"
 | 
			
		||||
    AUTHORIZE_APPLICATION = "authorize_application"
 | 
			
		||||
    SUSPICIOUS_REQUEST = "suspicious_request"
 | 
			
		||||
    SIGN_UP = "sign_up"
 | 
			
		||||
    PASSWORD_RESET = "password_reset"  # noqa # nosec
 | 
			
		||||
    INVITE_CREATED = "invitation_created"
 | 
			
		||||
    INVITE_USED = "invitation_used"
 | 
			
		||||
    CUSTOM = "custom"
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def as_choices():
 | 
			
		||||
@ -41,7 +68,9 @@ class EventAction(Enum):
 | 
			
		||||
class Event(UUIDModel):
 | 
			
		||||
    """An individual audit log event"""
 | 
			
		||||
 | 
			
		||||
    user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)
 | 
			
		||||
    user = models.ForeignKey(
 | 
			
		||||
        settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL
 | 
			
		||||
    )
 | 
			
		||||
    action = models.TextField(choices=EventAction.as_choices())
 | 
			
		||||
    date = models.DateTimeField(auto_now_add=True)
 | 
			
		||||
    app = models.TextField()
 | 
			
		||||
@ -56,28 +85,31 @@ class Event(UUIDModel):
 | 
			
		||||
        return request.resolver_match.app_name
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def new(action: EventAction,
 | 
			
		||||
            app: Optional[str] = None,
 | 
			
		||||
            _inspect_offset: int = 1,
 | 
			
		||||
            **kwargs) -> 'Event':
 | 
			
		||||
    def new(
 | 
			
		||||
        action: EventAction,
 | 
			
		||||
        app: Optional[str] = None,
 | 
			
		||||
        _inspect_offset: int = 1,
 | 
			
		||||
        **kwargs,
 | 
			
		||||
    ) -> "Event":
 | 
			
		||||
        """Create new Event instance from arguments. Instance is NOT saved."""
 | 
			
		||||
        if not isinstance(action, EventAction):
 | 
			
		||||
            raise ValueError(f"action must be EventAction instance but was {type(action)}")
 | 
			
		||||
            raise ValueError(
 | 
			
		||||
                f"action must be EventAction instance but was {type(action)}"
 | 
			
		||||
            )
 | 
			
		||||
        if not app:
 | 
			
		||||
            app = getmodule(stack()[_inspect_offset][0]).__name__
 | 
			
		||||
        event = Event(
 | 
			
		||||
            action=action.value,
 | 
			
		||||
            app=app,
 | 
			
		||||
            context=kwargs)
 | 
			
		||||
        LOGGER.debug("Created Audit event", action=action, context=kwargs)
 | 
			
		||||
        cleaned_kwargs = sanitize_dict(kwargs)
 | 
			
		||||
        event = Event(action=action.value, app=app, context=cleaned_kwargs)
 | 
			
		||||
        LOGGER.debug("Created Audit event", action=action, context=cleaned_kwargs)
 | 
			
		||||
        return event
 | 
			
		||||
 | 
			
		||||
    def from_http(self, request: HttpRequest,
 | 
			
		||||
                  user: Optional[settings.AUTH_USER_MODEL] = None) -> 'Event':
 | 
			
		||||
    def from_http(
 | 
			
		||||
        self, request: HttpRequest, user: Optional[settings.AUTH_USER_MODEL] = None
 | 
			
		||||
    ) -> "Event":
 | 
			
		||||
        """Add data from a Django-HttpRequest, allowing the creation of
 | 
			
		||||
        Events independently from requests.
 | 
			
		||||
        `user` arguments optionally overrides user from requests."""
 | 
			
		||||
        if hasattr(request, 'user'):
 | 
			
		||||
        if hasattr(request, "user"):
 | 
			
		||||
            if isinstance(request.user, AnonymousUser):
 | 
			
		||||
                self.user = get_anonymous_user()
 | 
			
		||||
            else:
 | 
			
		||||
@ -85,7 +117,7 @@ class Event(UUIDModel):
 | 
			
		||||
        if user:
 | 
			
		||||
            self.user = user
 | 
			
		||||
        # User 255.255.255.255 as fallback if IP cannot be determined
 | 
			
		||||
        self.client_ip = get_client_ip(request) or '255.255.255.255'
 | 
			
		||||
        self.client_ip = get_client_ip(request) or "255.255.255.255"
 | 
			
		||||
        # If there's no app set, we get it from the requests too
 | 
			
		||||
        if not self.app:
 | 
			
		||||
            self.app = Event._get_app_from_request(request)
 | 
			
		||||
@ -94,10 +126,12 @@ class Event(UUIDModel):
 | 
			
		||||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        if not self._state.adding:
 | 
			
		||||
            raise ValidationError("you may not edit an existing %s" % self._meta.model_name)
 | 
			
		||||
            raise ValidationError(
 | 
			
		||||
                "you may not edit an existing %s" % self._meta.model_name
 | 
			
		||||
            )
 | 
			
		||||
        return super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        verbose_name = _('Audit Event')
 | 
			
		||||
        verbose_name_plural = _('Audit Events')
 | 
			
		||||
        verbose_name = _("Audit Event")
 | 
			
		||||
        verbose_name_plural = _("Audit Events")
 | 
			
		||||
 | 
			
		||||
@ -3,31 +3,43 @@ from django.contrib.auth.signals import user_logged_in, user_logged_out
 | 
			
		||||
from django.dispatch import receiver
 | 
			
		||||
 | 
			
		||||
from passbook.audit.models import Event, EventAction
 | 
			
		||||
from passbook.core.signals import (invitation_created, invitation_used,
 | 
			
		||||
                                   user_signed_up)
 | 
			
		||||
from passbook.core.signals import invitation_created, invitation_used, user_signed_up
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(user_logged_in)
 | 
			
		||||
def on_user_logged_in(sender, request, user, **kwargs):
 | 
			
		||||
# pylint: disable=unused-argument
 | 
			
		||||
def on_user_logged_in(sender, request, user, **_):
 | 
			
		||||
    """Log successful login"""
 | 
			
		||||
    Event.new(EventAction.LOGIN).from_http(request)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(user_logged_out)
 | 
			
		||||
def on_user_logged_out(sender, request, user, **kwargs):
 | 
			
		||||
# pylint: disable=unused-argument
 | 
			
		||||
def on_user_logged_out(sender, request, user, **_):
 | 
			
		||||
    """Log successfully logout"""
 | 
			
		||||
    Event.new(EventAction.LOGOUT).from_http(request)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(user_signed_up)
 | 
			
		||||
def on_user_signed_up(sender, request, user, **kwargs):
 | 
			
		||||
# pylint: disable=unused-argument
 | 
			
		||||
def on_user_signed_up(sender, request, user, **_):
 | 
			
		||||
    """Log successfully signed up"""
 | 
			
		||||
    Event.new(EventAction.SIGN_UP).from_http(request)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(invitation_created)
 | 
			
		||||
def on_invitation_created(sender, request, invitation, **kwargs):
 | 
			
		||||
# pylint: disable=unused-argument
 | 
			
		||||
def on_invitation_created(sender, request, invitation, **_):
 | 
			
		||||
    """Log Invitation creation"""
 | 
			
		||||
    Event.new(EventAction.INVITE_CREATED, invitation_uuid=invitation.uuid.hex).from_http(request)
 | 
			
		||||
    Event.new(
 | 
			
		||||
        EventAction.INVITE_CREATED, invitation_uuid=invitation.uuid.hex
 | 
			
		||||
    ).from_http(request)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(invitation_used)
 | 
			
		||||
def on_invitation_used(sender, request, invitation, **kwargs):
 | 
			
		||||
# pylint: disable=unused-argument
 | 
			
		||||
def on_invitation_used(sender, request, invitation, **_):
 | 
			
		||||
    """Log Invitation usage"""
 | 
			
		||||
    Event.new(EventAction.INVITE_USED, invitation_uuid=invitation.uuid.hex).from_http(request)
 | 
			
		||||
    Event.new(EventAction.INVITE_USED, invitation_uuid=invitation.uuid.hex).from_http(
 | 
			
		||||
        request
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										0
									
								
								passbook/audit/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								passbook/audit/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										33
									
								
								passbook/audit/tests/test_event.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								passbook/audit/tests/test_event.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
"""audit event tests"""
 | 
			
		||||
 | 
			
		||||
from django.contrib.contenttypes.models import ContentType
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from guardian.shortcuts import get_anonymous_user
 | 
			
		||||
 | 
			
		||||
from passbook.audit.models import Event, EventAction
 | 
			
		||||
from passbook.core.models import Policy
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestAuditEvent(TestCase):
 | 
			
		||||
    """Test Audit Event"""
 | 
			
		||||
 | 
			
		||||
    def test_new_with_model(self):
 | 
			
		||||
        """Create a new Event passing a model as kwarg"""
 | 
			
		||||
        event = Event.new(EventAction.CUSTOM, test={"model": get_anonymous_user()})
 | 
			
		||||
        event.save()  # We save to ensure nothing is un-saveable
 | 
			
		||||
        model_content_type = ContentType.objects.get_for_model(get_anonymous_user())
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            event.context.get("test").get("model").get("app"),
 | 
			
		||||
            model_content_type.app_label,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_new_with_uuid_model(self):
 | 
			
		||||
        """Create a new Event passing a model (with UUID PK) as kwarg"""
 | 
			
		||||
        temp_model = Policy.objects.create()
 | 
			
		||||
        event = Event.new(EventAction.CUSTOM, model=temp_model)
 | 
			
		||||
        event.save()  # We save to ensure nothing is un-saveable
 | 
			
		||||
        model_content_type = ContentType.objects.get_for_model(temp_model)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            event.context.get("model").get("app"), model_content_type.app_label
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(event.context.get("model").get("pk"), temp_model.pk.hex)
 | 
			
		||||
@ -2,4 +2,4 @@
 | 
			
		||||
 | 
			
		||||
from passbook.lib.admin import admin_autoregister
 | 
			
		||||
 | 
			
		||||
admin_autoregister('passbook_core')
 | 
			
		||||
admin_autoregister("passbook_core")
 | 
			
		||||
 | 
			
		||||
@ -11,8 +11,16 @@ class ApplicationSerializer(ModelSerializer):
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = Application
 | 
			
		||||
        fields = ['pk', 'name', 'slug', 'launch_url', 'icon_url',
 | 
			
		||||
                  'provider', 'policies', 'skip_authorization']
 | 
			
		||||
        fields = [
 | 
			
		||||
            "pk",
 | 
			
		||||
            "name",
 | 
			
		||||
            "slug",
 | 
			
		||||
            "launch_url",
 | 
			
		||||
            "icon_url",
 | 
			
		||||
            "provider",
 | 
			
		||||
            "policies",
 | 
			
		||||
            "skip_authorization",
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ApplicationViewSet(ModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -8,16 +8,16 @@ from passbook.core.models import Factor
 | 
			
		||||
class FactorSerializer(ModelSerializer):
 | 
			
		||||
    """Factor Serializer"""
 | 
			
		||||
 | 
			
		||||
    __type__ = SerializerMethodField(method_name='get_type')
 | 
			
		||||
    __type__ = SerializerMethodField(method_name="get_type")
 | 
			
		||||
 | 
			
		||||
    def get_type(self, obj):
 | 
			
		||||
        """Get object type so that we know which API Endpoint to use to get the full object"""
 | 
			
		||||
        return obj._meta.object_name.lower().replace('factor', '')
 | 
			
		||||
        return obj._meta.object_name.lower().replace("factor", "")
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = Factor
 | 
			
		||||
        fields = ['pk', 'name', 'slug', 'order', 'enabled', '__type__']
 | 
			
		||||
        fields = ["pk", "name", "slug", "order", "enabled", "__type__"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FactorViewSet(ReadOnlyModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ class GroupSerializer(ModelSerializer):
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = Group
 | 
			
		||||
        fields = ['pk', 'name', 'parent', 'user_set', 'attributes']
 | 
			
		||||
        fields = ["pk", "name", "parent", "user_set", "attributes"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GroupViewSet(ModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,13 @@ class InvitationSerializer(ModelSerializer):
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = Invitation
 | 
			
		||||
        fields = ['pk', 'expires', 'fixed_username', 'fixed_email', 'needs_confirmation']
 | 
			
		||||
        fields = [
 | 
			
		||||
            "pk",
 | 
			
		||||
            "expires",
 | 
			
		||||
            "fixed_username",
 | 
			
		||||
            "fixed_email",
 | 
			
		||||
            "needs_confirmation",
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvitationViewSet(ModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -9,16 +9,16 @@ from passbook.policies.forms import GENERAL_FIELDS
 | 
			
		||||
class PolicySerializer(ModelSerializer):
 | 
			
		||||
    """Policy Serializer"""
 | 
			
		||||
 | 
			
		||||
    __type__ = SerializerMethodField(method_name='get_type')
 | 
			
		||||
    __type__ = SerializerMethodField(method_name="get_type")
 | 
			
		||||
 | 
			
		||||
    def get_type(self, obj):
 | 
			
		||||
        """Get object type so that we know which API Endpoint to use to get the full object"""
 | 
			
		||||
        return obj._meta.object_name.lower().replace('policy', '')
 | 
			
		||||
        return obj._meta.object_name.lower().replace("policy", "")
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = Policy
 | 
			
		||||
        fields = ['pk'] + GENERAL_FIELDS + ['__type__']
 | 
			
		||||
        fields = ["pk"] + GENERAL_FIELDS + ["__type__"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PolicyViewSet(ReadOnlyModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -8,16 +8,16 @@ from passbook.core.models import PropertyMapping
 | 
			
		||||
class PropertyMappingSerializer(ModelSerializer):
 | 
			
		||||
    """PropertyMapping Serializer"""
 | 
			
		||||
 | 
			
		||||
    __type__ = SerializerMethodField(method_name='get_type')
 | 
			
		||||
    __type__ = SerializerMethodField(method_name="get_type")
 | 
			
		||||
 | 
			
		||||
    def get_type(self, obj):
 | 
			
		||||
        """Get object type so that we know which API Endpoint to use to get the full object"""
 | 
			
		||||
        return obj._meta.object_name.lower().replace('propertymapping', '')
 | 
			
		||||
        return obj._meta.object_name.lower().replace("propertymapping", "")
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = PropertyMapping
 | 
			
		||||
        fields = ['pk', 'name', '__type__']
 | 
			
		||||
        fields = ["pk", "name", "__type__"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PropertyMappingViewSet(ReadOnlyModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -8,16 +8,16 @@ from passbook.core.models import Provider
 | 
			
		||||
class ProviderSerializer(ModelSerializer):
 | 
			
		||||
    """Provider Serializer"""
 | 
			
		||||
 | 
			
		||||
    __type__ = SerializerMethodField(method_name='get_type')
 | 
			
		||||
    __type__ = SerializerMethodField(method_name="get_type")
 | 
			
		||||
 | 
			
		||||
    def get_type(self, obj):
 | 
			
		||||
        """Get object type so that we know which API Endpoint to use to get the full object"""
 | 
			
		||||
        return obj._meta.object_name.lower().replace('provider', '')
 | 
			
		||||
        return obj._meta.object_name.lower().replace("provider", "")
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = Provider
 | 
			
		||||
        fields = ['pk', 'property_mappings', '__type__']
 | 
			
		||||
        fields = ["pk", "property_mappings", "__type__"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProviderViewSet(ReadOnlyModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -9,16 +9,16 @@ from passbook.core.models import Source
 | 
			
		||||
class SourceSerializer(ModelSerializer):
 | 
			
		||||
    """Source Serializer"""
 | 
			
		||||
 | 
			
		||||
    __type__ = SerializerMethodField(method_name='get_type')
 | 
			
		||||
    __type__ = SerializerMethodField(method_name="get_type")
 | 
			
		||||
 | 
			
		||||
    def get_type(self, obj):
 | 
			
		||||
        """Get object type so that we know which API Endpoint to use to get the full object"""
 | 
			
		||||
        return obj._meta.object_name.lower().replace('source', '')
 | 
			
		||||
        return obj._meta.object_name.lower().replace("source", "")
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = Source
 | 
			
		||||
        fields = SOURCE_SERIALIZER_FIELDS + ['__type__']
 | 
			
		||||
        fields = SOURCE_SERIALIZER_FIELDS + ["__type__"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SourceViewSet(ReadOnlyModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ class UserSerializer(ModelSerializer):
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = User
 | 
			
		||||
        fields = ['pk', 'username', 'name', 'email']
 | 
			
		||||
        fields = ["pk", "username", "name", "email"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserViewSet(ModelViewSet):
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ from django.apps import AppConfig
 | 
			
		||||
class PassbookCoreConfig(AppConfig):
 | 
			
		||||
    """passbook core app config"""
 | 
			
		||||
 | 
			
		||||
    name = 'passbook.core'
 | 
			
		||||
    label = 'passbook_core'
 | 
			
		||||
    verbose_name = 'passbook Core'
 | 
			
		||||
    mountpoint = ''
 | 
			
		||||
    name = "passbook.core"
 | 
			
		||||
    label = "passbook_core"
 | 
			
		||||
    verbose_name = "passbook Core"
 | 
			
		||||
    mountpoint = ""
 | 
			
		||||
 | 
			
		||||
@ -9,21 +9,29 @@ from passbook.core.models import Application, Provider
 | 
			
		||||
class ApplicationForm(forms.ModelForm):
 | 
			
		||||
    """Application Form"""
 | 
			
		||||
 | 
			
		||||
    provider = forms.ModelChoiceField(queryset=Provider.objects.all().select_subclasses(),
 | 
			
		||||
                                      required=False)
 | 
			
		||||
    provider = forms.ModelChoiceField(
 | 
			
		||||
        queryset=Provider.objects.all().select_subclasses(), required=False
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = Application
 | 
			
		||||
        fields = ['name', 'slug', 'launch_url', 'icon_url',
 | 
			
		||||
                  'provider', 'policies', 'skip_authorization']
 | 
			
		||||
        fields = [
 | 
			
		||||
            "name",
 | 
			
		||||
            "slug",
 | 
			
		||||
            "launch_url",
 | 
			
		||||
            "icon_url",
 | 
			
		||||
            "provider",
 | 
			
		||||
            "policies",
 | 
			
		||||
            "skip_authorization",
 | 
			
		||||
        ]
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'name': forms.TextInput(),
 | 
			
		||||
            'launch_url': forms.TextInput(),
 | 
			
		||||
            'icon_url': forms.TextInput(),
 | 
			
		||||
            'policies': FilteredSelectMultiple(_('policies'), False)
 | 
			
		||||
            "name": forms.TextInput(),
 | 
			
		||||
            "launch_url": forms.TextInput(),
 | 
			
		||||
            "icon_url": forms.TextInput(),
 | 
			
		||||
            "policies": FilteredSelectMultiple(_("policies"), False),
 | 
			
		||||
        }
 | 
			
		||||
        labels = {
 | 
			
		||||
            'launch_url': _('Launch URL'),
 | 
			
		||||
            'icon_url': _('Icon URL'),
 | 
			
		||||
            "launch_url": _("Launch URL"),
 | 
			
		||||
            "icon_url": _("Icon URL"),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -11,55 +11,64 @@ from passbook.lib.utils.ui import human_list
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LoginForm(forms.Form):
 | 
			
		||||
    """Allow users to login"""
 | 
			
		||||
 | 
			
		||||
    title = _('Log in to your account')
 | 
			
		||||
    title = _("Log in to your account")
 | 
			
		||||
    uid_field = forms.CharField()
 | 
			
		||||
    remember_me = forms.BooleanField(required=False)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        if CONFIG.y('passbook.uid_fields') == ['e-mail']:
 | 
			
		||||
            self.fields['uid_field'] = forms.EmailField()
 | 
			
		||||
        self.fields['uid_field'].widget.attrs = {
 | 
			
		||||
            'placeholder': _(human_list([x.title() for x in CONFIG.y('passbook.uid_fields')]))
 | 
			
		||||
        if CONFIG.y("passbook.uid_fields") == ["e-mail"]:
 | 
			
		||||
            self.fields["uid_field"] = forms.EmailField()
 | 
			
		||||
        self.fields["uid_field"].widget.attrs = {
 | 
			
		||||
            "placeholder": _(
 | 
			
		||||
                human_list([x.title() for x in CONFIG.y("passbook.uid_fields")])
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def clean_uid_field(self):
 | 
			
		||||
        """Validate uid_field after EmailValidator if 'email' is the only selected uid_fields"""
 | 
			
		||||
        if CONFIG.y('passbook.uid_fields') == ['email']:
 | 
			
		||||
            validate_email(self.cleaned_data.get('uid_field'))
 | 
			
		||||
        return self.cleaned_data.get('uid_field')
 | 
			
		||||
        if CONFIG.y("passbook.uid_fields") == ["email"]:
 | 
			
		||||
            validate_email(self.cleaned_data.get("uid_field"))
 | 
			
		||||
        return self.cleaned_data.get("uid_field")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SignUpForm(forms.Form):
 | 
			
		||||
    """SignUp Form"""
 | 
			
		||||
 | 
			
		||||
    title = _('Sign Up')
 | 
			
		||||
    name = forms.CharField(label=_('Name'),
 | 
			
		||||
                           widget=forms.TextInput(attrs={'placeholder': _('Name')}))
 | 
			
		||||
    username = forms.CharField(label=_('Username'),
 | 
			
		||||
                               widget=forms.TextInput(attrs={'placeholder': _('Username')}))
 | 
			
		||||
    email = forms.EmailField(label=_('E-Mail'),
 | 
			
		||||
                             widget=forms.TextInput(attrs={'placeholder': _('E-Mail')}))
 | 
			
		||||
    password = forms.CharField(label=_('Password'),
 | 
			
		||||
                               widget=forms.PasswordInput(attrs={'placeholder': _('Password')}))
 | 
			
		||||
    password_repeat = forms.CharField(label=_('Repeat Password'),
 | 
			
		||||
                                      widget=forms.PasswordInput(attrs={
 | 
			
		||||
                                          'placeholder': _('Repeat Password')
 | 
			
		||||
                                          }))
 | 
			
		||||
    title = _("Sign Up")
 | 
			
		||||
    name = forms.CharField(
 | 
			
		||||
        label=_("Name"), widget=forms.TextInput(attrs={"placeholder": _("Name")})
 | 
			
		||||
    )
 | 
			
		||||
    username = forms.CharField(
 | 
			
		||||
        label=_("Username"),
 | 
			
		||||
        widget=forms.TextInput(attrs={"placeholder": _("Username")}),
 | 
			
		||||
    )
 | 
			
		||||
    email = forms.EmailField(
 | 
			
		||||
        label=_("E-Mail"), widget=forms.TextInput(attrs={"placeholder": _("E-Mail")})
 | 
			
		||||
    )
 | 
			
		||||
    password = forms.CharField(
 | 
			
		||||
        label=_("Password"),
 | 
			
		||||
        widget=forms.PasswordInput(attrs={"placeholder": _("Password")}),
 | 
			
		||||
    )
 | 
			
		||||
    password_repeat = forms.CharField(
 | 
			
		||||
        label=_("Repeat Password"),
 | 
			
		||||
        widget=forms.PasswordInput(attrs={"placeholder": _("Repeat Password")}),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        # All fields which have initial data supplied are set to read only
 | 
			
		||||
        if 'initial' in kwargs:
 | 
			
		||||
            for field in kwargs.get('initial').keys():
 | 
			
		||||
                self.fields[field].widget.attrs['readonly'] = 'readonly'
 | 
			
		||||
        if "initial" in kwargs:
 | 
			
		||||
            for field in kwargs.get("initial").keys():
 | 
			
		||||
                self.fields[field].widget.attrs["readonly"] = "readonly"
 | 
			
		||||
 | 
			
		||||
    def clean_username(self):
 | 
			
		||||
        """Check if username is used already"""
 | 
			
		||||
        username = self.cleaned_data.get('username')
 | 
			
		||||
        username = self.cleaned_data.get("username")
 | 
			
		||||
        if User.objects.filter(username=username).exists():
 | 
			
		||||
            LOGGER.warning("Username %s already exists", username)
 | 
			
		||||
            raise ValidationError(_("Username already exists"))
 | 
			
		||||
@ -67,7 +76,7 @@ class SignUpForm(forms.Form):
 | 
			
		||||
 | 
			
		||||
    def clean_email(self):
 | 
			
		||||
        """Check if email is already used in django or other auth sources"""
 | 
			
		||||
        email = self.cleaned_data.get('email')
 | 
			
		||||
        email = self.cleaned_data.get("email")
 | 
			
		||||
        # Check if user exists already, error early
 | 
			
		||||
        if User.objects.filter(email=email).exists():
 | 
			
		||||
            LOGGER.debug("email %s exists in django", email)
 | 
			
		||||
@ -76,8 +85,8 @@ class SignUpForm(forms.Form):
 | 
			
		||||
 | 
			
		||||
    def clean_password_repeat(self):
 | 
			
		||||
        """Check if Password adheres to filter and if passwords matche"""
 | 
			
		||||
        password = self.cleaned_data.get('password')
 | 
			
		||||
        password_repeat = self.cleaned_data.get('password_repeat')
 | 
			
		||||
        password = self.cleaned_data.get("password")
 | 
			
		||||
        password_repeat = self.cleaned_data.get("password_repeat")
 | 
			
		||||
        if password != password_repeat:
 | 
			
		||||
            raise ValidationError(_("Passwords don't match"))
 | 
			
		||||
        return self.cleaned_data.get('password_repeat')
 | 
			
		||||
        return self.cleaned_data.get("password_repeat")
 | 
			
		||||
 | 
			
		||||
@ -9,24 +9,29 @@ class GroupForm(forms.ModelForm):
 | 
			
		||||
    """Group Form"""
 | 
			
		||||
 | 
			
		||||
    members = forms.ModelMultipleChoiceField(
 | 
			
		||||
        User.objects.all(), required=False, widget=FilteredSelectMultiple('users', False))
 | 
			
		||||
        User.objects.all(),
 | 
			
		||||
        required=False,
 | 
			
		||||
        widget=FilteredSelectMultiple("users", False),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        if self.instance.pk:
 | 
			
		||||
            self.initial['members'] = self.instance.user_set.values_list('pk', flat=True)
 | 
			
		||||
            self.initial["members"] = self.instance.user_set.values_list(
 | 
			
		||||
                "pk", flat=True
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        instance = super().save(*args, **kwargs)
 | 
			
		||||
        if instance.pk:
 | 
			
		||||
            instance.user_set.clear()
 | 
			
		||||
            instance.user_set.add(*self.cleaned_data['members'])
 | 
			
		||||
            instance.user_set.add(*self.cleaned_data["members"])
 | 
			
		||||
        return instance
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = Group
 | 
			
		||||
        fields = ['name', 'parent', 'members', 'attributes']
 | 
			
		||||
        fields = ["name", "parent", "members", "attributes"]
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'name': forms.TextInput(),
 | 
			
		||||
            "name": forms.TextInput(),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -12,27 +12,27 @@ class InvitationForm(forms.ModelForm):
 | 
			
		||||
 | 
			
		||||
    def clean_fixed_username(self):
 | 
			
		||||
        """Check if username is already used"""
 | 
			
		||||
        username = self.cleaned_data.get('fixed_username')
 | 
			
		||||
        username = self.cleaned_data.get("fixed_username")
 | 
			
		||||
        if User.objects.filter(username=username).exists():
 | 
			
		||||
            raise ValidationError(_('Username is already in use.'))
 | 
			
		||||
            raise ValidationError(_("Username is already in use."))
 | 
			
		||||
        return username
 | 
			
		||||
 | 
			
		||||
    def clean_fixed_email(self):
 | 
			
		||||
        """Check if email is already used"""
 | 
			
		||||
        email = self.cleaned_data.get('fixed_email')
 | 
			
		||||
        email = self.cleaned_data.get("fixed_email")
 | 
			
		||||
        if User.objects.filter(email=email).exists():
 | 
			
		||||
            raise ValidationError(_('E-Mail is already in use.'))
 | 
			
		||||
            raise ValidationError(_("E-Mail is already in use."))
 | 
			
		||||
        return email
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = Invitation
 | 
			
		||||
        fields = ['expires', 'fixed_username', 'fixed_email', 'needs_confirmation']
 | 
			
		||||
        fields = ["expires", "fixed_username", "fixed_email", "needs_confirmation"]
 | 
			
		||||
        labels = {
 | 
			
		||||
            'fixed_username': "Force user's username (optional)",
 | 
			
		||||
            'fixed_email': "Force user's email (optional)",
 | 
			
		||||
            "fixed_username": "Force user's username (optional)",
 | 
			
		||||
            "fixed_email": "Force user's email (optional)",
 | 
			
		||||
        }
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'fixed_username': forms.TextInput(),
 | 
			
		||||
            'fixed_email': forms.TextInput(),
 | 
			
		||||
            "fixed_username": forms.TextInput(),
 | 
			
		||||
            "fixed_email": forms.TextInput(),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -13,10 +13,8 @@ class DebugPolicyForm(forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = DebugPolicy
 | 
			
		||||
        fields = GENERAL_FIELDS + ['result', 'wait_min', 'wait_max']
 | 
			
		||||
        fields = GENERAL_FIELDS + ["result", "wait_min", "wait_max"]
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'name': forms.TextInput(),
 | 
			
		||||
        }
 | 
			
		||||
        labels = {
 | 
			
		||||
            'result': _('Allow user')
 | 
			
		||||
            "name": forms.TextInput(),
 | 
			
		||||
        }
 | 
			
		||||
        labels = {"result": _("Allow user")}
 | 
			
		||||
 | 
			
		||||
@ -13,29 +13,30 @@ class UserDetailForm(forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        model = User
 | 
			
		||||
        fields = ['username', 'name', 'email']
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'name': forms.TextInput
 | 
			
		||||
        }
 | 
			
		||||
        fields = ["username", "name", "email"]
 | 
			
		||||
        widgets = {"name": forms.TextInput}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PasswordChangeForm(forms.Form):
 | 
			
		||||
    """Form to update password"""
 | 
			
		||||
 | 
			
		||||
    password = forms.CharField(label=_('Password'),
 | 
			
		||||
                               widget=forms.PasswordInput(attrs={
 | 
			
		||||
                                   'placeholder': _('New Password'),
 | 
			
		||||
                                   'autocomplete': 'new-password'
 | 
			
		||||
                                   }))
 | 
			
		||||
    password_repeat = forms.CharField(label=_('Repeat Password'),
 | 
			
		||||
                                      widget=forms.PasswordInput(attrs={
 | 
			
		||||
                                          'placeholder': _('Repeat Password'),
 | 
			
		||||
                                          'autocomplete': 'new-password'
 | 
			
		||||
                                      }))
 | 
			
		||||
    password = forms.CharField(
 | 
			
		||||
        label=_("Password"),
 | 
			
		||||
        widget=forms.PasswordInput(
 | 
			
		||||
            attrs={"placeholder": _("New Password"), "autocomplete": "new-password"}
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
    password_repeat = forms.CharField(
 | 
			
		||||
        label=_("Repeat Password"),
 | 
			
		||||
        widget=forms.PasswordInput(
 | 
			
		||||
            attrs={"placeholder": _("Repeat Password"), "autocomplete": "new-password"}
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def clean_password_repeat(self):
 | 
			
		||||
        """Check if Password adheres to filter and if passwords matche"""
 | 
			
		||||
        password = self.cleaned_data.get('password')
 | 
			
		||||
        password_repeat = self.cleaned_data.get('password_repeat')
 | 
			
		||||
        password = self.cleaned_data.get("password")
 | 
			
		||||
        password_repeat = self.cleaned_data.get("password_repeat")
 | 
			
		||||
        if password != password_repeat:
 | 
			
		||||
            raise ValidationError(_("Passwords don't match"))
 | 
			
		||||
        return self.cleaned_data.get('password_repeat')
 | 
			
		||||
        return self.cleaned_data.get("password_repeat")
 | 
			
		||||
 | 
			
		||||
@ -18,206 +18,433 @@ class Migration(migrations.Migration):
 | 
			
		||||
    initial = True
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('auth', '0011_update_proxy_permissions'),
 | 
			
		||||
        ("auth", "0011_update_proxy_permissions"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='User',
 | 
			
		||||
            name="User",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('password', models.CharField(max_length=128, verbose_name='password')),
 | 
			
		||||
                ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
 | 
			
		||||
                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
 | 
			
		||||
                ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
 | 
			
		||||
                ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
 | 
			
		||||
                ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
 | 
			
		||||
                ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
 | 
			
		||||
                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
 | 
			
		||||
                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
 | 
			
		||||
                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
 | 
			
		||||
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
 | 
			
		||||
                ('name', models.TextField()),
 | 
			
		||||
                ('password_change_date', models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                (
 | 
			
		||||
                    "id",
 | 
			
		||||
                    models.AutoField(
 | 
			
		||||
                        auto_created=True,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                        verbose_name="ID",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("password", models.CharField(max_length=128, verbose_name="password")),
 | 
			
		||||
                (
 | 
			
		||||
                    "last_login",
 | 
			
		||||
                    models.DateTimeField(
 | 
			
		||||
                        blank=True, null=True, verbose_name="last login"
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "is_superuser",
 | 
			
		||||
                    models.BooleanField(
 | 
			
		||||
                        default=False,
 | 
			
		||||
                        help_text="Designates that this user has all permissions without explicitly assigning them.",
 | 
			
		||||
                        verbose_name="superuser status",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "username",
 | 
			
		||||
                    models.CharField(
 | 
			
		||||
                        error_messages={
 | 
			
		||||
                            "unique": "A user with that username already exists."
 | 
			
		||||
                        },
 | 
			
		||||
                        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
 | 
			
		||||
                        max_length=150,
 | 
			
		||||
                        unique=True,
 | 
			
		||||
                        validators=[
 | 
			
		||||
                            django.contrib.auth.validators.UnicodeUsernameValidator()
 | 
			
		||||
                        ],
 | 
			
		||||
                        verbose_name="username",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "first_name",
 | 
			
		||||
                    models.CharField(
 | 
			
		||||
                        blank=True, max_length=30, verbose_name="first name"
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "last_name",
 | 
			
		||||
                    models.CharField(
 | 
			
		||||
                        blank=True, max_length=150, verbose_name="last name"
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "email",
 | 
			
		||||
                    models.EmailField(
 | 
			
		||||
                        blank=True, max_length=254, verbose_name="email address"
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "is_staff",
 | 
			
		||||
                    models.BooleanField(
 | 
			
		||||
                        default=False,
 | 
			
		||||
                        help_text="Designates whether the user can log into this admin site.",
 | 
			
		||||
                        verbose_name="staff status",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "is_active",
 | 
			
		||||
                    models.BooleanField(
 | 
			
		||||
                        default=True,
 | 
			
		||||
                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
 | 
			
		||||
                        verbose_name="active",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "date_joined",
 | 
			
		||||
                    models.DateTimeField(
 | 
			
		||||
                        default=django.utils.timezone.now, verbose_name="date joined"
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("uuid", models.UUIDField(default=uuid.uuid4, editable=False)),
 | 
			
		||||
                ("name", models.TextField()),
 | 
			
		||||
                ("password_change_date", models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'user',
 | 
			
		||||
                'verbose_name_plural': 'users',
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
                "verbose_name": "user",
 | 
			
		||||
                "verbose_name_plural": "users",
 | 
			
		||||
                "abstract": False,
 | 
			
		||||
            },
 | 
			
		||||
            managers=[
 | 
			
		||||
                ('objects', django.contrib.auth.models.UserManager()),
 | 
			
		||||
            ],
 | 
			
		||||
            managers=[("objects", django.contrib.auth.models.UserManager()),],
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Policy',
 | 
			
		||||
            name="Policy",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('created', models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                ('last_updated', models.DateTimeField(auto_now=True)),
 | 
			
		||||
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
 | 
			
		||||
                ('name', models.TextField(blank=True, null=True)),
 | 
			
		||||
                ('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)),
 | 
			
		||||
                ('negate', models.BooleanField(default=False)),
 | 
			
		||||
                ('order', models.IntegerField(default=0)),
 | 
			
		||||
                ('timeout', models.IntegerField(default=30)),
 | 
			
		||||
                ("created", models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                ("last_updated", models.DateTimeField(auto_now=True)),
 | 
			
		||||
                (
 | 
			
		||||
                    "uuid",
 | 
			
		||||
                    models.UUIDField(
 | 
			
		||||
                        default=uuid.uuid4,
 | 
			
		||||
                        editable=False,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("name", models.TextField(blank=True, null=True)),
 | 
			
		||||
                (
 | 
			
		||||
                    "action",
 | 
			
		||||
                    models.CharField(
 | 
			
		||||
                        choices=[("allow", "allow"), ("deny", "deny")], max_length=20
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("negate", models.BooleanField(default=False)),
 | 
			
		||||
                ("order", models.IntegerField(default=0)),
 | 
			
		||||
                ("timeout", models.IntegerField(default=30)),
 | 
			
		||||
            ],
 | 
			
		||||
            options={"abstract": False,},
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name="PolicyModel",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ("created", models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                ("last_updated", models.DateTimeField(auto_now=True)),
 | 
			
		||||
                (
 | 
			
		||||
                    "uuid",
 | 
			
		||||
                    models.UUIDField(
 | 
			
		||||
                        default=uuid.uuid4,
 | 
			
		||||
                        editable=False,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "policies",
 | 
			
		||||
                    models.ManyToManyField(blank=True, to="passbook_core.Policy"),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
            options={"abstract": False,},
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name="PropertyMapping",
 | 
			
		||||
            fields=[
 | 
			
		||||
                (
 | 
			
		||||
                    "uuid",
 | 
			
		||||
                    models.UUIDField(
 | 
			
		||||
                        default=uuid.uuid4,
 | 
			
		||||
                        editable=False,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("name", models.TextField()),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
                "verbose_name": "Property Mapping",
 | 
			
		||||
                "verbose_name_plural": "Property Mappings",
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='PolicyModel',
 | 
			
		||||
            name="DebugPolicy",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('created', models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                ('last_updated', models.DateTimeField(auto_now=True)),
 | 
			
		||||
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
 | 
			
		||||
                ('policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')),
 | 
			
		||||
                (
 | 
			
		||||
                    "policy_ptr",
 | 
			
		||||
                    models.OneToOneField(
 | 
			
		||||
                        auto_created=True,
 | 
			
		||||
                        on_delete=django.db.models.deletion.CASCADE,
 | 
			
		||||
                        parent_link=True,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                        to="passbook_core.Policy",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("result", models.BooleanField(default=False)),
 | 
			
		||||
                ("wait_min", models.IntegerField(default=5)),
 | 
			
		||||
                ("wait_max", models.IntegerField(default=30)),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
                "verbose_name": "Debug Policy",
 | 
			
		||||
                "verbose_name_plural": "Debug Policies",
 | 
			
		||||
            },
 | 
			
		||||
            bases=("passbook_core.policy",),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='PropertyMapping',
 | 
			
		||||
            name="Factor",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
 | 
			
		||||
                ('name', models.TextField()),
 | 
			
		||||
                (
 | 
			
		||||
                    "policymodel_ptr",
 | 
			
		||||
                    models.OneToOneField(
 | 
			
		||||
                        auto_created=True,
 | 
			
		||||
                        on_delete=django.db.models.deletion.CASCADE,
 | 
			
		||||
                        parent_link=True,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                        to="passbook_core.PolicyModel",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("name", models.TextField()),
 | 
			
		||||
                ("slug", models.SlugField(unique=True)),
 | 
			
		||||
                ("order", models.IntegerField()),
 | 
			
		||||
                ("enabled", models.BooleanField(default=True)),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'Property Mapping',
 | 
			
		||||
                'verbose_name_plural': 'Property Mappings',
 | 
			
		||||
            },
 | 
			
		||||
            options={"abstract": False,},
 | 
			
		||||
            bases=("passbook_core.policymodel",),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='DebugPolicy',
 | 
			
		||||
            name="Source",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')),
 | 
			
		||||
                ('result', models.BooleanField(default=False)),
 | 
			
		||||
                ('wait_min', models.IntegerField(default=5)),
 | 
			
		||||
                ('wait_max', models.IntegerField(default=30)),
 | 
			
		||||
                (
 | 
			
		||||
                    "policymodel_ptr",
 | 
			
		||||
                    models.OneToOneField(
 | 
			
		||||
                        auto_created=True,
 | 
			
		||||
                        on_delete=django.db.models.deletion.CASCADE,
 | 
			
		||||
                        parent_link=True,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                        to="passbook_core.PolicyModel",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("name", models.TextField()),
 | 
			
		||||
                ("slug", models.SlugField()),
 | 
			
		||||
                ("enabled", models.BooleanField(default=True)),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'Debug Policy',
 | 
			
		||||
                'verbose_name_plural': 'Debug Policies',
 | 
			
		||||
            },
 | 
			
		||||
            bases=('passbook_core.policy',),
 | 
			
		||||
            options={"abstract": False,},
 | 
			
		||||
            bases=("passbook_core.policymodel",),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Factor',
 | 
			
		||||
            name="Provider",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
 | 
			
		||||
                ('name', models.TextField()),
 | 
			
		||||
                ('slug', models.SlugField(unique=True)),
 | 
			
		||||
                ('order', models.IntegerField()),
 | 
			
		||||
                ('enabled', models.BooleanField(default=True)),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
            },
 | 
			
		||||
            bases=('passbook_core.policymodel',),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Source',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
 | 
			
		||||
                ('name', models.TextField()),
 | 
			
		||||
                ('slug', models.SlugField()),
 | 
			
		||||
                ('enabled', models.BooleanField(default=True)),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
            },
 | 
			
		||||
            bases=('passbook_core.policymodel',),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Provider',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('property_mappings', models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping')),
 | 
			
		||||
                (
 | 
			
		||||
                    "id",
 | 
			
		||||
                    models.AutoField(
 | 
			
		||||
                        auto_created=True,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                        verbose_name="ID",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "property_mappings",
 | 
			
		||||
                    models.ManyToManyField(
 | 
			
		||||
                        blank=True, default=None, to="passbook_core.PropertyMapping"
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Nonce',
 | 
			
		||||
            name="Nonce",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
 | 
			
		||||
                ('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)),
 | 
			
		||||
                ('expiring', models.BooleanField(default=True)),
 | 
			
		||||
                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
 | 
			
		||||
                (
 | 
			
		||||
                    "uuid",
 | 
			
		||||
                    models.UUIDField(
 | 
			
		||||
                        default=uuid.uuid4,
 | 
			
		||||
                        editable=False,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "expires",
 | 
			
		||||
                    models.DateTimeField(
 | 
			
		||||
                        default=passbook.core.models.default_nonce_duration
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("expiring", models.BooleanField(default=True)),
 | 
			
		||||
                (
 | 
			
		||||
                    "user",
 | 
			
		||||
                    models.ForeignKey(
 | 
			
		||||
                        on_delete=django.db.models.deletion.CASCADE,
 | 
			
		||||
                        to=settings.AUTH_USER_MODEL,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
            options={"verbose_name": "Nonce", "verbose_name_plural": "Nonces",},
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name="Invitation",
 | 
			
		||||
            fields=[
 | 
			
		||||
                (
 | 
			
		||||
                    "uuid",
 | 
			
		||||
                    models.UUIDField(
 | 
			
		||||
                        default=uuid.uuid4,
 | 
			
		||||
                        editable=False,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("expires", models.DateTimeField(blank=True, default=None, null=True)),
 | 
			
		||||
                ("fixed_username", models.TextField(blank=True, default=None)),
 | 
			
		||||
                ("fixed_email", models.TextField(blank=True, default=None)),
 | 
			
		||||
                ("needs_confirmation", models.BooleanField(default=True)),
 | 
			
		||||
                (
 | 
			
		||||
                    "created_by",
 | 
			
		||||
                    models.ForeignKey(
 | 
			
		||||
                        on_delete=django.db.models.deletion.CASCADE,
 | 
			
		||||
                        to=settings.AUTH_USER_MODEL,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'Nonce',
 | 
			
		||||
                'verbose_name_plural': 'Nonces',
 | 
			
		||||
                "verbose_name": "Invitation",
 | 
			
		||||
                "verbose_name_plural": "Invitations",
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Invitation',
 | 
			
		||||
            name="Group",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
 | 
			
		||||
                ('expires', models.DateTimeField(blank=True, default=None, null=True)),
 | 
			
		||||
                ('fixed_username', models.TextField(blank=True, default=None)),
 | 
			
		||||
                ('fixed_email', models.TextField(blank=True, default=None)),
 | 
			
		||||
                ('needs_confirmation', models.BooleanField(default=True)),
 | 
			
		||||
                ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
 | 
			
		||||
                (
 | 
			
		||||
                    "uuid",
 | 
			
		||||
                    models.UUIDField(
 | 
			
		||||
                        default=uuid.uuid4,
 | 
			
		||||
                        editable=False,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("name", models.CharField(max_length=80, verbose_name="name")),
 | 
			
		||||
                (
 | 
			
		||||
                    "tags",
 | 
			
		||||
                    django.contrib.postgres.fields.jsonb.JSONField(
 | 
			
		||||
                        blank=True, default=dict
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "parent",
 | 
			
		||||
                    models.ForeignKey(
 | 
			
		||||
                        blank=True,
 | 
			
		||||
                        null=True,
 | 
			
		||||
                        on_delete=django.db.models.deletion.SET_NULL,
 | 
			
		||||
                        related_name="children",
 | 
			
		||||
                        to="passbook_core.Group",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'Invitation',
 | 
			
		||||
                'verbose_name_plural': 'Invitations',
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Group',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
 | 
			
		||||
                ('name', models.CharField(max_length=80, verbose_name='name')),
 | 
			
		||||
                ('tags', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
 | 
			
		||||
                ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'unique_together': {('name', 'parent')},
 | 
			
		||||
            },
 | 
			
		||||
            options={"unique_together": {("name", "parent")},},
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='user',
 | 
			
		||||
            name='groups',
 | 
			
		||||
            field=models.ManyToManyField(to='passbook_core.Group'),
 | 
			
		||||
            model_name="user",
 | 
			
		||||
            name="groups",
 | 
			
		||||
            field=models.ManyToManyField(to="passbook_core.Group"),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='user',
 | 
			
		||||
            name='user_permissions',
 | 
			
		||||
            field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
 | 
			
		||||
            model_name="user",
 | 
			
		||||
            name="user_permissions",
 | 
			
		||||
            field=models.ManyToManyField(
 | 
			
		||||
                blank=True,
 | 
			
		||||
                help_text="Specific permissions for this user.",
 | 
			
		||||
                related_name="user_set",
 | 
			
		||||
                related_query_name="user",
 | 
			
		||||
                to="auth.Permission",
 | 
			
		||||
                verbose_name="user permissions",
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='UserSourceConnection',
 | 
			
		||||
            name="UserSourceConnection",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('created', models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                ('last_updated', models.DateTimeField(auto_now=True)),
 | 
			
		||||
                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
 | 
			
		||||
                ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source')),
 | 
			
		||||
                (
 | 
			
		||||
                    "id",
 | 
			
		||||
                    models.AutoField(
 | 
			
		||||
                        auto_created=True,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                        verbose_name="ID",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("created", models.DateTimeField(auto_now_add=True)),
 | 
			
		||||
                ("last_updated", models.DateTimeField(auto_now=True)),
 | 
			
		||||
                (
 | 
			
		||||
                    "user",
 | 
			
		||||
                    models.ForeignKey(
 | 
			
		||||
                        on_delete=django.db.models.deletion.CASCADE,
 | 
			
		||||
                        to=settings.AUTH_USER_MODEL,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "source",
 | 
			
		||||
                    models.ForeignKey(
 | 
			
		||||
                        on_delete=django.db.models.deletion.CASCADE,
 | 
			
		||||
                        to="passbook_core.Source",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'unique_together': {('user', 'source')},
 | 
			
		||||
            },
 | 
			
		||||
            options={"unique_together": {("user", "source")},},
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Application',
 | 
			
		||||
            name="Application",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')),
 | 
			
		||||
                ('name', models.TextField()),
 | 
			
		||||
                ('slug', models.SlugField()),
 | 
			
		||||
                ('launch_url', models.URLField(blank=True, null=True)),
 | 
			
		||||
                ('icon_url', models.TextField(blank=True, null=True)),
 | 
			
		||||
                ('skip_authorization', models.BooleanField(default=False)),
 | 
			
		||||
                ('provider', models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='passbook_core.Provider')),
 | 
			
		||||
                (
 | 
			
		||||
                    "policymodel_ptr",
 | 
			
		||||
                    models.OneToOneField(
 | 
			
		||||
                        auto_created=True,
 | 
			
		||||
                        on_delete=django.db.models.deletion.CASCADE,
 | 
			
		||||
                        parent_link=True,
 | 
			
		||||
                        primary_key=True,
 | 
			
		||||
                        serialize=False,
 | 
			
		||||
                        to="passbook_core.PolicyModel",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("name", models.TextField()),
 | 
			
		||||
                ("slug", models.SlugField()),
 | 
			
		||||
                ("launch_url", models.URLField(blank=True, null=True)),
 | 
			
		||||
                ("icon_url", models.TextField(blank=True, null=True)),
 | 
			
		||||
                ("skip_authorization", models.BooleanField(default=False)),
 | 
			
		||||
                (
 | 
			
		||||
                    "provider",
 | 
			
		||||
                    models.OneToOneField(
 | 
			
		||||
                        blank=True,
 | 
			
		||||
                        default=None,
 | 
			
		||||
                        null=True,
 | 
			
		||||
                        on_delete=django.db.models.deletion.SET_DEFAULT,
 | 
			
		||||
                        to="passbook_core.Provider",
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
            },
 | 
			
		||||
            bases=('passbook_core.policymodel',),
 | 
			
		||||
            options={"abstract": False,},
 | 
			
		||||
            bases=("passbook_core.policymodel",),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='user',
 | 
			
		||||
            name='sources',
 | 
			
		||||
            field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'),
 | 
			
		||||
            model_name="user",
 | 
			
		||||
            name="sources",
 | 
			
		||||
            field=models.ManyToManyField(
 | 
			
		||||
                through="passbook_core.UserSourceConnection", to="passbook_core.Source"
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -6,12 +6,12 @@ from django.db import migrations
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('passbook_core', '0001_initial'),
 | 
			
		||||
        ("passbook_core", "0001_initial"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterModelOptions(
 | 
			
		||||
            name='user',
 | 
			
		||||
            options={'permissions': (('reset_user_password', 'Reset Password'),)},
 | 
			
		||||
            name="user",
 | 
			
		||||
            options={"permissions": (("reset_user_password", "Reset Password"),)},
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -6,13 +6,13 @@ from django.db import migrations, models
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('passbook_core', '0001_initial'),
 | 
			
		||||
        ("passbook_core", "0001_initial"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='nonce',
 | 
			
		||||
            name='description',
 | 
			
		||||
            field=models.TextField(blank=True, default=''),
 | 
			
		||||
            model_name="nonce",
 | 
			
		||||
            name="description",
 | 
			
		||||
            field=models.TextField(blank=True, default=""),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -7,23 +7,25 @@ from django.db import migrations, models
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('passbook_core', '0002_nonce_description'),
 | 
			
		||||
        ("passbook_core", "0002_nonce_description"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RenameField(
 | 
			
		||||
            model_name='group',
 | 
			
		||||
            old_name='tags',
 | 
			
		||||
            new_name='attributes',
 | 
			
		||||
            model_name="group", old_name="tags", new_name="attributes",
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='source',
 | 
			
		||||
            name='property_mappings',
 | 
			
		||||
            field=models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping'),
 | 
			
		||||
            model_name="source",
 | 
			
		||||
            name="property_mappings",
 | 
			
		||||
            field=models.ManyToManyField(
 | 
			
		||||
                blank=True, default=None, to="passbook_core.PropertyMapping"
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='user',
 | 
			
		||||
            name='attributes',
 | 
			
		||||
            field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict),
 | 
			
		||||
            model_name="user",
 | 
			
		||||
            name="attributes",
 | 
			
		||||
            field=django.contrib.postgres.fields.jsonb.JSONField(
 | 
			
		||||
                blank=True, default=dict
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -6,9 +6,8 @@ from django.db import migrations
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('passbook_core', '0002_auto_20191010_1058'),
 | 
			
		||||
        ('passbook_core', '0002_nonce_description'),
 | 
			
		||||
        ("passbook_core", "0002_auto_20191010_1058"),
 | 
			
		||||
        ("passbook_core", "0002_nonce_description"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
    ]
 | 
			
		||||
    operations = []
 | 
			
		||||
 | 
			
		||||
@ -6,12 +6,9 @@ from django.db import migrations
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('passbook_core', '0003_auto_20191011_0914'),
 | 
			
		||||
        ("passbook_core", "0003_auto_20191011_0914"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='policy',
 | 
			
		||||
            name='action',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(model_name="policy", name="action",),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
@ -6,9 +6,8 @@ from django.db import migrations
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('passbook_core', '0004_remove_policy_action'),
 | 
			
		||||
        ('passbook_core', '0003_merge_20191010_1541'),
 | 
			
		||||
        ("passbook_core", "0004_remove_policy_action"),
 | 
			
		||||
        ("passbook_core", "0003_merge_20191010_1541"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
    ]
 | 
			
		||||
    operations = []
 | 
			
		||||
 | 
			
		||||
@ -27,12 +27,18 @@ def default_nonce_duration():
 | 
			
		||||
    """Default duration a Nonce is valid"""
 | 
			
		||||
    return now() + timedelta(hours=4)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Group(UUIDModel):
 | 
			
		||||
    """Custom Group model which supports a basic hierarchy"""
 | 
			
		||||
 | 
			
		||||
    name = models.CharField(_('name'), max_length=80)
 | 
			
		||||
    parent = models.ForeignKey('Group', blank=True, null=True,
 | 
			
		||||
                               on_delete=models.SET_NULL, related_name='children')
 | 
			
		||||
    name = models.CharField(_("name"), max_length=80)
 | 
			
		||||
    parent = models.ForeignKey(
 | 
			
		||||
        "Group",
 | 
			
		||||
        blank=True,
 | 
			
		||||
        null=True,
 | 
			
		||||
        on_delete=models.SET_NULL,
 | 
			
		||||
        related_name="children",
 | 
			
		||||
    )
 | 
			
		||||
    attributes = JSONField(default=dict, blank=True)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
@ -40,7 +46,8 @@ class Group(UUIDModel):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        unique_together = (('name', 'parent',),)
 | 
			
		||||
        unique_together = (("name", "parent",),)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class User(GuardianUserMixin, AbstractUser):
 | 
			
		||||
    """Custom User model to allow easier adding o f user-based settings"""
 | 
			
		||||
@ -48,8 +55,8 @@ class User(GuardianUserMixin, AbstractUser):
 | 
			
		||||
    uuid = models.UUIDField(default=uuid4, editable=False)
 | 
			
		||||
    name = models.TextField()
 | 
			
		||||
 | 
			
		||||
    sources = models.ManyToManyField('Source', through='UserSourceConnection')
 | 
			
		||||
    groups = models.ManyToManyField('Group')
 | 
			
		||||
    sources = models.ManyToManyField("Source", through="UserSourceConnection")
 | 
			
		||||
    groups = models.ManyToManyField("Group")
 | 
			
		||||
    password_change_date = models.DateTimeField(auto_now_add=True)
 | 
			
		||||
 | 
			
		||||
    attributes = JSONField(default=dict, blank=True)
 | 
			
		||||
@ -62,28 +69,29 @@ class User(GuardianUserMixin, AbstractUser):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        permissions = (
 | 
			
		||||
            ('reset_user_password', 'Reset Password'),
 | 
			
		||||
        )
 | 
			
		||||
        permissions = (("reset_user_password", "Reset Password"),)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Provider(models.Model):
 | 
			
		||||
    """Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
 | 
			
		||||
 | 
			
		||||
    property_mappings = models.ManyToManyField('PropertyMapping', default=None, blank=True)
 | 
			
		||||
    property_mappings = models.ManyToManyField(
 | 
			
		||||
        "PropertyMapping", default=None, blank=True
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    objects = InheritanceManager()
 | 
			
		||||
 | 
			
		||||
    # This class defines no field for easier inheritance
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        if hasattr(self, 'name'):
 | 
			
		||||
            return getattr(self, 'name')
 | 
			
		||||
        if hasattr(self, "name"):
 | 
			
		||||
            return getattr(self, "name")
 | 
			
		||||
        return super().__str__()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PolicyModel(UUIDModel, CreatedUpdatedModel):
 | 
			
		||||
    """Base model which can have policies applied to it"""
 | 
			
		||||
 | 
			
		||||
    policies = models.ManyToManyField('Policy', blank=True)
 | 
			
		||||
    policies = models.ManyToManyField("Policy", blank=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserSettings:
 | 
			
		||||
@ -108,8 +116,8 @@ class Factor(PolicyModel):
 | 
			
		||||
    enabled = models.BooleanField(default=True)
 | 
			
		||||
 | 
			
		||||
    objects = InheritanceManager()
 | 
			
		||||
    type = ''
 | 
			
		||||
    form = ''
 | 
			
		||||
    type = ""
 | 
			
		||||
    form = ""
 | 
			
		||||
 | 
			
		||||
    def user_settings(self) -> Optional[UserSettings]:
 | 
			
		||||
        """Entrypoint to integrate with User settings. Can either return None if no
 | 
			
		||||
@ -129,8 +137,9 @@ class Application(PolicyModel):
 | 
			
		||||
    slug = models.SlugField()
 | 
			
		||||
    launch_url = models.URLField(null=True, blank=True)
 | 
			
		||||
    icon_url = models.TextField(null=True, blank=True)
 | 
			
		||||
    provider = models.OneToOneField('Provider', null=True, blank=True,
 | 
			
		||||
                                    default=None, on_delete=models.SET_DEFAULT)
 | 
			
		||||
    provider = models.OneToOneField(
 | 
			
		||||
        "Provider", null=True, blank=True, default=None, on_delete=models.SET_DEFAULT
 | 
			
		||||
    )
 | 
			
		||||
    skip_authorization = models.BooleanField(default=False)
 | 
			
		||||
 | 
			
		||||
    objects = InheritanceManager()
 | 
			
		||||
@ -151,9 +160,11 @@ class Source(PolicyModel):
 | 
			
		||||
    name = models.TextField()
 | 
			
		||||
    slug = models.SlugField()
 | 
			
		||||
    enabled = models.BooleanField(default=True)
 | 
			
		||||
    property_mappings = models.ManyToManyField('PropertyMapping', default=None, blank=True)
 | 
			
		||||
    property_mappings = models.ManyToManyField(
 | 
			
		||||
        "PropertyMapping", default=None, blank=True
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    form = '' # ModelForm-based class ued to create/edit instance
 | 
			
		||||
    form = ""  # ModelForm-based class ued to create/edit instance
 | 
			
		||||
 | 
			
		||||
    objects = InheritanceManager()
 | 
			
		||||
 | 
			
		||||
@ -185,7 +196,7 @@ class UserSourceConnection(CreatedUpdatedModel):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        unique_together = (('user', 'source'),)
 | 
			
		||||
        unique_together = (("user", "source"),)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Policy(UUIDModel, CreatedUpdatedModel):
 | 
			
		||||
@ -215,25 +226,25 @@ class DebugPolicy(Policy):
 | 
			
		||||
    wait_min = models.IntegerField(default=5)
 | 
			
		||||
    wait_max = models.IntegerField(default=30)
 | 
			
		||||
 | 
			
		||||
    form = 'passbook.core.forms.policies.DebugPolicyForm'
 | 
			
		||||
    form = "passbook.core.forms.policies.DebugPolicyForm"
 | 
			
		||||
 | 
			
		||||
    def passes(self, request: PolicyRequest) -> PolicyResult:
 | 
			
		||||
        """Wait random time then return result"""
 | 
			
		||||
        wait = SystemRandom().randrange(self.wait_min, self.wait_max)
 | 
			
		||||
        LOGGER.debug("Policy waiting", policy=self, delay=wait)
 | 
			
		||||
        sleep(wait)
 | 
			
		||||
        return PolicyResult(self.result, 'Debugging')
 | 
			
		||||
        return PolicyResult(self.result, "Debugging")
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        verbose_name = _('Debug Policy')
 | 
			
		||||
        verbose_name_plural = _('Debug Policies')
 | 
			
		||||
        verbose_name = _("Debug Policy")
 | 
			
		||||
        verbose_name_plural = _("Debug Policies")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Invitation(UUIDModel):
 | 
			
		||||
    """Single-use invitation link"""
 | 
			
		||||
 | 
			
		||||
    created_by = models.ForeignKey('User', on_delete=models.CASCADE)
 | 
			
		||||
    created_by = models.ForeignKey("User", on_delete=models.CASCADE)
 | 
			
		||||
    expires = models.DateTimeField(default=None, blank=True, null=True)
 | 
			
		||||
    fixed_username = models.TextField(blank=True, default=None)
 | 
			
		||||
    fixed_email = models.TextField(blank=True, default=None)
 | 
			
		||||
@ -242,24 +253,26 @@ class Invitation(UUIDModel):
 | 
			
		||||
    @property
 | 
			
		||||
    def link(self):
 | 
			
		||||
        """Get link to use invitation"""
 | 
			
		||||
        return reverse_lazy('passbook_core:auth-sign-up') + f'?invitation={self.uuid.hex}'
 | 
			
		||||
        return (
 | 
			
		||||
            reverse_lazy("passbook_core:auth-sign-up") + f"?invitation={self.uuid.hex}"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"Invitation {self.uuid.hex} created by {self.created_by}"
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        verbose_name = _('Invitation')
 | 
			
		||||
        verbose_name_plural = _('Invitations')
 | 
			
		||||
        verbose_name = _("Invitation")
 | 
			
		||||
        verbose_name_plural = _("Invitations")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Nonce(UUIDModel):
 | 
			
		||||
    """One-time link for password resets/sign-up-confirmations"""
 | 
			
		||||
 | 
			
		||||
    expires = models.DateTimeField(default=default_nonce_duration)
 | 
			
		||||
    user = models.ForeignKey('User', on_delete=models.CASCADE)
 | 
			
		||||
    user = models.ForeignKey("User", on_delete=models.CASCADE)
 | 
			
		||||
    expiring = models.BooleanField(default=True)
 | 
			
		||||
    description = models.TextField(default='', blank=True)
 | 
			
		||||
    description = models.TextField(default="", blank=True)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def is_expired(self) -> bool:
 | 
			
		||||
@ -271,8 +284,8 @@ class Nonce(UUIDModel):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        verbose_name = _('Nonce')
 | 
			
		||||
        verbose_name_plural = _('Nonces')
 | 
			
		||||
        verbose_name = _("Nonce")
 | 
			
		||||
        verbose_name_plural = _("Nonces")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PropertyMapping(UUIDModel):
 | 
			
		||||
@ -280,7 +293,7 @@ class PropertyMapping(UUIDModel):
 | 
			
		||||
 | 
			
		||||
    name = models.TextField()
 | 
			
		||||
 | 
			
		||||
    form = ''
 | 
			
		||||
    form = ""
 | 
			
		||||
    objects = InheritanceManager()
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
@ -288,5 +301,5 @@ class PropertyMapping(UUIDModel):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
        verbose_name = _('Property Mapping')
 | 
			
		||||
        verbose_name_plural = _('Property Mappings')
 | 
			
		||||
        verbose_name = _("Property Mapping")
 | 
			
		||||
        verbose_name_plural = _("Property Mappings")
 | 
			
		||||
 | 
			
		||||
@ -7,16 +7,18 @@ from structlog import get_logger
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
user_signed_up = Signal(providing_args=['request', 'user'])
 | 
			
		||||
invitation_created = Signal(providing_args=['request', 'invitation'])
 | 
			
		||||
invitation_used = Signal(providing_args=['request', 'invitation', 'user'])
 | 
			
		||||
password_changed = Signal(providing_args=['user', 'password'])
 | 
			
		||||
user_signed_up = Signal(providing_args=["request", "user"])
 | 
			
		||||
invitation_created = Signal(providing_args=["request", "invitation"])
 | 
			
		||||
invitation_used = Signal(providing_args=["request", "invitation", "user"])
 | 
			
		||||
password_changed = Signal(providing_args=["user", "password"])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(post_save)
 | 
			
		||||
# pylint: disable=unused-argument
 | 
			
		||||
def invalidate_policy_cache(sender, instance, **_):
 | 
			
		||||
    """Invalidate Policy cache when policy is updated"""
 | 
			
		||||
    from passbook.core.models import Policy
 | 
			
		||||
 | 
			
		||||
    if isinstance(instance, Policy):
 | 
			
		||||
        LOGGER.debug("Invalidating policy cache", policy=instance)
 | 
			
		||||
        keys = cache.keys("%s#*" % instance.pk)
 | 
			
		||||
 | 
			
		||||
@ -7,8 +7,9 @@ from passbook.root.celery import CELERY_APP
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@CELERY_APP.task()
 | 
			
		||||
def clean_nonces():
 | 
			
		||||
    """Remove expired nonces"""
 | 
			
		||||
    amount, _ = Nonce.objects.filter(expires__lt=now(), expiring=True).delete()
 | 
			
		||||
    LOGGER.debug('Deleted expired nonces', amount=amount)
 | 
			
		||||
    LOGGER.debug("Deleted expired nonces", amount=amount)
 | 
			
		||||
 | 
			
		||||
@ -9,29 +9,37 @@ from passbook.policies.engine import PolicyEngine
 | 
			
		||||
 | 
			
		||||
register = template.Library()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag(takes_context=True)
 | 
			
		||||
def user_factors(context: RequestContext) -> List[UserSettings]:
 | 
			
		||||
    """Return list of all factors which apply to user"""
 | 
			
		||||
    user = context.get('request').user
 | 
			
		||||
    _all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses()
 | 
			
		||||
    user = context.get("request").user
 | 
			
		||||
    _all_factors = (
 | 
			
		||||
        Factor.objects.filter(enabled=True).order_by("order").select_subclasses()
 | 
			
		||||
    )
 | 
			
		||||
    matching_factors: List[UserSettings] = []
 | 
			
		||||
    for factor in _all_factors:
 | 
			
		||||
        user_settings = factor.user_settings()
 | 
			
		||||
        policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request'))
 | 
			
		||||
        policy_engine = PolicyEngine(
 | 
			
		||||
            factor.policies.all(), user, context.get("request")
 | 
			
		||||
        )
 | 
			
		||||
        policy_engine.build()
 | 
			
		||||
        if policy_engine.passing and user_settings:
 | 
			
		||||
            matching_factors.append(user_settings)
 | 
			
		||||
    return matching_factors
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag(takes_context=True)
 | 
			
		||||
def user_sources(context: RequestContext) -> List[UserSettings]:
 | 
			
		||||
    """Return a list of all sources which are enabled for the user"""
 | 
			
		||||
    user = context.get('request').user
 | 
			
		||||
    user = context.get("request").user
 | 
			
		||||
    _all_sources = Source.objects.filter(enabled=True).select_subclasses()
 | 
			
		||||
    matching_sources: List[UserSettings] = []
 | 
			
		||||
    for factor in _all_sources:
 | 
			
		||||
        user_settings = factor.user_settings()
 | 
			
		||||
        policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request'))
 | 
			
		||||
        policy_engine = PolicyEngine(
 | 
			
		||||
            factor.policies.all(), user, context.get("request")
 | 
			
		||||
        )
 | 
			
		||||
        policy_engine.build()
 | 
			
		||||
        if policy_engine.passing and user_settings:
 | 
			
		||||
            matching_sources.append(user_settings)
 | 
			
		||||
 | 
			
		||||
@ -15,70 +15,78 @@ class TestAuthenticationViews(TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.sign_up_data = {
 | 
			
		||||
            'name': 'Test',
 | 
			
		||||
            'username': 'beryjuorg',
 | 
			
		||||
            'email': 'unittest@passbook.beryju.org',
 | 
			
		||||
            'password': 'B3ryju0rg!',
 | 
			
		||||
            'password_repeat': 'B3ryju0rg!',
 | 
			
		||||
            "name": "Test",
 | 
			
		||||
            "username": "beryjuorg",
 | 
			
		||||
            "email": "unittest@passbook.beryju.org",
 | 
			
		||||
            "password": "B3ryju0rg!",
 | 
			
		||||
            "password_repeat": "B3ryju0rg!",
 | 
			
		||||
        }
 | 
			
		||||
        self.login_data = {
 | 
			
		||||
            'uid_field': 'unittest@example.com',
 | 
			
		||||
            "uid_field": "unittest@example.com",
 | 
			
		||||
        }
 | 
			
		||||
        self.user = User.objects.create_superuser(
 | 
			
		||||
            username='unittest user',
 | 
			
		||||
            email='unittest@example.com',
 | 
			
		||||
            password=''.join(SystemRandom().choice(
 | 
			
		||||
                string.ascii_uppercase + string.digits) for _ in range(8)))
 | 
			
		||||
 | 
			
		||||
            username="unittest user",
 | 
			
		||||
            email="unittest@example.com",
 | 
			
		||||
            password="".join(
 | 
			
		||||
                SystemRandom().choice(string.ascii_uppercase + string.digits)
 | 
			
		||||
                for _ in range(8)
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_sign_up_view(self):
 | 
			
		||||
        """Test account.sign_up view (Anonymous)"""
 | 
			
		||||
        self.client.logout()
 | 
			
		||||
        response = self.client.get(reverse('passbook_core:auth-sign-up'))
 | 
			
		||||
        response = self.client.get(reverse("passbook_core:auth-sign-up"))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
    def test_login_view(self):
 | 
			
		||||
        """Test account.login view (Anonymous)"""
 | 
			
		||||
        self.client.logout()
 | 
			
		||||
        response = self.client.get(reverse('passbook_core:auth-login'))
 | 
			
		||||
        response = self.client.get(reverse("passbook_core:auth-login"))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        # test login with post
 | 
			
		||||
        form = LoginForm(self.login_data)
 | 
			
		||||
        self.assertTrue(form.is_valid())
 | 
			
		||||
 | 
			
		||||
        response = self.client.post(reverse('passbook_core:auth-login'), data=form.cleaned_data)
 | 
			
		||||
        response = self.client.post(
 | 
			
		||||
            reverse("passbook_core:auth-login"), data=form.cleaned_data
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(response.status_code, 302)
 | 
			
		||||
 | 
			
		||||
    def test_logout_view(self):
 | 
			
		||||
        """Test account.logout view"""
 | 
			
		||||
        self.client.force_login(self.user)
 | 
			
		||||
        response = self.client.get(reverse('passbook_core:auth-logout'))
 | 
			
		||||
        response = self.client.get(reverse("passbook_core:auth-logout"))
 | 
			
		||||
        self.assertEqual(response.status_code, 302)
 | 
			
		||||
 | 
			
		||||
    def test_sign_up_view_auth(self):
 | 
			
		||||
        """Test account.sign_up view (Authenticated)"""
 | 
			
		||||
        self.client.force_login(self.user)
 | 
			
		||||
        response = self.client.get(reverse('passbook_core:auth-logout'))
 | 
			
		||||
        response = self.client.get(reverse("passbook_core:auth-logout"))
 | 
			
		||||
        self.assertEqual(response.status_code, 302)
 | 
			
		||||
 | 
			
		||||
    def test_login_view_auth(self):
 | 
			
		||||
        """Test account.login view (Authenticated)"""
 | 
			
		||||
        self.client.force_login(self.user)
 | 
			
		||||
        response = self.client.get(reverse('passbook_core:auth-login'))
 | 
			
		||||
        response = self.client.get(reverse("passbook_core:auth-login"))
 | 
			
		||||
        self.assertEqual(response.status_code, 302)
 | 
			
		||||
 | 
			
		||||
    def test_login_view_post(self):
 | 
			
		||||
        """Test account.login view POST (Anonymous)"""
 | 
			
		||||
        login_response = self.client.post(reverse('passbook_core:auth-login'), data=self.login_data)
 | 
			
		||||
        login_response = self.client.post(
 | 
			
		||||
            reverse("passbook_core:auth-login"), data=self.login_data
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(login_response.status_code, 302)
 | 
			
		||||
        self.assertEqual(login_response.url, reverse('passbook_core:auth-process'))
 | 
			
		||||
        self.assertEqual(login_response.url, reverse("passbook_core:auth-process"))
 | 
			
		||||
 | 
			
		||||
    def test_sign_up_view_post(self):
 | 
			
		||||
        """Test account.sign_up view POST (Anonymous)"""
 | 
			
		||||
        form = SignUpForm(self.sign_up_data)
 | 
			
		||||
        self.assertTrue(form.is_valid())
 | 
			
		||||
 | 
			
		||||
        response = self.client.post(reverse('passbook_core:auth-sign-up'), data=form.cleaned_data)
 | 
			
		||||
        response = self.client.post(
 | 
			
		||||
            reverse("passbook_core:auth-sign-up"), data=form.cleaned_data
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(response.status_code, 302)
 | 
			
		||||
 | 
			
		||||
    # def test_reset_password_init_view(self):
 | 
			
		||||
 | 
			
		||||
@ -14,12 +14,17 @@ class TestOverviewViews(TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.user = User.objects.create_superuser(
 | 
			
		||||
            username='unittest user',
 | 
			
		||||
            email='unittest@example.com',
 | 
			
		||||
            password=''.join(SystemRandom().choice(
 | 
			
		||||
                string.ascii_uppercase + string.digits) for _ in range(8)))
 | 
			
		||||
            username="unittest user",
 | 
			
		||||
            email="unittest@example.com",
 | 
			
		||||
            password="".join(
 | 
			
		||||
                SystemRandom().choice(string.ascii_uppercase + string.digits)
 | 
			
		||||
                for _ in range(8)
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
        self.client.force_login(self.user)
 | 
			
		||||
 | 
			
		||||
    def test_overview(self):
 | 
			
		||||
        """Test UserSettingsView"""
 | 
			
		||||
        self.assertEqual(self.client.get(reverse('passbook_core:overview')).status_code, 200)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            self.client.get(reverse("passbook_core:overview")).status_code, 200
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@ -15,33 +15,43 @@ class TestUserViews(TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.user = User.objects.create_superuser(
 | 
			
		||||
            username='unittest user',
 | 
			
		||||
            email='unittest@example.com',
 | 
			
		||||
            password=''.join(SystemRandom().choice(
 | 
			
		||||
                string.ascii_uppercase + string.digits) for _ in range(8)))
 | 
			
		||||
            username="unittest user",
 | 
			
		||||
            email="unittest@example.com",
 | 
			
		||||
            password="".join(
 | 
			
		||||
                SystemRandom().choice(string.ascii_uppercase + string.digits)
 | 
			
		||||
                for _ in range(8)
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
        self.client.force_login(self.user)
 | 
			
		||||
 | 
			
		||||
    def test_user_settings(self):
 | 
			
		||||
        """Test UserSettingsView"""
 | 
			
		||||
        self.assertEqual(self.client.get(reverse('passbook_core:user-settings')).status_code, 200)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            self.client.get(reverse("passbook_core:user-settings")).status_code, 200
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_user_delete(self):
 | 
			
		||||
        """Test UserDeleteView"""
 | 
			
		||||
        self.assertEqual(self.client.post(reverse('passbook_core:user-delete')).status_code, 302)
 | 
			
		||||
        self.assertEqual(User.objects.filter(username='unittest user').exists(), False)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            self.client.post(reverse("passbook_core:user-delete")).status_code, 302
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(User.objects.filter(username="unittest user").exists(), False)
 | 
			
		||||
        self.setUp()
 | 
			
		||||
 | 
			
		||||
    def test_user_change_password(self):
 | 
			
		||||
        """Test UserChangePasswordView"""
 | 
			
		||||
        form_data = {
 | 
			
		||||
            'password': 'test2',
 | 
			
		||||
            'password_repeat': 'test2'
 | 
			
		||||
        }
 | 
			
		||||
        form_data = {"password": "test2", "password_repeat": "test2"}
 | 
			
		||||
        form = PasswordChangeForm(data=form_data)
 | 
			
		||||
        self.assertTrue(form.is_valid())
 | 
			
		||||
        self.assertEqual(self.client.get(
 | 
			
		||||
            reverse('passbook_core:user-change-password')).status_code, 200)
 | 
			
		||||
        self.assertEqual(self.client.post(
 | 
			
		||||
            reverse('passbook_core:user-change-password'), data=form_data).status_code, 302)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            self.client.get(reverse("passbook_core:user-change-password")).status_code,
 | 
			
		||||
            200,
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            self.client.post(
 | 
			
		||||
                reverse("passbook_core:user-change-password"), data=form_data
 | 
			
		||||
            ).status_code,
 | 
			
		||||
            302,
 | 
			
		||||
        )
 | 
			
		||||
        self.user.refresh_from_db()
 | 
			
		||||
        self.assertTrue(self.user.check_password('test2'))
 | 
			
		||||
        self.assertTrue(self.user.check_password("test2"))
 | 
			
		||||
 | 
			
		||||
@ -13,22 +13,25 @@ class TestUtilViews(TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.user = User.objects.create_superuser(
 | 
			
		||||
            username='unittest user',
 | 
			
		||||
            email='unittest@example.com',
 | 
			
		||||
            password=''.join(SystemRandom().choice(
 | 
			
		||||
                string.ascii_uppercase + string.digits) for _ in range(8)))
 | 
			
		||||
            username="unittest user",
 | 
			
		||||
            email="unittest@example.com",
 | 
			
		||||
            password="".join(
 | 
			
		||||
                SystemRandom().choice(string.ascii_uppercase + string.digits)
 | 
			
		||||
                for _ in range(8)
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
        self.factory = RequestFactory()
 | 
			
		||||
 | 
			
		||||
    def test_loading_view(self):
 | 
			
		||||
        """Test loading view"""
 | 
			
		||||
        request = self.factory.get('something')
 | 
			
		||||
        response = LoadingView.as_view(target_url='somestring')(request)
 | 
			
		||||
        request = self.factory.get("something")
 | 
			
		||||
        response = LoadingView.as_view(target_url="somestring")(request)
 | 
			
		||||
        response.render()
 | 
			
		||||
        self.assertIn('somestring', response.content.decode('utf-8'))
 | 
			
		||||
        self.assertIn("somestring", response.content.decode("utf-8"))
 | 
			
		||||
 | 
			
		||||
    def test_permission_denied_view(self):
 | 
			
		||||
        """Test PermissionDeniedView"""
 | 
			
		||||
        request = self.factory.get('something')
 | 
			
		||||
        request = self.factory.get("something")
 | 
			
		||||
        request.user = self.user
 | 
			
		||||
        response = PermissionDeniedView.as_view()(request)
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
@ -9,21 +9,38 @@ LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    # Authentication views
 | 
			
		||||
    path('auth/login/', authentication.LoginView.as_view(), name='auth-login'),
 | 
			
		||||
    path('auth/logout/', authentication.LogoutView.as_view(), name='auth-logout'),
 | 
			
		||||
    path('auth/sign_up/', authentication.SignUpView.as_view(), name='auth-sign-up'),
 | 
			
		||||
    path('auth/sign_up/<uuid:nonce>/confirm/', authentication.SignUpConfirmView.as_view(),
 | 
			
		||||
         name='auth-sign-up-confirm'),
 | 
			
		||||
    path('auth/process/denied/', view.FactorPermissionDeniedView.as_view(), name='auth-denied'),
 | 
			
		||||
    path('auth/password/reset/<uuid:nonce>/', authentication.PasswordResetView.as_view(),
 | 
			
		||||
         name='auth-password-reset'),
 | 
			
		||||
    path('auth/process/', view.AuthenticationView.as_view(), name='auth-process'),
 | 
			
		||||
    path('auth/process/<slug:factor>/', view.AuthenticationView.as_view(), name='auth-process'),
 | 
			
		||||
    path("auth/login/", authentication.LoginView.as_view(), name="auth-login"),
 | 
			
		||||
    path("auth/logout/", authentication.LogoutView.as_view(), name="auth-logout"),
 | 
			
		||||
    path("auth/sign_up/", authentication.SignUpView.as_view(), name="auth-sign-up"),
 | 
			
		||||
    path(
 | 
			
		||||
        "auth/sign_up/<uuid:nonce>/confirm/",
 | 
			
		||||
        authentication.SignUpConfirmView.as_view(),
 | 
			
		||||
        name="auth-sign-up-confirm",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "auth/process/denied/",
 | 
			
		||||
        view.FactorPermissionDeniedView.as_view(),
 | 
			
		||||
        name="auth-denied",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "auth/password/reset/<uuid:nonce>/",
 | 
			
		||||
        authentication.PasswordResetView.as_view(),
 | 
			
		||||
        name="auth-password-reset",
 | 
			
		||||
    ),
 | 
			
		||||
    path("auth/process/", view.AuthenticationView.as_view(), name="auth-process"),
 | 
			
		||||
    path(
 | 
			
		||||
        "auth/process/<slug:factor>/",
 | 
			
		||||
        view.AuthenticationView.as_view(),
 | 
			
		||||
        name="auth-process",
 | 
			
		||||
    ),
 | 
			
		||||
    # User views
 | 
			
		||||
    path('_/user/', user.UserSettingsView.as_view(), name='user-settings'),
 | 
			
		||||
    path('_/user/delete/', user.UserDeleteView.as_view(), name='user-delete'),
 | 
			
		||||
    path('_/user/change_password/', user.UserChangePasswordView.as_view(),
 | 
			
		||||
         name='user-change-password'),
 | 
			
		||||
    path("_/user/", user.UserSettingsView.as_view(), name="user-settings"),
 | 
			
		||||
    path("_/user/delete/", user.UserDeleteView.as_view(), name="user-delete"),
 | 
			
		||||
    path(
 | 
			
		||||
        "_/user/change_password/",
 | 
			
		||||
        user.UserChangePasswordView.as_view(),
 | 
			
		||||
        name="user-change-password",
 | 
			
		||||
    ),
 | 
			
		||||
    # Overview
 | 
			
		||||
    path('', overview.OverviewView.as_view(), name='overview'),
 | 
			
		||||
    path("", overview.OverviewView.as_view(), name="overview"),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ from passbook.policies.engine import PolicyEngine
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AccessMixin:
 | 
			
		||||
    """Mixin class for usage in Authorization views.
 | 
			
		||||
    Provider functions to check application access, etc"""
 | 
			
		||||
@ -23,12 +24,18 @@ class AccessMixin:
 | 
			
		||||
        try:
 | 
			
		||||
            return provider.application
 | 
			
		||||
        except Application.DoesNotExist as exc:
 | 
			
		||||
            messages.error(self.request, _('Provider "%(name)s" has no application assigned' % {
 | 
			
		||||
                'name': provider
 | 
			
		||||
                }))
 | 
			
		||||
            messages.error(
 | 
			
		||||
                self.request,
 | 
			
		||||
                _(
 | 
			
		||||
                    'Provider "%(name)s" has no application assigned'
 | 
			
		||||
                    % {"name": provider}
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
            raise exc
 | 
			
		||||
 | 
			
		||||
    def user_has_access(self, application: Application, user: User) -> Tuple[bool, List[str]]:
 | 
			
		||||
    def user_has_access(
 | 
			
		||||
        self, application: Application, user: User
 | 
			
		||||
    ) -> Tuple[bool, List[str]]:
 | 
			
		||||
        """Check if user has access to application."""
 | 
			
		||||
        LOGGER.debug("Checking permissions", user=user, application=application)
 | 
			
		||||
        policy_engine = PolicyEngine(application.policies.all(), user, self.request)
 | 
			
		||||
 | 
			
		||||
@ -25,41 +25,41 @@ LOGGER = get_logger()
 | 
			
		||||
class LoginView(UserPassesTestMixin, FormView):
 | 
			
		||||
    """Allow users to sign in"""
 | 
			
		||||
 | 
			
		||||
    template_name = 'login/form.html'
 | 
			
		||||
    template_name = "login/form.html"
 | 
			
		||||
    form_class = LoginForm
 | 
			
		||||
    success_url = '.'
 | 
			
		||||
    success_url = "."
 | 
			
		||||
 | 
			
		||||
    # Allow only not authenticated users to login
 | 
			
		||||
    def test_func(self):
 | 
			
		||||
        return self.request.user.is_authenticated is False
 | 
			
		||||
 | 
			
		||||
    def handle_no_permission(self):
 | 
			
		||||
        if 'next' in self.request.GET:
 | 
			
		||||
            return redirect(self.request.GET.get('next'))
 | 
			
		||||
        return redirect(reverse('passbook_core:overview'))
 | 
			
		||||
        if "next" in self.request.GET:
 | 
			
		||||
            return redirect(self.request.GET.get("next"))
 | 
			
		||||
        return redirect(reverse("passbook_core:overview"))
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['config'] = CONFIG.y('passbook')
 | 
			
		||||
        kwargs['is_login'] = True
 | 
			
		||||
        kwargs['title'] = _('Log in to your account')
 | 
			
		||||
        kwargs['primary_action'] = _('Log in')
 | 
			
		||||
        kwargs['show_sign_up_notice'] = CONFIG.y('passbook.sign_up.enabled')
 | 
			
		||||
        kwargs['sources'] = []
 | 
			
		||||
        kwargs["config"] = CONFIG.y("passbook")
 | 
			
		||||
        kwargs["is_login"] = True
 | 
			
		||||
        kwargs["title"] = _("Log in to your account")
 | 
			
		||||
        kwargs["primary_action"] = _("Log in")
 | 
			
		||||
        kwargs["show_sign_up_notice"] = CONFIG.y("passbook.sign_up.enabled")
 | 
			
		||||
        kwargs["sources"] = []
 | 
			
		||||
        sources = Source.objects.filter(enabled=True).select_subclasses()
 | 
			
		||||
        for source in sources:
 | 
			
		||||
            login_button = source.login_button
 | 
			
		||||
            if login_button:
 | 
			
		||||
                kwargs['sources'].append(login_button)
 | 
			
		||||
        if kwargs['sources']:
 | 
			
		||||
            self.template_name = 'login/with_sources.html'
 | 
			
		||||
                kwargs["sources"].append(login_button)
 | 
			
		||||
        if kwargs["sources"]:
 | 
			
		||||
            self.template_name = "login/with_sources.html"
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_user(self, uid_value) -> Optional[User]:
 | 
			
		||||
        """Find user instance. Returns None if no user was found."""
 | 
			
		||||
        for search_field in CONFIG.y('passbook.uid_fields'):
 | 
			
		||||
        for search_field in CONFIG.y("passbook.uid_fields"):
 | 
			
		||||
            # Workaround for E-Mail -> email
 | 
			
		||||
            if search_field == 'e-mail':
 | 
			
		||||
                search_field = 'email'
 | 
			
		||||
            if search_field == "e-mail":
 | 
			
		||||
                search_field = "email"
 | 
			
		||||
            users = User.objects.filter(**{search_field: uid_value})
 | 
			
		||||
            if users.exists():
 | 
			
		||||
                LOGGER.debug("Found user", user=users.first(), uid_field=search_field)
 | 
			
		||||
@ -68,17 +68,20 @@ class LoginView(UserPassesTestMixin, FormView):
 | 
			
		||||
 | 
			
		||||
    def form_valid(self, form: LoginForm) -> HttpResponse:
 | 
			
		||||
        """Form data is valid"""
 | 
			
		||||
        pre_user = self.get_user(form.cleaned_data.get('uid_field'))
 | 
			
		||||
        pre_user = self.get_user(form.cleaned_data.get("uid_field"))
 | 
			
		||||
        if not pre_user:
 | 
			
		||||
            # No user found
 | 
			
		||||
            return self.invalid_login(self.request)
 | 
			
		||||
        # self.request.session.flush()
 | 
			
		||||
        self.request.session[AuthenticationView.SESSION_PENDING_USER] = pre_user.pk
 | 
			
		||||
        return _redirect_with_qs('passbook_core:auth-process', self.request.GET)
 | 
			
		||||
        return _redirect_with_qs("passbook_core:auth-process", self.request.GET)
 | 
			
		||||
 | 
			
		||||
    def invalid_login(self, request: HttpRequest, disabled_user: User = None) -> HttpResponse:
 | 
			
		||||
    def invalid_login(
 | 
			
		||||
        self, request: HttpRequest, disabled_user: User = None
 | 
			
		||||
    ) -> HttpResponse:
 | 
			
		||||
        """Handle login for disabled users/invalid login attempts"""
 | 
			
		||||
        messages.error(request, _('Failed to authenticate.'))
 | 
			
		||||
        LOGGER.debug("invalid_login", user=disabled_user)
 | 
			
		||||
        messages.error(request, _("Failed to authenticate."))
 | 
			
		||||
        return self.render_to_response(self.get_context_data())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -89,15 +92,15 @@ class LogoutView(LoginRequiredMixin, View):
 | 
			
		||||
        """Log current user out"""
 | 
			
		||||
        logout(request)
 | 
			
		||||
        messages.success(request, _("You've successfully been logged out."))
 | 
			
		||||
        return redirect(reverse('passbook_core:auth-login'))
 | 
			
		||||
        return redirect(reverse("passbook_core:auth-login"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SignUpView(UserPassesTestMixin, FormView):
 | 
			
		||||
    """Sign up new user, optionally consume one-use invitation link."""
 | 
			
		||||
 | 
			
		||||
    template_name = 'login/form.html'
 | 
			
		||||
    template_name = "login/form.html"
 | 
			
		||||
    form_class = SignUpForm
 | 
			
		||||
    success_url = '.'
 | 
			
		||||
    success_url = "."
 | 
			
		||||
    # Invitation instance, if invitation link was used
 | 
			
		||||
    _invitation = None
 | 
			
		||||
    # Instance of newly created user
 | 
			
		||||
@ -108,38 +111,38 @@ class SignUpView(UserPassesTestMixin, FormView):
 | 
			
		||||
        return self.request.user.is_authenticated is False
 | 
			
		||||
 | 
			
		||||
    def handle_no_permission(self):
 | 
			
		||||
        return redirect(reverse('passbook_core:overview'))
 | 
			
		||||
        return redirect(reverse("passbook_core:overview"))
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        """Check if sign-up is enabled or invitation link given"""
 | 
			
		||||
        allowed = False
 | 
			
		||||
        if 'invitation' in request.GET:
 | 
			
		||||
            invitations = Invitation.objects.filter(uuid=request.GET.get('invitation'))
 | 
			
		||||
        if "invitation" in request.GET:
 | 
			
		||||
            invitations = Invitation.objects.filter(uuid=request.GET.get("invitation"))
 | 
			
		||||
            allowed = invitations.exists()
 | 
			
		||||
            if allowed:
 | 
			
		||||
                self._invitation = invitations.first()
 | 
			
		||||
        if CONFIG.y('passbook.sign_up.enabled'):
 | 
			
		||||
        if CONFIG.y("passbook.sign_up.enabled"):
 | 
			
		||||
            allowed = True
 | 
			
		||||
        if not allowed:
 | 
			
		||||
            messages.error(request, _('Sign-ups are currently disabled.'))
 | 
			
		||||
            return redirect(reverse('passbook_core:auth-login'))
 | 
			
		||||
            messages.error(request, _("Sign-ups are currently disabled."))
 | 
			
		||||
            return redirect(reverse("passbook_core:auth-login"))
 | 
			
		||||
        return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_initial(self):
 | 
			
		||||
        if self._invitation:
 | 
			
		||||
            initial = {}
 | 
			
		||||
            if self._invitation.fixed_username:
 | 
			
		||||
                initial['username'] = self._invitation.fixed_username
 | 
			
		||||
                initial["username"] = self._invitation.fixed_username
 | 
			
		||||
            if self._invitation.fixed_email:
 | 
			
		||||
                initial['email'] = self._invitation.fixed_email
 | 
			
		||||
                initial["email"] = self._invitation.fixed_email
 | 
			
		||||
            return initial
 | 
			
		||||
        return super().get_initial()
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['config'] = CONFIG.y('passbook')
 | 
			
		||||
        kwargs['is_login'] = True
 | 
			
		||||
        kwargs['title'] = _('Sign Up')
 | 
			
		||||
        kwargs['primary_action'] = _('Sign up')
 | 
			
		||||
        kwargs["config"] = CONFIG.y("passbook")
 | 
			
		||||
        kwargs["is_login"] = True
 | 
			
		||||
        kwargs["title"] = _("Sign Up")
 | 
			
		||||
        kwargs["primary_action"] = _("Sign up")
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def form_valid(self, form: SignUpForm) -> HttpResponse:
 | 
			
		||||
@ -172,9 +175,8 @@ class SignUpView(UserPassesTestMixin, FormView):
 | 
			
		||||
        #     self._user.save()
 | 
			
		||||
        self.consume_invitation()
 | 
			
		||||
        messages.success(self.request, _("Successfully signed up!"))
 | 
			
		||||
        LOGGER.debug("Successfully signed up %s",
 | 
			
		||||
                     form.cleaned_data.get('email'))
 | 
			
		||||
        return redirect(reverse('passbook_core:auth-login'))
 | 
			
		||||
        LOGGER.debug("Successfully signed up %s", form.cleaned_data.get("email"))
 | 
			
		||||
        return redirect(reverse("passbook_core:auth-login"))
 | 
			
		||||
 | 
			
		||||
    def consume_invitation(self):
 | 
			
		||||
        """Consume invitation if an invitation was used"""
 | 
			
		||||
@ -183,7 +185,8 @@ class SignUpView(UserPassesTestMixin, FormView):
 | 
			
		||||
                sender=self,
 | 
			
		||||
                request=self.request,
 | 
			
		||||
                invitation=self._invitation,
 | 
			
		||||
                user=self._user)
 | 
			
		||||
                user=self._user,
 | 
			
		||||
            )
 | 
			
		||||
            self._invitation.delete()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
@ -203,20 +206,17 @@ class SignUpView(UserPassesTestMixin, FormView):
 | 
			
		||||
        """
 | 
			
		||||
        # Create user
 | 
			
		||||
        new_user = User.objects.create(
 | 
			
		||||
            username=data.get('username'),
 | 
			
		||||
            email=data.get('email'),
 | 
			
		||||
            name=data.get('name'),
 | 
			
		||||
            username=data.get("username"),
 | 
			
		||||
            email=data.get("email"),
 | 
			
		||||
            name=data.get("name"),
 | 
			
		||||
        )
 | 
			
		||||
        new_user.is_active = True
 | 
			
		||||
        try:
 | 
			
		||||
            new_user.set_password(data.get('password'))
 | 
			
		||||
            new_user.set_password(data.get("password"))
 | 
			
		||||
            new_user.save()
 | 
			
		||||
            request.user = new_user
 | 
			
		||||
            # Send signal for other auth sources
 | 
			
		||||
            user_signed_up.send(
 | 
			
		||||
                sender=SignUpView,
 | 
			
		||||
                user=new_user,
 | 
			
		||||
                request=request)
 | 
			
		||||
            user_signed_up.send(sender=SignUpView, user=new_user, request=request)
 | 
			
		||||
            return new_user
 | 
			
		||||
        except PasswordPolicyInvalid as exc:
 | 
			
		||||
            new_user.delete()
 | 
			
		||||
@ -232,11 +232,11 @@ class SignUpConfirmView(View):
 | 
			
		||||
        nonce.user.is_active = True
 | 
			
		||||
        nonce.user.save()
 | 
			
		||||
        # Workaround: hardcoded reference to ModelBackend, needs testing
 | 
			
		||||
        nonce.user.backend = 'django.contrib.auth.backends.ModelBackend'
 | 
			
		||||
        nonce.user.backend = "django.contrib.auth.backends.ModelBackend"
 | 
			
		||||
        login(request, nonce.user)
 | 
			
		||||
        nonce.delete()
 | 
			
		||||
        messages.success(request, _('Successfully confirmed registration.'))
 | 
			
		||||
        return redirect('passbook_core:overview')
 | 
			
		||||
        messages.success(request, _("Successfully confirmed registration."))
 | 
			
		||||
        return redirect("passbook_core:overview")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PasswordResetView(View):
 | 
			
		||||
@ -247,9 +247,11 @@ class PasswordResetView(View):
 | 
			
		||||
        # 3. (Optional) Trap user in password change view
 | 
			
		||||
        nonce = get_object_or_404(Nonce, uuid=nonce)
 | 
			
		||||
        # Workaround: hardcoded reference to ModelBackend, needs testing
 | 
			
		||||
        nonce.user.backend = 'django.contrib.auth.backends.ModelBackend'
 | 
			
		||||
        nonce.user.backend = "django.contrib.auth.backends.ModelBackend"
 | 
			
		||||
        login(request, nonce.user)
 | 
			
		||||
        nonce.delete()
 | 
			
		||||
        messages.success(request, _(('Temporarily authenticated with Nonce, '
 | 
			
		||||
                                     'please change your password')))
 | 
			
		||||
        return redirect('passbook_core:user-change-password')
 | 
			
		||||
        messages.success(
 | 
			
		||||
            request,
 | 
			
		||||
            _(("Temporarily authenticated with Nonce, " "please change your password")),
 | 
			
		||||
        )
 | 
			
		||||
        return redirect("passbook_core:user-change-password")
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,11 @@
 | 
			
		||||
"""passbook core error views"""
 | 
			
		||||
 | 
			
		||||
from django.http.response import (HttpResponseBadRequest,
 | 
			
		||||
                                  HttpResponseForbidden, HttpResponseNotFound,
 | 
			
		||||
                                  HttpResponseServerError)
 | 
			
		||||
from django.http.response import (
 | 
			
		||||
    HttpResponseBadRequest,
 | 
			
		||||
    HttpResponseForbidden,
 | 
			
		||||
    HttpResponseNotFound,
 | 
			
		||||
    HttpResponseServerError,
 | 
			
		||||
)
 | 
			
		||||
from django.template.response import TemplateResponse
 | 
			
		||||
from django.views.generic import TemplateView
 | 
			
		||||
 | 
			
		||||
@ -10,54 +13,53 @@ from django.views.generic import TemplateView
 | 
			
		||||
class BadRequestTemplateResponse(TemplateResponse, HttpResponseBadRequest):
 | 
			
		||||
    """Combine Template response with Http Code 400"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ForbiddenTemplateResponse(TemplateResponse, HttpResponseForbidden):
 | 
			
		||||
    """Combine Template response with Http Code 403"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotFoundTemplateResponse(TemplateResponse, HttpResponseNotFound):
 | 
			
		||||
    """Combine Template response with Http Code 404"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ServerErrorTemplateResponse(TemplateResponse, HttpResponseServerError):
 | 
			
		||||
    """Combine Template response with Http Code 500"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BadRequestView(TemplateView):
 | 
			
		||||
    """Show Bad Request message"""
 | 
			
		||||
 | 
			
		||||
    response_class = BadRequestTemplateResponse
 | 
			
		||||
    template_name = 'error/400.html'
 | 
			
		||||
    template_name = "error/400.html"
 | 
			
		||||
 | 
			
		||||
    extra_context = {"is_login": True}
 | 
			
		||||
 | 
			
		||||
    extra_context = {
 | 
			
		||||
        'is_login': True
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
class ForbiddenView(TemplateView):
 | 
			
		||||
    """Show Forbidden message"""
 | 
			
		||||
 | 
			
		||||
    response_class = ForbiddenTemplateResponse
 | 
			
		||||
    template_name = 'error/403.html'
 | 
			
		||||
    template_name = "error/403.html"
 | 
			
		||||
 | 
			
		||||
    extra_context = {"is_login": True}
 | 
			
		||||
 | 
			
		||||
    extra_context = {
 | 
			
		||||
        'is_login': True
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
class NotFoundView(TemplateView):
 | 
			
		||||
    """Show Not Found message"""
 | 
			
		||||
 | 
			
		||||
    response_class = NotFoundTemplateResponse
 | 
			
		||||
    template_name = 'error/404.html'
 | 
			
		||||
    template_name = "error/404.html"
 | 
			
		||||
 | 
			
		||||
    extra_context = {"is_login": True}
 | 
			
		||||
 | 
			
		||||
    extra_context = {
 | 
			
		||||
        'is_login': True
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
class ServerErrorView(TemplateView):
 | 
			
		||||
    """Show Server Error message"""
 | 
			
		||||
 | 
			
		||||
    response_class = ServerErrorTemplateResponse
 | 
			
		||||
    template_name = 'error/500.html'
 | 
			
		||||
    template_name = "error/500.html"
 | 
			
		||||
 | 
			
		||||
    extra_context = {
 | 
			
		||||
        'is_login': True
 | 
			
		||||
    }
 | 
			
		||||
    extra_context = {"is_login": True}
 | 
			
		||||
 | 
			
		||||
    # pylint: disable=useless-super-delegation
 | 
			
		||||
    def dispatch(self, *args, **kwargs):
 | 
			
		||||
 | 
			
		||||
@ -11,13 +11,15 @@ class OverviewView(LoginRequiredMixin, TemplateView):
 | 
			
		||||
    """Overview for logged in user, incase user opens passbook directly
 | 
			
		||||
    and is not being forwarded"""
 | 
			
		||||
 | 
			
		||||
    template_name = 'overview/index.html'
 | 
			
		||||
    template_name = "overview/index.html"
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['applications'] = []
 | 
			
		||||
        kwargs["applications"] = []
 | 
			
		||||
        for application in Application.objects.all():
 | 
			
		||||
            engine = PolicyEngine(application.policies.all(), self.request.user, self.request)
 | 
			
		||||
            engine = PolicyEngine(
 | 
			
		||||
                application.policies.all(), self.request.user, self.request
 | 
			
		||||
            )
 | 
			
		||||
            engine.build()
 | 
			
		||||
            if engine.passing:
 | 
			
		||||
                kwargs['applications'].append(application)
 | 
			
		||||
                kwargs["applications"].append(application)
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
@ -17,11 +17,11 @@ from passbook.lib.config import CONFIG
 | 
			
		||||
class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
    """Update User settings"""
 | 
			
		||||
 | 
			
		||||
    template_name = 'user/settings.html'
 | 
			
		||||
    template_name = "user/settings.html"
 | 
			
		||||
    form_class = UserDetailForm
 | 
			
		||||
 | 
			
		||||
    success_message = _('Successfully updated user.')
 | 
			
		||||
    success_url = reverse_lazy('passbook_core:user-settings')
 | 
			
		||||
    success_message = _("Successfully updated user.")
 | 
			
		||||
    success_url = reverse_lazy("passbook_core:user-settings")
 | 
			
		||||
 | 
			
		||||
    def get_object(self):
 | 
			
		||||
        return self.request.user
 | 
			
		||||
@ -30,44 +30,44 @@ class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
 | 
			
		||||
class UserDeleteView(LoginRequiredMixin, DeleteView):
 | 
			
		||||
    """Delete user account"""
 | 
			
		||||
 | 
			
		||||
    template_name = 'generic/delete.html'
 | 
			
		||||
    template_name = "generic/delete.html"
 | 
			
		||||
 | 
			
		||||
    def get_object(self):
 | 
			
		||||
        return self.request.user
 | 
			
		||||
 | 
			
		||||
    def get_success_url(self):
 | 
			
		||||
        messages.success(self.request, _('Successfully deleted user.'))
 | 
			
		||||
        messages.success(self.request, _("Successfully deleted user."))
 | 
			
		||||
        logout(self.request)
 | 
			
		||||
        return reverse('passbook_core:auth-login')
 | 
			
		||||
        return reverse("passbook_core:auth-login")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserChangePasswordView(LoginRequiredMixin, FormView):
 | 
			
		||||
    """View for users to update their password"""
 | 
			
		||||
 | 
			
		||||
    form_class = PasswordChangeForm
 | 
			
		||||
    template_name = 'login/form_with_user.html'
 | 
			
		||||
    template_name = "login/form_with_user.html"
 | 
			
		||||
 | 
			
		||||
    def form_valid(self, form: PasswordChangeForm):
 | 
			
		||||
        try:
 | 
			
		||||
            # user.set_password checks against Policies so we don't need to manually do it here
 | 
			
		||||
            self.request.user.set_password(form.cleaned_data.get('password'))
 | 
			
		||||
            self.request.user.set_password(form.cleaned_data.get("password"))
 | 
			
		||||
            self.request.user.save()
 | 
			
		||||
            update_session_auth_hash(self.request, self.request.user)
 | 
			
		||||
            messages.success(self.request, _('Successfully changed password'))
 | 
			
		||||
            messages.success(self.request, _("Successfully changed password"))
 | 
			
		||||
        except PasswordPolicyInvalid as exc:
 | 
			
		||||
            # Manually inject error into form
 | 
			
		||||
            # pylint: disable=protected-access
 | 
			
		||||
            errors = form._errors.setdefault("password_repeat", ErrorList(''))
 | 
			
		||||
            errors = form._errors.setdefault("password_repeat", ErrorList(""))
 | 
			
		||||
            # pylint: disable=protected-access
 | 
			
		||||
            errors = form._errors.setdefault("password", ErrorList())
 | 
			
		||||
            for error in exc.messages:
 | 
			
		||||
                errors.append(error)
 | 
			
		||||
            return self.form_invalid(form)
 | 
			
		||||
        return redirect('passbook_core:overview')
 | 
			
		||||
        return redirect("passbook_core:overview")
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['config'] = CONFIG.y('passbook')
 | 
			
		||||
        kwargs['is_login'] = True
 | 
			
		||||
        kwargs['title'] = _('Change Password')
 | 
			
		||||
        kwargs['primary_action'] = _('Change')
 | 
			
		||||
        kwargs["config"] = CONFIG.y("passbook")
 | 
			
		||||
        kwargs["is_login"] = True
 | 
			
		||||
        kwargs["title"] = _("Change Password")
 | 
			
		||||
        kwargs["primary_action"] = _("Change")
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,8 @@ from django.views.generic import TemplateView
 | 
			
		||||
class LoadingView(TemplateView):
 | 
			
		||||
    """View showing a loading template, and forwarding to real view using html forwarding."""
 | 
			
		||||
 | 
			
		||||
    template_name = 'login/loading.html'
 | 
			
		||||
    title = _('Loading')
 | 
			
		||||
    template_name = "login/loading.html"
 | 
			
		||||
    title = _("Loading")
 | 
			
		||||
    target_url = None
 | 
			
		||||
 | 
			
		||||
    def get_url(self):
 | 
			
		||||
@ -15,18 +15,19 @@ class LoadingView(TemplateView):
 | 
			
		||||
        return self.target_url
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['is_login'] = True
 | 
			
		||||
        kwargs['title'] = self.title
 | 
			
		||||
        kwargs['target_url'] = self.get_url()
 | 
			
		||||
        kwargs["is_login"] = True
 | 
			
		||||
        kwargs["title"] = self.title
 | 
			
		||||
        kwargs["target_url"] = self.get_url()
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PermissionDeniedView(TemplateView):
 | 
			
		||||
    """Generic Permission denied view"""
 | 
			
		||||
 | 
			
		||||
    template_name = 'login/denied.html'
 | 
			
		||||
    title = _('Permission denied.')
 | 
			
		||||
    template_name = "login/denied.html"
 | 
			
		||||
    title = _("Permission denied.")
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['is_login'] = True
 | 
			
		||||
        kwargs['title'] = self.title
 | 
			
		||||
        kwargs["is_login"] = True
 | 
			
		||||
        kwargs["title"] = self.title
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
@ -17,16 +17,16 @@ class AuthenticationFactor(TemplateView):
 | 
			
		||||
    authenticator: AuthenticationView
 | 
			
		||||
    pending_user: User
 | 
			
		||||
    request: HttpRequest = None
 | 
			
		||||
    template_name = 'login/form_with_user.html'
 | 
			
		||||
    template_name = "login/form_with_user.html"
 | 
			
		||||
 | 
			
		||||
    def __init__(self, authenticator: AuthenticationView):
 | 
			
		||||
        self.authenticator = authenticator
 | 
			
		||||
        self.pending_user = None
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        kwargs['config'] = CONFIG.y('passbook')
 | 
			
		||||
        kwargs['is_login'] = True
 | 
			
		||||
        kwargs['title'] = _('Log in to your account')
 | 
			
		||||
        kwargs['primary_action'] = _('Log in')
 | 
			
		||||
        kwargs['user'] = self.pending_user
 | 
			
		||||
        kwargs["config"] = CONFIG.y("passbook")
 | 
			
		||||
        kwargs["is_login"] = True
 | 
			
		||||
        kwargs["title"] = _("Log in to your account")
 | 
			
		||||
        kwargs["primary_action"] = _("Log in")
 | 
			
		||||
        kwargs["user"] = self.pending_user
 | 
			
		||||
        return super().get_context_data(**kwargs)
 | 
			
		||||
 | 
			
		||||
@ -2,4 +2,4 @@
 | 
			
		||||
 | 
			
		||||
from passbook.lib.admin import admin_autoregister
 | 
			
		||||
 | 
			
		||||
admin_autoregister('passbook_factors_captcha')
 | 
			
		||||
admin_autoregister("passbook_factors_captcha")
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user