or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

active-directory.mdasync-frameworks.mdconnection-pooling.mdcore-ldap.mdindex.mdldif-support.mdutilities-errors.md

active-directory.mddocs/

0

# Active Directory Integration

1

2

Comprehensive Windows Active Directory support including Security Identifiers (SID), Access Control Lists (ACL), Security Descriptors, and User Account Control flags. These components enable working with Windows-specific LDAP attributes and security structures.

3

4

## Capabilities

5

6

### Security Identifiers (SID)

7

8

Windows Security Identifier handling for users, groups, and computer accounts with string and binary format conversion.

9

10

```python { .api }

11

from bonsai.active_directory import SID

12

13

class SID:

14

def __init__(

15

self,

16

str_rep: Optional[str] = None,

17

bytes_le: Optional[bytes] = None

18

) -> None:

19

"""

20

Initialize SID from string or binary representation.

21

22

Parameters:

23

- str_rep: String SID format (e.g., "S-1-5-21-1234567890-987654321-1111111111-1001")

24

- bytes_le: Binary SID in little-endian byte order

25

26

Note: Exactly one parameter must be provided

27

"""

28

29

def __str__(self) -> str:

30

"""Convert SID to standard string format."""

31

32

def __eq__(self, other) -> bool:

33

"""Compare SIDs for equality."""

34

35

def __hash__(self) -> int:

36

"""Hash SID for use in sets and dictionaries."""

37

38

@property

39

def revision(self) -> int:

40

"""SID revision number (usually 1)."""

41

42

@property

43

def identifier_authority(self) -> int:

44

"""Top-level authority that issued the SID."""

45

46

@property

47

def subauthorities(self) -> Tuple[int, ...]:

48

"""Tuple of subauthority values."""

49

50

@property

51

def bytes_le(self) -> bytes:

52

"""Binary representation in little-endian byte order."""

53

54

def is_well_known(self) -> bool:

55

"""Check if SID is a well-known Windows SID."""

56

57

def get_account_domain_sid(self) -> Optional["SID"]:

58

"""

59

Get the domain SID portion by removing the RID.

60

61

Returns:

62

Domain SID if this is an account SID, None otherwise

63

"""

64

65

def get_rid(self) -> Optional[int]:

66

"""

67

Get Relative Identifier (RID) - the last subauthority.

68

69

Returns:

70

RID value if this is an account SID, None otherwise

71

"""

72

73

@property

74

def sddl_alias(self) -> Optional[str]:

75

"""SDDL alias for well-known SIDs (e.g., 'BA' for Administrators)."""

76

77

@property

78

def size(self) -> int:

79

"""Size of SID in bytes."""

80

```

81

82

### Access Control Lists (ACL)

83

84

Windows Access Control List implementation with support for Discretionary (DACL) and System (SACL) access control lists.

85

86

