Skip to content

Business rules

clsADUserServices enforces 34 business rules, all of service origin — none is inherited from a base class or restated from an embedded or dependency library, because the repository contains no <ProjectReference> and no embedded library. The rules span the network-origin authorization gate (BR-01–BR-05), domain and container selection (BR-07–BR-09), the shape of each of the 19 published operations (BR-10–BR-32), and the shared directory-lookup failure behavior (BR-33–BR-34). Statements, triggers, enforcement points and outcomes below are taken verbatim from the analysis catalog.

Rule ID Title Reached from
BR-01 Mutating and password operations require an approved caller 8 gated operations
BR-02 A local request is always approved CheckAccess
BR-03 An allow-listed source address is approved CheckAccess
BR-04 The caller's source address prefers the forwarded-for header CheckAccess
BR-05 A denied request is reported as a normal response 8 gated operations
BR-06 Read-only directory queries are not access-controlled 11 ungated operations
BR-07 The directory domain is selected by the caller every operation except HelloWorld/CheckAccess
BR-08 Mutating operations always target the external domain 8 gated operations
BR-09 A new account is created in the CN=Users container AddUser
BR-10 An empty password is replaced with a hard-coded default AddUser
BR-11 A new account is provisioned enabled with fixed identity fields AddUser
BR-12 An existing account is not re-created AddUser
BR-13 An optional attribute is written only when supplied AddUser, ChangeUserAccount
BR-14 An unlock request for an unknown user is reported as such UnlockAccount
BR-15 An account is unlocked only when locked and the result is re-verified UnlockAccount
BR-16 A group-membership addition requires both user and group to exist AddUserToGroup
BR-17 Adding an already-a-member user is a distinct outcome AddUserToGroup
BR-18 Deleting a non-existent user is a successful no-op DeleteUser
BR-19 Group removal drops to the underlying DirectoryEntry DeleteUserFromGroup
BR-20 A failed group removal is reported as success DeleteUserFromGroup
BR-21 A user exists when a principal resolves DoesUserExist
BR-22 A user has a resource by exact transitive group-name match DoesUserHaveResource
BR-23 An account change updates only non-empty fields ChangeUserAccount
BR-24 Directory attribute changes commit only when modified is raised ChangeUserAccount
BR-25 A password change is applied via SetPassword and committed ChangeUserPassword, ChangeUserPasswordOrGetException
BR-26 Two password-change variants differ only in error strategy ChangeUserPassword, ChangeUserPasswordOrGetException
BR-27 A directory attribute query returns the value or UserDoesNotExist GetUserEmail, GetUserFAX, GetUserPrinter
BR-28 The group list includes nested membership GetUserGroups
BR-29 A resource is a Resource-named group filtered by caller-supplied environment GetUserResources
BR-30 Last logon is derived from the FILETIME attribute GetUserLastLogon
BR-31 The printer attribute is read from the internal domain only GetUserPrinter
BR-32 Group membership for IsUserInGroup is direct only IsUserInGroup
BR-33 A group is resolved by SamAccountName, taking the first match AddUserToGroup, DeleteUserFromGroup, DoesUserHaveResource, IsUserInGroup
BR-34 A directory lookup failure is reported as not found every operation except HelloWorld/CheckAccess

BR-01 Mutating and password operations require an approved caller

Field Value
Statement A mutating or password-changing operation may only proceed for an approved caller
Trigger Entry to any of the eight gated operations
Enforcement If (Not CheckAccess().HasAccess) Then Return AccessDenied() — src\ADUserServices\clsADUserServices.asmx.vb lines 105, 202, 234, 273, 302, 395, 474, 495
Origin service
Reached from AddUser, UnlockAccount, AddUserToGroup, DeleteUser, DeleteUserFromGroup, ChangeUserAccount, ChangeUserPassword, ChangeUserPasswordOrGetException
Outcome On denial, an ADServiceResponse with Response = Failure, Message = "Access Denied", returned as a normal (non-fault) SOAP response
Source src\ADUserServices\clsADUserServices.asmx.vb:105,202,234,273,302,395,474,495

BR-02 A local request is always approved

Field Value
Statement A request originating on the server itself is approved
Trigger CheckAccess evaluation
Enforcement If (HttpContext.Current.Request.IsLocal) — lines 762–765
Origin service
Reached from CheckAccess and all eight gated operations
Outcome HasAccess = True, Local = True in HasAccessResponse
Source src\ADUserServices\clsADUserServices.asmx.vb:762-765

