Compare commits
105 Commits
interfaces
...
dev/devcon
| Author | SHA1 | Date | |
|---|---|---|---|
| 811d50f84b | |||
| 7a41137278 | |||
| 2b7df39504 | |||
| 8ded11806a | |||
| 36bd4b1e51 | |||
| 95a679ab3b | |||
| 5ca8eefa8b | |||
| b0f5c9b010 | |||
| 6ae9071368 | |||
| ab795e6642 | |||
| b7b62ba089 | |||
| 7f0ccc61dd | |||
| d5abaed66a | |||
| 64d611212e | |||
| 9e9769d7fb | |||
| 5aa744edca | |||
| 0a7e2e9f81 | |||
| f43c0bc798 | |||
| ffd3924095 | |||
| ed275bce4a | |||
| b99ce890ef | |||
| 5509bce3d7 | |||
| a3f1e7a4d1 | |||
| 17fb4dab34 | |||
| c0f3b56012 | |||
| 53415d8af8 | |||
| ed99b3d98f | |||
| 6373dd2053 | |||
| 3f607ee2c8 | |||
| da6e74a353 | |||
| 9b879989fe | |||
| b1508b9d01 | |||
| 4601864f94 | |||
| a2994218e4 | |||
| 0ae53b1ce8 | |||
| d5fa9da444 | |||
| 91da421391 | |||
| a1e67377f9 | |||
| 5ad379f54f | |||
| 0be95d377a | |||
| 4da66cdb6b | |||
| a28b888ca4 | |||
| 5ec008d0d3 | |||
| b06dbab4ac | |||
| ab4d7ba2f0 | |||
| ea806daf3e | |||
| 27e5f45919 | |||
| 8b17ab9bb0 | |||
| 9283e02808 | |||
| d6b5359b8b | |||
| 77657b1f33 | |||
| 131a43033e | |||
| fef841a458 | |||
| bb8b87fcb3 | |||
| f36a5a053f | |||
| cc8f52b502 | |||
| 0b0e08446d | |||
| 1913b5ec41 | |||
| a8332eced6 | |||
| af7cc8d42d | |||
| 5830781a5a | |||
| a7f324b96f | |||
| 494cfc2fea | |||
| 3af27323de | |||
| 8a6febaa02 | |||
| ecce31ee87 | |||
| 967a38b7ac | |||
| 9d1ad104ec | |||
| 01663468de | |||
| 5e7731a4aa | |||
| cb0fa6beb9 | |||
| 6f67366dfa | |||
| 8b7922a5cd | |||
| dea44fc74d | |||
| dfe8a98849 | |||
| 54d508ae8c | |||
| 7b0d8f8991 | |||
| b058906074 | |||
| 4b0566c9d1 | |||
| 40dfa920e2 | |||
| 187d5e9b4c | |||
| 147312c160 | |||
| 4426cbec34 | |||
| e05f028c0a | |||
| 58a5c69f49 | |||
| 8c7c60b271 | |||
| d8c243bcd2 | |||
| f7cc4349d7 | |||
| 13591fc72c | |||
| b604ff5114 | |||
| f72fa41a75 | |||
| adf4191066 | |||
| d2de586cc9 | |||
| dad5021870 | |||
| ab3f993bb9 | |||
| 158fe2f9bb | |||
| 5970a6e2a2 | |||
| 5c8f024d12 | |||
| 428daa5323 | |||
| 4001af4d35 | |||
| f1cec03dcf | |||
| 574ed72b95 | |||
| 480f5c2aac | |||
| d4e502fdf5 | |||
| 05b2fb5ec1 |
7
.devcontainer/Dockerfile
Normal file
7
.devcontainer/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM ghcr.io/goauthentik/server:latest
|
||||
|
||||
USER root
|
||||
|
||||
HEALTHCHECK --interval=10s CMD exit 0
|
||||
|
||||
RUN pip install --no-cache-dir -r /app-root/requirements-dev.txt
|
||||
34
.devcontainer/devcontainer.json
Normal file
34
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "authentik",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/app-root",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/go:1": {},
|
||||
"ghcr.io/devcontainers/features/node:1": {}
|
||||
},
|
||||
"forwardPorts": [9000],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"EditorConfig.EditorConfig",
|
||||
"bashmish.es6-string-css",
|
||||
"bpruitt-goddard.mermaid-markdown-syntax-highlighting",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"golang.go",
|
||||
"Gruntfuggly.todo-tree",
|
||||
"mechatroner.rainbow-csv",
|
||||
"ms-python.black-formatter",
|
||||
"ms-python.isort",
|
||||
"ms-python.pylint",
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"redhat.vscode-yaml",
|
||||
"Tobermory.es6-string-html",
|
||||
"unifiedjs.vscode-mdx"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
32
.devcontainer/docker-compose.yml
Normal file
32
.devcontainer/docker-compose.yml
Normal file
@ -0,0 +1,32 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: .devcontainer/Dockerfile
|
||||
volumes:
|
||||
- ../:/app-root:cached
|
||||
command: debug
|
||||
environment:
|
||||
AUTHENTIK_POSTGRESQL__USER: postgres
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: postgres
|
||||
AUTHENTIK_BOOTSTRAP_PASSWORD: akadmin
|
||||
AUTHENTIK_BOOTSTRAP_TOKEN: akadmin
|
||||
db:
|
||||
image: docker.io/library/postgres:15
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
network_mode: service:app
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: authentik
|
||||
POSTGRES_PASSWORD: postgres
|
||||
redis:
|
||||
image: docker.io/redis/redis-stack-server
|
||||
restart: unless-stopped
|
||||
network_mode: service:app
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
@ -51,12 +51,14 @@ runs:
|
||||
version_family = ".".join(version.split(".")[:-1])
|
||||
safe_branch_name = branch_name.replace("refs/heads/", "").replace("/", "-")
|
||||
|
||||
sha = os.environ["GITHUB_SHA"] if not "${{ github.event.pull_request.head.sha }}" else "${{ github.event.pull_request.head.sha }}"
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
|
||||
print("branchName=%s" % branch_name, file=_output)
|
||||
print("branchNameContainer=%s" % safe_branch_name, file=_output)
|
||||
print("timestamp=%s" % int(time()), file=_output)
|
||||
print("sha=%s" % os.environ["GITHUB_SHA"], file=_output)
|
||||
print("shortHash=%s" % os.environ["GITHUB_SHA"][:7], file=_output)
|
||||
print("sha=%s" % sha, file=_output)
|
||||
print("shortHash=%s" % sha[:7], file=_output)
|
||||
print("shouldBuild=%s" % should_build, file=_output)
|
||||
print("version=%s" % version, file=_output)
|
||||
print("versionFamily=%s" % version_family, file=_output)
|
||||
|
||||
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@ -21,7 +21,7 @@ runs:
|
||||
python-version: "3.11"
|
||||
cache: "poetry"
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3.1.0
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
|
||||
6
.github/actions/setup/docker-compose.yml
vendored
6
.github/actions/setup/docker-compose.yml
vendored
@ -2,8 +2,7 @@ version: "3.7"
|
||||
|
||||
services:
|
||||
postgresql:
|
||||
container_name: postgres
|
||||
image: library/postgres:${PSQL_TAG:-12}
|
||||
image: docker.io/library/postgres:${PSQL_TAG:-12}
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
@ -14,8 +13,7 @@ services:
|
||||
- 5432:5432
|
||||
restart: always
|
||||
redis:
|
||||
container_name: redis
|
||||
image: library/redis
|
||||
image: docker.io/library/redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
restart: always
|
||||
|
||||
19
.github/pull_request_template.md
vendored
19
.github/pull_request_template.md
vendored
@ -19,6 +19,21 @@ Please check the [Contributing guidelines](https://github.com/goauthentik/authen
|
||||
|
||||
- Adds breaking change which causes \<issue\>.
|
||||
|
||||
## Additional
|
||||
## Checklist
|
||||
|
||||
Any further notes or comments you want to make.
|
||||
- [ ] Local tests pass (`ak test authentik/`)
|
||||
- [ ] The code has been formatted (`make lint-fix`)
|
||||
|
||||
If an API change has been made
|
||||
|
||||
- [ ] The API schema has been updated (`make gen-build`)
|
||||
|
||||
If changes to the frontend have been made
|
||||
|
||||
- [ ] The code has been formatted (`make web`)
|
||||
- [ ] The translation files have been updated (`make i18n-extract`)
|
||||
|
||||
If applicable
|
||||
|
||||
- [ ] The documentation has been updated
|
||||
- [ ] The documentation has been formatted (`make website`)
|
||||
|
||||
4
.github/workflows/ci-main.yml
vendored
4
.github/workflows/ci-main.yml
vendored
@ -187,6 +187,8 @@ jobs:
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.1.0
|
||||
- name: Set up Docker Buildx
|
||||
@ -229,6 +231,8 @@ jobs:
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.1.0
|
||||
- name: Set up Docker Buildx
|
||||
|
||||
5
.github/workflows/ci-outpost.yml
vendored
5
.github/workflows/ci-outpost.yml
vendored
@ -30,6 +30,7 @@ jobs:
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
args: --timeout 5000s
|
||||
skip-pkg-cache: true
|
||||
test-unittest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -63,6 +64,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.1.0
|
||||
- name: Set up Docker Buildx
|
||||
@ -110,6 +113,8 @@ jobs:
|
||||
goarch: [amd64, arm64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
28
.github/workflows/codeql-analysis.yml
vendored
28
.github/workflows/codeql-analysis.yml
vendored
@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches: [main, "*", next, version*]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: "30 6 * * 5"
|
||||
@ -22,39 +21,16 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ["go", "javascript", "python"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
34
.github/workflows/translation-advice.yml
vendored
Normal file
34
.github/workflows/translation-advice.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: authentik-translation-advice
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "!**"
|
||||
- "locale/**"
|
||||
- "web/src/locales/**"
|
||||
|
||||
jobs:
|
||||
post-comment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: authentik translations instructions
|
||||
- name: Create or update comment
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
edit-mode: replace
|
||||
body: |
|
||||
### authentik translations instructions
|
||||
|
||||
Thanks for your pull request!
|
||||
|
||||
authentik translations are handled using [Transifex](https://explore.transifex.com/authentik/authentik/). Please edit translations over there and they'll be included automatically.
|
||||
7
.github/workflows/translation-compile.yml
vendored
7
.github/workflows/translation-compile.yml
vendored
@ -3,10 +3,7 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "/locale/"
|
||||
pull_request:
|
||||
paths:
|
||||
- "/locale/"
|
||||
- "locale/**"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@ -24,7 +21,7 @@ jobs:
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: run compile
|
||||
run: poetry run ./manage.py compilemessages
|
||||
run: poetry run ak compilemessages
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
id: cpr
|
||||
|
||||
18
.vscode/tasks.json
vendored
18
.vscode/tasks.json
vendored
@ -3,26 +3,23 @@
|
||||
"tasks": [
|
||||
{
|
||||
"label": "authentik[core]: format & test",
|
||||
"command": "poetry",
|
||||
"args": [
|
||||
"run",
|
||||
"make"
|
||||
],
|
||||
"command": "make",
|
||||
"group": "build",
|
||||
},
|
||||
{
|
||||
"label": "authentik[core]: run",
|
||||
"command": "poetry",
|
||||
"command": "ak",
|
||||
"args": [
|
||||
"run",
|
||||
"make",
|
||||
"run",
|
||||
"server",
|
||||
],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"panel": "dedicated",
|
||||
"group": "running"
|
||||
},
|
||||
"runOptions": {
|
||||
"runOn": "folderOpen"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "authentik[web]: format",
|
||||
@ -39,6 +36,9 @@
|
||||
"panel": "dedicated",
|
||||
"group": "running"
|
||||
},
|
||||
"runOptions": {
|
||||
"runOn": "folderOpen"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "authentik: install",
|
||||
|
||||
44
Dockerfile
44
Dockerfile
@ -31,7 +31,7 @@ RUN pip install --no-cache-dir poetry && \
|
||||
poetry export -f requirements.txt --dev --output requirements-dev.txt
|
||||
|
||||
# Stage 4: Build go proxy
|
||||
FROM docker.io/golang:1.20.3-bullseye AS go-builder
|
||||
FROM docker.io/golang:1.20.4-bullseye AS go-builder
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
@ -68,14 +68,14 @@ LABEL org.opencontainers.image.url https://goauthentik.io
|
||||
LABEL org.opencontainers.image.description goauthentik.io Main server image, see https://goauthentik.io for more info.
|
||||
LABEL org.opencontainers.image.source https://github.com/goauthentik/authentik
|
||||
|
||||
WORKDIR /
|
||||
WORKDIR /app-root
|
||||
|
||||
ARG GIT_BUILD_HASH
|
||||
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
|
||||
|
||||
COPY --from=poetry-locker /work/requirements.txt /
|
||||
COPY --from=poetry-locker /work/requirements-dev.txt /
|
||||
COPY --from=geoip /usr/share/GeoIP /geoip
|
||||
COPY --from=poetry-locker /work/requirements.txt /app-root
|
||||
COPY --from=poetry-locker /work/requirements-dev.txt /app-root
|
||||
COPY --from=geoip /usr/share/GeoIP /app-root/geoip
|
||||
|
||||
RUN apt-get update && \
|
||||
# Required for installing pip packages
|
||||
@ -84,35 +84,35 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends libxmlsec1-openssl libmaxminddb0 && \
|
||||
# Required for bootstrap & healtcheck
|
||||
apt-get install -y --no-install-recommends runit && \
|
||||
pip install --no-cache-dir -r /requirements.txt && \
|
||||
pip install --no-cache-dir -r /app-root/requirements.txt && \
|
||||
apt-get remove --purge -y build-essential pkg-config libxmlsec1-dev && \
|
||||
apt-get autoremove --purge -y && \
|
||||
apt-get clean && \
|
||||
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \
|
||||
adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \
|
||||
adduser --system --no-create-home --uid 1000 --group --home /app-root authentik && \
|
||||
mkdir -p /app-root /app-root/.ssh && \
|
||||
mkdir -p /certs /media /blueprints && \
|
||||
mkdir -p /authentik/.ssh && \
|
||||
chown authentik:authentik /certs /media /authentik/.ssh
|
||||
chown -R authentik:authentik /certs /media /app-root/
|
||||
|
||||
COPY ./authentik/ /authentik
|
||||
COPY ./pyproject.toml /
|
||||
COPY ./schemas /schemas
|
||||
COPY ./locale /locale
|
||||
COPY ./tests /tests
|
||||
COPY ./manage.py /
|
||||
COPY ./authentik/ /app-root/authentik
|
||||
COPY ./pyproject.toml /app-root/
|
||||
COPY ./schemas /app-root/schemas
|
||||
COPY ./locale /app-root/locale
|
||||
COPY ./tests /app-root/tests
|
||||
COPY ./manage.py /app-root/
|
||||
COPY ./blueprints /blueprints
|
||||
COPY ./lifecycle/ /lifecycle
|
||||
COPY ./lifecycle/ /app-root/lifecycle
|
||||
COPY --from=go-builder /work/authentik /bin/authentik
|
||||
COPY --from=web-builder /work/web/dist/ /web/dist/
|
||||
COPY --from=web-builder /work/web/authentik/ /web/authentik/
|
||||
COPY --from=website-builder /work/website/help/ /website/help/
|
||||
COPY --from=web-builder /work/web/dist/ /app-root/web/dist/
|
||||
COPY --from=web-builder /work/web/authentik/ /app-root/web/authentik/
|
||||
COPY --from=website-builder /work/website/help/ /app-root/website/help/
|
||||
|
||||
USER 1000
|
||||
|
||||
ENV TMPDIR /dev/shm/
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV PATH "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/lifecycle"
|
||||
ENV PATH "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/app-root/lifecycle"
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "/lifecycle/ak", "healthcheck" ]
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "ak", "healthcheck" ]
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/dumb-init", "--", "/lifecycle/ak" ]
|
||||
ENTRYPOINT [ "/usr/local/bin/dumb-init", "--", "ak" ]
|
||||
|
||||
@ -49,7 +49,8 @@ class BlueprintInstanceSerializer(ModelSerializer):
|
||||
context = self.instance.context if self.instance else {}
|
||||
valid, logs = Importer(content, context).validate()
|
||||
if not valid:
|
||||
raise ValidationError(_("Failed to validate blueprint"), *[x["msg"] for x in logs])
|
||||
text_logs = "\n".join([x["event"] for x in logs])
|
||||
raise ValidationError(_("Failed to validate blueprint: %(logs)s" % {"logs": text_logs}))
|
||||
return content
|
||||
|
||||
def validate(self, attrs: dict) -> dict:
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
# Generated by Django 4.1.7 on 2023-04-28 10:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
from authentik.lib.migrations import fallback_names
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("authentik_blueprints", "0002_blueprintinstance_content"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(fallback_names("authentik_blueprints", "blueprintinstance", "name")),
|
||||
migrations.AlterField(
|
||||
model_name="blueprintinstance",
|
||||
name="name",
|
||||
field=models.TextField(unique=True),
|
||||
),
|
||||
]
|
||||
@ -57,7 +57,7 @@ class BlueprintInstance(SerializerModel, ManagedModel, CreatedUpdatedModel):
|
||||
|
||||
instance_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||
|
||||
name = models.TextField()
|
||||
name = models.TextField(unique=True)
|
||||
metadata = models.JSONField(default=dict)
|
||||
path = models.TextField(default="", blank=True)
|
||||
content = models.TextField(default="", blank=True)
|
||||
|
||||
@ -67,4 +67,7 @@ class TestBlueprintsV1API(APITestCase):
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(res.content.decode(), {"content": ["Failed to validate blueprint"]})
|
||||
self.assertJSONEqual(
|
||||
res.content.decode(),
|
||||
{"content": ["Failed to validate blueprint: Invalid blueprint version"]},
|
||||
)
|
||||
|
||||
@ -299,7 +299,7 @@ class Importer:
|
||||
orig_import = deepcopy(self.__import)
|
||||
if self.__import.version != 1:
|
||||
self.logger.warning("Invalid blueprint version")
|
||||
return False, []
|
||||
return False, [{"event": "Invalid blueprint version"}]
|
||||
with (
|
||||
transaction_rollback(),
|
||||
capture_logs() as logs,
|
||||
|
||||
@ -101,7 +101,10 @@ def blueprints_find():
|
||||
"""Find blueprints and return valid ones"""
|
||||
blueprints = []
|
||||
root = Path(CONFIG.y("blueprints_dir"))
|
||||
for path in root.glob("**/*.yaml"):
|
||||
for path in root.rglob("**/*.yaml"):
|
||||
# Check if any part in the path starts with a dot and assume a hidden file
|
||||
if any(part for part in path.parts if part.startswith(".")):
|
||||
continue
|
||||
LOGGER.debug("found blueprint", path=str(path))
|
||||
with open(path, "r", encoding="utf-8") as blueprint_file:
|
||||
try:
|
||||
|
||||
@ -93,7 +93,6 @@ class PropertyMappingViewSet(
|
||||
{
|
||||
"name": subclass._meta.verbose_name,
|
||||
"description": subclass.__doc__,
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
"component": subclass().component,
|
||||
"model_name": subclass._meta.model_name,
|
||||
}
|
||||
|
||||
@ -25,7 +25,6 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer):
|
||||
|
||||
def get_component(self, obj: Provider) -> str: # pragma: no cover
|
||||
"""Get object component so that we know how to edit the object"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
if obj.__class__ == Provider:
|
||||
return ""
|
||||
return obj.component
|
||||
|
||||
@ -40,7 +40,6 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
|
||||
|
||||
def get_component(self, obj: Source) -> str:
|
||||
"""Get object component so that we know how to edit the object"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
if obj.__class__ == Source:
|
||||
return ""
|
||||
return obj.component
|
||||
@ -139,7 +138,6 @@ class SourceViewSet(
|
||||
component = subclass.__bases__[0]().component
|
||||
else:
|
||||
component = subclass().component
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
data.append(
|
||||
{
|
||||
"name": subclass._meta.verbose_name,
|
||||
|
||||
@ -56,7 +56,6 @@ class UsedByMixin:
|
||||
# pylint: disable=too-many-locals
|
||||
def used_by(self, request: Request, *args, **kwargs) -> Response:
|
||||
"""Get a list of all objects that use this object"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
model: Model = self.get_object()
|
||||
used_by = []
|
||||
shadows = []
|
||||
|
||||
@ -13,6 +13,7 @@ from authentik.core.views.interface import FlowInterfaceView, InterfaceView
|
||||
from authentik.core.views.session import EndSessionView
|
||||
from authentik.root.asgi_middleware import SessionMiddleware
|
||||
from authentik.root.messages.consumer import MessageConsumer
|
||||
from authentik.root.middleware import ChannelsLoggingMiddleware
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
@ -70,7 +71,10 @@ urlpatterns = [
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path(
|
||||
"ws/client/", CookieMiddleware(SessionMiddleware(AuthMiddleware(MessageConsumer.as_asgi())))
|
||||
"ws/client/",
|
||||
ChannelsLoggingMiddleware(
|
||||
CookieMiddleware(SessionMiddleware(AuthMiddleware(MessageConsumer.as_asgi())))
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
# Generated by Django 4.1.7 on 2023-04-28 10:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
from authentik.lib.migrations import fallback_names
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("authentik_crypto", "0003_certificatekeypair_managed"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(fallback_names("authentik_crypto", "certificatekeypair", "name")),
|
||||
migrations.AlterField(
|
||||
model_name="certificatekeypair",
|
||||
name="name",
|
||||
field=models.TextField(unique=True),
|
||||
),
|
||||
]
|
||||
@ -26,7 +26,7 @@ class CertificateKeyPair(SerializerModel, ManagedModel, CreatedUpdatedModel):
|
||||
|
||||
kp_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||
|
||||
name = models.TextField()
|
||||
name = models.TextField(unique=True)
|
||||
certificate_data = models.TextField(help_text=_("PEM-encoded Certificate data"))
|
||||
key_data = models.TextField(
|
||||
help_text=_(
|
||||
|
||||
@ -37,20 +37,22 @@ class TestCrypto(APITestCase):
|
||||
keypair = create_test_cert()
|
||||
self.assertTrue(
|
||||
CertificateKeyPairSerializer(
|
||||
instance=keypair,
|
||||
data={
|
||||
"name": keypair.name,
|
||||
"certificate_data": keypair.certificate_data,
|
||||
"key_data": keypair.key_data,
|
||||
}
|
||||
},
|
||||
).is_valid()
|
||||
)
|
||||
self.assertFalse(
|
||||
CertificateKeyPairSerializer(
|
||||
instance=keypair,
|
||||
data={
|
||||
"name": keypair.name,
|
||||
"certificate_data": "test",
|
||||
"key_data": "test",
|
||||
}
|
||||
},
|
||||
).is_valid()
|
||||
)
|
||||
|
||||
@ -246,7 +248,6 @@ class TestCrypto(APITestCase):
|
||||
with open(f"{temp_dir}/foo.bar/privkey.pem", "w+", encoding="utf-8") as _key:
|
||||
_key.write(builder.private_key)
|
||||
with CONFIG.patch("cert_discovery_dir", temp_dir):
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
certificate_discovery() # pylint: disable=no-value-for-parameter
|
||||
keypair: CertificateKeyPair = CertificateKeyPair.objects.filter(
|
||||
managed=MANAGED_DISCOVERED % "foo"
|
||||
|
||||
@ -453,7 +453,6 @@ class NotificationTransport(SerializerModel):
|
||||
try:
|
||||
from authentik.stages.email.tasks import send_mail
|
||||
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
return send_mail(mail.__dict__) # pylint: disable=no-value-for-parameter
|
||||
except (SMTPException, ConnectionError, OSError) as exc:
|
||||
raise NotificationTransportError(exc) from exc
|
||||
|
||||
@ -57,10 +57,6 @@ def event_trigger_handler(event_uuid: str, trigger_name: str):
|
||||
LOGGER.debug("e(trigger): attempting to prevent infinite loop", trigger=trigger)
|
||||
return
|
||||
|
||||
if not trigger.group:
|
||||
LOGGER.debug("e(trigger): trigger has no group", trigger=trigger)
|
||||
return
|
||||
|
||||
LOGGER.debug("e(trigger): checking if trigger applies", trigger=trigger)
|
||||
try:
|
||||
user = User.objects.filter(pk=event.user.get("pk")).first() or get_anonymous_user()
|
||||
@ -77,6 +73,10 @@ def event_trigger_handler(event_uuid: str, trigger_name: str):
|
||||
if not result.passing:
|
||||
return
|
||||
|
||||
if not trigger.group:
|
||||
LOGGER.debug("e(trigger): trigger has no group", trigger=trigger)
|
||||
return
|
||||
|
||||
LOGGER.debug("e(trigger): event trigger matched", trigger=trigger)
|
||||
# Create the notification objects
|
||||
for transport in trigger.transports.all():
|
||||
|
||||
@ -27,7 +27,6 @@ class StageSerializer(ModelSerializer, MetaNameSerializer):
|
||||
|
||||
def get_component(self, obj: Stage) -> str:
|
||||
"""Get object type so that we know how to edit the object"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
if obj.__class__ == Stage:
|
||||
return ""
|
||||
return obj.component
|
||||
|
||||
@ -182,5 +182,4 @@ class HttpChallengeResponse(JsonResponse):
|
||||
"""Subclass of JsonResponse that uses the `DataclassEncoder`"""
|
||||
|
||||
def __init__(self, challenge, **kwargs) -> None:
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
super().__init__(challenge.data, encoder=DataclassEncoder, **kwargs)
|
||||
|
||||
@ -177,7 +177,6 @@ class ConfigLoader:
|
||||
# Walk each component of the path
|
||||
path_parts = path.split(sep)
|
||||
for comp in path_parts[:-1]:
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
if comp not in root:
|
||||
root[comp] = {}
|
||||
root = root.get(comp, {})
|
||||
|
||||
@ -72,7 +72,7 @@ cookie_domain: null
|
||||
disable_update_check: false
|
||||
disable_startup_analytics: false
|
||||
avatars: env://AUTHENTIK_AUTHENTIK__AVATARS?gravatar,initials
|
||||
geoip: "/geoip/GeoLite2-City.mmdb"
|
||||
geoip: "/app-root/geoip/GeoLite2-City.mmdb"
|
||||
|
||||
footer_links: []
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ def sentry_init(**sentry_init_kwargs):
|
||||
ArgvIntegration(),
|
||||
StdlibIntegration(),
|
||||
DjangoIntegration(transaction_style="function_name"),
|
||||
CeleryIntegration(monitor_beat_tasks=True),
|
||||
CeleryIntegration(),
|
||||
RedisIntegration(),
|
||||
ThreadingIntegration(propagate_hub=True),
|
||||
SocketIntegration(),
|
||||
|
||||
@ -31,7 +31,6 @@ class ServiceConnectionSerializer(ModelSerializer, MetaNameSerializer):
|
||||
|
||||
def get_component(self, obj: OutpostServiceConnection) -> str:
|
||||
"""Get object type so that we know how to edit the object"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
if obj.__class__ == OutpostServiceConnection:
|
||||
return ""
|
||||
return obj.component
|
||||
@ -77,7 +76,6 @@ class ServiceConnectionViewSet(
|
||||
data = []
|
||||
for subclass in all_subclasses(self.queryset.model):
|
||||
subclass: OutpostServiceConnection
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
data.append(
|
||||
{
|
||||
"name": subclass._meta.verbose_name,
|
||||
|
||||
@ -128,7 +128,7 @@ class OutpostServiceConnection(models.Model):
|
||||
@property
|
||||
def state_key(self) -> str:
|
||||
"""Key used to save connection state in cache"""
|
||||
return f"outpost_service_connection_{self.pk.hex}"
|
||||
return f"goauthentik.io/outposts/service_connection_state/{self.pk.hex}"
|
||||
|
||||
@property
|
||||
def state(self) -> OutpostServiceConnectionState:
|
||||
@ -278,7 +278,7 @@ class Outpost(SerializerModel, ManagedModel):
|
||||
@property
|
||||
def state_cache_prefix(self) -> str:
|
||||
"""Key by which the outposts status is saved"""
|
||||
return f"goauthentik.io/outposts/{self.uuid.hex}_state"
|
||||
return f"goauthentik.io/outposts/state/{self.uuid.hex}"
|
||||
|
||||
@property
|
||||
def state(self) -> list["OutpostState"]:
|
||||
@ -433,19 +433,19 @@ class OutpostState:
|
||||
@staticmethod
|
||||
def for_outpost(outpost: Outpost) -> list["OutpostState"]:
|
||||
"""Get all states for an outpost"""
|
||||
keys = cache.keys(f"{outpost.state_cache_prefix}_*")
|
||||
keys = cache.keys(f"{outpost.state_cache_prefix}/*")
|
||||
if not keys:
|
||||
return []
|
||||
states = []
|
||||
for key in keys:
|
||||
instance_uid = key.replace(f"{outpost.state_cache_prefix}_", "")
|
||||
instance_uid = key.replace(f"{outpost.state_cache_prefix}/", "")
|
||||
states.append(OutpostState.for_instance_uid(outpost, instance_uid))
|
||||
return states
|
||||
|
||||
@staticmethod
|
||||
def for_instance_uid(outpost: Outpost, uid: str) -> "OutpostState":
|
||||
"""Get state for a single instance"""
|
||||
key = f"{outpost.state_cache_prefix}_{uid}"
|
||||
key = f"{outpost.state_cache_prefix}/{uid}"
|
||||
default_data = {"uid": uid, "channel_ids": []}
|
||||
data = cache.get(key, default_data)
|
||||
if isinstance(data, str):
|
||||
@ -458,10 +458,10 @@ class OutpostState:
|
||||
|
||||
def save(self, timeout=OUTPOST_HELLO_INTERVAL):
|
||||
"""Save current state to cache"""
|
||||
full_key = f"{self._outpost.state_cache_prefix}_{self.uid}"
|
||||
full_key = f"{self._outpost.state_cache_prefix}/{self.uid}"
|
||||
return cache.set(full_key, asdict(self), timeout=timeout)
|
||||
|
||||
def delete(self):
|
||||
"""Manually delete from cache, used on channel disconnect"""
|
||||
full_key = f"{self._outpost.state_cache_prefix}_{self.uid}"
|
||||
full_key = f"{self._outpost.state_cache_prefix}/{self.uid}"
|
||||
cache.delete(full_key)
|
||||
|
||||
@ -45,7 +45,7 @@ from authentik.providers.proxy.controllers.kubernetes import ProxyKubernetesCont
|
||||
from authentik.root.celery import CELERY_APP
|
||||
|
||||
LOGGER = get_logger()
|
||||
CACHE_KEY_OUTPOST_DOWN = "outpost_teardown_%s"
|
||||
CACHE_KEY_OUTPOST_DOWN = "goauthentik.io/outposts/teardown/%s"
|
||||
|
||||
|
||||
def controller_for_outpost(outpost: Outpost) -> Optional[type[BaseController]]:
|
||||
@ -148,6 +148,8 @@ def outpost_controller(
|
||||
except (ControllerException, ServiceConnectionInvalid) as exc:
|
||||
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||
else:
|
||||
if from_cache:
|
||||
cache.delete(CACHE_KEY_OUTPOST_DOWN % outpost_pk)
|
||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, logs))
|
||||
|
||||
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
from django.urls import path
|
||||
|
||||
from authentik.outposts.channels import OutpostConsumer
|
||||
from authentik.root.middleware import ChannelsLoggingMiddleware
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path("ws/outpost/<uuid:pk>/", OutpostConsumer.as_asgi()),
|
||||
path("ws/outpost/<uuid:pk>/", ChannelsLoggingMiddleware(OutpostConsumer.as_asgi())),
|
||||
]
|
||||
|
||||
@ -40,7 +40,6 @@ class PolicySerializer(ModelSerializer, MetaNameSerializer):
|
||||
|
||||
def get_component(self, obj: Policy) -> str: # pragma: no cover
|
||||
"""Get object component so that we know how to edit the object"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
if obj.__class__ == Policy:
|
||||
return ""
|
||||
return obj.component
|
||||
@ -50,7 +49,6 @@ class PolicySerializer(ModelSerializer, MetaNameSerializer):
|
||||
return obj.bindings.count() + obj.promptstage_set.count()
|
||||
|
||||
def to_representation(self, instance: Policy):
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
if instance.__class__ == Policy or not self._resolve_inheritance:
|
||||
return super().to_representation(instance)
|
||||
return dict(instance.serializer(instance=instance, resolve_inheritance=False).data)
|
||||
|
||||
@ -19,7 +19,6 @@ class AccessDeniedResponse(TemplateResponse):
|
||||
error_message: Optional[str] = None
|
||||
policy_result: Optional[PolicyResult] = None
|
||||
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
def __init__(self, request: HttpRequest, template="policies/denied.html") -> None:
|
||||
super().__init__(request, template)
|
||||
self.title = _("Access denied")
|
||||
|
||||
@ -74,7 +74,6 @@ class PolicyEngine:
|
||||
|
||||
def _check_policy_type(self, binding: PolicyBinding):
|
||||
"""Check policy type, make sure it's not the root class as that has no logic implemented"""
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
if binding.policy is not None and binding.policy.__class__ == Policy:
|
||||
raise PolicyEngineException(f"Policy '{binding.policy}' is root type")
|
||||
|
||||
|
||||
@ -15,8 +15,8 @@ class Migration(migrations.Migration):
|
||||
name="policy_engine_mode",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("all", "ALL, all policies must pass"),
|
||||
("any", "ANY, any policy must pass"),
|
||||
("all", "all, all policies must pass"),
|
||||
("any", "any, any policy must pass"),
|
||||
],
|
||||
default="all",
|
||||
),
|
||||
@ -27,8 +27,8 @@ class Migration(migrations.Migration):
|
||||
name="policy_engine_mode",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("all", "ALL, all policies must pass"),
|
||||
("any", "ANY, any policy must pass"),
|
||||
("all", "all, all policies must pass"),
|
||||
("any", "any, any policy must pass"),
|
||||
],
|
||||
default="any",
|
||||
),
|
||||
|
||||
@ -19,10 +19,8 @@ from authentik.policies.types import PolicyRequest, PolicyResult
|
||||
class PolicyEngineMode(models.TextChoices):
|
||||
"""Decide how results of multiple policies should be combined."""
|
||||
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
MODE_ALL = "all", _("ALL, all policies must pass") # type: "PolicyEngineMode"
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
MODE_ANY = "any", _("ANY, any policy must pass") # type: "PolicyEngineMode"
|
||||
MODE_ALL = "all", _("all, all policies must pass") # type: "PolicyEngineMode"
|
||||
MODE_ANY = "any", _("any, any policy must pass") # type: "PolicyEngineMode"
|
||||
|
||||
|
||||
class PolicyBindingModel(models.Model):
|
||||
|
||||
@ -5,7 +5,9 @@ from django.dispatch import receiver
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.api.applications import user_app_cache_key
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.policies.apps import GAUGE_POLICIES_CACHED
|
||||
from authentik.policies.models import Policy, PolicyBinding, PolicyBindingModel
|
||||
from authentik.policies.types import CACHE_PREFIX
|
||||
from authentik.root.monitoring import monitoring_set
|
||||
|
||||
@ -18,12 +20,14 @@ def monitoring_set_policies(sender, **kwargs):
|
||||
GAUGE_POLICIES_CACHED.set(len(cache.keys(f"{CACHE_PREFIX}_*") or []))
|
||||
|
||||
|
||||
@receiver(post_save)
|
||||
@receiver(post_save, sender=Policy)
|
||||
@receiver(post_save, sender=PolicyBinding)
|
||||
@receiver(post_save, sender=PolicyBindingModel)
|
||||
@receiver(post_save, sender=Group)
|
||||
@receiver(post_save, sender=User)
|
||||
def invalidate_policy_cache(sender, instance, **_):
|
||||
"""Invalidate Policy cache when policy is updated"""
|
||||
from authentik.policies.models import Policy, PolicyBinding
|
||||
|
||||
if isinstance(instance, Policy):
|
||||
if sender == Policy:
|
||||
total = 0
|
||||
for binding in PolicyBinding.objects.filter(policy=instance):
|
||||
prefix = f"{CACHE_PREFIX}{binding.policy_binding_uuid.hex}_{binding.policy.pk.hex}*"
|
||||
|
||||
@ -202,5 +202,4 @@ class HttpResponseRedirectScheme(HttpResponseRedirect):
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
self.allowed_schemes = allowed_schemes or ["http", "https", "ftp"]
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
super().__init__(redirect_to, *args, **kwargs)
|
||||
|
||||
@ -2,19 +2,12 @@
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
from pydantic import ValidationError
|
||||
from pydanticscim.service_provider import (
|
||||
Bulk,
|
||||
ChangePassword,
|
||||
Filter,
|
||||
Patch,
|
||||
ServiceProviderConfiguration,
|
||||
Sort,
|
||||
)
|
||||
from requests import RequestException, Session
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.providers.scim.clients.exceptions import SCIMRequestException
|
||||
from authentik.providers.scim.clients.exceptions import ResourceMissing, SCIMRequestException
|
||||
from authentik.providers.scim.clients.schema import ServiceProviderConfiguration
|
||||
from authentik.providers.scim.models import SCIMProvider
|
||||
|
||||
T = TypeVar("T")
|
||||
@ -22,18 +15,6 @@ T = TypeVar("T")
|
||||
SchemaType = TypeVar("SchemaType")
|
||||
|
||||
|
||||
def default_service_provider_config() -> ServiceProviderConfiguration:
|
||||
"""Fallback service provider configuration"""
|
||||
return ServiceProviderConfiguration(
|
||||
patch=Patch(supported=False),
|
||||
bulk=Bulk(supported=False),
|
||||
filter=Filter(supported=False),
|
||||
changePassword=ChangePassword(supported=False),
|
||||
sort=Sort(supported=False),
|
||||
authenticationSchemes=[],
|
||||
)
|
||||
|
||||
|
||||
class SCIMClient(Generic[T, SchemaType]):
|
||||
"""SCIM Client"""
|
||||
|
||||
@ -73,6 +54,8 @@ class SCIMClient(Generic[T, SchemaType]):
|
||||
raise SCIMRequestException(None) from exc
|
||||
self.logger.debug("scim request", path=path, method=method, **kwargs)
|
||||
if response.status_code >= 400:
|
||||
if response.status_code == 404:
|
||||
raise ResourceMissing(response)
|
||||
self.logger.warning(
|
||||
"Failed to send SCIM request", path=path, method=method, response=response.text
|
||||
)
|
||||
@ -83,7 +66,7 @@ class SCIMClient(Generic[T, SchemaType]):
|
||||
|
||||
def get_service_provider_config(self):
|
||||
"""Get Service provider config"""
|
||||
default_config = default_service_provider_config()
|
||||
default_config = ServiceProviderConfiguration.default()
|
||||
try:
|
||||
return ServiceProviderConfiguration.parse_obj(
|
||||
self._request("GET", "/ServiceProviderConfig")
|
||||
|
||||
@ -41,3 +41,8 @@ class SCIMRequestException(SentryIgnoredException):
|
||||
except ValidationError:
|
||||
pass
|
||||
return super().__str__()
|
||||
|
||||
|
||||
class ResourceMissing(SCIMRequestException):
|
||||
"""Error raised when the provider raises a 404, meaning that we
|
||||
should delete our internal ID and re-create the object"""
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
from deepmerge import always_merger
|
||||
from pydantic import ValidationError
|
||||
from pydanticscim.group import GroupMember
|
||||
from pydanticscim.responses import PatchOp, PatchOperation, PatchRequest
|
||||
from pydanticscim.responses import PatchOp, PatchOperation
|
||||
|
||||
from authentik.core.exceptions import PropertyMappingExpressionException
|
||||
from authentik.core.models import Group
|
||||
@ -10,8 +10,13 @@ from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.policies.utils import delete_none_keys
|
||||
from authentik.providers.scim.clients.base import SCIMClient
|
||||
from authentik.providers.scim.clients.exceptions import StopSync
|
||||
from authentik.providers.scim.clients.exceptions import (
|
||||
ResourceMissing,
|
||||
SCIMRequestException,
|
||||
StopSync,
|
||||
)
|
||||
from authentik.providers.scim.clients.schema import Group as SCIMGroupSchema
|
||||
from authentik.providers.scim.clients.schema import PatchRequest
|
||||
from authentik.providers.scim.models import SCIMGroup, SCIMMapping, SCIMUser
|
||||
|
||||
|
||||
@ -23,15 +28,11 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]):
|
||||
scim_group = SCIMGroup.objects.filter(provider=self.provider, group=obj).first()
|
||||
if not scim_group:
|
||||
return self._create(obj)
|
||||
scim_group = self.to_scim(obj)
|
||||
scim_group.id = scim_group.id
|
||||
return self._request(
|
||||
"PUT",
|
||||
f"/Groups/{scim_group.id}",
|
||||
data=scim_group.json(
|
||||
exclude_unset=True,
|
||||
),
|
||||
)
|
||||
try:
|
||||
return self._update(obj, scim_group)
|
||||
except ResourceMissing:
|
||||
scim_group.delete()
|
||||
return self._create(obj)
|
||||
|
||||
def delete(self, obj: Group):
|
||||
"""Delete group"""
|
||||
@ -81,12 +82,15 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]):
|
||||
|
||||
users = list(obj.users.order_by("id").values_list("id", flat=True))
|
||||
connections = SCIMUser.objects.filter(provider=self.provider, user__pk__in=users)
|
||||
members = []
|
||||
for user in connections:
|
||||
scim_group.members.append(
|
||||
members.append(
|
||||
GroupMember(
|
||||
value=user.id,
|
||||
)
|
||||
)
|
||||
if members:
|
||||
scim_group.members = members
|
||||
return scim_group
|
||||
|
||||
def _create(self, group: Group):
|
||||
@ -101,13 +105,32 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]):
|
||||
)
|
||||
SCIMGroup.objects.create(provider=self.provider, group=group, id=response["id"])
|
||||
|
||||
def _update(self, group: Group, connection: SCIMGroup):
|
||||
"""Update existing group"""
|
||||
scim_group = self.to_scim(group)
|
||||
scim_group.id = connection.id
|
||||
try:
|
||||
return self._request(
|
||||
"PUT",
|
||||
f"/Groups/{scim_group.id}",
|
||||
data=scim_group.json(
|
||||
exclude_unset=True,
|
||||
),
|
||||
)
|
||||
except SCIMRequestException:
|
||||
# Some providers don't support PUT on groups, so this is mainly a fix for the initial
|
||||
# sync, send patch add requests for all the users the group currently has
|
||||
# TODO: send patch request for group name
|
||||
users = list(group.users.order_by("id").values_list("id", flat=True))
|
||||
return self._patch_add_users(group, users)
|
||||
|
||||
def _patch(
|
||||
self,
|
||||
group_id: str,
|
||||
*ops: PatchOperation,
|
||||
):
|
||||
req = PatchRequest(Operations=ops)
|
||||
self._request("PATCH", f"/Groups/{group_id}", data=req.json(exclude_unset=True))
|
||||
self._request("PATCH", f"/Groups/{group_id}", data=req.json())
|
||||
|
||||
def update_group(self, group: Group, action: PatchOp, users_set: set[int]):
|
||||
"""Update a group, either using PUT to replace it or PATCH if supported"""
|
||||
@ -116,7 +139,17 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]):
|
||||
return self._patch_add_users(group, users_set)
|
||||
if action == PatchOp.remove:
|
||||
return self._patch_remove_users(group, users_set)
|
||||
return self.write(group)
|
||||
try:
|
||||
return self.write(group)
|
||||
except SCIMRequestException as exc:
|
||||
if self._config.is_fallback:
|
||||
# Assume that provider does not support PUT and also doesn't support
|
||||
# ServiceProviderConfig, so try PATCH as a fallback
|
||||
if action == PatchOp.add:
|
||||
return self._patch_add_users(group, users_set)
|
||||
if action == PatchOp.remove:
|
||||
return self._patch_remove_users(group, users_set)
|
||||
raise exc
|
||||
|
||||
def _patch_add_users(self, group: Group, users_set: set[int]):
|
||||
"""Add users in users_set to group"""
|
||||
@ -133,6 +166,8 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]):
|
||||
"id", flat=True
|
||||
)
|
||||
)
|
||||
if len(user_ids) < 1:
|
||||
return
|
||||
self._patch(
|
||||
scim_group.id,
|
||||
PatchOperation(
|
||||
@ -157,6 +192,8 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]):
|
||||
"id", flat=True
|
||||
)
|
||||
)
|
||||
if len(user_ids) < 1:
|
||||
return
|
||||
self._patch(
|
||||
scim_group.id,
|
||||
PatchOperation(
|
||||
|
||||
@ -1,17 +1,54 @@
|
||||
"""Custom SCIM schemas"""
|
||||
from typing import Optional
|
||||
|
||||
from pydanticscim.group import Group as SCIMGroupSchema
|
||||
from pydanticscim.user import User as SCIMUserSchema
|
||||
from pydanticscim.group import Group as BaseGroup
|
||||
from pydanticscim.responses import PatchRequest as BasePatchRequest
|
||||
from pydanticscim.service_provider import Bulk, ChangePassword, Filter, Patch
|
||||
from pydanticscim.service_provider import (
|
||||
ServiceProviderConfiguration as BaseServiceProviderConfiguration,
|
||||
)
|
||||
from pydanticscim.service_provider import Sort
|
||||
from pydanticscim.user import User as BaseUser
|
||||
|
||||
|
||||
class User(SCIMUserSchema):
|
||||
class User(BaseUser):
|
||||
"""Modified User schema with added externalId field"""
|
||||
|
||||
externalId: Optional[str] = None
|
||||
|
||||
|
||||
class Group(SCIMGroupSchema):
|
||||
class Group(BaseGroup):
|
||||
"""Modified Group schema with added externalId field"""
|
||||
|
||||
externalId: Optional[str] = None
|
||||
|
||||
|
||||
class ServiceProviderConfiguration(BaseServiceProviderConfiguration):
|
||||
"""ServiceProviderConfig with fallback"""
|
||||
|
||||
_is_fallback: Optional[bool] = False
|
||||
|
||||
@property
|
||||
def is_fallback(self) -> bool:
|
||||
"""Check if this service provider config was retrieved from the API endpoint
|
||||
or a fallback was used"""
|
||||
return self._is_fallback
|
||||
|
||||
@staticmethod
|
||||
def default() -> "ServiceProviderConfiguration":
|
||||
"""Get default configuration, which doesn't support any optional features as fallback"""
|
||||
return ServiceProviderConfiguration(
|
||||
patch=Patch(supported=False),
|
||||
bulk=Bulk(supported=False),
|
||||
filter=Filter(supported=False),
|
||||
changePassword=ChangePassword(supported=False),
|
||||
sort=Sort(supported=False),
|
||||
authenticationSchemes=[],
|
||||
_is_fallback=True,
|
||||
)
|
||||
|
||||
|
||||
class PatchRequest(BasePatchRequest):
|
||||
"""PatchRequest which correctly sets schemas"""
|
||||
|
||||
schemas: tuple[str] = ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]
|
||||
|
||||
@ -8,7 +8,7 @@ from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.utils.errors import exception_to_string
|
||||
from authentik.policies.utils import delete_none_keys
|
||||
from authentik.providers.scim.clients.base import SCIMClient
|
||||
from authentik.providers.scim.clients.exceptions import StopSync
|
||||
from authentik.providers.scim.clients.exceptions import ResourceMissing, StopSync
|
||||
from authentik.providers.scim.clients.schema import User as SCIMUserSchema
|
||||
from authentik.providers.scim.models import SCIMMapping, SCIMUser
|
||||
|
||||
@ -21,7 +21,11 @@ class SCIMUserClient(SCIMClient[User, SCIMUserSchema]):
|
||||
scim_user = SCIMUser.objects.filter(provider=self.provider, user=obj).first()
|
||||
if not scim_user:
|
||||
return self._create(obj)
|
||||
return self._update(obj, scim_user)
|
||||
try:
|
||||
return self._update(obj, scim_user)
|
||||
except ResourceMissing:
|
||||
scim_user.delete()
|
||||
return self._create(obj)
|
||||
|
||||
def delete(self, obj: User):
|
||||
"""Delete user"""
|
||||
|
||||
0
authentik/providers/scim/management/__init__.py
Normal file
0
authentik/providers/scim/management/__init__.py
Normal file
23
authentik/providers/scim/management/commands/scim_sync.py
Normal file
23
authentik/providers/scim/management/commands/scim_sync.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""SCIM Sync"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.providers.scim.models import SCIMProvider
|
||||
from authentik.providers.scim.tasks import scim_sync
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Run sync for an SCIM Provider"""
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("providers", nargs="+", type=str)
|
||||
|
||||
def handle(self, **options):
|
||||
for provider_name in options["providers"]:
|
||||
provider = SCIMProvider.objects.filter(name=provider_name).first()
|
||||
if not provider:
|
||||
LOGGER.warning("Provider does not exist", name=provider_name)
|
||||
continue
|
||||
scim_sync.delay(provider.pk).get()
|
||||
@ -1,9 +1,9 @@
|
||||
"""SCIM Provider tasks"""
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
from celery.result import allow_join_result
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Model
|
||||
from django.db.models import Model, QuerySet
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from pydanticscim.responses import PatchOp
|
||||
@ -94,7 +94,8 @@ def scim_sync_users(page: int, provider_pk: int):
|
||||
}
|
||||
)
|
||||
)
|
||||
except StopSync:
|
||||
except StopSync as exc:
|
||||
LOGGER.warning("Stopping sync", exc=exc)
|
||||
break
|
||||
return messages
|
||||
|
||||
@ -126,7 +127,8 @@ def scim_sync_group(page: int, provider_pk: int):
|
||||
}
|
||||
)
|
||||
)
|
||||
except StopSync:
|
||||
except StopSync as exc:
|
||||
LOGGER.warning("Stopping sync", exc=exc)
|
||||
break
|
||||
return messages
|
||||
|
||||
@ -141,6 +143,20 @@ def scim_signal_direct(model: str, pk: Any, raw_op: str):
|
||||
operation = PatchOp(raw_op)
|
||||
for provider in SCIMProvider.objects.all():
|
||||
client = client_for_model(provider, instance)
|
||||
# Check if the object is allowed within the provider's restrictions
|
||||
queryset: Optional[QuerySet] = None
|
||||
if isinstance(instance, User):
|
||||
queryset = provider.get_user_qs()
|
||||
if isinstance(instance, Group):
|
||||
queryset = provider.get_group_qs()
|
||||
if not queryset:
|
||||
continue
|
||||
|
||||
# The queryset we get from the provider must include the instance we've got given
|
||||
# otherwise ignore this provider
|
||||
if not queryset.filter(pk=instance.pk).exists():
|
||||
continue
|
||||
|
||||
try:
|
||||
if operation == PatchOp.add:
|
||||
client.write(instance)
|
||||
@ -157,6 +173,13 @@ def scim_signal_m2m(group_pk: str, action: str, pk_set: list[int]):
|
||||
if not group:
|
||||
return
|
||||
for provider in SCIMProvider.objects.all():
|
||||
# Check if the object is allowed within the provider's restrictions
|
||||
queryset: QuerySet = provider.get_group_qs()
|
||||
# The queryset we get from the provider must include the instance we've got given
|
||||
# otherwise ignore this provider
|
||||
if not queryset.filter(pk=group_pk).exists():
|
||||
continue
|
||||
|
||||
client = SCIMGroupClient(provider)
|
||||
try:
|
||||
operation = None
|
||||
|
||||
@ -6,7 +6,7 @@ from requests_mock import Mocker
|
||||
from authentik.blueprints.tests import apply_blueprint
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.scim.clients.base import default_service_provider_config
|
||||
from authentik.providers.scim.clients.schema import ServiceProviderConfiguration
|
||||
from authentik.providers.scim.models import SCIMMapping, SCIMProvider
|
||||
from authentik.providers.scim.tasks import scim_sync
|
||||
|
||||
@ -39,7 +39,7 @@ class SCIMMembershipTests(TestCase):
|
||||
|
||||
def test_member_add(self):
|
||||
"""Test member add"""
|
||||
config = default_service_provider_config()
|
||||
config = ServiceProviderConfiguration.default()
|
||||
config.patch.supported = True
|
||||
user_scim_id = generate_id()
|
||||
group_scim_id = generate_id()
|
||||
@ -117,13 +117,14 @@ class SCIMMembershipTests(TestCase):
|
||||
"path": "members",
|
||||
"value": [{"value": user_scim_id}],
|
||||
}
|
||||
]
|
||||
],
|
||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
},
|
||||
)
|
||||
|
||||
def test_member_remove(self):
|
||||
"""Test member remove"""
|
||||
config = default_service_provider_config()
|
||||
config = ServiceProviderConfiguration.default()
|
||||
config.patch.supported = True
|
||||
user_scim_id = generate_id()
|
||||
group_scim_id = generate_id()
|
||||
@ -201,7 +202,8 @@ class SCIMMembershipTests(TestCase):
|
||||
"path": "members",
|
||||
"value": [{"value": user_scim_id}],
|
||||
}
|
||||
]
|
||||
],
|
||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
},
|
||||
)
|
||||
|
||||
@ -227,6 +229,7 @@ class SCIMMembershipTests(TestCase):
|
||||
"path": "members",
|
||||
"value": [{"value": user_scim_id}],
|
||||
}
|
||||
]
|
||||
],
|
||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
},
|
||||
)
|
||||
|
||||
@ -14,7 +14,6 @@ class ChannelsStorage(SessionStorage):
|
||||
"""Send contrib.messages over websocket"""
|
||||
|
||||
def __init__(self, request: HttpRequest) -> None:
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
super().__init__(request)
|
||||
self.channel = get_channel_layer()
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"""Dynamically set SameSite depending if the upstream connection is TLS or not"""
|
||||
from hashlib import sha512
|
||||
from time import time
|
||||
from timeit import default_timer
|
||||
from typing import Callable
|
||||
|
||||
from django.conf import settings
|
||||
@ -130,6 +131,28 @@ class SessionMiddleware(UpstreamSessionMiddleware):
|
||||
return response
|
||||
|
||||
|
||||
class ChannelsLoggingMiddleware:
|
||||
"""Logging middleware for channels"""
|
||||
|
||||
def __init__(self, inner):
|
||||
self.inner = inner
|
||||
|
||||
async def __call__(self, scope, receive, send):
|
||||
self.log(scope)
|
||||
return await self.inner(scope, receive, send)
|
||||
|
||||
def log(self, scope: dict, **kwargs):
|
||||
"""Log request"""
|
||||
headers = dict(scope.get("headers", {}))
|
||||
LOGGER.info(
|
||||
scope["path"],
|
||||
scheme="ws",
|
||||
remote=scope.get("client", [""])[0],
|
||||
user_agent=headers.get(b"user-agent", b"").decode(),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class LoggingMiddleware:
|
||||
"""Logger middleware"""
|
||||
|
||||
@ -139,14 +162,14 @@ class LoggingMiddleware:
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request: HttpRequest) -> HttpResponse:
|
||||
start = time()
|
||||
start = default_timer()
|
||||
response = self.get_response(request)
|
||||
status_code = response.status_code
|
||||
kwargs = {
|
||||
"request_id": request.request_id,
|
||||
}
|
||||
kwargs.update(getattr(response, "ak_context", {}))
|
||||
self.log(request, status_code, int((time() - start) * 1000), **kwargs)
|
||||
self.log(request, status_code, int((default_timer() - start) * 1000), **kwargs)
|
||||
return response
|
||||
|
||||
def log(self, request: HttpRequest, status_code: int, runtime: int, **kwargs):
|
||||
|
||||
@ -12,12 +12,13 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [
|
||||
"authentik.sources.oauth.types.facebook",
|
||||
"authentik.sources.oauth.types.github",
|
||||
"authentik.sources.oauth.types.google",
|
||||
"authentik.sources.oauth.types.mailcow",
|
||||
"authentik.sources.oauth.types.oidc",
|
||||
"authentik.sources.oauth.types.okta",
|
||||
"authentik.sources.oauth.types.patreon",
|
||||
"authentik.sources.oauth.types.reddit",
|
||||
"authentik.sources.oauth.types.twitter",
|
||||
"authentik.sources.oauth.types.mailcow",
|
||||
"authentik.sources.oauth.types.twitch",
|
||||
"authentik.sources.oauth.types.twitter",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -66,7 +66,7 @@ class OAuthClient(BaseOAuthClient):
|
||||
response.raise_for_status()
|
||||
except RequestException as exc:
|
||||
raise OAuthSourceException(
|
||||
response=exc.response.text if exc.response else str(exc),
|
||||
exc.response.text if exc.response else str(exc),
|
||||
) from exc
|
||||
return response.text
|
||||
|
||||
|
||||
@ -163,6 +163,15 @@ class DiscordOAuthSource(OAuthSource):
|
||||
verbose_name_plural = _("Discord OAuth Sources")
|
||||
|
||||
|
||||
class PatreonOAuthSource(OAuthSource):
|
||||
"""Social Login using Patreon."""
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = _("Patreon OAuth Source")
|
||||
verbose_name_plural = _("Patreon OAuth Sources")
|
||||
|
||||
|
||||
class GoogleOAuthSource(OAuthSource):
|
||||
"""Social Login using Google or Google Workspace (GSuite)."""
|
||||
|
||||
|
||||
67
authentik/sources/oauth/tests/test_type_patreon.py
Normal file
67
authentik/sources/oauth/tests/test_type_patreon.py
Normal file
@ -0,0 +1,67 @@
|
||||
"""Patreon Type tests"""
|
||||
from django.test import RequestFactory, TestCase
|
||||
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.patreon import PatreonOAuthCallback
|
||||
|
||||
PATREON_USER = {
|
||||
"data": {
|
||||
"attributes": {
|
||||
"about": None,
|
||||
"created": "2017-10-20T21:36:23+00:00",
|
||||
"discord_id": None,
|
||||
"email": "corgi@example.com",
|
||||
"facebook": None,
|
||||
"facebook_id": None,
|
||||
"first_name": "Corgi",
|
||||
"full_name": "Corgi The Dev",
|
||||
"gender": 0,
|
||||
"has_password": True,
|
||||
"image_url": "https://c8.patreon.com/2/400/0000000",
|
||||
"is_deleted": False,
|
||||
"is_email_verified": False,
|
||||
"is_nuked": False,
|
||||
"is_suspended": False,
|
||||
"last_name": "The Dev",
|
||||
"social_connections": {
|
||||
"deviantart": None,
|
||||
"discord": None,
|
||||
"facebook": None,
|
||||
"reddit": None,
|
||||
"spotify": None,
|
||||
"twitch": None,
|
||||
"twitter": None,
|
||||
"youtube": None,
|
||||
},
|
||||
"thumb_url": "https://c8.patreon.com/2/100/0000000",
|
||||
"twitch": None,
|
||||
"twitter": None,
|
||||
"url": "https://www.patreon.com/corgithedev",
|
||||
"vanity": "corgithedev",
|
||||
"youtube": None,
|
||||
},
|
||||
"id": "0000000",
|
||||
"relationships": {"pledges": {"data": []}},
|
||||
"type": "user",
|
||||
},
|
||||
"links": {"self": "https://www.patreon.com/api/user/0000000"},
|
||||
}
|
||||
|
||||
|
||||
class TestTypePatreon(TestCase):
|
||||
"""OAuth Source tests"""
|
||||
|
||||
def setUp(self):
|
||||
self.source = OAuthSource.objects.create(
|
||||
name="test",
|
||||
slug="test",
|
||||
provider_type="Patreon",
|
||||
)
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_enroll_context(self):
|
||||
"""Test Patreon Enrollment context"""
|
||||
ak_context = PatreonOAuthCallback().get_user_enroll_context(PATREON_USER)
|
||||
self.assertEqual(ak_context["username"], PATREON_USER["data"]["attributes"]["vanity"])
|
||||
self.assertEqual(ak_context["email"], PATREON_USER["data"]["attributes"]["email"])
|
||||
self.assertEqual(ak_context["name"], PATREON_USER["data"]["attributes"]["full_name"])
|
||||
@ -59,7 +59,6 @@ class AppleOAuthClient(OAuth2Client):
|
||||
"aud": "https://appleid.apple.com",
|
||||
"sub": parts[0].strip(),
|
||||
}
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
jwt = encode(payload, self.source.consumer_secret, "ES256", {"kid": parts[2].strip()})
|
||||
LOGGER.debug("signing payload as secret key", payload=payload, jwt=jwt)
|
||||
return jwt
|
||||
|
||||
50
authentik/sources/oauth/types/patreon.py
Normal file
50
authentik/sources/oauth/types/patreon.py
Normal file
@ -0,0 +1,50 @@
|
||||
"""Patreon OAuth Views"""
|
||||
from typing import Any
|
||||
|
||||
from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.registry import SourceType, registry
|
||||
from authentik.sources.oauth.views.callback import OAuthCallback
|
||||
from authentik.sources.oauth.views.redirect import OAuthRedirect
|
||||
|
||||
|
||||
class PatreonOAuthRedirect(OAuthRedirect):
|
||||
"""Patreon OAuth2 Redirect"""
|
||||
|
||||
def get_additional_parameters(self, source: OAuthSource): # pragma: no cover
|
||||
return {
|
||||
"scope": ["openid", "email", "profile"],
|
||||
}
|
||||
|
||||
|
||||
class PatreonOAuthCallback(OAuthCallback):
|
||||
"""Patreon OAuth2 Callback"""
|
||||
|
||||
client_class: UserprofileHeaderAuthClient
|
||||
|
||||
def get_user_id(self, info: dict[str, str]) -> str:
|
||||
return info.get("data", {}).get("id")
|
||||
|
||||
def get_user_enroll_context(
|
||||
self,
|
||||
info: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"username": info.get("data", {}).get("attributes", {}).get("vanity"),
|
||||
"email": info.get("data", {}).get("attributes", {}).get("email"),
|
||||
"name": info.get("data", {}).get("attributes", {}).get("full_name"),
|
||||
}
|
||||
|
||||
|
||||
@registry.register()
|
||||
class PatreonType(SourceType):
|
||||
"""OpenIDConnect Type definition"""
|
||||
|
||||
callback_view = PatreonOAuthCallback
|
||||
redirect_view = PatreonOAuthRedirect
|
||||
name = "Patreon"
|
||||
slug = "patreon"
|
||||
|
||||
authorization_url = "https://www.patreon.com/oauth2/authorize"
|
||||
access_token_url = "https://www.patreon.com/api/oauth2/token" # nosec
|
||||
profile_url = "https://www.patreon.com/api/oauth2/api/current_user"
|
||||
@ -33,7 +33,6 @@ class Command(BaseCommand):
|
||||
template_context={},
|
||||
)
|
||||
try:
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
send_mail(message.__dict__, stage.pk)
|
||||
finally:
|
||||
if delete_stage:
|
||||
|
||||
@ -24,7 +24,6 @@ class TestEmailStageAPI(APITestCase):
|
||||
def test_validate(self):
|
||||
"""Test EmailStage's validation"""
|
||||
self.assertEqual(
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
EmailStageSerializer().validate_template(EmailTemplates.ACCOUNT_CONFIRM),
|
||||
EmailTemplates.ACCOUNT_CONFIRM,
|
||||
)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
version: '3.4'
|
||||
version: "3.4"
|
||||
|
||||
services:
|
||||
postgresql:
|
||||
@ -14,9 +14,9 @@ services:
|
||||
volumes:
|
||||
- database:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=${PG_PASS:?database password required}
|
||||
- POSTGRES_USER=${PG_USER:-authentik}
|
||||
- POSTGRES_DB=${PG_DB:-authentik}
|
||||
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
|
||||
POSTGRES_USER: ${PG_USER:-authentik}
|
||||
POSTGRES_DB: ${PG_DB:-authentik}
|
||||
env_file:
|
||||
- .env
|
||||
redis:
|
||||
@ -47,8 +47,8 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "${AUTHENTIK_PORT_HTTP:-9000}:9000"
|
||||
- "${AUTHENTIK_PORT_HTTPS:-9443}:9443"
|
||||
- "${COMPOSE_PORT_HTTP:-9000}:9000"
|
||||
- "${COMPOSE_PORT_HTTPS:-9443}:9443"
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.4.1}
|
||||
restart: unless-stopped
|
||||
|
||||
4
go.mod
4
go.mod
@ -22,14 +22,14 @@ require (
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
github.com/prometheus/client_golang v1.15.0
|
||||
github.com/prometheus/client_golang v1.15.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
goauthentik.io/api/v3 v3.2023041.3
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sync v0.2.0
|
||||
gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
||||
|
||||
7
go.sum
7
go.sum
@ -278,8 +278,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac h1:jWKYCNlX4J5s8M0nHYkh7Y7c9gRVDEb3mq51j5J0F5M=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
|
||||
github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM=
|
||||
github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
|
||||
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
|
||||
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
@ -447,8 +447,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
@ -30,7 +30,7 @@ type RedisConfig struct {
|
||||
Password string `yaml:"password" env:"AUTHENTIK_REDIS__PASSWORD"`
|
||||
TLS bool `yaml:"tls" env:"AUTHENTIK_REDIS__TLS"`
|
||||
TLSReqs string `yaml:"tls_reqs" env:"AUTHENTIK_REDIS__TLS_REQS"`
|
||||
DB int `yaml:"cache_db" env:"AUTHENTIK_REDIS__CACHE_DB"`
|
||||
DB int `yaml:"cache_db" env:"AUTHENTIK_REDIS__DB"`
|
||||
CacheTimeout int `yaml:"cache_timeout" env:"AUTHENTIK_REDIS__CACHE_TIMEOUT"`
|
||||
CacheTimeoutFlows int `yaml:"cache_timeout_flows" env:"AUTHENTIK_REDIS__CACHE_TIMEOUT_FLOWS"`
|
||||
CacheTimeoutPolicies int `yaml:"cache_timeout_policies" env:"AUTHENTIK_REDIS__CACHE_TIMEOUT_POLICIES"`
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
"goauthentik.io/internal/config"
|
||||
"goauthentik.io/internal/utils"
|
||||
)
|
||||
|
||||
func (ls *LDAPServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
@ -38,11 +39,8 @@ func (ls *LDAPServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certifica
|
||||
|
||||
func (ls *LDAPServer) StartLDAPTLSServer() error {
|
||||
listen := config.Get().Listen.LDAPS
|
||||
tlsConfig := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
GetCertificate: ls.getCertificates,
|
||||
}
|
||||
tlsConfig := utils.GetTLSConfig()
|
||||
tlsConfig.GetCertificate = ls.getCertificates
|
||||
|
||||
ln, err := net.Listen("tcp", listen)
|
||||
if err != nil {
|
||||
|
||||
@ -18,6 +18,7 @@ import (
|
||||
"goauthentik.io/internal/outpost/ldap/search"
|
||||
"goauthentik.io/internal/outpost/ldap/server"
|
||||
"goauthentik.io/internal/outpost/ldap/utils"
|
||||
"goauthentik.io/internal/outpost/ldap/utils/paginator"
|
||||
)
|
||||
|
||||
type DirectSearcher struct {
|
||||
@ -124,15 +125,10 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
return nil
|
||||
}
|
||||
|
||||
u, _, err := searchReq.Execute()
|
||||
u := paginator.FetchUsers(searchReq)
|
||||
uapisp.Finish()
|
||||
|
||||
if err != nil {
|
||||
req.Log().WithError(err).Warning("failed to get users")
|
||||
return err
|
||||
}
|
||||
|
||||
users = &u.Results
|
||||
users = &u
|
||||
} else {
|
||||
if flags.UserInfo == nil {
|
||||
uapisp := sentry.StartSpan(errCtx, "authentik.providers.ldap.search.api_user")
|
||||
@ -170,29 +166,24 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
|
||||
searchReq = searchReq.MembersByPk([]int32{flags.UserPk})
|
||||
}
|
||||
|
||||
g, _, err := searchReq.Execute()
|
||||
g := paginator.FetchGroups(searchReq)
|
||||
gapisp.Finish()
|
||||
if err != nil {
|
||||
req.Log().WithError(err).Warning("failed to get groups")
|
||||
return err
|
||||
}
|
||||
req.Log().WithField("count", len(g.Results)).Trace("Got results from API")
|
||||
req.Log().WithField("count", len(g)).Trace("Got results from API")
|
||||
|
||||
if !flags.CanSearch {
|
||||
for i, results := range g.Results {
|
||||
for i, results := range g {
|
||||
// If they can't search, remove any users from the group results except the one we're looking for.
|
||||
g.Results[i].Users = []int32{flags.UserPk}
|
||||
g[i].Users = []int32{flags.UserPk}
|
||||
for _, u := range results.UsersObj {
|
||||
if u.Pk == flags.UserPk {
|
||||
g.Results[i].UsersObj = []api.GroupMember{u}
|
||||
g[i].UsersObj = []api.GroupMember{u}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groups = &g.Results
|
||||
|
||||
groups = &g
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"goauthentik.io/api/v3"
|
||||
)
|
||||
|
||||
const pageSize = 100
|
||||
|
||||
func (ms *MemorySearcher) FetchUsers() []api.User {
|
||||
fetchUsersOffset := func(page int) (*api.PaginatedUserList, error) {
|
||||
users, _, err := ms.si.GetAPIClient().CoreApi.CoreUsersList(context.TODO()).Page(int32(page)).PageSize(pageSize).Execute()
|
||||
if err != nil {
|
||||
ms.log.WithError(err).Warning("failed to update users")
|
||||
return nil, err
|
||||
}
|
||||
ms.log.WithField("page", page).WithField("count", len(users.Results)).Debug("fetched users")
|
||||
return users, nil
|
||||
}
|
||||
page := 1
|
||||
users := make([]api.User, 0)
|
||||
for {
|
||||
apiUsers, err := fetchUsersOffset(page)
|
||||
if err != nil {
|
||||
return users
|
||||
}
|
||||
users = append(users, apiUsers.Results...)
|
||||
if apiUsers.Pagination.Next > 0 {
|
||||
page += 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func (ms *MemorySearcher) FetchGroups() []api.Group {
|
||||
fetchGroupsOffset := func(page int) (*api.PaginatedGroupList, error) {
|
||||
groups, _, err := ms.si.GetAPIClient().CoreApi.CoreGroupsList(context.TODO()).Page(int32(page)).PageSize(pageSize).Execute()
|
||||
if err != nil {
|
||||
ms.log.WithError(err).Warning("failed to update groups")
|
||||
return nil, err
|
||||
}
|
||||
ms.log.WithField("page", page).WithField("count", len(groups.Results)).Debug("fetched groups")
|
||||
return groups, nil
|
||||
}
|
||||
page := 1
|
||||
groups := make([]api.Group, 0)
|
||||
for {
|
||||
apiGroups, err := fetchGroupsOffset(page)
|
||||
if err != nil {
|
||||
return groups
|
||||
}
|
||||
groups = append(groups, apiGroups.Results...)
|
||||
if apiGroups.Pagination.Next > 0 {
|
||||
page += 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return groups
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@ -16,6 +17,7 @@ import (
|
||||
"goauthentik.io/internal/outpost/ldap/search"
|
||||
"goauthentik.io/internal/outpost/ldap/server"
|
||||
"goauthentik.io/internal/outpost/ldap/utils"
|
||||
"goauthentik.io/internal/outpost/ldap/utils/paginator"
|
||||
)
|
||||
|
||||
type MemorySearcher struct {
|
||||
@ -32,8 +34,8 @@ func NewMemorySearcher(si server.LDAPServerInstance) *MemorySearcher {
|
||||
log: log.WithField("logger", "authentik.outpost.ldap.searcher.memory"),
|
||||
}
|
||||
ms.log.Debug("initialised memory searcher")
|
||||
ms.users = ms.FetchUsers()
|
||||
ms.groups = ms.FetchGroups()
|
||||
ms.users = paginator.FetchUsers(ms.si.GetAPIClient().CoreApi.CoreUsersList(context.TODO()))
|
||||
ms.groups = paginator.FetchGroups(ms.si.GetAPIClient().CoreApi.CoreGroupsList(context.TODO()))
|
||||
return ms
|
||||
}
|
||||
|
||||
|
||||
64
internal/outpost/ldap/utils/paginator/paginator.go
Normal file
64
internal/outpost/ldap/utils/paginator/paginator.go
Normal file
@ -0,0 +1,64 @@
|
||||
package paginator
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/api/v3"
|
||||
)
|
||||
|
||||
const PageSize = 100
|
||||
|
||||
func FetchUsers(req api.ApiCoreUsersListRequest) []api.User {
|
||||
fetchUsersOffset := func(page int) (*api.PaginatedUserList, error) {
|
||||
users, _, err := req.Page(int32(page)).PageSize(PageSize).Execute()
|
||||
if err != nil {
|
||||
log.WithError(err).Warning("failed to update users")
|
||||
return nil, err
|
||||
}
|
||||
log.WithField("page", page).WithField("count", len(users.Results)).Debug("fetched users")
|
||||
return users, nil
|
||||
}
|
||||
page := 1
|
||||
users := make([]api.User, 0)
|
||||
for {
|
||||
apiUsers, err := fetchUsersOffset(page)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("page", page).Warn("Failed to fetch user page")
|
||||
continue
|
||||
}
|
||||
users = append(users, apiUsers.Results...)
|
||||
if apiUsers.Pagination.Next > 0 {
|
||||
page += 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func FetchGroups(req api.ApiCoreGroupsListRequest) []api.Group {
|
||||
fetchGroupsOffset := func(page int) (*api.PaginatedGroupList, error) {
|
||||
groups, _, err := req.Page(int32(page)).PageSize(PageSize).Execute()
|
||||
if err != nil {
|
||||
log.WithError(err).Warning("failed to update groups")
|
||||
return nil, err
|
||||
}
|
||||
log.WithField("page", page).WithField("count", len(groups.Results)).Debug("fetched groups")
|
||||
return groups, nil
|
||||
}
|
||||
page := 1
|
||||
groups := make([]api.Group, 0)
|
||||
for {
|
||||
apiGroups, err := fetchGroupsOffset(page)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("page", page).Warn("Failed to fetch group page")
|
||||
continue
|
||||
}
|
||||
groups = append(groups, apiGroups.Results...)
|
||||
if apiGroups.Pagination.Next > 0 {
|
||||
page += 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return groups
|
||||
}
|
||||
@ -18,6 +18,7 @@ import (
|
||||
"goauthentik.io/internal/outpost/ak"
|
||||
"goauthentik.io/internal/outpost/proxyv2/application"
|
||||
"goauthentik.io/internal/outpost/proxyv2/metrics"
|
||||
"goauthentik.io/internal/utils"
|
||||
sentryutils "goauthentik.io/internal/utils/sentry"
|
||||
"goauthentik.io/internal/utils/web"
|
||||
)
|
||||
@ -129,11 +130,8 @@ func (ps *ProxyServer) ServeHTTP() {
|
||||
// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
|
||||
func (ps *ProxyServer) ServeHTTPS() {
|
||||
listenAddress := config.Get().Listen.HTTPS
|
||||
config := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
GetCertificate: ps.getCertificates,
|
||||
}
|
||||
tlsConfig := utils.GetTLSConfig()
|
||||
tlsConfig.GetCertificate = ps.getCertificates
|
||||
|
||||
ln, err := net.Listen("tcp", listenAddress)
|
||||
if err != nil {
|
||||
@ -143,7 +141,7 @@ func (ps *ProxyServer) ServeHTTPS() {
|
||||
proxyListener := &proxyproto.Listener{Listener: web.TCPKeepAliveListener{TCPListener: ln.(*net.TCPListener)}}
|
||||
defer proxyListener.Close()
|
||||
|
||||
tlsListener := tls.NewListener(proxyListener, config)
|
||||
tlsListener := tls.NewListener(proxyListener, tlsConfig)
|
||||
ps.log.WithField("listen", listenAddress).Info("Starting HTTPS server")
|
||||
ps.serve(tlsListener)
|
||||
ps.log.WithField("listen", listenAddress).Info("Stopping HTTPS server")
|
||||
|
||||
26
internal/utils/tls.go
Normal file
26
internal/utils/tls.go
Normal file
@ -0,0 +1,26 @@
|
||||
package utils
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func GetTLSConfig() *tls.Config {
|
||||
tlsConfig := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
// Insecure SWEET32 attack ciphers, TLS config uses a fallback
|
||||
insecureCiphersIds := []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
}
|
||||
defaultSecureCiphers := []uint16{}
|
||||
for _, cs := range tls.CipherSuites() {
|
||||
for _, icsId := range insecureCiphersIds {
|
||||
if cs.ID != icsId {
|
||||
defaultSecureCiphers = append(defaultSecureCiphers, cs.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
tlsConfig.CipherSuites = defaultSecureCiphers
|
||||
return tlsConfig
|
||||
}
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"github.com/pires/go-proxyproto"
|
||||
"goauthentik.io/internal/config"
|
||||
"goauthentik.io/internal/crypto"
|
||||
"goauthentik.io/internal/utils"
|
||||
"goauthentik.io/internal/utils/web"
|
||||
)
|
||||
|
||||
@ -35,11 +36,8 @@ func (ws *WebServer) GetCertificate() func(ch *tls.ClientHelloInfo) (*tls.Certif
|
||||
|
||||
// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
|
||||
func (ws *WebServer) listenTLS() {
|
||||
tlsConfig := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
GetCertificate: ws.GetCertificate(),
|
||||
}
|
||||
tlsConfig := utils.GetTLSConfig()
|
||||
tlsConfig.GetCertificate = ws.GetCertificate()
|
||||
|
||||
ln, err := net.Listen("tcp", config.Get().Listen.HTTPS)
|
||||
if err != nil {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Stage 1: Build
|
||||
FROM docker.io/golang:1.20.3-bullseye AS builder
|
||||
FROM docker.io/golang:1.20.4-bullseye AS builder
|
||||
|
||||
WORKDIR /go/src/goauthentik.io
|
||||
|
||||
|
||||
@ -69,7 +69,6 @@ if __name__ == "__main__":
|
||||
if not spec:
|
||||
continue
|
||||
mod = module_from_spec(spec)
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
spec.loader.exec_module(mod)
|
||||
|
||||
for name, sub in getmembers(mod, isclass):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
225
poetry.lock
generated
225
poetry.lock
generated
@ -262,14 +262,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "astroid"
|
||||
version = "2.15.2"
|
||||
version = "2.15.4"
|
||||
description = "An abstract syntax tree for Python with inference support."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7.2"
|
||||
files = [
|
||||
{file = "astroid-2.15.2-py3-none-any.whl", hash = "sha256:dea89d9f99f491c66ac9c04ebddf91e4acf8bd711722175fe6245c0725cc19bb"},
|
||||
{file = "astroid-2.15.2.tar.gz", hash = "sha256:6e61b85c891ec53b07471aec5878f4ac6446a41e590ede0f2ce095f39f7d49dd"},
|
||||
{file = "astroid-2.15.4-py3-none-any.whl", hash = "sha256:a1b8543ef9d36ea777194bc9b17f5f8678d2c56ee6a45b2c2f17eec96f242347"},
|
||||
{file = "astroid-2.15.4.tar.gz", hash = "sha256:c81e1c7fbac615037744d067a9bb5f9aeb655edf59b63ee8b59585475d6f80d8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -878,63 +878,63 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.2.3"
|
||||
version = "7.2.5"
|
||||
description = "Code coverage measurement for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "coverage-7.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-win32.whl", hash = "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91"},
|
||||
{file = "coverage-7.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-win32.whl", hash = "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925"},
|
||||
{file = "coverage-7.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-win32.whl", hash = "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1"},
|
||||
{file = "coverage-7.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-win32.whl", hash = "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22"},
|
||||
{file = "coverage-7.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-win32.whl", hash = "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30"},
|
||||
{file = "coverage-7.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a"},
|
||||
{file = "coverage-7.2.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0"},
|
||||
{file = "coverage-7.2.3.tar.gz", hash = "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:883123d0bbe1c136f76b56276074b0c79b5817dd4238097ffa64ac67257f4b6c"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2fbc2a127e857d2f8898aaabcc34c37771bf78a4d5e17d3e1f5c30cd0cbc62a"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f3671662dc4b422b15776cdca89c041a6349b4864a43aa2350b6b0b03bbcc7f"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780551e47d62095e088f251f5db428473c26db7829884323e56d9c0c3118791a"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:066b44897c493e0dcbc9e6a6d9f8bbb6607ef82367cf6810d387c09f0cd4fe9a"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9a4ee55174b04f6af539218f9f8083140f61a46eabcaa4234f3c2a452c4ed11"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:706ec567267c96717ab9363904d846ec009a48d5f832140b6ad08aad3791b1f5"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ae453f655640157d76209f42c62c64c4d4f2c7f97256d3567e3b439bd5c9b06c"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-win32.whl", hash = "sha256:f81c9b4bd8aa747d417407a7f6f0b1469a43b36a85748145e144ac4e8d303cb5"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:dc945064a8783b86fcce9a0a705abd7db2117d95e340df8a4333f00be5efb64c"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40cc0f91c6cde033da493227797be2826cbf8f388eaa36a0271a97a332bfd7ce"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a66e055254a26c82aead7ff420d9fa8dc2da10c82679ea850d8feebf11074d88"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c10fbc8a64aa0f3ed136b0b086b6b577bc64d67d5581acd7cc129af52654384e"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a22cbb5ede6fade0482111fa7f01115ff04039795d7092ed0db43522431b4f2"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292300f76440651529b8ceec283a9370532f4ecba9ad67d120617021bb5ef139"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7ff8f3fb38233035028dbc93715551d81eadc110199e14bbbfa01c5c4a43f8d8"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a08c7401d0b24e8c2982f4e307124b671c6736d40d1c39e09d7a8687bddf83ed"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef9659d1cda9ce9ac9585c045aaa1e59223b143f2407db0eaee0b61a4f266fb6"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-win32.whl", hash = "sha256:30dcaf05adfa69c2a7b9f7dfd9f60bc8e36b282d7ed25c308ef9e114de7fc23b"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:97072cc90f1009386c8a5b7de9d4fc1a9f91ba5ef2146c55c1f005e7b5c5e068"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bebea5f5ed41f618797ce3ffb4606c64a5de92e9c3f26d26c2e0aae292f015c1"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828189fcdda99aae0d6bf718ea766b2e715eabc1868670a0a07bf8404bf58c33"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e8a95f243d01ba572341c52f89f3acb98a3b6d1d5d830efba86033dd3687ade"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8834e5f17d89e05697c3c043d3e58a8b19682bf365048837383abfe39adaed5"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d1f25ee9de21a39b3a8516f2c5feb8de248f17da7eead089c2e04aa097936b47"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1637253b11a18f453e34013c665d8bf15904c9e3c44fbda34c643fbdc9d452cd"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8e575a59315a91ccd00c7757127f6b2488c2f914096077c745c2f1ba5b8c0969"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-win32.whl", hash = "sha256:509ecd8334c380000d259dc66feb191dd0a93b21f2453faa75f7f9cdcefc0718"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-win_amd64.whl", hash = "sha256:12580845917b1e59f8a1c2ffa6af6d0908cb39220f3019e36c110c943dc875b0"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5016e331b75310610c2cf955d9f58a9749943ed5f7b8cfc0bb89c6134ab0a84"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:373ea34dca98f2fdb3e5cb33d83b6d801007a8074f992b80311fc589d3e6b790"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a063aad9f7b4c9f9da7b2550eae0a582ffc7623dca1c925e50c3fbde7a579771"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38c0a497a000d50491055805313ed83ddba069353d102ece8aef5d11b5faf045"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b3b05e22a77bb0ae1a3125126a4e08535961c946b62f30985535ed40e26614"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0342a28617e63ad15d96dca0f7ae9479a37b7d8a295f749c14f3436ea59fdcb3"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf97ed82ca986e5c637ea286ba2793c85325b30f869bf64d3009ccc1a31ae3fd"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c2c41c1b1866b670573657d584de413df701f482574bad7e28214a2362cb1fd1"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-win32.whl", hash = "sha256:10b15394c13544fce02382360cab54e51a9e0fd1bd61ae9ce012c0d1e103c813"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-win_amd64.whl", hash = "sha256:a0b273fe6dc655b110e8dc89b8ec7f1a778d78c9fd9b4bda7c384c8906072212"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c587f52c81211d4530fa6857884d37f514bcf9453bdeee0ff93eaaf906a5c1b"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4436cc9ba5414c2c998eaedee5343f49c02ca93b21769c5fdfa4f9d799e84200"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6599bf92f33ab041e36e06d25890afbdf12078aacfe1f1d08c713906e49a3fe5"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:857abe2fa6a4973f8663e039ead8d22215d31db613ace76e4a98f52ec919068e"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f5cab2d7f0c12f8187a376cc6582c477d2df91d63f75341307fcdcb5d60303"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aa387bd7489f3e1787ff82068b295bcaafbf6f79c3dad3cbc82ef88ce3f48ad3"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:156192e5fd3dbbcb11cd777cc469cf010a294f4c736a2b2c891c77618cb1379a"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd3b4b8175c1db502adf209d06136c000df4d245105c8839e9d0be71c94aefe1"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-win32.whl", hash = "sha256:ddc5a54edb653e9e215f75de377354e2455376f416c4378e1d43b08ec50acc31"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-win_amd64.whl", hash = "sha256:338aa9d9883aaaad53695cb14ccdeb36d4060485bb9388446330bef9c361c252"},
|
||||
{file = "coverage-7.2.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:8877d9b437b35a85c18e3c6499b23674684bf690f5d96c1006a1ef61f9fdf0f3"},
|
||||
{file = "coverage-7.2.5.tar.gz", hash = "sha256:f99ef080288f09ffc687423b8d60978cf3a465d3f404a18d1a05474bd8575a47"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@ -1083,14 +1083,14 @@ bcrypt = ["bcrypt"]
|
||||
|
||||
[[package]]
|
||||
name = "django-filter"
|
||||
version = "23.1"
|
||||
version = "23.2"
|
||||
description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "django-filter-23.1.tar.gz", hash = "sha256:dee5dcf2cea4d7f767e271b6d01f767fce7500676d5e5dc58dac8154000b87df"},
|
||||
{file = "django_filter-23.1-py3-none-any.whl", hash = "sha256:e3c52ad83c32fb5882125105efb5fea2a1d6a85e7dc64b04ef52edbf14451b6c"},
|
||||
{file = "django-filter-23.2.tar.gz", hash = "sha256:2fe15f78108475eda525692813205fa6f9e8c1caf1ae65daa5862d403c6dbf00"},
|
||||
{file = "django_filter-23.2-py3-none-any.whl", hash = "sha256:d12d8e0fc6d3eb26641e553e5d53b191eb8cec611427d4bdce0becb1f7c172b5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1146,14 +1146,14 @@ qrcode = ["qrcode"]
|
||||
|
||||
[[package]]
|
||||
name = "django-prometheus"
|
||||
version = "2.2.0"
|
||||
version = "2.3.1"
|
||||
description = "Django middlewares to monitor your application with Prometheus.io."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "django-prometheus-2.2.0.tar.gz", hash = "sha256:240378a1307c408bd5fc85614a3a57f1ce633d4a222c9e291e2bbf325173b801"},
|
||||
{file = "django_prometheus-2.2.0-py2.py3-none-any.whl", hash = "sha256:e6616770d8820b8834762764bf1b76ec08e1b98e72a6f359d488a2e15fe3537c"},
|
||||
{file = "django-prometheus-2.3.1.tar.gz", hash = "sha256:f9c8b6c780c9419ea01043c63a437d79db2c33353451347894408184ad9c3e1e"},
|
||||
{file = "django_prometheus-2.3.1-py2.py3-none-any.whl", hash = "sha256:cf9b26f7ba2e4568f08f8f91480a2882023f5908579681bcf06a4d2465f12168"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1313,14 +1313,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "duo-client"
|
||||
version = "4.7.1"
|
||||
version = "5.0.1"
|
||||
description = "Reference client for Duo Security APIs"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "duo_client-4.7.1-py2.py3-none-any.whl", hash = "sha256:35f1ea54111fa1304127e4d169bf553aee826db62b33c972c45f65a02756c820"},
|
||||
{file = "duo_client-4.7.1.tar.gz", hash = "sha256:f859b5d5b434daf46674c74fd7c6f2d34002df3c54e519e64a284aeb491226ef"},
|
||||
{file = "duo_client-5.0.1-py2.py3-none-any.whl", hash = "sha256:8159b93a516979ae18e985bbca46ba3bf8ec27cd7ff6db464b37fa667d49c9b5"},
|
||||
{file = "duo_client-5.0.1.tar.gz", hash = "sha256:a13c47b0cb91e3a0d33b2f99271a0a6c2de17f92dd0ad4c649a366a0533db0c7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1676,14 +1676,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "6.5.0"
|
||||
version = "6.6.0"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "importlib_metadata-6.5.0-py3-none-any.whl", hash = "sha256:03ba783c3a2c69d751b109fc0c94a62c51f581b3d6acf8ed1331b6d5729321ff"},
|
||||
{file = "importlib_metadata-6.5.0.tar.gz", hash = "sha256:7a8bdf1bc3a726297f5cfbc999e6e7ff6b4fa41b26bba4afc580448624460045"},
|
||||
{file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"},
|
||||
{file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -2619,18 +2619,18 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pylint"
|
||||
version = "2.17.2"
|
||||
version = "2.17.3"
|
||||
description = "python code static checker"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7.2"
|
||||
files = [
|
||||
{file = "pylint-2.17.2-py3-none-any.whl", hash = "sha256:001cc91366a7df2970941d7e6bbefcbf98694e00102c1f121c531a814ddc2ea8"},
|
||||
{file = "pylint-2.17.2.tar.gz", hash = "sha256:1b647da5249e7c279118f657ca28b6aaebb299f86bf92affc632acf199f7adbb"},
|
||||
{file = "pylint-2.17.3-py3-none-any.whl", hash = "sha256:a6cbb4c6e96eab4a3c7de7c6383c512478f58f88d95764507d84c899d656a89a"},
|
||||
{file = "pylint-2.17.3.tar.gz", hash = "sha256:761907349e699f8afdcd56c4fe02f3021ab5b3a0fc26d19a9bfdc66c7d0d5cd5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
astroid = ">=2.15.2,<=2.17.0-dev0"
|
||||
astroid = ">=2.15.4,<=2.17.0-dev0"
|
||||
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
|
||||
dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""}
|
||||
isort = ">=4.2.5,<6"
|
||||
@ -2826,14 +2826,14 @@ testing = ["Django", "django-configurations (>=2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-github-actions-annotate-failures"
|
||||
version = "0.1.8"
|
||||
version = "0.2.0"
|
||||
description = "pytest plugin to annotate failed tests with a workflow command for GitHub Actions"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-github-actions-annotate-failures-0.1.8.tar.gz", hash = "sha256:2d6e6cb5f8d0aae4a27a20cc4e20fabd3199a121c57f44bc48fe28e372e0be23"},
|
||||
{file = "pytest_github_actions_annotate_failures-0.1.8-py2.py3-none-any.whl", hash = "sha256:6a882ff21672fa79deae8d917eb965a6bde2b25191e7632e1adfc23ffac008ab"},
|
||||
{file = "pytest-github-actions-annotate-failures-0.2.0.tar.gz", hash = "sha256:844ab626d389496e44f960b42f0a72cce29ae06d363426d17ea9ae1b4bef2288"},
|
||||
{file = "pytest_github_actions_annotate_failures-0.2.0-py3-none-any.whl", hash = "sha256:8bcef65fed503faaa0524b59cfeccc8995130972dd7b008d64193cc41b9cde85"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3086,29 +3086,29 @@ pyasn1 = ">=0.1.3"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.262"
|
||||
version = "0.0.264"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.0.262-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:c26c1abd420d041592d05d63aee8c6a18feb24aed4deb6e91129e9f2c7b4914a"},
|
||||
{file = "ruff-0.0.262-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:b379e9765afa679316e52288a942df085e590862f8945088936a7bce3116d8f3"},
|
||||
{file = "ruff-0.0.262-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b7e0ca6821aafbd2b059df3119fcd5881250721ca8e825789fd2c471f7c59be"},
|
||||
{file = "ruff-0.0.262-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cca35e2aeddff72bb4379a1dabc134e0c0d25ebc754a2cb733a1f8d4dbbb5e0"},
|
||||
{file = "ruff-0.0.262-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15bf5533ce169aebbafa00017987f673e879f60a625d932b464b8cdaf32a4fce"},
|
||||
{file = "ruff-0.0.262-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3909e249d984c4517194005a1c30eaa0c3a6d906c789d9fc0c9c7e007fb3e759"},
|
||||
{file = "ruff-0.0.262-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e2813013a19b3e147e840bdb2e42db5825b53b47364e58e7b467c5fa47ffda2"},
|
||||
{file = "ruff-0.0.262-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d25a94996b2037e566c2a801c8b324c0a826194d5d4d90ad7c1ccb8cf06521fa"},
|
||||
{file = "ruff-0.0.262-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ca04348372efc59f6ee808d903d35e0d352cf2c78e487757cd48b65104b83e"},
|
||||
{file = "ruff-0.0.262-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:24f989363e9bb5d0283490298102a5218682e49ebf300e445d69e24bee03ac83"},
|
||||
{file = "ruff-0.0.262-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3c24e678e43ca4b67e29cc9a7a54eea05f31a5898cbf17bfec47b68f08d32a60"},
|
||||
{file = "ruff-0.0.262-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0baff3c9a22227358ea109c165efe62dbdd0f2b9fd5256567dda8682b444fe23"},
|
||||
{file = "ruff-0.0.262-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:083bac6e238d8b7d5ac3618666ea63b7ac661cf94c5da160070a58e190082831"},
|
||||
{file = "ruff-0.0.262-py3-none-win32.whl", hash = "sha256:15bbfa2d15c137717627e0d56b0e535ae297b734551e34e03fcc25d7642cf43a"},
|
||||
{file = "ruff-0.0.262-py3-none-win_amd64.whl", hash = "sha256:973ac29193f718349cf5746b7d86dfeaf7d40e9651ed97790a9b9327305888b9"},
|
||||
{file = "ruff-0.0.262-py3-none-win_arm64.whl", hash = "sha256:f102904ebe395acd2a181d295b98120acd7a63f732b691672977fc688674f4af"},
|
||||
{file = "ruff-0.0.262.tar.gz", hash = "sha256:faea54231c265f5349975ba6f3d855b71881a01f391b2000c47740390c6d5f68"},
|
||||
{file = "ruff-0.0.264-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:ec2fa192c035b8b68cc2b91049c561cd69543e2b8c4d157d9aa7727320bedcca"},
|
||||
{file = "ruff-0.0.264-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:d97ba8db0fb601ffe9ee996ebb97c698e427a2fd4514fefbe7b803111354f783"},
|
||||
{file = "ruff-0.0.264-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4564e0f245eb515c6ed63988c21e9c40bcfd485cd1ec63bdd790f9a81d301f15"},
|
||||
{file = "ruff-0.0.264-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:323ae6c1702b26c96d0fbf939c5959c37e79021f86b70f63634df918bc77f36e"},
|
||||
{file = "ruff-0.0.264-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18a29ed37bf8cfe6dce8a2db56c313a64c0804095108753621f3c3321e0c9c5f"},
|
||||
{file = "ruff-0.0.264-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d628de91e2be7a83128526636097d2dd890669a06143f826f6c591d79aeefbc4"},
|
||||
{file = "ruff-0.0.264-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c6eb4f979b661a2dd850d9ac803842bb7b66d4926de84f09c787af82590f73"},
|
||||
{file = "ruff-0.0.264-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04ec5d75e4bca754cedd20d53e2ba4920d6259e7579abfb2e8e30c3c80e41b17"},
|
||||
{file = "ruff-0.0.264-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71fd865ebacc1083259b3fb7e3eb45235a86e62e21830b8a6b067be0ec54aa2e"},
|
||||
{file = "ruff-0.0.264-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cd4f60ffc3eb15802c554a9c8581bf2117c4d3d06fbc57e0ba58f04cb1aaa47f"},
|
||||
{file = "ruff-0.0.264-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:05ee163a046fc593d150179d23f4af447fb82f3e59cd34e031ea0868c65bb8e8"},
|
||||
{file = "ruff-0.0.264-py3-none-musllinux_1_2_i686.whl", hash = "sha256:484e395d1984ab9e1e66bd42e7a5192decfee86998d07d36ee50b2fadccc8734"},
|
||||
{file = "ruff-0.0.264-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:67326fdc9ac0a1b13e229c6e24e8d115863c52cd710faaaaa588851535281d6c"},
|
||||
{file = "ruff-0.0.264-py3-none-win32.whl", hash = "sha256:5a8658ebcc37d62f72840cbdf564171c1a2b6831db482b4d917962541a2f4a44"},
|
||||
{file = "ruff-0.0.264-py3-none-win_amd64.whl", hash = "sha256:068a82a29d80848a56e3d9d4308e6e0ca8b2ecdaf5ac342a292545a59b7f2c21"},
|
||||
{file = "ruff-0.0.264-py3-none-win_arm64.whl", hash = "sha256:3e2c38449548e122f2612843a7c04e22b4fd491656955c57b8cb05df11639ad6"},
|
||||
{file = "ruff-0.0.264.tar.gz", hash = "sha256:8fcd4b693ca1374eb7a5796581c90689f884f98f388740d94f0702fd30f8f78f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3131,14 +3131,14 @@ urllib3 = {version = ">=1.26,<2.0", extras = ["socks"]}
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "1.20.0"
|
||||
version = "1.21.1"
|
||||
description = "Python client for Sentry (https://sentry.io)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "sentry-sdk-1.20.0.tar.gz", hash = "sha256:a3410381ae769a436c0852cce140a5e5e49f566a07fb7c2ab445af1302f6ad89"},
|
||||
{file = "sentry_sdk-1.20.0-py2.py3-none-any.whl", hash = "sha256:0ad6bbbe78057b8031a07de7aca6d2a83234e51adc4d436eaf8d8c697184db71"},
|
||||
{file = "sentry-sdk-1.21.1.tar.gz", hash = "sha256:99c15556a23621be9f18c2955f7ce63321713bf1c0ad396b27b61399bac5f458"},
|
||||
{file = "sentry_sdk-1.21.1-py2.py3-none-any.whl", hash = "sha256:092888f3abf7a2ea78f0bfcefc3e0465caee2b6f0efb26f538ccc60f95dca179"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3263,16 +3263,21 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
description = "A non-validating SQL parser."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"},
|
||||
{file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"},
|
||||
{file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"},
|
||||
{file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["build", "flake8"]
|
||||
doc = ["sphinx"]
|
||||
test = ["pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "stevedore"
|
||||
version = "4.1.1"
|
||||
@ -3408,14 +3413,14 @@ wsproto = ">=0.14"
|
||||
|
||||
[[package]]
|
||||
name = "twilio"
|
||||
version = "8.1.0"
|
||||
version = "8.2.0"
|
||||
description = "Twilio API client and TwiML generator"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "twilio-8.1.0-py2.py3-none-any.whl", hash = "sha256:19be48f21e799b9dd10e2e0a5633962438e04842864e806409f4f2dbe446a868"},
|
||||
{file = "twilio-8.1.0.tar.gz", hash = "sha256:a31863119655cd3643f788099f6ea3fe74eea59ce3f65600f9a4931301311c08"},
|
||||
{file = "twilio-8.2.0-py2.py3-none-any.whl", hash = "sha256:23eceaec183995fc827e3bfad229cca6e1944bfd9604e57e2712e625b6e01223"},
|
||||
{file = "twilio-8.2.0.tar.gz", hash = "sha256:0c19eb6a5b84dbcd15658e23a142df026297236e4d72ad9304fd95e7dbff2662"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3593,14 +3598,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.21.1"
|
||||
version = "0.22.0"
|
||||
description = "The lightning-fast ASGI server."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "uvicorn-0.21.1-py3-none-any.whl", hash = "sha256:e47cac98a6da10cd41e6fd036d472c6f58ede6c5dbee3dbee3ef7a100ed97742"},
|
||||
{file = "uvicorn-0.21.1.tar.gz", hash = "sha256:0fac9cb342ba099e0d582966005f3fdba5b0290579fed4a6266dc702ca7bb032"},
|
||||
{file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"},
|
||||
{file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -3759,22 +3764,22 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "webauthn"
|
||||
version = "1.8.0"
|
||||
version = "1.8.1"
|
||||
description = "Pythonic WebAuthn"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "webauthn-1.8.0-py3-none-any.whl", hash = "sha256:1b87d55acd86234475591e2f60163232ba98d91685f235803f258fc891ebb6c0"},
|
||||
{file = "webauthn-1.8.0.tar.gz", hash = "sha256:0c981eb2575d66fa0a6586c2bfb24d9812dff8e158009a9030bc867717727709"},
|
||||
{file = "webauthn-1.8.1-py3-none-any.whl", hash = "sha256:53914b8529df3e70bec0e41438eea8d99442ad3e54e32b86ce1ba83513bcf30e"},
|
||||
{file = "webauthn-1.8.1.tar.gz", hash = "sha256:caca3cc62045923c542819169c22122bdb81efa0246c9d5208d073942d806bf3"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asn1crypto = ">=1.4.0"
|
||||
cbor2 = ">=5.4.2.post1"
|
||||
cryptography = ">=36.0.1"
|
||||
cryptography = ">=39.0.1"
|
||||
pydantic = ">=1.9.0"
|
||||
pyOpenSSL = ">=22.0.0"
|
||||
pyOpenSSL = ">=23.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "websocket-client"
|
||||
|
||||
@ -7,7 +7,7 @@ ENV NODE_ENV=production
|
||||
RUN cd /static && npm ci && npm run build-proxy
|
||||
|
||||
# Stage 2: Build
|
||||
FROM docker.io/golang:1.20.3-bullseye AS builder
|
||||
FROM docker.io/golang:1.20.4-bullseye AS builder
|
||||
|
||||
WORKDIR /go/src/goauthentik.io
|
||||
|
||||
|
||||
@ -5,6 +5,9 @@ strictParameterNoneValue = true
|
||||
strictDictionaryInference = true
|
||||
strictListInference = true
|
||||
reportOptionalMemberAccess = false
|
||||
reportOptionalContextManager = false
|
||||
# rest_framework's serializer's `validated_data` is typed as optional None
|
||||
reportOptionalSubscript = false
|
||||
# Sadly pyright still has issues with enums, and they fall under general type issues
|
||||
# so we have to disable those for now
|
||||
reportGeneralTypeIssues = false
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Stage 1: Build
|
||||
FROM docker.io/golang:1.20.3-bullseye AS builder
|
||||
FROM docker.io/golang:1.20.4-bullseye AS builder
|
||||
|
||||
WORKDIR /go/src/goauthentik.io
|
||||
|
||||
|
||||
46
schema.yml
46
schema.yml
@ -6806,11 +6806,11 @@ paths:
|
||||
- all
|
||||
- any
|
||||
description: |-
|
||||
* `all` - ALL, all policies must pass
|
||||
* `any` - ANY, any policy must pass
|
||||
* `all` - all, all policies must pass
|
||||
* `any` - any, any policy must pass
|
||||
|
||||
* `all` - ALL, all policies must pass
|
||||
* `any` - ANY, any policy must pass
|
||||
* `all` - all, all policies must pass
|
||||
* `any` - any, any policy must pass
|
||||
- in: query
|
||||
name: re_evaluate_policies
|
||||
schema:
|
||||
@ -17552,11 +17552,11 @@ paths:
|
||||
- all
|
||||
- any
|
||||
description: |-
|
||||
* `all` - ALL, all policies must pass
|
||||
* `any` - ANY, any policy must pass
|
||||
* `all` - all, all policies must pass
|
||||
* `any` - any, any policy must pass
|
||||
|
||||
* `all` - ALL, all policies must pass
|
||||
* `any` - ANY, any policy must pass
|
||||
* `all` - all, all policies must pass
|
||||
* `any` - any, any policy must pass
|
||||
- in: query
|
||||
name: profile_url
|
||||
schema:
|
||||
@ -17921,11 +17921,11 @@ paths:
|
||||
- all
|
||||
- any
|
||||
description: |-
|
||||
* `all` - ALL, all policies must pass
|
||||
* `any` - ANY, any policy must pass
|
||||
* `all` - all, all policies must pass
|
||||
* `any` - any, any policy must pass
|
||||
|
||||
* `all` - ALL, all policies must pass
|
||||
* `any` - ANY, any policy must pass
|
||||
* `all` - all, all policies must pass
|
||||
* `any` - any, any policy must pass
|
||||
- name: search
|
||||
required: false
|
||||
in: query
|
||||
@ -18358,11 +18358,11 @@ paths:
|
||||
- all
|
||||
- any
|
||||
description: |-
|
||||
* `all` - ALL, all policies must pass
|
||||
* `any` - ANY, any policy must pass
|
||||
* `all` - all, all policies must pass
|
||||
* `any` - any, any policy must pass
|
||||
|
||||
* `all` - ALL, all policies must pass
|
||||
* `any` - ANY, any policy must pass
|
||||
* `all` - all, all policies must pass
|
||||
* `any` - any, any policy must pass
|
||||
- in: query
|
||||
name: pre_authentication_flow
|
||||
schema:
|
||||
@ -37980,8 +37980,8 @@ components:
|
||||
- any
|
||||
type: string
|
||||
description: |-
|
||||
* `all` - ALL, all policies must pass
|
||||
* `any` - ANY, any policy must pass
|
||||
* `all` - all, all policies must pass
|
||||
* `any` - any, any policy must pass
|
||||
PolicyRequest:
|
||||
type: object
|
||||
description: Policy Serializer
|
||||
@ -38441,12 +38441,13 @@ components:
|
||||
- facebook
|
||||
- github
|
||||
- google
|
||||
- mailcow
|
||||
- openidconnect
|
||||
- okta
|
||||
- patreon
|
||||
- reddit
|
||||
- twitter
|
||||
- mailcow
|
||||
- twitch
|
||||
- twitter
|
||||
type: string
|
||||
description: |-
|
||||
* `apple` - Apple
|
||||
@ -38455,12 +38456,13 @@ components:
|
||||
* `facebook` - Facebook
|
||||
* `github` - GitHub
|
||||
* `google` - Google
|
||||
* `mailcow` - Mailcow
|
||||
* `openidconnect` - OpenID Connect
|
||||
* `okta` - Okta
|
||||
* `patreon` - Patreon
|
||||
* `reddit` - reddit
|
||||
* `twitter` - Twitter
|
||||
* `mailcow` - Mailcow
|
||||
* `twitch` - Twitch
|
||||
* `twitter` - Twitter
|
||||
ProxyMode:
|
||||
enum:
|
||||
- proxy
|
||||
|
||||
@ -6,6 +6,7 @@ from authentik.lib.generators import generate_id
|
||||
with open("local.env.yml", "w", encoding="utf-8") as _config:
|
||||
safe_dump(
|
||||
{
|
||||
"debug": True,
|
||||
"log_level": "debug",
|
||||
"secret_key": generate_id(),
|
||||
"postgresql": {
|
||||
|
||||
@ -2,13 +2,13 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
chrome:
|
||||
image: selenium/standalone-chrome:110.0
|
||||
image: docker.io/selenium/standalone-chrome:110.0
|
||||
volumes:
|
||||
- /dev/shm:/dev/shm
|
||||
network_mode: host
|
||||
restart: always
|
||||
mailhog:
|
||||
image: mailhog/mailhog:v1.0.1
|
||||
mailpit:
|
||||
image: docker.io/axllent/mailpit:v1.6.5
|
||||
ports:
|
||||
- 1025:1025
|
||||
- 8025:8025
|
||||
|
||||
@ -78,13 +78,13 @@ class TestFlowsEnroll(SeleniumTestCase):
|
||||
# Wait for the success message so we know the email is sent
|
||||
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, ".pf-c-form p")))
|
||||
|
||||
# Open Mailhog
|
||||
# Open Mailpit
|
||||
self.driver.get("http://localhost:8025")
|
||||
|
||||
# Click on first message
|
||||
self.wait.until(ec.presence_of_element_located((By.CLASS_NAME, "msglist-message")))
|
||||
self.driver.find_element(By.CLASS_NAME, "msglist-message").click()
|
||||
self.driver.switch_to.frame(self.driver.find_element(By.CLASS_NAME, "tab-pane"))
|
||||
self.wait.until(ec.presence_of_element_located((By.CLASS_NAME, "message")))
|
||||
self.driver.find_element(By.CLASS_NAME, "message").click()
|
||||
self.driver.switch_to.frame(self.driver.find_element(By.ID, "preview-html"))
|
||||
self.driver.find_element(By.ID, "confirm").click()
|
||||
self.driver.close()
|
||||
self.driver.switch_to.window(self.driver.window_handles[0])
|
||||
|
||||
@ -71,13 +71,13 @@ class TestFlowsRecovery(SeleniumTestCase):
|
||||
# Wait for the success message so we know the email is sent
|
||||
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, ".pf-c-form p")))
|
||||
|
||||
# Open Mailhog
|
||||
# Open mailpit
|
||||
self.driver.get("http://localhost:8025")
|
||||
|
||||
# Click on first message
|
||||
self.wait.until(ec.presence_of_element_located((By.CLASS_NAME, "msglist-message")))
|
||||
self.driver.find_element(By.CLASS_NAME, "msglist-message").click()
|
||||
self.driver.switch_to.frame(self.driver.find_element(By.CLASS_NAME, "tab-pane"))
|
||||
self.wait.until(ec.presence_of_element_located((By.CLASS_NAME, "message")))
|
||||
self.driver.find_element(By.CLASS_NAME, "message").click()
|
||||
self.driver.switch_to.frame(self.driver.find_element(By.ID, "preview-html"))
|
||||
self.driver.find_element(By.ID, "confirm").click()
|
||||
self.driver.close()
|
||||
self.driver.switch_to.window(self.driver.window_handles[0])
|
||||
|
||||
@ -62,7 +62,6 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
@ -119,7 +118,6 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
@ -194,7 +192,6 @@ class TestProviderOAuth2Github(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_denied(self):
|
||||
|
||||
@ -67,7 +67,6 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
@ -116,7 +115,6 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
@ -178,7 +176,6 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
@ -250,7 +247,6 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
@ -330,7 +326,6 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-oauth2.yaml",
|
||||
|
||||
@ -63,7 +63,6 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@ -109,7 +108,6 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@ -165,7 +163,6 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@apply_blueprint("system/providers-oauth2.yaml")
|
||||
@ -235,7 +232,6 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_denied(self):
|
||||
|
||||
@ -63,7 +63,6 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@ -109,7 +108,6 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@ -160,7 +158,6 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@apply_blueprint("system/providers-oauth2.yaml")
|
||||
@ -226,7 +223,6 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
def test_authorization_denied(self):
|
||||
|
||||
@ -60,7 +60,6 @@ class TestProviderProxy(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
@ -126,7 +125,6 @@ class TestProviderProxy(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
@ -203,7 +201,6 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@reconcile_app("authentik_crypto")
|
||||
@ -211,7 +208,7 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
||||
"""Test proxy connectivity over websocket"""
|
||||
outpost_connection_discovery() # pylint: disable=no-value-for-parameter
|
||||
proxy: ProxyProvider = ProxyProvider.objects.create(
|
||||
name="proxy_provider",
|
||||
name=generate_id(),
|
||||
authorization_flow=Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
),
|
||||
@ -225,7 +222,7 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
||||
Application.objects.create(name="proxy", slug="proxy", provider=proxy)
|
||||
service_connection = DockerServiceConnection.objects.get(local=True)
|
||||
outpost: Outpost = Outpost.objects.create(
|
||||
name="proxy_outpost",
|
||||
name=generate_id(),
|
||||
type=OutpostType.PROXY,
|
||||
service_connection=service_connection,
|
||||
_config=asdict(OutpostConfig(authentik_host=self.live_server_url, log_level="debug")),
|
||||
@ -244,7 +241,7 @@ class TestProviderProxyConnect(ChannelsLiveServerTestCase):
|
||||
sleep(0.5)
|
||||
|
||||
state = outpost.state
|
||||
self.assertTrue(len(state) >= 1)
|
||||
self.assertGreaterEqual(len(state), 1)
|
||||
|
||||
# Make sure to delete the outpost to remove the container
|
||||
outpost.delete()
|
||||
|
||||
@ -62,7 +62,6 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
@ -132,7 +131,6 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-saml.yaml",
|
||||
@ -216,7 +214,6 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"system/providers-saml.yaml",
|
||||
@ -299,7 +296,6 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
@ -374,7 +370,6 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
@ -423,7 +418,6 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
|
||||
@ -146,10 +146,6 @@ class TestSourceOAuth2(SeleniumTestCase):
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-source-authentication.yaml",
|
||||
"default/flow-default-source-enrollment.yaml",
|
||||
@ -199,14 +195,6 @@ class TestSourceOAuth2(SeleniumTestCase):
|
||||
self.assert_user(User(username="foo", name="admin", email="admin@example.com"))
|
||||
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
"default/flow-default-invalidation-flow.yaml",
|
||||
)
|
||||
@apply_blueprint(
|
||||
"default/flow-default-provider-authorization-explicit-consent.yaml",
|
||||
"default/flow-default-provider-authorization-implicit-consent.yaml",
|
||||
)
|
||||
def test_oauth_enroll_auth(self):
|
||||
"""test OAuth Source With With OIDC (enroll and authenticate again)"""
|
||||
self.test_oauth_enroll()
|
||||
|
||||
@ -3,6 +3,7 @@ import json
|
||||
import os
|
||||
from functools import lru_cache, wraps
|
||||
from os import environ
|
||||
from sys import stderr
|
||||
from time import sleep
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
@ -28,6 +29,7 @@ from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
|
||||
RETRIES = int(environ.get("RETRIES", "3"))
|
||||
IS_CI = "CI" in environ
|
||||
|
||||
|
||||
def get_docker_tag() -> str:
|
||||
@ -49,6 +51,8 @@ class SeleniumTestCase(StaticLiveServerTestCase):
|
||||
user: User
|
||||
|
||||
def setUp(self):
|
||||
if IS_CI:
|
||||
print("::group::authentik Logs", file=stderr)
|
||||
super().setUp()
|
||||
# pylint: disable=invalid-name
|
||||
self.maxDiff = None
|
||||
@ -91,8 +95,12 @@ class SeleniumTestCase(StaticLiveServerTestCase):
|
||||
def output_container_logs(self, container: Optional[Container] = None):
|
||||
"""Output the container logs to our STDOUT"""
|
||||
_container = container or self.container
|
||||
if IS_CI:
|
||||
print(f"::group::Container logs - {_container.image.tags[0]}")
|
||||
for log in _container.logs().decode().split("\n"):
|
||||
self.logger.info(log, source="container", container=_container.image.tags[0])
|
||||
print(log)
|
||||
if IS_CI:
|
||||
print("::endgroup::")
|
||||
|
||||
def get_container_specs(self) -> Optional[dict[str, Any]]:
|
||||
"""Optionally get container specs which will launched on setup, wait for the container to
|
||||
@ -118,15 +126,19 @@ class SeleniumTestCase(StaticLiveServerTestCase):
|
||||
raise ValueError(f"Webdriver failed after {RETRIES}.")
|
||||
|
||||
def tearDown(self):
|
||||
self.logger.debug("--------browser logs")
|
||||
super().tearDown()
|
||||
if IS_CI:
|
||||
print("::endgroup::", file=stderr)
|
||||
if IS_CI:
|
||||
print("::group::Browser logs")
|
||||
for line in self.driver.get_log("browser"):
|
||||
self.logger.debug(line["message"], source=line["source"], level=line["level"])
|
||||
self.logger.debug("--------end browser logs")
|
||||
print(line["message"])
|
||||
if IS_CI:
|
||||
print("::endgroup::")
|
||||
if self.container:
|
||||
self.output_container_logs()
|
||||
self.container.kill()
|
||||
self.driver.quit()
|
||||
super().tearDown()
|
||||
|
||||
def wait_for_url(self, desired_url):
|
||||
"""Wait until URL is `desired_url`."""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user