```python { .api }

87

from bonsai.active_directory import ACL, ACE, ACLRevision, ACEFlag, ACERight, ACEType

88

from enum import IntEnum

89

90

class ACLRevision(IntEnum):

91

ACL_REVISION = 2

92

ACL_REVISION_DS = 4

93

94

class ACEType(IntEnum):

95

ACCESS_ALLOWED = 0

96

ACCESS_DENIED = 1

97

SYSTEM_AUDIT = 2

98

SYSTEM_ALARM = 3

99

ACCESS_ALLOWED_COMPOUND = 4

100

ACCESS_ALLOWED_OBJECT = 5

101

ACCESS_DENIED_OBJECT = 6

102

SYSTEM_AUDIT_OBJECT = 7

103

SYSTEM_ALARM_OBJECT = 8

104

ACCESS_ALLOWED_CALLBACK = 9

105

ACCESS_DENIED_CALLBACK = 10

106

ACCESS_ALLOWED_CALLBACK_OBJECT = 11

107

ACCESS_DENIED_CALLBACK_OBJECT = 12

108

SYSTEM_AUDIT_CALLBACK = 13

109

SYSTEM_ALARM_CALLBACK = 14

110

SYSTEM_AUDIT_CALLBACK_OBJECT = 15

111

SYSTEM_ALARM_CALLBACK_OBJECT = 16

112

113

class ACEFlag(IntEnum):

114

OBJECT_INHERIT = 0x01

115

CONTAINER_INHERIT = 0x02

116

NO_PROPAGATE_INHERIT = 0x04

117

INHERIT_ONLY = 0x08

118

INHERITED = 0x10

119

SUCCESSFUL_ACCESS = 0x40

120

FAILED_ACCESS = 0x80

121

122

class ACERight(IntEnum):

123

GENERIC_READ = 0x80000000

124

GENERIC_WRITE = 0x40000000

125

GENERIC_EXECUTE = 0x20000000

126

GENERIC_ALL = 0x10000000

127

DELETE = 0x00010000

128

READ_CONTROL = 0x00020000

129

WRITE_DAC = 0x00040000

130

WRITE_OWNER = 0x00080000

131

SYNCHRONIZE = 0x00100000

132

133

class ACE:

134

def __init__(

135

self,

136

ace_type: ACEType,

137

ace_flags: int,

138

access_mask: int,

139

sid: SID,

140

object_type: Optional[str] = None,

141

inherited_object_type: Optional[str] = None

142

) -> None:

143

"""

144

Initialize Access Control Entry.

145

146

Parameters:

147

- ace_type: Type of ACE (allow, deny, audit, etc.)

148

- ace_flags: ACE flags (inheritance, audit success/failure)

149

- access_mask: Access rights mask

150

- sid: Security identifier for the principal

151

- object_type: GUID for object-specific ACE (optional)

152

- inherited_object_type: GUID for inheritance (optional)

153

"""

154

155

@classmethod

156

def from_binary(cls, data: bytes) -> "ACE":

157

"""

158

Create ACE from binary data.

159

160

Parameters:

161

- data: Binary ACE data

162

163

Returns:

164

ACE object

165

"""

166

167

def to_binary(self) -> bytes:

168

"""

169

Convert ACE to binary format.

170

171

Returns:

172

Binary representation of ACE

173

"""

174

175

@property

176

def ace_type(self) -> ACEType:

177

"""Type of access control entry."""

178

179

@property

180

def ace_flags(self) -> int:

181

"""ACE flags bitfield."""

182

183

@property

184

def access_mask(self) -> int:

185

"""Access rights mask."""

186

187

@property

188

def sid(self) -> SID:

189

"""Security identifier."""

190

191

@property

192

def object_type(self) -> Optional[str]:

193

"""Object type GUID for object-specific ACEs."""

194

195

@property

196

def inherited_object_type(self) -> Optional[str]:

197

"""Inherited object type GUID."""

198

199

@property

200

def short_name(self) -> str:

201

"""Short name for ACE type (for SDDL format)."""

202

203

@property

204

def is_object_type(self) -> bool:

205

"""Whether this ACE type supports object-specific permissions."""

206

207

class ACL:

208

def __init__(self, revision: ACLRevision = ACLRevision.ACL_REVISION) -> None:

209

"""

210

Initialize Access Control List.

211

212

Parameters:

213

- revision: ACL revision (ACL_REVISION or ACL_REVISION_DS)

214

"""

215

216

@classmethod

217

def from_binary(cls, data: bytes) -> "ACL":

218

"""

219

Create ACL from binary data.

220

221

Parameters:

222

- data: Binary ACL data

223

224

Returns:

225

ACL object

226

"""

227

228

def to_binary(self) -> bytes:

229

"""

230

Convert ACL to binary format.

231

232

Returns:

233

Binary representation of ACL

234

"""

235

236

def add_ace(self, ace: ACE, index: Optional[int] = None) -> None:

237

"""

238

Add ACE to the ACL.

239

240

Parameters:

241

- ace: ACE object to add

242

- index: Position to insert ACE (None to append)

243

"""

244

245

def remove_ace(self, index: int) -> None:

246

"""

247

Remove ACE from ACL by index.

248

249

Parameters:

250

- index: Index of ACE to remove

251

"""

252

253

def __len__(self) -> int:

254

"""Number of ACEs in the ACL."""

255

256

def __getitem__(self, index: int) -> ACE:

257

"""Get ACE by index."""

258

259

def __iter__(self):

260

"""Iterate over ACEs."""

261

262

@property

263

def revision(self) -> ACLRevision:

264

"""ACL revision."""

265

266

@property

267

def aces(self) -> List[ACE]:

268

"""List of Access Control Entries."""

269

```