BR-03 An allow-listed source address is approved

Field Value
Statement A request whose observed source address exactly matches an entry in the configured allow-list is approved
Trigger CheckAccess evaluation
Enforcement For Each ip In GetAllowedIPs(), comparing each entry to GetIncomingIp(); on a match, sets response.HasAccess = True and response.EnvironmentApi = True — lines 767–772
Origin service
Reached from CheckAccess and all eight gated operations
Outcome HasAccess = True, EnvironmentApi = True
Source src\ADUserServices\clsADUserServices.asmx.vb:767-772

BR-04 The caller's source address prefers the forwarded-for header

Field Value
Statement The caller's source address is taken from HTTP_X_FORWARDED_FOR, falling back to REMOTE_ADDR only when that header is empty
Trigger Every CheckAccess evaluation
Enforcement GetIncomingIp — lines 798–806
Origin service
Reached from CheckAccess and all eight gated operations
Outcome The chosen value is compared in BR-03 and returned in HasAccessResponse.RemoteAddress
Source src\ADUserServices\clsADUserServices.asmx.vb:798-806

BR-05 A denied request is reported as a normal response

Field Value
Statement A denied request is reported as a normal response carrying a failure code, never as a SOAP fault
Trigger Denial under BR-01
Enforcement AccessDenied — lines 781–789
Origin service
Reached from the eight gated operations
Outcome ADServiceResponse{Response=Failure, Message="Access Denied"}
Source src\ADUserServices\clsADUserServices.asmx.vb:781-789

BR-06 Read-only directory queries are not access-controlled

Field Value
Statement Read-only directory queries are not access-controlled
Trigger Entry to any of the eleven ungated operations
Enforcement Absence of any check in the method bodies
Origin service
Reached from HelloWorld, DoesUserExist, DoesUserHaveResource, GetUserEmail, GetUserGroups, GetUserResources, GetUserFAX, GetUserLastLogon, GetUserPrinter, IsUserInGroup, CheckAccess
Outcome The operation executes for any caller that can reach the endpoint
Source src\ADUserServices\clsADUserServices.asmx.vb

BR-07 The directory domain is selected by the caller

Field Value
Statement The directory domain is selected by the caller's inExternalDomain flag: external.saif.com when true, corp.saif.com when false
Trigger Every directory context creation
Enforcement GetContext — lines 823–827, using constants DOMAIN_EXT/DOMAIN_INT (lines 47–48)
Origin service
Reached from every operation except HelloWorld and CheckAccess
Outcome Determines which forest answers the query; invisible in the response
Source src\ADUserServices\clsADUserServices.asmx.vb:823-827

BR-08 Mutating operations always target the external domain

Field Value
Statement Every mutating operation targets the external domain unconditionally; the internal domain is never written
Trigger Entry to any mutating operation
Enforcement Literal True passed to GetContext/GetUserPrincipal/GetGroupPrincipal, with the repeated authored comment 'Assume external domain - NetSec will NOT use this
Origin service
Reached from the eight gated operations
Outcome Internal-domain accounts cannot be created, modified, deleted, unlocked, re-passworded or re-grouped through this service
Source src\ADUserServices\clsADUserServices.asmx.vb

BR-09 A new account is created in the CN=Users container

Field Value
Statement A new account is created in the CN=Users container of the external domain
Trigger AddUser, after the existence test
Enforcement Dim myContainer As String = "CN=Users," & CONTAINER_EXT (line 109) passed to GetContext (line 133)
Origin service
Reached from AddUser
Outcome The account's distinguished name is fixed to that container; no caller input can change it
Source src\ADUserServices\clsADUserServices.asmx.vb:109,133

BR-10 An empty password is replaced with a hard-coded default

Field Value
Statement When the caller supplies an empty password, a hard-coded default password is used instead
Trigger AddUser, before principal population
Enforcement If inPassword = String.Empty Then inPassword = <literal> (within lines 118–122)
Origin service
Reached from AddUser
Outcome The account is created with a known shared credential (SEC-05, CRITICAL). The literal is not reproduced in documentation
Source src\ADUserServices\clsADUserServices.asmx.vb:118-122

BR-11 A new account is provisioned enabled with fixed identity fields

