Skip to content

Commit

Permalink
Merge branch 'main' into core/include-version-in-js
Browse files Browse the repository at this point in the history
* main: (196 commits)
  website/docs: release notes for 2024.6 (#9812)
  policies/reputation: save to database directly (#10059)
  providers/enterprise: import user/group data when manually linking objects (#10089)
  core, web: update translations (#10108)
  web: Add enterprise / FIPS notification to the AdminOverviewPage (#10090)
  core: bump github.com/getsentry/sentry-go from 0.28.0 to 0.28.1 (#10095)
  web: bump API Client version (#10107)
  admin: system api: do not show FIPS status if no valid license (#10091)
  root: add configuration option to enable fips (#10088)
  web: bump the sentry group across 1 directory with 2 updates (#10101)
  web: bump ts-pattern from 5.1.2 to 5.2.0 in /web (#10098)
  web: bump the storybook group across 1 directory with 7 updates (#10102)
  core: bump github.com/gorilla/websocket from 1.5.2 to 1.5.3 (#10103)
  core: bump pydantic from 2.7.3 to 2.7.4 (#10093)
  core: bump bandit from 1.7.8 to 1.7.9 (#10094)
  website/developer-docs: add a baby Style Guide (#9900)
  website/integrations: gitlab: update certificate key pair location and specify sha (#9925)
  root: handle asgi exception (#10085)
  website: bump prettier from 3.3.1 to 3.3.2 in /website (#10082)
  web: bump prettier from 3.3.1 to 3.3.2 in /web (#10081)
  ...
  • Loading branch information
kensternberg-authentik committed Jun 17, 2024
2 parents 749353c + 0c604ce commit 2f2a3bc
Show file tree
Hide file tree
Showing 206 changed files with 13,545 additions and 5,337 deletions.
1 change: 1 addition & 0 deletions .github/codespell-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ hass
warmup
ontext
singed
assertIn
2 changes: 0 additions & 2 deletions .github/workflows/ci-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ jobs:
fail-fast: false
matrix:
psql:
- 12-alpine
- 15-alpine
- 16-alpine
steps:
Expand Down Expand Up @@ -104,7 +103,6 @@ jobs:
fail-fast: false
matrix:
psql:
- 12-alpine
- 15-alpine
- 16-alpine
steps:
Expand Down
30 changes: 19 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
RUN npm run build

# Stage 3: Build go proxy
FROM --platform=${BUILDPLATFORM} docker.io/golang:1.22.3-bookworm AS go-builder
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS go-builder

ARG TARGETOS
ARG TARGETARCH
Expand All @@ -50,6 +50,11 @@ ARG GOARCH=$TARGETARCH

WORKDIR /go/src/goauthentik.io

RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \
dpkg --add-architecture arm64 && \
apt-get update && \
apt-get install -y --no-install-recommends crossbuild-essential-arm64 gcc-aarch64-linux-gnu

RUN --mount=type=bind,target=/go/src/goauthentik.io/go.mod,src=./go.mod \
--mount=type=bind,target=/go/src/goauthentik.io/go.sum,src=./go.sum \
--mount=type=cache,target=/go/pkg/mod \
Expand All @@ -64,11 +69,11 @@ COPY ./internal /go/src/goauthentik.io/internal
COPY ./go.mod /go/src/goauthentik.io/go.mod
COPY ./go.sum /go/src/goauthentik.io/go.sum

ENV CGO_ENABLED=0

RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
--mount=type=cache,id=go-build-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/root/.cache/go-build \
GOARM="${TARGETVARIANT#v}" go build -o /go/authentik ./cmd/server
if [ "$TARGETARCH" = "arm64" ]; then export CC=aarch64-linux-gnu-gcc && export CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \
CGO_ENABLED=1 GOEXPERIMENT="systemcrypto" GOFLAGS="-tags=requirefips" GOARM="${TARGETVARIANT#v}" \
go build -o /go/authentik ./cmd/server

# Stage 4: MaxMind GeoIP
FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v7.0.1 as geoip
Expand All @@ -85,7 +90,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"

# Stage 5: Python dependencies
FROM docker.io/python:3.12.3-slim-bookworm AS python-deps
FROM ghcr.io/goauthentik/fips-python:3.12.3-slim-bookworm-fips-full AS python-deps

WORKDIR /ak-root/poetry

Expand All @@ -98,20 +103,21 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloa
RUN --mount=type=cache,id=apt-$TARGETARCH$TARGETVARIANT,sharing=locked,target=/var/cache/apt \
apt-get update && \
# Required for installing pip packages
apt-get install -y --no-install-recommends build-essential pkg-config libxmlsec1-dev zlib1g-dev libpq-dev
apt-get install -y --no-install-recommends build-essential pkg-config libpq-dev

RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \
--mount=type=bind,target=./poetry.lock,src=./poetry.lock \
--mount=type=cache,target=/root/.cache/pip \
--mount=type=cache,target=/root/.cache/pypoetry \
python -m venv /ak-root/venv/ && \
bash -c "source ${VENV_PATH}/bin/activate && \
pip3 install --upgrade pip && \
pip3 install poetry && \
poetry install --only=main --no-ansi --no-interaction --no-root"
pip3 install --upgrade pip && \
pip3 install poetry && \
poetry install --only=main --no-ansi --no-interaction --no-root && \
pip install --force-reinstall /wheels/*"

# Stage 6: Run
FROM docker.io/python:3.12.3-slim-bookworm AS final-image
FROM ghcr.io/goauthentik/fips-python:3.12.3-slim-bookworm-fips-full AS final-image

ARG GIT_BUILD_HASH
ARG VERSION
Expand All @@ -128,7 +134,7 @@ WORKDIR /
# We cannot cache this layer otherwise we'll end up with a bigger image
RUN apt-get update && \
# Required for runtime
apt-get install -y --no-install-recommends libpq5 openssl libxmlsec1-openssl libmaxminddb0 ca-certificates && \
apt-get install -y --no-install-recommends libpq5 libmaxminddb0 ca-certificates && \
# Required for bootstrap & healtcheck
apt-get install -y --no-install-recommends runit && \
apt-get clean && \
Expand Down Expand Up @@ -164,6 +170,8 @@ ENV TMPDIR=/dev/shm/ \
VENV_PATH="/ak-root/venv" \
POETRY_VIRTUALENVS_CREATE=false

ENV GOFIPS=1

HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "ak", "healthcheck" ]

ENTRYPOINT [ "dumb-init", "--", "ak" ]
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ website-watch: ## Build and watch the documentation website, updating automatic
#########################

docker: ## Build a docker image of the current source tree
mkdir -p ${GEN_API_TS}
DOCKER_BUILDKIT=1 docker build . --progress plain --tag ${DOCKER_IMAGE}

#########################
Expand Down
19 changes: 14 additions & 5 deletions authentik/admin/api/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@

import platform
from datetime import datetime
from ssl import OPENSSL_VERSION
from sys import version as python_version
from typing import TypedDict

from cryptography.hazmat.backends.openssl.backend import backend
from django.utils.timezone import now
from drf_spectacular.utils import extend_schema
from gunicorn import version_info as gunicorn_version
from rest_framework.fields import SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

from authentik import get_full_version
from authentik.core.api.utils import PassiveSerializer
from authentik.enterprise.license import LicenseKey
from authentik.lib.config import CONFIG
from authentik.lib.utils.reflection import get_env
from authentik.outposts.apps import MANAGED_OUTPOST
Expand All @@ -25,11 +28,13 @@ class RuntimeDict(TypedDict):
"""Runtime information"""

python_version: str
gunicorn_version: str
environment: str
architecture: str
platform: str
uname: str
openssl_version: str
openssl_fips_mode: bool | None
authentik_version: str


class SystemInfoSerializer(PassiveSerializer):
Expand Down Expand Up @@ -64,11 +69,15 @@ def get_http_is_secure(self, request: Request) -> bool:
def get_runtime(self, request: Request) -> RuntimeDict:
"""Get versions"""
return {
"python_version": python_version,
"gunicorn_version": ".".join(str(x) for x in gunicorn_version),
"environment": get_env(),
"architecture": platform.machine(),
"authentik_version": get_full_version(),
"environment": get_env(),
"openssl_fips_enabled": (
backend._fips_enabled if LicenseKey.get_total().is_valid() else None
),
"openssl_version": OPENSSL_VERSION,
"platform": platform.platform(),
"python_version": python_version,
"uname": " ".join(platform.uname()),
}

Expand Down
2 changes: 1 addition & 1 deletion authentik/blueprints/v1/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class BlueprintEntry:
_state: BlueprintEntryState = field(default_factory=BlueprintEntryState)

def __post_init__(self, *args, **kwargs) -> None:
self.__tag_contexts: list["YAMLTagContext"] = []
self.__tag_contexts: list[YAMLTagContext] = []

@staticmethod
def from_model(model: SerializerModel, *extra_identifier_names: str) -> "BlueprintEntry":
Expand Down
6 changes: 3 additions & 3 deletions authentik/blueprints/v1/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
from authentik.policies.models import Policy, PolicyBindingModel
from authentik.policies.reputation.models import Reputation
from authentik.providers.oauth2.models import AccessToken, AuthorizationCode, RefreshToken
from authentik.providers.scim.models import SCIMGroup, SCIMUser
from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser
from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser
from authentik.stages.authenticator_webauthn.models import WebAuthnDeviceType
from authentik.tenants.models import Tenant
Expand Down Expand Up @@ -97,8 +97,8 @@ def excluded_models() -> list[type[Model]]:
# FIXME: these shouldn't need to be explicitly listed, but rather based off of a mixin
FlowToken,
LicenseUsage,
SCIMGroup,
SCIMUser,
SCIMProviderGroup,
SCIMProviderUser,
Tenant,
SystemTask,
ConnectionToken,
Expand Down
7 changes: 7 additions & 0 deletions authentik/core/api/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from json import loads

from django.db.models import Prefetch
from django.http import Http404
from django_filters.filters import CharFilter, ModelMultipleChoiceFilter
from django_filters.filterset import FilterSet
Expand Down Expand Up @@ -166,8 +167,14 @@ class UserAccountSerializer(PassiveSerializer):

def get_queryset(self):
base_qs = Group.objects.all().select_related("parent").prefetch_related("roles")

if self.serializer_class(context={"request": self.request})._should_include_users:
base_qs = base_qs.prefetch_related("users")
else:
base_qs = base_qs.prefetch_related(
Prefetch("users", queryset=User.objects.all().only("id"))
)

return base_qs

@extend_schema(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.fields import BooleanField, CharField
from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField
Expand All @@ -22,7 +23,7 @@
PassiveSerializer,
)
from authentik.core.expression.evaluator import PropertyMappingEvaluator
from authentik.core.models import PropertyMapping
from authentik.core.models import Group, PropertyMapping, User
from authentik.events.utils import sanitize_item
from authentik.policies.api.exec import PolicyTestSerializer
from authentik.rbac.decorators import permission_required
Expand Down Expand Up @@ -76,20 +77,25 @@ class PropertyMappingViewSet(
):
"""PropertyMapping Viewset"""

queryset = PropertyMapping.objects.none()
class PropertyMappingTestSerializer(PolicyTestSerializer):
"""Test property mapping execution for a user/group with context"""

user = PrimaryKeyRelatedField(queryset=User.objects.all(), required=False, allow_null=True)
group = PrimaryKeyRelatedField(
queryset=Group.objects.all(), required=False, allow_null=True
)

queryset = PropertyMapping.objects.select_subclasses()
serializer_class = PropertyMappingSerializer
search_fields = [
"name",
]
filterset_fields = {"managed": ["isnull"]}
ordering = ["name"]

def get_queryset(self): # pragma: no cover
return PropertyMapping.objects.select_subclasses()

@permission_required("authentik_core.view_propertymapping")
@extend_schema(
request=PolicyTestSerializer(),
request=PropertyMappingTestSerializer(),
responses={
200: PropertyMappingTestResultSerializer,
400: OpenApiResponse(description="Invalid parameters"),
Expand All @@ -107,29 +113,39 @@ def test(self, request: Request, pk: str) -> Response:
"""Test Property Mapping"""
_mapping: PropertyMapping = self.get_object()
# Use `get_subclass` to get correct class and correct `.evaluate` implementation
mapping = PropertyMapping.objects.get_subclass(pk=_mapping.pk)
mapping: PropertyMapping = PropertyMapping.objects.get_subclass(pk=_mapping.pk)
# FIXME: when we separate policy mappings between ones for sources
# and ones for providers, we need to make the user field optional for the source mapping
test_params = PolicyTestSerializer(data=request.data)
test_params = self.PropertyMappingTestSerializer(data=request.data)
if not test_params.is_valid():
return Response(test_params.errors, status=400)

format_result = str(request.GET.get("format_result", "false")).lower() == "true"

# User permission check, only allow mapping testing for users that are readable
users = get_objects_for_user(request.user, "authentik_core.view_user").filter(
pk=test_params.validated_data["user"].pk
)
if not users.exists():
raise PermissionDenied()
context: dict = test_params.validated_data.get("context", {})
context.setdefault("user", None)

if user := test_params.validated_data.get("user"):
# User permission check, only allow mapping testing for users that are readable
users = get_objects_for_user(request.user, "authentik_core.view_user").filter(
pk=user.pk
)
if not users.exists():
raise PermissionDenied()
context["user"] = user
if group := test_params.validated_data.get("group"):
# Group permission check, only allow mapping testing for groups that are readable
groups = get_objects_for_user(request.user, "authentik_core.view_group").filter(
pk=group.pk
)
if not groups.exists():
raise PermissionDenied()
context["group"] = group
context["request"] = self.request

response_data = {"successful": True, "result": ""}
try:
result = mapping.evaluate(
users.first(),
self.request,
**test_params.validated_data.get("context", {}),
)
result = mapping.evaluate(**context)
response_data["result"] = dumps(
sanitize_item(result), indent=(4 if format_result else None)
)
Expand Down
8 changes: 4 additions & 4 deletions authentik/core/api/used_by.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ def get_delete_action(manager: Manager) -> str:
"""Get the delete action from the Foreign key, falls back to cascade"""
if hasattr(manager, "field"):
if manager.field.remote_field.on_delete.__name__ == SET_NULL.__name__:
return DeleteAction.SET_NULL.name
return DeleteAction.SET_NULL.value
if manager.field.remote_field.on_delete.__name__ == SET_DEFAULT.__name__:
return DeleteAction.SET_DEFAULT.name
return DeleteAction.SET_DEFAULT.value
if hasattr(manager, "source_field"):
return DeleteAction.CASCADE_MANY.name
return DeleteAction.CASCADE.name
return DeleteAction.CASCADE_MANY.value
return DeleteAction.CASCADE.value


class UsedByMixin:
Expand Down
Loading

0 comments on commit 2f2a3bc

Please sign in to comment.