270

271

### Security Descriptors

272

273

Windows Security Descriptor handling with support for owner, group, DACL, and SACL components.

274

275

```python { .api }

276

from bonsai.active_directory import SecurityDescriptor

277

278

class SecurityDescriptor:

279

def __init__(

280

self,

281

control: Dict[str, bool],

282

owner_sid: Optional[SID],

283

group_sid: Optional[SID],

284

sacl: Optional[ACL],

285

dacl: Optional[ACL],

286

revision: int = 1,

287

sbz1: int = 0,

288

) -> None:

289

"""

290

Initialize Security Descriptor.

291

292

Parameters:

293

- control: Control flags dictionary

294

- owner_sid: Owner security identifier

295

- group_sid: Group security identifier

296

- sacl: System Access Control List (for auditing)

297

- dacl: Discretionary Access Control List (for access control)

298

- revision: Security descriptor revision

299

- sbz1: Reserved field

300

"""

301

302

@classmethod

303

def from_binary(cls, data: bytes) -> "SecurityDescriptor":

304

"""

305

Create SecurityDescriptor from binary data.

306

307

Parameters:

308

- data: Binary security descriptor data in little-endian format

309

310

Returns:

311

SecurityDescriptor object

312

"""

313

314

def to_binary(self) -> bytes:

315

"""

316

Convert SecurityDescriptor to binary format.

317

318

Returns:

319

Binary representation in little-endian byte order

320

"""

321

322

def set_control(self, control: Union[Dict[str, bool], int]) -> None:

323

"""

324

Set control flags for the security descriptor.

325

326

Parameters:

327

- control: Control flags as dictionary or integer bitmask

328

"""

329

330

def set_owner_sid(self, owner_sid: SID) -> None:

331

"""Set owner SID."""

332

333

def set_group_sid(self, group_sid: SID) -> None:

334

"""Set group SID."""

335

336

def set_dacl(self, dacl: Optional[ACL]) -> None:

337

"""Set discretionary access control list."""

338

339

def set_sacl(self, sacl: Optional[ACL]) -> None:

340

"""Set system access control list."""

341

342

@property

343

def revision(self) -> int:

344

"""Security descriptor revision."""

345

346

@property

347

def control(self) -> Dict[str, bool]:

348

"""Control flags dictionary with keys like:

349

- owner_defaulted, group_defaulted

350

- dacl_present, dacl_defaulted, dacl_protected

351

- sacl_present, sacl_defaulted, sacl_protected

352

- self_relative, dacl_auto_inherited, sacl_auto_inherited

353

"""

354

355

@property

356

def owner_sid(self) -> Optional[SID]:

357

"""Owner security identifier."""

358

359

@property

360

def group_sid(self) -> Optional[SID]:

361

"""Group security identifier."""

362

363

@property

364

def dacl(self) -> Optional[ACL]:

365

"""Discretionary access control list."""

366

367

@property

368

def sacl(self) -> Optional[ACL]:

369

"""System access control list."""

370

```

371

372

### User Account Control

373

374

Parse and manipulate Windows User Account Control flags for user accounts.

375

376