Field Value
Statement A new account is provisioned enabled, with DisplayName and SamAccountName both set to the supplied user id, and ScriptPath set to a single space
Trigger AddUser, principal population
Enforcement With userPrin, setting .GivenName, .Surname, .DisplayName = inUserId, .Name, .SamAccountName = inUserId, .SetPassword(inPassword), .ScriptPath = " ", .Description, and .Enabled = True (lines 136–152)
Origin service
Reached from AddUser
Outcome The created account is immediately usable; Name is taken from the separate inName parameter
Source src\ADUserServices\clsADUserServices.asmx.vb:136-152

BR-12 An existing account is not re-created

Field Value
Statement An account that already exists is not re-created
Trigger AddUser, existence test
Enforcement userPrin = GetUserPrincipal(inUserId, True) then If Not userPrin Is Nothing (lines 127–131)
Origin service
Reached from AddUser
Outcome Response = Failure with an empty Message. The UserExists code is assigned and then immediately overwritten, so it never reaches the wire (see Tech debt)
Source src\ADUserServices\clsADUserServices.asmx.vb:127-131

BR-13 An optional attribute is written only when supplied

Field Value
Statement An optional directory attribute is written only when the corresponding input is non-empty
Trigger AddUser and ChangeUserAccount, attribute stage
Enforcement If inX <> String.Empty Then de.Properties("<attribute>").Value = inX, applied per attribute (company, telephoneNumber, mail, info, title, facsimileTelephoneNumber, division) — AddUser lines 158–179; ChangeUserAccount lines 435–457
Origin service
Reached from AddUser, ChangeUserAccount
Outcome An empty input leaves the existing attribute untouched; there is no way to clear an attribute through this service
Source src\ADUserServices\clsADUserServices.asmx.vb:158-179,435-457

BR-14 An unlock request for an unknown user is reported as such

Field Value
Statement An unlock request for an unknown user is reported as such
Trigger UnlockAccount
Enforcement If (userPrin Is Nothing) — lines 206–208
Origin service
Reached from UnlockAccount
Outcome Response = UserDoesNotExist, Message naming the user id
Source src\ADUserServices\clsADUserServices.asmx.vb:206-208

BR-15 An account is unlocked only when locked and the result is re-verified

Field Value
Statement An account is unlocked only when it is actually locked out, and the result is re-verified before reporting
Trigger UnlockAccount
Enforcement If (userPrin.IsAccountLockedOut()) calls userPrin.UnlockAccount(), then re-checks with IIf(userPrin.IsAccountLockedOut(), "The account was not unlocked", "The account was unlocked!") — lines 210–217
Origin service
Reached from UnlockAccount
Outcome Response = Success in every branch; the distinction ("was unlocked" / "was not unlocked" / "was not locked") is carried only in Message free text
Source src\ADUserServices\clsADUserServices.asmx.vb:210-217

BR-16 A group-membership addition requires both user and group to exist

Field Value
Statement A group-membership addition requires both the user and the group to exist
Trigger AddUserToGroup
Enforcement Lines 241–250
Origin service
Reached from AddUserToGroup
Outcome Response = UserDoesNotExist or GroupDoesNotExist; no membership change is attempted
Source src\ADUserServices\clsADUserServices.asmx.vb:241-250

BR-17 Adding an already-a-member user is a distinct outcome

Field Value
Statement An attempt to add a user who is already a member is reported as a distinct outcome rather than a failure
Trigger AddUserToGroup, exception path
Enforcement If ex.Message.Contains("already exists") — lines 256–258
Origin service
Reached from AddUserToGroup
Outcome Response = UserAlreadyInGroup. Detection depends on an English substring of the directory exception message*
Source src\ADUserServices\clsADUserServices.asmx.vb:256-258

BR-18 Deleting a non-existent user is a successful no-op

Field Value
Statement Deleting a user who does not exist is a successful no-op
Trigger DeleteUser
Enforcement If userPrin IsNot Nothing Then wraps userPrin.Delete() in a Try/Catch, with no Else branch — lines 283–293
Origin service
Reached from DeleteUser
Outcome Response = Success, empty Message
Source src\ADUserServices\clsADUserServices.asmx.vb:283-293

BR-19 Group removal drops to the underlying DirectoryEntry

Field Value
Statement Group removal is performed against the underlying DirectoryEntry rather than the GroupPrincipal, because the managed removal fails on large groups
Trigger DeleteUserFromGroup, removal stage
Enforcement deGroup.Invoke("Remove", New Object() {deUser.Path}) + deGroup.CommitChanges() — lines 326–330, with the authored comment at line 325
Origin service
Reached from DeleteUserFromGroup
Outcome The member is removed by distinguished-name path; late-bound invocation (see Tech debt)
Source src\ADUserServices\clsADUserServices.asmx.vb:325-330

