Compare commits
	
		
			83 Commits
		
	
	
		
			version/20
			...
			version/20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 276d8fe5cf | |||
| 92ce5f0931 | |||
| 7fea20375f | |||
| d4d4034d2c | |||
| f0db408699 | |||
| 5e200655d9 | |||
| d5d1f2a645 | |||
| cc5cc43baa | |||
| e512f085db | |||
| f323c01bd8 | |||
| f56cacb406 | |||
| eaecd31e9f | |||
| 36989d82e1 | |||
| 50777d9022 | |||
| a15571bd3e | |||
| 26fd66d831 | |||
| 0be873025a | |||
| 28ada49910 | |||
| 4fc8e61f8c | |||
| 7d26ea1a9c | |||
| 3a58dc62e1 | |||
| 71fe7bc827 | |||
| 933336c38b | |||
| 371feb9a31 | |||
| 95a2fd3c9e | |||
| 17cb76c334 | |||
| 88f0dfc8cc | |||
| f82aada23b | |||
| ecaee92634 | |||
| 89252ec47b | |||
| f0f25ab291 | |||
| e4d0fec15a | |||
| 6b10baf086 | |||
| f148b5d341 | |||
| 1471ff8940 | |||
| d9a6ec2ac0 | |||
| 5745ffa0a8 | |||
| b26202db35 | |||
| 6318577a51 | |||
| 6a2cd45847 | |||
| ef5cea2c01 | |||
| 69f4d54bae | |||
| b1eec5a7d2 | |||
| 1b8271d767 | |||
| 3e9f5ec5ef | |||
| 63f57b6a77 | |||
| a016f99450 | |||
| adc18b2991 | |||
| e37a326b95 | |||
| 048467e97d | |||
| cc2cd6919f | |||
| 0c6e781e5b | |||
| 7294d8fca5 | |||
| 16ec5680b4 | |||
| 87920fb1d7 | |||
| 523b96a6d2 | |||
| 45731d8069 | |||
| e872371970 | |||
| 08e8cf850a | |||
| b1ed2154ac | |||
| 7ef2aa3eb9 | |||
| 160139813d | |||
| 582ad92c76 | |||
| f61736e3d1 | |||
| eb02c96281 | |||
| 8619552920 | |||
| 6237352e25 | |||
| 2d8b4f543b | |||
| 8542dc10ab | |||
| c55b63337c | |||
| 12ddee3bb6 | |||
| dc41d0af27 | |||
| 3323b50036 | |||
| 8acb15a7fd | |||
| f601e04b38 | |||
| f50529cb5b | |||
| 3f1b6f9ed4 | |||
| f1ab0f4314 | |||
| 4d1129f385 | |||
| 03ac9c6e16 | |||
| c0839924f1 | |||
| 91e3aa760a | |||
| 5c0681d57b | 
@ -1,5 +1,5 @@
 | 
			
		||||
[bumpversion]
 | 
			
		||||
current_version = 2021.8.2
 | 
			
		||||
current_version = 2021.8.4
 | 
			
		||||
tag = True
 | 
			
		||||
commit = True
 | 
			
		||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
 | 
			
		||||
@ -23,7 +23,7 @@ values =
 | 
			
		||||
 | 
			
		||||
[bumpversion:file:schema.yml]
 | 
			
		||||
 | 
			
		||||
[bumpversion:file:.github/workflows/release.yml]
 | 
			
		||||
[bumpversion:file:.github/workflows/release-publish.yml]
 | 
			
		||||
 | 
			
		||||
[bumpversion:file:authentik/__init__.py]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										240
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,240 @@
 | 
			
		||||
name: authentik-ci-main
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - website
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  POSTGRES_DB: authentik
 | 
			
		||||
  POSTGRES_USER: authentik
 | 
			
		||||
  POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  lint-pylint:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.9'
 | 
			
		||||
      - name: prepare
 | 
			
		||||
        run: scripts/ci_prepare.sh
 | 
			
		||||
      - name: run pylint
 | 
			
		||||
        run: pipenv run pylint authentik tests lifecycle
 | 
			
		||||
  lint-black:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.9'
 | 
			
		||||
      - name: prepare
 | 
			
		||||
        run: scripts/ci_prepare.sh
 | 
			
		||||
      - name: run black
 | 
			
		||||
        run: pipenv run black --check authentik tests lifecycle
 | 
			
		||||
  lint-isort:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.9'
 | 
			
		||||
      - name: prepare
 | 
			
		||||
        run: scripts/ci_prepare.sh
 | 
			
		||||
      - name: run isort
 | 
			
		||||
        run: pipenv run isort --check authentik tests lifecycle
 | 
			
		||||
  lint-bandit:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.9'
 | 
			
		||||
      - name: prepare
 | 
			
		||||
        run: scripts/ci_prepare.sh
 | 
			
		||||
      - name: run bandit
 | 
			
		||||
        run: pipenv run bandit -r authentik tests lifecycle
 | 
			
		||||
  lint-pyright:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.9'
 | 
			
		||||
      - uses: actions/setup-node@v2
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: '16'
 | 
			
		||||
      - name: prepare
 | 
			
		||||
        run: |
 | 
			
		||||
          scripts/ci_prepare.sh
 | 
			
		||||
          npm install -g pyright@1.1.136
 | 
			
		||||
      - name: run bandit
 | 
			
		||||
        run: pipenv run pyright e2e lifecycle
 | 
			
		||||
  test-migrations:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.9'
 | 
			
		||||
      - name: prepare
 | 
			
		||||
        run: scripts/ci_prepare.sh
 | 
			
		||||
      - name: run migrations
 | 
			
		||||
        run: pipenv run python -m lifecycle.migrate
 | 
			
		||||
  test-migrations-from-stable:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.9'
 | 
			
		||||
      - name: checkout stable
 | 
			
		||||
        run: |
 | 
			
		||||
          # Copy current, latest config to local
 | 
			
		||||
          cp authentik/lib/default.yml local.env.yml
 | 
			
		||||
          git checkout $(git describe --abbrev=0 --match 'version/*')
 | 
			
		||||
      - name: prepare
 | 
			
		||||
        run: scripts/ci_prepare.sh
 | 
			
		||||
      - name: run migrations to stable
 | 
			
		||||
        run: pipenv run python -m lifecycle.migrate
 | 
			
		||||
      - name: checkout current code
 | 
			
		||||
        run: |
 | 
			
		||||
          set -x
 | 
			
		||||
          git checkout $GITHUB_REF
 | 
			
		||||
          pipenv sync --dev
 | 
			
		||||
      - name: migrate to latest
 | 
			
		||||
        run: pipenv run python -m lifecycle.migrate
 | 
			
		||||
  test-unittest:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.9'
 | 
			
		||||
      - name: prepare
 | 
			
		||||
        run: scripts/ci_prepare.sh
 | 
			
		||||
      - uses: testspace-com/setup-testspace@v1
 | 
			
		||||
        with:
 | 
			
		||||
          domain: ${{github.repository_owner}}
 | 
			
		||||
      - name: run unittest
 | 
			
		||||
        run: |
 | 
			
		||||
          pipenv run make test
 | 
			
		||||
          pipenv run coverage xml
 | 
			
		||||
      - name: run testspace
 | 
			
		||||
        if: ${{ always() }}
 | 
			
		||||
        run: |
 | 
			
		||||
          testspace unittest.xml ?add
 | 
			
		||||
      - if: ${{ always() }}
 | 
			
		||||
        uses: codecov/codecov-action@v2
 | 
			
		||||
  test-integration:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.9'
 | 
			
		||||
      - name: prepare
 | 
			
		||||
        run: scripts/ci_prepare.sh
 | 
			
		||||
      - uses: testspace-com/setup-testspace@v1
 | 
			
		||||
        with:
 | 
			
		||||
          domain: ${{github.repository_owner}}
 | 
			
		||||
      - name: prepare k3d
 | 
			
		||||
        run: |
 | 
			
		||||
          wget -q -O - https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash
 | 
			
		||||
      - name: run integration
 | 
			
		||||
        run: |
 | 
			
		||||
          pipenv run make test-integration
 | 
			
		||||
          pipenv run coverage xml
 | 
			
		||||
      - name: run testspace
 | 
			
		||||
        if: ${{ always() }}
 | 
			
		||||
        run: |
 | 
			
		||||
          testspace unittest.xml ?add
 | 
			
		||||
      - if: ${{ always() }}
 | 
			
		||||
        uses: codecov/codecov-action@v2
 | 
			
		||||
  test-e2e:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-python@v2
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.9'
 | 
			
		||||
      - uses: actions/setup-node@v2
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: '16'
 | 
			
		||||
          cache: 'npm'
 | 
			
		||||
          cache-dependency-path: web/package-lock.json
 | 
			
		||||
      - uses: testspace-com/setup-testspace@v1
 | 
			
		||||
        with:
 | 
			
		||||
          domain: ${{github.repository_owner}}
 | 
			
		||||
      - name: prepare
 | 
			
		||||
        run: |
 | 
			
		||||
          scripts/ci_prepare.sh
 | 
			
		||||
          docker-compose -f tests/e2e/ci.docker-compose.yml up -d
 | 
			
		||||
      - name: prepare web ui
 | 
			
		||||
        run: |
 | 
			
		||||
          cd web
 | 
			
		||||
          npm i
 | 
			
		||||
          npm run build
 | 
			
		||||
      - name: run e2e
 | 
			
		||||
        run: |
 | 
			
		||||
          pipenv run make test-e2e
 | 
			
		||||
          pipenv run coverage xml
 | 
			
		||||
      - name: run testspace
 | 
			
		||||
        if: ${{ always() }}
 | 
			
		||||
        run: |
 | 
			
		||||
          testspace unittest.xml ?add
 | 
			
		||||
      - if: ${{ always() }}
 | 
			
		||||
        uses: codecov/codecov-action@v2
 | 
			
		||||
  report:
 | 
			
		||||
    if: ${{ always() }}
 | 
			
		||||
    needs:
 | 
			
		||||
      - test-unittest
 | 
			
		||||
      - test-integration
 | 
			
		||||
      - test-e2e
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: testspace-com/setup-testspace@v1
 | 
			
		||||
        with:
 | 
			
		||||
          domain: ${{github.repository_owner}}
 | 
			
		||||
      - name: finish testspace
 | 
			
		||||
        run: |
 | 
			
		||||
          testspace ?finish
 | 
			
		||||
  build:
 | 
			
		||||
    needs:
 | 
			
		||||
      - lint-pylint
 | 
			
		||||
      - lint-black
 | 
			
		||||
      - lint-isort
 | 
			
		||||
      - lint-bandit
 | 
			
		||||
      - lint-pyright
 | 
			
		||||
      - test-migrations
 | 
			
		||||
      - test-migrations-from-stable
 | 
			
		||||
      - test-unittest
 | 
			
		||||
      - test-integration
 | 
			
		||||
      - test-e2e
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v1
 | 
			
		||||
      - name: prepare variables
 | 
			
		||||
        id: ev
 | 
			
		||||
        run: |
 | 
			
		||||
          python ./scripts/gh_do_set_branch.py
 | 
			
		||||
      - name: Login to Container Registry
 | 
			
		||||
        uses: docker/login-action@v1
 | 
			
		||||
        if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
 | 
			
		||||
        with:
 | 
			
		||||
          registry: beryju.org
 | 
			
		||||
          username: ${{ secrets.HARBOR_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.HARBOR_PASSWORD }}
 | 
			
		||||
      - name: Building Docker Image
 | 
			
		||||
        uses: docker/build-push-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          push: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
 | 
			
		||||
          tags: |
 | 
			
		||||
            beryju.org/authentik/server:gh-${{ steps.ev.outputs.branchName }}
 | 
			
		||||
            beryju.org/authentik/server:gh-${{ steps.ev.outputs.branchName }}-${{ steps.ev.outputs.timestamp }}
 | 
			
		||||
          build-args: |
 | 
			
		||||
            GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
 | 
			
		||||
							
								
								
									
										66
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								.github/workflows/ci-outpost.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
			
		||||
name: authentik-ci-outpost
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  lint-golint:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-go@v2
 | 
			
		||||
        with:
 | 
			
		||||
          go-version: '^1.16.3'
 | 
			
		||||
      - name: Generate API
 | 
			
		||||
        run: |
 | 
			
		||||
          make gen-outpost
 | 
			
		||||
      - name: Run linter
 | 
			
		||||
        run: |
 | 
			
		||||
          # Create folder structure for go embeds
 | 
			
		||||
          mkdir -p web/dist
 | 
			
		||||
          mkdir -p website/help
 | 
			
		||||
          touch web/dist/test website/help/test
 | 
			
		||||
          docker run \
 | 
			
		||||
            --rm \
 | 
			
		||||
            -v $(pwd):/app \
 | 
			
		||||
            -w /app \
 | 
			
		||||
            golangci/golangci-lint:v1.39.0 \
 | 
			
		||||
            golangci-lint run -v --timeout 200s
 | 
			
		||||
  build:
 | 
			
		||||
    needs:
 | 
			
		||||
      - lint-golint
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        type:
 | 
			
		||||
          - proxy
 | 
			
		||||
          - ldap
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Set up QEMU
 | 
			
		||||
        uses: docker/setup-qemu-action@v1.2.0
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v1
 | 
			
		||||
      - name: prepare variables
 | 
			
		||||
        id: ev
 | 
			
		||||
        run: |
 | 
			
		||||
          python ./scripts/gh_do_set_branch.py
 | 
			
		||||
      - name: Login to Container Registry
 | 
			
		||||
        uses: docker/login-action@v1
 | 
			
		||||
        if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
 | 
			
		||||
        with:
 | 
			
		||||
          registry: beryju.org
 | 
			
		||||
          username: ${{ secrets.HARBOR_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.HARBOR_PASSWORD }}
 | 
			
		||||
      - name: Building Docker Image
 | 
			
		||||
        uses: docker/build-push-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          push: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
 | 
			
		||||
          tags: |
 | 
			
		||||
            beryju.org/authentik/outpost-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchName }}
 | 
			
		||||
            beryju.org/authentik/outpost-${{ matrix.type }}:gh-${{ steps.ev.outputs.branchName }}-${{ steps.ev.outputs.timestamp }}
 | 
			
		||||
            beryju.org/authentik/outpost-${{ matrix.type }}:gh-${{ steps.ev.outputs.sha }}
 | 
			
		||||
          file: ${{ matrix.type }}.Dockerfile
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
          build-args: |
 | 
			
		||||
            GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
 | 
			
		||||
							
								
								
									
										82
									
								
								.github/workflows/ci-web.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								.github/workflows/ci-web.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
			
		||||
name: authentik-ci-web
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  lint-eslint:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-node@v2
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: '16'
 | 
			
		||||
          cache: 'npm'
 | 
			
		||||
          cache-dependency-path: web/package-lock.json
 | 
			
		||||
      - run: |
 | 
			
		||||
          cd web
 | 
			
		||||
          npm install
 | 
			
		||||
      - name: Generate API
 | 
			
		||||
        run: make gen-web
 | 
			
		||||
      - name: Eslint
 | 
			
		||||
        run: |
 | 
			
		||||
          cd web
 | 
			
		||||
          npm run lint
 | 
			
		||||
  lint-prettier:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-node@v2
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: '16'
 | 
			
		||||
          cache: 'npm'
 | 
			
		||||
          cache-dependency-path: web/package-lock.json
 | 
			
		||||
      - run: |
 | 
			
		||||
          cd web
 | 
			
		||||
          npm install
 | 
			
		||||
      - name: Generate API
 | 
			
		||||
        run: make gen-web
 | 
			
		||||
      - name: prettier
 | 
			
		||||
        run: |
 | 
			
		||||
          cd web
 | 
			
		||||
          npm run prettier-check
 | 
			
		||||
  lint-lit-analyse:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-node@v2
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: '16'
 | 
			
		||||
          cache: 'npm'
 | 
			
		||||
          cache-dependency-path: web/package-lock.json
 | 
			
		||||
      - run: |
 | 
			
		||||
          cd web
 | 
			
		||||
          npm install
 | 
			
		||||
      - name: Generate API
 | 
			
		||||
        run: make gen-web
 | 
			
		||||
      - name: prettier
 | 
			
		||||
        run: |
 | 
			
		||||
          cd web
 | 
			
		||||
          npm run lit-analyse
 | 
			
		||||
  build:
 | 
			
		||||
    needs:
 | 
			
		||||
      - lint-eslint
 | 
			
		||||
      - lint-prettier
 | 
			
		||||
      - lint-lit-analyse
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/setup-node@v2
 | 
			
		||||
        with:
 | 
			
		||||
          node-version: '16'
 | 
			
		||||
          cache: 'npm'
 | 
			
		||||
          cache-dependency-path: web/package-lock.json
 | 
			
		||||
      - run: |
 | 
			
		||||
          cd web
 | 
			
		||||
          npm install
 | 
			
		||||
      - name: Generate API
 | 
			
		||||
        run: make gen-web
 | 
			
		||||
      - name: build
 | 
			
		||||
        run: |
 | 
			
		||||
          cd web
 | 
			
		||||
          npm run build
 | 
			
		||||
@ -33,14 +33,14 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          push: ${{ github.event_name == 'release' }}
 | 
			
		||||
          tags: |
 | 
			
		||||
            beryju/authentik:2021.8.2,
 | 
			
		||||
            beryju/authentik:2021.8.4,
 | 
			
		||||
            beryju/authentik:latest,
 | 
			
		||||
            ghcr.io/goauthentik/server:2021.8.2,
 | 
			
		||||
            ghcr.io/goauthentik/server:2021.8.4,
 | 
			
		||||
            ghcr.io/goauthentik/server:latest
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
          context: .
 | 
			
		||||
      - name: Building Docker Image (stable)
 | 
			
		||||
        if: ${{ github.event_name == 'release' && !contains('2021.8.2', 'rc') }}
 | 
			
		||||
        if: ${{ github.event_name == 'release' && !contains('2021.8.4', 'rc') }}
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull beryju/authentik:latest
 | 
			
		||||
          docker tag beryju/authentik:latest beryju/authentik:stable
 | 
			
		||||
@ -75,14 +75,14 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          push: ${{ github.event_name == 'release' }}
 | 
			
		||||
          tags: |
 | 
			
		||||
            beryju/authentik-proxy:2021.8.2,
 | 
			
		||||
            beryju/authentik-proxy:2021.8.4,
 | 
			
		||||
            beryju/authentik-proxy:latest,
 | 
			
		||||
            ghcr.io/goauthentik/proxy:2021.8.2,
 | 
			
		||||
            ghcr.io/goauthentik/proxy:2021.8.4,
 | 
			
		||||
            ghcr.io/goauthentik/proxy:latest
 | 
			
		||||
          file: proxy.Dockerfile
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
      - name: Building Docker Image (stable)
 | 
			
		||||
        if: ${{ github.event_name == 'release' && !contains('2021.8.2', 'rc') }}
 | 
			
		||||
        if: ${{ github.event_name == 'release' && !contains('2021.8.4', 'rc') }}
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull beryju/authentik-proxy:latest
 | 
			
		||||
          docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
 | 
			
		||||
@ -117,14 +117,14 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          push: ${{ github.event_name == 'release' }}
 | 
			
		||||
          tags: |
 | 
			
		||||
            beryju/authentik-ldap:2021.8.2,
 | 
			
		||||
            beryju/authentik-ldap:2021.8.4,
 | 
			
		||||
            beryju/authentik-ldap:latest,
 | 
			
		||||
            ghcr.io/goauthentik/ldap:2021.8.2,
 | 
			
		||||
            ghcr.io/goauthentik/ldap:2021.8.4,
 | 
			
		||||
            ghcr.io/goauthentik/ldap:latest
 | 
			
		||||
          file: ldap.Dockerfile
 | 
			
		||||
          platforms: linux/amd64,linux/arm64
 | 
			
		||||
      - name: Building Docker Image (stable)
 | 
			
		||||
        if: ${{ github.event_name == 'release' && !contains('2021.8.2', 'rc') }}
 | 
			
		||||
        if: ${{ github.event_name == 'release' && !contains('2021.8.4', 'rc') }}
 | 
			
		||||
        run: |
 | 
			
		||||
          docker pull beryju/authentik-ldap:latest
 | 
			
		||||
          docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
 | 
			
		||||
