-
Notifications
You must be signed in to change notification settings - Fork 0
Add: sync user's and computer's names with principal name after ModifyRequest:sAMAccountName
#911
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
71afa54
3d60579
8f76819
7ab025f
9a32004
53025b5
dcc6af3
07bdcad
183dc81
8b4492c
2040f6e
11f45e1
febb0c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,7 @@ | |
| from typing import AsyncGenerator, ClassVar | ||
|
|
||
| from loguru import logger | ||
| from sqlalchemy import Select, and_, delete, or_, select, update | ||
| from sqlalchemy import Select, and_, delete, func, or_, select, update | ||
| from sqlalchemy.exc import IntegrityError | ||
| from sqlalchemy.ext.asyncio import AsyncSession | ||
| from sqlalchemy.orm import joinedload, selectinload | ||
|
|
@@ -37,11 +37,16 @@ | |
| from ldap_protocol.policies.password import PasswordPolicyUseCases | ||
| from ldap_protocol.session_storage import SessionStorage | ||
| from ldap_protocol.utils.cte import check_root_group_membership_intersection | ||
| from ldap_protocol.utils.helpers import ft_to_dt, validate_entry | ||
| from ldap_protocol.utils.helpers import ( | ||
| ft_to_dt, | ||
| is_dn_in_base_directory, | ||
| validate_entry, | ||
| ) | ||
| from ldap_protocol.utils.queries import ( | ||
| add_lock_and_expire_attributes, | ||
| clear_group_membership, | ||
| extend_group_membership, | ||
| get_base_directories, | ||
| get_directories, | ||
| get_directory_by_rid, | ||
| get_filter_from_path, | ||
|
|
@@ -99,6 +104,7 @@ class ModifyRequest(BaseRequest): | |
|
|
||
| object: str | ||
| changes: list[Changes] | ||
| _old_vals: dict[str, str | None] = {} | ||
|
|
||
| @classmethod | ||
| def from_data(cls, data: list[ASN1Row]) -> "ModifyRequest": | ||
|
|
@@ -222,7 +228,7 @@ async def handle( | |
| await ctx.session.rollback() | ||
| yield ModifyResponse( | ||
| result_code=LDAPCodes.UNDEFINED_ATTRIBUTE_TYPE, | ||
| message="Invalid attribute value(s)", | ||
| errorMessage="Invalid attribute value(s)", | ||
| ) | ||
| return | ||
|
|
||
|
|
@@ -612,6 +618,18 @@ async def _validate_object_class_modification( | |
| if is_object_class_in_replaced or is_object_class_in_deleted: | ||
| raise ModifyForbiddenError("ObjectClass can't be deleted.") | ||
|
|
||
| def _need_to_cache_old_value( | ||
|
milov-dmitriy marked this conversation as resolved.
Outdated
|
||
| self, | ||
| change: Changes, | ||
| directory: Directory, | ||
| ) -> bool: | ||
| return bool( | ||
| directory.entity_type | ||
| and directory.entity_type.name == EntityTypeNames.COMPUTER | ||
| and change.get_name() == "samaccountname" | ||
| and not self._old_vals.get(change.get_name()), | ||
|
milov-dmitriy marked this conversation as resolved.
Outdated
|
||
| ) | ||
|
|
||
| async def _delete( | ||
| self, | ||
| change: Changes, | ||
|
|
@@ -656,11 +674,22 @@ async def _delete( | |
|
|
||
| attrs.append( | ||
| and_( | ||
| qa(Attribute.name) == change.modification.type, | ||
| func.lower(qa(Attribute.name)) | ||
| == change.modification.type.lower(), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. С чем связано применение здесь .lower() ?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. остатки от полета фантазии. отменил
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. не, это нужно. если нам приходит ModifyRequest: REPLACE: и сохранять старое значение не надо, то не для всех атрибутов это работает корректно, например это работало некорректно для атрибута krbprincipalname (в общий чат скинул пример) |
||
| condition, | ||
| ), | ||
| ) | ||
|
|
||
| if self._need_to_cache_old_value(change, directory): | ||
| result = await session.execute( | ||
|
milov-dmitriy marked this conversation as resolved.
Outdated
|
||
| select(Attribute) | ||
| .filter_by( | ||
| directory=directory, | ||
| name=change.modification.type, | ||
| ), | ||
| ) # fmt: skip | ||
| self._old_vals[change.get_name()] = result.scalar_one().value | ||
|
milov-dmitriy marked this conversation as resolved.
Outdated
milov-dmitriy marked this conversation as resolved.
Outdated
|
||
|
|
||
| if attrs: | ||
| del_query = ( | ||
| delete(Attribute) | ||
|
|
@@ -800,10 +829,25 @@ async def _add( # noqa: C901 | |
| attrs = [] | ||
| name = change.get_name() | ||
|
|
||
| if name in {"memberof", "member", "primarygroupid"}: | ||
| if name in ("memberof", "member", "primarygroupid"): | ||
| await self._add_group_attrs(change, directory, session) | ||
| return | ||
|
|
||
| base_dir = None | ||
| for base_directory in await get_base_directories( | ||
|
milov-dmitriy marked this conversation as resolved.
Outdated
|
||
| session, | ||
| ): | ||
| if is_dn_in_base_directory( | ||
| base_directory, | ||
| directory.path_dn, | ||
| ): | ||
| base_dir = base_directory | ||
| break | ||
| else: | ||
| raise ModifyForbiddenError( | ||
| "Base directory for computer not found.", | ||
| ) | ||
|
|
||
| for value in change.modification.vals: | ||
| if name == "useraccountcontrol": | ||
| uac_val = int(value) | ||
|
|
@@ -812,9 +856,7 @@ async def _add( # noqa: C901 | |
| continue | ||
|
|
||
| elif ( | ||
| bool( | ||
| uac_val & UserAccountControlFlag.ACCOUNTDISABLE, | ||
| ) | ||
| bool(uac_val & UserAccountControlFlag.ACCOUNTDISABLE) | ||
| and directory.user | ||
| ): | ||
| if directory.path_dn == current_user.dn: | ||
|
|
@@ -823,7 +865,7 @@ async def _add( # noqa: C901 | |
| ) | ||
|
|
||
| await kadmin.lock_principal( | ||
| directory.user.get_upn_prefix(), | ||
| directory.user.sam_account_name, | ||
| ) | ||
|
|
||
| await add_lock_and_expire_attributes( | ||
|
|
@@ -837,9 +879,7 @@ async def _add( # noqa: C901 | |
| ) | ||
|
|
||
| elif ( | ||
| not bool( | ||
| uac_val & UserAccountControlFlag.ACCOUNTDISABLE, | ||
| ) | ||
| not bool(uac_val & UserAccountControlFlag.ACCOUNTDISABLE) | ||
| and directory.user | ||
| ): | ||
| await unlock_principal( | ||
|
|
@@ -860,7 +900,7 @@ async def _add( # noqa: C901 | |
|
|
||
| if name == "pwdlastset" and value == "0" and directory.user: | ||
| await kadmin.force_princ_pw_change( | ||
| directory.user.get_upn_prefix(), | ||
| directory.user.sam_account_name, | ||
| ) | ||
|
|
||
| if name == directory.rdname: | ||
|
|
@@ -877,17 +917,77 @@ async def _add( # noqa: C901 | |
| .values({name: value}), | ||
| ) | ||
|
|
||
| elif name in User.search_fields: | ||
| elif ( | ||
| name in User.search_fields | ||
| and directory.entity_type | ||
| and directory.entity_type.name == EntityTypeNames.USER | ||
| and directory.user | ||
| ): | ||
| if name == "accountexpires": | ||
| new_value = ft_to_dt(int(value)) if value != "0" else None | ||
| else: | ||
| new_value = value # type: ignore | ||
|
|
||
| if name == "userprincipalname": | ||
| new_samaccountname = str(new_value).split("@")[0] | ||
|
|
||
| await kadmin.rename_princ( | ||
| directory.user.sam_account_name, | ||
| new_samaccountname, | ||
| ) | ||
|
|
||
| await session.execute( | ||
| update(User) | ||
| .filter_by(directory=directory) | ||
| .values(samaccountname=new_samaccountname), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. а почему если приходит изменение на userprincipalname, то меняется только samaccountname ( и то же самое наоборот) ? и кажется, что можно объединить логику для этих атрибутов
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. всё дело в строках 959-963 на текущей ветке
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. по поводу объединения логик. сначала я так и сделал. но не надо. мне очень не понравилась такая реализация.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p.s.
|
||
| ) | ||
|
|
||
| elif name == "samaccountname": | ||
| new_userprincipalname = f"{str(new_value)}@{base_dir.name}" | ||
|
milov-dmitriy marked this conversation as resolved.
Outdated
|
||
|
|
||
| await kadmin.rename_princ( | ||
| directory.user.sam_account_name, | ||
| str(new_value), | ||
| ) | ||
|
|
||
| await session.execute( | ||
| update(User) | ||
| .filter_by(directory=directory) | ||
| .values(userprincipalname=new_userprincipalname), | ||
| ) | ||
|
|
||
| await session.execute( | ||
| update(User) | ||
| .filter_by(directory=directory) | ||
| .values({name: new_value}), | ||
| ) | ||
|
|
||
| elif ( | ||
| name == "samaccountname" | ||
| and directory.entity_type | ||
| and directory.entity_type.name == EntityTypeNames.COMPUTER | ||
| ): | ||
| samaccountname_old_val = self._old_vals.get( | ||
| change.get_name(), | ||
| ) | ||
|
|
||
| await kadmin.rename_princ( | ||
| f"host/{samaccountname_old_val}", | ||
| f"host/{str(value)}", | ||
| ) | ||
|
milov-dmitriy marked this conversation as resolved.
Outdated
|
||
| await kadmin.rename_princ( | ||
| f"host/{samaccountname_old_val}.{base_dir.name}", | ||
| f"host/{str(value)}.{base_dir.name}", | ||
| ) | ||
| attrs.append( | ||
| Attribute( | ||
| name=change.modification.type, | ||
| value=value if isinstance(value, str) else None, | ||
| bvalue=value if isinstance(value, bytes) else None, | ||
| directory_id=directory.id, | ||
| ), | ||
| ) # fmt: skip | ||
|
|
||
| elif name in ("userpassword", "unicodepwd") and directory.user: | ||
| if not settings.USE_CORE_TLS: | ||
| raise PermissionError("TLS required") | ||
|
|
@@ -918,7 +1018,7 @@ async def _add( # noqa: C901 | |
| directory.user, | ||
| ) | ||
| await kadmin.create_or_update_principal_pw( | ||
| directory.user.get_upn_prefix(), | ||
| directory.user.sam_account_name, | ||
| value, | ||
| ) | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.