BR-20 A failed group removal is reported as success

Field Value
Statement A failure to remove a user from a group is treated as "the user was probably not in the group" and reported as success
Trigger DeleteUserFromGroup, exception path
Enforcement Empty Catch with the authored comment 'gobble - user is pribably not in the group — lines 331–333
Origin service
Reached from DeleteUserFromGroup
Outcome Response = Success. A genuine directory failure is indistinguishable from a completed removal (see Tech debt — TD-20)
Source src\ADUserServices\clsADUserServices.asmx.vb:331-333

BR-21 A user exists when a principal resolves

Field Value
Statement A user "exists" when a principal can be resolved for the supplied id in the selected domain
Trigger DoesUserExist
Enforcement Lines 347–352
Origin service
Reached from DoesUserExist
Outcome True or False. A directory failure also yields False (see Tech debt — TD-22)
Source src\ADUserServices\clsADUserServices.asmx.vb:347-352

BR-22 A user has a resource by exact transitive group-name match

Field Value
Statement A user "has" a resource when one of the user's authorization groups has a name exactly equal to the requested resource name
Trigger DoesUserHaveResource
Enforcement For Each grp As GroupPrincipal In userPrin.GetAuthorizationGroups, checking If grp.Name = inResourceName — lines 372–377
Origin service
Reached from DoesUserHaveResource
Outcome Message = the literal "True" or "False"; Response = UserDoesNotExist when no principal resolves. Matching is on Name, not SamAccountName, and is exact
Source src\ADUserServices\clsADUserServices.asmx.vb:372-377

BR-23 An account change updates only non-empty fields

Field Value
Statement An account change updates only the fields whose inputs are non-empty, and principal-level fields are saved before directory-level attributes
Trigger ChangeUserAccount
Enforcement Lines 408–433 (GivenName, Surname, Description, then Save() when modified)
Origin service
Reached from ChangeUserAccount
Outcome Two separate directory writes per request; a partial failure can leave the two halves inconsistent*
Source src\ADUserServices\clsADUserServices.asmx.vb:408-433

BR-24 Directory attribute changes commit only when modified is raised

Field Value
Statement Directory-level attribute changes are committed only when the modified flag was raised
Trigger ChangeUserAccount, commit stage
Enforcement If modified Then de.CommitChanges() — lines 458–460
Origin service
Reached from ChangeUserAccount
Outcome Changes to company and info alone never set the flag, so such a request returns Success having committed nothing (see Tech debt — TD-19)
Source src\ADUserServices\clsADUserServices.asmx.vb:458-460

BR-25 A password change is applied via SetPassword and committed

Field Value
Statement A password change is applied by invoking SetPassword on the user's underlying DirectoryEntry and committing
Trigger ChangeUserPassword, ChangeUserPasswordOrGetException
Enforcement ChangePassword — de.Invoke("SetPassword", oPassword) + de.CommitChanges(), lines 514–517
Origin service
Reached from ChangeUserPassword, ChangeUserPasswordOrGetException
Outcome On success Response = Success; an unknown user yields UserDoesNotExist before any write
Source src\ADUserServices\clsADUserServices.asmx.vb:514-517

BR-26 Two password-change variants differ only in error strategy

Field Value
Statement Two published variants of the password change exist and differ only in error strategy: one absorbs exceptions into a failure code, the other lets them fault
Trigger Caller's choice of operation
Enforcement ChangeUserPassword wraps the call in Try/Catch (lines 478–483) versus ChangeUserPasswordOrGetException with no handler (lines 493–501)
Origin service
Reached from ChangeUserPassword, ChangeUserPasswordOrGetException
Outcome Failure + exception text, versus a framework SOAP fault carrying the exception
Source src\ADUserServices\clsADUserServices.asmx.vb:478-483,493-501

BR-27 A directory attribute query returns the value or UserDoesNotExist

Field Value
Statement A directory attribute query returns the attribute's value in Message, and UserDoesNotExist when the user cannot be resolved
Trigger GetUserEmail, GetUserFAX, GetUserPrinter
Enforcement myStruc.Message = de.Properties.Item("<attr>").Value — lines 546 (mail), 639 (facsimileTelephoneNumber), 707 (extensionAttribute2)
Origin service
Reached from GetUserEmail, GetUserFAX, GetUserPrinter
Outcome The value is returned as a string in Message with Response = Success; an unset attribute surfaces as Failure + a .NET exception message*
Source src\ADUserServices\clsADUserServices.asmx.vb:546,639,707

