Skip to content

Commit

Permalink
Merge branch 'main' into dev
Browse files Browse the repository at this point in the history
* main: (38 commits)
  crypto: fix race conditions when creating self-signed certificates on startup (#7344)
  blueprints: fix entries with state: absent not being deleted if their serializer has errors (#7345)
  web/admin: fix @change handler for ak-radio elements (#7348)
  rbac: handle lookup error (#7341)
  website/docs: add warning about upgrading to 2023.10 (#7340)
  web/admin: fix role form reacting to enter (#7330)
  core: bump github.com/google/uuid from 1.3.1 to 1.4.0 (#7333)
  core: bump goauthentik.io/api/v3 from 3.2023083.10 to 3.2023101.1 (#7334)
  core: bump ruff from 0.1.2 to 0.1.3 (#7335)
  core: bump pydantic-scim from 0.0.7 to 0.0.8 (#7336)
  website/blogs: Blog dockers (#7328)
  providers/proxy: attempt to fix duplicate cookie (#7324)
  stages/email: fix sending emails from task (#7325)
  web: bump API Client version (#7321)
  website/docs: update release notes for 2023.10.1 (#7316)
  release: 2023.10.1
  lifecycle: fix otp merge migration (#7315)
  root: fix pylint errors (#7312)
  web: bump API Client version (#7311)
  release: 2023.10.0
  ...
  • Loading branch information
kensternberg-authentik committed Oct 27, 2023
2 parents 0449fd0 + ad9f500 commit 639a8ce
Show file tree
Hide file tree
Showing 79 changed files with 5,732 additions and 3,765 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 2023.8.3
current_version = 2023.10.1
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: make empty ts client
run: mkdir -p ./gen-ts-client
- name: make empty clients
run: |
mkdir -p ./gen-ts-api
mkdir -p ./gen-go-api
- name: Build Docker Image
uses: docker/build-push-action@v5
with:
Expand Down Expand Up @@ -69,6 +71,10 @@ jobs:
- name: prepare variables
uses: ./.github/actions/docker-push-variables
id: ev
- name: make empty clients
run: |
mkdir -p ./gen-ts-api
mkdir -p ./gen-go-api
- name: Docker Login Registry
uses: docker/login-action@v3
with:
Expand All @@ -93,6 +99,7 @@ jobs:
ghcr.io/goauthentik/${{ matrix.type }}:latest
file: ${{ matrix.type }}.Dockerfile
platforms: linux/amd64,linux/arm64
context: .
build-args: |
VERSION=${{ steps.ev.outputs.version }}
VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
echo "PG_PASS=$(openssl rand -base64 32)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 32)" >> .env
docker buildx install
mkdir -p ./gen-ts-api
docker build -t testing:latest .
echo "AUTHENTIK_IMAGE=testing" >> .env
echo "AUTHENTIK_TAG=latest" >> .env
Expand Down
2 changes: 1 addition & 1 deletion authentik/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from os import environ
from typing import Optional

__version__ = "2023.8.3"
__version__ = "2023.10.1"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"


Expand Down
7 changes: 6 additions & 1 deletion authentik/blueprints/v1/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,12 +584,17 @@ class EntryInvalidError(SentryIgnoredException):
entry_model: Optional[str]
entry_id: Optional[str]
validation_error: Optional[ValidationError]
serializer: Optional[Serializer] = None

def __init__(self, *args: object, validation_error: Optional[ValidationError] = None) -> None:
def __init__(
self, *args: object, validation_error: Optional[ValidationError] = None, **kwargs
) -> None:
super().__init__(*args)
self.entry_model = None
self.entry_id = None
self.validation_error = validation_error
for key, value in kwargs.items():
setattr(self, key, value)

@staticmethod
def from_entry(
Expand Down
18 changes: 12 additions & 6 deletions authentik/blueprints/v1/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,10 @@ def _validate_single(self, entry: BlueprintEntry) -> Optional[BaseSerializer]:
try:
full_data = self.__update_pks_for_attrs(entry.get_attrs(self._import))
except ValueError as exc:
raise EntryInvalidError.from_entry(exc, entry) from exc
raise EntryInvalidError.from_entry(
exc,
entry,
) from exc
always_merger.merge(full_data, updated_identifiers)
serializer_kwargs["data"] = full_data

Expand All @@ -272,6 +275,7 @@ def _validate_single(self, entry: BlueprintEntry) -> Optional[BaseSerializer]:
f"Serializer errors {serializer.errors}",
validation_error=exc,
entry=entry,
serializer=serializer,
) from exc
return serializer

Expand Down Expand Up @@ -300,16 +304,18 @@ def _apply_models(self, raise_errors=False) -> bool:
)
return False
# Validate each single entry
serializer = None
try:
serializer = self._validate_single(entry)
except EntryInvalidError as exc:
# For deleting objects we don't need the serializer to be valid
if entry.get_state(self._import) == BlueprintEntryDesiredState.ABSENT:
continue
self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc)
if raise_errors:
raise exc
return False
serializer = exc.serializer
else:
self.logger.warning(f"entry invalid: {exc}", entry=entry, error=exc)
if raise_errors:
raise exc
return False
if not serializer:
continue

Expand Down
2 changes: 1 addition & 1 deletion authentik/blueprints/v1/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def on_any_event(self, event: FileSystemEvent):
path = Path(event.src_path)
root = Path(CONFIG.get("blueprints_dir")).absolute()
rel_path = str(path.relative_to(root))
for instance in BlueprintInstance.objects.filter(path=rel_path):
for instance in BlueprintInstance.objects.filter(path=rel_path, enabled=True):
LOGGER.debug("modified blueprint file, starting apply", instance=instance)
apply_blueprint.delay(instance.pk.hex)

Expand Down
1 change: 1 addition & 0 deletions authentik/core/api/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class Meta:
class ApplicationViewSet(UsedByMixin, ModelViewSet):
"""Application Viewset"""

# pylint: disable=no-member
queryset = Application.objects.all().prefetch_related("provider")
serializer_class = ApplicationSerializer
search_fields = [
Expand Down
1 change: 1 addition & 0 deletions authentik/core/api/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ class UserAccountSerializer(PassiveSerializer):
class GroupViewSet(UsedByMixin, ModelViewSet):
"""Group Viewset"""

# pylint: disable=no-member
queryset = Group.objects.all().select_related("parent").prefetch_related("users")
serializer_class = GroupSerializer
search_fields = ["name", "is_superuser"]
Expand Down
1 change: 1 addition & 0 deletions authentik/core/sources/flow_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def get_action(self, **kwargs) -> tuple[Action, Optional[UserSourceConnection]]:
if self.request.user.is_authenticated:
new_connection.user = self.request.user
new_connection = self.update_connection(new_connection, **kwargs)
# pylint: disable=no-member
new_connection.save()
return Action.LINK, new_connection

Expand Down
45 changes: 26 additions & 19 deletions authentik/crypto/apps.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
"""authentik crypto app config"""
from datetime import datetime
from typing import TYPE_CHECKING, Optional
from typing import Optional

from authentik.blueprints.apps import ManagedAppConfig
from authentik.lib.generators import generate_id

if TYPE_CHECKING:
from authentik.crypto.models import CertificateKeyPair

MANAGED_KEY = "goauthentik.io/crypto/jwt-managed"


Expand All @@ -23,33 +20,37 @@ def reconcile_load_crypto_tasks(self):
"""Load crypto tasks"""
self.import_module("authentik.crypto.tasks")

def _create_update_cert(self, cert: Optional["CertificateKeyPair"] = None):
def _create_update_cert(self):
from authentik.crypto.builder import CertificateBuilder
from authentik.crypto.models import CertificateKeyPair

builder = CertificateBuilder("authentik Internal JWT Certificate")
common_name = "authentik Internal JWT Certificate"
builder = CertificateBuilder(common_name)
builder.build(
subject_alt_names=["goauthentik.io"],
validity_days=360,
)
if not cert:
cert = CertificateKeyPair()
builder.cert = cert
builder.cert.managed = MANAGED_KEY
builder.save()
CertificateKeyPair.objects.update_or_create(
managed=MANAGED_KEY,
defaults={
"name": common_name,
"certificate_data": builder.certificate,
"key_data": builder.private_key,
},
)

def reconcile_managed_jwt_cert(self):
"""Ensure managed JWT certificate"""
from authentik.crypto.models import CertificateKeyPair

certs = CertificateKeyPair.objects.filter(managed=MANAGED_KEY)
if not certs.exists():
self._create_update_cert()
return
cert: CertificateKeyPair = certs.first()
cert: Optional[CertificateKeyPair] = CertificateKeyPair.objects.filter(
managed=MANAGED_KEY
).first()
now = datetime.now()
if now < cert.certificate.not_valid_before or now > cert.certificate.not_valid_after:
self._create_update_cert(cert)
if not cert or (
now < cert.certificate.not_valid_before or now > cert.certificate.not_valid_after
):
self._create_update_cert()

def reconcile_self_signed(self):
"""Create self-signed keypair"""
Expand All @@ -61,4 +62,10 @@ def reconcile_self_signed(self):
return
builder = CertificateBuilder(name)
builder.build(subject_alt_names=[f"{generate_id()}.self-signed.goauthentik.io"])
builder.save()
CertificateKeyPair.objects.get_or_create(
name=name,
defaults={
"certificate_data": builder.certificate,
"key_data": builder.private_key,
},
)
14 changes: 10 additions & 4 deletions authentik/rbac/api/rbac.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,19 @@ class PermissionSerializer(ModelSerializer):

def get_app_label_verbose(self, instance: Permission) -> str:
"""Human-readable app label"""
return apps.get_app_config(instance.content_type.app_label).verbose_name
try:
return apps.get_app_config(instance.content_type.app_label).verbose_name
except LookupError:
return f"{instance.content_type.app_label}.{instance.content_type.model}"

def get_model_verbose(self, instance: Permission) -> str:
"""Human-readable model name"""
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
try:
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
except LookupError:
return f"{instance.content_type.app_label}.{instance.content_type.model}"

class Meta:
model = Permission
Expand Down
14 changes: 10 additions & 4 deletions authentik/rbac/api/rbac_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,23 @@ def get_app_label_verbose(self, instance: GroupObjectPermission) -> str:

def get_model_verbose(self, instance: GroupObjectPermission) -> str:
"""Get model label from permission's model"""
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
try:
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
except LookupError:
return f"{instance.content_type.app_label}.{instance.content_type.model}"

def get_object_description(self, instance: GroupObjectPermission) -> Optional[str]:
"""Get model description from attached model. This operation takes at least
one additional query, and the description is only shown if the user/role has the
view_ permission on the object"""
app_label = instance.content_type.app_label
model = instance.content_type.model
model_class = apps.get_model(app_label, model)
try:
model_class = apps.get_model(app_label, model)
except LookupError:
return None
objects = get_objects_for_group(instance.group, f"{app_label}.view_{model}", model_class)
obj = objects.first()
if not obj:
Expand Down
14 changes: 10 additions & 4 deletions authentik/rbac/api/rbac_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,23 @@ def get_app_label_verbose(self, instance: UserObjectPermission) -> str:

def get_model_verbose(self, instance: UserObjectPermission) -> str:
"""Get model label from permission's model"""
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
try:
return apps.get_model(
instance.content_type.app_label, instance.content_type.model
)._meta.verbose_name
except LookupError:
return f"{instance.content_type.app_label}.{instance.content_type.model}"

def get_object_description(self, instance: UserObjectPermission) -> Optional[str]:
"""Get model description from attached model. This operation takes at least
one additional query, and the description is only shown if the user/role has the
view_ permission on the object"""
app_label = instance.content_type.app_label
model = instance.content_type.model
model_class = apps.get_model(app_label, model)
try:
model_class = apps.get_model(app_label, model)
except LookupError:
return None
objects = get_objects_for_user(instance.user, f"{app_label}.view_{model}", model_class)
obj = objects.first()
if not obj:
Expand Down
5 changes: 5 additions & 0 deletions authentik/stages/email/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
from authentik.root.celery import CELERY_APP
from authentik.stages.email.models import EmailStage
from authentik.stages.email.utils import logo_data

LOGGER = get_logger()

Expand Down Expand Up @@ -81,6 +82,10 @@ def send_mail(self: MonitoredTask, message: dict[Any, Any], email_stage_pk: Opti
# Because we use the Message-ID as UID for the task, manually assign it
message_object.extra_headers["Message-ID"] = message_id

# Add the logo (we can't add it in the previous message since MIMEImage
# can't be converted to json)
message_object.attach(logo_data())

LOGGER.debug("Sending mail", to=message_object.to)
backend.send_messages([message_object])
Event.new(
Expand Down
9 changes: 6 additions & 3 deletions authentik/stages/email/utils.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
"""email utils"""
from email.mime.image import MIMEImage
from functools import lru_cache
from pathlib import Path

from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils import translation


@lru_cache()
def logo_data():
def logo_data() -> MIMEImage:
"""Get logo as MIME Image for emails"""
with open("web/icons/icon_left_brand.png", "rb") as _logo_file:
path = Path("web/icons/icon_left_brand.png")
if not path.exists():
path = Path("web/dist/assets/icons/icon_left_brand.png")
with open(path, "rb") as _logo_file:
logo = MIMEImage(_logo_file.read())
logo.add_header("Content-ID", "logo.png")
return logo
Expand All @@ -25,5 +29,4 @@ def __init__(self, template_name=None, template_context=None, language="", **kwa
super().__init__(**kwargs)
self.content_subtype = "html"
self.mixed_subtype = "related"
self.attach(logo_data())
self.attach_alternative(html_content, "text/html")
1 change: 1 addition & 0 deletions authentik/stages/user_write/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Meta:
"user_creation_mode",
"create_users_as_inactive",
"create_users_group",
"user_type",
"user_path_template",
]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.6 on 2023-10-25 15:19

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("authentik_stages_user_write", "0007_remove_userwritestage_can_create_users_and_more"),
]

operations = [
migrations.AddField(
model_name="userwritestage",
name="user_type",
field=models.TextField(
choices=[
("internal", "Internal"),
("external", "External"),
("service_account", "Service Account"),
("internal_service_account", "Internal Service Account"),
],
default="external",
),
),
]
Loading

0 comments on commit 639a8ce

Please sign in to comment.