Merge branch 'main' into dev
* main: web/user: fix opening application with Enter not respecting new tab setting (#13115) web: bump API Client version (#13113) providers/rac: move to open source (#13015) website/docs: add 2025.2 release notes (#13002) core: clear expired database sessions (#13105) core: bump sentry-sdk from 2.21.0 to 2.22.0 (#13098) core: bump bandit from 1.8.2 to 1.8.3 (#13097) core: bump aws-cdk-lib from 2.178.2 to 2.179.0 (#13099) core: bump goauthentik.io/api/v3 from 3.2024123.4 to 3.2024123.6 (#13100) lifecycle/aws: bump aws-cdk from 2.178.2 to 2.179.0 in /lifecycle/aws (#13101) website/docs: Add AdventureLog Community Integration Documentation (#12928) website/docs: minor fixes (#13095) website/integrations: Update to Wizard and Styling Guide (#12919) web: bump API Client version (#13093) policies/geoip: distance + impossible travel (#12541) root: fix generated API docs not being excluded from codespell (#13091)
This commit is contained in:
		
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -21,7 +21,7 @@ pg_name := $(shell python -m authentik.lib.config postgresql.name 2>/dev/null) | |||||||
| CODESPELL_ARGS = -D - -D .github/codespell-dictionary.txt \ | CODESPELL_ARGS = -D - -D .github/codespell-dictionary.txt \ | ||||||
| 		-I .github/codespell-words.txt \ | 		-I .github/codespell-words.txt \ | ||||||
| 		-S 'web/src/locales/**' \ | 		-S 'web/src/locales/**' \ | ||||||
| 		-S 'website/developer-docs/api/reference/**' \ | 		-S 'website/docs/developer-docs/api/reference/**' \ | ||||||
| 		-S '**/node_modules/**' \ | 		-S '**/node_modules/**' \ | ||||||
| 		-S '**/dist/**' \ | 		-S '**/dist/**' \ | ||||||
| 		$(PY_SOURCES) \ | 		$(PY_SOURCES) \ | ||||||
|  | |||||||
| @ -50,7 +50,6 @@ from authentik.enterprise.providers.microsoft_entra.models import ( | |||||||
|     MicrosoftEntraProviderGroup, |     MicrosoftEntraProviderGroup, | ||||||
|     MicrosoftEntraProviderUser, |     MicrosoftEntraProviderUser, | ||||||
| ) | ) | ||||||
| from authentik.enterprise.providers.rac.models import ConnectionToken |  | ||||||
| from authentik.enterprise.providers.ssf.models import StreamEvent | from authentik.enterprise.providers.ssf.models import StreamEvent | ||||||
| from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import ( | from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import ( | ||||||
|     EndpointDevice, |     EndpointDevice, | ||||||
| @ -72,6 +71,7 @@ from authentik.providers.oauth2.models import ( | |||||||
|     DeviceToken, |     DeviceToken, | ||||||
|     RefreshToken, |     RefreshToken, | ||||||
| ) | ) | ||||||
|  | from authentik.providers.rac.models import ConnectionToken | ||||||
| from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser | from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser | ||||||
| from authentik.rbac.models import Role | from authentik.rbac.models import Role | ||||||
| from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser | from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser | ||||||
|  | |||||||
| @ -67,6 +67,8 @@ def clean_expired_models(self: SystemTask): | |||||||
|                 raise ImproperlyConfigured( |                 raise ImproperlyConfigured( | ||||||
|                     "Invalid session_storage setting, allowed values are db and cache" |                     "Invalid session_storage setting, allowed values are db and cache" | ||||||
|                 ) |                 ) | ||||||
|  |     if CONFIG.get("session_storage", "cache") == "db": | ||||||
|  |         DBSessionStore.clear_expired() | ||||||
|     LOGGER.debug("Expired sessions", model=AuthenticatedSession, amount=amount) |     LOGGER.debug("Expired sessions", model=AuthenticatedSession, amount=amount) | ||||||
|  |  | ||||||
|     messages.append(f"Expired {amount} {AuthenticatedSession._meta.verbose_name_plural}") |     messages.append(f"Expired {amount} {AuthenticatedSession._meta.verbose_name_plural}") | ||||||
|  | |||||||
| @ -1,14 +0,0 @@ | |||||||
| """RAC app config""" |  | ||||||
|  |  | ||||||
| from authentik.enterprise.apps import EnterpriseConfig |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AuthentikEnterpriseProviderRAC(EnterpriseConfig): |  | ||||||
|     """authentik enterprise rac app config""" |  | ||||||
|  |  | ||||||
|     name = "authentik.enterprise.providers.rac" |  | ||||||
|     label = "authentik_providers_rac" |  | ||||||
|     verbose_name = "authentik Enterprise.Providers.RAC" |  | ||||||
|     default = True |  | ||||||
|     mountpoint = "" |  | ||||||
|     ws_mountpoint = "authentik.enterprise.providers.rac.urls" |  | ||||||
| @ -16,7 +16,6 @@ TENANT_APPS = [ | |||||||
|     "authentik.enterprise.audit", |     "authentik.enterprise.audit", | ||||||
|     "authentik.enterprise.providers.google_workspace", |     "authentik.enterprise.providers.google_workspace", | ||||||
|     "authentik.enterprise.providers.microsoft_entra", |     "authentik.enterprise.providers.microsoft_entra", | ||||||
|     "authentik.enterprise.providers.rac", |  | ||||||
|     "authentik.enterprise.providers.ssf", |     "authentik.enterprise.providers.ssf", | ||||||
|     "authentik.enterprise.stages.authenticator_endpoint_gdtc", |     "authentik.enterprise.stages.authenticator_endpoint_gdtc", | ||||||
|     "authentik.enterprise.stages.source", |     "authentik.enterprise.stages.source", | ||||||
|  | |||||||
| @ -19,7 +19,6 @@ from authentik.core.api.used_by import UsedByMixin | |||||||
| from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer | from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer | ||||||
| from authentik.core.models import Provider | from authentik.core.models import Provider | ||||||
| from authentik.enterprise.license import LicenseKey | from authentik.enterprise.license import LicenseKey | ||||||
| from authentik.enterprise.providers.rac.models import RACProvider |  | ||||||
| from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator | from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator | ||||||
| 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 | ||||||
| @ -31,6 +30,7 @@ from authentik.outposts.models import ( | |||||||
| ) | ) | ||||||
| from authentik.providers.ldap.models import LDAPProvider | from authentik.providers.ldap.models import LDAPProvider | ||||||
| from authentik.providers.proxy.models import ProxyProvider | from authentik.providers.proxy.models import ProxyProvider | ||||||
|  | from authentik.providers.rac.models import RACProvider | ||||||
| from authentik.providers.radius.models import RadiusProvider | from authentik.providers.radius.models import RadiusProvider | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -18,8 +18,6 @@ from kubernetes.config.kube_config import KUBE_CONFIG_DEFAULT_LOCATION | |||||||
| from structlog.stdlib import get_logger | from structlog.stdlib import get_logger | ||||||
| from yaml import safe_load | from yaml import safe_load | ||||||
|  |  | ||||||
| from authentik.enterprise.providers.rac.controllers.docker import RACDockerController |  | ||||||
| from authentik.enterprise.providers.rac.controllers.kubernetes import RACKubernetesController |  | ||||||
| from authentik.events.models import TaskStatus | from authentik.events.models import TaskStatus | ||||||
| from authentik.events.system_tasks import SystemTask, prefill_task | from authentik.events.system_tasks import SystemTask, prefill_task | ||||||
| from authentik.lib.config import CONFIG | from authentik.lib.config import CONFIG | ||||||
| @ -41,6 +39,8 @@ from authentik.providers.ldap.controllers.docker import LDAPDockerController | |||||||
| from authentik.providers.ldap.controllers.kubernetes import LDAPKubernetesController | from authentik.providers.ldap.controllers.kubernetes import LDAPKubernetesController | ||||||
| from authentik.providers.proxy.controllers.docker import ProxyDockerController | from authentik.providers.proxy.controllers.docker import ProxyDockerController | ||||||
| from authentik.providers.proxy.controllers.kubernetes import ProxyKubernetesController | from authentik.providers.proxy.controllers.kubernetes import ProxyKubernetesController | ||||||
|  | from authentik.providers.rac.controllers.docker import RACDockerController | ||||||
|  | from authentik.providers.rac.controllers.kubernetes import RACKubernetesController | ||||||
| from authentik.providers.radius.controllers.docker import RadiusDockerController | from authentik.providers.radius.controllers.docker import RadiusDockerController | ||||||
| from authentik.providers.radius.controllers.kubernetes import RadiusKubernetesController | from authentik.providers.radius.controllers.kubernetes import RadiusKubernetesController | ||||||
| from authentik.root.celery import CELERY_APP | from authentik.root.celery import CELERY_APP | ||||||
|  | |||||||
| @ -42,6 +42,12 @@ class GeoIPPolicySerializer(CountryFieldMixin, PolicySerializer): | |||||||
|             "asns", |             "asns", | ||||||
|             "countries", |             "countries", | ||||||
|             "countries_obj", |             "countries_obj", | ||||||
|  |             "check_history_distance", | ||||||
|  |             "history_max_distance_km", | ||||||
|  |             "distance_tolerance_km", | ||||||
|  |             "history_login_count", | ||||||
|  |             "check_impossible_travel", | ||||||
|  |             "impossible_tolerance_km", | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -0,0 +1,43 @@ | |||||||
|  | # Generated by Django 5.0.10 on 2025-01-02 20:40 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("authentik_policies_geoip", "0001_initial"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="geoippolicy", | ||||||
|  |             name="check_history_distance", | ||||||
|  |             field=models.BooleanField(default=False), | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="geoippolicy", | ||||||
|  |             name="check_impossible_travel", | ||||||
|  |             field=models.BooleanField(default=False), | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="geoippolicy", | ||||||
|  |             name="distance_tolerance_km", | ||||||
|  |             field=models.PositiveIntegerField(default=50), | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="geoippolicy", | ||||||
|  |             name="history_login_count", | ||||||
|  |             field=models.PositiveIntegerField(default=5), | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="geoippolicy", | ||||||
|  |             name="history_max_distance_km", | ||||||
|  |             field=models.PositiveBigIntegerField(default=100), | ||||||
|  |         ), | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="geoippolicy", | ||||||
|  |             name="impossible_tolerance_km", | ||||||
|  |             field=models.PositiveIntegerField(default=100), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @ -4,15 +4,21 @@ from itertools import chain | |||||||
|  |  | ||||||
| from django.contrib.postgres.fields import ArrayField | from django.contrib.postgres.fields import ArrayField | ||||||
| from django.db import models | from django.db import models | ||||||
|  | from django.utils.timezone import now | ||||||
| from django.utils.translation import gettext as _ | from django.utils.translation import gettext as _ | ||||||
| from django_countries.fields import CountryField | from django_countries.fields import CountryField | ||||||
|  | from geopy import distance | ||||||
| from rest_framework.serializers import BaseSerializer | from rest_framework.serializers import BaseSerializer | ||||||
|  |  | ||||||
|  | from authentik.events.context_processors.geoip import GeoIPDict | ||||||
|  | from authentik.events.models import Event, EventAction | ||||||
| from authentik.policies.exceptions import PolicyException | from authentik.policies.exceptions import PolicyException | ||||||
| from authentik.policies.geoip.exceptions import GeoIPNotFoundException | from authentik.policies.geoip.exceptions import GeoIPNotFoundException | ||||||
| from authentik.policies.models import Policy | from authentik.policies.models import Policy | ||||||
| from authentik.policies.types import PolicyRequest, PolicyResult | from authentik.policies.types import PolicyRequest, PolicyResult | ||||||
|  |  | ||||||
|  | MAX_DISTANCE_HOUR_KM = 1000 | ||||||
|  |  | ||||||
|  |  | ||||||
| class GeoIPPolicy(Policy): | class GeoIPPolicy(Policy): | ||||||
|     """Ensure the user satisfies requirements of geography or network topology, based on IP |     """Ensure the user satisfies requirements of geography or network topology, based on IP | ||||||
| @ -21,6 +27,15 @@ class GeoIPPolicy(Policy): | |||||||
|     asns = ArrayField(models.IntegerField(), blank=True, default=list) |     asns = ArrayField(models.IntegerField(), blank=True, default=list) | ||||||
|     countries = CountryField(multiple=True, blank=True) |     countries = CountryField(multiple=True, blank=True) | ||||||
|  |  | ||||||
|  |     distance_tolerance_km = models.PositiveIntegerField(default=50) | ||||||
|  |  | ||||||
|  |     check_history_distance = models.BooleanField(default=False) | ||||||
|  |     history_max_distance_km = models.PositiveBigIntegerField(default=100) | ||||||
|  |     history_login_count = models.PositiveIntegerField(default=5) | ||||||
|  |  | ||||||
|  |     check_impossible_travel = models.BooleanField(default=False) | ||||||
|  |     impossible_tolerance_km = models.PositiveIntegerField(default=100) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def serializer(self) -> type[BaseSerializer]: |     def serializer(self) -> type[BaseSerializer]: | ||||||
|         from authentik.policies.geoip.api import GeoIPPolicySerializer |         from authentik.policies.geoip.api import GeoIPPolicySerializer | ||||||
| @ -37,21 +52,27 @@ class GeoIPPolicy(Policy): | |||||||
|         - the client IP is advertised by an autonomous system with ASN in the `asns` |         - the client IP is advertised by an autonomous system with ASN in the `asns` | ||||||
|         - the client IP is geolocated in a country of `countries` |         - the client IP is geolocated in a country of `countries` | ||||||
|         """ |         """ | ||||||
|         results: list[PolicyResult] = [] |         static_results: list[PolicyResult] = [] | ||||||
|  |         dynamic_results: list[PolicyResult] = [] | ||||||
|  |  | ||||||
|         if self.asns: |         if self.asns: | ||||||
|             results.append(self.passes_asn(request)) |             static_results.append(self.passes_asn(request)) | ||||||
|         if self.countries: |         if self.countries: | ||||||
|             results.append(self.passes_country(request)) |             static_results.append(self.passes_country(request)) | ||||||
|  |  | ||||||
|         if not results: |         if self.check_history_distance or self.check_impossible_travel: | ||||||
|  |             dynamic_results.append(self.passes_distance(request)) | ||||||
|  |  | ||||||
|  |         if not static_results and not dynamic_results: | ||||||
|             return PolicyResult(True) |             return PolicyResult(True) | ||||||
|  |  | ||||||
|         passing = any(r.passing for r in results) |         passing = any(r.passing for r in static_results) and all(r.passing for r in dynamic_results) | ||||||
|         messages = chain(*[r.messages for r in results]) |         messages = chain( | ||||||
|  |             *[r.messages for r in static_results], *[r.messages for r in dynamic_results] | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         result = PolicyResult(passing, *messages) |         result = PolicyResult(passing, *messages) | ||||||
|         result.source_results = results |         result.source_results = list(chain(static_results, dynamic_results)) | ||||||
|  |  | ||||||
|         return result |         return result | ||||||
|  |  | ||||||
| @ -73,7 +94,7 @@ class GeoIPPolicy(Policy): | |||||||
|  |  | ||||||
|     def passes_country(self, request: PolicyRequest) -> PolicyResult: |     def passes_country(self, request: PolicyRequest) -> PolicyResult: | ||||||
|         # This is not a single get chain because `request.context` can contain `{ "geoip": None }`. |         # This is not a single get chain because `request.context` can contain `{ "geoip": None }`. | ||||||
|         geoip_data = request.context.get("geoip") |         geoip_data: GeoIPDict | None = request.context.get("geoip") | ||||||
|         country = geoip_data.get("country") if geoip_data else None |         country = geoip_data.get("country") if geoip_data else None | ||||||
|  |  | ||||||
|         if not country: |         if not country: | ||||||
| @ -87,6 +108,42 @@ class GeoIPPolicy(Policy): | |||||||
|  |  | ||||||
|         return PolicyResult(True) |         return PolicyResult(True) | ||||||
|  |  | ||||||
|  |     def passes_distance(self, request: PolicyRequest) -> PolicyResult: | ||||||
|  |         """Check if current policy execution is out of distance range compared | ||||||
|  |         to previous authentication requests""" | ||||||
|  |         # Get previous login event and GeoIP data | ||||||
|  |         previous_logins = Event.objects.filter( | ||||||
|  |             action=EventAction.LOGIN, user__pk=request.user.pk, context__geo__isnull=False | ||||||
|  |         ).order_by("-created")[: self.history_login_count] | ||||||
|  |         _now = now() | ||||||
|  |         geoip_data: GeoIPDict | None = request.context.get("geoip") | ||||||
|  |         if not geoip_data: | ||||||
|  |             return PolicyResult(False) | ||||||
|  |         for previous_login in previous_logins: | ||||||
|  |             previous_login_geoip: GeoIPDict = previous_login.context["geo"] | ||||||
|  |  | ||||||
|  |             # Figure out distance | ||||||
|  |             dist = distance.geodesic( | ||||||
|  |                 (previous_login_geoip["lat"], previous_login_geoip["long"]), | ||||||
|  |                 (geoip_data["lat"], geoip_data["long"]), | ||||||
|  |             ) | ||||||
|  |             if self.check_history_distance and dist.km >= ( | ||||||
|  |                 self.history_max_distance_km - self.distance_tolerance_km | ||||||
|  |             ): | ||||||
|  |                 return PolicyResult( | ||||||
|  |                     False, _("Distance from previous authentication is larger than threshold.") | ||||||
|  |                 ) | ||||||
|  |             # Check if distance between `previous_login` and now is more | ||||||
|  |             # than max distance per hour times the amount of hours since the previous login | ||||||
|  |             # (round down to the lowest closest time of hours) | ||||||
|  |             # clamped to be at least 1 hour | ||||||
|  |             rel_time_hours = max(int((_now - previous_login.created).total_seconds() / 3600), 1) | ||||||
|  |             if self.check_impossible_travel and dist.km >= ( | ||||||
|  |                 (MAX_DISTANCE_HOUR_KM * rel_time_hours) - self.distance_tolerance_km | ||||||
|  |             ): | ||||||
|  |                 return PolicyResult(False, _("Distance is further than possible.")) | ||||||
|  |         return PolicyResult(True) | ||||||
|  |  | ||||||
|     class Meta(Policy.PolicyMeta): |     class Meta(Policy.PolicyMeta): | ||||||
|         verbose_name = _("GeoIP Policy") |         verbose_name = _("GeoIP Policy") | ||||||
|         verbose_name_plural = _("GeoIP Policies") |         verbose_name_plural = _("GeoIP Policies") | ||||||
|  | |||||||
| @ -1,8 +1,10 @@ | |||||||
| """geoip policy tests""" | """geoip policy tests""" | ||||||
|  |  | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| from guardian.shortcuts import get_anonymous_user |  | ||||||
|  |  | ||||||
|  | from authentik.core.tests.utils import create_test_user | ||||||
|  | from authentik.events.models import Event, EventAction | ||||||
|  | from authentik.events.utils import get_user | ||||||
| from authentik.policies.engine import PolicyRequest, PolicyResult | from authentik.policies.engine import PolicyRequest, PolicyResult | ||||||
| from authentik.policies.exceptions import PolicyException | from authentik.policies.exceptions import PolicyException | ||||||
| from authentik.policies.geoip.exceptions import GeoIPNotFoundException | from authentik.policies.geoip.exceptions import GeoIPNotFoundException | ||||||
| @ -14,8 +16,8 @@ class TestGeoIPPolicy(TestCase): | |||||||
|  |  | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|         super().setUp() |         super().setUp() | ||||||
|  |         self.user = create_test_user() | ||||||
|         self.request = PolicyRequest(get_anonymous_user()) |         self.request = PolicyRequest(self.user) | ||||||
|  |  | ||||||
|         self.context_disabled_geoip = {} |         self.context_disabled_geoip = {} | ||||||
|         self.context_unknown_ip = {"asn": None, "geoip": None} |         self.context_unknown_ip = {"asn": None, "geoip": None} | ||||||
| @ -126,3 +128,70 @@ class TestGeoIPPolicy(TestCase): | |||||||
|         result: PolicyResult = policy.passes(self.request) |         result: PolicyResult = policy.passes(self.request) | ||||||
|  |  | ||||||
|         self.assertTrue(result.passing) |         self.assertTrue(result.passing) | ||||||
|  |  | ||||||
|  |     def test_history(self): | ||||||
|  |         """Test history checks""" | ||||||
|  |         Event.objects.create( | ||||||
|  |             action=EventAction.LOGIN, | ||||||
|  |             user=get_user(self.user), | ||||||
|  |             context={ | ||||||
|  |                 # Random location in Canada | ||||||
|  |                 "geo": {"lat": 55.868351, "long": -104.441011}, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |         # Random location in Poland | ||||||
|  |         self.request.context["geoip"] = {"lat": 50.950613, "long": 20.363679} | ||||||
|  |  | ||||||
|  |         policy = GeoIPPolicy.objects.create(check_history_distance=True) | ||||||
|  |  | ||||||
|  |         result: PolicyResult = policy.passes(self.request) | ||||||
|  |         self.assertFalse(result.passing) | ||||||
|  |  | ||||||
|  |     def test_history_no_data(self): | ||||||
|  |         """Test history checks (with no geoip data in context)""" | ||||||
|  |         Event.objects.create( | ||||||
|  |             action=EventAction.LOGIN, | ||||||
|  |             user=get_user(self.user), | ||||||
|  |             context={ | ||||||
|  |                 # Random location in Canada | ||||||
|  |                 "geo": {"lat": 55.868351, "long": -104.441011}, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         policy = GeoIPPolicy.objects.create(check_history_distance=True) | ||||||
|  |  | ||||||
|  |         result: PolicyResult = policy.passes(self.request) | ||||||
|  |         self.assertFalse(result.passing) | ||||||
|  |  | ||||||
|  |     def test_history_impossible_travel(self): | ||||||
|  |         """Test history checks""" | ||||||
|  |         Event.objects.create( | ||||||
|  |             action=EventAction.LOGIN, | ||||||
|  |             user=get_user(self.user), | ||||||
|  |             context={ | ||||||
|  |                 # Random location in Canada | ||||||
|  |                 "geo": {"lat": 55.868351, "long": -104.441011}, | ||||||
|  |             }, | ||||||
|  |         ) | ||||||
|  |         # Random location in Poland | ||||||
|  |         self.request.context["geoip"] = {"lat": 50.950613, "long": 20.363679} | ||||||
|  |  | ||||||
|  |         policy = GeoIPPolicy.objects.create(check_impossible_travel=True) | ||||||
|  |  | ||||||
|  |         result: PolicyResult = policy.passes(self.request) | ||||||
|  |         self.assertFalse(result.passing) | ||||||
|  |  | ||||||
|  |     def test_history_no_geoip(self): | ||||||
|  |         """Test history checks (previous login with no geoip data)""" | ||||||
|  |         Event.objects.create( | ||||||
|  |             action=EventAction.LOGIN, | ||||||
|  |             user=get_user(self.user), | ||||||
|  |             context={}, | ||||||
|  |         ) | ||||||
|  |         # Random location in Poland | ||||||
|  |         self.request.context["geoip"] = {"lat": 50.950613, "long": 20.363679} | ||||||
|  |  | ||||||
|  |         policy = GeoIPPolicy.objects.create(check_history_distance=True) | ||||||
|  |  | ||||||
|  |         result: PolicyResult = policy.passes(self.request) | ||||||
|  |         self.assertFalse(result.passing) | ||||||
|  | |||||||
| @ -6,13 +6,12 @@ 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.core.api.utils import ModelSerializer | from authentik.core.api.utils import ModelSerializer | ||||||
| from authentik.enterprise.api import EnterpriseRequiredMixin | from authentik.providers.rac.api.endpoints import EndpointSerializer | ||||||
| from authentik.enterprise.providers.rac.api.endpoints import EndpointSerializer | from authentik.providers.rac.api.providers import RACProviderSerializer | ||||||
| from authentik.enterprise.providers.rac.api.providers import RACProviderSerializer | from authentik.providers.rac.models import ConnectionToken | ||||||
| from authentik.enterprise.providers.rac.models import ConnectionToken |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ConnectionTokenSerializer(EnterpriseRequiredMixin, ModelSerializer): | class ConnectionTokenSerializer(ModelSerializer): | ||||||
|     """ConnectionToken Serializer""" |     """ConnectionToken Serializer""" | ||||||
| 
 | 
 | ||||||
|     provider_obj = RACProviderSerializer(source="provider", read_only=True) |     provider_obj = RACProviderSerializer(source="provider", read_only=True) | ||||||
| @ -14,10 +14,9 @@ from structlog.stdlib import get_logger | |||||||
| from authentik.core.api.used_by import UsedByMixin | from authentik.core.api.used_by import UsedByMixin | ||||||
| from authentik.core.api.utils import ModelSerializer | from authentik.core.api.utils import ModelSerializer | ||||||
| from authentik.core.models import Provider | from authentik.core.models import Provider | ||||||
| from authentik.enterprise.api import EnterpriseRequiredMixin |  | ||||||
| from authentik.enterprise.providers.rac.api.providers import RACProviderSerializer |  | ||||||
| from authentik.enterprise.providers.rac.models import Endpoint |  | ||||||
| from authentik.policies.engine import PolicyEngine | from authentik.policies.engine import PolicyEngine | ||||||
|  | from authentik.providers.rac.api.providers import RACProviderSerializer | ||||||
|  | from authentik.providers.rac.models import Endpoint | ||||||
| from authentik.rbac.filters import ObjectFilter | from authentik.rbac.filters import ObjectFilter | ||||||
| 
 | 
 | ||||||
| LOGGER = get_logger() | LOGGER = get_logger() | ||||||
| @ -28,7 +27,7 @@ def user_endpoint_cache_key(user_pk: str) -> str: | |||||||
|     return f"goauthentik.io/providers/rac/endpoint_access/{user_pk}" |     return f"goauthentik.io/providers/rac/endpoint_access/{user_pk}" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class EndpointSerializer(EnterpriseRequiredMixin, ModelSerializer): | class EndpointSerializer(ModelSerializer): | ||||||
|     """Endpoint Serializer""" |     """Endpoint Serializer""" | ||||||
| 
 | 
 | ||||||
|     provider_obj = RACProviderSerializer(source="provider", read_only=True) |     provider_obj = RACProviderSerializer(source="provider", read_only=True) | ||||||
| @ -10,7 +10,7 @@ from rest_framework.viewsets import ModelViewSet | |||||||
| from authentik.core.api.property_mappings import PropertyMappingSerializer | from authentik.core.api.property_mappings import PropertyMappingSerializer | ||||||
| from authentik.core.api.used_by import UsedByMixin | from authentik.core.api.used_by import UsedByMixin | ||||||
| from authentik.core.api.utils import JSONDictField | from authentik.core.api.utils import JSONDictField | ||||||
| from authentik.enterprise.providers.rac.models import RACPropertyMapping | from authentik.providers.rac.models import RACPropertyMapping | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RACPropertyMappingSerializer(PropertyMappingSerializer): | class RACPropertyMappingSerializer(PropertyMappingSerializer): | ||||||
| @ -5,11 +5,10 @@ from rest_framework.viewsets import ModelViewSet | |||||||
| 
 | 
 | ||||||
| from authentik.core.api.providers import ProviderSerializer | 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.enterprise.api import EnterpriseRequiredMixin | from authentik.providers.rac.models import RACProvider | ||||||
| from authentik.enterprise.providers.rac.models import RACProvider |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RACProviderSerializer(EnterpriseRequiredMixin, ProviderSerializer): | class RACProviderSerializer(ProviderSerializer): | ||||||
|     """RACProvider Serializer""" |     """RACProvider Serializer""" | ||||||
| 
 | 
 | ||||||
|     outpost_set = ListField(child=CharField(), read_only=True, source="outpost_set.all") |     outpost_set = ListField(child=CharField(), read_only=True, source="outpost_set.all") | ||||||
							
								
								
									
										14
									
								
								authentik/providers/rac/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								authentik/providers/rac/apps.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | """RAC app config""" | ||||||
|  |  | ||||||
|  | from django.apps import AppConfig | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AuthentikProviderRAC(AppConfig): | ||||||
|  |     """authentik rac app config""" | ||||||
|  |  | ||||||
|  |     name = "authentik.providers.rac" | ||||||
|  |     label = "authentik_providers_rac" | ||||||
|  |     verbose_name = "authentik Providers.RAC" | ||||||
|  |     default = True | ||||||
|  |     mountpoint = "" | ||||||
|  |     ws_mountpoint = "authentik.providers.rac.urls" | ||||||
| @ -7,22 +7,22 @@ from channels.generic.websocket import AsyncWebsocketConsumer | |||||||
| from django.http.request import QueryDict | from django.http.request import QueryDict | ||||||
| from structlog.stdlib import BoundLogger, get_logger | from structlog.stdlib import BoundLogger, get_logger | ||||||
| 
 | 
 | ||||||
| from authentik.enterprise.providers.rac.models import ConnectionToken, RACProvider |  | ||||||
| from authentik.outposts.consumer import OUTPOST_GROUP_INSTANCE | from authentik.outposts.consumer import OUTPOST_GROUP_INSTANCE | ||||||
| from authentik.outposts.models import Outpost, OutpostState, OutpostType | from authentik.outposts.models import Outpost, OutpostState, OutpostType | ||||||
|  | from authentik.providers.rac.models import ConnectionToken, RACProvider | ||||||
| 
 | 
 | ||||||
| # Global broadcast group, which messages are sent to when the outpost connects back | # Global broadcast group, which messages are sent to when the outpost connects back | ||||||
| # to authentik for a specific connection | # to authentik for a specific connection | ||||||
| # The `RACClientConsumer` consumer adds itself to this group on connection, | # The `RACClientConsumer` consumer adds itself to this group on connection, | ||||||
| # and removes itself once it has been assigned a specific outpost channel | # and removes itself once it has been assigned a specific outpost channel | ||||||
| RAC_CLIENT_GROUP = "group_enterprise_rac_client" | RAC_CLIENT_GROUP = "group_rac_client" | ||||||
| # A group for all connections in a given authentik session ID | # A group for all connections in a given authentik session ID | ||||||
| # A disconnect message is sent to this group when the session expires/is deleted | # A disconnect message is sent to this group when the session expires/is deleted | ||||||
| RAC_CLIENT_GROUP_SESSION = "group_enterprise_rac_client_%(session)s" | RAC_CLIENT_GROUP_SESSION = "group_rac_client_%(session)s" | ||||||
| # A group for all connections with a specific token, which in almost all cases | # A group for all connections with a specific token, which in almost all cases | ||||||
| # is just one connection, however this is used to disconnect the connection | # is just one connection, however this is used to disconnect the connection | ||||||
| # when the token is deleted | # when the token is deleted | ||||||
| RAC_CLIENT_GROUP_TOKEN = "group_enterprise_rac_token_%(token)s"  # nosec | RAC_CLIENT_GROUP_TOKEN = "group_rac_token_%(token)s"  # nosec | ||||||
| 
 | 
 | ||||||
| # Step 1: Client connects to this websocket endpoint | # Step 1: Client connects to this websocket endpoint | ||||||
| # Step 2: We prepare all the connection args for Guac | # Step 2: We prepare all the connection args for Guac | ||||||
| @ -3,7 +3,7 @@ | |||||||
| from channels.exceptions import ChannelFull | from channels.exceptions import ChannelFull | ||||||
| from channels.generic.websocket import AsyncWebsocketConsumer | from channels.generic.websocket import AsyncWebsocketConsumer | ||||||
| 
 | 
 | ||||||
| from authentik.enterprise.providers.rac.consumer_client import RAC_CLIENT_GROUP | from authentik.providers.rac.consumer_client import RAC_CLIENT_GROUP | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RACOutpostConsumer(AsyncWebsocketConsumer): | class RACOutpostConsumer(AsyncWebsocketConsumer): | ||||||
| @ -74,7 +74,7 @@ class RACProvider(Provider): | |||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def serializer(self) -> type[Serializer]: |     def serializer(self) -> type[Serializer]: | ||||||
|         from authentik.enterprise.providers.rac.api.providers import RACProviderSerializer |         from authentik.providers.rac.api.providers import RACProviderSerializer | ||||||
| 
 | 
 | ||||||
|         return RACProviderSerializer |         return RACProviderSerializer | ||||||
| 
 | 
 | ||||||
| @ -100,7 +100,7 @@ class Endpoint(SerializerModel, PolicyBindingModel): | |||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def serializer(self) -> type[Serializer]: |     def serializer(self) -> type[Serializer]: | ||||||
|         from authentik.enterprise.providers.rac.api.endpoints import EndpointSerializer |         from authentik.providers.rac.api.endpoints import EndpointSerializer | ||||||
| 
 | 
 | ||||||
|         return EndpointSerializer |         return EndpointSerializer | ||||||
| 
 | 
 | ||||||
| @ -129,7 +129,7 @@ class RACPropertyMapping(PropertyMapping): | |||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def serializer(self) -> type[Serializer]: |     def serializer(self) -> type[Serializer]: | ||||||
|         from authentik.enterprise.providers.rac.api.property_mappings import ( |         from authentik.providers.rac.api.property_mappings import ( | ||||||
|             RACPropertyMappingSerializer, |             RACPropertyMappingSerializer, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| @ -10,12 +10,12 @@ from django.dispatch import receiver | |||||||
| from django.http import HttpRequest | from django.http import HttpRequest | ||||||
| 
 | 
 | ||||||
| from authentik.core.models import User | from authentik.core.models import User | ||||||
| from authentik.enterprise.providers.rac.api.endpoints import user_endpoint_cache_key | from authentik.providers.rac.api.endpoints import user_endpoint_cache_key | ||||||
| from authentik.enterprise.providers.rac.consumer_client import ( | from authentik.providers.rac.consumer_client import ( | ||||||
|     RAC_CLIENT_GROUP_SESSION, |     RAC_CLIENT_GROUP_SESSION, | ||||||
|     RAC_CLIENT_GROUP_TOKEN, |     RAC_CLIENT_GROUP_TOKEN, | ||||||
| ) | ) | ||||||
| from authentik.enterprise.providers.rac.models import ConnectionToken, Endpoint | from authentik.providers.rac.models import ConnectionToken, Endpoint | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @receiver(user_logged_out) | @receiver(user_logged_out) | ||||||
| @ -3,7 +3,7 @@ | |||||||
| {% load authentik_core %} | {% load authentik_core %} | ||||||
| 
 | 
 | ||||||
| {% block head %} | {% block head %} | ||||||
| <script src="{% versioned_script 'dist/enterprise/rac/index-%v.js' %}" type="module"></script> | <script src="{% versioned_script 'dist/rac/index-%v.js' %}" type="module"></script> | ||||||
| <meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)"> | <meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)"> | ||||||
| <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> | <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)"> | ||||||
| <link rel="icon" href="{{ tenant.branding_favicon_url }}"> | <link rel="icon" href="{{ tenant.branding_favicon_url }}"> | ||||||
| @ -1,16 +1,9 @@ | |||||||
| """Test RAC Provider""" | """Test RAC Provider""" | ||||||
| 
 | 
 | ||||||
| from datetime import timedelta |  | ||||||
| from time import mktime |  | ||||||
| from unittest.mock import MagicMock, patch |  | ||||||
| 
 |  | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
| from django.utils.timezone import now |  | ||||||
| from rest_framework.test import APITestCase | from rest_framework.test import APITestCase | ||||||
| 
 | 
 | ||||||
| from authentik.core.tests.utils import create_test_admin_user, create_test_flow | from authentik.core.tests.utils import create_test_admin_user, create_test_flow | ||||||
| from authentik.enterprise.license import LicenseKey |  | ||||||
| from authentik.enterprise.models import License |  | ||||||
| from authentik.lib.generators import generate_id | from authentik.lib.generators import generate_id | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -20,21 +13,8 @@ class TestAPI(APITestCase): | |||||||
|     def setUp(self) -> None: |     def setUp(self) -> None: | ||||||
|         self.user = create_test_admin_user() |         self.user = create_test_admin_user() | ||||||
| 
 | 
 | ||||||
|     @patch( |  | ||||||
|         "authentik.enterprise.license.LicenseKey.validate", |  | ||||||
|         MagicMock( |  | ||||||
|             return_value=LicenseKey( |  | ||||||
|                 aud="", |  | ||||||
|                 exp=int(mktime((now() + timedelta(days=3000)).timetuple())), |  | ||||||
|                 name=generate_id(), |  | ||||||
|                 internal_users=100, |  | ||||||
|                 external_users=100, |  | ||||||
|             ) |  | ||||||
|         ), |  | ||||||
|     ) |  | ||||||
|     def test_create(self): |     def test_create(self): | ||||||
|         """Test creation of RAC Provider""" |         """Test creation of RAC Provider""" | ||||||
|         License.objects.create(key=generate_id()) |  | ||||||
|         self.client.force_login(self.user) |         self.client.force_login(self.user) | ||||||
|         response = self.client.post( |         response = self.client.post( | ||||||
|             reverse("authentik_api:racprovider-list"), |             reverse("authentik_api:racprovider-list"), | ||||||
| @ -5,10 +5,10 @@ from rest_framework.test import APITestCase | |||||||
| 
 | 
 | ||||||
| from authentik.core.models import Application | from authentik.core.models import Application | ||||||
| from authentik.core.tests.utils import create_test_admin_user | from authentik.core.tests.utils import create_test_admin_user | ||||||
| from authentik.enterprise.providers.rac.models import Endpoint, Protocols, RACProvider |  | ||||||
| from authentik.lib.generators import generate_id | from authentik.lib.generators import generate_id | ||||||
| from authentik.policies.dummy.models import DummyPolicy | from authentik.policies.dummy.models import DummyPolicy | ||||||
| from authentik.policies.models import PolicyBinding | from authentik.policies.models import PolicyBinding | ||||||
|  | from authentik.providers.rac.models import Endpoint, Protocols, RACProvider | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestEndpointsAPI(APITestCase): | class TestEndpointsAPI(APITestCase): | ||||||
| @ -4,14 +4,14 @@ from django.test import TransactionTestCase | |||||||
| 
 | 
 | ||||||
| from authentik.core.models import Application, AuthenticatedSession | from authentik.core.models import Application, AuthenticatedSession | ||||||
| from authentik.core.tests.utils import create_test_admin_user | from authentik.core.tests.utils import create_test_admin_user | ||||||
| from authentik.enterprise.providers.rac.models import ( | from authentik.lib.generators import generate_id | ||||||
|  | from authentik.providers.rac.models import ( | ||||||
|     ConnectionToken, |     ConnectionToken, | ||||||
|     Endpoint, |     Endpoint, | ||||||
|     Protocols, |     Protocols, | ||||||
|     RACPropertyMapping, |     RACPropertyMapping, | ||||||
|     RACProvider, |     RACProvider, | ||||||
| ) | ) | ||||||
| from authentik.lib.generators import generate_id |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestModels(TransactionTestCase): | class TestModels(TransactionTestCase): | ||||||
| @ -1,23 +1,17 @@ | |||||||
| """RAC Views tests""" | """RAC Views tests""" | ||||||
| 
 | 
 | ||||||
| from datetime import timedelta |  | ||||||
| from json import loads | from json import loads | ||||||
| from time import mktime |  | ||||||
| from unittest.mock import MagicMock, patch |  | ||||||
| 
 | 
 | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
| from django.utils.timezone import now |  | ||||||
| from rest_framework.test import APITestCase | from rest_framework.test import APITestCase | ||||||
| 
 | 
 | ||||||
| from authentik.core.models import Application | from authentik.core.models import Application | ||||||
| from authentik.core.tests.utils import create_test_admin_user, create_test_flow | from authentik.core.tests.utils import create_test_admin_user, create_test_flow | ||||||
| from authentik.enterprise.license import LicenseKey |  | ||||||
| from authentik.enterprise.models import License |  | ||||||
| from authentik.enterprise.providers.rac.models import Endpoint, Protocols, RACProvider |  | ||||||
| from authentik.lib.generators import generate_id | from authentik.lib.generators import generate_id | ||||||
| from authentik.policies.denied import AccessDeniedResponse | from authentik.policies.denied import AccessDeniedResponse | ||||||
| from authentik.policies.dummy.models import DummyPolicy | from authentik.policies.dummy.models import DummyPolicy | ||||||
| from authentik.policies.models import PolicyBinding | from authentik.policies.models import PolicyBinding | ||||||
|  | from authentik.providers.rac.models import Endpoint, Protocols, RACProvider | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestRACViews(APITestCase): | class TestRACViews(APITestCase): | ||||||
| @ -39,21 +33,8 @@ class TestRACViews(APITestCase): | |||||||
|             provider=self.provider, |             provider=self.provider, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     @patch( |  | ||||||
|         "authentik.enterprise.license.LicenseKey.validate", |  | ||||||
|         MagicMock( |  | ||||||
|             return_value=LicenseKey( |  | ||||||
|                 aud="", |  | ||||||
|                 exp=int(mktime((now() + timedelta(days=3000)).timetuple())), |  | ||||||
|                 name=generate_id(), |  | ||||||
|                 internal_users=100, |  | ||||||
|                 external_users=100, |  | ||||||
|             ) |  | ||||||
|         ), |  | ||||||
|     ) |  | ||||||
|     def test_no_policy(self): |     def test_no_policy(self): | ||||||
|         """Test request""" |         """Test request""" | ||||||
|         License.objects.create(key=generate_id()) |  | ||||||
|         self.client.force_login(self.user) |         self.client.force_login(self.user) | ||||||
|         response = self.client.get( |         response = self.client.get( | ||||||
|             reverse( |             reverse( | ||||||
| @ -70,18 +51,6 @@ class TestRACViews(APITestCase): | |||||||
|         final_response = self.client.get(next_url) |         final_response = self.client.get(next_url) | ||||||
|         self.assertEqual(final_response.status_code, 200) |         self.assertEqual(final_response.status_code, 200) | ||||||
| 
 | 
 | ||||||
|     @patch( |  | ||||||
|         "authentik.enterprise.license.LicenseKey.validate", |  | ||||||
|         MagicMock( |  | ||||||
|             return_value=LicenseKey( |  | ||||||
|                 aud="", |  | ||||||
|                 exp=int(mktime((now() + timedelta(days=3000)).timetuple())), |  | ||||||
|                 name=generate_id(), |  | ||||||
|                 internal_users=100, |  | ||||||
|                 external_users=100, |  | ||||||
|             ) |  | ||||||
|         ), |  | ||||||
|     ) |  | ||||||
|     def test_app_deny(self): |     def test_app_deny(self): | ||||||
|         """Test request (deny on app level)""" |         """Test request (deny on app level)""" | ||||||
|         PolicyBinding.objects.create( |         PolicyBinding.objects.create( | ||||||
| @ -89,7 +58,6 @@ class TestRACViews(APITestCase): | |||||||
|             policy=DummyPolicy.objects.create(name="deny", result=False, wait_min=1, wait_max=2), |             policy=DummyPolicy.objects.create(name="deny", result=False, wait_min=1, wait_max=2), | ||||||
|             order=0, |             order=0, | ||||||
|         ) |         ) | ||||||
|         License.objects.create(key=generate_id()) |  | ||||||
|         self.client.force_login(self.user) |         self.client.force_login(self.user) | ||||||
|         response = self.client.get( |         response = self.client.get( | ||||||
|             reverse( |             reverse( | ||||||
| @ -99,18 +67,6 @@ class TestRACViews(APITestCase): | |||||||
|         ) |         ) | ||||||
|         self.assertIsInstance(response, AccessDeniedResponse) |         self.assertIsInstance(response, AccessDeniedResponse) | ||||||
| 
 | 
 | ||||||
|     @patch( |  | ||||||
|         "authentik.enterprise.license.LicenseKey.validate", |  | ||||||
|         MagicMock( |  | ||||||
|             return_value=LicenseKey( |  | ||||||
|                 aud="", |  | ||||||
|                 exp=int(mktime((now() + timedelta(days=3000)).timetuple())), |  | ||||||
|                 name=generate_id(), |  | ||||||
|                 internal_users=100, |  | ||||||
|                 external_users=100, |  | ||||||
|             ) |  | ||||||
|         ), |  | ||||||
|     ) |  | ||||||
|     def test_endpoint_deny(self): |     def test_endpoint_deny(self): | ||||||
|         """Test request (deny on endpoint level)""" |         """Test request (deny on endpoint level)""" | ||||||
|         PolicyBinding.objects.create( |         PolicyBinding.objects.create( | ||||||
| @ -118,7 +74,6 @@ class TestRACViews(APITestCase): | |||||||
|             policy=DummyPolicy.objects.create(name="deny", result=False, wait_min=1, wait_max=2), |             policy=DummyPolicy.objects.create(name="deny", result=False, wait_min=1, wait_max=2), | ||||||
|             order=0, |             order=0, | ||||||
|         ) |         ) | ||||||
|         License.objects.create(key=generate_id()) |  | ||||||
|         self.client.force_login(self.user) |         self.client.force_login(self.user) | ||||||
|         response = self.client.get( |         response = self.client.get( | ||||||
|             reverse( |             reverse( | ||||||
| @ -4,14 +4,14 @@ from channels.auth import AuthMiddleware | |||||||
| from channels.sessions import CookieMiddleware | from channels.sessions import CookieMiddleware | ||||||
| from django.urls import path | from django.urls import path | ||||||
| 
 | 
 | ||||||
| from authentik.enterprise.providers.rac.api.connection_tokens import ConnectionTokenViewSet |  | ||||||
| from authentik.enterprise.providers.rac.api.endpoints import EndpointViewSet |  | ||||||
| from authentik.enterprise.providers.rac.api.property_mappings import RACPropertyMappingViewSet |  | ||||||
| from authentik.enterprise.providers.rac.api.providers import RACProviderViewSet |  | ||||||
| from authentik.enterprise.providers.rac.consumer_client import RACClientConsumer |  | ||||||
| from authentik.enterprise.providers.rac.consumer_outpost import RACOutpostConsumer |  | ||||||
| from authentik.enterprise.providers.rac.views import RACInterface, RACStartView |  | ||||||
| from authentik.outposts.channels import TokenOutpostMiddleware | from authentik.outposts.channels import TokenOutpostMiddleware | ||||||
|  | from authentik.providers.rac.api.connection_tokens import ConnectionTokenViewSet | ||||||
|  | from authentik.providers.rac.api.endpoints import EndpointViewSet | ||||||
|  | from authentik.providers.rac.api.property_mappings import RACPropertyMappingViewSet | ||||||
|  | from authentik.providers.rac.api.providers import RACProviderViewSet | ||||||
|  | from authentik.providers.rac.consumer_client import RACClientConsumer | ||||||
|  | from authentik.providers.rac.consumer_outpost import RACOutpostConsumer | ||||||
|  | from authentik.providers.rac.views import RACInterface, RACStartView | ||||||
| from authentik.root.asgi_middleware import SessionMiddleware | from authentik.root.asgi_middleware import SessionMiddleware | ||||||
| from authentik.root.middleware import ChannelsLoggingMiddleware | from authentik.root.middleware import ChannelsLoggingMiddleware | ||||||
| 
 | 
 | ||||||
| @ -10,8 +10,6 @@ from django.utils.translation import gettext as _ | |||||||
| 
 | 
 | ||||||
| from authentik.core.models import Application, AuthenticatedSession | from authentik.core.models import Application, AuthenticatedSession | ||||||
| from authentik.core.views.interface import InterfaceView | from authentik.core.views.interface import InterfaceView | ||||||
| from authentik.enterprise.policy import EnterprisePolicyAccessView |  | ||||||
| from authentik.enterprise.providers.rac.models import ConnectionToken, Endpoint, RACProvider |  | ||||||
| from authentik.events.models import Event, EventAction | from authentik.events.models import Event, EventAction | ||||||
| from authentik.flows.challenge import RedirectChallenge | from authentik.flows.challenge import RedirectChallenge | ||||||
| from authentik.flows.exceptions import FlowNonApplicableException | from authentik.flows.exceptions import FlowNonApplicableException | ||||||
| @ -20,9 +18,11 @@ from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner | |||||||
| from authentik.flows.stage import RedirectStage | from authentik.flows.stage import RedirectStage | ||||||
| from authentik.lib.utils.time import timedelta_from_string | from authentik.lib.utils.time import timedelta_from_string | ||||||
| from authentik.policies.engine import PolicyEngine | from authentik.policies.engine import PolicyEngine | ||||||
|  | from authentik.policies.views import PolicyAccessView | ||||||
|  | from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RACStartView(EnterprisePolicyAccessView): | class RACStartView(PolicyAccessView): | ||||||
|     """Start a RAC connection by checking access and creating a connection token""" |     """Start a RAC connection by checking access and creating a connection token""" | ||||||
| 
 | 
 | ||||||
|     endpoint: Endpoint |     endpoint: Endpoint | ||||||
| @ -87,6 +87,7 @@ TENANT_APPS = [ | |||||||
|     "authentik.providers.ldap", |     "authentik.providers.ldap", | ||||||
|     "authentik.providers.oauth2", |     "authentik.providers.oauth2", | ||||||
|     "authentik.providers.proxy", |     "authentik.providers.proxy", | ||||||
|  |     "authentik.providers.rac", | ||||||
|     "authentik.providers.radius", |     "authentik.providers.radius", | ||||||
|     "authentik.providers.saml", |     "authentik.providers.saml", | ||||||
|     "authentik.providers.scim", |     "authentik.providers.scim", | ||||||
|  | |||||||
| @ -801,6 +801,126 @@ | |||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     }, |                     }, | ||||||
|  |                     { | ||||||
|  |                         "type": "object", | ||||||
|  |                         "required": [ | ||||||
|  |                             "model", | ||||||
|  |                             "identifiers" | ||||||
|  |                         ], | ||||||
|  |                         "properties": { | ||||||
|  |                             "model": { | ||||||
|  |                                 "const": "authentik_providers_rac.racprovider" | ||||||
|  |                             }, | ||||||
|  |                             "id": { | ||||||
|  |                                 "type": "string" | ||||||
|  |                             }, | ||||||
|  |                             "state": { | ||||||
|  |                                 "type": "string", | ||||||
|  |                                 "enum": [ | ||||||
|  |                                     "absent", | ||||||
|  |                                     "present", | ||||||
|  |                                     "created", | ||||||
|  |                                     "must_created" | ||||||
|  |                                 ], | ||||||
|  |                                 "default": "present" | ||||||
|  |                             }, | ||||||
|  |                             "conditions": { | ||||||
|  |                                 "type": "array", | ||||||
|  |                                 "items": { | ||||||
|  |                                     "type": "boolean" | ||||||
|  |                                 } | ||||||
|  |                             }, | ||||||
|  |                             "permissions": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_providers_rac.racprovider_permissions" | ||||||
|  |                             }, | ||||||
|  |                             "attrs": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_providers_rac.racprovider" | ||||||
|  |                             }, | ||||||
|  |                             "identifiers": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_providers_rac.racprovider" | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                         "type": "object", | ||||||
|  |                         "required": [ | ||||||
|  |                             "model", | ||||||
|  |                             "identifiers" | ||||||
|  |                         ], | ||||||
|  |                         "properties": { | ||||||
|  |                             "model": { | ||||||
|  |                                 "const": "authentik_providers_rac.endpoint" | ||||||
|  |                             }, | ||||||
|  |                             "id": { | ||||||
|  |                                 "type": "string" | ||||||
|  |                             }, | ||||||
|  |                             "state": { | ||||||
|  |                                 "type": "string", | ||||||
|  |                                 "enum": [ | ||||||
|  |                                     "absent", | ||||||
|  |                                     "present", | ||||||
|  |                                     "created", | ||||||
|  |                                     "must_created" | ||||||
|  |                                 ], | ||||||
|  |                                 "default": "present" | ||||||
|  |                             }, | ||||||
|  |                             "conditions": { | ||||||
|  |                                 "type": "array", | ||||||
|  |                                 "items": { | ||||||
|  |                                     "type": "boolean" | ||||||
|  |                                 } | ||||||
|  |                             }, | ||||||
|  |                             "permissions": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_providers_rac.endpoint_permissions" | ||||||
|  |                             }, | ||||||
|  |                             "attrs": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_providers_rac.endpoint" | ||||||
|  |                             }, | ||||||
|  |                             "identifiers": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_providers_rac.endpoint" | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                         "type": "object", | ||||||
|  |                         "required": [ | ||||||
|  |                             "model", | ||||||
|  |                             "identifiers" | ||||||
|  |                         ], | ||||||
|  |                         "properties": { | ||||||
|  |                             "model": { | ||||||
|  |                                 "const": "authentik_providers_rac.racpropertymapping" | ||||||
|  |                             }, | ||||||
|  |                             "id": { | ||||||
|  |                                 "type": "string" | ||||||
|  |                             }, | ||||||
|  |                             "state": { | ||||||
|  |                                 "type": "string", | ||||||
|  |                                 "enum": [ | ||||||
|  |                                     "absent", | ||||||
|  |                                     "present", | ||||||
|  |                                     "created", | ||||||
|  |                                     "must_created" | ||||||
|  |                                 ], | ||||||
|  |                                 "default": "present" | ||||||
|  |                             }, | ||||||
|  |                             "conditions": { | ||||||
|  |                                 "type": "array", | ||||||
|  |                                 "items": { | ||||||
|  |                                     "type": "boolean" | ||||||
|  |                                 } | ||||||
|  |                             }, | ||||||
|  |                             "permissions": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_providers_rac.racpropertymapping_permissions" | ||||||
|  |                             }, | ||||||
|  |                             "attrs": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_providers_rac.racpropertymapping" | ||||||
|  |                             }, | ||||||
|  |                             "identifiers": { | ||||||
|  |                                 "$ref": "#/$defs/model_authentik_providers_rac.racpropertymapping" | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     }, | ||||||
|                     { |                     { | ||||||
|                         "type": "object", |                         "type": "object", | ||||||
|                         "required": [ |                         "required": [ | ||||||
| @ -3561,126 +3681,6 @@ | |||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     }, |                     }, | ||||||
|                     { |  | ||||||
|                         "type": "object", |  | ||||||
|                         "required": [ |  | ||||||
|                             "model", |  | ||||||
|                             "identifiers" |  | ||||||
|                         ], |  | ||||||
|                         "properties": { |  | ||||||
|                             "model": { |  | ||||||
|                                 "const": "authentik_providers_rac.racprovider" |  | ||||||
|                             }, |  | ||||||
|                             "id": { |  | ||||||
|                                 "type": "string" |  | ||||||
|                             }, |  | ||||||
|                             "state": { |  | ||||||
|                                 "type": "string", |  | ||||||
|                                 "enum": [ |  | ||||||
|                                     "absent", |  | ||||||
|                                     "present", |  | ||||||
|                                     "created", |  | ||||||
|                                     "must_created" |  | ||||||
|                                 ], |  | ||||||
|                                 "default": "present" |  | ||||||
|                             }, |  | ||||||
|                             "conditions": { |  | ||||||
|                                 "type": "array", |  | ||||||
|                                 "items": { |  | ||||||
|                                     "type": "boolean" |  | ||||||
|                                 } |  | ||||||
|                             }, |  | ||||||
|                             "permissions": { |  | ||||||
|                                 "$ref": "#/$defs/model_authentik_providers_rac.racprovider_permissions" |  | ||||||
|                             }, |  | ||||||
|                             "attrs": { |  | ||||||
|                                 "$ref": "#/$defs/model_authentik_providers_rac.racprovider" |  | ||||||
|                             }, |  | ||||||
|                             "identifiers": { |  | ||||||
|                                 "$ref": "#/$defs/model_authentik_providers_rac.racprovider" |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                     { |  | ||||||
|                         "type": "object", |  | ||||||
|                         "required": [ |  | ||||||
|                             "model", |  | ||||||
|                             "identifiers" |  | ||||||
|                         ], |  | ||||||
|                         "properties": { |  | ||||||
|                             "model": { |  | ||||||
|                                 "const": "authentik_providers_rac.endpoint" |  | ||||||
|                             }, |  | ||||||
|                             "id": { |  | ||||||
|                                 "type": "string" |  | ||||||
|                             }, |  | ||||||
|                             "state": { |  | ||||||
|                                 "type": "string", |  | ||||||
|                                 "enum": [ |  | ||||||
|                                     "absent", |  | ||||||
|                                     "present", |  | ||||||
|                                     "created", |  | ||||||
|                                     "must_created" |  | ||||||
|                                 ], |  | ||||||
|                                 "default": "present" |  | ||||||
|                             }, |  | ||||||
|                             "conditions": { |  | ||||||
|                                 "type": "array", |  | ||||||
|                                 "items": { |  | ||||||
|                                     "type": "boolean" |  | ||||||
|                                 } |  | ||||||
|                             }, |  | ||||||
|                             "permissions": { |  | ||||||
|                                 "$ref": "#/$defs/model_authentik_providers_rac.endpoint_permissions" |  | ||||||
|                             }, |  | ||||||
|                             "attrs": { |  | ||||||
|                                 "$ref": "#/$defs/model_authentik_providers_rac.endpoint" |  | ||||||
|                             }, |  | ||||||
|                             "identifiers": { |  | ||||||
|                                 "$ref": "#/$defs/model_authentik_providers_rac.endpoint" |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                     { |  | ||||||
|                         "type": "object", |  | ||||||
|                         "required": [ |  | ||||||
|                             "model", |  | ||||||
|                             "identifiers" |  | ||||||
|                         ], |  | ||||||
|                         "properties": { |  | ||||||
|                             "model": { |  | ||||||
|                                 "const": "authentik_providers_rac.racpropertymapping" |  | ||||||
|                             }, |  | ||||||
|                             "id": { |  | ||||||
|                                 "type": "string" |  | ||||||
|                             }, |  | ||||||
|                             "state": { |  | ||||||
|                                 "type": "string", |  | ||||||
|                                 "enum": [ |  | ||||||
|                                     "absent", |  | ||||||
|                                     "present", |  | ||||||
|                                     "created", |  | ||||||
|                                     "must_created" |  | ||||||
|                                 ], |  | ||||||
|                                 "default": "present" |  | ||||||
|                             }, |  | ||||||
|                             "conditions": { |  | ||||||
|                                 "type": "array", |  | ||||||
|                                 "items": { |  | ||||||
|                                     "type": "boolean" |  | ||||||
|                                 } |  | ||||||
|                             }, |  | ||||||
|                             "permissions": { |  | ||||||
|                                 "$ref": "#/$defs/model_authentik_providers_rac.racpropertymapping_permissions" |  | ||||||
|                             }, |  | ||||||
|                             "attrs": { |  | ||||||
|                                 "$ref": "#/$defs/model_authentik_providers_rac.racpropertymapping" |  | ||||||
|                             }, |  | ||||||
|                             "identifiers": { |  | ||||||
|                                 "$ref": "#/$defs/model_authentik_providers_rac.racpropertymapping" |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     }, |  | ||||||
|                     { |                     { | ||||||
|                         "type": "object", |                         "type": "object", | ||||||
|                         "required": [ |                         "required": [ | ||||||
| @ -4663,6 +4663,7 @@ | |||||||
|                         "authentik.providers.ldap", |                         "authentik.providers.ldap", | ||||||
|                         "authentik.providers.oauth2", |                         "authentik.providers.oauth2", | ||||||
|                         "authentik.providers.proxy", |                         "authentik.providers.proxy", | ||||||
|  |                         "authentik.providers.rac", | ||||||
|                         "authentik.providers.radius", |                         "authentik.providers.radius", | ||||||
|                         "authentik.providers.saml", |                         "authentik.providers.saml", | ||||||
|                         "authentik.providers.scim", |                         "authentik.providers.scim", | ||||||
| @ -4703,7 +4704,6 @@ | |||||||
|                         "authentik.enterprise.audit", |                         "authentik.enterprise.audit", | ||||||
|                         "authentik.enterprise.providers.google_workspace", |                         "authentik.enterprise.providers.google_workspace", | ||||||
|                         "authentik.enterprise.providers.microsoft_entra", |                         "authentik.enterprise.providers.microsoft_entra", | ||||||
|                         "authentik.enterprise.providers.rac", |  | ||||||
|                         "authentik.enterprise.providers.ssf", |                         "authentik.enterprise.providers.ssf", | ||||||
|                         "authentik.enterprise.stages.authenticator_endpoint_gdtc", |                         "authentik.enterprise.stages.authenticator_endpoint_gdtc", | ||||||
|                         "authentik.enterprise.stages.source", |                         "authentik.enterprise.stages.source", | ||||||
| @ -4738,6 +4738,9 @@ | |||||||
|                         "authentik_providers_oauth2.scopemapping", |                         "authentik_providers_oauth2.scopemapping", | ||||||
|                         "authentik_providers_oauth2.oauth2provider", |                         "authentik_providers_oauth2.oauth2provider", | ||||||
|                         "authentik_providers_proxy.proxyprovider", |                         "authentik_providers_proxy.proxyprovider", | ||||||
|  |                         "authentik_providers_rac.racprovider", | ||||||
|  |                         "authentik_providers_rac.endpoint", | ||||||
|  |                         "authentik_providers_rac.racpropertymapping", | ||||||
|                         "authentik_providers_radius.radiusprovider", |                         "authentik_providers_radius.radiusprovider", | ||||||
|                         "authentik_providers_radius.radiusproviderpropertymapping", |                         "authentik_providers_radius.radiusproviderpropertymapping", | ||||||
|                         "authentik_providers_saml.samlprovider", |                         "authentik_providers_saml.samlprovider", | ||||||
| @ -4807,9 +4810,6 @@ | |||||||
|                         "authentik_providers_google_workspace.googleworkspaceprovidermapping", |                         "authentik_providers_google_workspace.googleworkspaceprovidermapping", | ||||||
|                         "authentik_providers_microsoft_entra.microsoftentraprovider", |                         "authentik_providers_microsoft_entra.microsoftentraprovider", | ||||||
|                         "authentik_providers_microsoft_entra.microsoftentraprovidermapping", |                         "authentik_providers_microsoft_entra.microsoftentraprovidermapping", | ||||||
|                         "authentik_providers_rac.racprovider", |  | ||||||
|                         "authentik_providers_rac.endpoint", |  | ||||||
|                         "authentik_providers_rac.racpropertymapping", |  | ||||||
|                         "authentik_providers_ssf.ssfprovider", |                         "authentik_providers_ssf.ssfprovider", | ||||||
|                         "authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage", |                         "authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage", | ||||||
|                         "authentik_stages_source.sourcestage", |                         "authentik_stages_source.sourcestage", | ||||||
| @ -5232,6 +5232,38 @@ | |||||||
|                     }, |                     }, | ||||||
|                     "maxItems": 249, |                     "maxItems": 249, | ||||||
|                     "title": "Countries" |                     "title": "Countries" | ||||||
|  |                 }, | ||||||
|  |                 "check_history_distance": { | ||||||
|  |                     "type": "boolean", | ||||||
|  |                     "title": "Check history distance" | ||||||
|  |                 }, | ||||||
|  |                 "history_max_distance_km": { | ||||||
|  |                     "type": "integer", | ||||||
|  |                     "minimum": 0, | ||||||
|  |                     "maximum": 9223372036854775807, | ||||||
|  |                     "title": "History max distance km" | ||||||
|  |                 }, | ||||||
|  |                 "distance_tolerance_km": { | ||||||
|  |                     "type": "integer", | ||||||
|  |                     "minimum": 0, | ||||||
|  |                     "maximum": 2147483647, | ||||||
|  |                     "title": "Distance tolerance km" | ||||||
|  |                 }, | ||||||
|  |                 "history_login_count": { | ||||||
|  |                     "type": "integer", | ||||||
|  |                     "minimum": 0, | ||||||
|  |                     "maximum": 2147483647, | ||||||
|  |                     "title": "History login count" | ||||||
|  |                 }, | ||||||
|  |                 "check_impossible_travel": { | ||||||
|  |                     "type": "boolean", | ||||||
|  |                     "title": "Check impossible travel" | ||||||
|  |                 }, | ||||||
|  |                 "impossible_tolerance_km": { | ||||||
|  |                     "type": "integer", | ||||||
|  |                     "minimum": 0, | ||||||
|  |                     "maximum": 2147483647, | ||||||
|  |                     "title": "Impossible tolerance km" | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             "required": [] |             "required": [] | ||||||
| @ -6014,6 +6046,216 @@ | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         "model_authentik_providers_rac.racprovider": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "name": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "minLength": 1, | ||||||
|  |                     "title": "Name" | ||||||
|  |                 }, | ||||||
|  |                 "authentication_flow": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "format": "uuid", | ||||||
|  |                     "title": "Authentication flow", | ||||||
|  |                     "description": "Flow used for authentication when the associated application is accessed by an un-authenticated user." | ||||||
|  |                 }, | ||||||
|  |                 "authorization_flow": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "format": "uuid", | ||||||
|  |                     "title": "Authorization flow", | ||||||
|  |                     "description": "Flow used when authorizing this provider." | ||||||
|  |                 }, | ||||||
|  |                 "property_mappings": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "format": "uuid" | ||||||
|  |                     }, | ||||||
|  |                     "title": "Property mappings" | ||||||
|  |                 }, | ||||||
|  |                 "settings": { | ||||||
|  |                     "type": "object", | ||||||
|  |                     "additionalProperties": true, | ||||||
|  |                     "title": "Settings" | ||||||
|  |                 }, | ||||||
|  |                 "connection_expiry": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "minLength": 1, | ||||||
|  |                     "title": "Connection expiry", | ||||||
|  |                     "description": "Determines how long a session lasts. Default of 0 means that the sessions lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)" | ||||||
|  |                 }, | ||||||
|  |                 "delete_token_on_disconnect": { | ||||||
|  |                     "type": "boolean", | ||||||
|  |                     "title": "Delete token on disconnect", | ||||||
|  |                     "description": "When set to true, connection tokens will be deleted upon disconnect." | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             "required": [] | ||||||
|  |         }, | ||||||
|  |         "model_authentik_providers_rac.racprovider_permissions": { | ||||||
|  |             "type": "array", | ||||||
|  |             "items": { | ||||||
|  |                 "type": "object", | ||||||
|  |                 "required": [ | ||||||
|  |                     "permission" | ||||||
|  |                 ], | ||||||
|  |                 "properties": { | ||||||
|  |                     "permission": { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "enum": [ | ||||||
|  |                             "add_racprovider", | ||||||
|  |                             "change_racprovider", | ||||||
|  |                             "delete_racprovider", | ||||||
|  |                             "view_racprovider" | ||||||
|  |                         ] | ||||||
|  |                     }, | ||||||
|  |                     "user": { | ||||||
|  |                         "type": "integer" | ||||||
|  |                     }, | ||||||
|  |                     "role": { | ||||||
|  |                         "type": "string" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "model_authentik_providers_rac.endpoint": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "name": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "minLength": 1, | ||||||
|  |                     "title": "Name" | ||||||
|  |                 }, | ||||||
|  |                 "provider": { | ||||||
|  |                     "type": "integer", | ||||||
|  |                     "title": "Provider" | ||||||
|  |                 }, | ||||||
|  |                 "protocol": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "enum": [ | ||||||
|  |                         "rdp", | ||||||
|  |                         "vnc", | ||||||
|  |                         "ssh" | ||||||
|  |                     ], | ||||||
|  |                     "title": "Protocol" | ||||||
|  |                 }, | ||||||
|  |                 "host": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "minLength": 1, | ||||||
|  |                     "title": "Host" | ||||||
|  |                 }, | ||||||
|  |                 "settings": { | ||||||
|  |                     "type": "object", | ||||||
|  |                     "additionalProperties": true, | ||||||
|  |                     "title": "Settings" | ||||||
|  |                 }, | ||||||
|  |                 "property_mappings": { | ||||||
|  |                     "type": "array", | ||||||
|  |                     "items": { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "format": "uuid" | ||||||
|  |                     }, | ||||||
|  |                     "title": "Property mappings" | ||||||
|  |                 }, | ||||||
|  |                 "auth_mode": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "enum": [ | ||||||
|  |                         "static", | ||||||
|  |                         "prompt" | ||||||
|  |                     ], | ||||||
|  |                     "title": "Auth mode" | ||||||
|  |                 }, | ||||||
|  |                 "maximum_connections": { | ||||||
|  |                     "type": "integer", | ||||||
|  |                     "minimum": -2147483648, | ||||||
|  |                     "maximum": 2147483647, | ||||||
|  |                     "title": "Maximum connections" | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             "required": [] | ||||||
|  |         }, | ||||||
|  |         "model_authentik_providers_rac.endpoint_permissions": { | ||||||
|  |             "type": "array", | ||||||
|  |             "items": { | ||||||
|  |                 "type": "object", | ||||||
|  |                 "required": [ | ||||||
|  |                     "permission" | ||||||
|  |                 ], | ||||||
|  |                 "properties": { | ||||||
|  |                     "permission": { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "enum": [ | ||||||
|  |                             "add_endpoint", | ||||||
|  |                             "change_endpoint", | ||||||
|  |                             "delete_endpoint", | ||||||
|  |                             "view_endpoint" | ||||||
|  |                         ] | ||||||
|  |                     }, | ||||||
|  |                     "user": { | ||||||
|  |                         "type": "integer" | ||||||
|  |                     }, | ||||||
|  |                     "role": { | ||||||
|  |                         "type": "string" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "model_authentik_providers_rac.racpropertymapping": { | ||||||
|  |             "type": "object", | ||||||
|  |             "properties": { | ||||||
|  |                 "managed": { | ||||||
|  |                     "type": [ | ||||||
|  |                         "string", | ||||||
|  |                         "null" | ||||||
|  |                     ], | ||||||
|  |                     "minLength": 1, | ||||||
|  |                     "title": "Managed by authentik", | ||||||
|  |                     "description": "Objects that are managed by authentik. These objects are created and updated automatically. This flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update." | ||||||
|  |                 }, | ||||||
|  |                 "name": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "minLength": 1, | ||||||
|  |                     "title": "Name" | ||||||
|  |                 }, | ||||||
|  |                 "expression": { | ||||||
|  |                     "type": "string", | ||||||
|  |                     "title": "Expression" | ||||||
|  |                 }, | ||||||
|  |                 "static_settings": { | ||||||
|  |                     "type": "object", | ||||||
|  |                     "additionalProperties": true, | ||||||
|  |                     "title": "Static settings" | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             "required": [] | ||||||
|  |         }, | ||||||
|  |         "model_authentik_providers_rac.racpropertymapping_permissions": { | ||||||
|  |             "type": "array", | ||||||
|  |             "items": { | ||||||
|  |                 "type": "object", | ||||||
|  |                 "required": [ | ||||||
|  |                     "permission" | ||||||
|  |                 ], | ||||||
|  |                 "properties": { | ||||||
|  |                     "permission": { | ||||||
|  |                         "type": "string", | ||||||
|  |                         "enum": [ | ||||||
|  |                             "add_racpropertymapping", | ||||||
|  |                             "change_racpropertymapping", | ||||||
|  |                             "delete_racpropertymapping", | ||||||
|  |                             "view_racpropertymapping" | ||||||
|  |                         ] | ||||||
|  |                     }, | ||||||
|  |                     "user": { | ||||||
|  |                         "type": "integer" | ||||||
|  |                     }, | ||||||
|  |                     "role": { | ||||||
|  |                         "type": "string" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "model_authentik_providers_radius.radiusprovider": { |         "model_authentik_providers_radius.radiusprovider": { | ||||||
|             "type": "object", |             "type": "object", | ||||||
|             "properties": { |             "properties": { | ||||||
| @ -14183,216 +14425,6 @@ | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "model_authentik_providers_rac.racprovider": { |  | ||||||
|             "type": "object", |  | ||||||
|             "properties": { |  | ||||||
|                 "name": { |  | ||||||
|                     "type": "string", |  | ||||||
|                     "minLength": 1, |  | ||||||
|                     "title": "Name" |  | ||||||
|                 }, |  | ||||||
|                 "authentication_flow": { |  | ||||||
|                     "type": "string", |  | ||||||
|                     "format": "uuid", |  | ||||||
|                     "title": "Authentication flow", |  | ||||||
|                     "description": "Flow used for authentication when the associated application is accessed by an un-authenticated user." |  | ||||||
|                 }, |  | ||||||
|                 "authorization_flow": { |  | ||||||
|                     "type": "string", |  | ||||||
|                     "format": "uuid", |  | ||||||
|                     "title": "Authorization flow", |  | ||||||
|                     "description": "Flow used when authorizing this provider." |  | ||||||
|                 }, |  | ||||||
|                 "property_mappings": { |  | ||||||
|                     "type": "array", |  | ||||||
|                     "items": { |  | ||||||
|                         "type": "string", |  | ||||||
|                         "format": "uuid" |  | ||||||
|                     }, |  | ||||||
|                     "title": "Property mappings" |  | ||||||
|                 }, |  | ||||||
|                 "settings": { |  | ||||||
|                     "type": "object", |  | ||||||
|                     "additionalProperties": true, |  | ||||||
|                     "title": "Settings" |  | ||||||
|                 }, |  | ||||||
|                 "connection_expiry": { |  | ||||||
|                     "type": "string", |  | ||||||
|                     "minLength": 1, |  | ||||||
|                     "title": "Connection expiry", |  | ||||||
|                     "description": "Determines how long a session lasts. Default of 0 means that the sessions lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)" |  | ||||||
|                 }, |  | ||||||
|                 "delete_token_on_disconnect": { |  | ||||||
|                     "type": "boolean", |  | ||||||
|                     "title": "Delete token on disconnect", |  | ||||||
|                     "description": "When set to true, connection tokens will be deleted upon disconnect." |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             "required": [] |  | ||||||
|         }, |  | ||||||
|         "model_authentik_providers_rac.racprovider_permissions": { |  | ||||||
|             "type": "array", |  | ||||||
|             "items": { |  | ||||||
|                 "type": "object", |  | ||||||
|                 "required": [ |  | ||||||
|                     "permission" |  | ||||||
|                 ], |  | ||||||
|                 "properties": { |  | ||||||
|                     "permission": { |  | ||||||
|                         "type": "string", |  | ||||||
|                         "enum": [ |  | ||||||
|                             "add_racprovider", |  | ||||||
|                             "change_racprovider", |  | ||||||
|                             "delete_racprovider", |  | ||||||
|                             "view_racprovider" |  | ||||||
|                         ] |  | ||||||
|                     }, |  | ||||||
|                     "user": { |  | ||||||
|                         "type": "integer" |  | ||||||
|                     }, |  | ||||||
|                     "role": { |  | ||||||
|                         "type": "string" |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         "model_authentik_providers_rac.endpoint": { |  | ||||||
|             "type": "object", |  | ||||||
|             "properties": { |  | ||||||
|                 "name": { |  | ||||||
|                     "type": "string", |  | ||||||
|                     "minLength": 1, |  | ||||||
|                     "title": "Name" |  | ||||||
|                 }, |  | ||||||
|                 "provider": { |  | ||||||
|                     "type": "integer", |  | ||||||
|                     "title": "Provider" |  | ||||||
|                 }, |  | ||||||
|                 "protocol": { |  | ||||||
|                     "type": "string", |  | ||||||
|                     "enum": [ |  | ||||||
|                         "rdp", |  | ||||||
|                         "vnc", |  | ||||||
|                         "ssh" |  | ||||||
|                     ], |  | ||||||
|                     "title": "Protocol" |  | ||||||
|                 }, |  | ||||||
|                 "host": { |  | ||||||
|                     "type": "string", |  | ||||||
|                     "minLength": 1, |  | ||||||
|                     "title": "Host" |  | ||||||
|                 }, |  | ||||||
|                 "settings": { |  | ||||||
|                     "type": "object", |  | ||||||
|                     "additionalProperties": true, |  | ||||||
|                     "title": "Settings" |  | ||||||
|                 }, |  | ||||||
|                 "property_mappings": { |  | ||||||
|                     "type": "array", |  | ||||||
|                     "items": { |  | ||||||
|                         "type": "string", |  | ||||||
|                         "format": "uuid" |  | ||||||
|                     }, |  | ||||||
|                     "title": "Property mappings" |  | ||||||
|                 }, |  | ||||||
|                 "auth_mode": { |  | ||||||
|                     "type": "string", |  | ||||||
|                     "enum": [ |  | ||||||
|                         "static", |  | ||||||
|                         "prompt" |  | ||||||
|                     ], |  | ||||||
|                     "title": "Auth mode" |  | ||||||
|                 }, |  | ||||||
|                 "maximum_connections": { |  | ||||||
|                     "type": "integer", |  | ||||||
|                     "minimum": -2147483648, |  | ||||||
|                     "maximum": 2147483647, |  | ||||||
|                     "title": "Maximum connections" |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             "required": [] |  | ||||||
|         }, |  | ||||||
|         "model_authentik_providers_rac.endpoint_permissions": { |  | ||||||
|             "type": "array", |  | ||||||
|             "items": { |  | ||||||
|                 "type": "object", |  | ||||||
|                 "required": [ |  | ||||||
|                     "permission" |  | ||||||
|                 ], |  | ||||||
|                 "properties": { |  | ||||||
|                     "permission": { |  | ||||||
|                         "type": "string", |  | ||||||
|                         "enum": [ |  | ||||||
|                             "add_endpoint", |  | ||||||
|                             "change_endpoint", |  | ||||||
|                             "delete_endpoint", |  | ||||||
|                             "view_endpoint" |  | ||||||
|                         ] |  | ||||||
|                     }, |  | ||||||
|                     "user": { |  | ||||||
|                         "type": "integer" |  | ||||||
|                     }, |  | ||||||
|                     "role": { |  | ||||||
|                         "type": "string" |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         "model_authentik_providers_rac.racpropertymapping": { |  | ||||||
|             "type": "object", |  | ||||||
|             "properties": { |  | ||||||
|                 "managed": { |  | ||||||
|                     "type": [ |  | ||||||
|                         "string", |  | ||||||
|                         "null" |  | ||||||
|                     ], |  | ||||||
|                     "minLength": 1, |  | ||||||
|                     "title": "Managed by authentik", |  | ||||||
|                     "description": "Objects that are managed by authentik. These objects are created and updated automatically. This flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update." |  | ||||||
|                 }, |  | ||||||
|                 "name": { |  | ||||||
|                     "type": "string", |  | ||||||
|                     "minLength": 1, |  | ||||||
|                     "title": "Name" |  | ||||||
|                 }, |  | ||||||
|                 "expression": { |  | ||||||
|                     "type": "string", |  | ||||||
|                     "title": "Expression" |  | ||||||
|                 }, |  | ||||||
|                 "static_settings": { |  | ||||||
|                     "type": "object", |  | ||||||
|                     "additionalProperties": true, |  | ||||||
|                     "title": "Static settings" |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             "required": [] |  | ||||||
|         }, |  | ||||||
|         "model_authentik_providers_rac.racpropertymapping_permissions": { |  | ||||||
|             "type": "array", |  | ||||||
|             "items": { |  | ||||||
|                 "type": "object", |  | ||||||
|                 "required": [ |  | ||||||
|                     "permission" |  | ||||||
|                 ], |  | ||||||
|                 "properties": { |  | ||||||
|                     "permission": { |  | ||||||
|                         "type": "string", |  | ||||||
|                         "enum": [ |  | ||||||
|                             "add_racpropertymapping", |  | ||||||
|                             "change_racpropertymapping", |  | ||||||
|                             "delete_racpropertymapping", |  | ||||||
|                             "view_racpropertymapping" |  | ||||||
|                         ] |  | ||||||
|                     }, |  | ||||||
|                     "user": { |  | ||||||
|                         "type": "integer" |  | ||||||
|                     }, |  | ||||||
|                     "role": { |  | ||||||
|                         "type": "string" |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         "model_authentik_providers_ssf.ssfprovider": { |         "model_authentik_providers_ssf.ssfprovider": { | ||||||
|             "type": "object", |             "type": "object", | ||||||
|             "properties": { |             "properties": { | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							| @ -29,7 +29,7 @@ require ( | |||||||
| 	github.com/spf13/cobra v1.9.1 | 	github.com/spf13/cobra v1.9.1 | ||||||
| 	github.com/stretchr/testify v1.10.0 | 	github.com/stretchr/testify v1.10.0 | ||||||
| 	github.com/wwt/guac v1.3.2 | 	github.com/wwt/guac v1.3.2 | ||||||
| 	goauthentik.io/api/v3 v3.2024123.4 | 	goauthentik.io/api/v3 v3.2024123.6 | ||||||
| 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | 	golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab | ||||||
| 	golang.org/x/oauth2 v0.26.0 | 	golang.org/x/oauth2 v0.26.0 | ||||||
| 	golang.org/x/sync v0.11.0 | 	golang.org/x/sync v0.11.0 | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							| @ -299,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y | |||||||
| go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= | go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= | ||||||
| go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | ||||||
| go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | ||||||
| goauthentik.io/api/v3 v3.2024123.4 h1:JYLsUjkJ7kT+jHO72DyFTXFwKEGAcOOlLh36SRG9BDw= | goauthentik.io/api/v3 v3.2024123.6 h1:AGOCa7Fc/9eONCPEW4sEhTiyEBvxN57Lfqz1zm6Gy98= | ||||||
| goauthentik.io/api/v3 v3.2024123.4/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | goauthentik.io/api/v3 v3.2024123.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= | ||||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||||
| golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||||
| golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								lifecycle/aws/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								lifecycle/aws/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -9,7 +9,7 @@ | |||||||
|             "version": "0.0.0", |             "version": "0.0.0", | ||||||
|             "license": "MIT", |             "license": "MIT", | ||||||
|             "devDependencies": { |             "devDependencies": { | ||||||
|                 "aws-cdk": "^2.178.2", |                 "aws-cdk": "^2.179.0", | ||||||
|                 "cross-env": "^7.0.3" |                 "cross-env": "^7.0.3" | ||||||
|             }, |             }, | ||||||
|             "engines": { |             "engines": { | ||||||
| @ -17,9 +17,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/aws-cdk": { |         "node_modules/aws-cdk": { | ||||||
|             "version": "2.178.2", |             "version": "2.179.0", | ||||||
|             "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.178.2.tgz", |             "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.179.0.tgz", | ||||||
|             "integrity": "sha512-ojMCMnBGinvDUD6+BOOlUOB9pjsYXoQdFVbf4bvi3dy3nwn557r0j6qDUcJMeikzPJ6YWzfAdL0fYxBZg4xcOg==", |             "integrity": "sha512-aA2+8S2g4UBQHkUEt0mYd16VLt/ucR+QfyUJi34LDKRAhOCNDjPCZ4z9z/JEDyuni0BdzsYA55pnpDN9tMULpA==", | ||||||
|             "dev": true, |             "dev": true, | ||||||
|             "license": "Apache-2.0", |             "license": "Apache-2.0", | ||||||
|             "bin": { |             "bin": { | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ | |||||||
|         "node": ">=20" |         "node": ">=20" | ||||||
|     }, |     }, | ||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "aws-cdk": "^2.178.2", |         "aws-cdk": "^2.179.0", | ||||||
|         "cross-env": "^7.0.3" |         "cross-env": "^7.0.3" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										72
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										72
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							| @ -358,22 +358,6 @@ jsii = ">=1.105.0,<2.0.0" | |||||||
| publication = ">=0.0.3" | publication = ">=0.0.3" | ||||||
| typeguard = ">=2.13.3,<4.3.0" | typeguard = ">=2.13.3,<4.3.0" | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "aws-cdk-asset-kubectl-v20" |  | ||||||
| version = "2.1.3" |  | ||||||
| description = "A Lambda Layer that contains kubectl v1.20" |  | ||||||
| optional = false |  | ||||||
| python-versions = "~=3.8" |  | ||||||
| files = [ |  | ||||||
|     {file = "aws_cdk.asset_kubectl_v20-2.1.3-py3-none-any.whl", hash = "sha256:d5612e5bd03c215a28ce53193b1144ecf4e93b3b6779563c046a8a74d83a3979"}, |  | ||||||
|     {file = "aws_cdk_asset_kubectl_v20-2.1.3.tar.gz", hash = "sha256:237cd8530d9e8be0bbc7159af927dbb6b7f91bf3f4099c8ef4d9a213b34264be"}, |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [package.dependencies] |  | ||||||
| jsii = ">=1.103.1,<2.0.0" |  | ||||||
| publication = ">=0.0.3" |  | ||||||
| typeguard = ">=2.13.3,<5.0.0" |  | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-cdk-asset-node-proxy-agent-v6" | name = "aws-cdk-asset-node-proxy-agent-v6" | ||||||
| version = "2.1.0" | version = "2.1.0" | ||||||
| @ -408,18 +392,17 @@ typeguard = ">=2.13.3,<4.3.0" | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "aws-cdk-lib" | name = "aws-cdk-lib" | ||||||
| version = "2.178.2" | version = "2.179.0" | ||||||
| description = "Version 2 of the AWS Cloud Development Kit library" | description = "Version 2 of the AWS Cloud Development Kit library" | ||||||
| optional = false | optional = false | ||||||
| python-versions = "~=3.8" | python-versions = "~=3.8" | ||||||
| files = [ | files = [ | ||||||
|     {file = "aws_cdk_lib-2.178.2-py3-none-any.whl", hash = "sha256:624383e57fe2b32f7d0fc098b78b4cd21d19ae3af3f24b01f32ec4795baaee25"}, |     {file = "aws_cdk_lib-2.179.0-py3-none-any.whl", hash = "sha256:1d7b88ee69067b8d58dac9eeb6697bbaf5d5c032a3070898389c41e7c4f3e3d7"}, | ||||||
|     {file = "aws_cdk_lib-2.178.2.tar.gz", hash = "sha256:c00757885b74023350bb34f388f6447155e802ecf827e595bda917098a4925fe"}, |     {file = "aws_cdk_lib-2.179.0.tar.gz", hash = "sha256:b653a55754f4020a4b36e4ae183d213e76e27b18b842cbf9e430e9eccb700550"}, | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [package.dependencies] | [package.dependencies] | ||||||
| "aws-cdk.asset-awscli-v1" = ">=2.2.208,<3.0.0" | "aws-cdk.asset-awscli-v1" = ">=2.2.208,<3.0.0" | ||||||
| "aws-cdk.asset-kubectl-v20" = ">=2.1.3,<3.0.0" |  | ||||||
| "aws-cdk.asset-node-proxy-agent-v6" = ">=2.1.0,<3.0.0" | "aws-cdk.asset-node-proxy-agent-v6" = ">=2.1.0,<3.0.0" | ||||||
| "aws-cdk.cloud-assembly-schema" = ">=39.2.0,<40.0.0" | "aws-cdk.cloud-assembly-schema" = ">=39.2.0,<40.0.0" | ||||||
| constructs = ">=10.0.0,<11.0.0" | constructs = ">=10.0.0,<11.0.0" | ||||||
| @ -466,13 +449,13 @@ typing-extensions = ">=4.0.0" | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "bandit" | name = "bandit" | ||||||
| version = "1.8.2" | version = "1.8.3" | ||||||
| description = "Security oriented static analyser for python code." | description = "Security oriented static analyser for python code." | ||||||
| optional = false | optional = false | ||||||
| python-versions = ">=3.9" | python-versions = ">=3.9" | ||||||
| files = [ | files = [ | ||||||
|     {file = "bandit-1.8.2-py3-none-any.whl", hash = "sha256:df6146ad73dd30e8cbda4e29689ddda48364e36ff655dbfc86998401fcf1721f"}, |     {file = "bandit-1.8.3-py3-none-any.whl", hash = "sha256:28f04dc0d258e1dd0f99dee8eefa13d1cb5e3fde1a5ab0c523971f97b289bcd8"}, | ||||||
|     {file = "bandit-1.8.2.tar.gz", hash = "sha256:e00ad5a6bc676c0954669fe13818024d66b70e42cf5adb971480cf3b671e835f"}, |     {file = "bandit-1.8.3.tar.gz", hash = "sha256:f5847beb654d309422985c36644649924e0ea4425c76dec2e89110b87506193a"}, | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [package.dependencies] | [package.dependencies] | ||||||
| @ -1884,6 +1867,17 @@ files = [ | |||||||
|     {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, |     {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "geographiclib" | ||||||
|  | version = "2.0" | ||||||
|  | description = "The geodesic routines from GeographicLib" | ||||||
|  | optional = false | ||||||
|  | python-versions = ">=3.7" | ||||||
|  | files = [ | ||||||
|  |     {file = "geographiclib-2.0-py3-none-any.whl", hash = "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734"}, | ||||||
|  |     {file = "geographiclib-2.0.tar.gz", hash = "sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859"}, | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "geoip2" | name = "geoip2" | ||||||
| version = "5.0.1" | version = "5.0.1" | ||||||
| @ -1903,6 +1897,29 @@ requests = ">=2.24.0,<3.0.0" | |||||||
| [package.extras] | [package.extras] | ||||||
| test = ["pytest-httpserver (>=1.0.10)"] | test = ["pytest-httpserver (>=1.0.10)"] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "geopy" | ||||||
|  | version = "2.4.1" | ||||||
|  | description = "Python Geocoding Toolbox" | ||||||
|  | optional = false | ||||||
|  | python-versions = ">=3.7" | ||||||
|  | files = [ | ||||||
|  |     {file = "geopy-2.4.1-py3-none-any.whl", hash = "sha256:ae8b4bc5c1131820f4d75fce9d4aaaca0c85189b3aa5d64c3dcaf5e3b7b882a7"}, | ||||||
|  |     {file = "geopy-2.4.1.tar.gz", hash = "sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1"}, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [package.dependencies] | ||||||
|  | geographiclib = ">=1.52,<3" | ||||||
|  |  | ||||||
|  | [package.extras] | ||||||
|  | aiohttp = ["aiohttp"] | ||||||
|  | dev = ["coverage", "flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"] | ||||||
|  | dev-docs = ["readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"] | ||||||
|  | dev-lint = ["flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)"] | ||||||
|  | dev-test = ["coverage", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "sphinx (<=4.3.2)"] | ||||||
|  | requests = ["requests (>=2.16.2)", "urllib3 (>=1.24.2)"] | ||||||
|  | timezone = ["pytz"] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "google-api-core" | name = "google-api-core" | ||||||
| version = "2.19.1" | version = "2.19.1" | ||||||
| @ -4611,13 +4628,13 @@ websocket-client = ">=1.8,<2.0" | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "sentry-sdk" | name = "sentry-sdk" | ||||||
| version = "2.21.0" | version = "2.22.0" | ||||||
| description = "Python client for Sentry (https://sentry.io)" | description = "Python client for Sentry (https://sentry.io)" | ||||||
| optional = false | optional = false | ||||||
| python-versions = ">=3.6" | python-versions = ">=3.6" | ||||||
| files = [ | files = [ | ||||||
|     {file = "sentry_sdk-2.21.0-py2.py3-none-any.whl", hash = "sha256:7623cfa9e2c8150948a81ca253b8e2bfe4ce0b96ab12f8cd78e3ac9c490fd92f"}, |     {file = "sentry_sdk-2.22.0-py2.py3-none-any.whl", hash = "sha256:3d791d631a6c97aad4da7074081a57073126c69487560c6f8bffcf586461de66"}, | ||||||
|     {file = "sentry_sdk-2.21.0.tar.gz", hash = "sha256:a6d38e0fb35edda191acf80b188ec713c863aaa5ad8d5798decb8671d02077b6"}, |     {file = "sentry_sdk-2.22.0.tar.gz", hash = "sha256:b4bf43bb38f547c84b2eadcefbe389b36ef75f3f38253d7a74d6b928c07ae944"}, | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [package.dependencies] | [package.dependencies] | ||||||
| @ -4661,6 +4678,7 @@ sanic = ["sanic (>=0.8)"] | |||||||
| sqlalchemy = ["sqlalchemy (>=1.2)"] | sqlalchemy = ["sqlalchemy (>=1.2)"] | ||||||
| starlette = ["starlette (>=0.19.1)"] | starlette = ["starlette (>=0.19.1)"] | ||||||
| starlite = ["starlite (>=1.48)"] | starlite = ["starlite (>=1.48)"] | ||||||
|  | statsig = ["statsig (>=0.55.3)"] | ||||||
| tornado = ["tornado (>=6)"] | tornado = ["tornado (>=6)"] | ||||||
| unleash = ["UnleashClient (>=6.0.1)"] | unleash = ["UnleashClient (>=6.0.1)"] | ||||||
|  |  | ||||||
| @ -5847,4 +5865,4 @@ files = [ | |||||||
| [metadata] | [metadata] | ||||||
| lock-version = "2.0" | lock-version = "2.0" | ||||||
| python-versions = "~3.12" | python-versions = "~3.12" | ||||||
| content-hash = "a3915ac2ef2bb53f7cd67070912cdaf717c3bf73ed972fa337a9b07fce162451" | content-hash = "8a6bfd4833e415a9f4f613ab4f33e60c8332b9f5743583222cdb7190f6286216" | ||||||
|  | |||||||
| @ -113,6 +113,7 @@ duo-client = "*" | |||||||
| fido2 = "*" | fido2 = "*" | ||||||
| flower = "*" | flower = "*" | ||||||
| geoip2 = "*" | geoip2 = "*" | ||||||
|  | geopy = "*" | ||||||
| google-api-python-client = "*" | google-api-python-client = "*" | ||||||
| gunicorn = "*" | gunicorn = "*" | ||||||
| gssapi = "*" | gssapi = "*" | ||||||
|  | |||||||
							
								
								
									
										71
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								schema.yml
									
									
									
									
									
								
							| @ -39482,6 +39482,7 @@ components: | |||||||
|       - authentik.providers.ldap |       - authentik.providers.ldap | ||||||
|       - authentik.providers.oauth2 |       - authentik.providers.oauth2 | ||||||
|       - authentik.providers.proxy |       - authentik.providers.proxy | ||||||
|  |       - authentik.providers.rac | ||||||
|       - authentik.providers.radius |       - authentik.providers.radius | ||||||
|       - authentik.providers.saml |       - authentik.providers.saml | ||||||
|       - authentik.providers.scim |       - authentik.providers.scim | ||||||
| @ -39522,7 +39523,6 @@ components: | |||||||
|       - authentik.enterprise.audit |       - authentik.enterprise.audit | ||||||
|       - authentik.enterprise.providers.google_workspace |       - authentik.enterprise.providers.google_workspace | ||||||
|       - authentik.enterprise.providers.microsoft_entra |       - authentik.enterprise.providers.microsoft_entra | ||||||
|       - authentik.enterprise.providers.rac |  | ||||||
|       - authentik.enterprise.providers.ssf |       - authentik.enterprise.providers.ssf | ||||||
|       - authentik.enterprise.stages.authenticator_endpoint_gdtc |       - authentik.enterprise.stages.authenticator_endpoint_gdtc | ||||||
|       - authentik.enterprise.stages.source |       - authentik.enterprise.stages.source | ||||||
| @ -44006,6 +44006,27 @@ components: | |||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/DetailedCountryField' |             $ref: '#/components/schemas/DetailedCountryField' | ||||||
|           readOnly: true |           readOnly: true | ||||||
|  |         check_history_distance: | ||||||
|  |           type: boolean | ||||||
|  |         history_max_distance_km: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 9223372036854775807 | ||||||
|  |           minimum: 0 | ||||||
|  |           format: int64 | ||||||
|  |         distance_tolerance_km: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 2147483647 | ||||||
|  |           minimum: 0 | ||||||
|  |         history_login_count: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 2147483647 | ||||||
|  |           minimum: 0 | ||||||
|  |         check_impossible_travel: | ||||||
|  |           type: boolean | ||||||
|  |         impossible_tolerance_km: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 2147483647 | ||||||
|  |           minimum: 0 | ||||||
|       required: |       required: | ||||||
|       - bound_to |       - bound_to | ||||||
|       - component |       - component | ||||||
| @ -44038,6 +44059,27 @@ components: | |||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/CountryCodeEnum' |             $ref: '#/components/schemas/CountryCodeEnum' | ||||||
|           maxItems: 249 |           maxItems: 249 | ||||||
|  |         check_history_distance: | ||||||
|  |           type: boolean | ||||||
|  |         history_max_distance_km: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 9223372036854775807 | ||||||
|  |           minimum: 0 | ||||||
|  |           format: int64 | ||||||
|  |         distance_tolerance_km: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 2147483647 | ||||||
|  |           minimum: 0 | ||||||
|  |         history_login_count: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 2147483647 | ||||||
|  |           minimum: 0 | ||||||
|  |         check_impossible_travel: | ||||||
|  |           type: boolean | ||||||
|  |         impossible_tolerance_km: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 2147483647 | ||||||
|  |           minimum: 0 | ||||||
|       required: |       required: | ||||||
|       - countries |       - countries | ||||||
|       - name |       - name | ||||||
| @ -46583,6 +46625,9 @@ components: | |||||||
|       - authentik_providers_oauth2.scopemapping |       - authentik_providers_oauth2.scopemapping | ||||||
|       - authentik_providers_oauth2.oauth2provider |       - authentik_providers_oauth2.oauth2provider | ||||||
|       - authentik_providers_proxy.proxyprovider |       - authentik_providers_proxy.proxyprovider | ||||||
|  |       - authentik_providers_rac.racprovider | ||||||
|  |       - authentik_providers_rac.endpoint | ||||||
|  |       - authentik_providers_rac.racpropertymapping | ||||||
|       - authentik_providers_radius.radiusprovider |       - authentik_providers_radius.radiusprovider | ||||||
|       - authentik_providers_radius.radiusproviderpropertymapping |       - authentik_providers_radius.radiusproviderpropertymapping | ||||||
|       - authentik_providers_saml.samlprovider |       - authentik_providers_saml.samlprovider | ||||||
| @ -46652,9 +46697,6 @@ components: | |||||||
|       - authentik_providers_google_workspace.googleworkspaceprovidermapping |       - authentik_providers_google_workspace.googleworkspaceprovidermapping | ||||||
|       - authentik_providers_microsoft_entra.microsoftentraprovider |       - authentik_providers_microsoft_entra.microsoftentraprovider | ||||||
|       - authentik_providers_microsoft_entra.microsoftentraprovidermapping |       - authentik_providers_microsoft_entra.microsoftentraprovidermapping | ||||||
|       - authentik_providers_rac.racprovider |  | ||||||
|       - authentik_providers_rac.endpoint |  | ||||||
|       - authentik_providers_rac.racpropertymapping |  | ||||||
|       - authentik_providers_ssf.ssfprovider |       - authentik_providers_ssf.ssfprovider | ||||||
|       - authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage |       - authentik_stages_authenticator_endpoint_gdtc.authenticatorendpointgdtcstage | ||||||
|       - authentik_stages_source.sourcestage |       - authentik_stages_source.sourcestage | ||||||
| @ -50557,6 +50599,27 @@ components: | |||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/CountryCodeEnum' |             $ref: '#/components/schemas/CountryCodeEnum' | ||||||
|           maxItems: 249 |           maxItems: 249 | ||||||
|  |         check_history_distance: | ||||||
|  |           type: boolean | ||||||
|  |         history_max_distance_km: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 9223372036854775807 | ||||||
|  |           minimum: 0 | ||||||
|  |           format: int64 | ||||||
|  |         distance_tolerance_km: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 2147483647 | ||||||
|  |           minimum: 0 | ||||||
|  |         history_login_count: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 2147483647 | ||||||
|  |           minimum: 0 | ||||||
|  |         check_impossible_travel: | ||||||
|  |           type: boolean | ||||||
|  |         impossible_tolerance_km: | ||||||
|  |           type: integer | ||||||
|  |           maximum: 2147483647 | ||||||
|  |           minimum: 0 | ||||||
|     PatchedGoogleWorkspaceProviderMappingRequest: |     PatchedGoogleWorkspaceProviderMappingRequest: | ||||||
|       type: object |       type: object | ||||||
|       description: GoogleWorkspaceProviderMapping Serializer |       description: GoogleWorkspaceProviderMapping Serializer | ||||||
|  | |||||||
| @ -74,7 +74,7 @@ const interfaces = [ | |||||||
|     ["user/UserInterface.ts", "user"], |     ["user/UserInterface.ts", "user"], | ||||||
|     ["flow/FlowInterface.ts", "flow"], |     ["flow/FlowInterface.ts", "flow"], | ||||||
|     ["standalone/api-browser/index.ts", "standalone/api-browser"], |     ["standalone/api-browser/index.ts", "standalone/api-browser"], | ||||||
|     ["enterprise/rac/index.ts", "enterprise/rac"], |     ["rac/index.ts", "rac"], | ||||||
|     ["standalone/loading/index.ts", "standalone/loading"], |     ["standalone/loading/index.ts", "standalone/loading"], | ||||||
|     ["polyfill/poly.ts", "."], |     ["polyfill/poly.ts", "."], | ||||||
| ]; | ]; | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -23,7 +23,7 @@ | |||||||
|                 "@floating-ui/dom": "^1.6.11", |                 "@floating-ui/dom": "^1.6.11", | ||||||
|                 "@formatjs/intl-listformat": "^7.5.7", |                 "@formatjs/intl-listformat": "^7.5.7", | ||||||
|                 "@fortawesome/fontawesome-free": "^6.6.0", |                 "@fortawesome/fontawesome-free": "^6.6.0", | ||||||
|                 "@goauthentik/api": "^2024.12.3-1739801838", |                 "@goauthentik/api": "^2024.12.3-1739965710", | ||||||
|                 "@lit-labs/ssr": "^3.2.2", |                 "@lit-labs/ssr": "^3.2.2", | ||||||
|                 "@lit/context": "^1.1.2", |                 "@lit/context": "^1.1.2", | ||||||
|                 "@lit/localize": "^0.12.2", |                 "@lit/localize": "^0.12.2", | ||||||
| @ -1814,9 +1814,9 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/@goauthentik/api": { |         "node_modules/@goauthentik/api": { | ||||||
|             "version": "2024.12.3-1739801838", |             "version": "2024.12.3-1739965710", | ||||||
|             "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.12.3-1739801838.tgz", |             "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.12.3-1739965710.tgz", | ||||||
|             "integrity": "sha512-gK+chjueX2MbHyjeYczx+plapSRmzaTkZDH6PFwulcvxIFb3awfMkw4cWDjwHZunfDgu8EoP50cPlF3AA/PNoQ==" |             "integrity": "sha512-16zoQWeJhAFSwttvqLRoXoQA43tMW1ZXDEihW6r8rtWtlxqPh7n36RtcWYraYiLcjmJskI90zdgz6k1kmY5AXw==" | ||||||
|         }, |         }, | ||||||
|         "node_modules/@goauthentik/web": { |         "node_modules/@goauthentik/web": { | ||||||
|             "resolved": "", |             "resolved": "", | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ | |||||||
|         "@floating-ui/dom": "^1.6.11", |         "@floating-ui/dom": "^1.6.11", | ||||||
|         "@formatjs/intl-listformat": "^7.5.7", |         "@formatjs/intl-listformat": "^7.5.7", | ||||||
|         "@fortawesome/fontawesome-free": "^6.6.0", |         "@fortawesome/fontawesome-free": "^6.6.0", | ||||||
|         "@goauthentik/api": "^2024.12.3-1739801838", |         "@goauthentik/api": "^2024.12.3-1739965710", | ||||||
|         "@lit-labs/ssr": "^3.2.2", |         "@lit-labs/ssr": "^3.2.2", | ||||||
|         "@lit/context": "^1.1.2", |         "@lit/context": "^1.1.2", | ||||||
|         "@lit/localize": "^0.12.2", |         "@lit/localize": "^0.12.2", | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ const config: KnipConfig = { | |||||||
|         "./src/user/UserInterface.ts", |         "./src/user/UserInterface.ts", | ||||||
|         "./src/flow/FlowInterface.ts", |         "./src/flow/FlowInterface.ts", | ||||||
|         "./src/standalone/api-browser/index.ts", |         "./src/standalone/api-browser/index.ts", | ||||||
|         "./src/enterprise/rac/index.ts", |         "./src/rac/index.ts", | ||||||
|         "./src/standalone/loading/index.ts", |         "./src/standalone/loading/index.ts", | ||||||
|         "./src/polyfill/poly.ts", |         "./src/polyfill/poly.ts", | ||||||
|     ], |     ], | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { BasePolicyForm } from "@goauthentik/admin/policies/BasePolicyForm"; | import { BasePolicyForm } from "@goauthentik/admin/policies/BasePolicyForm"; | ||||||
| import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; | ||||||
|  | import { first } from "@goauthentik/common/utils"; | ||||||
| import "@goauthentik/elements/ak-dual-select"; | import "@goauthentik/elements/ak-dual-select"; | ||||||
| import { DataProvision, DualSelectPair } from "@goauthentik/elements/ak-dual-select/types"; | import { DataProvision, DualSelectPair } from "@goauthentik/elements/ak-dual-select/types"; | ||||||
| import "@goauthentik/elements/forms/FormGroup"; | import "@goauthentik/elements/forms/FormGroup"; | ||||||
| @ -46,7 +47,7 @@ export class GeoIPPolicyForm extends BasePolicyForm<GeoIPPolicy> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     renderForm(): TemplateResult { |     renderForm(): TemplateResult { | ||||||
|         return html` <span> |         return html`<span> | ||||||
|                 ${msg( |                 ${msg( | ||||||
|                     "Ensure the user satisfies requirements of geography or network topology, based on IP address. If any of the configured values match, the policy passes.", |                     "Ensure the user satisfies requirements of geography or network topology, based on IP address. If any of the configured values match, the policy passes.", | ||||||
|                 )} |                 )} | ||||||
| @ -79,13 +80,125 @@ export class GeoIPPolicyForm extends BasePolicyForm<GeoIPPolicy> { | |||||||
|                     )} |                     )} | ||||||
|                 </p> |                 </p> | ||||||
|             </ak-form-element-horizontal> |             </ak-form-element-horizontal> | ||||||
|             <ak-form-group .expanded=${true}> |             <ak-form-group> | ||||||
|                 <span slot="header"> ${msg("Policy-specific settings")} </span> |                 <span slot="header"> ${msg("Distance settings")} </span> | ||||||
|  |                 <div slot="body" class="pf-c-form"> | ||||||
|  |                     <ak-form-element-horizontal name="checkHistoryDistance"> | ||||||
|  |                         <label class="pf-c-switch"> | ||||||
|  |                             <input | ||||||
|  |                                 class="pf-c-switch__input" | ||||||
|  |                                 type="checkbox" | ||||||
|  |                                 ?checked=${this.instance?.checkHistoryDistance ?? false} | ||||||
|  |                             /> | ||||||
|  |                             <span class="pf-c-switch__toggle"> | ||||||
|  |                                 <span class="pf-c-switch__toggle-icon"> | ||||||
|  |                                     <i class="fas fa-check" aria-hidden="true"></i> | ||||||
|  |                                 </span> | ||||||
|  |                             </span> | ||||||
|  |                             <span class="pf-c-switch__label" | ||||||
|  |                                 >${msg("Check historical distance of logins")}</span | ||||||
|  |                             > | ||||||
|  |                         </label> | ||||||
|  |                         <p class="pf-c-form__helper-text"> | ||||||
|  |                             ${msg( | ||||||
|  |                                 "When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins.", | ||||||
|  |                             )} | ||||||
|  |                         </p> | ||||||
|  |                     </ak-form-element-horizontal> | ||||||
|  |                     <ak-form-element-horizontal | ||||||
|  |                         label=${msg("Distance tolerance")} | ||||||
|  |                         name="distanceToleranceKm" | ||||||
|  |                     > | ||||||
|  |                         <input | ||||||
|  |                             type="number" | ||||||
|  |                             min="1" | ||||||
|  |                             value="${first(this.instance?.distanceToleranceKm, 50)}" | ||||||
|  |                             class="pf-c-form-control" | ||||||
|  |                         /> | ||||||
|  |                         <p class="pf-c-form__helper-text"> | ||||||
|  |                             ${msg("Tolerance in checking for distances in kilometers.")} | ||||||
|  |                         </p> | ||||||
|  |                     </ak-form-element-horizontal> | ||||||
|  |                     <ak-form-element-horizontal | ||||||
|  |                         label=${msg("Historical Login Count")} | ||||||
|  |                         name="historyLoginCount" | ||||||
|  |                     > | ||||||
|  |                         <input | ||||||
|  |                             type="number" | ||||||
|  |                             min="1" | ||||||
|  |                             value="${first(this.instance?.historyLoginCount, 5)}" | ||||||
|  |                             class="pf-c-form-control" | ||||||
|  |                         /> | ||||||
|  |                         <p class="pf-c-form__helper-text"> | ||||||
|  |                             ${msg("Amount of previous login events to check against.")} | ||||||
|  |                         </p> | ||||||
|  |                     </ak-form-element-horizontal> | ||||||
|  |                     <ak-form-element-horizontal | ||||||
|  |                         label=${msg("Maximum distance")} | ||||||
|  |                         name="historyMaxDistanceKm" | ||||||
|  |                     > | ||||||
|  |                         <input | ||||||
|  |                             type="number" | ||||||
|  |                             min="1" | ||||||
|  |                             value="${first(this.instance?.historyMaxDistanceKm, 100)}" | ||||||
|  |                             class="pf-c-form-control" | ||||||
|  |                         /> | ||||||
|  |                         <p class="pf-c-form__helper-text"> | ||||||
|  |                             ${msg( | ||||||
|  |                                 "Maximum distance a login attempt is allowed from in kilometers.", | ||||||
|  |                             )} | ||||||
|  |                         </p> | ||||||
|  |                     </ak-form-element-horizontal> | ||||||
|  |                 </div> | ||||||
|  |             </ak-form-group> | ||||||
|  |             <ak-form-group> | ||||||
|  |                 <span slot="header"> ${msg("Distance settings (Impossible travel)")} </span> | ||||||
|  |                 <div slot="body" class="pf-c-form"> | ||||||
|  |                     <ak-form-element-horizontal name="checkImpossibleTravel"> | ||||||
|  |                         <label class="pf-c-switch"> | ||||||
|  |                             <input | ||||||
|  |                                 class="pf-c-switch__input" | ||||||
|  |                                 type="checkbox" | ||||||
|  |                                 ?checked=${this.instance?.checkImpossibleTravel ?? true} | ||||||
|  |                             /> | ||||||
|  |                             <span class="pf-c-switch__toggle"> | ||||||
|  |                                 <span class="pf-c-switch__toggle-icon"> | ||||||
|  |                                     <i class="fas fa-check" aria-hidden="true"></i> | ||||||
|  |                                 </span> | ||||||
|  |                             </span> | ||||||
|  |                             <span class="pf-c-switch__label" | ||||||
|  |                                 >${msg("Check impossible travel")}</span | ||||||
|  |                             > | ||||||
|  |                         </label> | ||||||
|  |                         <p class="pf-c-form__helper-text"> | ||||||
|  |                             ${msg( | ||||||
|  |                                 "When this option enabled, the GeoIP data of the policy request is compared to the specified number of historical logins and if the travel would have been possible in the amount of time since the previous event.", | ||||||
|  |                             )} | ||||||
|  |                         </p> | ||||||
|  |                     </ak-form-element-horizontal> | ||||||
|  |                     <ak-form-element-horizontal | ||||||
|  |                         label=${msg("Impossible travel tolerance")} | ||||||
|  |                         name="impossibleToleranceKm" | ||||||
|  |                     > | ||||||
|  |                         <input | ||||||
|  |                             type="number" | ||||||
|  |                             min="1" | ||||||
|  |                             value="${first(this.instance?.impossibleToleranceKm, 50)}" | ||||||
|  |                             class="pf-c-form-control" | ||||||
|  |                         /> | ||||||
|  |                         <p class="pf-c-form__helper-text"> | ||||||
|  |                             ${msg("Tolerance in checking for distances in kilometers.")} | ||||||
|  |                         </p> | ||||||
|  |                     </ak-form-element-horizontal> | ||||||
|  |                 </div> | ||||||
|  |             </ak-form-group> | ||||||
|  |             <ak-form-group> | ||||||
|  |                 <span slot="header">${msg("Static rule settings")}</span> | ||||||
|                 <div slot="body" class="pf-c-form"> |                 <div slot="body" class="pf-c-form"> | ||||||
|                     <ak-form-element-horizontal label=${msg("ASNs")} name="asns"> |                     <ak-form-element-horizontal label=${msg("ASNs")} name="asns"> | ||||||
|                         <input |                         <input | ||||||
|                             type="text" |                             type="text" | ||||||
|                             value="${this.instance?.asns ?? ""}" |                             value="${this.instance?.asns?.join(",") ?? ""}" | ||||||
|                             class="pf-c-form-control pf-m-monospace" |                             class="pf-c-form-control pf-m-monospace" | ||||||
|                             autocomplete="off" |                             autocomplete="off" | ||||||
|                             spellcheck="false" |                             spellcheck="false" | ||||||
|  | |||||||
| @ -116,8 +116,13 @@ export class LibraryPage extends AKElement { | |||||||
|     @bound |     @bound | ||||||
|     launchRequest(event: LibraryPageSearchSelected) { |     launchRequest(event: LibraryPageSearchSelected) { | ||||||
|         event.stopPropagation(); |         event.stopPropagation(); | ||||||
|         if (this.selectedApp?.launchUrl) { |         if (!this.selectedApp?.launchUrl) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if (!this.selectedApp.openInNewTab) { | ||||||
|             window.location.assign(this.selectedApp?.launchUrl); |             window.location.assign(this.selectedApp?.launchUrl); | ||||||
|  |         } else { | ||||||
|  |             window.open(this.selectedApp.launchUrl); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										171
									
								
								website/docs/releases/2025/v2025.2.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								website/docs/releases/2025/v2025.2.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,171 @@ | |||||||
|  | --- | ||||||
|  | title: Release 2025.2 | ||||||
|  | slug: "/releases/2025.2" | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | :::::note | ||||||
|  | 2025.2 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 2025.2.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. | ||||||
|  | ::::: | ||||||
|  |  | ||||||
|  | ## Highlights | ||||||
|  |  | ||||||
|  | - **SSF Provider <span class="badge badge--primary">Enterprise</span> <span class="badge badge--info">Preview</span>** Add support for Shared Signals Framework | ||||||
|  |   TODO: Add preview banner to UI | ||||||
|  | - **RAC moved open source** Remote access is now available to everyone! | ||||||
|  | - **GeoIP distance and impossible travel checks** Add the ability to check for the distance a user has moved compared to a previous login, and if the user could have travelled the distance | ||||||
|  | - **Email OTP Stage** Allow users to use their email accounts as a one-time-password during authentication | ||||||
|  | - **Fine-grained permission for superuser toggle on groups** Setting the **Is superuser** toggle on a group now requires a separate permission. | ||||||
|  |  | ||||||
|  | ## Breaking changes | ||||||
|  |  | ||||||
|  | - **Deprecated and frozen `:latest` container image tag after 2025.2** | ||||||
|  |  | ||||||
|  |     Using the `:latest` tag with container images is not recommended as it can lead to unintentional updates and potentially broken setups. | ||||||
|  |  | ||||||
|  |     The tag will not be removed, however it will also not be updated past 2025.2. | ||||||
|  |  | ||||||
|  |     We strongly recommended the use of a specific version tag for authentik instances' container images like `:2025.2`. | ||||||
|  |  | ||||||
|  | ## New features | ||||||
|  |  | ||||||
|  | - SSF Provider <span class="badge badge--primary">Enterprise</span> <span class="badge badge--info">Preview</span> | ||||||
|  |  | ||||||
|  |     [Shared Signals Framework](#todo) allows applications to register a stream with authentik within which they can received events from authentik such as when a session was revoked or a credential was add/changed/deleted and execute actions based on these events. | ||||||
|  |  | ||||||
|  |     This allows admins to integrate authentik with Apple Business/School Manager for federated Apple IDs. See the integration docs [here](#todo) | ||||||
|  |  | ||||||
|  | - RAC to open source | ||||||
|  |  | ||||||
|  |     Remote access (RDP, VNC and SSH) has moved from enterprise to our free, open source code. We try our best to limit enterprise-specific functionality to features that would be non-essential to homelab users and far more valuable to enterprise use cases. We've had a variety of homelab users reach out with excellent use cases for RAC functionality, so while this will mean giving up some potential revenue, we think that opening up RAC to the community is the right thing to do! | ||||||
|  |  | ||||||
|  | - GeoIP distance and impossible travel checks | ||||||
|  |  | ||||||
|  |     Add the ability to check for the distance a user has moved compared to a previous login, and add the option to check impossible travel distances based on client IP. | ||||||
|  |  | ||||||
|  |     These options can be used to detect and prevent access from potentially stolen authentik sessions or stolen devices. | ||||||
|  |  | ||||||
|  | - Email OTP Stage | ||||||
|  |  | ||||||
|  |     Admins now have the ability to configure the option for users to use their email as an authenticator. Users that already have an email address set on their account will be able to use that address to receive one-time-passwords. It is also possible to configure authentik to allow users to add additional email addresses as authenticators. | ||||||
|  |  | ||||||
|  |     See [Email OTP Stage](#todo) | ||||||
|  |  | ||||||
|  | - Application Wizard is the default way to create applications | ||||||
|  |  | ||||||
|  |     The default way of creating an application now allows admins to configure the provider and any kind of bindings without having to jump through different sections of the UI. The previous way of creating an application is and will stay available alongside the new and streamlined method. | ||||||
|  |  | ||||||
|  | - Fine-grained permission for superuser toggle on groups | ||||||
|  |  | ||||||
|  |     Setting the **Is superuser** toggle on a group now requires a separate permission, making it much easier to allow for delegated management of groups without risking the ability for users to self-elevate permissions. | ||||||
|  |  | ||||||
|  | - Improved debugging experience | ||||||
|  |  | ||||||
|  |     For people developing authentik or building very complex, custom integrations, configuring debugging in authentik is now documented [here](#todo) | ||||||
|  |  | ||||||
|  | ## TODO | ||||||
|  |  | ||||||
|  | temp | ||||||
|  |  | ||||||
|  | ## Upgrading | ||||||
|  |  | ||||||
|  | This release does not introduce any new requirements. You can follow the upgrade instructions below; for more detailed information about upgrading authentik, refer to our [Upgrade documentation](../../install-config/upgrade.mdx). | ||||||
|  |  | ||||||
|  | :::warning | ||||||
|  | When you upgrade, be aware that the version of the authentik instance and of any outposts must be the same. We recommended that you always upgrade any outposts at the same time you upgrade your authentik instance. | ||||||
|  | ::: | ||||||
|  |  | ||||||
|  | ### Docker Compose | ||||||
|  |  | ||||||
|  | To upgrade, download the new docker-compose file and update the Docker stack with the new version, using these commands: | ||||||
|  |  | ||||||
|  | ```shell | ||||||
|  | wget -O docker-compose.yml https://goauthentik.io/version/2025.2/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 ^2025.2 | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Minor changes/fixes | ||||||
|  |  | ||||||
|  | - admin: monitor worker version (#12463) | ||||||
|  | - api: cleanup owner permissions (#12598) | ||||||
|  | - blueprints: add REPL for blueprint YAML tags (#9223) | ||||||
|  | - blueprints: fix schema for meta models (#12421) | ||||||
|  | - core: add indexes on ExpiringModel (#12658) | ||||||
|  | - core: fix application entitlements not creatable with blueprints (#12673) | ||||||
|  | - core: fix error when creating new user with default path (#12609) | ||||||
|  | - core: fix generic sources not being fetchable by pk (#12896) | ||||||
|  | - core: fix permissions for admin device listing (#12787) | ||||||
|  | - core: search users' attributes (#12740) | ||||||
|  | - core: show last password change date (#12958) | ||||||
|  | - enterprise/providers: SSF (#12327) | ||||||
|  | - enterprise/providers/SSF: fix a couple of bugs after real world testing (#12987) | ||||||
|  | - enterprise/rac: Improve client connection status & bugfixes (#12684) | ||||||
|  | - events: make sure password set event has the correct IP (#12585) | ||||||
|  | - events: notification_cleanup: avoid unnecessary loop (#12417) | ||||||
|  | - flows: clear flow state before redirecting to final URL (#12788) | ||||||
|  | - flows: fix history containing other plans (#12655) | ||||||
|  | - flows: fix inspector permission check (#12907) | ||||||
|  | - flows: more tests (#11587) | ||||||
|  | - flows: show policy messages in reevaluate marker (#12855) | ||||||
|  | - flows/inspector: add button to open flow inspector (#12656) | ||||||
|  | - internal: fix missing trailing slash in outpost websocket (#12470) | ||||||
|  | - internal: fix URL generation for websocket connection (#12439) | ||||||
|  | - lifecycle: update python to 3.12.8 (#12783) | ||||||
|  | - lifecycle/migrate: don't migrate tenants if not enabled (#12850) | ||||||
|  | - outposts: fix version label (#12486) | ||||||
|  | - providers/oauth2: include scope in token response (#12921) | ||||||
|  | - providers/oauth2: support token revocation for public clients (#12704) | ||||||
|  | - providers/saml: fix handle Accept: application/xml for SAML Metadata endpoint (#12483) (#12518) | ||||||
|  | - providers/saml: fix invalid SAML Response when assertion and response are signed (#12611) | ||||||
|  | - providers/saml: provide generic metadata url when possible (#12413) | ||||||
|  | - rbac: exclude permissions for internal models (#12803) | ||||||
|  | - rbac: permissions endpoint: allow authenticated users (#12608) | ||||||
|  | - root: backport version bump (#12426) | ||||||
|  | - root: docker: ensure apt packages are up-to-date (#12683) | ||||||
|  | - root: expose CONN_MAX_AGE, CONN_HEALTH_CHECKS and DISABLE_SERVER_SIDE_CURSORS for PostgreSQL config (#10159) | ||||||
|  | - root: fix dev build version being invalid semver (#12472) | ||||||
|  | - root: redis, make sure tlscacert isn't an empty string (#12407) | ||||||
|  | - sources: allow uuid or slug to be used for retrieving a source (#12780) | ||||||
|  | - sources: allow uuid or slug to be used for retrieving a source (2024.12 fix) (#12772) | ||||||
|  | - sources/kerberos: authenticate with the user's username instead of the first username in authentik (#12497) | ||||||
|  | - sources/kerberos: handle principal expire time (#12748) | ||||||
|  | - sources/oauth: fix authentication only being sent in form body (#12713) | ||||||
|  | - sources/scim: fix user creation (duplicate userName) (#12547) | ||||||
|  | - stages/authenticator: add user field to devices (#12636) | ||||||
|  | - stages/prompt: always show policy messages (#12765) | ||||||
|  | - stages/redirect: fix query parameter when redirecting to flow (#12750) | ||||||
|  | - web, core: fix grammatical issue in stage bindings (#10799) | ||||||
|  | - web: fix build dev build (#12473) | ||||||
|  | - web: fix error handling bug in ApplicationWizard.RACProviderForm (#12640) | ||||||
|  | - web: Fix issue where Codemirror partially applies OneDark theme. (#12811) | ||||||
|  | - web: fix mobile scrolling bug (#12601) | ||||||
|  | - web: fix source selection and outpost integration health (#12530) | ||||||
|  | - web: fix source selection and outpost integration health (#12530) | ||||||
|  | - web: fixes broken docLinks - url missing s (#12789) | ||||||
|  | - web: housekeeping, optimizations and small fixes (#12450) | ||||||
|  | - web: improve notification and API drawers (#12659) | ||||||
|  | - web: misc fixes for admin and flow inspector (#12461) | ||||||
|  | - web: only load version context when authenticated (#12482) | ||||||
|  | - web: update gen-client-ts to OpenAPI 7.11.0 (#12756) | ||||||
|  | - web/admin: fix role changelog missing primary key filter (#12671) | ||||||
|  | - web/admin: improve user display view (#12988) | ||||||
|  | - web/admin: more cleanup and consistency (#12657) | ||||||
|  | - web/admin: Refine navigation (#12441) | ||||||
|  | - web/components: ak-number-input: add support for min (#12703) | ||||||
|  | - web/flows: fix `login` / `log in` inconsistency (#12526) | ||||||
|  |  | ||||||
|  | ## API Changes | ||||||
|  |  | ||||||
|  | <!-- _Insert output of `make gen-diff` here_ --> | ||||||
| @ -22,7 +22,7 @@ The following placeholders are used in this guide: | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 3. Select **OAuth2** from the left Menu | 3. Select **OAuth2** from the left menu | ||||||
|  |  | ||||||
| 4. Copy the **Client ID** and _save it for later_ | 4. Copy the **Client ID** and _save it for later_ | ||||||
|  |  | ||||||
| @ -38,8 +38,8 @@ Here is an example of a completed OAuth2 screen for Discord. | |||||||
|  |  | ||||||
| 8. Under _Directory -> Federation & Social login_ Click **Create Discord OAuth Source** | 8. Under _Directory -> Federation & Social login_ Click **Create Discord OAuth Source** | ||||||
|  |  | ||||||
| 9. **Name:** Choose a name (For the example I used Discord) | 9. **Name:** Choose a name (For the example I used `Discord`) | ||||||
| 10. **Slug:** discord (You can choose a different slug, if you do you will need to update the Discord redirect URLand point it to the correct slug.) | 10. **Slug:** discord (You can choose a different slug, if you do you will need to update the Discord redirect URL and point it to the correct slug.) | ||||||
| 11. **Consumer Key:** Client ID from step 4 | 11. **Consumer Key:** Client ID from step 4 | ||||||
| 12. **Consumer Secret:** Client Secret from step 5 | 12. **Consumer Secret:** Client Secret from step 5 | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										100
									
								
								website/integrations/services/adventurelog/index.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								website/integrations/services/adventurelog/index.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,100 @@ | |||||||
|  | --- | ||||||
|  | title: Integrate with AdventureLog | ||||||
|  | sidebar_label: AdventureLog | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | # Integrate with AdventureLog | ||||||
|  |  | ||||||
|  | <span class="badge badge--secondary">Support level: Community</span> | ||||||
|  |  | ||||||
|  | ## What is AdventureLog | ||||||
|  |  | ||||||
|  | > AdventureLog is a self-hosted travel tracker and trip planner. AdventureLog is the ultimate travel companion for the modern-day explorer. | ||||||
|  | > | ||||||
|  | > -- https://adventurelog.app/ | ||||||
|  |  | ||||||
|  | ## Preparation | ||||||
|  |  | ||||||
|  | The following placeholders are used in this guide: | ||||||
|  |  | ||||||
|  | - `https://adventurelog.company` is the FQDN of the AdventureLog server installation. | ||||||
|  | - `https://authentik.company` is the FQDN of the authentik installation. | ||||||
|  |  | ||||||
|  | :::note | ||||||
|  | This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application. | ||||||
|  | ::: | ||||||
|  |  | ||||||
|  | ## authentik configuration | ||||||
|  |  | ||||||
|  | 1. Create a new OAuth2/OpenID Provider under **Applications** > **Providers** using the following settings: | ||||||
|  |     - **Name**: AdventureLog | ||||||
|  |     - **Authentication flow**: default-authentication-flow | ||||||
|  |     - **Authorization flow**: default-provider-authorization-explicit-consent | ||||||
|  |     - **Client type**: Confidential | ||||||
|  |     - **Client ID**: Either create your own Client ID or use the auto-populated ID | ||||||
|  |     - **Client Secret**: Either create your own Client Secret or use the auto-populated secret | ||||||
|  |       :::note | ||||||
|  |       Take note of the `Client ID` and `Client Secret` as they are required when configuring AdventureLog. | ||||||
|  |       ::: | ||||||
|  |     - **Redirect URIs/Origins (RegEx)**: | ||||||
|  |       :::note | ||||||
|  |       Make sure type is set to `RegEx` and the following RegEx is used. | ||||||
|  |       ::: | ||||||
|  |         - `^https://adventurelog.company/accounts/oidc/.*$` | ||||||
|  |     - **Signing Key**: authentik Self-signed Certificate | ||||||
|  |     - Leave everything else as default | ||||||
|  | 2. Open the new provider you've just created. | ||||||
|  | 3. Make a note of the **OpenID Configuration Issuer**. | ||||||
|  | 4. Navigate to **Applications -> Applications** and create a new application that uses the provider you just created. | ||||||
|  |  | ||||||
|  | ## AdventureLog configuration | ||||||
|  |  | ||||||
|  | AdventureLog documentation can be found here: https://adventurelog.app/docs/configuration/social_auth/authentik.html | ||||||
|  |  | ||||||
|  | This configuration is done in the Admin Panel. Launch the panel by clicking your user avatar in the navbar, selecting **Settings**, and then clicking **Launch Admin Panel**. Make sure you are logged in as an administrator for this to work. | ||||||
|  |  | ||||||
|  | Alternatively, navigate to `/admin` on your AdventureLog server. | ||||||
|  |  | ||||||
|  | 1. In the admin panel, scroll down to the **Social Accounts** section and click **Add** next to **Social applications**. Fill in the following fields: | ||||||
|  |  | ||||||
|  |     - Provider: OpenID Connect | ||||||
|  |     - Provider ID: authentik Client ID | ||||||
|  |     - Name: authentik | ||||||
|  |     - Client ID: authentik Client ID | ||||||
|  |     - Secret Key: authentik Client Secret | ||||||
|  |     - Key: _should be left blank_ | ||||||
|  |     - Settings: (make sure http/https is set correctly) | ||||||
|  |  | ||||||
|  |     ```json | ||||||
|  |     { | ||||||
|  |         "server_url": "https://authentik.company/application/o/[YOUR_SLUG]/" | ||||||
|  |     } | ||||||
|  |     ``` | ||||||
|  |  | ||||||
|  |     - Sites: move over the sites you want to enable authentik on, usually `example.com` and `www.example.com` unless you renamed your sites. | ||||||
|  |  | ||||||
|  | :::warning | ||||||
|  | `localhost` is most likely not a valid `server_url` for authentik in this instance because `localhost` is the server running AdventureLog, not authentik. You should use the IP address of the server running authentik or the domain name if you have one. | ||||||
|  | ::: | ||||||
|  |  | ||||||
|  | 2. Save the configuration. | ||||||
|  |  | ||||||
|  | Ensure that the authentik server is running and accessible by AdventureLog. Users should now be able to log in to AdventureLog using their authentik account. | ||||||
|  |  | ||||||
|  | ## Configuration validation | ||||||
|  |  | ||||||
|  | To validate the configuration, either link to an existing account as described below or naviage to the AdventureLog login page and click the **authentik** button to log in. You should be redirected to the authentik login page. After logging in, you should be redirected back to AdventureLog. | ||||||
|  |  | ||||||
|  | ### Linking to Existing Account | ||||||
|  |  | ||||||
|  | If a user has an existing AdventureLog account and wants to link it to their authentik account, they can do so by logging in to their AdventureLog account and navigating to the **Settings** page. There is a button that says **Launch Account Connections**, click that and then choose the provider to link to the existing account. | ||||||
|  |  | ||||||
|  | ## Troubleshooting | ||||||
|  |  | ||||||
|  | ### 404 error when logging in. | ||||||
|  |  | ||||||
|  | Ensure the `https://adventurelog.company/accounts` path is routed to the backend, as it shouldn't hit the frontend when it's properly configured. For information on how to configure this, refer to the AdventureLog documentation on reverse proxy configuration [here](https://adventurelog.app/docs/install/getting_started.html). | ||||||
|  |  | ||||||
|  | ### authentik - No Permission | ||||||
|  |  | ||||||
|  | Launch your authentik dashboard as an admin and find the AdventureLog app. Click **More details** then **Edit**. In the admin interface, click **Test** under **Check Access**. If you get a 403 error, you need to grant the user the correct permissions. This can be done by going to the user's profile and adding the correct permissions. | ||||||
| @ -1,95 +0,0 @@ | |||||||
| --- |  | ||||||
| title: Integrate with engomo |  | ||||||
| sidebar_label: engomo |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| # Integrate with engomo |  | ||||||
|  |  | ||||||
| <span class="badge badge--secondary">Support level: Community</span> |  | ||||||
|  |  | ||||||
| ## What is engomo |  | ||||||
|  |  | ||||||
| > engomo is an low-code app development platform to create enterprise apps for smartphones and tablets based on Android, iOS, or iPadOS. |  | ||||||
| > -- https://engomo.com/ |  | ||||||
| > |  | ||||||
| > This guide explains how to set up engomo to use authentik as the OAuth provider for the application login on the smartphone/tablet and login to the admin WebGUI (composer). |  | ||||||
|  |  | ||||||
| ## Preparation |  | ||||||
|  |  | ||||||
| The following placeholders are used in this guide: |  | ||||||
|  |  | ||||||
| - `engomo.company` is the FQDN of the engomo installation. |  | ||||||
| - `authentik.company` is the FQDN of the authentik installation. |  | ||||||
| - `engomo.mapping` is the name of the Scope Mapping. |  | ||||||
| - `ak.cert` is the self-signed certificate that will be used for the service provider. |  | ||||||
|  |  | ||||||
| :::note |  | ||||||
| This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application. |  | ||||||
| ::: |  | ||||||
|  |  | ||||||
| ## authentik configuration |  | ||||||
|  |  | ||||||
| In authentik, create a new scope mapping. To do so, log in and navigate to the Admin interface, then go to **Customization --> Property Mapping** and click **Create**. |  | ||||||
|  |  | ||||||
| - `engomo.mapping` is the value of the Mapping's name. |  | ||||||
| - `profile` is the value for the Scope name. |  | ||||||
| - `return {"preferred_username": request.user.email}` is the value for the Expression. |  | ||||||
|  |  | ||||||
| Create an application and an OAuth2/OpenID provider in authentik. Use the following parameters for the OAuth2/OpenID provider: |  | ||||||
|  |  | ||||||
| **Provider:** |  | ||||||
|  |  | ||||||
| - Name: `SP-engomo` |  | ||||||
| - Client type: `Public` |  | ||||||
| - Redirect URIs/Origins (RegEx): `https://engomo.company/auth` and `com.engomo.engomo://callback/` |  | ||||||
| - Signing Key: `ak.cert` |  | ||||||
| - Scopes: `authentik default OAuth Mapping: OpenID 'email', 'offline_access', OpenID 'openid'` and `engomo.mapping` |  | ||||||
|  |  | ||||||
| > [!IMPORTANT] |  | ||||||
| > Redirect URIs => write the values line by line. |  | ||||||
|  |  | ||||||
| Leave the rest as default values. The durations can be changed as needed. |  | ||||||
|  |  | ||||||
| **Application:** |  | ||||||
|  |  | ||||||
| - Name: `engomo` |  | ||||||
| - Slug: `engomo` |  | ||||||
| - Launch URL: `https://engomo.company/` |  | ||||||
|  |  | ||||||
| ## engomo configuration |  | ||||||
|  |  | ||||||
| Navigate to `https://engomo.company/composer` and log in with your admin credentials. |  | ||||||
|  |  | ||||||
| - Select `Server`. |  | ||||||
| - Select `Authentication`. |  | ||||||
| - Add a new authentication method by clicking on the plus icon on the right. |  | ||||||
| - Name: `authentik` |  | ||||||
| - Type: `OpenID Connect` |  | ||||||
| - Click **Create**. |  | ||||||
| - Set the `Issuer` to the authentik FQDN `https://authentik.company/application/o/engomo`. |  | ||||||
| - Set the `Client ID` to the Client ID from the SP-engomo provider that you created in authentik. |  | ||||||
| - Set the `Client Secret` to the Client Secret from the SP-engomo provider that you created in authentik. |  | ||||||
|  |  | ||||||
| Leave the rest as default. |  | ||||||
|  |  | ||||||
| ## engomo user creation |  | ||||||
|  |  | ||||||
| engomo doesn't create users automatically when signing in. So you have to do it manually right now. |  | ||||||
| Navigate to `https://engomo.company/composer` and log in with your admin credentials. |  | ||||||
|  |  | ||||||
| - Select `Users & Devices`. |  | ||||||
| - Click the plus button next in the Users section. |  | ||||||
| - Select `authentik` as the Authenticator in the dropdown. |  | ||||||
| - Create your user by typing in the email as the Username used in authentik. |  | ||||||
|  |  | ||||||
| At this point you are done. |  | ||||||
|  |  | ||||||
| ## Test the login |  | ||||||
|  |  | ||||||
| - Open a browser of your choice and open the URL `https://engomo.company`. |  | ||||||
| - Enter the created user's email address and click the small arrow icon to log in. |  | ||||||
| - You should be redirected to authentik (with the login flows you created) and then authentik should redirect you back to `https://engomo.company/composer` URL. |  | ||||||
| - If you are redirected back to the `https://engomo.company/composer` URL you did everything correct. |  | ||||||
|  |  | ||||||
| > [!IMPORTANT] |  | ||||||
| > The created user will only have access to the app or composer page if you granted the permission to the user of course. |  | ||||||
							
								
								
									
										87
									
								
								website/integrations/services/engomo/index.mdx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								website/integrations/services/engomo/index.mdx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,87 @@ | |||||||
|  | --- | ||||||
|  | title: Integrate with engomo | ||||||
|  | sidebar_label: engomo | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | # Integrate with engomo | ||||||
|  |  | ||||||
|  | <span class="badge badge--secondary">Support level: Community</span> | ||||||
|  |  | ||||||
|  | ## What is engomo | ||||||
|  |  | ||||||
|  | > engomo is an low-code app development platform to create enterprise apps for smartphones and tablets based on Android, iOS, or iPadOS. | ||||||
|  | > | ||||||
|  | > -- https://engomo.com/ | ||||||
|  | > | ||||||
|  | > This guide explains how to set up engomo to use authentik as the OAuth provider for the application login on the smartphone/tablet and login to the admin WebGUI (composer). | ||||||
|  |  | ||||||
|  | ## Preparation | ||||||
|  |  | ||||||
|  | The following placeholders are used in this guide: | ||||||
|  |  | ||||||
|  | - `engomo.company` is the FQDN of the engomo installation. | ||||||
|  | - `authentik.company` is the FQDN of the authentik installation. | ||||||
|  | - `engomo.mapping` is the name of the Scope Mapping. | ||||||
|  |  | ||||||
|  | :::note | ||||||
|  | This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application. | ||||||
|  | ::: | ||||||
|  |  | ||||||
|  | ## authentik configuration | ||||||
|  |  | ||||||
|  | In authentik, create a new scope mapping. To do so, log in and navigate to the Admin interface, then go to **Customization --> Property Mapping** and click **Create**. | ||||||
|  |  | ||||||
|  | - `engomo.mapping` is the value of the Mapping's name. | ||||||
|  | - `profile` is the value for the Scope name. | ||||||
|  | - `return {"preferred_username": request.user.email}` is the value for the Expression. | ||||||
|  |  | ||||||
|  | [Create](https://docs.goauthentik.io/docs/add-secure-apps/applications/manage_apps#add-new-applications) an OAuth2/OpenID provider and an application in authentik. Use the following parameters for the OAuth2/OpenID provider: | ||||||
|  |  | ||||||
|  | 1. In the authentik Admin interface, navigate to **Applications** -> **Applications**. | ||||||
|  | 2. Use the wizard to create a new application and provider. During this process: | ||||||
|  |     - Note the **Client ID**, **Client Secret**, and **slug** values for later use. | ||||||
|  |     - Select implicit or explicit authorization flow as desired. | ||||||
|  |     - Set Client type to `Public`. | ||||||
|  |     - Set the redirect URI to <kbd>https://<em>engomo.company</em>/auth</kbd> and <kbd>com.engomo.engomo://callback/</kbd>. | ||||||
|  |     - Select any available signing key. | ||||||
|  |     - Add the `engomo.mapping` scope in addition to the default values. | ||||||
|  |  | ||||||
|  | :::note | ||||||
|  | Redirect URIs => write the values line by line. | ||||||
|  | ::: | ||||||
|  |  | ||||||
|  | ## engomo configuration | ||||||
|  |  | ||||||
|  | Navigate to <kbd>https://<em>engomo.company</em>/composer</kbd> and log in with your admin credentials. | ||||||
|  |  | ||||||
|  | 1. Select **Server**. | ||||||
|  | 2. Select **Authentication**. | ||||||
|  | 3. Add a new authentication method by clicking on the plus icon on the right. | ||||||
|  | 4. Name: `authentik` | ||||||
|  | 5. Type: **OpenID Connect** | ||||||
|  | 6. Click **Create**. | ||||||
|  | 7. Configure the following values using information from the authentik provider: | ||||||
|  |     - Set **Issuer** to <kbd>https://<em>authentik.company</em>/application/o/<em>engomo</em></kbd>. | ||||||
|  |     - Set **Client ID** to the Client ID copied from authentik. | ||||||
|  |     - Set **Client secret** to the Client Secret copied from authentik. | ||||||
|  |  | ||||||
|  | ## engomo user creation | ||||||
|  |  | ||||||
|  | engomo doesn't create users automatically when signing in. So you have to do it manually right now. | ||||||
|  | Navigate to <kbd>https://<em>engomo.company</em>/composer</kbd> and log in with your admin credentials. | ||||||
|  |  | ||||||
|  | - Select **Users & Devices**. | ||||||
|  | - Click the plus button in the Users section. | ||||||
|  | - Choose `authentik` from the Authenticator dropdown. | ||||||
|  | - Create your user by entering the email address as the username. This email must match the one used for the user in authentik. | ||||||
|  |  | ||||||
|  | ## Test the login | ||||||
|  |  | ||||||
|  | - Open a browser of your choice and open the URL <kbd>https://<em>engomo.company</em></kbd>. | ||||||
|  | - Enter the created user's email address and click the small arrow icon to log in. | ||||||
|  | - You should be redirected to authentik (with the login flows you created) and then authentik should redirect you back to <kbd>https://<em>engomo.company</em>/composer</kbd> URL. | ||||||
|  | - If you are redirected back to the <kbd>https://<em>engomo.company</em>/composer</kbd> URL you did everything correct. | ||||||
|  |  | ||||||
|  | :::note | ||||||
|  | The created user will only have access to the app or composer page if they have been granted the necessary permissions. | ||||||
|  | ::: | ||||||
| @ -14,6 +14,8 @@ sidebar_label: RustDesk Server Pro | |||||||
| > Ideal for businesses, it provides full control over data while ensuring scalable and reliable remote access. | > Ideal for businesses, it provides full control over data while ensuring scalable and reliable remote access. | ||||||
| > | > | ||||||
| > -- https://rustdesk.com/ | > -- https://rustdesk.com/ | ||||||
|  | > | ||||||
|  | > This guide explains how to configure Rustdesk Server Pro to use authentik as the OAuth provider for logging in to the Web GUI. | ||||||
|  |  | ||||||
| ## Preparation | ## Preparation | ||||||
|  |  | ||||||
| @ -28,31 +30,30 @@ This documentation lists only the settings that you need to change from their de | |||||||
|  |  | ||||||
| ## authentik configuration | ## authentik configuration | ||||||
|  |  | ||||||
|  | [Create](https://docs.goauthentik.io/docs/add-secure-apps/applications/manage_apps#add-new-applications) an OAuth2/OpenID provider and an application in authentik. Use the following parameters for the OAuth2/OpenID provider: | ||||||
|  |  | ||||||
| 1. In the authentik Admin interface, navigate to **Applications** -> **Applications**. | 1. In the authentik Admin interface, navigate to **Applications** -> **Applications**. | ||||||
| 2. Use the wizard to create a new application and provider. During this process: | 2. Use the wizard to create a new application and provider. During this process: | ||||||
|     - Note the **Client ID**, **Client Secret**, and **slug** values for later use. |     - Note the **Client ID**, **Client Secret**, and **slug** values for later use. | ||||||
|     - Set the redirect URI to https://_rustdesk.company_/api/oidc/callback. |     - Select implicit or explicit authorization flow as desired. | ||||||
|  |     - Set the redirect URI to <kbd>https://<em>rustdesk.company</em>/api/oidc/callback</kbd>. | ||||||
|     - Select any available signing key. |     - Select any available signing key. | ||||||
|  |  | ||||||
| ## RustDesk Server Pro configuration | ## RustDesk Server Pro configuration | ||||||
|  |  | ||||||
| 1. Sign in to RustDesk Server Pro using a browser. | 1. Sign in to RustDesk Server Pro using a browser. | ||||||
|  |  | ||||||
| 2. In the left menu, select **Settings** and then **OIDC**. | 2. In the left menu, select **Settings** and then **OIDC**. | ||||||
|  |  | ||||||
| 3. Click **+ New Auth Provider**. | 3. Click **+ New Auth Provider**. | ||||||
|  |  | ||||||
| 4. In the popup window, select **custom** as the **Auth Type** and click **OK**. | 4. In the popup window, select **custom** as the **Auth Type** and click **OK**. | ||||||
|  |  | ||||||
| 5. Configure the following values using information from the authentik provider: | 5. Configure the following values using information from the authentik provider: | ||||||
|     - **Name**: _SSO-Login_ |     - Set **Name** to `authentik` | ||||||
|     - **Client ID**: _client-id_ |     - Set **Client ID** to the Client ID copied from authentik. | ||||||
|     - **Client Secret**: _client-secret_ |     - Set **Client secret** to the Client Secret copied from authentik. | ||||||
|     - **Issuer**: https://_authentik.company_/application/o/_slug_/ |     - Set **Issuer** to <kbd>https://<em>authentik.company</em>/application/o/<em>slug</em>/</kbd> | ||||||
|     - **Authorization Endpoint**: https://_authentik.company_/application/o/authorize/ |     - Set **Authorization Endpoint** to <kbd>https://<em>authentik.company</em>/application/o/authorize/</kbd> | ||||||
|     - **Token Endpoint**: https://_authentik.company_/application/o/token/ |     - Set **Token Endpoint** to <kbd>https://<em>authentik.company</em>/application/o/token/</kbd> | ||||||
|     - **Userinfo Endpoint**: https://_authentik.company_/application/o/userinfo/ |     - Set **Userinfo Endpoint** to <kbd>https://<em>authentik.company</em>/application/o/userinfo/</kbd> | ||||||
|     - **JWKS Endpoint**: https://_authentik.company_/application/o/_slug_/jwks/ |     - Set **JWKS Endpoint** to <kbd>https://<em>authentik.company</em>/application/o/<em>slug</em>/jwks/</kbd> | ||||||
|  |  | ||||||
| :::info | :::info | ||||||
| Users are created automatically on login. Permissions must be assigned by an administrator after user creation. | Users are created automatically on login. Permissions must be assigned by an administrator after user creation. | ||||||
| @ -60,7 +61,7 @@ Users are created automatically on login. Permissions must be assigned by an adm | |||||||
|  |  | ||||||
| ## Test the Login | ## Test the Login | ||||||
|  |  | ||||||
| - Open a browser and navigate to https://_rustdesk.company_. | - Open a browser and navigate to <kbd>https://<em>rustdesk.company</em></kbd>. | ||||||
| - Click **Continue with SSO-Login**. | - Click **Continue with authentik**. | ||||||
| - You should be redirected to authentik (with the login flows you configured). After logging in, authentik will redirect you back to https://_rustdesk.company_. | - You should be redirected to authentik (with the login flows you configured). After logging in, authentik will redirect you back to <kbd>https://<em>rustdesk.company</em></kbd>. | ||||||
| - If you are redirected back to https://_rustdesk.company_ and can read the username in the top right corner, the setup was successful. | - If you are redirected back to <kbd>https://<em>rustdesk.company</em></kbd> and can read the username in the top right corner, the setup was successful. | ||||||
|  | |||||||
| @ -28,27 +28,14 @@ This documentation lists only the settings that you need to change from their de | |||||||
|  |  | ||||||
| ## authentik configuration | ## authentik configuration | ||||||
|  |  | ||||||
| Start the wizard for adding a new application. | [Create](https://docs.goauthentik.io/docs/add-secure-apps/applications/manage_apps#add-new-applications) an OAuth2/OpenID provider and an application in authentik. Use the following parameters for the OAuth2/OpenID provider: | ||||||
|  |  | ||||||
| **1. Application:** | 1. In the authentik Admin interface, navigate to **Applications** -> **Applications**. | ||||||
|  | 2. Use the wizard to create a new application and provider. During this process: | ||||||
| - Name: `Semaphore UI` |     - Note the **Client ID**, **Client Secret**, and **slug** values for later use. | ||||||
| - Slug: `semaphore` |     - Select implicit or explicit authorization flow as desired. | ||||||
|  |     - Set the redirect URI to <kbd>https://<em>semaphore.company</em>/api/auth/oidc/authentik/redirect/</kbd>. | ||||||
| **2. Choose a Provider** |     - Select any available signing key. | ||||||
|  |  | ||||||
| Select `OAuth2/OpenID Provider` |  | ||||||
|  |  | ||||||
| **3. Configure Provider** |  | ||||||
|  |  | ||||||
| Select implicit or explicit authorization flow as desired. |  | ||||||
|  |  | ||||||
| Take note of the Client ID and Client Secret, you'll need to give them to Semaphore UI later. |  | ||||||
|  |  | ||||||
| - Redirect URIs/Origins (RegEx): `https://semaphore.company/api/auth/oidc/authentik/redirect/` |  | ||||||
| - Signing Key: `authentik Self-signed Certificate` |  | ||||||
|  |  | ||||||
| Leave the rest as default values. |  | ||||||
|  |  | ||||||
| ## Semaphore UI configuration | ## Semaphore UI configuration | ||||||
|  |  | ||||||
| @ -60,7 +47,7 @@ Add the `oidc_providers` configuration: | |||||||
| { | { | ||||||
|   "oidc_providers": { |   "oidc_providers": { | ||||||
|     "authentik": { |     "authentik": { | ||||||
|       "display_name": "Sign in with Authentik", |       "display_name": "Sign in with authentik", | ||||||
|       "provider_url": "https://authentik.company/application/o/<slug>/", |       "provider_url": "https://authentik.company/application/o/<slug>/", | ||||||
|       "client_id": "<client-id>", |       "client_id": "<client-id>", | ||||||
|       "client_secret": "<client-secret>", |       "client_secret": "<client-secret>", | ||||||
| @ -89,14 +76,12 @@ SEMAPHORE_WEB_ROOT: / | |||||||
|  |  | ||||||
| More information on this can be found in the Semaphore documentation https://docs.semaphoreui.com/administration-guide/openid/authentik/. | More information on this can be found in the Semaphore documentation https://docs.semaphoreui.com/administration-guide/openid/authentik/. | ||||||
|  |  | ||||||
| Leave the rest as default. |  | ||||||
|  |  | ||||||
| ## Test the login | ## Test the login | ||||||
|  |  | ||||||
| - Open a browser of your choice and open the URL `https://semaphore.company`. | - Open a browser of your choice and open the URL <kbd>https://<em>semaphore.company</em></kbd>. | ||||||
| - Click on the SSO-Login button. | - Click on the SSO-Login button. | ||||||
| - You should be redirected to authentik (with the login flows you created) and then authentik should redirect you back to `https://semaphore.company` URL. | - You should be redirected to authentik (with the login flows you created) and then authentik should redirect you back to <kbd>https://<em>semaphore.company</em></kbd> URL. | ||||||
| - If you are redirected back to the `https://semaphore.company` URL you did everything correct. | - If you are redirected back to the <kbd>https://<em>semaphore.company</em></kbd> URL you did everything correct. | ||||||
|  |  | ||||||
| :::info | :::info | ||||||
| Users are created upon logging in with authentik. They will not have the rights to create anything initially. These permissions must be assigned later by the local admin created during the first login to the Semaphore UI. | Users are created upon logging in with authentik. They will not have the rights to create anything initially. These permissions must be assigned later by the local admin created during the first login to the Semaphore UI. | ||||||
|  | |||||||
| @ -123,6 +123,7 @@ module.exports = { | |||||||
|                     label: "Miscellaneous", |                     label: "Miscellaneous", | ||||||
|                     items: [ |                     items: [ | ||||||
|                         "services/actual-budget/index", |                         "services/actual-budget/index", | ||||||
|  |                         "services/adventurelog/index", | ||||||
|                         "services/engomo/index", |                         "services/engomo/index", | ||||||
|                         "services/frappe/index", |                         "services/frappe/index", | ||||||
|                         "services/freshrss/index", |                         "services/freshrss/index", | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Ken Sternberg
					Ken Sternberg