@ -175,7 +175,7 @@ jobs:
 | 
			
		||||
          SENTRY_PROJECT: authentik
 | 
			
		||||
          SENTRY_URL: https://sentry.beryju.org
 | 
			
		||||
        with:
 | 
			
		||||
          version: authentik@2021.8.2
 | 
			
		||||
          version: authentik@2021.8.4
 | 
			
		||||
          environment: beryjuorg-prod
 | 
			
		||||
          sourcemaps: './web/dist'
 | 
			
		||||
          url_prefix: '~/static/dist'
 | 
			
		||||
							
								
								
									
										154
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										154
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							@ -122,19 +122,19 @@
 | 
			
		||||
        },
 | 
			
		||||
        "boto3": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4dc7e346e92c01e8a997daa58a4c990151841d2d2962067325d963f665c7287a",
 | 
			
		||||
                "sha256:79b7e6e0167def749352968ed6eb96954d9e2dd1dca8f297f122414753ce73a3"
 | 
			
		||||
                "sha256:5116e9bdec19adcc5531a9b7b535be77d5314eef092aaf7033ace48a9be65036",
 | 
			
		||||
                "sha256:658ddf4ba552f654fd4d48335fa95ff4e3e1a4e82f90021a1a1d3de4a5428ba4"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.18.29"
 | 
			
		||||
            "version": "==1.18.34"
 | 
			
		||||
        },
 | 
			
		||||
        "botocore": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1f16998b4f5a88e6844196feee7fa5eef6b36034d377f9845c7df12b8803b3be",
 | 
			
		||||
                "sha256:fec924f63b40bd29b522fa109ecbc45f16eedcbeb22b68c6c79773c22a552b16"
 | 
			
		||||
                "sha256:1b4999fb0e1a4c050c4d9118ebdaac8d83761ef32c3c0f13a25f9204045998fe",
 | 
			
		||||
                "sha256:ec2cdf1c8ed64a7f392f352125d248c76103fa9d137b275b7c76836776cedf56"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.6'",
 | 
			
		||||
            "version": "==1.21.29"
 | 
			
		||||
            "version": "==1.21.34"
 | 
			
		||||
        },
 | 
			
		||||
        "cachetools": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -359,11 +359,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:7f92413529aa0e291f3be78ab19be31aefb1e1c9a52cd59e130f505f27a51f13",
 | 
			
		||||
                "sha256:f27f8544c9d4c383bbe007c57e3235918e258364577373d4920e9162837be022"
 | 
			
		||||
                "sha256:95b318319d6997bac3595517101ad9cc83fe5672ac498ba48d1a410f47afecd2",
 | 
			
		||||
                "sha256:e93c93565005b37ddebf2396b4dc4b6913c1838baa82efdfb79acedd5816c240"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==3.2.6"
 | 
			
		||||
            "version": "==3.2.7"
 | 
			
		||||
        },
 | 
			
		||||
        "django-dbbackup": {
 | 
			
		||||
            "git": "https://github.com/django-dbbackup/django-dbbackup.git",
 | 
			
		||||
@ -443,19 +443,19 @@
 | 
			
		||||
        },
 | 
			
		||||
        "docker": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3e8bc47534e0ca9331d72c32f2881bb13b93ded0bcdeab3c833fb7cf61c0a9a5",
 | 
			
		||||
                "sha256:fc961d622160e8021c10d1bcabc388c57d55fb1f917175afbe24af442e6879bd"
 | 
			
		||||
                "sha256:5aafaec0d2a1de0e32010b43b5eac9f6f851c9db99a46ad32b8e44eeeb55616d",
 | 
			
		||||
                "sha256:b88eef725b33c0ed59c67506631bbb09b480b7ca5a739bbbb948b446443fe914"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==5.0.0"
 | 
			
		||||
            "version": "==5.0.1"
 | 
			
		||||
        },
 | 
			
		||||
        "drf-spectacular": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:5b1c27de127c86564be5a967a6fa195cfe161b552d98364282ae9e6ed3d75a85",
 | 
			
		||||
                "sha256:8588706c27f44adfbb3405bae9ef9cd6506f4b59d4cbd66c59780dce035602d9"
 | 
			
		||||
                "sha256:98681add6671db9e6dba5f0d3dcf8aab5950cbb978497390507356e593bf082f",
 | 
			
		||||
                "sha256:a430bab0f4ecfc90786b7b63bbee3f9a56094201fbed9bdfbf952e99e6469104"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.18.0"
 | 
			
		||||
            "version": "==0.18.1"
 | 
			
		||||
        },
 | 
			
		||||
        "duo-client": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -490,11 +490,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "google-auth": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:c012c8be7c442c8309ca8fa0876fef33f5fd977c467be1e1c1c2f721e8ebd73c",
 | 
			
		||||
                "sha256:ea1af050b3e06eb73e4470f704d23007307bc0e87c13e015f6b90460f1407bd3"
 | 
			
		||||
                "sha256:104475dc4d57bbae49017aea16fffbb763204fa2d6a70f1f3cc79962c1a383a4",
 | 
			
		||||
                "sha256:cde472372e030e1e0bc64dac00fb53e6c095d7ab641f4281e2c995e85e205d8b"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.6'",
 | 
			
		||||
            "version": "==2.0.1"
 | 
			
		||||
            "version": "==2.0.2"
 | 
			
		||||
        },
 | 
			
		||||
        "gunicorn": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1151,11 +1151,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "typing-extensions": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497",
 | 
			
		||||
                "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342",
 | 
			
		||||
                "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"
 | 
			
		||||
                "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
 | 
			
		||||
                "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
 | 
			
		||||
                "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.10.0.0"
 | 
			
		||||
            "version": "==3.10.0.2"
 | 
			
		||||
        },
 | 
			
		||||
        "ua-parser": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1420,11 +1420,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "astroid": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:b6c2d75cd7c2982d09e7d41d70213e863b3ba34d3bd4014e08f167cee966e99e",
 | 
			
		||||
                "sha256:ecc50f9b3803ebf8ea19aa2c6df5622d8a5c31456a53c741d3be044d96ff0948"
 | 
			
		||||
                "sha256:3b680ce0419b8a771aba6190139a3998d14b413852506d99aff8dc2bf65ee67c",
 | 
			
		||||
                "sha256:dc1e8b28427d6bbef6b8842b18765ab58f558c42bb80540bd7648c98412af25e"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version ~= '3.6'",
 | 
			
		||||
            "version": "==2.7.2"
 | 
			
		||||
            "version": "==2.7.3"
 | 
			
		||||
        },
 | 
			
		||||
        "attrs": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1652,19 +1652,19 @@
 | 
			
		||||
        },
 | 
			
		||||
        "platformdirs": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4666d822218db6a262bdfdc9c39d21f23b4cfdb08af331a81e92751daf6c866c",
 | 
			
		||||
                "sha256:632daad3ab546bd8e6af0537d09805cec458dce201bccfe23012df73332e181e"
 | 
			
		||||
                "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f",
 | 
			
		||||
                "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.6'",
 | 
			
		||||
            "version": "==2.2.0"
 | 
			
		||||
            "version": "==2.3.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pluggy": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
 | 
			
		||||
                "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
 | 
			
		||||
                "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
 | 
			
		||||
                "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==0.13.1"
 | 
			
		||||
            "markers": "python_version >= '3.6'",
 | 
			
		||||
            "version": "==1.0.0"
 | 
			
		||||
        },
 | 
			
		||||
        "py": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1707,11 +1707,11 @@
 | 
			
		||||
        },
 | 
			
		||||
        "pytest": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
 | 
			
		||||
                "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
 | 
			
		||||
                "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
 | 
			
		||||
                "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==6.2.4"
 | 
			
		||||
            "version": "==6.2.5"
 | 
			
		||||
        },
 | 
			
		||||
        "pytest-django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
@ -1758,49 +1758,49 @@
 | 
			
		||||
        },
 | 
			
		||||
        "regex": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:03840a07a402576b8e3a6261f17eb88abd653ad4e18ec46ef10c9a63f8c99ebd",
 | 
			
		||||
                "sha256:06ba444bbf7ede3890a912bd4904bb65bf0da8f0d8808b90545481362c978642",
 | 
			
		||||
                "sha256:1f9974826aeeda32a76648fc677e3125ade379869a84aa964b683984a2dea9f1",
 | 
			
		||||
                "sha256:330836ad89ff0be756b58758878409f591d4737b6a8cef26a162e2a4961c3321",
 | 
			
		||||
                "sha256:38600fd58c2996829480de7d034fb2d3a0307110e44dae80b6b4f9b3d2eea529",
 | 
			
		||||
                "sha256:3a195e26df1fbb40ebee75865f9b64ba692a5824ecb91c078cc665b01f7a9a36",
 | 
			
		||||
                "sha256:41acdd6d64cd56f857e271009966c2ffcbd07ec9149ca91f71088574eaa4278a",
 | 
			
		||||
                "sha256:45f97ade892ace20252e5ccecdd7515c7df5feeb42c3d2a8b8c55920c3551c30",
 | 
			
		||||
                "sha256:4b0c211c55d4aac4309c3209833c803fada3fc21cdf7b74abedda42a0c9dc3ce",
 | 
			
		||||
                "sha256:5d5209c3ba25864b1a57461526ebde31483db295fc6195fdfc4f8355e10f7376",
 | 
			
		||||
                "sha256:615fb5a524cffc91ab4490b69e10ae76c1ccbfa3383ea2fad72e54a85c7d47dd",
 | 
			
		||||
                "sha256:61e734c2bcb3742c3f454dfa930ea60ea08f56fd1a0eb52d8cb189a2f6be9586",
 | 
			
		||||
                "sha256:640ccca4d0a6fcc6590f005ecd7b16c3d8f5d52174e4854f96b16f34c39d6cb7",
 | 
			
		||||
                "sha256:6dbd51c3db300ce9d3171f4106da18fe49e7045232630fe3d4c6e37cb2b39ab9",
 | 
			
		||||
                "sha256:71a904da8c9c02aee581f4452a5a988c3003207cb8033db426f29e5b2c0b7aea",
 | 
			
		||||
                "sha256:8021dee64899f993f4b5cca323aae65aabc01a546ed44356a0965e29d7893c94",
 | 
			
		||||
                "sha256:8b8d551f1bd60b3e1c59ff55b9e8d74607a5308f66e2916948cafd13480b44a3",
 | 
			
		||||
                "sha256:93f9f720081d97acee38a411e861d4ce84cbc8ea5319bc1f8e38c972c47af49f",
 | 
			
		||||
                "sha256:96f0c79a70642dfdf7e6a018ebcbea7ea5205e27d8e019cad442d2acfc9af267",
 | 
			
		||||
                "sha256:9966337353e436e6ba652814b0a957a517feb492a98b8f9d3b6ba76d22301dcc",
 | 
			
		||||
                "sha256:a34ba9e39f8269fd66ab4f7a802794ffea6d6ac500568ec05b327a862c21ce23",
 | 
			
		||||
                "sha256:a49f85f0a099a5755d0a2cc6fc337e3cb945ad6390ec892332c691ab0a045882",
 | 
			
		||||
                "sha256:a795829dc522227265d72b25d6ee6f6d41eb2105c15912c230097c8f5bfdbcdc",
 | 
			
		||||
                "sha256:a89ca4105f8099de349d139d1090bad387fe2b208b717b288699ca26f179acbe",
 | 
			
		||||
                "sha256:ac95101736239260189f426b1e361dc1b704513963357dc474beb0f39f5b7759",
 | 
			
		||||
                "sha256:ae87ab669431f611c56e581679db33b9a467f87d7bf197ac384e71e4956b4456",
 | 
			
		||||
                "sha256:b091dcfee169ad8de21b61eb2c3a75f9f0f859f851f64fdaf9320759a3244239",
 | 
			
		||||
                "sha256:b511c6009d50d5c0dd0bab85ed25bc8ad6b6f5611de3a63a59786207e82824bb",
 | 
			
		||||
                "sha256:b79dc2b2e313565416c1e62807c7c25c67a6ff0a0f8d83a318df464555b65948",
 | 
			
		||||
                "sha256:bca14dfcfd9aae06d7d8d7e105539bd77d39d06caaae57a1ce945670bae744e0",
 | 
			
		||||
                "sha256:c835c30f3af5c63a80917b72115e1defb83de99c73bc727bddd979a3b449e183",
 | 
			
		||||
                "sha256:ccd721f1d4fc42b541b633d6e339018a08dd0290dc67269df79552843a06ca92",
 | 
			
		||||
                "sha256:d6c2b1d78ceceb6741d703508cd0e9197b34f6bf6864dab30f940f8886e04ade",
 | 
			
		||||
                "sha256:d6ec4ae13760ceda023b2e5ef1f9bc0b21e4b0830458db143794a117fdbdc044",
 | 
			
		||||
                "sha256:d8b623fc429a38a881ab2d9a56ef30e8ea20c72a891c193f5ebbddc016e083ee",
 | 
			
		||||
                "sha256:ea9753d64cba6f226947c318a923dadaf1e21cd8db02f71652405263daa1f033",
 | 
			
		||||
                "sha256:ebbceefbffae118ab954d3cd6bf718f5790db66152f95202ebc231d58ad4e2c2",
 | 
			
		||||
                "sha256:ecb6e7c45f9cd199c10ec35262b53b2247fb9a408803ed00ee5bb2b54aa626f5",
 | 
			
		||||
                "sha256:ef9326c64349e2d718373415814e754183057ebc092261387a2c2f732d9172b2",
 | 
			
		||||
                "sha256:f93a9d8804f4cec9da6c26c8cfae2c777028b4fdd9f49de0302e26e00bb86504",
 | 
			
		||||
                "sha256:faf08b0341828f6a29b8f7dd94d5cf8cc7c39bfc3e67b78514c54b494b66915a"
 | 
			
		||||
                "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468",
 | 
			
		||||
                "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354",
 | 
			
		||||
                "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308",
 | 
			
		||||
                "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d",
 | 
			
		||||
                "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc",
 | 
			
		||||
                "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8",
 | 
			
		||||
                "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797",
 | 
			
		||||
                "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2",
 | 
			
		||||
                "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13",
 | 
			
		||||
                "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d",
 | 
			
		||||
                "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a",
 | 
			
		||||
                "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0",
 | 
			
		||||
                "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73",
 | 
			
		||||
                "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1",
 | 
			
		||||
                "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed",
 | 
			
		||||
                "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a",
 | 
			
		||||
                "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b",
 | 
			
		||||
                "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f",
 | 
			
		||||
                "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256",
 | 
			
		||||
                "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb",
 | 
			
		||||
                "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2",
 | 
			
		||||
                "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983",
 | 
			
		||||
                "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb",
 | 
			
		||||
                "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645",
 | 
			
		||||
                "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8",
 | 
			
		||||
                "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a",
 | 
			
		||||
                "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906",
 | 
			
		||||
                "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f",
 | 
			
		||||
                "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c",
 | 
			
		||||
                "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892",
 | 
			
		||||
                "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0",
 | 
			
		||||
                "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e",
 | 
			
		||||
                "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e",
 | 
			
		||||
                "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed",
 | 
			
		||||
                "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c",
 | 
			
		||||
                "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374",
 | 
			
		||||
                "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd",
 | 
			
		||||
                "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791",
 | 
			
		||||
                "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a",
 | 
			
		||||
                "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1",
 | 
			
		||||
                "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2021.8.21"
 | 
			
		||||
            "version": "==2021.8.28"
 | 
			
		||||
        },
 | 
			
		||||
        "requests": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
 | 
			
		||||