```python { .api }

377

from bonsai.active_directory import UserAccountControl

378

379

class UserAccountControl:

380

def __init__(self, flags: int) -> None:

381

"""

382

Initialize User Account Control parser.

383

384

Parameters:

385

- flags: Integer value of userAccountControl attribute

386

"""

387

388

@property

389

def properties(self) -> Dict[str, bool]:

390

"""

391

Dictionary of user account properties including:

392

- script: Login script execution

393

- accountdisable: Account is disabled

394

- homedir_required: Home directory required

395

- lockout: Account is locked out

396

- passwd_notreqd: Password not required

397

- passwd_cant_change: User cannot change password

398

- encrypted_text_pwd_allowed: Encrypted text password allowed

399

- temp_duplicate_account: Temporary duplicate account

400

- normal_account: Normal user account

401

- interdomain_trust_account: Interdomain trust account

402

- workstation_trust_account: Workstation trust account

403

- server_trust_account: Server trust account

404

- dont_expire_password: Password doesn't expire

405

- mns_logon_account: MNS logon account

406

- smartcard_required: Smart card required for logon

407

- trusted_for_delegation: Account trusted for delegation

408

- not_delegated: Account cannot be delegated

409

- use_des_key_only: Use DES keys only

410

- dont_req_preauth: Kerberos pre-authentication not required

411

- password_expired: Password expired

412

- trusted_to_auth_for_delegation: Trusted to authenticate for delegation

413

- partial_secrets_account: Partial secrets account (RODC)

414

"""

415

416

@property

417

def value(self) -> int:

418

"""Integer value computed from properties."""

419

420

# Convenience properties for common flags

421

@property

422

def account_disabled(self) -> bool:

423

"""Account is disabled."""

424

425

@property

426

def password_not_required(self) -> bool:

427

"""Password not required."""

428

429

@property

430

def password_cant_change(self) -> bool:

431

"""User cannot change password."""

432

433

@property

434

def password_expired(self) -> bool:

435

"""Password has expired."""

436

437

@property

438

def account_locked_out(self) -> bool:

439

"""Account is locked out."""

440

441

@property

442

def normal_account(self) -> bool:

443

"""Normal user account."""

444

445

@property

446

def dont_expire_password(self) -> bool:

447

"""Password never expires."""

448

449

@property

450

def smartcard_required(self) -> bool:

451

"""Smart card required for interactive logon."""

452

```

453

454

## Usage Examples

455

456

### Working with Security Identifiers

457

458

```python

459

from bonsai.active_directory import SID

460

461

# Create SID from string representation

462

user_sid = SID(str_rep="S-1-5-21-1234567890-987654321-1111111111-1001")

463

print(f"User SID: {user_sid}")

464

print(f"Domain SID: {user_sid.get_account_domain_sid()}")

465

print(f"RID: {user_sid.get_rid()}")

466

467

# Create SID from binary data (from LDAP objectSid attribute)

468

binary_sid = entry['objectSid'][0] # Raw bytes from LDAP

469

sid_obj = SID(bytes_le=binary_sid)

470

print(f"SID from binary: {sid_obj}")

471

472

# Compare SIDs

473

admin_sid = SID(str_rep="S-1-5-32-544") # Built-in Administrators

474

if admin_sid.is_well_known():

475

print("This is a well-known SID")

476

```

477

478

### Parsing Security Descriptors

479

480

```python

481

from bonsai.active_directory import SecurityDescriptor, SID

482

from bonsai import LDAPClient

483

484

# Get security descriptor from Active Directory

485

client = LDAPClient("ldap://dc.example.com")

486

client.set_credentials("SIMPLE", user="admin@example.com", password="secret")

487

488

with client.connect() as conn:

489

# Search for user and get ntSecurityDescriptor

490

results = conn.search(

491

"cn=Users,dc=example,dc=com",

492

2,

493

"(sAMAccountName=jdoe)",

494

["ntSecurityDescriptor"]

495

)

496

497

if results:

498

# Parse security descriptor from binary attribute

499

sd_binary = results[0]['ntSecurityDescriptor'][0]

500

security_desc = SecurityDescriptor.from_binary(sd_binary)

501

502

print(f"Owner: {security_desc.owner_sid}")

503

print(f"Group: {security_desc.group_sid}")

504

print(f"Control flags: {security_desc.control}")

505

506

# Examine DACL

507

if security_desc.dacl:

508

print(f"DACL has {len(security_desc.dacl)} ACEs:")

509

for i, ace in enumerate(security_desc.dacl):

510

print(f" ACE {i}: {ace.ace_type.name} for {ace.sid}")

511

print(f" Access mask: 0x{ace.access_mask:08x}")

512

print(f" Flags: 0x{ace.ace_flags:02x}")

513

```

