From 86306f9eba3dbfba226875fcabb7d258564231d0 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 9 Dec 2025 13:25:04 +0300 Subject: [PATCH 01/22] refactor: change async functions to synchronous in MainProvider --- app/ioc.py | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/app/ioc.py b/app/ioc.py index c111396dd..8579e2be7 100644 --- a/app/ioc.py +++ b/app/ioc.py @@ -214,7 +214,7 @@ async def get_kadmin_http( yield KadminHTTPClient(client) @provide(scope=Scope.REQUEST) - async def get_kadmin( + def get_kadmin( self, client: KadminHTTPClient, kadmin_class: type[AbstractKadmin], @@ -269,14 +269,14 @@ async def get_dns_http_client( yield DNSManagerHTTPClient(client) @provide(scope=Scope.REQUEST) - async def get_dns_mngr( + def get_dns_mngr( self, settings: DNSManagerSettings, dns_manager_class: type[AbstractDNSManager], http_client: DNSManagerHTTPClient, - ) -> AsyncIterator[AbstractDNSManager]: + ) -> AbstractDNSManager: """Get DNSManager class.""" - yield dns_manager_class(settings=settings, http_client=http_client) + return dns_manager_class(settings=settings, http_client=http_client) @provide(scope=Scope.APP) async def get_redis_for_sessions( @@ -293,7 +293,7 @@ async def get_redis_for_sessions( await client.aclose() @provide(scope=Scope.APP) - async def get_session_storage( + def get_session_storage( self, client: SessionStorageClient, settings: Settings, @@ -306,7 +306,7 @@ async def get_session_storage( ) @provide() - async def get_normalized_audit_event( + def get_normalized_audit_event( self, ) -> type[NormalizedAuditEvent]: """Get normalized audit event class.""" @@ -327,13 +327,13 @@ async def get_audit_redis_client( await client.aclose() @provide(scope=Scope.APP) - async def get_raw_audit_manager( + def get_raw_audit_manager( self, client: AuditRedisClient, settings: Settings, - ) -> AsyncIterator[RawAuditManager]: + ) -> RawAuditManager: """Get raw audit manager.""" - yield RawAuditManager( + return RawAuditManager( client, settings.RAW_EVENT_STREAM_NAME, settings.EVENT_HANDLER_GROUP, @@ -342,13 +342,13 @@ async def get_raw_audit_manager( ) @provide(scope=Scope.APP) - async def get_normalized_audit_manager( + def get_normalized_audit_manager( self, client: AuditRedisClient, settings: Settings, - ) -> AsyncIterator[NormalizedAuditManager]: + ) -> NormalizedAuditManager: """Get raw audit manager.""" - yield NormalizedAuditManager( + return NormalizedAuditManager( client, settings.NORMALIZED_EVENT_STREAM_NAME, settings.EVENT_SENDER_GROUP, @@ -361,7 +361,7 @@ async def get_normalized_audit_manager( audit_destination_dao = provide(AuditDestinationDAO, scope=Scope.REQUEST) @provide(scope=Scope.REQUEST) - async def get_dhcp_manager_repository( + def get_dhcp_manager_repository( self, session: AsyncSession, ) -> DHCPManagerRepository: @@ -377,20 +377,20 @@ async def get_dhcp_manager_state( return await dhcp_manager_repository.ensure_state() @provide(scope=Scope.REQUEST) - async def get_dhcp_mngr_class( + def get_dhcp_mngr_class( self, dhcp_state: DHCPManagerState, ) -> type[AbstractDHCPManager]: """Get DHCP manager type.""" - return await get_dhcp_manager_class(dhcp_state) + return get_dhcp_manager_class(dhcp_state) @provide(scope=Scope.REQUEST) - async def get_dhcp_api_repository_class( + def get_dhcp_api_repository_class( self, dhcp_state: DHCPManagerState, ) -> type[DHCPAPIRepository]: """Get DHCP API repository type.""" - return await get_dhcp_api_repository_class(dhcp_state) + return get_dhcp_api_repository_class(dhcp_state) @provide(scope=Scope.APP) async def get_dhcp_http_client( @@ -404,7 +404,7 @@ async def get_dhcp_http_client( yield DHCPManagerHTTPClient(http_client) @provide(scope=Scope.REQUEST) - async def get_dhcp_api_repository( + def get_dhcp_api_repository( self, http_client: DHCPManagerHTTPClient, dhcp_api_repository_class: type[DHCPAPIRepository], @@ -413,7 +413,7 @@ async def get_dhcp_api_repository( return dhcp_api_repository_class(http_client) @provide(scope=Scope.REQUEST) - async def get_dhcp_mngr( + def get_dhcp_mngr( self, dhcp_manager_class: type[AbstractDHCPManager], dhcp_api_repository: DHCPAPIRepository, @@ -535,7 +535,7 @@ class HTTPProvider(LDAPContextProvider): ) @provide() - async def get_audit_monitor( + def get_audit_monitor( self, session: AsyncSession, audit_use_case: "AuditUseCase", @@ -595,7 +595,7 @@ def get_permissions_provider( return auth_provider @provide() - async def get_identity_provider( + def get_identity_provider( self, request: Request, session_storage: SessionStorage, @@ -816,7 +816,7 @@ async def get_client( yield MFAHTTPClient(client) @provide(provides=MultifactorAPI) - async def get_http_mfa( + def get_http_mfa( self, credentials: MFA_HTTP_Creds, client: MFAHTTPClient, @@ -838,7 +838,7 @@ async def get_http_mfa( ) @provide(provides=LDAPMultiFactorAPI) - async def get_ldap_mfa( + def get_ldap_mfa( self, credentials: MFA_LDAP_Creds, client: MFAHTTPClient, From b62519db65e51e3c3cadc8030dc01a3bdc75dd9f Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 9 Dec 2025 15:20:20 +0300 Subject: [PATCH 02/22] refactor: optimize query loading for entity type in SearchRequest --- app/ldap_protocol/ldap_requests/search.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/ldap_protocol/ldap_requests/search.py b/app/ldap_protocol/ldap_requests/search.py index 01ec77169..a5cabeeaa 100644 --- a/app/ldap_protocol/ldap_requests/search.py +++ b/app/ldap_protocol/ldap_requests/search.py @@ -337,10 +337,7 @@ def _mutate_query_with_attributes_to_load( ) -> Select: """Get attributes to load.""" if self.entity_type_name: - query = ( - query.join(qa(Directory.entity_type)) - .options(selectinload(qa(Directory.entity_type))) - ) # fmt: skip + query = query.options(joinedload(qa(Directory.entity_type))) if self.all_attrs: return query.options(selectinload(qa(Directory.attributes))) From 57354b2fcfb7b249566119deff11613a5660cff4 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 9 Dec 2025 15:20:41 +0300 Subject: [PATCH 03/22] refactor: change loading strategy for Directory group in SearchRequest --- app/ldap_protocol/ldap_requests/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ldap_protocol/ldap_requests/search.py b/app/ldap_protocol/ldap_requests/search.py index a5cabeeaa..7b1d3e28c 100644 --- a/app/ldap_protocol/ldap_requests/search.py +++ b/app/ldap_protocol/ldap_requests/search.py @@ -367,7 +367,7 @@ def _build_query( select(Directory) .join(qa(Directory.user), isouter=True) .options(joinedload(qa(Directory.user))) - .options(selectinload(qa(Directory.group))) + .options(joinedload(qa(Directory.group))) ) query = self._mutate_query_with_attributes_to_load(query) From ef6e467506163dbbf17c10bd52a7e1eaf63f5b89 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 9 Dec 2025 15:21:56 +0300 Subject: [PATCH 04/22] refactor: change loading strategy from selectinload to joinedload for Directory group in SearchRequest --- app/ldap_protocol/ldap_requests/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ldap_protocol/ldap_requests/search.py b/app/ldap_protocol/ldap_requests/search.py index 7b1d3e28c..37d76fbe4 100644 --- a/app/ldap_protocol/ldap_requests/search.py +++ b/app/ldap_protocol/ldap_requests/search.py @@ -420,7 +420,7 @@ def _build_query( if self.member: query = query.options( - selectinload(qa(Directory.group)).selectinload( + joinedload(qa(Directory.group)).selectinload( qa(Group.members), ), ) From 4c05c891faf678500fe2a7c795c11783ee50c530 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 9 Dec 2025 15:23:53 +0300 Subject: [PATCH 05/22] refactor: change async functions to synchronous in DHCP manager and API repository classes --- app/ldap_protocol/dhcp/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/ldap_protocol/dhcp/__init__.py b/app/ldap_protocol/dhcp/__init__.py index 27df7d0c0..cf26f1903 100644 --- a/app/ldap_protocol/dhcp/__init__.py +++ b/app/ldap_protocol/dhcp/__init__.py @@ -26,7 +26,7 @@ from .stub import StubDHCPAPIRepository, StubDHCPManager -async def get_dhcp_manager_class( +def get_dhcp_manager_class( dhcp_state: DHCPManagerState, ) -> type[AbstractDHCPManager]: """Get an instance of the DHCP manager.""" @@ -35,7 +35,7 @@ async def get_dhcp_manager_class( return StubDHCPManager -async def get_dhcp_api_repository_class( +def get_dhcp_api_repository_class( dhcp_state: DHCPManagerState, ) -> type[DHCPAPIRepository]: """Get an instance of the DHCP API repository.""" From 62e2709a893d2b555ac8bee352bdbb51c9aac13b Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 9 Dec 2025 15:34:03 +0300 Subject: [PATCH 06/22] refactor: change async iteration to synchronous for directory results in SearchRequest --- app/ldap_protocol/ldap_requests/search.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/ldap_protocol/ldap_requests/search.py b/app/ldap_protocol/ldap_requests/search.py index 37d76fbe4..079e5f9b1 100644 --- a/app/ldap_protocol/ldap_requests/search.py +++ b/app/ldap_protocol/ldap_requests/search.py @@ -538,9 +538,9 @@ async def tree_view( # noqa: C901 access_manager: AccessManager, ) -> AsyncGenerator[SearchResultEntry, None]: """Yield all resulted directories.""" - directories = await session.stream_scalars(query) + directories = await session.scalars(query) - async for directory in directories: + for directory in directories: attrs = defaultdict(list) obj_classes = [] From 416987afb087a98e205a57cfa9c306bd50d547d3 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 9 Dec 2025 17:25:11 +0300 Subject: [PATCH 07/22] refactor: change loading strategy to use contains_eager for Directory entity in SearchRequest --- app/ldap_protocol/ldap_requests/search.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/ldap_protocol/ldap_requests/search.py b/app/ldap_protocol/ldap_requests/search.py index 079e5f9b1..647dfa6a3 100644 --- a/app/ldap_protocol/ldap_requests/search.py +++ b/app/ldap_protocol/ldap_requests/search.py @@ -337,7 +337,10 @@ def _mutate_query_with_attributes_to_load( ) -> Select: """Get attributes to load.""" if self.entity_type_name: - query = query.options(joinedload(qa(Directory.entity_type))) + query = ( + query.join(qa(Directory.entity_type)) + .options(contains_eager(qa(Directory.entity_type))) + ) # fmt: skip if self.all_attrs: return query.options(selectinload(qa(Directory.attributes))) @@ -366,7 +369,7 @@ def _build_query( query = ( select(Directory) .join(qa(Directory.user), isouter=True) - .options(joinedload(qa(Directory.user))) + .options(contains_eager(qa(Directory.user))) .options(joinedload(qa(Directory.group))) ) From e0465ed296c202e0bba7ce0214bb2a1394877d6c Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 9 Dec 2025 17:25:29 +0300 Subject: [PATCH 08/22] refactor: reorganize import statements in search.py for clarity --- app/ldap_protocol/ldap_requests/search.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/ldap_protocol/ldap_requests/search.py b/app/ldap_protocol/ldap_requests/search.py index 647dfa6a3..69f594fc5 100644 --- a/app/ldap_protocol/ldap_requests/search.py +++ b/app/ldap_protocol/ldap_requests/search.py @@ -14,7 +14,12 @@ from pydantic import Field, PrivateAttr, field_serializer from sqlalchemy import func, or_, select from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import joinedload, selectinload, with_loader_criteria +from sqlalchemy.orm import ( + contains_eager, + joinedload, + selectinload, + with_loader_criteria, +) from sqlalchemy.sql.elements import ColumnElement, UnaryExpression from sqlalchemy.sql.expression import Select From ab973a8b45ac042a8822a118059a8902ddac3cfc Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 9 Dec 2025 17:28:41 +0300 Subject: [PATCH 09/22] refactor: add CONTEXT_TYPE class variable to LDAP request classes --- app/ldap_protocol/ldap_requests/abandon.py | 2 +- app/ldap_protocol/ldap_requests/add.py | 1 + app/ldap_protocol/ldap_requests/base.py | 11 ++++++++--- app/ldap_protocol/ldap_requests/bind.py | 2 ++ app/ldap_protocol/ldap_requests/delete.py | 1 + app/ldap_protocol/ldap_requests/extended.py | 1 + app/ldap_protocol/ldap_requests/modify.py | 1 + app/ldap_protocol/ldap_requests/modify_dn.py | 1 + app/ldap_protocol/ldap_requests/search.py | 1 + 9 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/ldap_protocol/ldap_requests/abandon.py b/app/ldap_protocol/ldap_requests/abandon.py index 3facb0562..754fe3358 100644 --- a/app/ldap_protocol/ldap_requests/abandon.py +++ b/app/ldap_protocol/ldap_requests/abandon.py @@ -27,7 +27,7 @@ def from_data( """Create structure from ASN1Row dataclass list.""" return cls(message_id=1) - async def handle(self) -> AsyncGenerator: + async def handle(self, ctx) -> AsyncGenerator: # type: ignore """Handle message with current user.""" await asyncio.sleep(0) return diff --git a/app/ldap_protocol/ldap_requests/add.py b/app/ldap_protocol/ldap_requests/add.py index fec2a8679..aa5d3b3ee 100644 --- a/app/ldap_protocol/ldap_requests/add.py +++ b/app/ldap_protocol/ldap_requests/add.py @@ -65,6 +65,7 @@ class AddRequest(BaseRequest): """ PROTOCOL_OP: ClassVar[int] = ProtocolRequests.ADD + CONTEXT_TYPE: ClassVar[type] = LDAPAddRequestContext entry: str = Field(..., description="Any `DistinguishedName`") attributes: list[PartialAttribute] diff --git a/app/ldap_protocol/ldap_requests/base.py b/app/ldap_protocol/ldap_requests/base.py index 3123e6247..5f56898d6 100644 --- a/app/ldap_protocol/ldap_requests/base.py +++ b/app/ldap_protocol/ldap_requests/base.py @@ -24,6 +24,7 @@ from ldap_protocol.dependency import resolve_deps from ldap_protocol.dialogue import LDAPSession from ldap_protocol.ldap_responses import BaseResponse, LDAPResult +from ldap_protocol.objects import ProtocolRequests from ldap_protocol.policies.audit.audit_use_case import AuditUseCase from ldap_protocol.policies.audit.events.factory import ( RawAuditEventBuilderRedis, @@ -62,6 +63,7 @@ class _APIProtocol: ... class BaseRequest(ABC, _APIProtocol, BaseModel): """Base request builder.""" + CONTEXT_TYPE: ClassVar[type] handle: ClassVar[handler] from_data: ClassVar[serializer] __event_data: dict = {} @@ -113,10 +115,13 @@ async def handle_tcp( container: AsyncContainer, ) -> AsyncIterator[BaseResponse]: """Hanlde response with tcp.""" - kwargs = await resolve_deps(func=self.handle, container=container) - responses = [] + if self.PROTOCOL_OP != ProtocolRequests.ABANDON: + ctx = await container.get(self.CONTEXT_TYPE) # type: ignore + else: + ctx = None - async for response in self.handle(**kwargs): + responses = [] + async for response in self.handle(ctx=ctx): responses.append(response) yield response diff --git a/app/ldap_protocol/ldap_requests/bind.py b/app/ldap_protocol/ldap_requests/bind.py index a303f0fdf..445b2f25c 100644 --- a/app/ldap_protocol/ldap_requests/bind.py +++ b/app/ldap_protocol/ldap_requests/bind.py @@ -43,6 +43,7 @@ class BindRequest(BaseRequest): """Bind request fields mapping.""" PROTOCOL_OP: ClassVar[int] = ProtocolRequests.BIND + CONTEXT_TYPE: ClassVar[type] = LDAPBindRequestContext version: int name: str @@ -230,6 +231,7 @@ class UnbindRequest(BaseRequest): """Remove user from ldap_session.""" PROTOCOL_OP: ClassVar[int] = ProtocolRequests.UNBIND + CONTEXT_TYPE: ClassVar[type] = LDAPUnbindRequestContext @classmethod def from_data( diff --git a/app/ldap_protocol/ldap_requests/delete.py b/app/ldap_protocol/ldap_requests/delete.py index 5c731e9b7..334df621a 100644 --- a/app/ldap_protocol/ldap_requests/delete.py +++ b/app/ldap_protocol/ldap_requests/delete.py @@ -43,6 +43,7 @@ class DeleteRequest(BaseRequest): """ PROTOCOL_OP: ClassVar[int] = ProtocolRequests.DELETE + CONTEXT_TYPE: ClassVar[type] = LDAPDeleteRequestContext entry: str diff --git a/app/ldap_protocol/ldap_requests/extended.py b/app/ldap_protocol/ldap_requests/extended.py index 85ca1f31b..c3967889e 100644 --- a/app/ldap_protocol/ldap_requests/extended.py +++ b/app/ldap_protocol/ldap_requests/extended.py @@ -308,6 +308,7 @@ class ExtendedRequest(BaseRequest): """ PROTOCOL_OP: ClassVar[int] = ProtocolRequests.EXTENDED + CONTEXT_TYPE: ClassVar[type] = LDAPExtendedRequestContext request_name: LDAPOID request_value: SerializeAsAny[BaseExtendedValue] diff --git a/app/ldap_protocol/ldap_requests/modify.py b/app/ldap_protocol/ldap_requests/modify.py index 7334ba225..8ba60b963 100644 --- a/app/ldap_protocol/ldap_requests/modify.py +++ b/app/ldap_protocol/ldap_requests/modify.py @@ -102,6 +102,7 @@ class ModifyRequest(BaseRequest): """ PROTOCOL_OP: ClassVar[int] = ProtocolRequests.MODIFY + CONTEXT_TYPE: ClassVar[type] = LDAPModifyRequestContext object: str changes: list[Changes] diff --git a/app/ldap_protocol/ldap_requests/modify_dn.py b/app/ldap_protocol/ldap_requests/modify_dn.py index c1ff681d3..0cf547833 100644 --- a/app/ldap_protocol/ldap_requests/modify_dn.py +++ b/app/ldap_protocol/ldap_requests/modify_dn.py @@ -68,6 +68,7 @@ class ModifyDNRequest(BaseRequest): """ PROTOCOL_OP: ClassVar[int] = ProtocolRequests.MODIFY_DN + CONTEXT_TYPE: ClassVar[type] = LDAPModifyDNRequestContext entry: str newrdn: str diff --git a/app/ldap_protocol/ldap_requests/search.py b/app/ldap_protocol/ldap_requests/search.py index 69f594fc5..155e3619a 100644 --- a/app/ldap_protocol/ldap_requests/search.py +++ b/app/ldap_protocol/ldap_requests/search.py @@ -105,6 +105,7 @@ class SearchRequest(BaseRequest): """ PROTOCOL_OP: ClassVar[int] = ProtocolRequests.SEARCH + CONTEXT_TYPE: ClassVar[type] = LDAPSearchRequestContext base_object: str = Field("", description="Any `DistinguishedName`") scope: Scope From 757a756d996d6d0317aa0b84dc9e194bf9a0649d Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 16 Dec 2025 13:00:58 +0300 Subject: [PATCH 10/22] refactor: LDAP context provider to use AsyncSessionSearchRequest and adjust scopes --- app/ioc.py | 32 +++++++++++++++++---- app/ldap_protocol/ldap_requests/contexts.py | 4 ++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/app/ioc.py b/app/ioc.py index 8579e2be7..21fab49ba 100644 --- a/app/ioc.py +++ b/app/ioc.py @@ -66,6 +66,7 @@ from ldap_protocol.kerberos.service import KerberosService from ldap_protocol.kerberos.template_render import KRBTemplateRenderer from ldap_protocol.ldap_requests.contexts import ( + AsyncSessionSearchRequest, LDAPAddRequestContext, LDAPBindRequestContext, LDAPDeleteRequestContext, @@ -458,7 +459,7 @@ def get_dhcp_mngr( ) password_utils = provide(PasswordUtils, scope=Scope.RUNTIME) - access_manager = provide(AccessManager, scope=Scope.REQUEST) + access_manager = provide(AccessManager, scope=Scope.RUNTIME) role_dao = provide(RoleDAO, scope=Scope.REQUEST) ace_dao = provide(AccessControlEntryDAO, scope=Scope.REQUEST) role_use_case = provide(RoleUseCase, scope=Scope.REQUEST) @@ -503,15 +504,36 @@ class LDAPContextProvider(Provider): LDAPModifyDNRequestContext, scope=Scope.REQUEST, ) - search_request_context = provide( - LDAPSearchRequestContext, - scope=Scope.REQUEST, - ) unbind_request_context = provide( LDAPUnbindRequestContext, scope=Scope.REQUEST, ) + @provide(scope=Scope.SESSION) + async def create_search_session( + self, + async_session: async_sessionmaker[AsyncSession], + ) -> AsyncIterator[AsyncSessionSearchRequest]: + """Create session for request.""" + async with async_session() as session: + yield session # type: ignore + + @provide(scope=Scope.SESSION, provides=LDAPSearchRequestContext) + async def get_search_request_context( + self, + session: AsyncSessionSearchRequest, + ldap_session: LDAPSession, + settings: Settings, + access_manager: AccessManager, + ) -> LDAPSearchRequestContext: + """Get search request context.""" + return LDAPSearchRequestContext( + session=session, + ldap_session=ldap_session, + settings=settings, + access_manager=access_manager, + rootdse_rd=RootDSEReader(settings, SADomainGateway(session)), + ) class HTTPProvider(LDAPContextProvider): """HTTP LDAP session.""" diff --git a/app/ldap_protocol/ldap_requests/contexts.py b/app/ldap_protocol/ldap_requests/contexts.py index 4926d826e..23ff5c7f8 100644 --- a/app/ldap_protocol/ldap_requests/contexts.py +++ b/app/ldap_protocol/ldap_requests/contexts.py @@ -5,6 +5,7 @@ """ from dataclasses import dataclass +from typing import NewType from sqlalchemy.ext.asyncio import AsyncSession @@ -24,6 +25,7 @@ from ldap_protocol.session_storage import SessionStorage from password_utils import PasswordUtils +AsyncSessionSearchRequest = NewType("AsyncSessionSearchRequest", AsyncSession) @dataclass class LDAPAddRequestContext: @@ -74,7 +76,7 @@ class LDAPBindRequestContext: class LDAPSearchRequestContext: """Context for LDAP search request.""" - session: AsyncSession + session: AsyncSessionSearchRequest ldap_session: LDAPSession settings: Settings access_manager: AccessManager From 4e0c379586bada1fba0e48a58f40efa20ac55418 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 16 Dec 2025 13:01:22 +0300 Subject: [PATCH 11/22] test: enhance LDAP search request context provision with a dedicated method --- tests/conftest.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b97a8ce4a..fc869ad87 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -665,10 +665,25 @@ async def get_audit_monitor( LDAPModifyDNRequestContext, scope=Scope.REQUEST, ) - search_request_context = provide( - LDAPSearchRequestContext, - scope=Scope.REQUEST, - ) + + @provide(scope=Scope.REQUEST, provides=LDAPSearchRequestContext) + def get_search_request_context( + self, + session: AsyncSession, + ldap_session: LDAPSession, + settings: Settings, + access_manager: AccessManager, + rootdse_reader: RootDSEReader, + ) -> LDAPSearchRequestContext: + """Get search request context.""" + return LDAPSearchRequestContext( + session=session, # type: ignore + ldap_session=ldap_session, + settings=settings, + access_manager=access_manager, + rootdse_rd=rootdse_reader, + ) + unbind_request_context = provide( LDAPUnbindRequestContext, scope=Scope.REQUEST, From 37fde5a91c7717e125097f0d8271db225314b35c Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 16 Dec 2025 13:01:32 +0300 Subject: [PATCH 12/22] refactor: optimize event processing logic in BaseRequest class --- app/ldap_protocol/ldap_requests/base.py | 49 +++++++++++++------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/app/ldap_protocol/ldap_requests/base.py b/app/ldap_protocol/ldap_requests/base.py index 5f56898d6..89c3a4fbf 100644 --- a/app/ldap_protocol/ldap_requests/base.py +++ b/app/ldap_protocol/ldap_requests/base.py @@ -125,31 +125,32 @@ async def handle_tcp( responses.append(response) yield response - ldap_session = await container.get(LDAPSession) - settings = await container.get(Settings) - audit_use_case = await container.get(AuditUseCase) - - if await audit_use_case.check_event_processing_enabled( - self.PROTOCOL_OP, - ): - username = getattr( - ldap_session.user, - "user_principal_name", - "ANONYMOUS", - ) - event = RawAuditEventBuilderRedis.from_ldap_request( - self, - responses=responses, - username=username, - ip=ldap_session.ip, - protocol="TCP_LDAP", - settings=settings, - context=self.get_event_data(), - ) + if self.PROTOCOL_OP != ProtocolRequests.SEARCH: + ldap_session = await container.get(LDAPSession) + settings = await container.get(Settings) + audit_use_case = await container.get(AuditUseCase) + + if await audit_use_case.check_event_processing_enabled( + self.PROTOCOL_OP, + ): + username = getattr( + ldap_session.user, + "user_principal_name", + "ANONYMOUS", + ) + event = RawAuditEventBuilderRedis.from_ldap_request( + self, + responses=responses, + username=username, + ip=ldap_session.ip, + protocol="TCP_LDAP", + settings=settings, + context=self.get_event_data(), + ) - ldap_session.event_task_group.create_task( - audit_use_case.manager.send_event(event), - ) + ldap_session.event_task_group.create_task( + audit_use_case.manager.send_event(event), + ) async def _handle_api( self, From 6941c94a14c5b22b37e1eb4693c17abe4a3b2580 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 16 Dec 2025 13:21:12 +0300 Subject: [PATCH 13/22] refactor: add async method to provide LDAP search request context --- app/ioc.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/ioc.py b/app/ioc.py index 21fab49ba..d685308f7 100644 --- a/app/ioc.py +++ b/app/ioc.py @@ -693,6 +693,23 @@ def get_krb_template_render( scope=Scope.REQUEST, ) + @provide(scope=Scope.REQUEST, provides=LDAPSearchRequestContext) + async def get_search_request_context( + self, + session: AsyncSession, + ldap_session: LDAPSession, + settings: Settings, + access_manager: AccessManager, + ) -> LDAPSearchRequestContext: + """Get search request context.""" + return LDAPSearchRequestContext( + session=session, # type: ignore + ldap_session=ldap_session, + settings=settings, + access_manager=access_manager, + rootdse_rd=RootDSEReader(settings, SADomainGateway(session)), + ) + class LDAPServerProvider(LDAPContextProvider): """Provider with session scope.""" From 33a7d40ea9a14667198b67e75e21ddf2671080d9 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 16 Dec 2025 13:22:20 +0300 Subject: [PATCH 14/22] refactor: add blank line for improved readability in LDAP context provider and request context files --- app/ioc.py | 1 + app/ldap_protocol/ldap_requests/contexts.py | 1 + 2 files changed, 2 insertions(+) diff --git a/app/ioc.py b/app/ioc.py index d685308f7..9661b7a4f 100644 --- a/app/ioc.py +++ b/app/ioc.py @@ -535,6 +535,7 @@ async def get_search_request_context( rootdse_rd=RootDSEReader(settings, SADomainGateway(session)), ) + class HTTPProvider(LDAPContextProvider): """HTTP LDAP session.""" diff --git a/app/ldap_protocol/ldap_requests/contexts.py b/app/ldap_protocol/ldap_requests/contexts.py index 23ff5c7f8..0b20995d6 100644 --- a/app/ldap_protocol/ldap_requests/contexts.py +++ b/app/ldap_protocol/ldap_requests/contexts.py @@ -27,6 +27,7 @@ AsyncSessionSearchRequest = NewType("AsyncSessionSearchRequest", AsyncSession) + @dataclass class LDAPAddRequestContext: """Context for LDAP add request.""" From 2e4b692d0d1651dc1b5e97835d78cad7d7b2f694 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 16 Dec 2025 13:28:00 +0300 Subject: [PATCH 15/22] refactor: specify type for ctx parameter in handle method of AbandonRequest class --- app/ldap_protocol/ldap_requests/abandon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ldap_protocol/ldap_requests/abandon.py b/app/ldap_protocol/ldap_requests/abandon.py index 754fe3358..fd7c6a601 100644 --- a/app/ldap_protocol/ldap_requests/abandon.py +++ b/app/ldap_protocol/ldap_requests/abandon.py @@ -27,7 +27,7 @@ def from_data( """Create structure from ASN1Row dataclass list.""" return cls(message_id=1) - async def handle(self, ctx) -> AsyncGenerator: # type: ignore + async def handle(self, ctx: None) -> AsyncGenerator: # noqa: ARG002 """Handle message with current user.""" await asyncio.sleep(0) return From 8d578da9dd58864694c1cd3700a514f21cb7e968 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 16 Dec 2025 13:35:49 +0300 Subject: [PATCH 16/22] refactor: remove debug logging for group membership in SearchRequest class --- app/ldap_protocol/ldap_requests/search.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/ldap_protocol/ldap_requests/search.py b/app/ldap_protocol/ldap_requests/search.py index 155e3619a..1f9579dc2 100644 --- a/app/ldap_protocol/ldap_requests/search.py +++ b/app/ldap_protocol/ldap_requests/search.py @@ -507,7 +507,6 @@ async def _fill_attrs( ) if self.member_of: - logger.debug(f"Member of group: {directory.groups}") for group in directory.groups: attrs["memberOf"].append(group.directory.path_dn) From 165e9f0f71ed35702c6c606768fd925e18d952cb Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 16 Dec 2025 14:40:42 +0300 Subject: [PATCH 17/22] refactor: update context handling in BaseRequest class for improved protocol operation management --- app/ldap_protocol/ldap_requests/base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/ldap_protocol/ldap_requests/base.py b/app/ldap_protocol/ldap_requests/base.py index 89c3a4fbf..b4a480aa0 100644 --- a/app/ldap_protocol/ldap_requests/base.py +++ b/app/ldap_protocol/ldap_requests/base.py @@ -162,7 +162,11 @@ async def _handle_api( :param AsyncSession session: db session :return list[BaseResponse]: list of handled responses """ - kwargs = await resolve_deps(func=self.handle, container=container) + if self.PROTOCOL_OP != ProtocolRequests.ABANDON: + ctx = await container.get(self.CONTEXT_TYPE) # type: ignore + else: + ctx = None + ldap_session = await container.get(LDAPSession) settings = await container.get(Settings) audit_use_case = await container.get(AuditUseCase) @@ -174,7 +178,7 @@ async def _handle_api( else: log_api.info(f"{get_class_name(self)}[{un}]") - responses = [response async for response in self.handle(**kwargs)] + responses = [response async for response in self.handle(ctx=ctx)] if settings.DEBUG: for response in responses: From 848ef182268a51bd88d3005a7fcecd4193615bcc Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 16 Dec 2025 15:45:51 +0300 Subject: [PATCH 18/22] refactor: change get_search_request_context method to synchronous --- app/ioc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ioc.py b/app/ioc.py index 9661b7a4f..aa5a0ca56 100644 --- a/app/ioc.py +++ b/app/ioc.py @@ -519,7 +519,7 @@ async def create_search_session( yield session # type: ignore @provide(scope=Scope.SESSION, provides=LDAPSearchRequestContext) - async def get_search_request_context( + def get_search_request_context( self, session: AsyncSessionSearchRequest, ldap_session: LDAPSession, From 406426d65623f3c16a744d682af96b584f503256 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 13 Jan 2026 13:00:36 +0300 Subject: [PATCH 19/22] refactor: change search context scope back to request --- app/ioc.py | 48 ++------------------- app/ldap_protocol/ldap_requests/contexts.py | 5 +-- 2 files changed, 5 insertions(+), 48 deletions(-) diff --git a/app/ioc.py b/app/ioc.py index aa5a0ca56..e969f45c6 100644 --- a/app/ioc.py +++ b/app/ioc.py @@ -66,7 +66,6 @@ from ldap_protocol.kerberos.service import KerberosService from ldap_protocol.kerberos.template_render import KRBTemplateRenderer from ldap_protocol.ldap_requests.contexts import ( - AsyncSessionSearchRequest, LDAPAddRequestContext, LDAPBindRequestContext, LDAPDeleteRequestContext, @@ -508,32 +507,10 @@ class LDAPContextProvider(Provider): LDAPUnbindRequestContext, scope=Scope.REQUEST, ) - - @provide(scope=Scope.SESSION) - async def create_search_session( - self, - async_session: async_sessionmaker[AsyncSession], - ) -> AsyncIterator[AsyncSessionSearchRequest]: - """Create session for request.""" - async with async_session() as session: - yield session # type: ignore - - @provide(scope=Scope.SESSION, provides=LDAPSearchRequestContext) - def get_search_request_context( - self, - session: AsyncSessionSearchRequest, - ldap_session: LDAPSession, - settings: Settings, - access_manager: AccessManager, - ) -> LDAPSearchRequestContext: - """Get search request context.""" - return LDAPSearchRequestContext( - session=session, - ldap_session=ldap_session, - settings=settings, - access_manager=access_manager, - rootdse_rd=RootDSEReader(settings, SADomainGateway(session)), - ) + search_request_context = provide( + LDAPSearchRequestContext, + scope=Scope.REQUEST, + ) class HTTPProvider(LDAPContextProvider): @@ -694,23 +671,6 @@ def get_krb_template_render( scope=Scope.REQUEST, ) - @provide(scope=Scope.REQUEST, provides=LDAPSearchRequestContext) - async def get_search_request_context( - self, - session: AsyncSession, - ldap_session: LDAPSession, - settings: Settings, - access_manager: AccessManager, - ) -> LDAPSearchRequestContext: - """Get search request context.""" - return LDAPSearchRequestContext( - session=session, # type: ignore - ldap_session=ldap_session, - settings=settings, - access_manager=access_manager, - rootdse_rd=RootDSEReader(settings, SADomainGateway(session)), - ) - class LDAPServerProvider(LDAPContextProvider): """Provider with session scope.""" diff --git a/app/ldap_protocol/ldap_requests/contexts.py b/app/ldap_protocol/ldap_requests/contexts.py index 0b20995d6..4926d826e 100644 --- a/app/ldap_protocol/ldap_requests/contexts.py +++ b/app/ldap_protocol/ldap_requests/contexts.py @@ -5,7 +5,6 @@ """ from dataclasses import dataclass -from typing import NewType from sqlalchemy.ext.asyncio import AsyncSession @@ -25,8 +24,6 @@ from ldap_protocol.session_storage import SessionStorage from password_utils import PasswordUtils -AsyncSessionSearchRequest = NewType("AsyncSessionSearchRequest", AsyncSession) - @dataclass class LDAPAddRequestContext: @@ -77,7 +74,7 @@ class LDAPBindRequestContext: class LDAPSearchRequestContext: """Context for LDAP search request.""" - session: AsyncSessionSearchRequest + session: AsyncSession ldap_session: LDAPSession settings: Settings access_manager: AccessManager From ed35abecc09f9138d1a03b0fc555e183d3fef49a Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 13 Jan 2026 13:13:43 +0300 Subject: [PATCH 20/22] test: change search request context scope to request --- tests/conftest.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index fc869ad87..77080021b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -665,24 +665,10 @@ async def get_audit_monitor( LDAPModifyDNRequestContext, scope=Scope.REQUEST, ) - - @provide(scope=Scope.REQUEST, provides=LDAPSearchRequestContext) - def get_search_request_context( - self, - session: AsyncSession, - ldap_session: LDAPSession, - settings: Settings, - access_manager: AccessManager, - rootdse_reader: RootDSEReader, - ) -> LDAPSearchRequestContext: - """Get search request context.""" - return LDAPSearchRequestContext( - session=session, # type: ignore - ldap_session=ldap_session, - settings=settings, - access_manager=access_manager, - rootdse_rd=rootdse_reader, - ) + search_request_context = provide( + LDAPSearchRequestContext, + scope=Scope.REQUEST, + ) unbind_request_context = provide( LDAPUnbindRequestContext, From dbcea97dbd246153c28d81ad209cafe12f197aea Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 13 Jan 2026 13:27:57 +0300 Subject: [PATCH 21/22] add: abandon request context stub --- app/ioc.py | 5 +++++ app/ldap_protocol/ldap_requests/abandon.py | 4 +++- app/ldap_protocol/ldap_requests/base.py | 10 ++-------- app/ldap_protocol/ldap_requests/contexts.py | 4 ++++ tests/conftest.py | 5 +++++ 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/ioc.py b/app/ioc.py index e969f45c6..870657023 100644 --- a/app/ioc.py +++ b/app/ioc.py @@ -66,6 +66,7 @@ from ldap_protocol.kerberos.service import KerberosService from ldap_protocol.kerberos.template_render import KRBTemplateRenderer from ldap_protocol.ldap_requests.contexts import ( + LDAPAbandonRequestContext, LDAPAddRequestContext, LDAPBindRequestContext, LDAPDeleteRequestContext, @@ -511,6 +512,10 @@ class LDAPContextProvider(Provider): LDAPSearchRequestContext, scope=Scope.REQUEST, ) + abandon_request_context = provide( + LDAPAbandonRequestContext, + scope=Scope.REQUEST, + ) class HTTPProvider(LDAPContextProvider): diff --git a/app/ldap_protocol/ldap_requests/abandon.py b/app/ldap_protocol/ldap_requests/abandon.py index fd7c6a601..b9569ca0e 100644 --- a/app/ldap_protocol/ldap_requests/abandon.py +++ b/app/ldap_protocol/ldap_requests/abandon.py @@ -8,6 +8,7 @@ from typing import AsyncGenerator, ClassVar from ldap_protocol.asn1parser import ASN1Row +from ldap_protocol.ldap_requests.contexts import LDAPAbandonRequestContext from ldap_protocol.objects import ProtocolRequests from .base import BaseRequest @@ -16,6 +17,7 @@ class AbandonRequest(BaseRequest): """Abandon protocol.""" + CONTEXT_TYPE: ClassVar[type] = LDAPAbandonRequestContext PROTOCOL_OP: ClassVar[int] = ProtocolRequests.ABANDON message_id: int @@ -27,7 +29,7 @@ def from_data( """Create structure from ASN1Row dataclass list.""" return cls(message_id=1) - async def handle(self, ctx: None) -> AsyncGenerator: # noqa: ARG002 + async def handle(self, ctx: LDAPAbandonRequestContext) -> AsyncGenerator: # noqa: ARG002 """Handle message with current user.""" await asyncio.sleep(0) return diff --git a/app/ldap_protocol/ldap_requests/base.py b/app/ldap_protocol/ldap_requests/base.py index b4a480aa0..445ce3bae 100644 --- a/app/ldap_protocol/ldap_requests/base.py +++ b/app/ldap_protocol/ldap_requests/base.py @@ -115,10 +115,7 @@ async def handle_tcp( container: AsyncContainer, ) -> AsyncIterator[BaseResponse]: """Hanlde response with tcp.""" - if self.PROTOCOL_OP != ProtocolRequests.ABANDON: - ctx = await container.get(self.CONTEXT_TYPE) # type: ignore - else: - ctx = None + ctx = await container.get(self.CONTEXT_TYPE) # type: ignore responses = [] async for response in self.handle(ctx=ctx): @@ -162,10 +159,7 @@ async def _handle_api( :param AsyncSession session: db session :return list[BaseResponse]: list of handled responses """ - if self.PROTOCOL_OP != ProtocolRequests.ABANDON: - ctx = await container.get(self.CONTEXT_TYPE) # type: ignore - else: - ctx = None + ctx = await container.get(self.CONTEXT_TYPE) # type: ignore ldap_session = await container.get(LDAPSession) settings = await container.get(Settings) diff --git a/app/ldap_protocol/ldap_requests/contexts.py b/app/ldap_protocol/ldap_requests/contexts.py index 4926d826e..0e3f6e252 100644 --- a/app/ldap_protocol/ldap_requests/contexts.py +++ b/app/ldap_protocol/ldap_requests/contexts.py @@ -123,3 +123,7 @@ class LDAPModifyDNRequestContext: access_manager: AccessManager role_use_case: RoleUseCase attribute_value_validator: AttributeValueValidator + +@dataclass +class LDAPAbandonRequestContext: + ... diff --git a/tests/conftest.py b/tests/conftest.py index 77080021b..a90d4fee6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -87,6 +87,7 @@ from ldap_protocol.kerberos.template_render import KRBTemplateRenderer from ldap_protocol.ldap_requests.bind import BindRequest from ldap_protocol.ldap_requests.contexts import ( + LDAPAbandonRequestContext, LDAPAddRequestContext, LDAPBindRequestContext, LDAPDeleteRequestContext, @@ -669,6 +670,10 @@ async def get_audit_monitor( LDAPSearchRequestContext, scope=Scope.REQUEST, ) + abandon_request_context = provide( + LDAPAbandonRequestContext, + scope=Scope.REQUEST, + ) unbind_request_context = provide( LDAPUnbindRequestContext, From adfd3861efd2de042deca7f1fa63c9f66d7a71f0 Mon Sep 17 00:00:00 2001 From: Naksen Date: Tue, 13 Jan 2026 15:04:35 +0300 Subject: [PATCH 22/22] refactor: format --- app/ldap_protocol/ldap_requests/contexts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/ldap_protocol/ldap_requests/contexts.py b/app/ldap_protocol/ldap_requests/contexts.py index 0e3f6e252..98f6e1a9b 100644 --- a/app/ldap_protocol/ldap_requests/contexts.py +++ b/app/ldap_protocol/ldap_requests/contexts.py @@ -124,6 +124,6 @@ class LDAPModifyDNRequestContext: role_use_case: RoleUseCase attribute_value_validator: AttributeValueValidator + @dataclass -class LDAPAbandonRequestContext: - ... +class LDAPAbandonRequestContext: ...