Compare commits
7 Commits
dev/devcon
...
stages/ema
Author | SHA1 | Date | |
---|---|---|---|
f05f440c78 | |||
7df0e88b9d | |||
53f827b54f | |||
395dc08f05 | |||
080f2ab5e7 | |||
2a2e159a0d | |||
564b2874a9 |
@ -1,7 +0,0 @@
|
||||
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
|
@ -1,34 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
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:
|
18
.vscode/tasks.json
vendored
18
.vscode/tasks.json
vendored
@ -3,23 +3,26 @@
|
||||
"tasks": [
|
||||
{
|
||||
"label": "authentik[core]: format & test",
|
||||
"command": "make",
|
||||
"command": "poetry",
|
||||
"args": [
|
||||
"run",
|
||||
"make"
|
||||
],
|
||||
"group": "build",
|
||||
},
|
||||
{
|
||||
"label": "authentik[core]: run",
|
||||
"command": "ak",
|
||||
"command": "poetry",
|
||||
"args": [
|
||||
"server",
|
||||
"run",
|
||||
"make",
|
||||
"run",
|
||||
],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"panel": "dedicated",
|
||||
"group": "running"
|
||||
},
|
||||
"runOptions": {
|
||||
"runOn": "folderOpen"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "authentik[web]: format",
|
||||
@ -36,9 +39,6 @@
|
||||
"panel": "dedicated",
|
||||
"group": "running"
|
||||
},
|
||||
"runOptions": {
|
||||
"runOn": "folderOpen"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "authentik: install",
|
||||
|
42
Dockerfile
42
Dockerfile
@ -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 /app-root
|
||||
WORKDIR /
|
||||
|
||||
ARG GIT_BUILD_HASH
|
||||
ENV GIT_BUILD_HASH=$GIT_BUILD_HASH
|
||||
|
||||
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
|
||||
COPY --from=poetry-locker /work/requirements.txt /
|
||||
COPY --from=poetry-locker /work/requirements-dev.txt /
|
||||
COPY --from=geoip /usr/share/GeoIP /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 /app-root/requirements.txt && \
|
||||
pip install --no-cache-dir -r /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 /app-root authentik && \
|
||||
mkdir -p /app-root /app-root/.ssh && \
|
||||
adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \
|
||||
mkdir -p /certs /media /blueprints && \
|
||||
chown -R authentik:authentik /certs /media /app-root/
|
||||
mkdir -p /authentik/.ssh && \
|
||||
chown authentik:authentik /certs /media /authentik/.ssh
|
||||
|
||||
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 ./authentik/ /authentik
|
||||
COPY ./pyproject.toml /
|
||||
COPY ./schemas /schemas
|
||||
COPY ./locale /locale
|
||||
COPY ./tests /tests
|
||||
COPY ./manage.py /
|
||||
COPY ./blueprints /blueprints
|
||||
COPY ./lifecycle/ /app-root/lifecycle
|
||||
COPY ./lifecycle/ /lifecycle
|
||||
COPY --from=go-builder /work/authentik /bin/authentik
|
||||
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/
|
||||
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/
|
||||
|
||||
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:/app-root/lifecycle"
|
||||
ENV PATH "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/lifecycle"
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "ak", "healthcheck" ]
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "/lifecycle/ak", "healthcheck" ]
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/dumb-init", "--", "ak" ]
|
||||
ENTRYPOINT [ "/usr/local/bin/dumb-init", "--", "/lifecycle/ak" ]
|
||||
|
@ -1,12 +1,17 @@
|
||||
"""Generate JSON Schema for blueprints"""
|
||||
from json import dumps, loads
|
||||
from pathlib import Path
|
||||
from json import dumps
|
||||
from typing import Any
|
||||
|
||||
from django.core.management.base import BaseCommand, no_translations
|
||||
from django.db.models import Model
|
||||
from drf_jsonschema_serializer.convert import field_to_converter
|
||||
from rest_framework.fields import Field, JSONField, UUIDField
|
||||
from rest_framework.serializers import Serializer
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.blueprints.v1.importer import is_model_allowed
|
||||
from authentik.blueprints.v1.meta.registry import registry
|
||||
from authentik.lib.models import SerializerModel
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
@ -16,21 +21,138 @@ class Command(BaseCommand):
|
||||
|
||||
schema: dict
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": "authentik Blueprint schema",
|
||||
"required": ["version", "entries"],
|
||||
"properties": {
|
||||
"version": {
|
||||
"$id": "#/properties/version",
|
||||
"type": "integer",
|
||||
"title": "Blueprint version",
|
||||
"default": 1,
|
||||
},
|
||||
"metadata": {
|
||||
"$id": "#/properties/metadata",
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"labels": {"type": "object", "additionalProperties": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
"context": {
|
||||
"$id": "#/properties/context",
|
||||
"type": "object",
|
||||
"additionalProperties": True,
|
||||
},
|
||||
"entries": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [],
|
||||
},
|
||||
},
|
||||
},
|
||||
"$defs": {},
|
||||
}
|
||||
|
||||
@no_translations
|
||||
def handle(self, *args, **options):
|
||||
"""Generate JSON Schema for blueprints"""
|
||||
path = Path(__file__).parent.joinpath("./schema_template.json")
|
||||
with open(path, "r", encoding="utf-8") as _template_file:
|
||||
self.schema = loads(_template_file.read())
|
||||
self.set_model_allowed()
|
||||
self.stdout.write(dumps(self.schema, indent=4))
|
||||
self.build()
|
||||
self.stdout.write(dumps(self.schema, indent=4, default=Command.json_default))
|
||||
|
||||
def set_model_allowed(self):
|
||||
"""Set model enum"""
|
||||
model_names = []
|
||||
@staticmethod
|
||||
def json_default(value: Any) -> Any:
|
||||
"""Helper that handles gettext_lazy strings that JSON doesn't handle"""
|
||||
return str(value)
|
||||
|
||||
def build(self):
|
||||
"""Build all models into the schema"""
|
||||
for model in registry.get_models():
|
||||
if model._meta.abstract:
|
||||
continue
|
||||
if not is_model_allowed(model):
|
||||
continue
|
||||
model_names.append(f"{model._meta.app_label}.{model._meta.model_name}")
|
||||
model_names.sort()
|
||||
self.schema["properties"]["entries"]["items"]["properties"]["model"]["enum"] = model_names
|
||||
model_instance: Model = model()
|
||||
if not isinstance(model_instance, SerializerModel):
|
||||
continue
|
||||
serializer = model_instance.serializer()
|
||||
model_path = f"{model._meta.app_label}.{model._meta.model_name}"
|
||||
self.schema["properties"]["entries"]["items"]["oneOf"].append(
|
||||
self.template_entry(model_path, serializer)
|
||||
)
|
||||
|
||||
def template_entry(self, model_path: str, serializer: Serializer) -> dict:
|
||||
"""Template entry for a single model"""
|
||||
model_schema = self.to_jsonschema(serializer)
|
||||
model_schema["required"] = []
|
||||
def_name = f"model_{model_path}"
|
||||
def_path = f"#/$defs/{def_name}"
|
||||
self.schema["$defs"][def_name] = model_schema
|
||||
return {
|
||||
"type": "object",
|
||||
"required": ["model", "attrs"],
|
||||
"properties": {
|
||||
"model": {"const": model_path},
|
||||
"id": {"type": "string"},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": ["absent", "present", "created"],
|
||||
"default": "present",
|
||||
},
|
||||
"conditions": {"type": "array", "items": {"type": "boolean"}},
|
||||
"attrs": {"$ref": def_path},
|
||||
"identifiers": {"$ref": def_path},
|
||||
},
|
||||
}
|
||||
|
||||
def field_to_jsonschema(self, field: Field) -> dict:
|
||||
"""Convert a single field to json schema"""
|
||||
if isinstance(field, Serializer):
|
||||
result = self.to_jsonschema(field)
|
||||
else:
|
||||
try:
|
||||
converter = field_to_converter[field]
|
||||
result = converter.convert(field)
|
||||
except KeyError:
|
||||
if isinstance(field, JSONField):
|
||||
result = {"type": "object", "additionalProperties": True}
|
||||
elif isinstance(field, UUIDField):
|
||||
result = {"type": "string", "format": "uuid"}
|
||||
else:
|
||||
raise
|
||||
if field.label:
|
||||
result["title"] = field.label
|
||||
if field.help_text:
|
||||
result["description"] = field.help_text
|
||||
return self.clean_result(result)
|
||||
|
||||
def clean_result(self, result: dict) -> dict:
|
||||
"""Remove enumNames from result, recursively"""
|
||||
result.pop("enumNames", None)
|
||||
for key, value in result.items():
|
||||
if isinstance(value, dict):
|
||||
result[key] = self.clean_result(value)
|
||||
return result
|
||||
|
||||
def to_jsonschema(self, serializer: Serializer) -> dict:
|
||||
"""Convert serializer to json schema"""
|
||||
properties = {}
|
||||
required = []
|
||||
for name, field in serializer.fields.items():
|
||||
if field.read_only:
|
||||
continue
|
||||
sub_schema = self.field_to_jsonschema(field)
|
||||
if field.required:
|
||||
required.append(name)
|
||||
properties[name] = sub_schema
|
||||
|
||||
result = {"type": "object", "properties": properties}
|
||||
if required:
|
||||
result["required"] = required
|
||||
return result
|
||||
|
@ -1,105 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "http://example.com/example.json",
|
||||
"type": "object",
|
||||
"title": "authentik Blueprint schema",
|
||||
"default": {},
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"$id": "#/properties/version",
|
||||
"type": "integer",
|
||||
"title": "Blueprint version",
|
||||
"default": 1
|
||||
},
|
||||
"metadata": {
|
||||
"$id": "#/properties/metadata",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"labels": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
},
|
||||
"context": {
|
||||
"$id": "#/properties/context",
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"entries": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$id": "#entry",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"model"
|
||||
],
|
||||
"properties": {
|
||||
"model": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"placeholder"
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"absent",
|
||||
"present",
|
||||
"created"
|
||||
],
|
||||
"default": "present"
|
||||
},
|
||||
"conditions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"attrs": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Commonly available field, may not exist on all models"
|
||||
}
|
||||
},
|
||||
"default": {},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"identifiers": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"properties": {
|
||||
"pk": {
|
||||
"description": "Commonly available field, may not exist on all models",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -160,6 +160,7 @@ class CertificateKeyPairSerializer(ModelSerializer):
|
||||
"managed",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"managed": {"read_only": True},
|
||||
"key_data": {"write_only": True},
|
||||
"certificate_data": {"write_only": True},
|
||||
}
|
||||
|
@ -219,13 +219,13 @@ class Event(SerializerModel, ExpiringModel):
|
||||
self.context["http_request"] = {
|
||||
"path": request.path,
|
||||
"method": request.method,
|
||||
"args": QueryDict(request.META.get("QUERY_STRING", "")),
|
||||
"args": cleanse_dict(QueryDict(request.META.get("QUERY_STRING", ""))),
|
||||
}
|
||||
# Special case for events created during flow execution
|
||||
# since they keep the http query within a wrapped query
|
||||
if QS_QUERY in self.context["http_request"]["args"]:
|
||||
wrapped = self.context["http_request"]["args"][QS_QUERY]
|
||||
self.context["http_request"]["args"] = QueryDict(wrapped)
|
||||
self.context["http_request"]["args"] = cleanse_dict(QueryDict(wrapped))
|
||||
if hasattr(request, "tenant"):
|
||||
tenant: Tenant = request.tenant
|
||||
# Because self.created only gets set on save, we can't use it's value here
|
||||
|
@ -1,17 +1,25 @@
|
||||
"""event tests"""
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.views.debug import SafeExceptionReporterFilter
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
|
||||
from authentik.core.models import Group
|
||||
from authentik.events.models import Event
|
||||
from authentik.flows.views.executor import QS_QUERY
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.policies.dummy.models import DummyPolicy
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
|
||||
class TestEvents(TestCase):
|
||||
"""Test Event"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_new_with_model(self):
|
||||
"""Create a new Event passing a model as kwarg"""
|
||||
test_model = Group.objects.create(name="test")
|
||||
@ -40,3 +48,58 @@ class TestEvents(TestCase):
|
||||
model_content_type = ContentType.objects.get_for_model(temp_model)
|
||||
self.assertEqual(event.context.get("model").get("app"), model_content_type.app_label)
|
||||
self.assertEqual(event.context.get("model").get("pk"), temp_model.pk.hex)
|
||||
|
||||
def test_from_http_basic(self):
|
||||
"""Test plain from_http"""
|
||||
event = Event.new("unittest").from_http(self.factory.get("/"))
|
||||
self.assertEqual(
|
||||
event.context, {"http_request": {"args": {}, "method": "GET", "path": "/"}}
|
||||
)
|
||||
|
||||
def test_from_http_clean_querystring(self):
|
||||
"""Test cleansing query string"""
|
||||
request = self.factory.get(f"/?token={generate_id()}")
|
||||
event = Event.new("unittest").from_http(request)
|
||||
self.assertEqual(
|
||||
event.context,
|
||||
{
|
||||
"http_request": {
|
||||
"args": {"token": SafeExceptionReporterFilter.cleansed_substitute},
|
||||
"method": "GET",
|
||||
"path": "/",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def test_from_http_clean_querystring_flow(self):
|
||||
"""Test cleansing query string (nested query string like flow executor)"""
|
||||
nested_qs = {"token": generate_id()}
|
||||
request = self.factory.get(f"/?{QS_QUERY}={urlencode(nested_qs)}")
|
||||
event = Event.new("unittest").from_http(request)
|
||||
self.assertEqual(
|
||||
event.context,
|
||||
{
|
||||
"http_request": {
|
||||
"args": {"token": SafeExceptionReporterFilter.cleansed_substitute},
|
||||
"method": "GET",
|
||||
"path": "/",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def test_from_http_tenant(self):
|
||||
"""Test from_http tenant"""
|
||||
# Test tenant
|
||||
request = self.factory.get("/")
|
||||
tenant = Tenant(domain="test-tenant")
|
||||
setattr(request, "tenant", tenant)
|
||||
event = Event.new("unittest").from_http(request)
|
||||
self.assertEqual(
|
||||
event.tenant,
|
||||
{
|
||||
"app": "authentik_tenants",
|
||||
"model_name": "tenant",
|
||||
"name": "Tenant test-tenant",
|
||||
"pk": tenant.pk.hex,
|
||||
},
|
||||
)
|
||||
|
@ -72,7 +72,7 @@ cookie_domain: null
|
||||
disable_update_check: false
|
||||
disable_startup_analytics: false
|
||||
avatars: env://AUTHENTIK_AUTHENTIK__AVATARS?gravatar,initials
|
||||
geoip: "/app-root/geoip/GeoLite2-City.mmdb"
|
||||
geoip: "/geoip/GeoLite2-City.mmdb"
|
||||
|
||||
footer_links: []
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
# Generated by Django 4.1.7 on 2023-05-06 16:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import authentik.providers.oauth2.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
(
|
||||
"authentik_providers_oauth2",
|
||||
"0015_accesstoken_auth_time_authorizationcode_auth_time_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="refreshtoken",
|
||||
name="token",
|
||||
field=models.TextField(
|
||||
default=authentik.providers.oauth2.models.generate_client_secret
|
||||
),
|
||||
),
|
||||
]
|
@ -382,7 +382,7 @@ class AccessToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
||||
"""OAuth2 Refresh Token, opaque"""
|
||||
|
||||
token = models.TextField(default=generate_key)
|
||||
token = models.TextField(default=generate_client_secret)
|
||||
_id_token = models.TextField(verbose_name=_("ID Token"))
|
||||
|
||||
@property
|
||||
|
@ -114,9 +114,10 @@ class EmailStageView(ChallengeStageView):
|
||||
user.is_active = True
|
||||
user.save()
|
||||
return self.executor.stage_ok()
|
||||
if PLAN_CONTEXT_PENDING_USER not in self.executor.plan.context:
|
||||
self.logger.debug("No pending user")
|
||||
messages.error(self.request, _("No pending user."))
|
||||
if not user.is_authenticated:
|
||||
# We'll only get here if there's no user in the flow plan context
|
||||
# and no authenticated user either
|
||||
self.logger.debug("Unauthenticated user", user=user)
|
||||
return self.executor.stage_invalid()
|
||||
# Check if we've already sent the initial e-mail
|
||||
if PLAN_CONTEXT_EMAIL_SENT not in self.executor.plan.context:
|
||||
|
File diff suppressed because it is too large
Load Diff
28
poetry.lock
generated
28
poetry.lock
generated
@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
@ -1272,6 +1272,30 @@ websocket-client = ">=0.32.0"
|
||||
[package.extras]
|
||||
ssh = ["paramiko (>=2.4.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "drf-jsonschema-serializer"
|
||||
version = "1.0.0"
|
||||
description = "JSON Schema support for Django REST Framework"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "drf-jsonschema-serializer-1.0.0.tar.gz", hash = "sha256:aa58d03deba5a936bc0b0dbca4b69ee902886b7a0be130797f1d5e741b92e42b"},
|
||||
{file = "drf_jsonschema_serializer-1.0.0-py3-none-any.whl", hash = "sha256:06401c94f1a2610797a26c390b701504b90b6b44683932daccbc250ea2aad3b1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
django = ">=3.2"
|
||||
djangorestframework = ">=3.13"
|
||||
jsonschema = ">=4.0.0"
|
||||
|
||||
[package.extras]
|
||||
all-format-validators = ["fqdn", "idna", "isoduration", "jsonpointer", "rfc3339-validator", "rfc3987", "uri-template", "webcolors"]
|
||||
coverage = ["pytest-cov"]
|
||||
docs = ["sphinx", "sphinx-rtd-theme"]
|
||||
release = ["bump2version", "twine"]
|
||||
tests = ["black", "django-stubs[compatible-mypy]", "djangorestframework-stubs[compatible-mypy]", "flake8", "fqdn", "idna", "isoduration", "isort", "jsonpointer", "mypy", "pytest", "pytest-django", "rfc3339-validator", "rfc3987", "tox", "types-jsonschema", "uri-template", "webcolors"]
|
||||
|
||||
[[package]]
|
||||
name = "drf-spectacular"
|
||||
version = "0.26.2"
|
||||
@ -4152,4 +4176,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "82fc267d6041997d1410a951033cdb9f6c57d91df7d48acaecdbab320daab58e"
|
||||
content-hash = "da0f14183137ec5d4fcd7df877f1488860bc26f795f8aaa19c78655f77e3f409"
|
||||
|
@ -179,6 +179,7 @@ bump2version = "*"
|
||||
colorama = "*"
|
||||
coverage = { extras = ["toml"], version = "*" }
|
||||
django-silk = "*"
|
||||
drf-jsonschema-serializer = "*"
|
||||
importlib-metadata = "*"
|
||||
pylint = "*"
|
||||
pylint-django = "*"
|
||||
|
20
schema.yml
20
schema.yml
@ -27912,6 +27912,7 @@ components:
|
||||
readOnly: true
|
||||
managed:
|
||||
type: string
|
||||
readOnly: true
|
||||
nullable: true
|
||||
title: Managed by authentik
|
||||
description: Objects which are managed by authentik. These objects are created
|
||||
@ -27924,6 +27925,7 @@ components:
|
||||
- certificate_download_url
|
||||
- fingerprint_sha1
|
||||
- fingerprint_sha256
|
||||
- managed
|
||||
- name
|
||||
- pk
|
||||
- private_key_available
|
||||
@ -27946,15 +27948,6 @@ components:
|
||||
writeOnly: true
|
||||
description: Optional Private Key. If this is set, you can use this keypair
|
||||
for encryption.
|
||||
managed:
|
||||
type: string
|
||||
nullable: true
|
||||
minLength: 1
|
||||
title: Managed by authentik
|
||||
description: Objects which are managed by authentik. These objects are created
|
||||
and updated automatically. This is flag only indicates that an object
|
||||
can be overwritten by migrations. You can still modify the objects via
|
||||
the API, but expect changes to be overwritten in a later update.
|
||||
required:
|
||||
- certificate_data
|
||||
- name
|
||||
@ -35649,15 +35642,6 @@ components:
|
||||
writeOnly: true
|
||||
description: Optional Private Key. If this is set, you can use this keypair
|
||||
for encryption.
|
||||
managed:
|
||||
type: string
|
||||
nullable: true
|
||||
minLength: 1
|
||||
title: Managed by authentik
|
||||
description: Objects which are managed by authentik. These objects are created
|
||||
and updated automatically. This is flag only indicates that an object
|
||||
can be overwritten by migrations. You can still modify the objects via
|
||||
the API, but expect changes to be overwritten in a later update.
|
||||
PatchedConsentStageRequest:
|
||||
type: object
|
||||
description: ConsentStage Serializer
|
||||
|
8
web/package-lock.json
generated
8
web/package-lock.json
generated
@ -22,7 +22,7 @@
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@formatjs/intl-listformat": "^7.2.2",
|
||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||
"@goauthentik/api": "^2023.4.1-1683127005",
|
||||
"@goauthentik/api": "^2023.4.1-1683455546",
|
||||
"@hcaptcha/types": "^1.0.3",
|
||||
"@jackfranklin/rollup-plugin-markdown": "^0.4.0",
|
||||
"@lingui/cli": "^4.0.0",
|
||||
@ -2347,9 +2347,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@goauthentik/api": {
|
||||
"version": "2023.4.1-1683127005",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.4.1-1683127005.tgz",
|
||||
"integrity": "sha512-+qSD+LlaBvGKrN6qXK7peabIbT2CAIsXbgqFvHjOD6/9t98fwKUX6jDMJqFrGV4xdANZ14a5wYLV6m9Wm3rqDA=="
|
||||
"version": "2023.4.1-1683455546",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2023.4.1-1683455546.tgz",
|
||||
"integrity": "sha512-RpHngzXzuSi4OKzQRCbRUpbmHzAR4eHUghUiMm15fVVMYSYkjYs2t6b9iEUwpf7xENhKPgrKSNptfv8U+3B++Q=="
|
||||
},
|
||||
"node_modules/@hcaptcha/types": {
|
||||
"version": "1.0.3",
|
||||
|
@ -29,7 +29,7 @@
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@formatjs/intl-listformat": "^7.2.2",
|
||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||
"@goauthentik/api": "^2023.4.1-1683127005",
|
||||
"@goauthentik/api": "^2023.4.1-1683455546",
|
||||
"@hcaptcha/types": "^1.0.3",
|
||||
"@jackfranklin/rollup-plugin-markdown": "^0.4.0",
|
||||
"@lingui/cli": "^4.0.0",
|
||||
|
@ -1,37 +1,25 @@
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
import "@goauthentik/flow/FormStatic";
|
||||
import { AccessDeniedStage } from "@goauthentik/flow/stages/access_denied/AccessDeniedStage";
|
||||
import { BaseStage } from "@goauthentik/flow/stages/base";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { OAuthDeviceCodeFinishChallenge } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-flow-provider-oauth2-code-finish")
|
||||
export class DeviceCodeFinish extends AccessDeniedStage {
|
||||
export class DeviceCodeFinish extends BaseStage<
|
||||
OAuthDeviceCodeFinishChallenge,
|
||||
OAuthDeviceCodeFinishChallenge
|
||||
> {
|
||||
render(): TemplateResult {
|
||||
if (!this.challenge) {
|
||||
return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`;
|
||||
}
|
||||
return html`<header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
|
||||
</header>
|
||||
<div class="pf-c-login__main-body">
|
||||
<form class="pf-c-form">
|
||||
<div class="pf-c-form__group">
|
||||
<p class="big-icon">
|
||||
<i class="pf-icon pf-icon-ok"></i>
|
||||
</p>
|
||||
<h3 class="pf-c-title pf-m-3xl reason">
|
||||
${t`You've successfully authenticated your device.`}
|
||||
</h3>
|
||||
<hr />
|
||||
<p>${t`You can close this tab now.`}</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<footer class="pf-c-login__main-footer">
|
||||
<ul class="pf-c-login__main-footer-links"></ul>
|
||||
</footer>`;
|
||||
return html`<ak-empty-state icon="fas fa-check" header=${t`You may close this page now.`}>
|
||||
<span slot="body"> ${t`You've successfully authenticated your device.`} </span>
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
}
|
||||
|
@ -64,9 +64,6 @@ export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeRes
|
||||
// As this wouldn't really be a redirect, show a message that the page can be closed
|
||||
// and try to close it ourselves
|
||||
if (!url.protocol.startsWith("http")) {
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 500);
|
||||
return html`<ak-empty-state
|
||||
icon="fas fa-check"
|
||||
header=${t`You may close this page now.`}
|
||||
|
@ -5,6 +5,7 @@ Blueprints are YAML files, which can use some additional tags to ease blueprint
|
||||
## Structure
|
||||
|
||||
```yaml
|
||||
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
|
||||
# The version of this blueprint, currently 1
|
||||
version: 1
|
||||
# Optional block of metadata, name is required if metadata is set
|
||||
|
Reference in New Issue
Block a user