Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions app/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,6 @@ class User:
"homedirectory": "homeDirectory",
}

def get_upn_prefix(self) -> str:
return self.user_principal_name.split("@")[0]

def is_expired(self) -> bool:
if self.account_exp is None:
return False
Expand Down
2 changes: 1 addition & 1 deletion app/extra/scripts/principal_block_user_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async def principal_block_sync(

if "@" in user.user_principal_name:
principal_postfix = user.user_principal_name.split("@")[1].upper()
principal_name = f"{user.get_upn_prefix()}@{principal_postfix}"
principal_name = f"{user.sam_account_name}@{principal_postfix}"
else:
continue

Expand Down
2 changes: 1 addition & 1 deletion app/extra/scripts/uac_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async def disable_accounts(
) # fmt: skip

async for user in users:
await kadmin.lock_principal(user.get_upn_prefix())
await kadmin.lock_principal(user.sam_account_name)

await add_lock_and_expire_attributes(
session,
Expand Down
2 changes: 1 addition & 1 deletion app/ldap_protocol/auth/auth_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ async def _update_password(

if include_krb:
await self._kadmin.create_or_update_principal_pw(
user.get_upn_prefix(),
user.sam_account_name,
new_password,
)

Expand Down
31 changes: 17 additions & 14 deletions app/ldap_protocol/ldap_requests/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,7 @@ async def handle( # noqa: C901
# in the attributes
if (
attr_name in Directory.ro_fields
or attr_name
in (
"userpassword",
"unicodepwd",
)
or attr_name in ("userpassword", "unicodepwd")
or attr_name == new_dir.rdname
):
continue
Expand Down Expand Up @@ -342,11 +338,11 @@ async def handle( # noqa: C901
),
)

for uattr, value in {
"loginShell": "/bin/bash",
"uidNumber": str(create_integer_hash(user.sam_account_name)),
"homeDirectory": f"/home/{user.sam_account_name}",
}.items():
for uattr, value in (
("loginShell", "/bin/bash"),
("uidNumber", str(create_integer_hash(user.sam_account_name))),
("homeDirectory", f"/home/{user.sam_account_name}"),
):
if uattr in user_attributes:
value = user_attributes[uattr]
del user_attributes[uattr]
Expand Down Expand Up @@ -421,6 +417,15 @@ async def handle( # noqa: C901
),
)

if is_computer:
attributes.append(
Attribute(
name="sAMAccountName",
value=f"{new_dir.name}",
directory_id=new_dir.id,
),
)

if not ctx.attribute_value_validator.is_directory_attributes_valid(
entity_type.name if entity_type else "",
attributes,
Expand Down Expand Up @@ -461,16 +466,14 @@ async def handle( # noqa: C901
KRBAPIDeletePrincipalError,
KRBAPIPrincipalNotFoundError,
):
await ctx.kadmin.del_principal(
user.get_upn_prefix(),
)
await ctx.kadmin.del_principal(user.sam_account_name)

pw = (
self.password.get_secret_value()
if self.password
else None
)
await ctx.kadmin.add_principal(user.get_upn_prefix(), pw)
await ctx.kadmin.add_principal(user.sam_account_name, pw)

elif is_computer:
await ctx.kadmin.add_principal(
Expand Down
2 changes: 1 addition & 1 deletion app/ldap_protocol/ldap_requests/bind.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ async def handle(
KRBAPIConnectionError,
):
await ctx.kadmin.add_principal(
user.get_upn_prefix(),
user.sam_account_name,
self.authentication_choice.password.get_secret_value(),
0.1,
)
Expand Down
2 changes: 1 addition & 1 deletion app/ldap_protocol/ldap_requests/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ async def handle( # noqa: C901
await ctx.session_storage.clear_user_sessions(
directory.user.id,
)
await ctx.kadmin.del_principal(directory.user.get_upn_prefix())
await ctx.kadmin.del_principal(directory.user.sam_account_name)

if await is_computer(directory.id, ctx.session):
await ctx.kadmin.del_principal(directory.host_principal)
Expand Down
2 changes: 1 addition & 1 deletion app/ldap_protocol/ldap_requests/extended.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ async def handle(
):
try:
await ctx.kadmin.create_or_update_principal_pw(
user.get_upn_prefix(),
user.sam_account_name,
new_password,
)
except (
Expand Down
130 changes: 115 additions & 15 deletions app/ldap_protocol/ldap_requests/modify.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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)",
Comment thread
milov-dmitriy marked this conversation as resolved.
Outdated
)
return

Expand Down Expand Up @@ -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(
Comment thread
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()),
Comment thread
milov-dmitriy marked this conversation as resolved.
Outdated
)

async def _delete(
self,
change: Changes,
Expand Down Expand Up @@ -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(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

С чем связано применение здесь .lower() ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

остатки от полета фантазии. отменил
но изначально задумывалось для кейса когда не все значения колонки name таблицы Attributes хранятся в нижнем регистре

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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(
Comment thread
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
Comment thread
milov-dmitriy marked this conversation as resolved.
Outdated
Comment thread
milov-dmitriy marked this conversation as resolved.
Outdated

if attrs:
del_query = (
delete(Attribute)
Expand Down Expand Up @@ -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(
Comment thread
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)
Expand All @@ -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:
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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:
Expand All @@ -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),
Copy link
Copy Markdown
Collaborator

@Naksen Naksen Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а почему если приходит изменение на userprincipalname, то меняется только samaccountname ( и то же самое наоборот) ? и кажется, что можно объединить логику для этих атрибутов

Copy link
Copy Markdown
Collaborator Author

@milov-dmitriy milov-dmitriy Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

всё дело в строках 959-963 на текущей ветке
при изменении свойства Х - свойство Х поменяет значение, но для этих двух атрибутов нужно менять не только этот атрибут но еще и атрибут, "связанный" с ним, т.е.:

  1. изменяется samaccName - меняем userprincipalname и потом samaccName
  2. изменяется userprincipalname - меняем samaccName и потом userprincipalname

Copy link
Copy Markdown
Collaborator Author

@milov-dmitriy milov-dmitriy Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

по поводу объединения логик. сначала я так и сделал. но не надо. мне очень не понравилась такая реализация.
с разбиением на две разные ветки гораздо лучше выглядит
если хочешь можем вместе попробовать объединить логику, но мой предыдущий вариант мне не понравилась. все становится более "макаронным" чем есть

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p.s.

  1. для юзера дергаем rename принципала 1 раз
  2. для компьютера дергаем rename принципала 2 раза

)

elif name == "samaccountname":
new_userprincipalname = f"{str(new_value)}@{base_dir.name}"
Comment thread
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)}",
)
Comment thread
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")
Expand Down Expand Up @@ -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,
)

Expand Down
2 changes: 1 addition & 1 deletion interface
Loading
Loading