Compare commits

...

19 Commits

Author SHA1 Message Date
35cd126406 release: 2024.6.0-rc1 2024-06-14 18:42:26 +02:00
f89a4fc276 website/docs: update 2024.6 release notes with latest changes (cherry-pick #10109) (#10115)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-06-14 18:33:26 +02:00
4d7f380b2d web: bump API Client version (cherry-pick #10113) (#10114) 2024-06-15 00:33:28 +09:00
cb8379031a admin: system api: fix FIPS status schema (cherry-pick #10110) (#10112)
admin: system api: fix FIPS status schema (#10110)

Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-06-15 00:27:33 +09:00
0c604ceba4 website/docs: release notes for 2024.6 (#9812)
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-06-14 15:38:21 +02:00
30e39c75ff policies/reputation: save to database directly (#10059)
* policies/reputation: save to database directly

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* makemigrations

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix settings

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* also update expiry

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* lint?

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-06-14 22:34:43 +09:00
6d7bebbcc3 providers/enterprise: import user/group data when manually linking objects (#10089)
* providers/enterprise: import user/group data when manually linking objects

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* select immutable ID

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* generalize and implement for all

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix more

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-06-14 22:34:33 +09:00
dc332ec7b0 core, web: update translations (#10108)
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2024-06-14 12:11:55 +00:00
31e94a2814 web: Add enterprise / FIPS notification to the AdminOverviewPage (#10090)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-06-14 11:38:48 +00:00
eb08214f0e core: bump github.com/getsentry/sentry-go from 0.28.0 to 0.28.1 (#10095)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-14 13:21:57 +02:00
a5ab8a618e web: bump API Client version (#10107)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2024-06-14 11:15:08 +00:00
b8cbdcae22 admin: system api: do not show FIPS status if no valid license (#10091)
* admin: system api: do not show FIPS status if no valid license

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* also for outposts

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* black

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2024-06-14 12:52:24 +02:00
ae86184511 root: add configuration option to enable fips (#10088) 2024-06-14 10:04:00 +00:00
b704388c2f web: bump the sentry group across 1 directory with 2 updates (#10101) 2024-06-14 15:52:07 +09:00
a35f9fdd7b web: bump ts-pattern from 5.1.2 to 5.2.0 in /web (#10098)
Bumps [ts-pattern](https://github.com/gvergnaud/ts-pattern) from 5.1.2 to 5.2.0.
- [Release notes](https://github.com/gvergnaud/ts-pattern/releases)
- [Commits](https://github.com/gvergnaud/ts-pattern/compare/v5.1.2...v5.2.0)

---
updated-dependencies:
- dependency-name: ts-pattern
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-14 15:31:30 +09:00
d95220be0e web: bump the storybook group across 1 directory with 7 updates (#10102)
Bumps the storybook group with 6 updates in the /web directory:

| Package | From | To |
| --- | --- | --- |
| [@storybook/addon-essentials](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/essentials) | `8.1.6` | `8.1.9` |
| [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/links) | `8.1.6` | `8.1.9` |
| [@storybook/manager-api](https://github.com/storybookjs/storybook/tree/HEAD/code/lib/manager-api) | `8.1.6` | `8.1.9` |
| [@storybook/web-components](https://github.com/storybookjs/storybook/tree/HEAD/code/renderers/web-components) | `8.1.6` | `8.1.9` |
| [@storybook/web-components-vite](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/web-components-vite) | `8.1.6` | `8.1.9` |
| [storybook](https://github.com/storybookjs/storybook/tree/HEAD/code/lib/cli) | `8.1.6` | `8.1.9` |



Updates `@storybook/addon-essentials` from 8.1.6 to 8.1.9
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v8.1.9/code/addons/essentials)

Updates `@storybook/addon-links` from 8.1.6 to 8.1.9
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v8.1.9/code/addons/links)

Updates `@storybook/blocks` from 8.1.6 to 8.1.9
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v8.1.9/code/ui/blocks)

Updates `@storybook/manager-api` from 8.1.6 to 8.1.9
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v8.1.9/code/lib/manager-api)

Updates `@storybook/web-components` from 8.1.6 to 8.1.9
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v8.1.9/code/renderers/web-components)

Updates `@storybook/web-components-vite` from 8.1.6 to 8.1.9
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v8.1.9/code/frameworks/web-components-vite)

Updates `storybook` from 8.1.6 to 8.1.9
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v8.1.9/code/lib/cli)

---
updated-dependencies:
- dependency-name: "@storybook/addon-essentials"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/addon-links"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/blocks"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/manager-api"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: "@storybook/web-components-vite"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: storybook
- dependency-name: storybook
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: storybook
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-14 15:31:16 +09:00
ba1b86efa1 core: bump github.com/gorilla/websocket from 1.5.2 to 1.5.3 (#10103)
Bumps [github.com/gorilla/websocket](https://github.com/gorilla/websocket) from 1.5.2 to 1.5.3.
- [Release notes](https://github.com/gorilla/websocket/releases)
- [Commits](https://github.com/gorilla/websocket/compare/v1.5.2...v1.5.3)

---
updated-dependencies:
- dependency-name: github.com/gorilla/websocket
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-14 15:31:05 +09:00
cd93de1141 core: bump pydantic from 2.7.3 to 2.7.4 (#10093)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-13 15:47:13 +00:00
cc148bd552 core: bump bandit from 1.7.8 to 1.7.9 (#10094)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-13 17:27:09 +02:00
59 changed files with 2478 additions and 950 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 2024.4.2 current_version = 2024.6.0-rc1
tag = True tag = True
commit = True commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))? parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?

View File

@ -2,7 +2,7 @@
from os import environ from os import environ
__version__ = "2024.4.2" __version__ = "2024.6.0"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@ -16,6 +16,7 @@ from rest_framework.views import APIView
from authentik import get_full_version from authentik import get_full_version
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.enterprise.license import LicenseKey
from authentik.lib.config import CONFIG from authentik.lib.config import CONFIG
from authentik.lib.utils.reflection import get_env from authentik.lib.utils.reflection import get_env
from authentik.outposts.apps import MANAGED_OUTPOST from authentik.outposts.apps import MANAGED_OUTPOST
@ -32,7 +33,7 @@ class RuntimeDict(TypedDict):
platform: str platform: str
uname: str uname: str
openssl_version: str openssl_version: str
openssl_fips_mode: bool openssl_fips_enabled: bool | None
authentik_version: str authentik_version: str
@ -71,7 +72,9 @@ class SystemInfoSerializer(PassiveSerializer):
"architecture": platform.machine(), "architecture": platform.machine(),
"authentik_version": get_full_version(), "authentik_version": get_full_version(),
"environment": get_env(), "environment": get_env(),
"openssl_fips_enabled": backend._fips_enabled, "openssl_fips_enabled": (
backend._fips_enabled if LicenseKey.get_total().is_valid() else None
),
"openssl_version": OPENSSL_VERSION, "openssl_version": OPENSSL_VERSION,
"platform": platform.platform(), "platform": platform.platform(),
"python_version": python_version, "python_version": python_version,

View File

@ -7,6 +7,7 @@ from rest_framework.viewsets import GenericViewSet
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserGroupSerializer from authentik.core.api.users import UserGroupSerializer
from authentik.enterprise.providers.google_workspace.models import GoogleWorkspaceProviderGroup from authentik.enterprise.providers.google_workspace.models import GoogleWorkspaceProviderGroup
from authentik.lib.sync.outgoing.api import OutgoingSyncConnectionCreateMixin
class GoogleWorkspaceProviderGroupSerializer(ModelSerializer): class GoogleWorkspaceProviderGroupSerializer(ModelSerializer):
@ -30,6 +31,7 @@ class GoogleWorkspaceProviderGroupSerializer(ModelSerializer):
class GoogleWorkspaceProviderGroupViewSet( class GoogleWorkspaceProviderGroupViewSet(
mixins.CreateModelMixin, mixins.CreateModelMixin,
OutgoingSyncConnectionCreateMixin,
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin, UsedByMixin,

View File

@ -7,6 +7,7 @@ from rest_framework.viewsets import GenericViewSet
from authentik.core.api.groups import GroupMemberSerializer from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.enterprise.providers.google_workspace.models import GoogleWorkspaceProviderUser from authentik.enterprise.providers.google_workspace.models import GoogleWorkspaceProviderUser
from authentik.lib.sync.outgoing.api import OutgoingSyncConnectionCreateMixin
class GoogleWorkspaceProviderUserSerializer(ModelSerializer): class GoogleWorkspaceProviderUserSerializer(ModelSerializer):
@ -30,6 +31,7 @@ class GoogleWorkspaceProviderUserSerializer(ModelSerializer):
class GoogleWorkspaceProviderUserViewSet( class GoogleWorkspaceProviderUserViewSet(
mixins.CreateModelMixin, mixins.CreateModelMixin,
OutgoingSyncConnectionCreateMixin,
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin, UsedByMixin,

View File

@ -214,3 +214,7 @@ class GoogleWorkspaceGroupClient(
google_id=google_id, google_id=google_id,
attributes=group, attributes=group,
) )
def update_single_attribute(self, connection: GoogleWorkspaceProviderUser):
group = self.directory_service.groups().get(connection.google_id)
connection.attributes = group

View File

@ -119,3 +119,7 @@ class GoogleWorkspaceUserClient(GoogleWorkspaceSyncClient[User, GoogleWorkspaceP
google_id=email, google_id=email,
attributes=user, attributes=user,
) )
def update_single_attribute(self, connection: GoogleWorkspaceProviderUser):
user = self.directory_service.users().get(connection.google_id)
connection.attributes = user

View File

@ -31,6 +31,58 @@ def default_scopes() -> list[str]:
] ]
class GoogleWorkspaceProviderUser(SerializerModel):
"""Mapping of a user and provider to a Google user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
google_id = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
provider = models.ForeignKey("GoogleWorkspaceProvider", on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)
@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.google_workspace.api.users import (
GoogleWorkspaceProviderUserSerializer,
)
return GoogleWorkspaceProviderUserSerializer
class Meta:
verbose_name = _("Google Workspace Provider User")
verbose_name_plural = _("Google Workspace Provider Users")
unique_together = (("google_id", "user", "provider"),)
def __str__(self) -> str:
return f"Google Workspace Provider User {self.user_id} to {self.provider_id}"
class GoogleWorkspaceProviderGroup(SerializerModel):
"""Mapping of a group and provider to a Google group ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
google_id = models.TextField()
group = models.ForeignKey(Group, on_delete=models.CASCADE)
provider = models.ForeignKey("GoogleWorkspaceProvider", on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)
@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.google_workspace.api.groups import (
GoogleWorkspaceProviderGroupSerializer,
)
return GoogleWorkspaceProviderGroupSerializer
class Meta:
verbose_name = _("Google Workspace Provider Group")
verbose_name_plural = _("Google Workspace Provider Groups")
unique_together = (("google_id", "group", "provider"),)
def __str__(self) -> str:
return f"Google Workspace Provider Group {self.group_id} to {self.provider_id}"
class GoogleWorkspaceProvider(OutgoingSyncProvider, BackchannelProvider): class GoogleWorkspaceProvider(OutgoingSyncProvider, BackchannelProvider):
"""Sync users from authentik into Google Workspace.""" """Sync users from authentik into Google Workspace."""
@ -59,15 +111,16 @@ class GoogleWorkspaceProvider(OutgoingSyncProvider, BackchannelProvider):
) )
def client_for_model( def client_for_model(
self, model: type[User | Group] self,
model: type[User | Group | GoogleWorkspaceProviderUser | GoogleWorkspaceProviderGroup],
) -> BaseOutgoingSyncClient[User | Group, Any, Any, Self]: ) -> BaseOutgoingSyncClient[User | Group, Any, Any, Self]:
if issubclass(model, User): if issubclass(model, User | GoogleWorkspaceProviderUser):
from authentik.enterprise.providers.google_workspace.clients.users import ( from authentik.enterprise.providers.google_workspace.clients.users import (
GoogleWorkspaceUserClient, GoogleWorkspaceUserClient,
) )
return GoogleWorkspaceUserClient(self) return GoogleWorkspaceUserClient(self)
if issubclass(model, Group): if issubclass(model, Group | GoogleWorkspaceProviderGroup):
from authentik.enterprise.providers.google_workspace.clients.groups import ( from authentik.enterprise.providers.google_workspace.clients.groups import (
GoogleWorkspaceGroupClient, GoogleWorkspaceGroupClient,
) )
@ -144,55 +197,3 @@ class GoogleWorkspaceProviderMapping(PropertyMapping):
class Meta: class Meta:
verbose_name = _("Google Workspace Provider Mapping") verbose_name = _("Google Workspace Provider Mapping")
verbose_name_plural = _("Google Workspace Provider Mappings") verbose_name_plural = _("Google Workspace Provider Mappings")
class GoogleWorkspaceProviderUser(SerializerModel):
"""Mapping of a user and provider to a Google user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
google_id = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
provider = models.ForeignKey(GoogleWorkspaceProvider, on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)
@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.google_workspace.api.users import (
GoogleWorkspaceProviderUserSerializer,
)
return GoogleWorkspaceProviderUserSerializer
class Meta:
verbose_name = _("Google Workspace Provider User")
verbose_name_plural = _("Google Workspace Provider Users")
unique_together = (("google_id", "user", "provider"),)
def __str__(self) -> str:
return f"Google Workspace Provider User {self.user_id} to {self.provider_id}"
class GoogleWorkspaceProviderGroup(SerializerModel):
"""Mapping of a group and provider to a Google group ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
google_id = models.TextField()
group = models.ForeignKey(Group, on_delete=models.CASCADE)
provider = models.ForeignKey(GoogleWorkspaceProvider, on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)
@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.google_workspace.api.groups import (
GoogleWorkspaceProviderGroupSerializer,
)
return GoogleWorkspaceProviderGroupSerializer
class Meta:
verbose_name = _("Google Workspace Provider Group")
verbose_name_plural = _("Google Workspace Provider Groups")
unique_together = (("google_id", "group", "provider"),)
def __str__(self) -> str:
return f"Google Workspace Provider Group {self.group_id} to {self.provider_id}"

View File

@ -7,6 +7,7 @@ from rest_framework.viewsets import GenericViewSet
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserGroupSerializer from authentik.core.api.users import UserGroupSerializer
from authentik.enterprise.providers.microsoft_entra.models import MicrosoftEntraProviderGroup from authentik.enterprise.providers.microsoft_entra.models import MicrosoftEntraProviderGroup
from authentik.lib.sync.outgoing.api import OutgoingSyncConnectionCreateMixin
class MicrosoftEntraProviderGroupSerializer(ModelSerializer): class MicrosoftEntraProviderGroupSerializer(ModelSerializer):
@ -30,6 +31,7 @@ class MicrosoftEntraProviderGroupSerializer(ModelSerializer):
class MicrosoftEntraProviderGroupViewSet( class MicrosoftEntraProviderGroupViewSet(
mixins.CreateModelMixin, mixins.CreateModelMixin,
OutgoingSyncConnectionCreateMixin,
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin, UsedByMixin,

View File

@ -7,6 +7,7 @@ from rest_framework.viewsets import GenericViewSet
from authentik.core.api.groups import GroupMemberSerializer from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.enterprise.providers.microsoft_entra.models import MicrosoftEntraProviderUser from authentik.enterprise.providers.microsoft_entra.models import MicrosoftEntraProviderUser
from authentik.lib.sync.outgoing.api import OutgoingSyncConnectionCreateMixin
class MicrosoftEntraProviderUserSerializer(ModelSerializer): class MicrosoftEntraProviderUserSerializer(ModelSerializer):
@ -29,6 +30,7 @@ class MicrosoftEntraProviderUserSerializer(ModelSerializer):
class MicrosoftEntraProviderUserViewSet( class MicrosoftEntraProviderUserViewSet(
OutgoingSyncConnectionCreateMixin,
mixins.CreateModelMixin, mixins.CreateModelMixin,
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,

View File

@ -226,3 +226,7 @@ class MicrosoftEntraGroupClient(
microsoft_id=group.id, microsoft_id=group.id,
attributes=self.entity_as_dict(group), attributes=self.entity_as_dict(group),
) )
def update_single_attribute(self, connection: MicrosoftEntraProviderGroup):
data = self._request(self.client.groups.by_group_id(connection.microsoft_id).get())
connection.attributes = self.entity_as_dict(data)

View File

@ -66,6 +66,26 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
microsoft_user.delete() microsoft_user.delete()
return response return response
def get_select_fields(self) -> list[str]:
"""All fields that should be selected when we fetch user data."""
# TODO: Make this customizable in the future
return [
# Default fields
"businessPhones",
"displayName",
"givenName",
"jobTitle",
"mail",
"mobilePhone",
"officeLocation",
"preferredLanguage",
"surname",
"userPrincipalName",
"id",
# Required for logging into M365 using authentik
"onPremisesImmutableId",
]
def create(self, user: User): def create(self, user: User):
"""Create user from scratch and create a connection object""" """Create user from scratch and create a connection object"""
microsoft_user = self.to_schema(user, None) microsoft_user = self.to_schema(user, None)
@ -75,12 +95,12 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
response = self._request(self.client.users.post(microsoft_user)) response = self._request(self.client.users.post(microsoft_user))
except ObjectExistsSyncException: except ObjectExistsSyncException:
# user already exists in microsoft entra, so we can connect them manually # user already exists in microsoft entra, so we can connect them manually
query_params = UsersRequestBuilder.UsersRequestBuilderGetQueryParameters()(
filter=f"mail eq '{microsoft_user.mail}'",
)
request_configuration = ( request_configuration = (
UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration( UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration(
query_parameters=query_params, query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters(
filter=f"mail eq '{microsoft_user.mail}'",
select=self.get_select_fields(),
),
) )
) )
user_data = self._request(self.client.users.get(request_configuration)) user_data = self._request(self.client.users.get(request_configuration))
@ -99,7 +119,6 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
except TransientSyncException as exc: except TransientSyncException as exc:
raise exc raise exc
else: else:
print(self.entity_as_dict(response))
return MicrosoftEntraProviderUser.objects.create( return MicrosoftEntraProviderUser.objects.create(
provider=self.provider, provider=self.provider,
user=user, user=user,
@ -120,7 +139,12 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
def discover(self): def discover(self):
"""Iterate through all users and connect them with authentik users if possible""" """Iterate through all users and connect them with authentik users if possible"""
users = self._request(self.client.users.get()) request_configuration = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration(
query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters(
select=self.get_select_fields(),
),
)
users = self._request(self.client.users.get(request_configuration))
next_link = True next_link = True
while next_link: while next_link:
for user in users.value: for user in users.value:
@ -141,3 +165,14 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
microsoft_id=user.id, microsoft_id=user.id,
attributes=self.entity_as_dict(user), attributes=self.entity_as_dict(user),
) )
def update_single_attribute(self, connection: MicrosoftEntraProviderUser):
request_configuration = UsersRequestBuilder.UsersRequestBuilderGetRequestConfiguration(
query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters(
select=self.get_select_fields(),
),
)
data = self._request(
self.client.users.by_user_id(connection.microsoft_id).get(request_configuration)
)
connection.attributes = self.entity_as_dict(data)

View File

@ -22,6 +22,58 @@ from authentik.lib.sync.outgoing.base import BaseOutgoingSyncClient
from authentik.lib.sync.outgoing.models import OutgoingSyncDeleteAction, OutgoingSyncProvider from authentik.lib.sync.outgoing.models import OutgoingSyncDeleteAction, OutgoingSyncProvider
class MicrosoftEntraProviderUser(SerializerModel):
"""Mapping of a user and provider to a Microsoft user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
microsoft_id = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
provider = models.ForeignKey("MicrosoftEntraProvider", on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)
@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.microsoft_entra.api.users import (
MicrosoftEntraProviderUserSerializer,
)
return MicrosoftEntraProviderUserSerializer
class Meta:
verbose_name = _("Microsoft Entra Provider User")
verbose_name_plural = _("Microsoft Entra Provider User")
unique_together = (("microsoft_id", "user", "provider"),)
def __str__(self) -> str:
return f"Microsoft Entra Provider User {self.user_id} to {self.provider_id}"
class MicrosoftEntraProviderGroup(SerializerModel):
"""Mapping of a group and provider to a Microsoft group ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
microsoft_id = models.TextField()
group = models.ForeignKey(Group, on_delete=models.CASCADE)
provider = models.ForeignKey("MicrosoftEntraProvider", on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)
@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.microsoft_entra.api.groups import (
MicrosoftEntraProviderGroupSerializer,
)
return MicrosoftEntraProviderGroupSerializer
class Meta:
verbose_name = _("Microsoft Entra Provider Group")
verbose_name_plural = _("Microsoft Entra Provider Groups")
unique_together = (("microsoft_id", "group", "provider"),)
def __str__(self) -> str:
return f"Microsoft Entra Provider Group {self.group_id} to {self.provider_id}"
class MicrosoftEntraProvider(OutgoingSyncProvider, BackchannelProvider): class MicrosoftEntraProvider(OutgoingSyncProvider, BackchannelProvider):
"""Sync users from authentik into Microsoft Entra.""" """Sync users from authentik into Microsoft Entra."""
@ -48,15 +100,16 @@ class MicrosoftEntraProvider(OutgoingSyncProvider, BackchannelProvider):
) )
def client_for_model( def client_for_model(
self, model: type[User | Group] self,
model: type[User | Group | MicrosoftEntraProviderUser | MicrosoftEntraProviderGroup],
) -> BaseOutgoingSyncClient[User | Group, Any, Any, Self]: ) -> BaseOutgoingSyncClient[User | Group, Any, Any, Self]:
if issubclass(model, User): if issubclass(model, User | MicrosoftEntraProviderUser):
from authentik.enterprise.providers.microsoft_entra.clients.users import ( from authentik.enterprise.providers.microsoft_entra.clients.users import (
MicrosoftEntraUserClient, MicrosoftEntraUserClient,
) )
return MicrosoftEntraUserClient(self) return MicrosoftEntraUserClient(self)
if issubclass(model, Group): if issubclass(model, Group | MicrosoftEntraProviderGroup):
from authentik.enterprise.providers.microsoft_entra.clients.groups import ( from authentik.enterprise.providers.microsoft_entra.clients.groups import (
MicrosoftEntraGroupClient, MicrosoftEntraGroupClient,
) )
@ -133,55 +186,3 @@ class MicrosoftEntraProviderMapping(PropertyMapping):
class Meta: class Meta:
verbose_name = _("Microsoft Entra Provider Mapping") verbose_name = _("Microsoft Entra Provider Mapping")
verbose_name_plural = _("Microsoft Entra Provider Mappings") verbose_name_plural = _("Microsoft Entra Provider Mappings")
class MicrosoftEntraProviderUser(SerializerModel):
"""Mapping of a user and provider to a Microsoft user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
microsoft_id = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
provider = models.ForeignKey(MicrosoftEntraProvider, on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)
@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.microsoft_entra.api.users import (
MicrosoftEntraProviderUserSerializer,
)
return MicrosoftEntraProviderUserSerializer
class Meta:
verbose_name = _("Microsoft Entra Provider User")
verbose_name_plural = _("Microsoft Entra Provider User")
unique_together = (("microsoft_id", "user", "provider"),)
def __str__(self) -> str:
return f"Microsoft Entra Provider User {self.user_id} to {self.provider_id}"
class MicrosoftEntraProviderGroup(SerializerModel):
"""Mapping of a group and provider to a Microsoft group ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
microsoft_id = models.TextField()
group = models.ForeignKey(Group, on_delete=models.CASCADE)
provider = models.ForeignKey(MicrosoftEntraProvider, on_delete=models.CASCADE)
attributes = models.JSONField(default=dict)
@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.microsoft_entra.api.groups import (
MicrosoftEntraProviderGroupSerializer,
)
return MicrosoftEntraProviderGroupSerializer
class Meta:
verbose_name = _("Microsoft Entra Provider Group")
verbose_name_plural = _("Microsoft Entra Provider Groups")
unique_together = (("microsoft_id", "group", "provider"),)
def __str__(self) -> str:
return f"Microsoft Entra Provider Group {self.group_id} to {self.provider_id}"

View File

@ -3,16 +3,18 @@
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
from azure.identity.aio import ClientSecretCredential from azure.identity.aio import ClientSecretCredential
from django.test import TestCase from django.urls import reverse
from msgraph.generated.models.group_collection_response import GroupCollectionResponse from msgraph.generated.models.group_collection_response import GroupCollectionResponse
from msgraph.generated.models.organization import Organization from msgraph.generated.models.organization import Organization
from msgraph.generated.models.organization_collection_response import OrganizationCollectionResponse from msgraph.generated.models.organization_collection_response import OrganizationCollectionResponse
from msgraph.generated.models.user import User as MSUser from msgraph.generated.models.user import User as MSUser
from msgraph.generated.models.user_collection_response import UserCollectionResponse from msgraph.generated.models.user_collection_response import UserCollectionResponse
from msgraph.generated.models.verified_domain import VerifiedDomain from msgraph.generated.models.verified_domain import VerifiedDomain
from rest_framework.test import APITestCase
from authentik.blueprints.tests import apply_blueprint from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, User from authentik.core.models import Application, Group, User
from authentik.core.tests.utils import create_test_admin_user
from authentik.enterprise.providers.microsoft_entra.models import ( from authentik.enterprise.providers.microsoft_entra.models import (
MicrosoftEntraProvider, MicrosoftEntraProvider,
MicrosoftEntraProviderMapping, MicrosoftEntraProviderMapping,
@ -25,11 +27,12 @@ from authentik.lib.sync.outgoing.models import OutgoingSyncDeleteAction
from authentik.tenants.models import Tenant from authentik.tenants.models import Tenant
class MicrosoftEntraUserTests(TestCase): class MicrosoftEntraUserTests(APITestCase):
"""Microsoft Entra User tests""" """Microsoft Entra User tests"""
@apply_blueprint("system/providers-microsoft-entra.yaml") @apply_blueprint("system/providers-microsoft-entra.yaml")
def setUp(self) -> None: def setUp(self) -> None:
# Delete all users and groups as the mocked HTTP responses only return one ID # Delete all users and groups as the mocked HTTP responses only return one ID
# which will cause errors with multiple users # which will cause errors with multiple users
Tenant.objects.update(avatars="none") Tenant.objects.update(avatars="none")
@ -371,3 +374,45 @@ class MicrosoftEntraUserTests(TestCase):
) )
self.assertFalse(Event.objects.filter(action=EventAction.SYSTEM_EXCEPTION).exists()) self.assertFalse(Event.objects.filter(action=EventAction.SYSTEM_EXCEPTION).exists())
user_list.assert_called_once() user_list.assert_called_once()
def test_connect_manual(self):
"""test manual user connection"""
uid = generate_id()
self.app.backchannel_providers.remove(self.provider)
admin = create_test_admin_user()
different_user = User.objects.create(
username=uid,
email=f"{uid}@goauthentik.io",
)
self.app.backchannel_providers.add(self.provider)
with (
patch(
"authentik.enterprise.providers.microsoft_entra.models.MicrosoftEntraProvider.microsoft_credentials",
MagicMock(return_value={"credentials": self.creds}),
),
patch(
"msgraph.generated.organization.organization_request_builder.OrganizationRequestBuilder.get",
AsyncMock(
return_value=OrganizationCollectionResponse(
value=[
Organization(verified_domains=[VerifiedDomain(name="goauthentik.io")])
]
)
),
),
patch(
"authentik.enterprise.providers.microsoft_entra.clients.users.MicrosoftEntraUserClient.update_single_attribute",
MagicMock(),
) as user_get,
):
self.client.force_login(admin)
response = self.client.post(
reverse("authentik_api:microsoftentraprovideruser-list"),
data={
"microsoft_id": generate_id(),
"user": different_user.pk,
"provider": self.provider.pk,
},
)
self.assertEqual(response.status_code, 201)
user_get.assert_called_once()

View File

@ -50,7 +50,6 @@ cache:
timeout: 300 timeout: 300
timeout_flows: 300 timeout_flows: 300
timeout_policies: 300 timeout_policies: 300
timeout_reputation: 300
# channel: # channel:
# url: "" # url: ""
@ -116,6 +115,9 @@ events:
context_processors: context_processors:
geoip: "/geoip/GeoLite2-City.mmdb" geoip: "/geoip/GeoLite2-City.mmdb"
asn: "/geoip/GeoLite2-ASN.mmdb" asn: "/geoip/GeoLite2-ASN.mmdb"
compliance:
fips:
enabled: false
cert_discovery_dir: /certs cert_discovery_dir: /certs

View File

@ -7,6 +7,7 @@ from rest_framework.decorators import action
from rest_framework.fields import BooleanField from rest_framework.fields import BooleanField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.events.api.tasks import SystemTaskSerializer from authentik.events.api.tasks import SystemTaskSerializer
@ -54,3 +55,17 @@ class OutgoingSyncProviderStatusMixin:
"is_running": not lock_acquired, "is_running": not lock_acquired,
} }
return Response(SyncStatusSerializer(status).data) return Response(SyncStatusSerializer(status).data)
class OutgoingSyncConnectionCreateMixin:
"""Mixin for connection objects that fetches remote data upon creation"""
def perform_create(self, serializer: ModelSerializer):
super().perform_create(serializer)
try:
instance = serializer.instance
client = instance.provider.client_for_model(instance.__class__)
client.update_single_attribute(instance)
instance.save()
except NotImplementedError:
pass

View File

@ -114,3 +114,8 @@ class BaseOutgoingSyncClient[
pre-link any users/groups in the remote system with the respective pre-link any users/groups in the remote system with the respective
object in authentik based on a common identifier""" object in authentik based on a common identifier"""
raise NotImplementedError() raise NotImplementedError()
def update_single_attribute(self, connection: TConnection):
"""Update connection attributes on a connection object, when the connection
is manually created"""
raise NotImplementedError

View File

@ -6,7 +6,7 @@ from django_filters.filters import ModelMultipleChoiceFilter
from django_filters.filterset import FilterSet from django_filters.filterset import FilterSet
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, DateTimeField from rest_framework.fields import BooleanField, CharField, DateTimeField, SerializerMethodField
from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
@ -18,6 +18,7 @@ from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import JSONDictField, PassiveSerializer from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.models import Provider from authentik.core.models import Provider
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.providers.rac.models import RACProvider from authentik.enterprise.providers.rac.models import RACProvider
from authentik.outposts.api.service_connections import ServiceConnectionSerializer from authentik.outposts.api.service_connections import ServiceConnectionSerializer
from authentik.outposts.apps import MANAGED_OUTPOST, MANAGED_OUTPOST_NAME from authentik.outposts.apps import MANAGED_OUTPOST, MANAGED_OUTPOST_NAME
@ -120,7 +121,7 @@ class OutpostHealthSerializer(PassiveSerializer):
golang_version = CharField(read_only=True) golang_version = CharField(read_only=True)
openssl_enabled = BooleanField(read_only=True) openssl_enabled = BooleanField(read_only=True)
openssl_version = CharField(read_only=True) openssl_version = CharField(read_only=True)
fips_enabled = BooleanField(read_only=True) fips_enabled = SerializerMethodField()
version_should = CharField(read_only=True) version_should = CharField(read_only=True)
version_outdated = BooleanField(read_only=True) version_outdated = BooleanField(read_only=True)
@ -130,6 +131,12 @@ class OutpostHealthSerializer(PassiveSerializer):
hostname = CharField(read_only=True, required=False) hostname = CharField(read_only=True, required=False)
def get_fips_enabled(self, obj: dict) -> bool | None:
"""Get FIPS enabled"""
if not LicenseKey.get_total().is_valid():
return None
return obj["fips_enabled"]
class OutpostFilter(FilterSet): class OutpostFilter(FilterSet):
"""Filter for Outposts""" """Filter for Outposts"""

View File

@ -2,8 +2,6 @@
from authentik.blueprints.apps import ManagedAppConfig from authentik.blueprints.apps import ManagedAppConfig
CACHE_KEY_PREFIX = "goauthentik.io/policies/reputation/scores/"
class AuthentikPolicyReputationConfig(ManagedAppConfig): class AuthentikPolicyReputationConfig(ManagedAppConfig):
"""Authentik reputation app config""" """Authentik reputation app config"""

View File

@ -0,0 +1,25 @@
# Generated by Django 5.0.6 on 2024-06-11 08:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_reputation", "0006_reputation_ip_asn_data"),
]
operations = [
migrations.AddIndex(
model_name="reputation",
index=models.Index(fields=["identifier"], name="authentik_p_identif_9434d7_idx"),
),
migrations.AddIndex(
model_name="reputation",
index=models.Index(fields=["ip"], name="authentik_p_ip_7ad0df_idx"),
),
migrations.AddIndex(
model_name="reputation",
index=models.Index(fields=["ip", "identifier"], name="authentik_p_ip_d779aa_idx"),
),
]

View File

@ -96,3 +96,8 @@ class Reputation(ExpiringModel, SerializerModel):
verbose_name = _("Reputation Score") verbose_name = _("Reputation Score")
verbose_name_plural = _("Reputation Scores") verbose_name_plural = _("Reputation Scores")
unique_together = ("identifier", "ip") unique_together = ("identifier", "ip")
indexes = [
models.Index(fields=["identifier"]),
models.Index(fields=["ip"]),
models.Index(fields=["ip", "identifier"]),
]

View File

@ -1,11 +0,0 @@
"""Reputation Settings"""
from celery.schedules import crontab
CELERY_BEAT_SCHEDULE = {
"policies_reputation_save": {
"task": "authentik.policies.reputation.tasks.save_reputation",
"schedule": crontab(minute="1-59/5"),
"options": {"queue": "authentik_scheduled"},
},
}

View File

@ -1,40 +1,35 @@
"""authentik reputation request signals""" """authentik reputation request signals"""
from django.contrib.auth.signals import user_logged_in from django.contrib.auth.signals import user_logged_in
from django.core.cache import cache
from django.dispatch import receiver from django.dispatch import receiver
from django.http import HttpRequest from django.http import HttpRequest
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.core.signals import login_failed from authentik.core.signals import login_failed
from authentik.lib.config import CONFIG from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
from authentik.policies.reputation.apps import CACHE_KEY_PREFIX from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
from authentik.policies.reputation.tasks import save_reputation from authentik.policies.reputation.models import Reputation, reputation_expiry
from authentik.root.middleware import ClientIPMiddleware from authentik.root.middleware import ClientIPMiddleware
from authentik.stages.identification.signals import identification_failed from authentik.stages.identification.signals import identification_failed
LOGGER = get_logger() LOGGER = get_logger()
CACHE_TIMEOUT = CONFIG.get_int("cache.timeout_reputation")
def update_score(request: HttpRequest, identifier: str, amount: int): def update_score(request: HttpRequest, identifier: str, amount: int):
"""Update score for IP and User""" """Update score for IP and User"""
remote_ip = ClientIPMiddleware.get_client_ip(request) remote_ip = ClientIPMiddleware.get_client_ip(request)
try: Reputation.objects.update_or_create(
# We only update the cache here, as its faster than writing to the DB ip=remote_ip,
score = cache.get_or_set( identifier=identifier,
CACHE_KEY_PREFIX + remote_ip + "/" + identifier, defaults={
{"ip": remote_ip, "identifier": identifier, "score": 0}, "score": amount,
CACHE_TIMEOUT, "ip_geo_data": GEOIP_CONTEXT_PROCESSOR.city_dict(remote_ip) or {},
) "ip_asn_data": ASN_CONTEXT_PROCESSOR.asn_dict(remote_ip) or {},
score["score"] += amount "expires": reputation_expiry(),
cache.set(CACHE_KEY_PREFIX + remote_ip + "/" + identifier, score) },
except ValueError as exc: )
LOGGER.warning("failed to set reputation", exc=exc)
LOGGER.debug("Updated score", amount=amount, for_user=identifier, for_ip=remote_ip) LOGGER.debug("Updated score", amount=amount, for_user=identifier, for_ip=remote_ip)
save_reputation.delay()
@receiver(login_failed) @receiver(login_failed)

View File

@ -1,32 +0,0 @@
"""Reputation tasks"""
from django.core.cache import cache
from structlog.stdlib import get_logger
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
from authentik.events.models import TaskStatus
from authentik.events.system_tasks import SystemTask, prefill_task
from authentik.policies.reputation.apps import CACHE_KEY_PREFIX
from authentik.policies.reputation.models import Reputation
from authentik.root.celery import CELERY_APP
LOGGER = get_logger()
@CELERY_APP.task(bind=True, base=SystemTask)
@prefill_task
def save_reputation(self: SystemTask):
"""Save currently cached reputation to database"""
objects_to_update = []
for _, score in cache.get_many(cache.keys(CACHE_KEY_PREFIX + "*")).items():
rep, _ = Reputation.objects.get_or_create(
ip=score["ip"],
identifier=score["identifier"],
)
rep.ip_geo_data = GEOIP_CONTEXT_PROCESSOR.city_dict(score["ip"]) or {}
rep.ip_asn_data = ASN_CONTEXT_PROCESSOR.asn_dict(score["ip"]) or {}
rep.score = score["score"]
objects_to_update.append(rep)
Reputation.objects.bulk_update(objects_to_update, ["score", "ip_geo_data"])
self.set_status(TaskStatus.SUCCESSFUL, "Successfully updated Reputation")

View File

@ -1,14 +1,11 @@
"""test reputation signals and policy""" """test reputation signals and policy"""
from django.core.cache import cache
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
from authentik.core.models import User from authentik.core.models import User
from authentik.lib.generators import generate_id from authentik.lib.generators import generate_id
from authentik.policies.reputation.api import ReputationPolicySerializer from authentik.policies.reputation.api import ReputationPolicySerializer
from authentik.policies.reputation.apps import CACHE_KEY_PREFIX
from authentik.policies.reputation.models import Reputation, ReputationPolicy from authentik.policies.reputation.models import Reputation, ReputationPolicy
from authentik.policies.reputation.tasks import save_reputation
from authentik.policies.types import PolicyRequest from authentik.policies.types import PolicyRequest
from authentik.stages.password import BACKEND_INBUILT from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.stage import authenticate from authentik.stages.password.stage import authenticate
@ -22,8 +19,6 @@ class TestReputationPolicy(TestCase):
self.request = self.request_factory.get("/") self.request = self.request_factory.get("/")
self.test_ip = "127.0.0.1" self.test_ip = "127.0.0.1"
self.test_username = "test" self.test_username = "test"
keys = cache.keys(CACHE_KEY_PREFIX + "*")
cache.delete_many(keys)
# We need a user for the one-to-one in userreputation # We need a user for the one-to-one in userreputation
self.user = User.objects.create(username=self.test_username) self.user = User.objects.create(username=self.test_username)
self.backends = [BACKEND_INBUILT] self.backends = [BACKEND_INBUILT]
@ -34,13 +29,6 @@ class TestReputationPolicy(TestCase):
authenticate( authenticate(
self.request, self.backends, username=self.test_username, password=self.test_username self.request, self.backends, username=self.test_username, password=self.test_username
) )
# Test value in cache
self.assertEqual(
cache.get(CACHE_KEY_PREFIX + self.test_ip + "/" + self.test_username),
{"ip": "127.0.0.1", "identifier": "test", "score": -1},
)
# Save cache and check db values
save_reputation.delay().get()
self.assertEqual(Reputation.objects.get(ip=self.test_ip).score, -1) self.assertEqual(Reputation.objects.get(ip=self.test_ip).score, -1)
def test_user_reputation(self): def test_user_reputation(self):
@ -49,13 +37,6 @@ class TestReputationPolicy(TestCase):
authenticate( authenticate(
self.request, self.backends, username=self.test_username, password=self.test_username self.request, self.backends, username=self.test_username, password=self.test_username
) )
# Test value in cache
self.assertEqual(
cache.get(CACHE_KEY_PREFIX + self.test_ip + "/" + self.test_username),
{"ip": "127.0.0.1", "identifier": "test", "score": -1},
)
# Save cache and check db values
save_reputation.delay().get()
self.assertEqual(Reputation.objects.get(identifier=self.test_username).score, -1) self.assertEqual(Reputation.objects.get(identifier=self.test_username).score, -1)
def test_policy(self): def test_policy(self):

View File

@ -6,6 +6,7 @@ from rest_framework.viewsets import GenericViewSet
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.users import UserGroupSerializer from authentik.core.api.users import UserGroupSerializer
from authentik.lib.sync.outgoing.api import OutgoingSyncConnectionCreateMixin
from authentik.providers.scim.models import SCIMProviderGroup from authentik.providers.scim.models import SCIMProviderGroup
@ -28,6 +29,7 @@ class SCIMProviderGroupSerializer(ModelSerializer):
class SCIMProviderGroupViewSet( class SCIMProviderGroupViewSet(
mixins.CreateModelMixin, mixins.CreateModelMixin,
OutgoingSyncConnectionCreateMixin,
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin, UsedByMixin,

View File

@ -6,6 +6,7 @@ from rest_framework.viewsets import GenericViewSet
from authentik.core.api.groups import GroupMemberSerializer from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.lib.sync.outgoing.api import OutgoingSyncConnectionCreateMixin
from authentik.providers.scim.models import SCIMProviderUser from authentik.providers.scim.models import SCIMProviderUser
@ -28,6 +29,7 @@ class SCIMProviderUserSerializer(ModelSerializer):
class SCIMProviderUserViewSet( class SCIMProviderUserViewSet(
mixins.CreateModelMixin, mixins.CreateModelMixin,
OutgoingSyncConnectionCreateMixin,
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,
mixins.DestroyModelMixin, mixins.DestroyModelMixin,
UsedByMixin, UsedByMixin,

View File

@ -15,6 +15,48 @@ from authentik.lib.sync.outgoing.base import BaseOutgoingSyncClient
from authentik.lib.sync.outgoing.models import OutgoingSyncProvider from authentik.lib.sync.outgoing.models import OutgoingSyncProvider
class SCIMProviderUser(SerializerModel):
"""Mapping of a user and provider to a SCIM user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
scim_id = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
provider = models.ForeignKey("SCIMProvider", on_delete=models.CASCADE)
@property
def serializer(self) -> type[Serializer]:
from authentik.providers.scim.api.users import SCIMProviderUserSerializer
return SCIMProviderUserSerializer
class Meta:
unique_together = (("scim_id", "user", "provider"),)
def __str__(self) -> str:
return f"SCIM Provider User {self.user_id} to {self.provider_id}"
class SCIMProviderGroup(SerializerModel):
"""Mapping of a group and provider to a SCIM user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
scim_id = models.TextField()
group = models.ForeignKey(Group, on_delete=models.CASCADE)
provider = models.ForeignKey("SCIMProvider", on_delete=models.CASCADE)
@property
def serializer(self) -> type[Serializer]:
from authentik.providers.scim.api.groups import SCIMProviderGroupSerializer
return SCIMProviderGroupSerializer
class Meta:
unique_together = (("scim_id", "group", "provider"),)
def __str__(self) -> str:
return f"SCIM Provider Group {self.group_id} to {self.provider_id}"
class SCIMProvider(OutgoingSyncProvider, BackchannelProvider): class SCIMProvider(OutgoingSyncProvider, BackchannelProvider):
"""SCIM 2.0 provider to create users and groups in external applications""" """SCIM 2.0 provider to create users and groups in external applications"""
@ -39,13 +81,13 @@ class SCIMProvider(OutgoingSyncProvider, BackchannelProvider):
return static("authentik/sources/scim.png") return static("authentik/sources/scim.png")
def client_for_model( def client_for_model(
self, model: type[User | Group] self, model: type[User | Group | SCIMProviderUser | SCIMProviderGroup]
) -> BaseOutgoingSyncClient[User | Group, Any, Any, Self]: ) -> BaseOutgoingSyncClient[User | Group, Any, Any, Self]:
if issubclass(model, User): if issubclass(model, User | SCIMProviderUser):
from authentik.providers.scim.clients.users import SCIMUserClient from authentik.providers.scim.clients.users import SCIMUserClient
return SCIMUserClient(self) return SCIMUserClient(self)
if issubclass(model, Group): if issubclass(model, Group | SCIMProviderGroup):
from authentik.providers.scim.clients.groups import SCIMGroupClient from authentik.providers.scim.clients.groups import SCIMGroupClient
return SCIMGroupClient(self) return SCIMGroupClient(self)
@ -105,45 +147,3 @@ class SCIMMapping(PropertyMapping):
class Meta: class Meta:
verbose_name = _("SCIM Mapping") verbose_name = _("SCIM Mapping")
verbose_name_plural = _("SCIM Mappings") verbose_name_plural = _("SCIM Mappings")
class SCIMProviderUser(SerializerModel):
"""Mapping of a user and provider to a SCIM user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
scim_id = models.TextField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
provider = models.ForeignKey(SCIMProvider, on_delete=models.CASCADE)
@property
def serializer(self) -> type[Serializer]:
from authentik.providers.scim.api.users import SCIMProviderUserSerializer
return SCIMProviderUserSerializer
class Meta:
unique_together = (("scim_id", "user", "provider"),)
def __str__(self) -> str:
return f"SCIM Provider User {self.user_id} to {self.provider_id}"
class SCIMProviderGroup(SerializerModel):
"""Mapping of a group and provider to a SCIM user ID"""
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
scim_id = models.TextField()
group = models.ForeignKey(Group, on_delete=models.CASCADE)
provider = models.ForeignKey(SCIMProvider, on_delete=models.CASCADE)
@property
def serializer(self) -> type[Serializer]:
from authentik.providers.scim.api.groups import SCIMProviderGroupSerializer
return SCIMProviderGroupSerializer
class Meta:
unique_together = (("scim_id", "group", "provider"),)
def __str__(self) -> str:
return f"SCIM Provider Group {self.group_id} to {self.provider_id}"

View File

@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema", "$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://goauthentik.io/blueprints/schema.json", "$id": "https://goauthentik.io/blueprints/schema.json",
"type": "object", "type": "object",
"title": "authentik 2024.4.2 Blueprint schema", "title": "authentik 2024.6.0 Blueprint schema",
"required": [ "required": [
"version", "version",
"entries" "entries"

View File

@ -31,7 +31,7 @@ services:
volumes: volumes:
- redis:/data - redis:/data
server: server:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.4.2} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.6.0}
restart: unless-stopped restart: unless-stopped
command: server command: server
environment: environment:
@ -52,7 +52,7 @@ services:
- postgresql - postgresql
- redis - redis
worker: worker:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.4.2} image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.6.0}
restart: unless-stopped restart: unless-stopped
command: worker command: worker
environment: environment:

4
go.mod
View File

@ -5,7 +5,7 @@ go 1.22.2
require ( require (
beryju.io/ldap v0.1.0 beryju.io/ldap v0.1.0
github.com/coreos/go-oidc v2.2.1+incompatible github.com/coreos/go-oidc v2.2.1+incompatible
github.com/getsentry/sentry-go v0.28.0 github.com/getsentry/sentry-go v0.28.1
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.8 github.com/go-ldap/ldap/v3 v3.4.8
github.com/go-openapi/runtime v0.28.0 github.com/go-openapi/runtime v0.28.0
@ -16,7 +16,7 @@ require (
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/gorilla/securecookie v1.1.2 github.com/gorilla/securecookie v1.1.2
github.com/gorilla/sessions v1.2.2 github.com/gorilla/sessions v1.2.2
github.com/gorilla/websocket v1.5.2 github.com/gorilla/websocket v1.5.3
github.com/jellydator/ttlcache/v3 v3.2.0 github.com/jellydator/ttlcache/v3 v3.2.0
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484

8
go.sum
View File

@ -69,8 +69,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsentry/sentry-go v0.28.0 h1:7Rqx9M3ythTKy2J6uZLHmc8Sz9OGgIlseuO1iBX/s0M= github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
github.com/getsentry/sentry-go v0.28.0/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@ -176,8 +176,8 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.2 h1:qoW6V1GT3aZxybsbC6oLnailWnB+qTMVwMreOso9XUw= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.2/go.mod h1:0n9H61RBAcf5/38py2MCYbxzPIY9rOkpvvMT24Rqs30= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=

View File

@ -29,4 +29,4 @@ func UserAgent() string {
return fmt.Sprintf("authentik@%s", FullVersion()) return fmt.Sprintf("authentik@%s", FullVersion())
} }
const VERSION = "2024.4.2" const VERSION = "2024.6.0"

View File

@ -7,7 +7,6 @@ from pathlib import Path
from tempfile import gettempdir from tempfile import gettempdir
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from cryptography.exceptions import InternalError
from cryptography.hazmat.backends.openssl.backend import backend from cryptography.hazmat.backends.openssl.backend import backend
from defusedxml import defuse_stdlib from defusedxml import defuse_stdlib
from prometheus_client.values import MultiProcessValue from prometheus_client.values import MultiProcessValue
@ -30,10 +29,8 @@ if TYPE_CHECKING:
defuse_stdlib() defuse_stdlib()
try: if CONFIG.get_bool("compliance.fips.enabled", False):
backend._enable_fips() backend._enable_fips()
except InternalError:
pass
wait_for_db() wait_for_db()

View File

@ -4,7 +4,7 @@ import os
import sys import sys
import warnings import warnings
from cryptography.exceptions import InternalError from authentik.lib.config import CONFIG
from cryptography.hazmat.backends.openssl.backend import backend from cryptography.hazmat.backends.openssl.backend import backend
from defusedxml import defuse_stdlib from defusedxml import defuse_stdlib
from django.utils.autoreload import DJANGO_AUTORELOAD_ENV from django.utils.autoreload import DJANGO_AUTORELOAD_ENV
@ -24,10 +24,8 @@ warnings.filterwarnings(
defuse_stdlib() defuse_stdlib()
try: if CONFIG.get_bool("compliance.fips.enabled", False):
backend._enable_fips() backend._enable_fips()
except InternalError:
pass
if __name__ == "__main__": if __name__ == "__main__":

12
poetry.lock generated
View File

@ -353,13 +353,13 @@ msal-extensions = ">=0.3.0"
[[package]] [[package]]
name = "bandit" name = "bandit"
version = "1.7.8" version = "1.7.9"
description = "Security oriented static analyser for python code." description = "Security oriented static analyser for python code."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "bandit-1.7.8-py3-none-any.whl", hash = "sha256:509f7af645bc0cd8fd4587abc1a038fc795636671ee8204d502b933aee44f381"}, {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"},
{file = "bandit-1.7.8.tar.gz", hash = "sha256:36de50f720856ab24a24dbaa5fee2c66050ed97c1477e0a1159deab1775eab6b"}, {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"},
] ]
[package.dependencies] [package.dependencies]
@ -3376,13 +3376,13 @@ files = [
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.7.3" version = "2.7.4"
description = "Data validation using Python type hints" description = "Data validation using Python type hints"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "pydantic-2.7.3-py3-none-any.whl", hash = "sha256:ea91b002777bf643bb20dd717c028ec43216b24a6001a280f83877fd2655d0b4"}, {file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"},
{file = "pydantic-2.7.3.tar.gz", hash = "sha256:c46c76a40bb1296728d7a8b99aa73dd70a48c3510111ff290034f860c99c419e"}, {file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"},
] ]
[package.dependencies] [package.dependencies]

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "authentik" name = "authentik"
version = "2024.4.2" version = "2024.6.0"
description = "" description = ""
authors = ["authentik Team <hello@goauthentik.io>"] authors = ["authentik Team <hello@goauthentik.io>"]

View File

@ -1,7 +1,7 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: authentik title: authentik
version: 2024.4.2 version: 2024.6.0
description: Making authentication simple. description: Making authentication simple.
contact: contact:
email: hello@goauthentik.io email: hello@goauthentik.io
@ -39547,6 +39547,8 @@ components:
readOnly: true readOnly: true
fips_enabled: fips_enabled:
type: boolean type: boolean
nullable: true
description: Get FIPS enabled
readOnly: true readOnly: true
version_should: version_should:
type: string type: string
@ -47404,15 +47406,16 @@ components:
type: string type: string
openssl_version: openssl_version:
type: string type: string
openssl_fips_mode: openssl_fips_enabled:
type: boolean type: boolean
nullable: true
authentik_version: authentik_version:
type: string type: string
required: required:
- architecture - architecture
- authentik_version - authentik_version
- environment - environment
- openssl_fips_mode - openssl_fips_enabled
- openssl_version - openssl_version
- platform - platform
- python_version - python_version

1086
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@
"build-proxy": "run-s build-locales esbuild:build-proxy", "build-proxy": "run-s build-locales esbuild:build-proxy",
"watch": "run-s build-locales esbuild:watch", "watch": "run-s build-locales esbuild:watch",
"lint": "cross-env NODE_OPTIONS='--max_old_space_size=65536' eslint . --max-warnings 0 --fix", "lint": "cross-env NODE_OPTIONS='--max_old_space_size=65536' eslint . --max-warnings 0 --fix",
"lint:precommit": "cross-env NODE_OPTIONS='--max_old_space_size=65536' node scripts/eslint-precommit.mjs", "lint:precommit": "bun scripts/eslint-precommit.mjs",
"lint:spelling": "node scripts/check-spelling.mjs", "lint:spelling": "node scripts/check-spelling.mjs",
"lit-analyse": "lit-analyzer src", "lit-analyse": "lit-analyzer src",
"precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier", "precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier",
@ -38,7 +38,7 @@
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.7", "@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.5.2", "@fortawesome/fontawesome-free": "^6.5.2",
"@goauthentik/api": "^2024.4.2-1717645682", "@goauthentik/api": "^2024.4.2-1718378698",
"@lit-labs/task": "^3.1.0", "@lit-labs/task": "^3.1.0",
"@lit/context": "^1.1.2", "@lit/context": "^1.1.2",
"@lit/localize": "^0.12.1", "@lit/localize": "^0.12.1",
@ -46,7 +46,7 @@
"@open-wc/lit-helpers": "^0.7.0", "@open-wc/lit-helpers": "^0.7.0",
"@patternfly/elements": "^3.0.1", "@patternfly/elements": "^3.0.1",
"@patternfly/patternfly": "^4.224.2", "@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^8.9.1", "@sentry/browser": "^8.9.2",
"@webcomponents/webcomponentsjs": "^2.8.0", "@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"chart.js": "^4.4.3", "chart.js": "^4.4.3",
@ -63,7 +63,7 @@
"rapidoc": "^9.3.4", "rapidoc": "^9.3.4",
"showdown": "^2.1.0", "showdown": "^2.1.0",
"style-mod": "^4.1.2", "style-mod": "^4.1.2",
"ts-pattern": "^5.1.2", "ts-pattern": "^5.2.0",
"webcomponent-qr-code": "^1.2.0", "webcomponent-qr-code": "^1.2.0",
"yaml": "^2.4.5" "yaml": "^2.4.5"
}, },
@ -80,14 +80,14 @@
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0", "@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
"@lit/localize-tools": "^0.7.2", "@lit/localize-tools": "^0.7.2",
"@rollup/plugin-replace": "^5.0.7", "@rollup/plugin-replace": "^5.0.7",
"@spotlightjs/spotlight": "^1.2.17", "@spotlightjs/spotlight": "^2.0.0",
"@storybook/addon-essentials": "^8.1.6", "@storybook/addon-essentials": "^8.1.9",
"@storybook/addon-links": "^8.1.6", "@storybook/addon-links": "^8.1.9",
"@storybook/api": "^7.6.17", "@storybook/api": "^7.6.17",
"@storybook/blocks": "^8.0.8", "@storybook/blocks": "^8.0.8",
"@storybook/manager-api": "^8.1.6", "@storybook/manager-api": "^8.1.9",
"@storybook/web-components": "^8.1.6", "@storybook/web-components": "^8.1.9",
"@storybook/web-components-vite": "^8.1.6", "@storybook/web-components-vite": "^8.1.9",
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/chart.js": "^2.9.41", "@types/chart.js": "^2.9.41",
"@types/codemirror": "5.60.15", "@types/codemirror": "5.60.15",
@ -117,7 +117,7 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"rollup-plugin-modify": "^3.0.0", "rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0", "rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^8.1.6", "storybook": "^8.1.9",
"storybook-addon-mock": "^5.0.0", "storybook-addon-mock": "^5.0.0",
"ts-lit-plugin": "^2.0.2", "ts-lit-plugin": "^2.0.2",
"tslib": "^2.6.3", "tslib": "^2.6.3",

View File

@ -1,5 +1,6 @@
import "@goauthentik/admin/admin-overview/TopApplicationsTable"; import "@goauthentik/admin/admin-overview/TopApplicationsTable";
import "@goauthentik/admin/admin-overview/cards/AdminStatusCard"; import "@goauthentik/admin/admin-overview/cards/AdminStatusCard";
import "@goauthentik/admin/admin-overview/cards/FipsStatusCard";
import "@goauthentik/admin/admin-overview/cards/RecentEventsCard"; import "@goauthentik/admin/admin-overview/cards/RecentEventsCard";
import "@goauthentik/admin/admin-overview/cards/SystemStatusCard"; import "@goauthentik/admin/admin-overview/cards/SystemStatusCard";
import "@goauthentik/admin/admin-overview/cards/VersionStatusCard"; import "@goauthentik/admin/admin-overview/cards/VersionStatusCard";
@ -10,13 +11,17 @@ import "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
import { VERSION } from "@goauthentik/common/constants"; import { VERSION } from "@goauthentik/common/constants";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider.js";
import "@goauthentik/elements/PageHeader"; import "@goauthentik/elements/PageHeader";
import "@goauthentik/elements/cards/AggregatePromiseCard"; import "@goauthentik/elements/cards/AggregatePromiseCard";
import { paramURL } from "@goauthentik/elements/router/RouterOutlet"; import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
import { msg, str } from "@lit/localize"; import { msg, str } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement, state } from "lit/decorators.js"; import { customElement, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { map } from "lit/directives/map.js";
import { when } from "lit/directives/when.js";
import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFDivider from "@patternfly/patternfly/components/Divider/divider.css"; import PFDivider from "@patternfly/patternfly/components/Divider/divider.css";
@ -33,8 +38,12 @@ export function versionFamily(): string {
return parts.join("."); return parts.join(".");
} }
const AdminOverviewBase = WithLicenseSummary(AKElement);
type Renderer = () => TemplateResult | typeof nothing;
@customElement("ak-admin-overview") @customElement("ak-admin-overview")
export class AdminOverviewPage extends AKElement { export class AdminOverviewPage extends AdminOverviewBase {
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
PFBase, PFBase,
@ -73,6 +82,7 @@ export class AdminOverviewPage extends AKElement {
render(): TemplateResult { render(): TemplateResult {
const name = this.user?.user.name ?? this.user?.user.username; const name = this.user?.user.name ?? this.user?.user.username;
return html`<ak-page-header icon="" header="" description=${msg("General system status")}> return html`<ak-page-header icon="" header="" description=${msg("General system status")}>
<span slot="header"> ${msg(str`Welcome, ${name}.`)} </span> <span slot="header"> ${msg(str`Welcome, ${name}.`)} </span>
</ak-page-header> </ak-page-header>
@ -89,48 +99,7 @@ export class AdminOverviewPage extends AKElement {
.isCenter=${false} .isCenter=${false}
> >
<ul class="pf-c-list"> <ul class="pf-c-list">
<li> ${this.renderActions()}
<a
class="pf-u-mb-xl"
href=${paramURL("/core/applications", {
createForm: true,
})}
>${msg("Create a new application")}</a
>
</li>
<li>
<a class="pf-u-mb-xl" href=${paramURL("/events/log")}
>${msg("Check the logs")}</a
>
</li>
<li>
<a
class="pf-u-mb-xl"
target="_blank"
href="https://goauthentik.io/integrations/"
>${msg("Explore integrations")}<i
class="fas fa-external-link-alt ak-external-link"
></i
></a>
</li>
<li>
<a class="pf-u-mb-xl" href=${paramURL("/identity/users")}
>${msg("Manage users")}</a
>
</li>
<li>
<a
class="pf-u-mb-xl"
target="_blank"
href="https://goauthentik.io/docs/releases/${versionFamily()}#fixed-in-${VERSION.replaceAll(
".",
"",
)}"
>${msg("Check the release notes")}<i
class="fas fa-external-link-alt ak-external-link"
></i
></a>
</li>
</ul> </ul>
</ak-aggregate-card> </ak-aggregate-card>
</div> </div>
@ -153,21 +122,7 @@ export class AdminOverviewPage extends AKElement {
<div class="pf-l-grid__item pf-m-12-col"> <div class="pf-l-grid__item pf-m-12-col">
<hr class="pf-c-divider" /> <hr class="pf-c-divider" />
</div> </div>
<div ${this.renderCards()}
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container"
>
<ak-admin-status-system> </ak-admin-status-system>
</div>
<div
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container"
>
<ak-admin-status-version> </ak-admin-status-version>
</div>
<div
class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-md pf-m-4-col-on-xl card-container"
>
<ak-admin-status-card-workers> </ak-admin-status-card-workers>
</div>
</div> </div>
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl"> <div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl">
<ak-recent-events pageSize="6"></ak-recent-events> <ak-recent-events pageSize="6"></ak-recent-events>
@ -201,4 +156,70 @@ export class AdminOverviewPage extends AKElement {
</div> </div>
</section>`; </section>`;
} }
renderCards() {
const isEnterprise = this.hasEnterpriseLicense;
const classes = {
"card-container": true,
"pf-l-grid__item": true,
"pf-m-6-col": true,
"pf-m-4-col-on-md": !isEnterprise,
"pf-m-4-col-on-xl": !isEnterprise,
"pf-m-3-col-on-md": isEnterprise,
"pf-m-3-col-on-xl": isEnterprise,
};
return html`<div class=${classMap(classes)}>
<ak-admin-status-system> </ak-admin-status-system>
</div>
<div class=${classMap(classes)}>
<ak-admin-status-version> </ak-admin-status-version>
</div>
<div class=${classMap(classes)}>
<ak-admin-status-card-workers> </ak-admin-status-card-workers>
</div>
${isEnterprise
? html` <div class=${classMap(classes)}>
<ak-admin-fips-status-system> </ak-admin-fips-status-system>
</div>`
: nothing} `;
}
renderActions() {
const release = `${versionFamily()}#fixed-in-${VERSION.replaceAll(".", "")}`;
const quickActions: [string, string][] = [
[msg("Create a new application"), paramURL("/core/applications", { createForm: true })],
[msg("Check the logs"), paramURL("/events/log")],
[msg("Explore integrations"), "https://goauthentik.io/integrations/"],
[msg("Manage users"), paramURL("/identity/users")],
[msg("Check the release notes"), `https://goauthentik.io/docs/releases/${release}`],
];
const action = ([label, url]: [string, string]) => {
const isExternal = url.startsWith("https://");
const ex = (truecase: Renderer, falsecase: Renderer) =>
when(isExternal, truecase, falsecase);
const content = html`${label}${ex(
() => html`<i class="fas fa-external-link-alt ak-external-link"></i>`,
() => nothing,
)}`;
return html`<li>
${ex(
() => html`<a href="${url}" class="pf-u-mb-xl" target="_blank">${content}</a>`,
() => html`<a href="${url}" class="pf-u-mb-xl" )>${content}</a>`,
)}
</li>`;
};
return html`${map(quickActions, action)}`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-admin-overview": AdminOverviewPage;
}
} }

View File

@ -0,0 +1,56 @@
import {
AdminStatus,
AdminStatusCard,
} from "@goauthentik/admin/admin-overview/cards/AdminStatusCard";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { AdminApi, SystemInfo } from "@goauthentik/api";
type StatusContent = { icon: string; message: TemplateResult };
@customElement("ak-admin-fips-status-system")
export class FipsStatusCard extends AdminStatusCard<SystemInfo> {
icon = "pf-icon pf-icon-server";
@state()
statusSummary?: string;
async getPrimaryValue(): Promise<SystemInfo> {
return await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
}
setStatus(summary: string, content: StatusContent): Promise<AdminStatus> {
this.statusSummary = summary;
return Promise.resolve<AdminStatus>(content);
}
getStatus(value: SystemInfo): Promise<AdminStatus> {
return value.runtime.opensslFipsEnabled
? this.setStatus(msg("OK"), {
icon: "fa fa-check-circle pf-m-success",
message: html`${msg("FIPS compliance: passing")}`,
})
: this.setStatus(msg("Unverified"), {
icon: "fa fa-info-circle pf-m-warning",
message: html`${msg("FIPS compliance: unverified")}`,
});
}
renderHeader(): TemplateResult {
return html`${msg("FIPS Status")}`;
}
renderValue(): TemplateResult {
return html`${this.statusSummary}`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-admin-fips-status-system": FipsStatusCard;
}
}

View File

@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
export const ERROR_CLASS = "pf-m-danger"; export const ERROR_CLASS = "pf-m-danger";
export const PROGRESS_CLASS = "pf-m-in-progress"; export const PROGRESS_CLASS = "pf-m-in-progress";
export const CURRENT_CLASS = "pf-m-current"; export const CURRENT_CLASS = "pf-m-current";
export const VERSION = "2024.4.2"; export const VERSION = "2024.6.0";
export const TITLE_DEFAULT = "authentik"; export const TITLE_DEFAULT = "authentik";
export const ROUTE_SEPARATOR = ";"; export const ROUTE_SEPARATOR = ";";

View File

@ -6677,6 +6677,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
</trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -6943,6 +6943,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
</trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -6594,6 +6594,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
</trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8789,6 +8789,18 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
<target>Utilisateur(s) du fournisseur SCIM</target> <target>Utilisateur(s) du fournisseur SCIM</target>
</trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8523,6 +8523,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
</trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8367,6 +8367,18 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
</trans-unit> </trans-unit>
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
</trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8793,6 +8793,18 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
<target>Użytkowni(k/cy) SCIM</target> <target>Użytkowni(k/cy) SCIM</target>
</trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8638,4 +8638,16 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
</trans-unit> </trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit>
</body></file></xliff> </body></file></xliff>

View File

@ -6587,6 +6587,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
</trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -5509,6 +5509,18 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
</trans-unit> </trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -8791,6 +8791,18 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
<target>SCIM 用户</target> <target>SCIM 用户</target>
</trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -6635,6 +6635,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
</trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -8484,6 +8484,18 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit> </trans-unit>
<trans-unit id="s146769fb55f1ee50"> <trans-unit id="s146769fb55f1ee50">
<source>SCIM User(s)</source> <source>SCIM User(s)</source>
</trans-unit>
<trans-unit id="s35f47dbd321aaf15">
<source>FIPS compliance: passing</source>
</trans-unit>
<trans-unit id="sc94578030c702562">
<source>Unverified</source>
</trans-unit>
<trans-unit id="s16749cce7c4c1589">
<source>FIPS compliance: unverified</source>
</trans-unit>
<trans-unit id="s0b2ad58c3deaa8dd">
<source>FIPS Status</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

File diff suppressed because it is too large Load Diff

View File

@ -1,54 +0,0 @@
---
title: Release 2024.next
slug: "/releases/2024.next"
---
:::::note
2024.next has not been released yet! We're publishing these release notes as a preview of what's to come, and for our awesome beta testers trying out release candidates.
To try out the release candidate, replace your Docker image tag with the latest release candidate number, such as 2024.next.0-rc1. You can find the latest one in [the latest releases on GitHub](https://github.com/goauthentik/authentik/releases). If you don't find any, it means we haven't released one yet.
:::::
## Breaking changes
### PostgreSQL minimum supported version upgrade
authentik now requires PostgreSQL version 14 or later. We recommend upgrading to the latest version if you are running an older version.
The provided Helm chart defaults to PostgreSQL 15. If you are using the Helm chart with the default values, no action is required.
The provided Compose file was updated with PostgreSQL 16. You can follow the procedure [here](../../troubleshooting/postgres/upgrade_docker.md) to upgrade.
## New features
## Upgrading
authentik now requires PostgreSQL version 14 or later. We recommend upgrading to the latest version if needed. Follow the instructions [here](../../troubleshooting/postgres/upgrade_docker.md) if you need to upgrade PostgreSQL with docker-compose.
### Docker Compose
To upgrade, download the new Compose file and update the Docker stack with the new version, using these commands:
```shell
wget -O docker-compose.yml https://goauthentik.io/version/2024.next/docker-compose.yml
docker compose up -d
```
The `-O` flag retains the downloaded file's name, overwriting any existing local file with the same name.
### Kubernetes
Upgrade the Helm Chart to the new version, using the following commands:
```shell
helm repo update
helm upgrade authentik authentik/authentik -f values.yaml --version ^2024.next
```
## Minor changes/fixes
<!-- _Insert the output of `make gen-changelog` here_ -->
## API Changes
<!-- _Insert output of `make gen-diff` here_ -->

View File

@ -409,16 +409,17 @@ const docsSidebar = {
type: "generated-index", type: "generated-index",
title: "Releases", title: "Releases",
slug: "releases", slug: "releases",
description: "Release notes for recent authentik versions", description: "Release Notes for recent authentik versions",
}, },
items: [ items: [
"releases/2024/v2024.6",
"releases/2024/v2024.4", "releases/2024/v2024.4",
"releases/2024/v2024.2", "releases/2024/v2024.2",
"releases/2023/v2023.10",
{ {
type: "category", type: "category",
label: "Previous versions", label: "Previous versions",
items: [ items: [
"releases/2023/v2023.10",
"releases/2023/v2023.8", "releases/2023/v2023.8",
"releases/2023/v2023.6", "releases/2023/v2023.6",
"releases/2023/v2023.5", "releases/2023/v2023.5",