Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
86 changes: 86 additions & 0 deletions app/alembic/versions/f4e6cd18a01d_add_samaccounttype.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Add sAMAccountType to existing user/group/computer entries.

Revision ID: f4e6cd18a01d
Revises: 379fce54fb08
Create Date: 2026-01-30 13:08:26.299158

"""

from alembic import op
from dishka import AsyncContainer, Scope
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncSession
from sqlalchemy.orm import selectinload

from entities import Attribute, Directory, EntityType
from enums import EntityTypeNames, SamAccountType
from repo.pg.tables import queryable_attr as qa

revision: None | str = "f4e6cd18a01d"
down_revision: None | str = "379fce54fb08"
branch_labels: None | list[str] = None
depends_on: None | list[str] = None

_SAM_ACCOUNT_TYPE_ATTR = "sAMAccountType"
_SECURITY_PRINCIPAL_TYPES = (
EntityTypeNames.USER,
EntityTypeNames.GROUP,
EntityTypeNames.COMPUTER,
)
_ENTITY_TO_SAM: dict[str, SamAccountType] = {
EntityTypeNames.USER: SamAccountType.SAM_USER_OBJECT,
EntityTypeNames.GROUP: SamAccountType.SAM_GROUP_OBJECT,
EntityTypeNames.COMPUTER: SamAccountType.SAM_MACHINE_ACCOUNT,
}


def upgrade(container: AsyncContainer) -> None:
"""Add sAMAccountType attributes for user/group/computer."""

async def _add_samaccounttype(connection: AsyncConnection) -> None: # noqa: ARG001
async with container(scope=Scope.REQUEST) as cnt:
session = await cnt.get(AsyncSession)

entity_types = await session.scalars(
select(EntityType).where(
qa(EntityType.name).in_(_SECURITY_PRINCIPAL_TYPES),
),
)
entity_type_ids = [et.id for et in entity_types]
if not entity_type_ids:
return

has_sam = select(qa(Attribute.directory_id)).where(
qa(Attribute.name).ilike(_SAM_ACCOUNT_TYPE_ATTR.lower()),
)
dirs_without_sam = await session.scalars(
select(Directory)
.where(
qa(Directory.entity_type_id).in_(entity_type_ids),
~qa(Directory.id).in_(has_sam),
)
.options(selectinload(qa(Directory.entity_type))),
)

for directory in dirs_without_sam:
if not directory.entity_type:
continue
sam_value = _ENTITY_TO_SAM.get(directory.entity_type.name)
if sam_value is None:
continue

session.add(
Attribute(
name=_SAM_ACCOUNT_TYPE_ATTR,
value=str(sam_value),
directory_id=directory.id,
),
)

await session.commit()

op.run_async(_add_samaccounttype)


def downgrade(container: AsyncContainer) -> None:
"""Downgrade."""
20 changes: 14 additions & 6 deletions app/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from typing import TypedDict

from enums import EntityTypeNames
from enums import EntityTypeNames, SamAccountType

GROUPS_CONTAINER_NAME = "Groups"
COMPUTERS_CONTAINER_NAME = "Computers"
Expand All @@ -24,7 +24,7 @@
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": ["groups"],
"sAMAccountType": ["268435456"],
"sAMAccountType": [str(SamAccountType.SAM_GROUP_OBJECT.value)],
}


Expand Down Expand Up @@ -308,7 +308,9 @@ class EntityTypeData(TypedDict):
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": [DOMAIN_ADMIN_GROUP_NAME],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
"gidNumber": ["512"],
},
"objectSid": 512,
Expand All @@ -321,7 +323,9 @@ class EntityTypeData(TypedDict):
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": [DOMAIN_USERS_GROUP_NAME],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
"gidNumber": ["513"],
},
"objectSid": 513,
Expand All @@ -334,7 +338,9 @@ class EntityTypeData(TypedDict):
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": [READ_ONLY_GROUP_NAME],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
"gidNumber": ["521"],
},
"objectSid": 521,
Expand All @@ -347,7 +353,9 @@ class EntityTypeData(TypedDict):
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": [DOMAIN_COMPUTERS_GROUP_NAME],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
"gidNumber": ["515"],
},
"objectSid": 515,
Expand Down
20 changes: 20 additions & 0 deletions app/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,23 @@ class DomainCodes(IntEnum):
DHCP = 12
LDAP_SCHEMA = 13
SHADOW = 14


class SamAccountType(IntEnum):
"""SAM Account Type values."""

SAM_DOMAIN_OBJECT = 0
SAM_GROUP_OBJECT = 268435456
SAM_NON_SECURITY_GROUP_OBJECT = 268435457
SAM_ALIAS_OBJECT = 536870912
SAM_NON_SECURITY_ALIAS_OBJECT = 536870913
SAM_USER_OBJECT = 805306368
SAM_MACHINE_ACCOUNT = 805306369
SAM_TRUST_ACCOUNT = 805306370
SAM_APP_BASIC_GROUP = 1073741824
SAM_APP_QUERY_GROUP = 1073741825

@staticmethod
def to_hex(value: int) -> str:
"""Convert decimal value to hex string."""
return hex(value)
4 changes: 4 additions & 0 deletions app/ldap_protocol/auth/use_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
FIRST_SETUP_DATA,
USERS_CONTAINER_NAME,
)
from enums import SamAccountType
from ldap_protocol.auth.dto import SetupDTO
from ldap_protocol.auth.setup_gateway import SetupGateway
from ldap_protocol.identity.exceptions import (
Expand Down Expand Up @@ -114,6 +115,9 @@ def _create_user_data(self, dto: SetupDTO) -> dict:
"userAccountControl": ["512"],
"primaryGroupID": ["512"],
"givenName": [dto.username],
"sAMAccountType": [
str(SamAccountType.SAM_USER_OBJECT)
],
},
"objectSid": 500,
},
Expand Down
28 changes: 27 additions & 1 deletion app/ldap_protocol/ldap_requests/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from constants import DOMAIN_COMPUTERS_GROUP_NAME, DOMAIN_USERS_GROUP_NAME
from entities import Attribute, Directory, Group, User
from enums import AceType, EntityTypeNames
from enums import AceType, EntityTypeNames, SamAccountType
from ldap_protocol.asn1parser import ASN1Row
from ldap_protocol.kerberos.exceptions import (
KRBAPIAddPrincipalError,
Expand Down Expand Up @@ -426,6 +426,32 @@ async def handle( # noqa: C901
),
)

if "samaccounttype" not in self.l_attrs_dict:
if is_user:
attributes.append(
Attribute(
name="sAMAccountType",
value=str(SamAccountType.SAM_USER_OBJECT),
directory_id=new_dir.id,
),
)
elif is_group:
attributes.append(
Attribute(
name="sAMAccountType",
value=str(SamAccountType.SAM_GROUP_OBJECT),
directory_id=new_dir.id,
),
)
elif is_computer:
attributes.append(
Attribute(
name="sAMAccountType",
value=str(SamAccountType.SAM_MACHINE_ACCOUNT),
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
3 changes: 2 additions & 1 deletion app/ldap_protocol/utils/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from sqlalchemy.sql.expression import ColumnElement

from entities import Attribute, Directory, Group, User
from enums import SamAccountType
from ldap_protocol.ldap_schema.attribute_value_validator import (
AttributeValueValidator,
AttributeValueValidatorError,
Expand Down Expand Up @@ -394,7 +395,7 @@ async def create_group(
"instanceType": ["4"],
"sAMAccountName": [dir_.name],
dir_.rdname: [dir_.name],
"sAMAccountType": ["268435456"],
"sAMAccountType": [str(SamAccountType.SAM_GROUP_OBJECT.value)],
"gidNumber": [str(create_integer_hash(dir_.name))],
}

Expand Down
2 changes: 1 addition & 1 deletion interface
33 changes: 25 additions & 8 deletions tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
GROUPS_CONTAINER_NAME,
USERS_CONTAINER_NAME,
)
from enums import SamAccountType
from ldap_protocol.objects import UserAccountControlFlag

TEST_DATA = [
Expand All @@ -30,7 +31,9 @@
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": [DOMAIN_ADMIN_GROUP_NAME],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
},
},
{
Expand All @@ -42,7 +45,9 @@
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": ["developers"],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
},
},
{
Expand All @@ -53,7 +58,9 @@
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": ["admin login only"],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
},
},
{
Expand All @@ -64,7 +71,9 @@
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": [DOMAIN_USERS_GROUP_NAME],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
},
},
{
Expand All @@ -75,7 +84,9 @@
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": [DOMAIN_COMPUTERS_GROUP_NAME],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
},
},
],
Expand Down Expand Up @@ -368,7 +379,9 @@
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": ["testGroup1"],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
},
},
],
Expand All @@ -381,7 +394,9 @@
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": ["testGroup2"],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
},
},
],
Expand All @@ -402,7 +417,9 @@
"groupType": ["-2147483646"],
"instanceType": ["4"],
"sAMAccountName": ["testGroup3"],
"sAMAccountType": ["268435456"],
"sAMAccountType": [
str(SamAccountType.SAM_GROUP_OBJECT.value),
],
},
},
],
Expand Down
Loading
Loading