diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f78e67f49..d679517c7 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2023.10.4 +current_version = 2023.10.5 tag = True commit = True parse = (?P\d+)\.(?P\d+)\.(?P\d+) diff --git a/.dockerignore b/.dockerignore index 352faf761..8d20d66d6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,3 +9,4 @@ blueprints/local .git !gen-ts-api/node_modules !gen-ts-api/dist/** +!gen-go-api/ diff --git a/.github/codespell-words.txt b/.github/codespell-words.txt index 71f2f1c2c..29fb24832 100644 --- a/.github/codespell-words.txt +++ b/.github/codespell-words.txt @@ -2,3 +2,4 @@ keypair keypairs hass warmup +ontext diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci-main.yml index 4f7cf5256..71bfc0d7a 100644 --- a/.github/workflows/ci-main.yml +++ b/.github/workflows/ci-main.yml @@ -61,10 +61,6 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Setup authentik env - uses: ./.github/actions/setup - with: - postgresql_version: ${{ matrix.psql }} - name: checkout stable run: | # Delete all poetry envs @@ -76,7 +72,7 @@ jobs: git checkout version/$(python -c "from authentik import __version__; print(__version__)") rm -rf .github/ scripts/ mv ../.github ../scripts . - - name: Setup authentik env (ensure stable deps are installed) + - name: Setup authentik env (stable) uses: ./.github/actions/setup with: postgresql_version: ${{ matrix.psql }} @@ -90,15 +86,20 @@ jobs: git clean -d -fx . git checkout $GITHUB_SHA # Delete previous poetry env - rm -rf $(poetry env info --path) + rm -rf /home/runner/.cache/pypoetry/virtualenvs/* - name: Setup authentik env (ensure latest deps are installed) uses: ./.github/actions/setup with: postgresql_version: ${{ matrix.psql }} - name: migrate to latest run: | - poetry install poetry run python -m lifecycle.migrate + - name: run tests + env: + # Test in the main database that we just migrated from the previous stable version + AUTHENTIK_POSTGRESQL__TEST__NAME: authentik + run: | + poetry run make test test-unittest: name: test-unittest - PostgreSQL ${{ matrix.psql }} runs-on: ubuntu-latest @@ -248,12 +249,6 @@ jobs: VERSION_FAMILY=${{ steps.ev.outputs.versionFamily }} cache-from: type=gha cache-to: type=gha,mode=max - - name: Comment on PR - if: github.event_name == 'pull_request' - continue-on-error: true - uses: ./.github/actions/comment-pr-instructions - with: - tag: gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.shortHash }} build-arm64: needs: ci-core-mark runs-on: ubuntu-latest @@ -302,3 +297,26 @@ jobs: platforms: linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max + pr-comment: + needs: + - build + - build-arm64 + runs-on: ubuntu-latest + if: ${{ github.event_name == 'pull_request' }} + permissions: + # Needed to write comments on PRs + pull-requests: write + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: prepare variables + uses: ./.github/actions/docker-push-variables + id: ev + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + - name: Comment on PR + uses: ./.github/actions/comment-pr-instructions + with: + tag: gh-${{ steps.ev.outputs.branchNameContainer }}-${{ steps.ev.outputs.timestamp }}-${{ steps.ev.outputs.shortHash }} diff --git a/.github/workflows/ci-outpost.yml b/.github/workflows/ci-outpost.yml index 196fa0b3b..35c83ac86 100644 --- a/.github/workflows/ci-outpost.yml +++ b/.github/workflows/ci-outpost.yml @@ -65,6 +65,7 @@ jobs: - proxy - ldap - radius + - rac runs-on: ubuntu-latest permissions: # Needed to upload contianer images to ghcr.io @@ -119,6 +120,7 @@ jobs: - proxy - ldap - radius + - rac goos: [linux] goarch: [amd64, arm64] steps: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5f1255f56..c8c0cc11f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,10 +27,10 @@ jobs: - name: Setup authentik env uses: ./.github/actions/setup - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index c3c6a0d48..c002ab8a5 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -65,6 +65,7 @@ jobs: - proxy - ldap - radius + - rac steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 diff --git a/Dockerfile b/Dockerfile index 629d3258b..114be6253 100644 --- a/Dockerfile +++ b/Dockerfile @@ -71,7 +71,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \ # Stage 4: MaxMind GeoIP FROM --platform=${BUILDPLATFORM} ghcr.io/maxmind/geoipupdate:v6.0 as geoip -ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City" +ENV GEOIPUPDATE_EDITION_IDS="GeoLite2-City GeoLite2-ASN" ENV GEOIPUPDATE_VERBOSE="true" ENV GEOIPUPDATE_ACCOUNT_ID_FILE="/run/secrets/GEOIPUPDATE_ACCOUNT_ID" ENV GEOIPUPDATE_LICENSE_KEY_FILE="/run/secrets/GEOIPUPDATE_LICENSE_KEY" diff --git a/Makefile b/Makefile index c649ff230..93092a779 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ test: ## Run the server tests and produce a coverage report (locally) lint-fix: ## Lint and automatically fix errors in the python source code. Reports spelling errors. isort $(PY_SOURCES) black $(PY_SOURCES) - ruff $(PY_SOURCES) + ruff --fix $(PY_SOURCES) codespell -w $(CODESPELL_ARGS) lint: ## Lint the python and golang sources @@ -115,8 +115,9 @@ gen-diff: ## (Release) generate the changelog diff between the current schema a npx prettier --write diff.md gen-clean: - rm -rf web/api/src/ - rm -rf api/ + rm -rf gen-go-api/ + rm -rf gen-ts-api/ + rm -rf web/node_modules/@goauthentik/api/ gen-client-ts: ## Build and install the authentik API for Typescript into the authentik UI Application docker run \ diff --git a/authentik/__init__.py b/authentik/__init__.py index fc368ceeb..0c651c834 100644 --- a/authentik/__init__.py +++ b/authentik/__init__.py @@ -2,7 +2,7 @@ from os import environ from typing import Optional -__version__ = "2023.10.4" +__version__ = "2023.10.5" ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" diff --git a/authentik/api/tests/test_auth.py b/authentik/api/tests/test_auth.py index cd23a1835..c09bca5a3 100644 --- a/authentik/api/tests/test_auth.py +++ b/authentik/api/tests/test_auth.py @@ -12,6 +12,8 @@ from authentik.blueprints.tests import reconcile_app from authentik.core.models import Token, TokenIntents, User, UserTypes from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.lib.generators import generate_id +from authentik.outposts.apps import MANAGED_OUTPOST +from authentik.outposts.models import Outpost from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API from authentik.providers.oauth2.models import AccessToken, OAuth2Provider @@ -49,8 +51,12 @@ class TestAPIAuth(TestCase): with self.assertRaises(AuthenticationFailed): bearer_auth(f"Bearer {token.key}".encode()) - def test_managed_outpost(self): + @reconcile_app("authentik_outposts") + def test_managed_outpost_fail(self): """Test managed outpost""" + outpost = Outpost.objects.filter(managed=MANAGED_OUTPOST).first() + outpost.user.delete() + outpost.delete() with self.assertRaises(AuthenticationFailed): bearer_auth(f"Bearer {settings.SECRET_KEY}".encode()) diff --git a/authentik/api/v3/config.py b/authentik/api/v3/config.py index 0defd1a5b..93b783629 100644 --- a/authentik/api/v3/config.py +++ b/authentik/api/v3/config.py @@ -19,7 +19,7 @@ from rest_framework.response import Response from rest_framework.views import APIView from authentik.core.api.utils import PassiveSerializer -from authentik.events.geo import GEOIP_READER +from authentik.events.context_processors.base import get_context_processors from authentik.lib.config import CONFIG capabilities = Signal() @@ -30,6 +30,7 @@ class Capabilities(models.TextChoices): CAN_SAVE_MEDIA = "can_save_media" CAN_GEO_IP = "can_geo_ip" + CAN_ASN = "can_asn" CAN_IMPERSONATE = "can_impersonate" CAN_DEBUG = "can_debug" IS_ENTERPRISE = "is_enterprise" @@ -68,8 +69,9 @@ class ConfigView(APIView): deb_test = settings.DEBUG or settings.TEST if Path(settings.MEDIA_ROOT).is_mount() or deb_test: caps.append(Capabilities.CAN_SAVE_MEDIA) - if GEOIP_READER.enabled: - caps.append(Capabilities.CAN_GEO_IP) + for processor in get_context_processors(): + if cap := processor.capability(): + caps.append(cap) if CONFIG.get_bool("impersonation"): caps.append(Capabilities.CAN_IMPERSONATE) if settings.DEBUG: # pragma: no cover diff --git a/authentik/blueprints/api.py b/authentik/blueprints/api.py index 721eb5dcb..7abf488da 100644 --- a/authentik/blueprints/api.py +++ b/authentik/blueprints/api.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from drf_spectacular.utils import extend_schema, inline_serializer from rest_framework.decorators import action from rest_framework.exceptions import ValidationError -from rest_framework.fields import CharField, DateTimeField, JSONField +from rest_framework.fields import CharField, DateTimeField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ListSerializer, ModelSerializer @@ -15,7 +15,7 @@ from authentik.blueprints.v1.importer import Importer from authentik.blueprints.v1.oci import OCI_PREFIX from authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict from authentik.core.api.used_by import UsedByMixin -from authentik.core.api.utils import PassiveSerializer +from authentik.core.api.utils import JSONDictField, PassiveSerializer class ManagedSerializer: @@ -28,7 +28,7 @@ class MetadataSerializer(PassiveSerializer): """Serializer for blueprint metadata""" name = CharField() - labels = JSONField() + labels = JSONDictField() class BlueprintInstanceSerializer(ModelSerializer): diff --git a/authentik/blueprints/apps.py b/authentik/blueprints/apps.py index 90df91c00..aba14d552 100644 --- a/authentik/blueprints/apps.py +++ b/authentik/blueprints/apps.py @@ -40,7 +40,7 @@ class ManagedAppConfig(AppConfig): meth() self._logger.debug("Successfully reconciled", name=name) except (DatabaseError, ProgrammingError, InternalError) as exc: - self._logger.debug("Failed to run reconcile", name=name, exc=exc) + self._logger.warning("Failed to run reconcile", name=name, exc=exc) class AuthentikBlueprintsConfig(ManagedAppConfig): diff --git a/authentik/blueprints/v1/meta/apply_blueprint.py b/authentik/blueprints/v1/meta/apply_blueprint.py index 5946342a3..0a8d84e66 100644 --- a/authentik/blueprints/v1/meta/apply_blueprint.py +++ b/authentik/blueprints/v1/meta/apply_blueprint.py @@ -2,11 +2,11 @@ from typing import TYPE_CHECKING from rest_framework.exceptions import ValidationError -from rest_framework.fields import BooleanField, JSONField +from rest_framework.fields import BooleanField from structlog.stdlib import get_logger from authentik.blueprints.v1.meta.registry import BaseMetaModel, MetaResult, registry -from authentik.core.api.utils import PassiveSerializer, is_dict +from authentik.core.api.utils import JSONDictField, PassiveSerializer if TYPE_CHECKING: from authentik.blueprints.models import BlueprintInstance @@ -17,7 +17,7 @@ LOGGER = get_logger() class ApplyBlueprintMetaSerializer(PassiveSerializer): """Serializer for meta apply blueprint model""" - identifiers = JSONField(validators=[is_dict]) + identifiers = JSONDictField() required = BooleanField(default=True) # We cannot override `instance` as that will confuse rest_framework diff --git a/authentik/core/api/authenticated_sessions.py b/authentik/core/api/authenticated_sessions.py index 03c1aeaf3..2d77937be 100644 --- a/authentik/core/api/authenticated_sessions.py +++ b/authentik/core/api/authenticated_sessions.py @@ -14,7 +14,8 @@ from ua_parser import user_agent_parser from authentik.api.authorization import OwnerSuperuserPermissions from authentik.core.api.used_by import UsedByMixin from authentik.core.models import AuthenticatedSession -from authentik.events.geo import GEOIP_READER, GeoIPDict +from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR, ASNDict +from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR, GeoIPDict class UserAgentDeviceDict(TypedDict): @@ -59,6 +60,7 @@ class AuthenticatedSessionSerializer(ModelSerializer): current = SerializerMethodField() user_agent = SerializerMethodField() geo_ip = SerializerMethodField() + asn = SerializerMethodField() def get_current(self, instance: AuthenticatedSession) -> bool: """Check if session is currently active session""" @@ -70,8 +72,12 @@ class AuthenticatedSessionSerializer(ModelSerializer): return user_agent_parser.Parse(instance.last_user_agent) def get_geo_ip(self, instance: AuthenticatedSession) -> Optional[GeoIPDict]: # pragma: no cover - """Get parsed user agent""" - return GEOIP_READER.city_dict(instance.last_ip) + """Get GeoIP Data""" + return GEOIP_CONTEXT_PROCESSOR.city_dict(instance.last_ip) + + def get_asn(self, instance: AuthenticatedSession) -> Optional[ASNDict]: # pragma: no cover + """Get ASN Data""" + return ASN_CONTEXT_PROCESSOR.asn_dict(instance.last_ip) class Meta: model = AuthenticatedSession @@ -80,6 +86,7 @@ class AuthenticatedSessionSerializer(ModelSerializer): "current", "user_agent", "geo_ip", + "asn", "user", "last_ip", "last_user_agent", diff --git a/authentik/core/api/groups.py b/authentik/core/api/groups.py index 21ba19974..04670844d 100644 --- a/authentik/core/api/groups.py +++ b/authentik/core/api/groups.py @@ -8,7 +8,7 @@ from django_filters.filterset import FilterSet from drf_spectacular.utils import OpenApiResponse, extend_schema from guardian.shortcuts import get_objects_for_user from rest_framework.decorators import action -from rest_framework.fields import CharField, IntegerField, JSONField +from rest_framework.fields import CharField, IntegerField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ListSerializer, ModelSerializer, ValidationError @@ -16,7 +16,7 @@ from rest_framework.viewsets import ModelViewSet from authentik.api.decorators import permission_required from authentik.core.api.used_by import UsedByMixin -from authentik.core.api.utils import PassiveSerializer, is_dict +from authentik.core.api.utils import JSONDictField, PassiveSerializer from authentik.core.models import Group, User from authentik.rbac.api.roles import RoleSerializer @@ -24,7 +24,7 @@ from authentik.rbac.api.roles import RoleSerializer class GroupMemberSerializer(ModelSerializer): """Stripped down user serializer to show relevant users for groups""" - attributes = JSONField(validators=[is_dict], required=False) + attributes = JSONDictField(required=False) uid = CharField(read_only=True) class Meta: @@ -44,7 +44,7 @@ class GroupMemberSerializer(ModelSerializer): class GroupSerializer(ModelSerializer): """Group Serializer""" - attributes = JSONField(validators=[is_dict], required=False) + attributes = JSONDictField(required=False) users_obj = ListSerializer( child=GroupMemberSerializer(), read_only=True, source="users", required=False ) diff --git a/authentik/core/api/propertymappings.py b/authentik/core/api/propertymappings.py index 1e7436be9..d0fa7267b 100644 --- a/authentik/core/api/propertymappings.py +++ b/authentik/core/api/propertymappings.py @@ -19,6 +19,7 @@ from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import MetaNameSerializer, PassiveSerializer, TypeCreateSerializer from authentik.core.expression.evaluator import PropertyMappingEvaluator from authentik.core.models import PropertyMapping +from authentik.enterprise.apps import EnterpriseConfig from authentik.events.utils import sanitize_item from authentik.lib.utils.reflection import all_subclasses from authentik.policies.api.exec import PolicyTestSerializer @@ -95,6 +96,7 @@ class PropertyMappingViewSet( "description": subclass.__doc__, "component": subclass().component, "model_name": subclass._meta.model_name, + "requires_enterprise": isinstance(subclass._meta.app_config, EnterpriseConfig), } ) return Response(TypeCreateSerializer(data, many=True).data) diff --git a/authentik/core/api/providers.py b/authentik/core/api/providers.py index a5095dcde..6c0f4db06 100644 --- a/authentik/core/api/providers.py +++ b/authentik/core/api/providers.py @@ -16,6 +16,7 @@ from rest_framework.viewsets import GenericViewSet from authentik.core.api.used_by import UsedByMixin from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer from authentik.core.models import Provider +from authentik.enterprise.apps import EnterpriseConfig from authentik.lib.utils.reflection import all_subclasses @@ -113,6 +114,7 @@ class ProviderViewSet( "description": subclass.__doc__, "component": subclass().component, "model_name": subclass._meta.model_name, + "requires_enterprise": isinstance(subclass._meta.app_config, EnterpriseConfig), } ) data.append( diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index 5ee249729..5b6a4a199 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -32,13 +32,7 @@ from drf_spectacular.utils import ( ) from guardian.shortcuts import get_anonymous_user, get_objects_for_user from rest_framework.decorators import action -from rest_framework.fields import ( - CharField, - IntegerField, - JSONField, - ListField, - SerializerMethodField, -) +from rest_framework.fields import CharField, IntegerField, ListField, SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ( @@ -57,7 +51,7 @@ from authentik.admin.api.metrics import CoordinateSerializer from authentik.api.decorators import permission_required from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT from authentik.core.api.used_by import UsedByMixin -from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict +from authentik.core.api.utils import JSONDictField, LinkSerializer, PassiveSerializer from authentik.core.middleware import ( SESSION_KEY_IMPERSONATE_ORIGINAL_USER, SESSION_KEY_IMPERSONATE_USER, @@ -89,7 +83,7 @@ LOGGER = get_logger() class UserGroupSerializer(ModelSerializer): """Simplified Group Serializer for user's groups""" - attributes = JSONField(required=False) + attributes = JSONDictField(required=False) parent_name = CharField(source="parent.name", read_only=True) class Meta: @@ -110,7 +104,7 @@ class UserSerializer(ModelSerializer): is_superuser = BooleanField(read_only=True) avatar = CharField(read_only=True) - attributes = JSONField(validators=[is_dict], required=False) + attributes = JSONDictField(required=False) groups = PrimaryKeyRelatedField( allow_empty=True, many=True, source="ak_groups", queryset=Group.objects.all(), default=list ) diff --git a/authentik/core/api/utils.py b/authentik/core/api/utils.py index cf1870197..c79fec22e 100644 --- a/authentik/core/api/utils.py +++ b/authentik/core/api/utils.py @@ -2,7 +2,10 @@ from typing import Any from django.db.models import Model -from rest_framework.fields import CharField, IntegerField, JSONField +from drf_spectacular.extensions import OpenApiSerializerFieldExtension +from drf_spectacular.plumbing import build_basic_type +from drf_spectacular.types import OpenApiTypes +from rest_framework.fields import BooleanField, CharField, IntegerField, JSONField from rest_framework.serializers import Serializer, SerializerMethodField, ValidationError @@ -13,6 +16,21 @@ def is_dict(value: Any): raise ValidationError("Value must be a dictionary, and not have any duplicate keys.") +class JSONDictField(JSONField): + """JSON Field which only allows dictionaries""" + + default_validators = [is_dict] + + +class JSONExtension(OpenApiSerializerFieldExtension): + """Generate API Schema for JSON fields as""" + + target_class = "authentik.core.api.utils.JSONDictField" + + def map_serializer_field(self, auto_schema, direction): + return build_basic_type(OpenApiTypes.OBJECT) + + class PassiveSerializer(Serializer): """Base serializer class which doesn't implement create/update methods""" @@ -26,7 +44,7 @@ class PassiveSerializer(Serializer): class PropertyMappingPreviewSerializer(PassiveSerializer): """Preview how the current user is mapped via the property mappings selected in a provider""" - preview = JSONField(read_only=True) + preview = JSONDictField(read_only=True) class MetaNameSerializer(PassiveSerializer): @@ -56,6 +74,7 @@ class TypeCreateSerializer(PassiveSerializer): description = CharField(required=True) component = CharField(required=True) model_name = CharField(required=True) + requires_enterprise = BooleanField(default=False) class CacheSerializer(PassiveSerializer): diff --git a/authentik/core/channels.py b/authentik/core/channels.py index 00f213efc..722e9e03f 100644 --- a/authentik/core/channels.py +++ b/authentik/core/channels.py @@ -1,22 +1,29 @@ """Channels base classes""" +from channels.db import database_sync_to_async from channels.exceptions import DenyConnection -from channels.generic.websocket import JsonWebsocketConsumer from rest_framework.exceptions import AuthenticationFailed from structlog.stdlib import get_logger from authentik.api.authentication import bearer_auth -from authentik.core.models import User LOGGER = get_logger() -class AuthJsonConsumer(JsonWebsocketConsumer): +class TokenOutpostMiddleware: """Authorize a client with a token""" - user: User + def __init__(self, inner): + self.inner = inner - def connect(self): - headers = dict(self.scope["headers"]) + async def __call__(self, scope, receive, send): + scope = dict(scope) + await self.auth(scope) + return await self.inner(scope, receive, send) + + @database_sync_to_async + def auth(self, scope): + """Authenticate request from header""" + headers = dict(scope["headers"]) if b"authorization" not in headers: LOGGER.warning("WS Request without authorization header") raise DenyConnection() @@ -32,4 +39,4 @@ class AuthJsonConsumer(JsonWebsocketConsumer): LOGGER.warning("Failed to authenticate", exc=exc) raise DenyConnection() - self.user = user + scope["user"] = user diff --git a/authentik/core/expression/evaluator.py b/authentik/core/expression/evaluator.py index 85e6ccbc4..480caea21 100644 --- a/authentik/core/expression/evaluator.py +++ b/authentik/core/expression/evaluator.py @@ -44,6 +44,7 @@ class PropertyMappingEvaluator(BaseEvaluator): if request: req.http_request = request self._context["request"] = req + req.context.update(**kwargs) self._context.update(**kwargs) self.dry_run = dry_run diff --git a/authentik/core/models.py b/authentik/core/models.py index 7d11af3d6..125d5b0c8 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -30,7 +30,6 @@ from authentik.lib.models import ( DomainlessFormattedURLValidator, SerializerModel, ) -from authentik.lib.utils.http import get_client_ip from authentik.policies.models import PolicyBindingModel from authentik.root.install_id import get_install_id @@ -748,12 +747,14 @@ class AuthenticatedSession(ExpiringModel): @staticmethod def from_request(request: HttpRequest, user: User) -> Optional["AuthenticatedSession"]: """Create a new session from a http request""" + from authentik.root.middleware import ClientIPMiddleware + if not hasattr(request, "session") or not request.session.session_key: return None return AuthenticatedSession( session_key=request.session.session_key, user=user, - last_ip=get_client_ip(request), + last_ip=ClientIPMiddleware.get_client_ip(request), last_user_agent=request.META.get("HTTP_USER_AGENT", ""), expires=request.session.get_expiry_date(), ) diff --git a/authentik/core/templates/login/base_full.html b/authentik/core/templates/login/base_full.html index c2dcd874b..be6e3a040 100644 --- a/authentik/core/templates/login/base_full.html +++ b/authentik/core/templates/login/base_full.html @@ -44,28 +44,14 @@ {% block body %}
- - - - - - - - - - -
-