@ -5,13 +5,14 @@
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
[](https://discord.gg/jg33eMhnj6)
 | 
			
		||||
[](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=6)
 | 
			
		||||
[](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=6)
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
[](https://codecov.io/gh/goauthentik/authentik)
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
[Transifex](https://www.transifex.com/beryjuorg/authentik/)
 | 
			
		||||
[](https://www.transifex.com/beryjuorg/authentik/)
 | 
			
		||||
 | 
			
		||||
## What is authentik?
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,3 @@
 | 
			
		||||
"""authentik"""
 | 
			
		||||
__version__ = "2021.8.2"
 | 
			
		||||
__version__ = "2021.8.4"
 | 
			
		||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,10 @@
 | 
			
		||||
"""authentik api urls"""
 | 
			
		||||
from django.urls import include, path
 | 
			
		||||
 | 
			
		||||
from authentik.api.v2.urls import urlpatterns as v2_urls
 | 
			
		||||
from authentik.api.v3.urls import urlpatterns as v3_urls
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path("v2beta/", include(v2_urls)),
 | 
			
		||||
    # Remove in 2022.1
 | 
			
		||||
    path("v2beta/", include(v3_urls)),
 | 
			
		||||
    path("v3/", include(v3_urls)),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -4,16 +4,44 @@ from json import loads
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.http.request import HttpRequest
 | 
			
		||||
from django.http.response import HttpResponse
 | 
			
		||||
from django.views.generic.base import View
 | 
			
		||||
from requests import post
 | 
			
		||||
from requests.exceptions import RequestException
 | 
			
		||||
from rest_framework.authentication import SessionAuthentication
 | 
			
		||||
from rest_framework.parsers import BaseParser
 | 
			
		||||
from rest_framework.permissions import AllowAny
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
from rest_framework.throttling import AnonRateThrottle
 | 
			
		||||
from rest_framework.views import APIView
 | 
			
		||||
 | 
			
		||||
from authentik.lib.config import CONFIG
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SentryTunnelView(View):
 | 
			
		||||
class PlainTextParser(BaseParser):
 | 
			
		||||
    """Plain text parser."""
 | 
			
		||||
 | 
			
		||||
    media_type = "text/plain"
 | 
			
		||||
 | 
			
		||||
    def parse(self, stream, media_type=None, parser_context=None) -> str:
 | 
			
		||||
        """Simply return a string representing the body of the request."""
 | 
			
		||||
        return stream.read()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CsrfExemptSessionAuthentication(SessionAuthentication):
 | 
			
		||||
    """CSRF-exempt Session authentication"""
 | 
			
		||||
 | 
			
		||||
    def enforce_csrf(self, request: Request):
 | 
			
		||||
        return  # To not perform the csrf check previously happening
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SentryTunnelView(APIView):
 | 
			
		||||
    """Sentry tunnel, to prevent ad blockers from blocking sentry"""
 | 
			
		||||
 | 
			
		||||
    serializer_class = None
 | 
			
		||||
    parser_classes = [PlainTextParser]
 | 
			
		||||
    throttle_classes = [AnonRateThrottle]
 | 
			
		||||
    permission_classes = [AllowAny]
 | 
			
		||||
    authentication_classes = [CsrfExemptSessionAuthentication]
 | 
			
		||||
 | 
			
		||||
    def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
 | 
			
		||||
        """Sentry tunnel, to prevent ad blockers from blocking sentry"""
 | 
			
		||||
        # Only allow usage of this endpoint when error reporting is enabled
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
"""api v2 urls"""
 | 
			
		||||
"""api v3 urls"""
 | 
			
		||||
from django.urls import path
 | 
			
		||||
from django.views.decorators.csrf import csrf_exempt
 | 
			
		||||
from drf_spectacular.views import SpectacularAPIView
 | 
			
		||||
@ -10,8 +10,8 @@ from authentik.admin.api.system import SystemView
 | 
			
		||||
from authentik.admin.api.tasks import TaskViewSet
 | 
			
		||||
from authentik.admin.api.version import VersionView
 | 
			
		||||
from authentik.admin.api.workers import WorkerView
 | 
			
		||||
from authentik.api.v2.config import ConfigView
 | 
			
		||||
from authentik.api.v2.sentry import SentryTunnelView
 | 
			
		||||
from authentik.api.v3.config import ConfigView
 | 
			
		||||
from authentik.api.v3.sentry import SentryTunnelView
 | 
			
		||||
from authentik.api.views import APIBrowserView
 | 
			
		||||
from authentik.core.api.applications import ApplicationViewSet
 | 
			
		||||
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
 | 
			
		||||
@ -23,7 +23,7 @@ from authentik.managed.api import ManagedSerializer
 | 
			
		||||
class TokenSerializer(ManagedSerializer, ModelSerializer):
 | 
			
		||||
    """Token Serializer"""
 | 
			
		||||
 | 
			
		||||
    user_obj = UserSerializer(required=False)
 | 
			
		||||
    user_obj = UserSerializer(required=False, source="user")
 | 
			
		||||
 | 
			
		||||
    def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
 | 
			
		||||
        """Ensure only API or App password tokens are created."""
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ from django.core.cache import cache
 | 
			
		||||
from prometheus_client import Gauge
 | 
			
		||||
 | 
			
		||||
from authentik.events.models import Event, EventAction
 | 
			
		||||
from authentik.lib.utils.errors import exception_to_string
 | 
			
		||||
 | 
			
		||||
GAUGE_TASKS = Gauge(
 | 
			
		||||
    "authentik_system_tasks",
 | 
			
		||||
@ -174,9 +175,7 @@ class MonitoredTask(Task):
 | 
			
		||||
        ).save(self.result_timeout_hours)
 | 
			
		||||
        Event.new(
 | 
			
		||||
            EventAction.SYSTEM_TASK_EXCEPTION,
 | 
			
		||||
            message=(
 | 
			
		||||
                f"Task {self.__name__} encountered an error: " "\n".join(self._result.messages)
 | 
			
		||||
            ),
 | 
			
		||||
            message=(f"Task {self.__name__} encountered an error: {exception_to_string(exc)}"),
 | 
			
		||||
        ).save()
 | 
			
		||||
        return super().on_failure(exc, task_id, args, kwargs, einfo=einfo)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,7 @@ class FlowPlanProcess(PROCESS_CLASS):  # pragma: no cover
 | 
			
		||||
        self.request = RequestFactory().get("/")
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        """Execute 1000 flow plans"""
 | 
			
		||||
        print(f"Proc {self.index} Running")
 | 
			
		||||
 | 
			
		||||
        def test_inner():
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
# Generated by Django 3.2.6 on 2021-08-30 14:49
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("authentik_flows", "0023_alter_flow_background"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="flow",
 | 
			
		||||
            name="compatibility_mode",
 | 
			
		||||
            field=models.BooleanField(
 | 
			
		||||
                default=False,
 | 
			
		||||
                help_text="Enable compatibility mode, increases compatibility with password managers on mobile devices.",
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -125,7 +125,7 @@ class Flow(SerializerModel, PolicyBindingModel):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    compatibility_mode = models.BooleanField(
 | 
			
		||||
        default=True,
 | 
			
		||||
        default=False,
 | 
			
		||||
        help_text=_(
 | 
			
		||||
            "Enable compatibility mode, increases compatibility with "
 | 
			
		||||
            "password managers on mobile devices."
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,10 @@
 | 
			
		||||
from unittest.mock import MagicMock, PropertyMock, patch
 | 
			
		||||
 | 
			
		||||
from django.http import HttpRequest, HttpResponse
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.test.client import RequestFactory
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -37,7 +37,7 @@ def to_stage_response(request: HttpRequest, source: HttpResponse):
 | 
			
		||||
TO_STAGE_RESPONSE_MOCK = MagicMock(side_effect=to_stage_response)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestFlowExecutor(TestCase):
 | 
			
		||||
class TestFlowExecutor(APITestCase):
 | 
			
		||||
    """Test views logic"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
"""flow views tests"""
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from authentik.flows.models import Flow, FlowDesignation
 | 
			
		||||
@ -10,9 +10,6 @@ from authentik.flows.views import SESSION_KEY_PLAN
 | 
			
		||||
class TestHelperView(TestCase):
 | 
			
		||||
    """Test helper views logic"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
    def test_default_view(self):
 | 
			
		||||
        """Test that ToDefaultFlow returns the expected URL"""
 | 
			
		||||
        flow = Flow.objects.filter(
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,9 @@ class DockerController(BaseController):
 | 
			
		||||
            raise ControllerException from exc
 | 
			
		||||
 | 
			
		||||
    def _get_labels(self) -> dict[str, str]:
 | 
			
		||||
        return {}
 | 
			
		||||
        return {
 | 
			
		||||
            "io.goauthentik.outpost-uuid": self.outpost.pk.hex,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def _get_env(self) -> dict[str, str]:
 | 
			
		||||
        return {
 | 
			
		||||
@ -49,6 +51,17 @@ class DockerController(BaseController):
 | 
			
		||||
                return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def _comp_labels(self, container: Container) -> bool:
 | 
			
		||||
        """Check if container's labels is equal to what we would set. Return true if container needs
 | 
			
		||||
        to be rebuilt."""
 | 
			
		||||
        should_be = self._get_labels()
 | 
			
		||||
        for key, expected_value in should_be.items():
 | 
			
		||||
            if key not in container.labels:
 | 
			
		||||
                return True
 | 
			
		||||
            if container.labels[key] != expected_value:
 | 
			
		||||
                return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def _comp_ports(self, container: Container) -> bool:
 | 
			
		||||
        """Check that the container has the correct ports exposed. Return true if container needs
 | 
			
		||||
        to be rebuilt."""
 | 
			
		||||
@ -92,9 +105,11 @@ class DockerController(BaseController):
 | 
			
		||||
                "environment": self._get_env(),
 | 
			
		||||
                "labels": self._get_labels(),
 | 
			
		||||
                "restart_policy": {"Name": "unless-stopped"},
 | 
			
		||||
                "network": self.outpost.config.docker_network,
 | 
			
		||||
            }
 | 
			
		||||
            if settings.TEST:
 | 
			
		||||
                del container_args["ports"]
 | 
			
		||||
                del container_args["network"]
 | 
			
		||||
                container_args["network_mode"] = "host"
 | 
			
		||||
            return (
 | 
			
		||||
                self.client.containers.create(**container_args),
 | 
			
		||||
@ -133,6 +148,11 @@ class DockerController(BaseController):
 | 
			
		||||
                self.logger.info("Container has outdated config, re-creating...")
 | 
			
		||||
                self.down()
 | 
			
		||||
                return self.up(depth + 1)
 | 
			
		||||
            # Check that container values match our values
 | 
			
		||||
            if self._comp_labels(container):
 | 
			
		||||
                self.logger.info("Container has outdated labels, re-creating...")
 | 
			
		||||
                self.down()
 | 
			
		||||
                return self.up(depth + 1)
 | 
			
		||||
            if (
 | 
			
		||||
                container.attrs.get("HostConfig", {})
 | 
			
		||||
                .get("RestartPolicy", {})
 | 
			
		||||
 | 
			
		||||
@ -3,10 +3,11 @@ from typing import TYPE_CHECKING, Generic, TypeVar
 | 
			
		||||
 | 
			
		||||
from django.utils.text import slugify
 | 
			
		||||
from kubernetes.client import V1ObjectMeta
 | 
			
		||||
from kubernetes.client.exceptions import ApiException, OpenApiException
 | 
			
		||||
from kubernetes.client.models.v1_deployment import V1Deployment
 | 
			
		||||
from kubernetes.client.models.v1_pod import V1Pod
 | 
			
		||||
from kubernetes.client.rest import ApiException
 | 
			
		||||
from structlog.stdlib import get_logger
 | 
			
		||||
from urllib3.exceptions import HTTPError
 | 
			
		||||
 | 
			
		||||
from authentik import __version__
 | 
			
		||||
from authentik.lib.sentry import SentryIgnoredException
 | 
			
		||||
@ -72,8 +73,9 @@ class KubernetesObjectReconciler(Generic[T]):
 | 
			
		||||
        try:
 | 
			
		||||
            try:
 | 
			
		||||
                current = self.retrieve()
 | 
			
		||||
            except ApiException as exc:
 | 
			
		||||
                if exc.status == 404:
 | 
			
		||||
            except (OpenApiException, HTTPError) as exc:
 | 
			
		||||
                # pylint: disable=no-member
 | 
			
		||||
                if isinstance(exc, ApiException) and exc.status == 404:
 | 
			
		||||
                    self.logger.debug("Failed to get current, triggering recreate")
 | 
			
		||||
                    raise NeedsRecreate from exc
 | 
			
		||||
                self.logger.debug("Other unhandled error", exc=exc)
 | 
			
		||||
@ -104,8 +106,9 @@ class KubernetesObjectReconciler(Generic[T]):
 | 
			
		||||
            current = self.retrieve()
 | 
			
		||||
            self.delete(current)
 | 
			
		||||
            self.logger.debug("Removing")
 | 
			
		||||
        except ApiException as exc:
 | 
			
		||||
            if exc.status == 404:
 | 
			
		||||
        except (OpenApiException, HTTPError) as exc:
 | 
			
		||||
            # pylint: disable=no-member
 | 
			
		||||
            if isinstance(exc, ApiException) and exc.status == 404:
 | 
			
		||||
                self.logger.debug("Failed to get current, assuming non-existant")
 | 
			
		||||
                return
 | 
			
		||||
            self.logger.debug("Other unhandled error", exc=exc)
 | 
			
		||||
 | 
			
		||||
@ -3,8 +3,9 @@ from io import StringIO
 | 
			
		||||
from typing import Type
 | 
			
		||||
 | 
			
		||||
from kubernetes.client.api_client import ApiClient
 | 
			
		||||
from kubernetes.client.exceptions import ApiException
 | 
			
		||||
from kubernetes.client.exceptions import OpenApiException
 | 
			
		||||
from structlog.testing import capture_logs
 | 
			
		||||
from urllib3.exceptions import HTTPError
 | 
			
		||||
from yaml import dump_all
 | 
			
		||||
 | 
			
		||||
from authentik.outposts.controllers.base import BaseController, ControllerException
 | 
			
		||||
@ -12,7 +13,7 @@ from authentik.outposts.controllers.k8s.base import KubernetesObjectReconciler
 | 
			
		||||
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
 | 
			
		||||
from authentik.outposts.controllers.k8s.secret import SecretReconciler
 | 
			
		||||
from authentik.outposts.controllers.k8s.service import ServiceReconciler
 | 
			
		||||
from authentik.outposts.models import KubernetesServiceConnection, Outpost
 | 
			
		||||
from authentik.outposts.models import KubernetesServiceConnection, Outpost, ServiceConnectionInvalid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class KubernetesController(BaseController):
 | 
			
		||||
@ -40,7 +41,7 @@ class KubernetesController(BaseController):
 | 
			
		||||
                reconciler = self.reconcilers[reconcile_key](self)
 | 
			
		||||
                reconciler.up()
 | 
			
		||||
 | 
			
		||||
        except ApiException as exc:
 | 
			
		||||
        except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
 | 
			
		||||
            raise ControllerException(str(exc)) from exc
 | 
			
		||||
 | 
			
		||||
    def up_with_logs(self) -> list[str]:
 | 
			
		||||
@ -55,7 +56,7 @@ class KubernetesController(BaseController):
 | 
			
		||||
                    reconciler.up()
 | 
			
		||||
                all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
 | 
			
		||||
            return all_logs
 | 
			
		||||
        except ApiException as exc:
 | 
			
		||||
        except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
 | 
			
		||||
            raise ControllerException(str(exc)) from exc
 | 
			
		||||
 | 
			
		||||
    def down(self):
 | 
			
		||||
@ -65,7 +66,7 @@ class KubernetesController(BaseController):
 | 
			
		||||
                self.logger.debug("Tearing down object", name=reconcile_key)
 | 
			
		||||
                reconciler.down()
 | 
			
		||||
 | 
			
		||||
        except ApiException as exc:
 | 
			
		||||
        except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
 | 
			
		||||
            raise ControllerException(str(exc)) from exc
 | 
			
		||||
 | 
			
		||||
    def down_with_logs(self) -> list[str]:
 | 
			
		||||
@ -80,7 +81,7 @@ class KubernetesController(BaseController):
 | 
			
		||||
                    reconciler.down()
 | 
			
		||||
                all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
 | 
			
		||||
            return all_logs
 | 
			
		||||
        except ApiException as exc:
 | 
			
		||||
        except (OpenApiException, HTTPError, ServiceConnectionInvalid) as exc:
 | 
			
		||||
            raise ControllerException(str(exc)) from exc
 | 
			
		||||
 | 
			
		||||
    def get_static_deployment(self) -> str:
 | 
			
		||||
 | 
			
		||||
@ -56,6 +56,7 @@ class ServiceConnectionInvalid(SentryIgnoredException):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass
 | 
			
		||||
# pylint: disable=too-many-instance-attributes
 | 
			
		||||
class OutpostConfig:
 | 
			
		||||
    """Configuration an outpost uses to configure it self"""
 | 
			
		||||
 | 
			
		||||
@ -67,8 +68,10 @@ class OutpostConfig:
 | 
			
		||||
    log_level: str = CONFIG.y("log_level")
 | 
			
		||||
    error_reporting_enabled: bool = CONFIG.y_bool("error_reporting.enabled")
 | 
			
		||||
    error_reporting_environment: str = CONFIG.y("error_reporting.environment", "customer")
 | 
			
		||||
 | 
			
		||||
    object_naming_template: str = field(default="ak-outpost-%(name)s")
 | 
			
		||||
 | 
			
		||||
    docker_network: Optional[str] = field(default=None)
 | 
			
		||||
 | 
			
		||||
    kubernetes_replicas: int = field(default=1)
 | 
			
		||||
    kubernetes_namespace: str = field(default_factory=get_namespace)
 | 
			
		||||
    kubernetes_ingress_annotations: dict[str, str] = field(default_factory=dict)
 | 
			
		||||
@ -362,7 +365,7 @@ class Outpost(ManagedModel):
 | 
			
		||||
                    )
 | 
			
		||||
                    try:
 | 
			
		||||
                        assign_perm(code_name, user, model_or_perm)
 | 
			
		||||
                    except Permission.DoesNotExist as exc:
 | 
			
		||||
                    except (Permission.DoesNotExist, AttributeError) as exc:
 | 
			
		||||
                        LOGGER.warning(
 | 
			
		||||
                            "permission doesn't exist",
 | 
			
		||||
                            code_name=code_name,
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,11 @@ from structlog.stdlib import get_logger
 | 
			
		||||
 | 
			
		||||
from authentik.policies.models import Policy
 | 
			
		||||
from authentik.policies.types import PolicyRequest, PolicyResult
 | 
			
		||||
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
RE_LOWER = re.compile("[a-z]")
 | 
			
		||||
RE_UPPER = re.compile("[A-Z]")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PasswordPolicy(Policy):
 | 
			
		||||
@ -38,31 +41,39 @@ class PasswordPolicy(Policy):
 | 
			
		||||
        return "ak-policy-password-form"
 | 
			
		||||
 | 
			
		||||
    def passes(self, request: PolicyRequest) -> PolicyResult:
 | 
			
		||||
        if self.password_field not in request.context:
 | 
			
		||||
        if (
 | 
			
		||||
            self.password_field not in request.context
 | 
			
		||||
            and self.password_field not in request.context.get(PLAN_CONTEXT_PROMPT, {})
 | 
			
		||||
        ):
 | 
			
		||||
            LOGGER.warning(
 | 
			
		||||
                "Password field not set in Policy Request",
 | 
			
		||||
                field=self.password_field,
 | 
			
		||||
                fields=request.context.keys(),
 | 
			
		||||
                prompt_fields=request.context.get(PLAN_CONTEXT_PROMPT, {}).keys(),
 | 
			
		||||
            )
 | 
			
		||||
            return PolicyResult(False, _("Password not set in context"))
 | 
			
		||||
 | 
			
		||||
        if self.password_field in request.context:
 | 
			
		||||
            password = request.context[self.password_field]
 | 
			
		||||
        else:
 | 
			
		||||
            password = request.context[PLAN_CONTEXT_PROMPT][self.password_field]
 | 
			
		||||
 | 
			
		||||
        filter_regex = []
 | 
			
		||||
        if self.amount_lowercase > 0:
 | 
			
		||||
            filter_regex.append(r"[a-z]{%d,}" % self.amount_lowercase)
 | 
			
		||||
        if self.amount_uppercase > 0:
 | 
			
		||||
            filter_regex.append(r"[A-Z]{%d,}" % self.amount_uppercase)
 | 
			
		||||
        if self.amount_symbols > 0:
 | 
			
		||||
            filter_regex.append(r"[%s]{%d,}" % (self.symbol_charset, self.amount_symbols))
 | 
			
		||||
        full_regex = "|".join(filter_regex)
 | 
			
		||||
        LOGGER.debug("Built regex", regexp=full_regex)
 | 
			
		||||
        result = bool(re.compile(full_regex).match(password))
 | 
			
		||||
        if len(password) < self.length_min:
 | 
			
		||||
            LOGGER.debug("password failed", reason="length", p=password)
 | 
			
		||||
            return PolicyResult(False, self.error_message)
 | 
			
		||||
 | 
			
		||||
        result = result and len(password) >= self.length_min
 | 
			
		||||
        if self.amount_lowercase > 0 and len(RE_LOWER.findall(password)) < self.amount_lowercase:
 | 
			
		||||
            LOGGER.debug("password failed", reason="amount_lowercase", p=password)
 | 
			
		||||
            return PolicyResult(False, self.error_message)
 | 
			
		||||
        if self.amount_uppercase > 0 and len(RE_UPPER.findall(password)) < self.amount_lowercase:
 | 
			
		||||
            LOGGER.debug("password failed", reason="amount_uppercase", p=password)
 | 
			
		||||
            return PolicyResult(False, self.error_message)
 | 
			
		||||
        regex = re.compile(r"[%s]" % self.symbol_charset)
 | 
			
		||||
        if self.amount_symbols > 0 and len(regex.findall(password)) < self.amount_symbols:
 | 
			
		||||
            LOGGER.debug("password failed", reason="amount_symbols", p=password)
 | 
			
		||||
            return PolicyResult(False, self.error_message)
 | 
			
		||||
 | 
			
		||||
        if not result:
 | 
			
		||||
            return PolicyResult(result, self.error_message)
 | 
			
		||||
        return PolicyResult(result)
 | 
			
		||||
        return PolicyResult(True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,57 +0,0 @@
 | 
			
		||||
"""Password Policy tests"""
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from guardian.shortcuts import get_anonymous_user
 | 
			
		||||
 | 
			
		||||
from authentik.policies.password.models import PasswordPolicy
 | 
			
		||||
from authentik.policies.types import PolicyRequest, PolicyResult
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestPasswordPolicy(TestCase):
 | 
			
		||||
    """Test Password Policy"""
 | 
			
		||||
 | 
			
		||||
    def test_invalid(self):
 | 
			
		||||
        """Test without password"""
 | 
			
		||||
        policy = PasswordPolicy.objects.create(
 | 
			
		||||
            name="test_invalid",
 | 
			
		||||
            amount_uppercase=1,
 | 
			
		||||
            amount_lowercase=2,
 | 
			
		||||
            amount_symbols=3,
 | 
			
		||||
            length_min=24,
 | 
			
		||||
            error_message="test message",
 | 
			
		||||
        )
 | 
			
		||||
        request = PolicyRequest(get_anonymous_user())
 | 
			
		||||
        result: PolicyResult = policy.passes(request)
 | 
			
		||||
        self.assertFalse(result.passing)
 | 
			
		||||
        self.assertEqual(result.messages[0], "Password not set in context")
 | 
			
		||||
 | 
			
		||||
    def test_false(self):
 | 
			
		||||
        """Failing password case"""
 | 
			
		||||
        policy = PasswordPolicy.objects.create(
 | 
			
		||||
            name="test_false",
 | 
			
		||||
            amount_uppercase=1,
 | 
			
		||||
            amount_lowercase=2,
 | 
			
		||||
            amount_symbols=3,
 | 
			
		||||
            length_min=24,
 | 
			
		||||
            error_message="test message",
 | 
			
		||||
        )
 | 
			
		||||
        request = PolicyRequest(get_anonymous_user())
 | 
			
		||||
        request.context["password"] = "test"
 | 
			
		||||
        result: PolicyResult = policy.passes(request)
 | 
			
		||||
        self.assertFalse(result.passing)
 | 
			
		||||
        self.assertEqual(result.messages, ("test message",))
 | 
			
		||||
 | 
			
		||||
    def test_true(self):
 | 
			
		||||
        """Positive password case"""
 | 
			
		||||
        policy = PasswordPolicy.objects.create(
 | 
			
		||||
            name="test_true",
 | 
			
		||||
            amount_uppercase=1,
 | 
			
		||||
            amount_lowercase=2,
 | 
			
		||||
            amount_symbols=3,
 | 
			
		||||
            length_min=3,
 | 
			
		||||
            error_message="test message",
 | 
			
		||||
        )
 | 
			
		||||
        request = PolicyRequest(get_anonymous_user())
 | 
			
		||||
        request.context["password"] = "Test()!"
 | 
			
		||||
        result: PolicyResult = policy.passes(request)
 | 
			
		||||
        self.assertTrue(result.passing)
 | 
			
		||||
        self.assertEqual(result.messages, tuple())
 | 
			
		||||
							
								
								
									
										0
									
								
								authentik/policies/password/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								authentik/policies/password/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										80
									
								
								authentik/policies/password/tests/test_flows.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								authentik/policies/password/tests/test_flows.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,80 @@
 | 
			
		||||
"""Password flow tests"""
 | 
			
		||||
from django.urls.base import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
 | 
			
		||||
from authentik.policies.password.models import PasswordPolicy
 | 
			
		||||
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestPasswordPolicyFlow(APITestCase):
 | 
			
		||||
    """Test Password Policy"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        self.user = User.objects.create(username="unittest", email="test@beryju.org")
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-prompt",
 | 
			
		||||
            slug="test-prompt",
 | 
			
		||||
            designation=FlowDesignation.AUTHENTICATION,
 | 
			
		||||
        )
 | 
			
		||||
        password_prompt = Prompt.objects.create(
 | 
			
		||||
            field_key="password",
 | 
			
		||||
            label="PASSWORD_LABEL",
 | 
			
		||||
            type=FieldTypes.PASSWORD,
 | 
			
		||||
            required=True,
 | 
			
		||||
            placeholder="PASSWORD_PLACEHOLDER",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.policy = PasswordPolicy.objects.create(
 | 
			
		||||
            name="test_true",
 | 
			
		||||
            amount_uppercase=1,
 | 
			
		||||
            amount_lowercase=2,
 | 
			
		||||
            amount_symbols=3,
 | 
			
		||||
            length_min=3,
 | 
			
		||||
            error_message="test message",
 | 
			
		||||
        )
 | 
			
		||||
        stage = PromptStage.objects.create(name="prompt-stage")
 | 
			
		||||
        stage.validation_policies.set([self.policy])
 | 
			
		||||
        stage.fields.set(
 | 
			
		||||
            [
 | 
			
		||||
                password_prompt,
 | 
			
		||||
            ]
 | 
			
		||||
        )
 | 
			
		||||
        FlowStageBinding.objects.create(target=self.flow, stage=stage, order=2)
 | 
			
		||||
 | 
			
		||||
    def test_prompt_data(self):
 | 
			
		||||
        """Test policy attached to a prompt stage"""
 | 
			
		||||
        response = self.client.post(
 | 
			
		||||
            reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
 | 
			
		||||
            {"password": "akadmin"},
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertJSONEqual(
 | 
			
		||||
            force_str(response.content),
 | 
			
		||||
            {
 | 
			
		||||
                "component": "ak-stage-prompt",
 | 
			
		||||
                "fields": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "field_key": "password",
 | 
			
		||||
                        "label": "PASSWORD_LABEL",
 | 
			
		||||
                        "order": 0,
 | 
			
		||||
                        "placeholder": "PASSWORD_PLACEHOLDER",
 | 
			
		||||
                        "required": True,
 | 
			
		||||
                        "type": "password",
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "flow_info": {
 | 
			
		||||
                    "background": self.flow.background_url,
 | 
			
		||||
                    "cancel_url": reverse("authentik_flows:cancel"),
 | 
			
		||||
                    "title": "",
 | 
			
		||||
                },
 | 
			
		||||
                "response_errors": {
 | 
			
		||||
                    "non_field_errors": [{"code": "invalid", "string": self.policy.error_message}]
 | 
			
		||||
                },
 | 
			
		||||
                "type": ChallengeTypes.NATIVE.value,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
							
								
								
									
										68
									
								
								authentik/policies/password/tests/test_policy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								authentik/policies/password/tests/test_policy.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
			
		||||
"""Password Policy tests"""
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from guardian.shortcuts import get_anonymous_user
 | 
			
		||||
 | 
			
		||||
from authentik.lib.generators import generate_key
 | 
			
		||||
from authentik.policies.password.models import PasswordPolicy
 | 
			
		||||
from authentik.policies.types import PolicyRequest, PolicyResult
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestPasswordPolicy(TestCase):
 | 
			
		||||
    """Test Password Policy"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        self.policy = PasswordPolicy.objects.create(
 | 
			
		||||
            name="test_false",
 | 
			
		||||
            amount_uppercase=1,
 | 
			
		||||
            amount_lowercase=2,
 | 
			
		||||
            amount_symbols=3,
 | 
			
		||||
            length_min=24,
 | 
			
		||||
            error_message="test message",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_invalid(self):
 | 
			
		||||
        """Test without password"""
 | 
			
		||||
        request = PolicyRequest(get_anonymous_user())
 | 
			
		||||
        result: PolicyResult = self.policy.passes(request)
 | 
			
		||||
        self.assertFalse(result.passing)
 | 
			
		||||
        self.assertEqual(result.messages[0], "Password not set in context")
 | 
			
		||||
 | 
			
		||||
    def test_failed_length(self):
 | 
			
		||||
        """Password too short"""
 | 
			
		||||
        request = PolicyRequest(get_anonymous_user())
 | 
			
		||||
        request.context["password"] = "test"
 | 
			
		||||
        result: PolicyResult = self.policy.passes(request)
 | 
			
		||||
        self.assertFalse(result.passing)
 | 
			
		||||
        self.assertEqual(result.messages, ("test message",))
 | 
			
		||||
 | 
			
		||||
    def test_failed_lowercase(self):
 | 
			
		||||
        """not enough lowercase"""
 | 
			
		||||
        request = PolicyRequest(get_anonymous_user())
 | 
			
		||||
        request.context["password"] = "TTTTTTTTTTTTTTTTTTTTTTTe"
 | 
			
		||||
        result: PolicyResult = self.policy.passes(request)
 | 
			
		||||
        self.assertFalse(result.passing)
 | 
			
		||||
        self.assertEqual(result.messages, ("test message",))
 | 
			
		||||
 | 
			
		||||
    def test_failed_uppercase(self):
 | 
			
		||||
        """not enough uppercase"""
 | 
			
		||||
        request = PolicyRequest(get_anonymous_user())
 | 
			
		||||
        request.context["password"] = "tttttttttttttttttttttttE"
 | 
			
		||||
        result: PolicyResult = self.policy.passes(request)
 | 
			
		||||
        self.assertFalse(result.passing)
 | 
			
		||||
        self.assertEqual(result.messages, ("test message",))
 | 
			
		||||
 | 
			
		||||
    def test_failed_symbols(self):
 | 
			
		||||
        """not enough uppercase"""
 | 
			
		||||
        request = PolicyRequest(get_anonymous_user())
 | 
			
		||||
        request.context["password"] = "TETETETETETETETETETETETETe!!!"
 | 
			
		||||
        result: PolicyResult = self.policy.passes(request)
 | 
			
		||||
        self.assertFalse(result.passing)
 | 
			
		||||
        self.assertEqual(result.messages, ("test message",))
 | 
			
		||||
 | 
			
		||||
    def test_true(self):
 | 
			
		||||
        """Positive password case"""
 | 
			
		||||
        request = PolicyRequest(get_anonymous_user())
 | 
			
		||||
        request.context["password"] = generate_key() + "ee!!!"
 | 
			
		||||
        result: PolicyResult = self.policy.passes(request)
 | 
			
		||||
        self.assertTrue(result.passing)
 | 
			
		||||
        self.assertEqual(result.messages, tuple())
 | 
			
		||||
@ -22,13 +22,13 @@ class ProxyDockerController(DockerController):
 | 
			
		||||
        for proxy_provider in ProxyProvider.objects.filter(outpost__in=[self.outpost]):
 | 
			
		||||
            proxy_provider: ProxyProvider
 | 
			
		||||
            external_host_name = urlparse(proxy_provider.external_host)
 | 
			
		||||
            hosts.append(f"`{external_host_name}`")
 | 
			
		||||
            hosts.append(f"`{external_host_name.netloc}`")
 | 
			
		||||
        traefik_name = f"ak-outpost-{self.outpost.pk.hex}"
 | 
			
		||||
        return {
 | 
			
		||||
            "traefik.enable": "true",
 | 
			
		||||
            f"traefik.http.routers.{traefik_name}-router.rule": f"Host({','.join(hosts)})",
 | 
			
		||||
            f"traefik.http.routers.{traefik_name}-router.tls": "true",
 | 
			
		||||
            f"traefik.http.routers.{traefik_name}-router.service": f"{traefik_name}-service",
 | 
			
		||||
            f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path": "/",
 | 
			
		||||
            f"traefik.http.services.{traefik_name}-service.loadbalancer.server.port": "4180",
 | 
			
		||||
        }
 | 
			
		||||
        labels = super()._get_labels()
 | 
			
		||||
        labels["traefik.enable"] = "true"
 | 
			
		||||
        labels[f"traefik.http.routers.{traefik_name}-router.rule"] = f"Host({','.join(hosts)})"
 | 
			
		||||
        labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true"
 | 
			
		||||
        labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service"
 | 
			
		||||
        labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"] = "/"
 | 
			
		||||
        labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.server.port"] = "4180"
 | 
			
		||||
        return labels
 | 
			
		||||
 | 
			
		||||
@ -61,6 +61,7 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
 | 
			
		||||
        have_hosts.sort()
 | 
			
		||||
 | 
			
		||||
        have_hosts_tls = []
 | 
			
		||||
        if current.spec.tls:
 | 
			
		||||
            for tls_config in current.spec.tls:
 | 
			
		||||
                if tls_config and tls_config.hosts:
 | 
			
		||||
                    have_hosts_tls += tls_config.hosts
 | 
			
		||||
 | 
			
		||||
@ -96,6 +96,7 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
 | 
			
		||||
 | 
			
		||||
    def get_reference_object(self) -> TraefikMiddleware:
 | 
			
		||||
        """Get deployment object for outpost"""
 | 
			
		||||
        port = 9000 if self.is_embedded else 4180
 | 
			
		||||
        return TraefikMiddleware(
 | 
			
		||||
            apiVersion=f"{CRD_GROUP}/{CRD_VERSION}",
 | 
			
		||||
            kind="Middleware",
 | 
			
		||||
@ -106,7 +107,7 @@ class TraefikMiddlewareReconciler(KubernetesObjectReconciler[TraefikMiddleware])
 | 
			
		||||
            ),
 | 
			
		||||
            spec=TraefikMiddlewareSpec(
 | 
			
		||||
                forwardAuth=TraefikMiddlewareSpecForwardAuth(
 | 
			
		||||
                    address=f"http://{self.name}.{self.namespace}:4180/akprox/auth?traefik",
 | 
			
		||||
                    address=f"http://{self.name}.{self.namespace}:{port}/akprox/auth?traefik",
 | 
			
		||||
                    authResponseHeaders=[
 | 
			
		||||
                        "Set-Cookie",
 | 
			
		||||
                        "X-Auth-Username",
 | 
			
		||||
 | 
			
		||||
@ -150,9 +150,20 @@ SPECTACULAR_SETTINGS = {
 | 
			
		||||
    "DESCRIPTION": "Making authentication simple.",
 | 
			
		||||
    "VERSION": __version__,
 | 
			
		||||
    "COMPONENT_SPLIT_REQUEST": True,
 | 
			
		||||
    "SCHEMA_PATH_PREFIX": "/api/v([0-9]+(beta)?)",
 | 
			
		||||
    "SCHEMA_PATH_PREFIX_TRIM": True,
 | 
			
		||||
    "SERVERS": [
 | 
			
		||||
        {
 | 
			
		||||
            "url": "http://authentik.tld/api/v3/",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "url": "http://authentik.tld/api/v2beta/",
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    "CONTACT": {
 | 
			
		||||
        "email": "hello@beryju.org",
 | 
			
		||||
    },
 | 
			
		||||
    "AUTHENTICATION_WHITELIST": ["authentik.api.authentication.TokenAuthentication"],
 | 
			
		||||
    "LICENSE": {
 | 
			
		||||
        "name": "GNU GPLv3",
 | 
			
		||||
        "url": "https://github.com/goauthentik/authentik/blob/master/LICENSE",
 | 
			
		||||
@ -180,6 +191,9 @@ REST_FRAMEWORK = {
 | 
			
		||||
        "rest_framework.filters.OrderingFilter",
 | 
			
		||||
        "rest_framework.filters.SearchFilter",
 | 
			
		||||
    ],
 | 
			
		||||
    "DEFAULT_PARSER_CLASSES": [
 | 
			
		||||
        "rest_framework.parsers.JSONParser",
 | 
			
		||||
    ],
 | 
			
		||||
    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.DjangoObjectPermissions",),
 | 
			
		||||
    "DEFAULT_AUTHENTICATION_CLASSES": (
 | 
			
		||||
        "authentik.api.authentication.TokenAuthentication",
 | 
			
		||||
@ -189,6 +203,7 @@ REST_FRAMEWORK = {
 | 
			
		||||
        "rest_framework.renderers.JSONRenderer",
 | 
			
		||||
    ],
 | 
			
		||||
    "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
 | 
			
		||||
    "TEST_REQUEST_DEFAULT_FORMAT": "json",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
REDIS_PROTOCOL_PREFIX = "redis://"
 | 
			
		||||
@ -372,6 +387,7 @@ if CONFIG.y("postgresql.s3_backup"):
 | 
			
		||||
        "default_acl": "private",
 | 
			
		||||
        "endpoint_url": CONFIG.y("postgresql.s3_backup.host"),
 | 
			
		||||
        "location": CONFIG.y("postgresql.s3_backup.location", ""),
 | 
			
		||||
        "verify": not CONFIG.y_bool("postgresql.s3_backup.insecure_skip_verify", False),
 | 
			
		||||
    }
 | 
			
		||||
    j_print(
 | 
			
		||||
        "Database backup to S3 is configured",
 | 
			
		||||
 | 
			
		||||
@ -2,17 +2,13 @@
 | 
			
		||||
from base64 import b64encode
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestRoot(TestCase):
 | 
			
		||||
    """Test root application"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
    def test_monitoring_error(self):
 | 
			
		||||
        """Test monitoring without any credentials"""
 | 
			
		||||
        response = self.client.get(reverse("metrics"))
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
"""Twitter Type tests"""
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
 | 
			
		||||
from authentik.sources.oauth.models import OAuthSource
 | 
			
		||||
from authentik.sources.oauth.types.twitter import TwitterOAuthCallback
 | 
			
		||||
@ -92,7 +92,6 @@ class TestTypeGitHub(TestCase):
 | 
			
		||||
    """OAuth Source tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
        self.source = OAuthSource.objects.create(
 | 
			
		||||
            name="test",
 | 
			
		||||
            slug="test",
 | 
			
		||||
 | 
			
		||||
@ -51,20 +51,30 @@ def get_webauthn_challenge(request: HttpRequest, device: WebAuthnDevice) -> dict
 | 
			
		||||
    # for the reasons outlined in the comment in webauthn_begin_activate.
 | 
			
		||||
    request.session["challenge"] = challenge.rstrip("=")
 | 
			
		||||
 | 
			
		||||
    assertion = {}
 | 
			
		||||
    user = device.user
 | 
			
		||||
 | 
			
		||||
    # We want all the user's WebAuthn devices and merge their challenges
 | 
			
		||||
    for user_device in WebAuthnDevice.objects.filter(user=device.user).order_by("name"):
 | 
			
		||||
        webauthn_user = WebAuthnUser(
 | 
			
		||||
        device.user.uid,
 | 
			
		||||
        device.user.username,
 | 
			
		||||
        device.user.name,
 | 
			
		||||
        device.user.avatar,
 | 
			
		||||
        device.credential_id,
 | 
			
		||||
        device.public_key,
 | 
			
		||||
        device.sign_count,
 | 
			
		||||
        device.rp_id,
 | 
			
		||||
            user.uid,
 | 
			
		||||
            user.username,
 | 
			
		||||
            user.name,
 | 
			
		||||
            user.avatar,
 | 
			
		||||
            user_device.credential_id,
 | 
			
		||||
            user_device.public_key,
 | 
			
		||||
            user_device.sign_count,
 | 
			
		||||
            user_device.rp_id,
 | 
			
		||||
        )
 | 
			
		||||
        webauthn_assertion_options = WebAuthnAssertionOptions(webauthn_user, challenge)
 | 
			
		||||
        if assertion == {}:
 | 
			
		||||
            assertion = webauthn_assertion_options.assertion_dict
 | 
			
		||||
        else:
 | 
			
		||||
            assertion["allowCredentials"] += webauthn_assertion_options.assertion_dict.get(
 | 
			
		||||
                "allowCredentials"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    webauthn_assertion_options = WebAuthnAssertionOptions(webauthn_user, challenge)
 | 
			
		||||
 | 
			
		||||
    return webauthn_assertion_options.assertion_dict
 | 
			
		||||
    return assertion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_challenge_code(code: str, request: HttpRequest, user: User) -> str:
 | 
			
		||||
 | 
			
		||||
@ -20,8 +20,6 @@ from authentik.stages.authenticator_validate.models import AuthenticatorValidate
 | 
			
		||||
 | 
			
		||||
LOGGER = get_logger()
 | 
			
		||||
 | 
			
		||||
PER_DEVICE_CLASSES = [DeviceClasses.WEBAUTHN]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthenticatorValidationChallenge(WithUserInfoChallenge):
 | 
			
		||||
    """Authenticator challenge"""
 | 
			
		||||
@ -91,9 +89,9 @@ class AuthenticatorValidateStageView(ChallengeStageView):
 | 
			
		||||
            if device_class not in stage.device_classes:
 | 
			
		||||
                LOGGER.debug("device class not allowed", device_class=device_class)
 | 
			
		||||
                continue
 | 
			
		||||
            # Ensure only classes in PER_DEVICE_CLASSES are returned per device
 | 
			
		||||
            # otherwise only return a single challenge
 | 
			
		||||
            if device_class in seen_classes and device_class not in PER_DEVICE_CLASSES:
 | 
			
		||||
            # Ensure only one challenge per device class
 | 
			
		||||
            # WebAuthn does another device loop to find all webuahtn devices
 | 
			
		||||
            if device_class in seen_classes:
 | 
			
		||||
                continue
 | 
			
		||||
            if device_class not in seen_classes:
 | 
			
		||||
                seen_classes.append(device_class)
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,12 @@
 | 
			
		||||
"""Test validator stage"""
 | 
			
		||||
from unittest.mock import MagicMock, patch
 | 
			
		||||
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.test.client import RequestFactory
 | 
			
		||||
from django.urls.base import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from django_otp.plugins.otp_totp.models import TOTPDevice
 | 
			
		||||
from rest_framework.exceptions import ValidationError
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -26,7 +26,7 @@ from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
 | 
			
		||||
from authentik.stages.identification.models import IdentificationStage, UserFields
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthenticatorValidateStageTests(TestCase):
 | 
			
		||||
class AuthenticatorValidateStageTests(APITestCase):
 | 
			
		||||
    """Test validator stage"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
"""captcha tests"""
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -16,13 +16,12 @@ RECAPTCHA_PUBLIC_KEY = "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
 | 
			
		||||
RECAPTCHA_PRIVATE_KEY = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestCaptchaStage(TestCase):
 | 
			
		||||
class TestCaptchaStage(APITestCase):
 | 
			
		||||
    """Captcha tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.user = User.objects.create_user(username="unittest", email="test@beryju.org")
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-captcha",
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
"""consent tests"""
 | 
			
		||||
from time import sleep
 | 
			
		||||
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import Application, User
 | 
			
		||||
from authentik.core.tasks import clean_expired_models
 | 
			
		||||
@ -15,7 +15,7 @@ from authentik.flows.views import SESSION_KEY_PLAN
 | 
			
		||||
from authentik.stages.consent.models import ConsentMode, ConsentStage, UserConsent
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestConsentStage(TestCase):
 | 
			
		||||
class TestConsentStage(APITestCase):
 | 
			
		||||
    """Consent tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
@ -25,7 +25,6 @@ class TestConsentStage(TestCase):
 | 
			
		||||
            name="test-application",
 | 
			
		||||
            slug="test-application",
 | 
			
		||||
        )
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
    def test_always_required(self):
 | 
			
		||||
        """Test always required consent"""
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
"""deny tests"""
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -12,13 +12,12 @@ from authentik.flows.views import SESSION_KEY_PLAN
 | 
			
		||||
from authentik.stages.deny.models import DenyStage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserDenyStage(TestCase):
 | 
			
		||||
class TestUserDenyStage(APITestCase):
 | 
			
		||||
    """Deny tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.user = User.objects.create(username="unittest", email="test@beryju.org")
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-logout",
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
"""dummy tests"""
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -9,7 +9,7 @@ from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
 | 
			
		||||
from authentik.stages.dummy.models import DummyStage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestDummyStage(TestCase):
 | 
			
		||||
class TestDummyStage(APITestCase):
 | 
			
		||||
    """Dummy tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
 | 
			
		||||
@ -4,8 +4,8 @@ from unittest.mock import MagicMock, patch
 | 
			
		||||
 | 
			
		||||
from django.core import mail
 | 
			
		||||
from django.core.mail.backends.locmem import EmailBackend
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.events.models import Event, EventAction
 | 
			
		||||
@ -16,13 +16,12 @@ from authentik.flows.views import SESSION_KEY_PLAN
 | 
			
		||||
from authentik.stages.email.models import EmailStage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestEmailStageSending(TestCase):
 | 
			
		||||
class TestEmailStageSending(APITestCase):
 | 
			
		||||
    """Email tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.user = User.objects.create_user(username="unittest", email="test@beryju.org")
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-email",
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,10 @@
 | 
			
		||||
from unittest.mock import MagicMock, patch
 | 
			
		||||
 | 
			
		||||
from django.core import mail
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from django.utils.http import urlencode
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import Token, User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -17,13 +17,12 @@ from authentik.stages.email.models import EmailStage
 | 
			
		||||
from authentik.stages.email.stage import QS_KEY_TOKEN
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestEmailStage(TestCase):
 | 
			
		||||
class TestEmailStage(APITestCase):
 | 
			
		||||
    """Email tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.user = User.objects.create_user(username="unittest", email="test@beryju.org")
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-email",
 | 
			
		||||
 | 
			
		||||
@ -96,7 +96,9 @@ class IdentificationChallengeResponse(ChallengeResponse):
 | 
			
		||||
            # No password stage select, don't validate the password
 | 
			
		||||
            return attrs
 | 
			
		||||
 | 
			
		||||
        password = attrs["password"]
 | 
			
		||||
        password = attrs.get("password", None)
 | 
			
		||||
        if not password:
 | 
			
		||||
            LOGGER.warning("Password not set for ident+auth attempt")
 | 
			
		||||
        try:
 | 
			
		||||
            user = authenticate(
 | 
			
		||||
                self.stage.request,
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
"""identification tests"""
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -13,7 +13,7 @@ from authentik.stages.password import BACKEND_INBUILT
 | 
			
		||||
from authentik.stages.password.models import PasswordStage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestIdentificationStage(TestCase):
 | 
			
		||||
class TestIdentificationStage(APITestCase):
 | 
			
		||||
    """Identification tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
@ -22,7 +22,6 @@ class TestIdentificationStage(TestCase):
 | 
			
		||||
        self.user = User.objects.create_user(
 | 
			
		||||
            username="unittest", email="test@beryju.org", password=self.password
 | 
			
		||||
        )
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        # OAuthSource for the login view
 | 
			
		||||
        source = OAuthSource.objects.create(name="test", slug="test")
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
# Generated by Django 3.2.6 on 2021-09-01 12:11
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
import authentik.core.models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("authentik_stages_invitation", "0004_invitation_single_use"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="invitation",
 | 
			
		||||
            name="expiring",
 | 
			
		||||
            field=models.BooleanField(default=True),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="invitation",
 | 
			
		||||
            name="expires",
 | 
			
		||||
            field=models.DateTimeField(default=authentik.core.models.default_token_duration),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.views import View
 | 
			
		||||
from rest_framework.serializers import BaseSerializer
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.core.models import ExpiringModel, User
 | 
			
		||||
from authentik.flows.models import Stage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,7 @@ class InvitationStage(Stage):
 | 
			
		||||
        verbose_name_plural = _("Invitation Stages")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Invitation(models.Model):
 | 
			
		||||
class Invitation(ExpiringModel):
 | 
			
		||||
    """Single-use invitation link"""
 | 
			
		||||
 | 
			
		||||
    invite_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
 | 
			
		||||
@ -59,7 +59,6 @@ class Invitation(models.Model):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    created_by = models.ForeignKey(User, on_delete=models.CASCADE)
 | 
			
		||||
    expires = models.DateTimeField(default=None, blank=True, null=True)
 | 
			
		||||
    fixed_data = models.JSONField(
 | 
			
		||||
        default=dict,
 | 
			
		||||
        blank=True,
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
"""invitation tests"""
 | 
			
		||||
from unittest.mock import MagicMock, patch
 | 
			
		||||
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from django.utils.http import urlencode
 | 
			
		||||
@ -21,13 +20,12 @@ from authentik.stages.password import BACKEND_INBUILT
 | 
			
		||||
from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserLoginStage(TestCase):
 | 
			
		||||
class TestUserLoginStage(APITestCase):
 | 
			
		||||
    """Login tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.user = User.objects.create(username="unittest", email="test@beryju.org")
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-invitation",
 | 
			
		||||
 | 
			
		||||
@ -32,9 +32,7 @@ PLAN_CONTEXT_METHOD_ARGS = "auth_method_args"
 | 
			
		||||
SESSION_INVALID_TRIES = "user_invalid_tries"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def authenticate(
 | 
			
		||||
    request: HttpRequest, backends: list[str], **credentials: dict[str, Any]
 | 
			
		||||
) -> Optional[User]:
 | 
			
		||||
def authenticate(request: HttpRequest, backends: list[str], **credentials: Any) -> Optional[User]:
 | 
			
		||||
    """If the given credentials are valid, return a User object.
 | 
			
		||||
 | 
			
		||||
    Customized version of django's authenticate, which accepts a list of backends"""
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,9 @@
 | 
			
		||||
from unittest.mock import MagicMock, patch
 | 
			
		||||
 | 
			
		||||
from django.core.exceptions import PermissionDenied
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -20,7 +20,7 @@ from authentik.stages.password.models import PasswordStage
 | 
			
		||||
MOCK_BACKEND_AUTHENTICATE = MagicMock(side_effect=PermissionDenied("test"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestPasswordStage(TestCase):
 | 
			
		||||
class TestPasswordStage(APITestCase):
 | 
			
		||||
    """Password tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
@ -29,7 +29,6 @@ class TestPasswordStage(TestCase):
 | 
			
		||||
        self.user = User.objects.create_user(
 | 
			
		||||
            username="unittest", email="test@beryju.org", password=self.password
 | 
			
		||||
        )
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-password",
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
"""Prompt tests"""
 | 
			
		||||
from unittest.mock import MagicMock, patch
 | 
			
		||||
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.exceptions import ErrorDetail
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -17,13 +17,12 @@ from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
 | 
			
		||||
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT, PromptChallengeResponse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestPromptStage(TestCase):
 | 
			
		||||
class TestPromptStage(APITestCase):
 | 
			
		||||
    """Prompt tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.user = User.objects.create(username="unittest", email="test@beryju.org")
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-prompt",
 | 
			
		||||
@ -97,7 +96,6 @@ class TestPromptStage(TestCase):
 | 
			
		||||
                static_prompt,
 | 
			
		||||
            ]
 | 
			
		||||
        )
 | 
			
		||||
        self.stage.save()
 | 
			
		||||
 | 
			
		||||
        self.prompt_data = {
 | 
			
		||||
            username_prompt.field_key: "test-username",
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
"""delete tests"""
 | 
			
		||||
from unittest.mock import patch
 | 
			
		||||
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -15,14 +15,13 @@ from authentik.flows.views import SESSION_KEY_PLAN
 | 
			
		||||
from authentik.stages.user_delete.models import UserDeleteStage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserDeleteStage(TestCase):
 | 
			
		||||
class TestUserDeleteStage(APITestCase):
 | 
			
		||||
    """Delete tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.username = "qerqwerqrwqwerwq"
 | 
			
		||||
        self.user = User.objects.create(username=self.username, email="test@beryju.org")
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-delete",
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,9 @@
 | 
			
		||||
from time import sleep
 | 
			
		||||
from unittest.mock import patch
 | 
			
		||||
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -16,13 +16,12 @@ from authentik.flows.views import SESSION_KEY_PLAN
 | 
			
		||||
from authentik.stages.user_login.models import UserLoginStage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserLoginStage(TestCase):
 | 
			
		||||
class TestUserLoginStage(APITestCase):
 | 
			
		||||
    """Login tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.user = User.objects.create(username="unittest", email="test@beryju.org")
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-login",
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
"""logout tests"""
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import User
 | 
			
		||||
from authentik.flows.challenge import ChallengeTypes
 | 
			
		||||
@ -14,13 +14,12 @@ from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
 | 
			
		||||
from authentik.stages.user_logout.models import UserLogoutStage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserLogoutStage(TestCase):
 | 
			
		||||
class TestUserLogoutStage(APITestCase):
 | 
			
		||||
    """Logout tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.user = User.objects.create(username="unittest", email="test@beryju.org")
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-logout",
 | 
			
		||||
 | 
			
		||||
@ -3,9 +3,9 @@ import string
 | 
			
		||||
from random import SystemRandom
 | 
			
		||||
from unittest.mock import patch
 | 
			
		||||
 | 
			
		||||
from django.test import Client, TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.encoding import force_str
 | 
			
		||||
from rest_framework.test import APITestCase
 | 
			
		||||
 | 
			
		||||
from authentik.core.models import USER_ATTRIBUTE_SOURCES, Source, User, UserSourceConnection
 | 
			
		||||
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION
 | 
			
		||||
@ -19,13 +19,11 @@ from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
 | 
			
		||||
from authentik.stages.user_write.models import UserWriteStage
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUserWriteStage(TestCase):
 | 
			
		||||
class TestUserWriteStage(APITestCase):
 | 
			
		||||
    """Write tests"""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super().setUp()
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
 | 
			
		||||
        self.flow = Flow.objects.create(
 | 
			
		||||
            name="test-write",
 | 
			
		||||
            slug="test-write",
 | 
			
		||||
 | 
			
		||||
@ -1,120 +0,0 @@
 | 
			
		||||
trigger:
 | 
			
		||||
  batch: true
 | 
			
		||||
  branches:
 | 
			
		||||
    include:
 | 
			
		||||
      - master
 | 
			
		||||
      - next
 | 
			
		||||
      - version-*
 | 
			
		||||
 | 
			
		||||
stages:
 | 
			
		||||
  - stage: generate
 | 
			
		||||
    jobs:
 | 
			
		||||
      - job: generate_api
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: GoTool@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              version: '1.16.3'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: make gen-outpost
 | 
			
		||||
          - task: PublishPipelineArtifact@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              targetPath: 'api/'
 | 
			
		||||
              artifact: 'go_api_client'
 | 
			
		||||
              publishLocation: 'pipeline'
 | 
			
		||||
  - stage: lint
 | 
			
		||||
    jobs:
 | 
			
		||||
      - job: golint
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: GoTool@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              version: '1.16.3'
 | 
			
		||||
          - task: DownloadPipelineArtifact@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              buildType: 'current'
 | 
			
		||||
              artifactName: 'go_api_client'
 | 
			
		||||
              path: "api/"
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                mkdir -p web/dist
 | 
			
		||||
                mkdir -p website/help
 | 
			
		||||
                touch web/dist/test website/help/test
 | 
			
		||||
                docker run \
 | 
			
		||||
                  --rm \
 | 
			
		||||
                  -v $(pwd):/app \
 | 
			
		||||
                  -w /app \
 | 
			
		||||
                  golangci/golangci-lint:v1.39.0 \
 | 
			
		||||
                  golangci-lint run -v --timeout 200s
 | 
			
		||||
  - stage: build_docker
 | 
			
		||||
    jobs:
 | 
			
		||||
      - job: proxy_build_docker
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: GoTool@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              version: '1.16.3'
 | 
			
		||||
          - task: Bash@3
 | 
			
		||||
            inputs:
 | 
			
		||||
              targetType: 'inline'
 | 
			
		||||
              script: |
 | 
			
		||||
                python ./scripts/az_do_set_branch.py
 | 
			
		||||
          - task: Docker@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              containerRegistry: 'beryjuorg-harbor'
 | 
			
		||||
              repository: 'authentik/outpost-proxy'
 | 
			
		||||
              command: 'build'
 | 
			
		||||
              Dockerfile: 'proxy.Dockerfile'
 | 
			
		||||
              buildContext: '$(Build.SourcesDirectory)'
 | 
			
		||||
              tags: |
 | 
			
		||||
                gh-$(branchName)
 | 
			
		||||
                gh-$(branchName)-$(timestamp)
 | 
			
		||||
                gh-$(Build.SourceVersion)
 | 
			
		||||
              arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)'
 | 
			
		||||
          - task: Docker@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              containerRegistry: 'beryjuorg-harbor'
 | 
			
		||||
              repository: 'authentik/outpost-proxy'
 | 
			
		||||
              command: 'push'
 | 
			
		||||
              tags: |
 | 
			
		||||
                gh-$(branchName)
 | 
			
		||||
                gh-$(branchName)-$(timestamp)
 | 
			
		||||
                gh-$(Build.SourceVersion)
 | 
			
		||||
      - job: ldap_build_docker
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: GoTool@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              version: '1.16.3'
 | 
			
		||||
          - task: Bash@3
 | 
			
		||||
            inputs:
 | 
			
		||||
              targetType: 'inline'
 | 
			
		||||
              script: |
 | 
			
		||||
                python ./scripts/az_do_set_branch.py
 | 
			
		||||
          - task: Docker@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              containerRegistry: 'beryjuorg-harbor'
 | 
			
		||||
              repository: 'authentik/outpost-ldap'
 | 
			
		||||
              command: 'build'
 | 
			
		||||
              Dockerfile: 'ldap.Dockerfile'
 | 
			
		||||
              buildContext: '$(Build.SourcesDirectory)'
 | 
			
		||||
              tags: |
 | 
			
		||||
                gh-$(branchName)
 | 
			
		||||
                gh-$(branchName)-$(timestamp)
 | 
			
		||||
                gh-$(Build.SourceVersion)
 | 
			
		||||
              arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)'
 | 
			
		||||
          - task: Docker@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              containerRegistry: 'beryjuorg-harbor'
 | 
			
		||||
              repository: 'authentik/outpost-ldap'
 | 
			
		||||
              command: 'push'
 | 
			
		||||
              tags: |
 | 
			
		||||
                gh-$(branchName)
 | 
			
		||||
                gh-$(branchName)-$(timestamp)
 | 
			
		||||
                gh-$(Build.SourceVersion)
 | 
			
		||||
@ -1,426 +0,0 @@
 | 
			
		||||
trigger:
 | 
			
		||||
  batch: true
 | 
			
		||||
  branches:
 | 
			
		||||
    include:
 | 
			
		||||
      - master
 | 
			
		||||
      - next
 | 
			
		||||
      - version-*
 | 
			
		||||
  paths:
 | 
			
		||||
    exclude:
 | 
			
		||||
      - website
 | 
			
		||||
      - outpost
 | 
			
		||||
 | 
			
		||||
resources:
 | 
			
		||||
  - repo: self
 | 
			
		||||
 | 
			
		||||
variables:
 | 
			
		||||
  - name: POSTGRES_DB
 | 
			
		||||
    value: authentik
 | 
			
		||||
  - name: POSTGRES_USER
 | 
			
		||||
    value: authentik
 | 
			
		||||
  - name: POSTGRES_PASSWORD
 | 
			
		||||
    value: "EK-5jnKfjrGRm<77"
 | 
			
		||||
  - group: coverage
 | 
			
		||||
 | 
			
		||||
stages:
 | 
			
		||||
  - stage: Lint_and_test
 | 
			
		||||
    jobs:
 | 
			
		||||
      - job: pylint
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.9'
 | 
			
		||||
          - task: DockerCompose@0
 | 
			
		||||
            displayName: Run services
 | 
			
		||||
            inputs:
 | 
			
		||||
              dockerComposeFile: 'scripts/ci.docker-compose.yml'
 | 
			
		||||
              action: 'Run services'
 | 
			
		||||
              buildImages: false
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                sudo apt update
 | 
			
		||||
                sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
                sudo pip install -U wheel pipenv
 | 
			
		||||
                pipenv install --dev
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                pipenv run python -m scripts.generate_ci_config
 | 
			
		||||
                pipenv run pylint authentik tests lifecycle
 | 
			
		||||
      - job: black
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.9'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                sudo apt update
 | 
			
		||||
                sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
                sudo pip install -U wheel pipenv
 | 
			
		||||
                pipenv install --dev
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: pipenv run black --check authentik tests lifecycle
 | 
			
		||||
      - job: isort
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.9'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                sudo apt update
 | 
			
		||||
                sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
                sudo pip install -U wheel pipenv
 | 
			
		||||
                pipenv install --dev
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: pipenv run isort --check authentik tests lifecycle
 | 
			
		||||
      - job: bandit
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.9'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                sudo apt update
 | 
			
		||||
                sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
                sudo pip install -U wheel pipenv
 | 
			
		||||
                pipenv install --dev
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: pipenv run bandit -r authentik tests lifecycle
 | 
			
		||||
      - job: pyright
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: ubuntu-latest
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: UseNode@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              version: '12.x'
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.9'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: npm install -g pyright@1.1.136
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                sudo apt update
 | 
			
		||||
                sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
                sudo pip install -U wheel pipenv
 | 
			
		||||
                pipenv install --dev
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: pipenv run pyright e2e lifecycle
 | 
			
		||||
      - job: migrations
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.9'
 | 
			
		||||
          - task: DockerCompose@0
 | 
			
		||||
            displayName: Run services
 | 
			
		||||
            inputs:
 | 
			
		||||
              dockerComposeFile: 'scripts/ci.docker-compose.yml'
 | 
			
		||||
              action: 'Run services'
 | 
			
		||||
              buildImages: false
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                sudo apt update
 | 
			
		||||
                sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
                sudo pip install -U wheel pipenv
 | 
			
		||||
                pipenv install --dev
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                pipenv run python -m scripts.generate_ci_config
 | 
			
		||||
                pipenv run python -m lifecycle.migrate
 | 
			
		||||
      - job: migrations_from_previous_release
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.8'
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.9'
 | 
			
		||||
          - task: DockerCompose@0
 | 
			
		||||
            displayName: Run services
 | 
			
		||||
            inputs:
 | 
			
		||||
              dockerComposeFile: 'scripts/ci.docker-compose.yml'
 | 
			
		||||
              action: 'Run services'
 | 
			
		||||
              buildImages: false
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            displayName: Prepare Last tagged release
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                # Copy current, latest config to local
 | 
			
		||||
                cp authentik/lib/default.yml local.env.yml
 | 
			
		||||
                git checkout $(git describe --abbrev=0 --match 'version/*')
 | 
			
		||||
                sudo apt update
 | 
			
		||||
                sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
                sudo pip install -U wheel pipenv
 | 
			
		||||
                pipenv install --dev
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            displayName: Migrate to last tagged release
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                pipenv run python -m scripts.generate_ci_config
 | 
			
		||||
                pipenv run python -m lifecycle.migrate
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            displayName: Install current branch
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                set -x
 | 
			
		||||
                git checkout ${{ variables.branchName }}
 | 
			
		||||
                pipenv sync --dev
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            displayName: Migrate to current branch
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                pipenv run python -m scripts.generate_ci_config
 | 
			
		||||
                pipenv run python -m lifecycle.migrate
 | 
			
		||||
      - job: coverage_unittest
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.9'
 | 
			
		||||
          - task: DockerCompose@0
 | 
			
		||||
            displayName: Run services
 | 
			
		||||
            inputs:
 | 
			
		||||
              dockerComposeFile: 'scripts/ci.docker-compose.yml'
 | 
			
		||||
              action: 'Run services'
 | 
			
		||||
              buildImages: false
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                sudo apt update
 | 
			
		||||
                sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
                sudo pip install -U wheel pipenv
 | 
			
		||||
                pipenv install --dev
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            displayName: Run full test suite
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                pipenv run python -m scripts.generate_ci_config
 | 
			
		||||
                pipenv run make test
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                mkdir output-unittest
 | 
			
		||||
                mv unittest.xml output-unittest/unittest.xml
 | 
			
		||||
                mv .coverage output-unittest/coverage
 | 
			
		||||
          - task: PublishPipelineArtifact@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              targetPath: 'output-unittest/'
 | 
			
		||||
              artifact: 'coverage-unittest'
 | 
			
		||||
              publishLocation: 'pipeline'
 | 
			
		||||
      - job: coverage_integration
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.9'
 | 
			
		||||
          - task: DockerCompose@0
 | 
			
		||||
            displayName: Run services
 | 
			
		||||
            inputs:
 | 
			
		||||
              dockerComposeFile: 'scripts/ci.docker-compose.yml'
 | 
			
		||||
              action: 'Run services'
 | 
			
		||||
              buildImages: false
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                sudo apt update
 | 
			
		||||
                sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
                sudo pip install -U wheel pipenv
 | 
			
		||||
                pipenv install --dev
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            displayName: Install K3d and prepare
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                wget -q -O - https://raw.githubusercontent.com/rancher/k3d/main/install.sh | bash
 | 
			
		||||
                k3d cluster create
 | 
			
		||||
                k3d kubeconfig write -o ~/.kube/config --overwrite
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            displayName: Run full test suite
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                pipenv run python -m scripts.generate_ci_config
 | 
			
		||||
                pipenv run make test-integration
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                mkdir output-integration
 | 
			
		||||
                mv unittest.xml output-integration/unittest.xml
 | 
			
		||||
                mv .coverage output-integration/coverage
 | 
			
		||||
          - task: PublishPipelineArtifact@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              targetPath: 'output-integration/'
 | 
			
		||||
              artifact: 'coverage-integration'
 | 
			
		||||
              publishLocation: 'pipeline'
 | 
			
		||||
      - job: coverage_e2e
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.9'
 | 
			
		||||
          - task: NodeTool@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '16.x'
 | 
			
		||||
          - task: DockerCompose@0
 | 
			
		||||
            displayName: Run services
 | 
			
		||||
            inputs:
 | 
			
		||||
              dockerComposeFile: 'scripts/ci.docker-compose.yml'
 | 
			
		||||
              action: 'Run services'
 | 
			
		||||
              buildImages: false
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                sudo apt update
 | 
			
		||||
                sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
                sudo pip install -U wheel pipenv
 | 
			
		||||
                pipenv install --dev --python python3.9
 | 
			
		||||
          - task: DockerCompose@0
 | 
			
		||||
            displayName: Run ChromeDriver
 | 
			
		||||
            inputs:
 | 
			
		||||
              dockerComposeFile: 'tests/e2e/ci.docker-compose.yml'
 | 
			
		||||
              action: 'Run a specific service'
 | 
			
		||||
              serviceName: 'chrome'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            displayName: Build static files for e2e
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                cd web
 | 
			
		||||
                npm i
 | 
			
		||||
                npm run build
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            displayName: Run full test suite
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                pipenv run python -m scripts.generate_ci_config
 | 
			
		||||
                pipenv run make test-e2e
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            condition: always()
 | 
			
		||||
            displayName: Cleanup
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                docker stop $(docker ps -aq)
 | 
			
		||||
                docker container prune -f
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            displayName: Prepare unittests and coverage for upload
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                mkdir output-e2e
 | 
			
		||||
                mv unittest.xml output-e2e/unittest.xml
 | 
			
		||||
                mv .coverage output-e2e/coverage
 | 
			
		||||
          - task: PublishPipelineArtifact@1
 | 
			
		||||
            condition: failed()
 | 
			
		||||
            displayName: Upload screenshots if selenium tests fail
 | 
			
		||||
            inputs:
 | 
			
		||||
              targetPath: 'selenium_screenshots/'
 | 
			
		||||
              artifact: 'selenium screenshots'
 | 
			
		||||
              publishLocation: 'pipeline'
 | 
			
		||||
          - task: PublishPipelineArtifact@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              targetPath: 'output-e2e/'
 | 
			
		||||
              artifact: 'coverage-e2e'
 | 
			
		||||
              publishLocation: 'pipeline'
 | 
			
		||||
  - stage: test_combine
 | 
			
		||||
    jobs:
 | 
			
		||||
      - job: test_coverage_combine
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: DownloadPipelineArtifact@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              buildType: 'current'
 | 
			
		||||
              artifactName: 'coverage-e2e'
 | 
			
		||||
              path: "coverage-e2e/"
 | 
			
		||||
          - task: DownloadPipelineArtifact@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              buildType: 'current'
 | 
			
		||||
              artifactName: 'coverage-integration'
 | 
			
		||||
              path: "coverage-integration/"
 | 
			
		||||
          - task: DownloadPipelineArtifact@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              buildType: 'current'
 | 
			
		||||
              artifactName: 'coverage-unittest'
 | 
			
		||||
              path: "coverage-unittest/"
 | 
			
		||||
          - task: UsePythonVersion@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '3.9'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: |
 | 
			
		||||
                sudo apt update
 | 
			
		||||
                sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
                sudo pip install -U wheel pipenv
 | 
			
		||||
                pipenv install --dev
 | 
			
		||||
                pipenv run coverage combine coverage-e2e/coverage coverage-unittest/coverage coverage-integration/coverage
 | 
			
		||||
                pipenv run coverage xml
 | 
			
		||||
                pipenv run coverage html
 | 
			
		||||
          - task: PublishCodeCoverageResults@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              codeCoverageTool: 'Cobertura'
 | 
			
		||||
              summaryFileLocation: 'coverage.xml'
 | 
			
		||||
              pathToSources: '$(System.DefaultWorkingDirectory)'
 | 
			
		||||
          - task: PublishTestResults@2
 | 
			
		||||
            condition: succeededOrFailed()
 | 
			
		||||
            inputs:
 | 
			
		||||
              testResultsFormat: 'JUnit'
 | 
			
		||||
              testResultsFiles: |
 | 
			
		||||
                coverage-e2e/unittest.xml
 | 
			
		||||
                coverage-integration/unittest.xml
 | 
			
		||||
                coverage-unittest/unittest.xml
 | 
			
		||||
              mergeTestResults: true
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: bash <(curl -s https://codecov.io/bash)
 | 
			
		||||
  - stage: Build
 | 
			
		||||
    jobs:
 | 
			
		||||
      - job: build_server
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
        - task: Bash@3
 | 
			
		||||
          inputs:
 | 
			
		||||
            targetType: 'inline'
 | 
			
		||||
            script: |
 | 
			
		||||
              python ./scripts/az_do_set_branch.py
 | 
			
		||||
        - task: Docker@2
 | 
			
		||||
          inputs:
 | 
			
		||||
            containerRegistry: 'beryjuorg-harbor'
 | 
			
		||||
            repository: 'authentik/server'
 | 
			
		||||
            command: 'build'
 | 
			
		||||
            Dockerfile: 'Dockerfile'
 | 
			
		||||
            tags: |
 | 
			
		||||
              gh-$(branchName)
 | 
			
		||||
              gh-$(branchName)-$(timestamp)
 | 
			
		||||
            arguments: '--build-arg GIT_BUILD_HASH=$(Build.SourceVersion)'
 | 
			
		||||
        - task: Docker@2
 | 
			
		||||
          inputs:
 | 
			
		||||
            containerRegistry: 'beryjuorg-harbor'
 | 
			
		||||
            repository: 'authentik/server'
 | 
			
		||||
            command: 'push'
 | 
			
		||||
            tags: |
 | 
			
		||||
              gh-$(branchName)
 | 
			
		||||
              gh-$(branchName)-$(timestamp)
 | 
			
		||||
@ -60,7 +60,9 @@ func main() {
 | 
			
		||||
	for {
 | 
			
		||||
		go attemptStartBackend(g)
 | 
			
		||||
		ws.Start()
 | 
			
		||||
		if !config.G.Web.DisableEmbeddedOutpost {
 | 
			
		||||
			go attemptProxyStart(ws, u)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		<-ex
 | 
			
		||||
		running = false
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ services:
 | 
			
		||||
    networks:
 | 
			
		||||
      - internal
 | 
			
		||||
  server:
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.8.2}
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.8.4}
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    command: server
 | 
			
		||||
    environment:
 | 
			
		||||
@ -44,7 +44,7 @@ services:
 | 
			
		||||
      - "0.0.0.0:9000:9000"
 | 
			
		||||
      - "0.0.0.0:9443:9443"
 | 
			
		||||
  worker:
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.8.2}
 | 
			
		||||
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.8.4}
 | 
			
		||||
    restart: unless-stopped
 | 
			
		||||
    command: worker
 | 
			
		||||
    networks:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@ -10,7 +10,7 @@ require (
 | 
			
		||||
	github.com/go-ldap/ldap/v3 v3.4.1
 | 
			
		||||
	github.com/go-openapi/analysis v0.20.1 // indirect
 | 
			
		||||
	github.com/go-openapi/errors v0.20.0 // indirect
 | 
			
		||||
	github.com/go-openapi/runtime v0.19.30
 | 
			
		||||
	github.com/go-openapi/runtime v0.19.31
 | 
			
		||||
	github.com/go-openapi/strfmt v0.20.2
 | 
			
		||||
	github.com/go-openapi/swag v0.19.15 // indirect
 | 
			
		||||
	github.com/go-openapi/validate v0.20.2 // indirect
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							@ -205,8 +205,8 @@ github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29g
 | 
			
		||||
github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
 | 
			
		||||
github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
 | 
			
		||||
github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
 | 
			
		||||
github.com/go-openapi/runtime v0.19.30 h1:bVDeSf4HU9EMth+lHD1EthaHe1SFoUVPaUvQtkGS9g8=
 | 
			
		||||
github.com/go-openapi/runtime v0.19.30/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
 | 
			
		||||
github.com/go-openapi/runtime v0.19.31 h1:GX+MgBxN12s/tQiHNJpvHDIoZiEXAz6j6Rqg0oJcnpg=
 | 
			
		||||
github.com/go-openapi/runtime v0.19.31/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
 | 
			
		||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 | 
			
		||||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 | 
			
		||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,7 @@ type WebConfig struct {
 | 
			
		||||
	Listen                 string `yaml:"listen"`
 | 
			
		||||
	ListenTLS              string `yaml:"listen_tls"`
 | 
			
		||||
	LoadLocalFiles         bool   `yaml:"load_local_files" env:"AUTHENTIK_WEB_LOAD_LOCAL_FILES"`
 | 
			
		||||
	DisableEmbeddedOutpost bool   `yaml:"disable_embedded_outpost" env:"AUTHENTIK_WEB__DISABLE_EMBEDDED_OUTPOST"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PathsConfig struct {
 | 
			
		||||
 | 
			
		||||
@ -17,4 +17,4 @@ func OutpostUserAgent() string {
 | 
			
		||||
	return fmt.Sprintf("authentik-outpost@%s (%s)", VERSION, BUILD())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const VERSION = "2021.8.2"
 | 
			
		||||
const VERSION = "2021.8.4"
 | 
			
		||||
 | 
			
		||||
@ -152,7 +152,9 @@ func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
 | 
			
		||||
	responseReq := fe.api.FlowsApi.FlowsExecutorSolve(scsp.Context(), fe.flowSlug).Query(fe.Params.Encode())
 | 
			
		||||
	switch ch.GetComponent() {
 | 
			
		||||
	case string(StageIdentification):
 | 
			
		||||
		responseReq = responseReq.FlowChallengeResponseRequest(api.IdentificationChallengeResponseRequestAsFlowChallengeResponseRequest(api.NewIdentificationChallengeResponseRequest(fe.getAnswer(StageIdentification))))
 | 
			
		||||
		r := api.NewIdentificationChallengeResponseRequest(fe.getAnswer(StageIdentification))
 | 
			
		||||
		r.SetPassword(fe.getAnswer(StagePassword))
 | 
			
		||||
		responseReq = responseReq.FlowChallengeResponseRequest(api.IdentificationChallengeResponseRequestAsFlowChallengeResponseRequest(r))
 | 
			
		||||
	case string(StagePassword):
 | 
			
		||||
		responseReq = responseReq.FlowChallengeResponseRequest(api.PasswordChallengeResponseRequestAsFlowChallengeResponseRequest(api.NewPasswordChallengeResponseRequest(fe.getAnswer(StagePassword))))
 | 
			
		||||
	case string(StageAuthenticatorValidate):
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										32
									
								
								internal/outpost/ldap/close.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								internal/outpost/ldap/close.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
package ldap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (ls *LDAPServer) Close(boundDN string, conn net.Conn) error {
 | 
			
		||||
	for _, p := range ls.providers {
 | 
			
		||||
		p.delayDeleteUserInfo(boundDN)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pi *ProviderInstance) delayDeleteUserInfo(dn string) {
 | 
			
		||||
	ticker := time.NewTicker(30 * time.Second)
 | 
			
		||||
	quit := make(chan struct{})
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			select {
 | 
			
		||||
			case <-ticker.C:
 | 
			
		||||
				pi.boundUsersMutex.Lock()
 | 
			
		||||
				delete(pi.boundUsers, dn)
 | 
			
		||||
				pi.boundUsersMutex.Unlock()
 | 
			
		||||
				close(quit)
 | 
			
		||||
			case <-quit:
 | 
			
		||||
				ticker.Stop()
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
@ -4,7 +4,6 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/getsentry/sentry-go"
 | 
			
		||||
	goldap "github.com/go-ldap/ldap/v3"
 | 
			
		||||
@ -83,7 +82,6 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
 | 
			
		||||
	}
 | 
			
		||||
	uisp.Finish()
 | 
			
		||||
	defer pi.boundUsersMutex.Unlock()
 | 
			
		||||
	pi.delayDeleteUserInfo(username)
 | 
			
		||||
	return ldap.LDAPResultSuccess, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -100,25 +98,6 @@ func (pi *ProviderInstance) SearchAccessCheck(user api.UserSelf) *string {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pi *ProviderInstance) delayDeleteUserInfo(dn string) {
 | 
			
		||||
	ticker := time.NewTicker(30 * time.Second)
 | 
			
		||||
	quit := make(chan struct{})
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			select {
 | 
			
		||||
			case <-ticker.C:
 | 
			
		||||
				pi.boundUsersMutex.Lock()
 | 
			
		||||
				delete(pi.boundUsers, dn)
 | 
			
		||||
				pi.boundUsersMutex.Unlock()
 | 
			
		||||
				close(quit)
 | 
			
		||||
			case <-quit:
 | 
			
		||||
				ticker.Stop()
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pi *ProviderInstance) TimerFlowCacheExpiry() {
 | 
			
		||||
	fe := outpost.NewFlowExecutor(context.Background(), pi.flowSlug, pi.s.ac.Client.GetConfig(), log.Fields{})
 | 
			
		||||
	fe.Params.Add("goauthentik.io/outpost/ldap", "true")
 | 
			
		||||
 | 
			
		||||
@ -83,5 +83,6 @@ func NewServer(ac *ak.APIController) *LDAPServer {
 | 
			
		||||
	ls.defaultCert = &defaultCert
 | 
			
		||||
	s.BindFunc("", ls)
 | 
			
		||||
	s.SearchFunc("", ls)
 | 
			
		||||
	s.CloseFunc("", ls)
 | 
			
		||||
	return ls
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,7 @@ type providerBundle struct {
 | 
			
		||||
	Host  string
 | 
			
		||||
 | 
			
		||||
	endSessionUrl string
 | 
			
		||||
	Mode          *api.ProxyMode
 | 
			
		||||
 | 
			
		||||
	cert *tls.Certificate
 | 
			
		||||
 | 
			
		||||
@ -38,8 +39,8 @@ func intToPointer(i int) *int {
 | 
			
		||||
func (pb *providerBundle) replaceLocal(url string) string {
 | 
			
		||||
	if strings.HasPrefix(url, "http://localhost:8000") {
 | 
			
		||||
		authentikHost, c := pb.s.ak.Outpost.Config["authentik_host"]
 | 
			
		||||
		if !c {
 | 
			
		||||
			pb.log.Warning("Outpost has localhost API Connection but no authentik_host is configured.")
 | 
			
		||||
		if !c || authentikHost == "" {
 | 
			
		||||
			pb.log.Warning("Outpost has localhost/blank API Connection but no authentik_host is configured.")
 | 
			
		||||
			return url
 | 
			
		||||
		}
 | 
			
		||||
		f := strings.ReplaceAll(url, "http://localhost:8000", authentikHost.(string))
 | 
			
		||||
@ -49,6 +50,10 @@ func (pb *providerBundle) replaceLocal(url string) string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pb *providerBundle) prepareOpts(provider api.ProxyOutpostConfig) *options.Options {
 | 
			
		||||
	// We need to save the mode in the bundle
 | 
			
		||||
	// Since for the embedded outpost we only switch for fully proxy providers
 | 
			
		||||
	pb.Mode = provider.Mode
 | 
			
		||||
 | 
			
		||||
	externalHost, err := url.Parse(provider.ExternalHost)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.WithError(err).Warning("Failed to parse URL, skipping provider")
 | 
			
		||||
 | 
			
		||||
@ -121,7 +121,7 @@ func NewOAuthProxy(opts *options.Options, provider api.ProxyOutpostConfig, c *ht
 | 
			
		||||
		redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Printf("proxy instance configured for Client ID: %s", opts.ClientID)
 | 
			
		||||
	logger.WithField("auth_url", opts.GetProvider().Data().LoginURL).WithField("client_id", opts.ClientID).Info("proxy instance configured")
 | 
			
		||||
 | 
			
		||||
	sessionChain := buildSessionChain(opts, sessionStore)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import (
 | 
			
		||||
	"net/http/httputil"
 | 
			
		||||
	"net/url"
 | 
			
		||||
 | 
			
		||||
	"goauthentik.io/api"
 | 
			
		||||
	"goauthentik.io/internal/utils/web"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -36,12 +37,14 @@ func (ws *WebServer) configureProxy() {
 | 
			
		||||
	ws.m.PathPrefix("/").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		host := web.GetHost(r)
 | 
			
		||||
		if ws.ProxyServer != nil {
 | 
			
		||||
			if _, ok := ws.ProxyServer.Handlers[host]; ok {
 | 
			
		||||
			if p, ok := ws.ProxyServer.Handlers[host]; ok {
 | 
			
		||||
				if *p.Mode == api.PROXYMODE_PROXY {
 | 
			
		||||
					ws.log.WithField("host", host).Trace("routing to proxy outpost")
 | 
			
		||||
					ws.ProxyServer.Handler(rw, r)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ws.log.WithField("host", host).Trace("routing to application server")
 | 
			
		||||
		rp.ServeHTTP(rw, r)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2251
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										2251
									
								
								schema.yml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,13 +0,0 @@
 | 
			
		||||
"""Helper script to get the actual branch name, docker safe"""
 | 
			
		||||
import os
 | 
			
		||||
from time import time
 | 
			
		||||
 | 
			
		||||
env_pr_branch = "SYSTEM_PULLREQUEST_SOURCEBRANCH"
 | 
			
		||||
default_branch = "BUILD_SOURCEBRANCHNAME"
 | 
			
		||||
 | 
			
		||||
branch_name = os.environ[default_branch]
 | 
			
		||||
if env_pr_branch in os.environ:
 | 
			
		||||
    branch_name = os.environ[env_pr_branch].replace("/", "-")
 | 
			
		||||
 | 
			
		||||
print("##vso[task.setvariable variable=branchName]%s" % branch_name)
 | 
			
		||||
print("##vso[task.setvariable variable=timestamp]%s" % int(time()))
 | 
			
		||||
							
								
								
									
										7
									
								
								scripts/ci_prepare.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								scripts/ci_prepare.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
docker-compose -f scripts/ci.docker-compose.yml up -d
 | 
			
		||||
sudo apt update
 | 
			
		||||
sudo apt install -y libxmlsec1-dev pkg-config
 | 
			
		||||
sudo pip install -U wheel pipenv
 | 
			
		||||
pipenv install --dev
 | 
			
		||||
pipenv run python -m scripts.generate_ci_config
 | 
			
		||||
							
								
								
									
										16
									
								
								scripts/gh_do_set_branch.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								scripts/gh_do_set_branch.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
"""Helper script to get the actual branch name, docker safe"""
 | 
			
		||||
import os
 | 
			
		||||
from time import time
 | 
			
		||||
 | 
			
		||||
env_pr_branch = "GITHUB_HEAD_REF"
 | 
			
		||||
default_branch = "GITHUB_REF"
 | 
			
		||||
sha = "GITHUB_SHA"
 | 
			
		||||
 | 
			
		||||
branch_name = os.environ[default_branch]
 | 
			
		||||
if os.environ.get(env_pr_branch, "") != "":
 | 
			
		||||
    branch_name = os.environ[env_pr_branch]
 | 
			
		||||
branch_name = branch_name.replace("refs/heads/", "").replace("/", "-")
 | 
			
		||||
 | 
			
		||||
print("##[set-output name=branchName]%s" % branch_name)
 | 
			
		||||
print("##[set-output name=timestamp]%s" % int(time()))
 | 
			
		||||
print("##[set-output name=sha]%s" % os.environ[sha])
 | 
			
		||||
@ -6,4 +6,3 @@ dist
 | 
			
		||||
coverage
 | 
			
		||||
# don't lint generated code
 | 
			
		||||
api/
 | 
			
		||||
azure-pipelines.yml
 | 
			
		||||
 | 
			
		||||
@ -1,93 +0,0 @@
 | 
			
		||||
trigger:
 | 
			
		||||
  batch: true
 | 
			
		||||
  branches:
 | 
			
		||||
    include:
 | 
			
		||||
      - master
 | 
			
		||||
      - next
 | 
			
		||||
      - version-*
 | 
			
		||||
 | 
			
		||||
stages:
 | 
			
		||||
  - stage: lint
 | 
			
		||||
    jobs:
 | 
			
		||||
      - job: eslint
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: NodeTool@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '16.x'
 | 
			
		||||
            displayName: 'Install Node.js'
 | 
			
		||||
          - task: Npm@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              command: 'install'
 | 
			
		||||
              workingDir: 'web/'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: make gen-web
 | 
			
		||||
          - task: Npm@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              command: 'custom'
 | 
			
		||||
              workingDir: 'web/'
 | 
			
		||||
              customCommand: 'run lint'
 | 
			
		||||
      - job: prettier
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: NodeTool@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '16.x'
 | 
			
		||||
            displayName: 'Install Node.js'
 | 
			
		||||
          - task: Npm@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              command: 'install'
 | 
			
		||||
              workingDir: 'web/'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: make gen-web
 | 
			
		||||
          - task: Npm@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              command: 'custom'
 | 
			
		||||
              workingDir: 'web/'
 | 
			
		||||
              customCommand: 'run prettier-check'
 | 
			
		||||
      - job: lit_analyse
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: NodeTool@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '16.x'
 | 
			
		||||
            displayName: 'Install Node.js'
 | 
			
		||||
          - task: Npm@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              command: 'install'
 | 
			
		||||
              workingDir: 'web/'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: make gen-web
 | 
			
		||||
          - task: Npm@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              command: 'custom'
 | 
			
		||||
              workingDir: 'web/'
 | 
			
		||||
              customCommand: 'run lit-analyse'
 | 
			
		||||
  - stage: build_local
 | 
			
		||||
    jobs:
 | 
			
		||||
      - job: build
 | 
			
		||||
        pool:
 | 
			
		||||
          vmImage: 'ubuntu-latest'
 | 
			
		||||
        steps:
 | 
			
		||||
          - task: NodeTool@0
 | 
			
		||||
            inputs:
 | 
			
		||||
              versionSpec: '16.x'
 | 
			
		||||
            displayName: 'Install Node.js'
 | 
			
		||||
          - task: Npm@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              command: 'install'
 | 
			
		||||
              workingDir: 'web/'
 | 
			
		||||
          - task: CmdLine@2
 | 
			
		||||
            inputs:
 | 
			
		||||
              script: make gen-web
 | 
			
		||||
          - task: Npm@1
 | 
			
		||||
            inputs:
 | 
			
		||||
              command: 'custom'
 | 
			
		||||
              workingDir: 'web/'
 | 
			
		||||
              customCommand: 'run build'
 | 
			
		||||
							
								
								
									
										334
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										334
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -15,23 +15,23 @@
 | 
			
		||||
                "@babel/preset-env": "^7.15.0",
 | 
			
		||||
                "@babel/preset-typescript": "^7.15.0",
 | 
			
		||||
                "@fortawesome/fontawesome-free": "^5.15.4",
 | 
			
		||||
                "@goauthentik/api": "^2021.8.1-1629986812",
 | 
			
		||||
                "@goauthentik/api": "^2021.8.3-1630597235",
 | 
			
		||||
                "@lingui/cli": "^3.10.2",
 | 
			
		||||
                "@lingui/core": "^3.10.4",
 | 
			
		||||
                "@lingui/macro": "^3.10.2",
 | 
			
		||||
                "@patternfly/patternfly": "^4.125.3",
 | 
			
		||||
                "@patternfly/patternfly": "^4.132.2",
 | 
			
		||||
                "@polymer/iron-form": "^3.0.1",
 | 
			
		||||
                "@polymer/paper-input": "^3.2.1",
 | 
			
		||||
                "@rollup/plugin-babel": "^5.3.0",
 | 
			
		||||
                "@rollup/plugin-replace": "^3.0.0",
 | 
			
		||||
                "@rollup/plugin-typescript": "^8.2.5",
 | 
			
		||||
                "@sentry/browser": "^6.11.0",
 | 
			
		||||
                "@sentry/tracing": "^6.11.0",
 | 
			
		||||
                "@sentry/browser": "^6.12.0",
 | 
			
		||||
                "@sentry/tracing": "^6.12.0",
 | 
			
		||||
                "@types/chart.js": "^2.9.34",
 | 
			
		||||
                "@types/codemirror": "5.60.2",
 | 
			
		||||
                "@types/grecaptcha": "^3.0.3",
 | 
			
		||||
                "@typescript-eslint/eslint-plugin": "^4.29.3",
 | 
			
		||||
                "@typescript-eslint/parser": "^4.29.3",
 | 
			
		||||
                "@typescript-eslint/eslint-plugin": "^4.30.0",
 | 
			
		||||
                "@typescript-eslint/parser": "^4.30.0",
 | 
			
		||||
                "@webcomponents/webcomponentsjs": "^2.6.0",
 | 
			
		||||
                "babel-plugin-macros": "^3.1.0",
 | 
			
		||||
                "base64-js": "^1.5.1",
 | 
			
		||||
@ -59,7 +59,7 @@
 | 
			
		||||
                "rollup-plugin-terser": "^7.0.2",
 | 
			
		||||
                "ts-lit-plugin": "^1.2.1",
 | 
			
		||||
                "tslib": "^2.3.1",
 | 
			
		||||
                "typescript": "^4.3.5",
 | 
			
		||||
                "typescript": "^4.4.2",
 | 
			
		||||
                "webcomponent-qr-code": "^1.0.5",
 | 
			
		||||
                "yaml": "^1.10.2"
 | 
			
		||||
            }
 | 
			
		||||
@ -1689,9 +1689,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@goauthentik/api": {
 | 
			
		||||
            "version": "2021.8.1-1629986812",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.8.1-1629986812.tgz",
 | 
			
		||||
            "integrity": "sha512-/wKkUjm6fTDpjhfp0LJlLA6HIFhMt96BdadMIFPRrRl/DWXcIdPzDJMioiteXRWwrRXC0a9fnxEC/xgFcjz7Bg=="
 | 
			
		||||
            "version": "2021.8.3-1630597235",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.8.3-1630597235.tgz",
 | 
			
		||||
            "integrity": "sha512-W/AWgWE8WS8pWYPqy9JuzbzdYcTJ7aCjzgcLbOmD4roYTp1vP1znSZKx0zg9ZH7EBskFfDqm2eAueF1ktPC09g=="
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@humanwhocodes/config-array": {
 | 
			
		||||
            "version": "0.5.0",
 | 
			
		||||
@ -2071,9 +2071,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@patternfly/patternfly": {
 | 
			
		||||
            "version": "4.125.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.125.3.tgz",
 | 
			
		||||
            "integrity": "sha512-B0L3TFdFYsioV1loCsd3s3Y6eNV/9YjHQIlFnxF1KRgj+eVq0idKi1Mnq28eycKQgFi6ld3tEveMSxBsaw3R9A=="
 | 
			
		||||
            "version": "4.132.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.132.2.tgz",
 | 
			
		||||
            "integrity": "sha512-66qBgIpwPPeTUMTUUO6Z73XApvNXxn3uFaXMeVa09viYGDKzEX3L1FIfc4VzVk2okhk/9KJIYYgxofeuGi5v6A=="
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@polymer/font-roboto": {
 | 
			
		||||
            "version": "3.0.2",
 | 
			
		||||
@ -2276,13 +2276,13 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@sentry/browser": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-Qr2QRA0t5/S9QQqxzYKvM9W8prvmiWuldfwRX4hubovXzcXLgUi4WK0/H612wSbYZ4dNAEcQbtlxFWJNN4wxdg==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-wsJi1NLOmfwtPNYxEC50dpDcVY7sdYckzwfqz1/zHrede1mtxpqSw+7iP4bHADOJXuF+ObYYTHND0v38GSXznQ==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@sentry/core": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/utils": "6.11.0",
 | 
			
		||||
                "@sentry/core": "6.12.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "@sentry/utils": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -2295,14 +2295,14 @@
 | 
			
		||||
            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@sentry/core": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-09TB+f3pqEq8LFahFWHO6I/4DxHo+NcS52OkbWMDqEi6oNZRD7PhPn3i14LfjsYVv3u3AESU8oxSEGbFrr2UjQ==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-mU/zdjlzFHzdXDZCPZm8OeCw7c9xsbL49Mq0TrY0KJjLt4CJBkiq5SDTGfRsenBLgTedYhe5Z/J8Z+xVVq+MfQ==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@sentry/hub": "6.11.0",
 | 
			
		||||
                "@sentry/minimal": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/utils": "6.11.0",
 | 
			
		||||
                "@sentry/hub": "6.12.0",
 | 
			
		||||
                "@sentry/minimal": "6.12.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "@sentry/utils": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -2315,12 +2315,12 @@
 | 
			
		||||
            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@sentry/hub": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-pT9hf+ZJfVFpoZopoC+yJmFNclr4NPqPcl2cgguqCHb69DklD1NxgBNWK8D6X05qjnNFDF991U6t1mxP9HrGuw==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-yR/UQVU+ukr42bSYpeqvb989SowIXlKBanU0cqLFDmv5LPCnaQB8PGeXwJAwWhQgx44PARhmB82S6Xor8gYNxg==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/utils": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "@sentry/utils": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -2333,12 +2333,12 @@
 | 
			
		||||
            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@sentry/minimal": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-XkZ7qrdlGp4IM/gjGxf1Q575yIbl5RvPbg+WFeekpo16Ufvzx37Mr8c2xsZaWosISVyE6eyFpooORjUlzy8EDw==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-r3C54Q1KN+xIqUvcgX9DlcoWE7ezWvFk2pSu1Ojx9De81hVqR9u5T3sdSAP2Xma+um0zr6coOtDJG4WtYlOtsw==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@sentry/hub": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/hub": "6.12.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -2351,14 +2351,14 @@
 | 
			
		||||
            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@sentry/tracing": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-9VA1/SY++WeoMQI4K6n/sYgIdRtCu9NLWqmGqu/5kbOtESYFgAt1DqSyqGCr00ZjQiC2s7tkDkTNZb38K6KytQ==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-u10QHNknPBzbWSUUNMkvuH53sQd5NaBo6YdNPj4p5b7sE7445Sh0PwBpRbY3ZiUUiwyxV59fx9UQ4yVnPGxZQA==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@sentry/hub": "6.11.0",
 | 
			
		||||
                "@sentry/minimal": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/utils": "6.11.0",
 | 
			
		||||
                "@sentry/hub": "6.12.0",
 | 
			
		||||
                "@sentry/minimal": "6.12.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "@sentry/utils": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -2371,19 +2371,19 @@
 | 
			
		||||
            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@sentry/types": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-gm5H9eZhL6bsIy/h3T+/Fzzz2vINhHhqd92CjHle3w7uXdTdFV98i2pDpErBGNTSNzbntqOMifYEB5ENtZAvcg==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-urtgLzE4EDMAYQHYdkgC0Ei9QvLajodK1ntg71bGn0Pm84QUpaqpPDfHRU+i6jLeteyC7kWwa5O5W1m/jrjGXA==",
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": ">=6"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@sentry/utils": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-IOvyFHcnbRQxa++jO+ZUzRvFHEJ1cZjrBIQaNVc0IYF0twUOB5PTP6joTcix38ldaLeapaPZ9LGfudbvYvxkdg==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-oRHQ7TH5TSsJqoP9Gqq25Jvn9LKexXfAh/OoKwjMhYCGKGhqpDNUIZVgl9DWsGw5A5N5xnQyLOxDfyRV5RshdA==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -2557,12 +2557,12 @@
 | 
			
		||||
            "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/eslint-plugin": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-tBgfA3K/3TsZY46ROGvoRxQr1wBkclbVqRQep97MjVHJzcRBURRY3sNFqLk0/Xr//BY5hM9H2p/kp+6qim85SA==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-NgAnqk55RQ/SD+tZFD9aPwNSeHmDHHe5rtUyhIq0ZeCWZEvo4DK9rYz7v9HDuQZFvn320Ot+AikaCKMFKLlD0g==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/experimental-utils": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/experimental-utils": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.30.0",
 | 
			
		||||
                "debug": "^4.3.1",
 | 
			
		||||
                "functional-red-black-tree": "^1.0.1",
 | 
			
		||||
                "regexpp": "^3.1.0",
 | 
			
		||||
@ -2601,14 +2601,14 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/experimental-utils": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-ffIvbytTVWz+3keg+Sy94FG1QeOvmV9dP2YSdLFHw/ieLXWCa3U1TYu8IRCOpMv2/SPS8XqhM1+ou1YHsdzKrg==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-K8RNIX9GnBsv5v4TjtwkKtqMSzYpjqAQg/oSphtxf3xxdt6T0owqnpojztjjTcatSteH3hLj3t/kklKx87NPqw==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@types/json-schema": "^7.0.7",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/types": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/types": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.30.0",
 | 
			
		||||
                "eslint-scope": "^5.1.1",
 | 
			
		||||
                "eslint-utils": "^3.0.0"
 | 
			
		||||
            },
 | 
			
		||||
@ -2624,13 +2624,13 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/parser": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-jrHOV5g2u8ROghmspKoW7pN8T/qUzk0+DITun0MELptvngtMrwUJ1tv5zMI04CYVEUsSrN4jV7AKSv+I0y0EfQ==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-HJ0XuluSZSxeboLU7Q2VQ6eLlCwXPBOGnA7CqgBnz2Db3JRQYyBDJgQnop6TZ+rsbSx5gEdWhw4rE4mDa1FnZg==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/types": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/types": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.30.0",
 | 
			
		||||
                "debug": "^4.3.1"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -2650,12 +2650,12 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/scope-manager": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-x+w8BLXO7iWPkG5mEy9bA1iFRnk36p/goVlYobVWHyDw69YmaH9q6eA+Fgl7kYHmFvWlebUTUfhtIg4zbbl8PA==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-VJ/jAXovxNh7rIXCQbYhkyV2Y3Ac/0cVHP/FruTJSAUUm4Oacmn/nkN5zfWmWFEanN4ggP0vJSHOeajtHq3f8A==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/types": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.29.3"
 | 
			
		||||
                "@typescript-eslint/types": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.30.0"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": "^8.10.0 || ^10.13.0 || >=11.10.1"
 | 
			
		||||
@ -2666,9 +2666,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/types": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-s1eV1lKNgoIYLAl1JUba8NhULmf+jOmmeFO1G5MN/RBCyyzg4TIOfIOICVNC06lor+Xmy4FypIIhFiJXOknhIg==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-YKldqbNU9K4WpTNwBqtAerQKLLW/X2A/j4yw92e3ZJYLx+BpKLeheyzoPfzIXHfM8BXfoleTdiYwpsvVPvHrDw==",
 | 
			
		||||
            "engines": {
 | 
			
		||||
                "node": "^8.10.0 || ^10.13.0 || >=11.10.1"
 | 
			
		||||
            },
 | 
			
		||||
@ -2678,12 +2678,12 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/typescript-estree": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-45oQJA0bxna4O5TMwz55/TpgjX1YrAPOI/rb6kPgmdnemRZx/dB0rsx+Ku8jpDvqTxcE1C/qEbVHbS3h0hflag==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-6WN7UFYvykr/U0Qgy4kz48iGPWILvYL34xXJxvDQeiRE018B7POspNRVtAZscWntEPZpFCx4hcz/XBT+erenfg==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/types": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/types": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.30.0",
 | 
			
		||||
                "debug": "^4.3.1",
 | 
			
		||||
                "globby": "^11.0.3",
 | 
			
		||||
                "is-glob": "^4.0.1",
 | 
			
		||||
@ -2718,11 +2718,11 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/@typescript-eslint/visitor-keys": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-MGGfJvXT4asUTeVs0Q2m+sY63UsfnA+C/FDgBKV3itLBmM9H0u+URcneePtkd0at1YELmZK6HSolCqM4Fzs6yA==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-pNaaxDt/Ol/+JZwzP7MqWc8PJQTUhZwoee/PVlQ+iYoYhagccvoHnC9e4l+C/krQYYkENxznhVSDwClIbZVxRw==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "@typescript-eslint/types": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/types": "4.30.0",
 | 
			
		||||
                "eslint-visitor-keys": "^2.0.0"
 | 
			
		||||
            },
 | 
			
		||||
            "engines": {
 | 
			
		||||
@ -8021,9 +8021,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/typescript": {
 | 
			
		||||
            "version": "4.3.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
 | 
			
		||||
            "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
 | 
			
		||||
            "version": "4.4.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz",
 | 
			
		||||
            "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==",
 | 
			
		||||
            "bin": {
 | 
			
		||||
                "tsc": "bin/tsc",
 | 
			
		||||
                "tsserver": "bin/tsserver"
 | 
			
		||||
@ -9566,9 +9566,9 @@
 | 
			
		||||
            "integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg=="
 | 
			
		||||
        },
 | 
			
		||||
        "@goauthentik/api": {
 | 
			
		||||
            "version": "2021.8.1-1629986812",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.8.1-1629986812.tgz",
 | 
			
		||||
            "integrity": "sha512-/wKkUjm6fTDpjhfp0LJlLA6HIFhMt96BdadMIFPRrRl/DWXcIdPzDJMioiteXRWwrRXC0a9fnxEC/xgFcjz7Bg=="
 | 
			
		||||
            "version": "2021.8.3-1630597235",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.8.3-1630597235.tgz",
 | 
			
		||||
            "integrity": "sha512-W/AWgWE8WS8pWYPqy9JuzbzdYcTJ7aCjzgcLbOmD4roYTp1vP1znSZKx0zg9ZH7EBskFfDqm2eAueF1ktPC09g=="
 | 
			
		||||
        },
 | 
			
		||||
        "@humanwhocodes/config-array": {
 | 
			
		||||
            "version": "0.5.0",
 | 
			
		||||
@ -9847,9 +9847,9 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@patternfly/patternfly": {
 | 
			
		||||
            "version": "4.125.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.125.3.tgz",
 | 
			
		||||
            "integrity": "sha512-B0L3TFdFYsioV1loCsd3s3Y6eNV/9YjHQIlFnxF1KRgj+eVq0idKi1Mnq28eycKQgFi6ld3tEveMSxBsaw3R9A=="
 | 
			
		||||
            "version": "4.132.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.132.2.tgz",
 | 
			
		||||
            "integrity": "sha512-66qBgIpwPPeTUMTUUO6Z73XApvNXxn3uFaXMeVa09viYGDKzEX3L1FIfc4VzVk2okhk/9KJIYYgxofeuGi5v6A=="
 | 
			
		||||
        },
 | 
			
		||||
        "@polymer/font-roboto": {
 | 
			
		||||
            "version": "3.0.2",
 | 
			
		||||
@ -10022,13 +10022,13 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/browser": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-Qr2QRA0t5/S9QQqxzYKvM9W8prvmiWuldfwRX4hubovXzcXLgUi4WK0/H612wSbYZ4dNAEcQbtlxFWJNN4wxdg==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-wsJi1NLOmfwtPNYxEC50dpDcVY7sdYckzwfqz1/zHrede1mtxpqSw+7iP4bHADOJXuF+ObYYTHND0v38GSXznQ==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/core": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/utils": "6.11.0",
 | 
			
		||||
                "@sentry/core": "6.12.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "@sentry/utils": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@ -10040,14 +10040,14 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/core": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-09TB+f3pqEq8LFahFWHO6I/4DxHo+NcS52OkbWMDqEi6oNZRD7PhPn3i14LfjsYVv3u3AESU8oxSEGbFrr2UjQ==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-mU/zdjlzFHzdXDZCPZm8OeCw7c9xsbL49Mq0TrY0KJjLt4CJBkiq5SDTGfRsenBLgTedYhe5Z/J8Z+xVVq+MfQ==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/hub": "6.11.0",
 | 
			
		||||
                "@sentry/minimal": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/utils": "6.11.0",
 | 
			
		||||
                "@sentry/hub": "6.12.0",
 | 
			
		||||
                "@sentry/minimal": "6.12.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "@sentry/utils": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@ -10059,12 +10059,12 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/hub": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-pT9hf+ZJfVFpoZopoC+yJmFNclr4NPqPcl2cgguqCHb69DklD1NxgBNWK8D6X05qjnNFDF991U6t1mxP9HrGuw==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-yR/UQVU+ukr42bSYpeqvb989SowIXlKBanU0cqLFDmv5LPCnaQB8PGeXwJAwWhQgx44PARhmB82S6Xor8gYNxg==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/utils": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "@sentry/utils": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@ -10076,12 +10076,12 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/minimal": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-XkZ7qrdlGp4IM/gjGxf1Q575yIbl5RvPbg+WFeekpo16Ufvzx37Mr8c2xsZaWosISVyE6eyFpooORjUlzy8EDw==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-r3C54Q1KN+xIqUvcgX9DlcoWE7ezWvFk2pSu1Ojx9De81hVqR9u5T3sdSAP2Xma+um0zr6coOtDJG4WtYlOtsw==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/hub": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/hub": "6.12.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@ -10093,14 +10093,14 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/tracing": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-9VA1/SY++WeoMQI4K6n/sYgIdRtCu9NLWqmGqu/5kbOtESYFgAt1DqSyqGCr00ZjQiC2s7tkDkTNZb38K6KytQ==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-u10QHNknPBzbWSUUNMkvuH53sQd5NaBo6YdNPj4p5b7sE7445Sh0PwBpRbY3ZiUUiwyxV59fx9UQ4yVnPGxZQA==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/hub": "6.11.0",
 | 
			
		||||
                "@sentry/minimal": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/utils": "6.11.0",
 | 
			
		||||
                "@sentry/hub": "6.12.0",
 | 
			
		||||
                "@sentry/minimal": "6.12.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "@sentry/utils": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@ -10112,16 +10112,16 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/types": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-gm5H9eZhL6bsIy/h3T+/Fzzz2vINhHhqd92CjHle3w7uXdTdFV98i2pDpErBGNTSNzbntqOMifYEB5ENtZAvcg=="
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-urtgLzE4EDMAYQHYdkgC0Ei9QvLajodK1ntg71bGn0Pm84QUpaqpPDfHRU+i6jLeteyC7kWwa5O5W1m/jrjGXA=="
 | 
			
		||||
        },
 | 
			
		||||
        "@sentry/utils": {
 | 
			
		||||
            "version": "6.11.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.11.0.tgz",
 | 
			
		||||
            "integrity": "sha512-IOvyFHcnbRQxa++jO+ZUzRvFHEJ1cZjrBIQaNVc0IYF0twUOB5PTP6joTcix38ldaLeapaPZ9LGfudbvYvxkdg==",
 | 
			
		||||
            "version": "6.12.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.12.0.tgz",
 | 
			
		||||
            "integrity": "sha512-oRHQ7TH5TSsJqoP9Gqq25Jvn9LKexXfAh/OoKwjMhYCGKGhqpDNUIZVgl9DWsGw5A5N5xnQyLOxDfyRV5RshdA==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@sentry/types": "6.11.0",
 | 
			
		||||
                "@sentry/types": "6.12.0",
 | 
			
		||||
                "tslib": "^1.9.3"
 | 
			
		||||
            },
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
@ -10292,12 +10292,12 @@
 | 
			
		||||
            "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw=="
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/eslint-plugin": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-tBgfA3K/3TsZY46ROGvoRxQr1wBkclbVqRQep97MjVHJzcRBURRY3sNFqLk0/Xr//BY5hM9H2p/kp+6qim85SA==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-NgAnqk55RQ/SD+tZFD9aPwNSeHmDHHe5rtUyhIq0ZeCWZEvo4DK9rYz7v9HDuQZFvn320Ot+AikaCKMFKLlD0g==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@typescript-eslint/experimental-utils": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/experimental-utils": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.30.0",
 | 
			
		||||
                "debug": "^4.3.1",
 | 
			
		||||
                "functional-red-black-tree": "^1.0.1",
 | 
			
		||||
                "regexpp": "^3.1.0",
 | 
			
		||||
@ -10316,50 +10316,50 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/experimental-utils": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-ffIvbytTVWz+3keg+Sy94FG1QeOvmV9dP2YSdLFHw/ieLXWCa3U1TYu8IRCOpMv2/SPS8XqhM1+ou1YHsdzKrg==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-K8RNIX9GnBsv5v4TjtwkKtqMSzYpjqAQg/oSphtxf3xxdt6T0owqnpojztjjTcatSteH3hLj3t/kklKx87NPqw==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@types/json-schema": "^7.0.7",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/types": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/types": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.30.0",
 | 
			
		||||
                "eslint-scope": "^5.1.1",
 | 
			
		||||
                "eslint-utils": "^3.0.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/parser": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-jrHOV5g2u8ROghmspKoW7pN8T/qUzk0+DITun0MELptvngtMrwUJ1tv5zMI04CYVEUsSrN4jV7AKSv+I0y0EfQ==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-HJ0XuluSZSxeboLU7Q2VQ6eLlCwXPBOGnA7CqgBnz2Db3JRQYyBDJgQnop6TZ+rsbSx5gEdWhw4rE4mDa1FnZg==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/types": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/scope-manager": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/types": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/typescript-estree": "4.30.0",
 | 
			
		||||
                "debug": "^4.3.1"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/scope-manager": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-x+w8BLXO7iWPkG5mEy9bA1iFRnk36p/goVlYobVWHyDw69YmaH9q6eA+Fgl7kYHmFvWlebUTUfhtIg4zbbl8PA==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-VJ/jAXovxNh7rIXCQbYhkyV2Y3Ac/0cVHP/FruTJSAUUm4Oacmn/nkN5zfWmWFEanN4ggP0vJSHOeajtHq3f8A==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@typescript-eslint/types": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.29.3"
 | 
			
		||||
                "@typescript-eslint/types": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.30.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/types": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-s1eV1lKNgoIYLAl1JUba8NhULmf+jOmmeFO1G5MN/RBCyyzg4TIOfIOICVNC06lor+Xmy4FypIIhFiJXOknhIg=="
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-YKldqbNU9K4WpTNwBqtAerQKLLW/X2A/j4yw92e3ZJYLx+BpKLeheyzoPfzIXHfM8BXfoleTdiYwpsvVPvHrDw=="
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/typescript-estree": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-45oQJA0bxna4O5TMwz55/TpgjX1YrAPOI/rb6kPgmdnemRZx/dB0rsx+Ku8jpDvqTxcE1C/qEbVHbS3h0hflag==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-6WN7UFYvykr/U0Qgy4kz48iGPWILvYL34xXJxvDQeiRE018B7POspNRVtAZscWntEPZpFCx4hcz/XBT+erenfg==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@typescript-eslint/types": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/types": "4.30.0",
 | 
			
		||||
                "@typescript-eslint/visitor-keys": "4.30.0",
 | 
			
		||||
                "debug": "^4.3.1",
 | 
			
		||||
                "globby": "^11.0.3",
 | 
			
		||||
                "is-glob": "^4.0.1",
 | 
			
		||||
@ -10378,11 +10378,11 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "@typescript-eslint/visitor-keys": {
 | 
			
		||||
            "version": "4.29.3",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.3.tgz",
 | 
			
		||||
            "integrity": "sha512-MGGfJvXT4asUTeVs0Q2m+sY63UsfnA+C/FDgBKV3itLBmM9H0u+URcneePtkd0at1YELmZK6HSolCqM4Fzs6yA==",
 | 
			
		||||
            "version": "4.30.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.30.0.tgz",
 | 
			
		||||
            "integrity": "sha512-pNaaxDt/Ol/+JZwzP7MqWc8PJQTUhZwoee/PVlQ+iYoYhagccvoHnC9e4l+C/krQYYkENxznhVSDwClIbZVxRw==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "@typescript-eslint/types": "4.29.3",
 | 
			
		||||
                "@typescript-eslint/types": "4.30.0",
 | 
			
		||||
                "eslint-visitor-keys": "^2.0.0"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
@ -14339,9 +14339,9 @@
 | 
			
		||||
            "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="
 | 
			
		||||
        },
 | 
			
		||||
        "typescript": {
 | 
			
		||||
            "version": "4.3.5",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
 | 
			
		||||
            "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA=="
 | 
			
		||||
            "version": "4.4.2",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz",
 | 
			
		||||
            "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ=="
 | 
			
		||||
        },
 | 
			
		||||
        "uglify-js": {
 | 
			
		||||
            "version": "3.14.1",
 | 
			
		||||
 | 
			
		||||
@ -46,23 +46,23 @@
 | 
			
		||||
        "@babel/preset-env": "^7.15.0",
 | 
			
		||||
        "@babel/preset-typescript": "^7.15.0",
 | 
			
		||||
        "@fortawesome/fontawesome-free": "^5.15.4",
 | 
			
		||||
        "@goauthentik/api": "^2021.8.1-1629986812",
 | 
			
		||||
        "@goauthentik/api": "^2021.8.3-1630597235",
 | 
			
		||||
        "@lingui/cli": "^3.10.2",
 | 
			
		||||
        "@lingui/core": "^3.10.4",
 | 
			
		||||
        "@lingui/macro": "^3.10.2",
 | 
			
		||||
        "@patternfly/patternfly": "^4.125.3",
 | 
			
		||||
        "@patternfly/patternfly": "^4.132.2",
 | 
			
		||||
        "@polymer/iron-form": "^3.0.1",
 | 
			
		||||
        "@polymer/paper-input": "^3.2.1",
 | 
			
		||||
        "@rollup/plugin-babel": "^5.3.0",
 | 
			
		||||
        "@rollup/plugin-replace": "^3.0.0",
 | 
			
		||||
        "@rollup/plugin-typescript": "^8.2.5",
 | 
			
		||||
        "@sentry/browser": "^6.11.0",
 | 
			
		||||
        "@sentry/tracing": "^6.11.0",
 | 
			
		||||
        "@sentry/browser": "^6.12.0",
 | 
			
		||||
        "@sentry/tracing": "^6.12.0",
 | 
			
		||||
        "@types/chart.js": "^2.9.34",
 | 
			
		||||
        "@types/codemirror": "5.60.2",
 | 
			
		||||
        "@types/grecaptcha": "^3.0.3",
 | 
			
		||||
        "@typescript-eslint/eslint-plugin": "^4.29.3",
 | 
			
		||||
        "@typescript-eslint/parser": "^4.29.3",
 | 
			
		||||
        "@typescript-eslint/eslint-plugin": "^4.30.0",
 | 
			
		||||
        "@typescript-eslint/parser": "^4.30.0",
 | 
			
		||||
        "@webcomponents/webcomponentsjs": "^2.6.0",
 | 
			
		||||
        "babel-plugin-macros": "^3.1.0",
 | 
			
		||||
        "base64-js": "^1.5.1",
 | 
			
		||||
@ -90,7 +90,7 @@
 | 
			
		||||
        "rollup-plugin-terser": "^7.0.2",
 | 
			
		||||
        "ts-lit-plugin": "^1.2.1",
 | 
			
		||||
        "tslib": "^2.3.1",
 | 
			
		||||
        "typescript": "^4.3.5",
 | 
			
		||||
        "typescript": "^4.4.2",
 | 
			
		||||
        "webcomponent-qr-code": "^1.0.5",
 | 
			
		||||
        "yaml": "^1.10.2"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { Config, Configuration, CoreApi, CurrentTenant, Middleware, ResponseCont
 | 
			
		||||
import { getCookie } from "../utils";
 | 
			
		||||
import { APIMiddleware } from "../elements/notifications/APIDrawer";
 | 
			
		||||
import { MessageMiddleware } from "../elements/messages/Middleware";
 | 
			
		||||
import { VERSION } from "../constants";
 | 
			
		||||
 | 
			
		||||
export class LoggingMiddleware implements Middleware {
 | 
			
		||||
 | 
			
		||||
@ -49,7 +50,7 @@ export function tenant(): Promise<CurrentTenant> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_CONFIG = new Configuration({
 | 
			
		||||
    basePath: "",
 | 
			
		||||
    basePath: "/api/v3",
 | 
			
		||||
    headers: {
 | 
			
		||||
        "X-CSRFToken": getCookie("authentik_csrf"),
 | 
			
		||||
    },
 | 
			
		||||
@ -59,3 +60,5 @@ export const DEFAULT_CONFIG = new Configuration({
 | 
			
		||||
        new LoggingMiddleware(),
 | 
			
		||||
    ],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
console.debug(`authentik(early): version ${VERSION}`);
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ export function configureSentry(canDoPpi: boolean = false): Promise<Config> {
 | 
			
		||||
            Sentry.init({
 | 
			
		||||
                dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
 | 
			
		||||
                release: `authentik@${VERSION}`,
 | 
			
		||||
                tunnel: "/api/v2beta/sentry/",
 | 
			
		||||
                tunnel: "/api/v3/sentry/",
 | 
			
		||||
                integrations: [
 | 
			
		||||
                    new Integrations.BrowserTracing({
 | 
			
		||||
                        tracingOrigins: [window.location.host, "localhost"],
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
 | 
			
		||||
export const ERROR_CLASS = "pf-m-danger";
 | 
			
		||||
export const PROGRESS_CLASS = "pf-m-in-progress";
 | 
			
		||||
export const CURRENT_CLASS = "pf-m-current";
 | 
			
		||||
export const VERSION = "2021.8.2";
 | 
			
		||||
export const VERSION = "2021.8.4";
 | 
			
		||||
export const PAGE_SIZE = 20;
 | 
			
		||||
export const TITLE_DEFAULT = "authentik";
 | 
			
		||||
export const ROUTE_SEPARATOR = ";";
 | 
			
		||||
 | 
			
		||||
@ -224,9 +224,15 @@ export class Form<T> extends LitElement {
 | 
			
		||||
                throw ex;
 | 
			
		||||
            })
 | 
			
		||||
            .catch((ex: Error) => {
 | 
			
		||||
                let msg = ex.toString();
 | 
			
		||||
                // Only change the message when we have `detail`.
 | 
			
		||||
                // Everything else is handled in the form.
 | 
			
		||||
                if (ex instanceof APIError && "detail" in ex.response) {
 | 
			
		||||
                    msg = ex.response.detail;
 | 
			
		||||
                }
 | 
			
		||||
                // error is local or not from rest_framework
 | 
			
		||||
                showMessage({
 | 
			
		||||
                    message: ex.toString(),
 | 
			
		||||
                    message: msg,
 | 
			
		||||
                    level: MessageLevel.error,
 | 
			
		||||
                });
 | 
			
		||||
                // rethrow the error so the form doesn't close
 | 
			
		||||
 | 
			
		||||
@ -55,8 +55,34 @@ import { WebsocketClient } from "../common/ws";
 | 
			
		||||
export class FlowExecutor extends LitElement implements StageHost {
 | 
			
		||||
    flowSlug: string;
 | 
			
		||||
 | 
			
		||||
    private _challenge?: ChallengeTypes;
 | 
			
		||||
 | 
			
		||||
    @property({ attribute: false })
 | 
			
		||||
    challenge?: ChallengeTypes;
 | 
			
		||||
    set challenge(value: ChallengeTypes | undefined) {
 | 
			
		||||
        this._challenge = value;
 | 
			
		||||
        // Assign the location as soon as we get the challenge and *not* in the render function
 | 
			
		||||
        // as the render function might be called multiple times, which will navigate multiple
 | 
			
		||||
        // times and can invalidate oauth codes
 | 
			
		||||
        if (value?.type === ChallengeChoices.Redirect) {
 | 
			
		||||
            console.debug(
 | 
			
		||||
                "authentik/flows: redirecting to url from server",
 | 
			
		||||
                (value as RedirectChallenge).to,
 | 
			
		||||
            );
 | 
			
		||||
            window.location.assign((value as RedirectChallenge).to);
 | 
			
		||||
        }
 | 
			
		||||
        tenant().then((tenant) => {
 | 
			
		||||
            if (value?.flowInfo?.title) {
 | 
			
		||||
                document.title = `${value.flowInfo?.title} - ${tenant.brandingTitle}`;
 | 
			
		||||
            } else {
 | 
			
		||||
                document.title = tenant.brandingTitle || TITLE_DEFAULT;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        this.requestUpdate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get challenge(): ChallengeTypes | undefined {
 | 
			
		||||
        return this._challenge;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    loading = false;
 | 
			
		||||
@ -95,16 +121,6 @@ export class FlowExecutor extends LitElement implements StageHost {
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private postUpdate(): void {
 | 
			
		||||
        tenant().then((tenant) => {
 | 
			
		||||
            if (this.challenge?.flowInfo?.title) {
 | 
			
		||||
                document.title = `${this.challenge.flowInfo?.title} - ${tenant.brandingTitle}`;
 | 
			
		||||
            } else {
 | 
			
		||||
                document.title = tenant.brandingTitle || TITLE_DEFAULT;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    submit(payload?: FlowChallengeResponseRequest): Promise<void> {
 | 
			
		||||
        if (!payload) return Promise.reject();
 | 
			
		||||
        if (!this.challenge) return Promise.reject();
 | 
			
		||||
@ -119,7 +135,6 @@ export class FlowExecutor extends LitElement implements StageHost {
 | 
			
		||||
            })
 | 
			
		||||
            .then((data) => {
 | 
			
		||||
                this.challenge = data;
 | 
			
		||||
                this.postUpdate();
 | 
			
		||||
            })
 | 
			
		||||
            .catch((e: Error | Response) => {
 | 
			
		||||
                this.errorMessage(e);
 | 
			
		||||
@ -144,7 +159,6 @@ export class FlowExecutor extends LitElement implements StageHost {
 | 
			
		||||
                if (this.challenge?.flowInfo?.background) {
 | 
			
		||||
                    this.setBackground(this.challenge.flowInfo.background);
 | 
			
		||||
                }
 | 
			
		||||
                this.postUpdate();
 | 
			
		||||
            })
 | 
			
		||||
            .catch((e: Error | Response) => {
 | 
			
		||||
                // Catch JSON or Update errors
 | 
			
		||||
@ -189,11 +203,6 @@ export class FlowExecutor extends LitElement implements StageHost {
 | 
			
		||||
        }
 | 
			
		||||
        switch (this.challenge.type) {
 | 
			
		||||
            case ChallengeChoices.Redirect:
 | 
			
		||||
                console.debug(
 | 
			
		||||
                    "authentik/flows: redirecting to url from server",
 | 
			
		||||
                    (this.challenge as RedirectChallenge).to,
 | 
			
		||||
                );
 | 
			
		||||
                window.location.assign((this.challenge as RedirectChallenge).to);
 | 
			
		||||
                return html`<ak-empty-state ?loading=${true} header=${t`Loading`}>
 | 
			
		||||
                </ak-empty-state>`;
 | 
			
		||||
            case ChallengeChoices.Shell:
 | 
			
		||||
 | 
			
		||||
@ -180,7 +180,7 @@ export class AuthenticatorValidateStage
 | 
			
		||||
                ${this.selectedDeviceChallenge
 | 
			
		||||
                    ? ""
 | 
			
		||||
                    : html`<p class="pf-c-login__main-header-desc">
 | 
			
		||||
                          ${t`Select an identification method.`}
 | 
			
		||||
                          ${t`Select an authentication method.`}
 | 
			
		||||
                      </p>`}
 | 
			
		||||
            </header>
 | 
			
		||||
            ${this.selectedDeviceChallenge
 | 
			
		||||
 | 
			
		||||
@ -64,13 +64,6 @@ export class PromptStage extends BaseStage<PromptChallenge, PromptChallengeRespo
 | 
			
		||||
                    placeholder="${prompt.placeholder}"
 | 
			
		||||
                    class="pf-c-form-control"
 | 
			
		||||
                    ?required=${prompt.required}>`;
 | 
			
		||||
            case "checkbox":
 | 
			
		||||
                return `<input
 | 
			
		||||
                    type="checkbox"
 | 
			
		||||
                    name="${prompt.fieldKey}"
 | 
			
		||||
                    placeholder="${prompt.placeholder}"
 | 
			
		||||
                    class="pf-c-form-control"
 | 
			
		||||
                    ?required=${prompt.required}>`;
 | 
			
		||||
            case "date":
 | 
			
		||||
                return `<input
 | 
			
		||||
                    type="date"
 | 
			
		||||
@ -115,6 +108,22 @@ export class PromptStage extends BaseStage<PromptChallenge, PromptChallengeRespo
 | 
			
		||||
                    }}
 | 
			
		||||
                >
 | 
			
		||||
                    ${this.challenge.fields.map((prompt) => {
 | 
			
		||||
                        // Checkbox is rendered differently
 | 
			
		||||
                        if (prompt.type === "checkbox") {
 | 
			
		||||
                            return html`<div class="pf-c-check">
 | 
			
		||||
                                <input
 | 
			
		||||
                                    type="checkbox"
 | 
			
		||||
                                    class="pf-c-check__input"
 | 
			
		||||
                                    name="${prompt.fieldKey}"
 | 
			
		||||
                                    ?checked=${prompt.placeholder !== ""}
 | 
			
		||||
                                    ?required=${prompt.required}
 | 
			
		||||
                                />
 | 
			
		||||
                                <label class="pf-c-check__label">${prompt.label}</label>
 | 
			
		||||
                                ${prompt.required
 | 
			
		||||
                                    ? html`<p class="pf-c-form__helper-text">${t`Required.`}</p>`
 | 
			
		||||
                                    : html``}
 | 
			
		||||
                            </div>`;
 | 
			
		||||
                        }
 | 
			
		||||
                        // Special types that aren't rendered in a wrapper
 | 
			
		||||
                        if (
 | 
			
		||||
                            prompt.type === "static" ||
 | 
			
		||||
 | 
			
		||||
@ -3370,6 +3370,10 @@ msgstr "Request token URL"
 | 
			
		||||
msgid "Required"
 | 
			
		||||
msgstr "Required"
 | 
			
		||||
 | 
			
		||||
#: src/flows/stages/prompt/PromptStage.ts
 | 
			
		||||
msgid "Required."
 | 
			
		||||
msgstr "Required."
 | 
			
		||||
 | 
			
		||||
#: src/pages/user-settings/UserSelfForm.ts
 | 
			
		||||
#: src/pages/users/ServiceAccountForm.ts
 | 
			
		||||
#: src/pages/users/UserForm.ts
 | 
			
		||||
@ -3524,13 +3528,17 @@ msgstr "Select a provider that this application should use. Alternatively, creat
 | 
			
		||||
msgid "Select all rows"
 | 
			
		||||
msgstr "Select all rows"
 | 
			
		||||
 | 
			
		||||
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
 | 
			
		||||
msgid "Select an authentication method."
 | 
			
		||||
msgstr "Select an authentication method."
 | 
			
		||||
 | 
			
		||||
#: src/pages/stages/invitation/InvitationListLink.ts
 | 
			
		||||
msgid "Select an enrollment flow"
 | 
			
		||||
msgstr "Select an enrollment flow"
 | 
			
		||||
 | 
			
		||||
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
 | 
			
		||||
msgid "Select an identification method."
 | 
			
		||||
msgstr "Select an identification method."
 | 
			
		||||
#: 
 | 
			
		||||
#~ msgid "Select an identification method."
 | 
			
		||||
#~ msgstr "Select an identification method."
 | 
			
		||||
 | 
			
		||||
#: src/pages/users/GroupSelectModal.ts
 | 
			
		||||
msgid "Select groups to add user to"
 | 
			
		||||
@ -3757,9 +3765,13 @@ msgstr "Source(s)"
 | 
			
		||||
msgid "Sources"
 | 
			
		||||
msgstr "Sources"
 | 
			
		||||
 | 
			
		||||
#: 
 | 
			
		||||
#~ msgid "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins"
 | 
			
		||||
#~ msgstr "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins"
 | 
			
		||||
 | 
			
		||||
#: src/pages/sources/SourcesListPage.ts
 | 
			
		||||
msgid "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins"
 | 
			
		||||
msgstr "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins"
 | 
			
		||||
msgid "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves."
 | 
			
		||||
msgstr "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves."
 | 
			
		||||
 | 
			
		||||
#: src/pages/flows/BoundStagesList.ts
 | 
			
		||||
#: src/pages/flows/StageBindingForm.ts
 | 
			
		||||
 | 
			
		||||
@ -3362,6 +3362,10 @@ msgstr ""
 | 
			
		||||
msgid "Required"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: src/flows/stages/prompt/PromptStage.ts
 | 
			
		||||
msgid "Required."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: src/pages/user-settings/UserSelfForm.ts
 | 
			
		||||
#: src/pages/users/ServiceAccountForm.ts
 | 
			
		||||
#: src/pages/users/UserForm.ts
 | 
			
		||||
@ -3516,13 +3520,17 @@ msgstr ""
 | 
			
		||||
msgid "Select all rows"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
 | 
			
		||||
msgid "Select an authentication method."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: src/pages/stages/invitation/InvitationListLink.ts
 | 
			
		||||
msgid "Select an enrollment flow"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
 | 
			
		||||
msgid "Select an identification method."
 | 
			
		||||
msgstr ""
 | 
			
		||||
#: 
 | 
			
		||||
#~ msgid "Select an identification method."
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: src/pages/users/GroupSelectModal.ts
 | 
			
		||||
msgid "Select groups to add user to"
 | 
			
		||||
@ -3749,8 +3757,12 @@ msgstr ""
 | 
			
		||||
msgid "Sources"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: 
 | 
			
		||||
#~ msgid "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins"
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: src/pages/sources/SourcesListPage.ts
 | 
			
		||||
msgid "Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins"
 | 
			
		||||
msgid "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves."
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: src/pages/flows/BoundStagesList.ts
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,6 @@ export class SystemStatusCard extends AdminStatusCard<System> {
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        const timeDiff = value.serverTime.getTime() - (this.now || new Date()).getTime();
 | 
			
		||||
        console.log(`authentik/: timediff ${timeDiff}`);
 | 
			
		||||
        if (timeDiff > 5000 || timeDiff < -5000) {
 | 
			
		||||
            this.header = t`Warning`;
 | 
			
		||||
            return Promise.resolve<AdminStatus>({
 | 
			
		||||
 | 
			
		||||
@ -243,7 +243,7 @@ export class FlowForm extends ModelForm<Flow, string> {
 | 
			
		||||
                    <input
 | 
			
		||||
                        type="checkbox"
 | 
			
		||||
                        class="pf-c-check__input"
 | 
			
		||||
                        ?checked=${first(this.instance?.compatibilityMode, true)}
 | 
			
		||||
                        ?checked=${first(this.instance?.compatibilityMode, false)}
 | 
			
		||||
                    />
 | 
			
		||||
                    <label class="pf-c-check__label"> ${t`Compatibility mode`} </label>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
@ -101,9 +101,7 @@ export class FlowListPage extends TablePage<Flow> {
 | 
			
		||||
                                slug: item.slug,
 | 
			
		||||
                            })
 | 
			
		||||
                            .then((link) => {
 | 
			
		||||
                                window.location.assign(
 | 
			
		||||
                                    `${link.link}?next=/%23${window.location.href}`,
 | 
			
		||||
                                );
 | 
			
		||||
                                window.open(`${link.link}?next=/%23${window.location.href}`);
 | 
			
		||||
                            });
 | 
			
		||||
                    }}
 | 
			
		||||
                >
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ export class SourceListPage extends TablePage<Source> {
 | 
			
		||||
        return t`Sources`;
 | 
			
		||||
    }
 | 
			
		||||
    pageDescription(): string | undefined {
 | 
			
		||||
        return t`Sources of identities, which can either be synced into authentik's database, like LDAP, or can be used by users to authenticate and enroll themselves, like OAuth and social logins`;
 | 
			
		||||
        return t`Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves.`;
 | 
			
		||||
    }
 | 
			
		||||
    pageIcon(): string {
 | 
			
		||||
        return "pf-icon pf-icon-middleware";
 | 
			
		||||
 | 
			
		||||
@ -48,6 +48,9 @@ export class InvitationListLink extends LitElement {
 | 
			
		||||
                                        designation: FlowsInstancesListDesignationEnum.Enrollment,
 | 
			
		||||
                                    })
 | 
			
		||||
                                    .then((flows) => {
 | 
			
		||||
                                        if (!this.selectedFlow && flows.results.length > 0) {
 | 
			
		||||
                                            this.selectedFlow = flows.results[0].slug;
 | 
			
		||||
                                        }
 | 
			
		||||
                                        return flows.results.map((flow) => {
 | 
			
		||||
                                            return html`<option
 | 
			
		||||
                                                value=${flow.slug}
 | 
			
		||||
 | 
			
		||||
@ -157,7 +157,7 @@ export class PromptForm extends ModelForm<Prompt, string> {
 | 
			
		||||
            <ak-form-element-horizontal label=${t`Order`} ?required=${true} name="order">
 | 
			
		||||
                <input
 | 
			
		||||
                    type="number"
 | 
			
		||||
                    value="${ifDefined(this.instance?.order)}"
 | 
			
		||||
                    value="${first(this.instance?.order, 0)}"
 | 
			
		||||
                    class="pf-c-form-control"
 | 
			
		||||
                    required
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
@ -103,7 +103,7 @@ export class TokenListPage extends TablePage<Token> {
 | 
			
		||||
    row(item: Token): TemplateResult[] {
 | 
			
		||||
        return [
 | 
			
		||||
            html`${item.identifier}`,
 | 
			
		||||
            html`${item.userObj?.username}`,
 | 
			
		||||
            html`<a href="#/identity/users/${item.userObj?.pk}">${item.userObj?.username}</a>`,
 | 
			
		||||
            html`${item.expiring ? t`Yes` : t`No`}`,
 | 
			
		||||
            html`${item.expiring ? item.expires?.toLocaleString() : "-"}`,
 | 
			
		||||
            html`${IntentToLabel(item.intent || IntentEnum.Api)}`,
 | 
			
		||||
 | 
			
		||||
@ -51,9 +51,13 @@ export class UserSettingsAuthenticatorWebAuthn extends BaseUserSettings {
 | 
			
		||||
                slot="form"
 | 
			
		||||
                successMessage=${t`Successfully updated device.`}
 | 
			
		||||
                .send=${(data: unknown) => {
 | 
			
		||||
                    return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsWebauthnUpdate({
 | 
			
		||||
                    return new AuthenticatorsApi(DEFAULT_CONFIG)
 | 
			
		||||
                        .authenticatorsWebauthnUpdate({
 | 
			
		||||
                            id: device.pk || 0,
 | 
			
		||||
                            webAuthnDeviceRequest: data as WebAuthnDevice,
 | 
			
		||||
                        })
 | 
			
		||||
                        .then(() => {
 | 
			
		||||
                            this.requestUpdate();
 | 
			
		||||
                        });
 | 
			
		||||
                }}
 | 
			
		||||
            >
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,9 @@
 | 
			
		||||
title: API
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
Starting with 2021.3.5, every authentik instance has a built-in API browser, which can be accessed at https://authentik.company/api/v2beta/.
 | 
			
		||||
Starting with 2021.3.5, every authentik instance has a built-in API browser, which can be accessed at https://authentik.company/api/v3/.
 | 
			
		||||
 | 
			
		||||
To generate an API client, you can use the OpenAPI v3 schema at https://authentik.company/api/v2beta/schema/.
 | 
			
		||||
To generate an API client, you can use the OpenAPI v3 schema at https://authentik.company/api/v3/schema/.
 | 
			
		||||
 | 
			
		||||
While testing, the API requests are authenticated by your browser session.
 | 
			
		||||
 | 
			
		||||
@ -10,13 +10,13 @@ However, any flow can be executed via an API from anywhere, in fact that is what
 | 
			
		||||
Because the flow executor stores its state in the HTTP Session, so you need to ensure cookies between flow executor requests are persisted.
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
The main endpoint for flow execution is `/api/v2beta/flows/executor/:slug`.
 | 
			
		||||
The main endpoint for flow execution is `/api/v3/flows/executor/:slug`.
 | 
			
		||||
 | 
			
		||||
This endpoint accepts a query parameter called `query`, in which the flow executor sends the full Query-string.
 | 
			
		||||
 | 
			
		||||
To initiate a new flow, execute a GET request.
 | 
			
		||||
 | 
			
		||||
## `GET /api/v2beta/flows/executor/test-flow/`
 | 
			
		||||
## `GET /api/v3/flows/executor/test-flow/`
 | 
			
		||||
 | 
			
		||||
Below is the response, for example for an Identification stage.
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,7 @@ Below is the response, for example for an Identification stage.
 | 
			
		||||
 | 
			
		||||
To respond to this challenge, send a response:
 | 
			
		||||
 | 
			
		||||
## `POST /api/v2beta/flows/executor/test-flow/`
 | 
			
		||||
## `POST /api/v3/flows/executor/test-flow/`
 | 
			
		||||
 | 
			
		||||
With this body
 | 
			
		||||
 | 
			
		||||
@ -63,7 +63,7 @@ Depending on the flow, you'll either get a 200 Response with another challenge,
 | 
			
		||||
 | 
			
		||||
Depending also on the stage, a response might take longer to be returned (especially with the Duo Authenticator validation).
 | 
			
		||||
 | 
			
		||||
To see the data layout for every stage possible, see the [API Browser](https://goauthentik.io/api/#get-/api/v2beta/flows/executor/-flow_slug-/)
 | 
			
		||||
To see the data layout for every stage possible, see the [API Browser](https://goauthentik.io/api/#get-/api/v3/flows/executor/-flow_slug-/)
 | 
			
		||||
 | 
			
		||||
## Result
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user