BR-28 The group list includes nested membership

Field Value
Statement The group list for a user is the names of all of the user's authorization groups, including nested membership
Trigger GetUserGroups
Enforcement For Each grp As GroupPrincipal In userPrin.GetAuthorizationGroups, calling userGroups.Add(grp.Name) — lines 570–574
Origin service
Reached from GetUserGroups
Outcome An ArrayList of group-name strings, untyped on the wire (see Tech debt — TD-05); empty on any failure
Source src\ADUserServices\clsADUserServices.asmx.vb:570-574

BR-29 A resource is a Resource-named group filtered by caller-supplied environment

Field Value
Statement A "resource" is an authorization group whose name contains Resource; groups whose name also contains TestResource are returned only for non-production callers, and all other resource groups only for production callers
Trigger GetUserResources
Enforcement Lines 596–608
Origin service
Reached from GetUserResources
Outcome An ArrayList of the filtered group names. A production caller receives no TestResource group; a non-production caller receives only TestResource groups
Source src\ADUserServices\clsADUserServices.asmx.vb:596-608

BR-30 Last logon is derived from the FILETIME attribute

Field Value
Statement Last-logon is derived from the lastLogonTimestamp FILETIME and rendered as a date-time string; a value below 1 means the information is unavailable
Trigger GetUserLastLogon
Enforcement Lines 664–678 — HighPart * 4294967296 + lowpart, < 1 test, division by 60 * 10000000, DateAdd(DateInterval.Minute, lastLogon, #1/1/1601#)
Origin service
Reached from GetUserLastLogon
Outcome Message = the rendered timestamp with Response = Success, or Response = InfoNotAvailable. Rendering uses the server's culture and time zone*
Source src\ADUserServices\clsADUserServices.asmx.vb:664-678

BR-31 The printer attribute is read from the internal domain only

Field Value
Statement The user's printer is read from extensionAttribute2 in the internal domain only
Trigger GetUserPrinter
Enforcement GetUserPrincipal(inUserId, False) (line 700) with the authored comment 'Assume internal domain
Origin service
Reached from GetUserPrinter
Outcome External-domain users are always reported as UserDoesNotExist; the caller cannot select a domain
Source src\ADUserServices\clsADUserServices.asmx.vb:700

BR-32 Group membership for IsUserInGroup is direct only

Field Value
Statement Group membership for IsUserInGroup is direct membership of the resolved group, not transitive authorization-group membership
Trigger IsUserInGroup
Enforcement If groupPrin.Members.Contains(userPrin) — lines 733–737
Origin service
Reached from IsUserInGroup
Outcome Message = the literal "True"/"False". A user who is a member only through a nested group answers "False", which differs from BR-22/BR-28 on the same underlying question
Source src\ADUserServices\clsADUserServices.asmx.vb:733-737

BR-33 A group is resolved by SamAccountName, taking the first match

Field Value
Statement A group is resolved by matching SamAccountName through a PrincipalSearcher, taking the first match
Trigger Any group lookup
Enforcement GetGroupPrincipal — lines 880–886
Origin service
Reached from AddUserToGroup, DeleteUserFromGroup, DoesUserHaveResource, IsUserInGroup
Outcome An unresolved group yields GroupDoesNotExist in the calling operation. FindOne makes the result non-deterministic if more than one group matches*
Source src\ADUserServices\clsADUserServices.asmx.vb:880-886

BR-34 A directory lookup failure is reported as not found

Field Value
Statement A directory lookup failure is captured into an internal field and reported to the caller as "not found"
Trigger Any exception inside GetContext, GetUserPrincipal or GetGroupPrincipal
Enforcement m_LastMessage = ex.Message + Return Nothing — lines 836, 850, 855, 888
Origin service
Reached from every operation except HelloWorld and CheckAccess
Outcome The caller receives UserDoesNotExist, GroupDoesNotExist, False, or an empty list — never the failure detail, which is written to a field no operation reads (see Tech debt — TD-07, TD-22)
Source src\ADUserServices\clsADUserServices.asmx.vb:836,850,855,888

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts. Statements marked with ** could not be confirmed; treat as unknown until verified.

Sources

  • src\ADUserServices\clsADUserServices.asmx.vb
  • .saifdocgen\state\runs\20260922-101326-aduserservices\analysis\aduserservices-analysis.md