514

515

### Creating Access Control Lists

516

517

```python

518

from bonsai.active_directory import ACL, ACE, ACEType, ACERight, SID

519

520

# Create new DACL

521

dacl = ACL()

522

523

# Add Allow ACE for user

524

user_sid = SID(str_rep="S-1-5-21-1234567890-987654321-1111111111-1001")

525

allow_ace = ACE(

526

ace_type=ACEType.ACCESS_ALLOWED,

527

ace_flags=0,

528

access_mask=ACERight.GENERIC_READ | ACERight.GENERIC_WRITE,

529

sid=user_sid

530

)

531

dacl.add_ace(allow_ace)

532

533

# Add Deny ACE for guest account

534

guest_sid = SID(str_rep="S-1-5-21-1234567890-987654321-1111111111-501")

535

deny_ace = ACE(

536

ace_type=ACEType.ACCESS_DENIED,

537

ace_flags=0,

538

access_mask=ACERight.GENERIC_ALL,

539

sid=guest_sid

540

)

541

dacl.add_ace(deny_ace, index=0) # Insert at beginning for precedence

542

543

print(f"DACL has {len(dacl)} ACEs")

544

for i, ace in enumerate(dacl):

545

print(f"ACE {i}: {ace.ace_type.name} for {ace.sid}")

546

```

547

548

### Analyzing User Account Control

549

550

```python

551

from bonsai.active_directory import UserAccountControl

552

553

# Parse userAccountControl from Active Directory

554

with client.connect() as conn:

555

results = conn.search(

556

"cn=Users,dc=example,dc=com",

557

2,

558

"(sAMAccountName=jdoe)",

559

["userAccountControl"]

560

)

561

562

if results:

563

uac_value = int(results[0]['userAccountControl'][0])

564

uac = UserAccountControl(uac_value)

565

566

print(f"Account Control Value: {uac.value}")

567

print(f"Account Disabled: {uac.account_disabled}")

568

print(f"Password Required: {not uac.password_not_required}")

569

print(f"Password Expires: {not uac.dont_expire_password}")

570

print(f"Smart Card Required: {uac.smartcard_required}")

571

print(f"Account Locked: {uac.account_locked_out}")

572

573

# Check all properties

574

for prop, value in uac.properties.items():

575

if value:

576

print(f" {prop}: {value}")

577

```

578

579

### Modifying Security Descriptors

580

581

```python

582

from bonsai.active_directory import SecurityDescriptor, ACL, ACE, ACEType, ACERight, SID

583

584

# Create new security descriptor

585

control_flags = {

586

"dacl_present": True,

587

"dacl_defaulted": False,

588

"self_relative": True,

589

"dacl_protected": False

590

}

591

592

owner_sid = SID(str_rep="S-1-5-21-1234567890-987654321-1111111111-1001")

593

group_sid = SID(str_rep="S-1-5-21-1234567890-987654321-1111111111-513")

594

595

# Create DACL with full control for owner

596

dacl = ACL()

597

full_control_ace = ACE(

598

ace_type=ACEType.ACCESS_ALLOWED,

599

ace_flags=0,

600

access_mask=ACERight.GENERIC_ALL,

601

sid=owner_sid

602

)

603

dacl.add_ace(full_control_ace)

604

605

# Create security descriptor

606

security_desc = SecurityDescriptor(

607

control=control_flags,

608

owner_sid=owner_sid,

609

group_sid=group_sid,

610

dacl=dacl,

611

sacl=None

612

)

613

614

# Convert to binary for storing back to LDAP

615

sd_binary = security_desc.to_binary()

616

617

# Update LDAP entry (requires appropriate permissions)

618

with client.connect() as conn:

619

entry = conn.search("cn=test-object,dc=example,dc=com", 0)[0]

620

entry['ntSecurityDescriptor'] = sd_binary

621

conn.modify(entry)

622

```