clsADUserServices declares Windows authentication in Web.config but ships no <authorization> element, and the release pipeline enables IIS anonymous authentication for both deployment variants — so the effective barrier in front of the service is not the Windows identity but a single application-layer, programmatic, machine-level (network-origin) IP allow-list. That allow-list gates 8 of the 19 published operations; the remaining 11, including several that disclose Active Directory attribute and group data, carry no application-layer check at all.
Windows/NTLM declared via <authentication mode="Windows"/>, but IIS anonymous authentication is enabled for both deployment variants by the release pipeline
Established by
Web.config declaration; effective enforcement owned by IIS site configuration and the external deploy template
<authentication> element
mode="Windows"
Identity type
Not determined from source — no User.Identity, WindowsIdentity, or IsInRole usage exists anywhere in the codebehind
Session state
Not used — every <WebMethod()> is declared bare, so EnableSession defaults to False on all 19 operations
Anonymous access
Allowed — IISAnonymousAuthInternal: true and IISAnonymousAuthExternal: true in pipelines\release.yml for both variants
Web.config declares <authentication mode="Windows"/> with no <authorization>, <identity impersonate>, or <location> element anywhere in the file. The release pipeline enables anonymous authentication for both the internal and external deployment variants, and never supplies IISWindowsAuthInternal/IISWindowsAuthExternal to the external deploy template, so the effective IIS Windows-authentication state is not determinable from this repository.** In combination, no caller is required to present a Windows identity to reach any operation; the only application-layer control is the IP allow-list documented below. AddUser.inPassword, ChangeUserPassword.inNewPassword, and ChangeUserPasswordOrGetException.inNewPassword accept a plaintext account password as a SOAP string parameter; these travel in the request body and their protection depends entirely on whatever transport binding fronts the deployed endpoint, which is not evidenced in this repository.**
Grants access to a request that is local to the server, or whose observed source address exactly matches a configured allow-list entry; applied to 8 mutating/password operations
None in code
11 read operations
—
Every reachable request invokes the operation; access relies entirely on IIS anonymous authentication being disabled by the external deploy template, or on network placement, neither of which is confirmed by this repository
This is a network-origin (IP) allow-list, not a role check, a claim check, or a SAIF role-model integration. It carries no notion of which user or calling application is acting, so no operation can be constrained to a subset of accounts, groups, or attributes.
Authenticates the service process to Active Directory for every directory read and write
Every mutating operation, executed as the app-pool identity
Azure DevOps variable group Architecture.ServiceAccounts.IIS (IISAppPoolUserNonProd/IISAppPoolPasswordNonProd, IISAppPoolUserProd/IISAppPoolPasswordProd)
The same account variables are used for non-prod and prod (SEC-07, MEDIUM for identity reuse; the externalization mechanism itself is correct). No PrincipalContext in source is constructed with explicit credentials, so every AD action is attributed to this one shared account with no requester correlation
Embedded literal in source (src\ADUserServices\clsADUserServices.asmx.vb, AddUser, the If inPassword = String.Empty Then branch, within lines 118–122)
Direct assignment when inPassword is empty
clsADUserServices.AddUser
CRITICAL (SEC-05). The account is created .Enabled = True. The literal value is not reproduced here or anywhere in this documentation set — see Tech debt
HasAccess = True, Local = True. Restated as BR-02. Any other process, site, or application co-hosted on the same server obtains the full directory-administration surface (SEC-11, MEDIUM)
Source
clsADUserServices.asmx.vb:762-765
SEC-SVC-002 An allow-listed source address is approved¶
Field
Value
Statement
A request whose observed source address exactly (case-sensitive, untrimmed) matches a configured allow-list entry is granted access
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; comparison is raw string equality with no CIDR, parse, or trim (SEC-10, MEDIUM)
HasAccess = True, EnvironmentApi = True. Restated as BR-03
Source
clsADUserServices.asmx.vb:767-772
SEC-SVC-003 The incoming-IP source prefers a spoofable header¶
Field
Value
Statement
The address compared against the allow-list is taken from the HTTP_X_FORWARDED_FOR server variable, falling back to REMOTE_ADDR only when that header is null or empty, with no trusted-proxy validation
The chosen value feeds SEC-SVC-002; a caller able to set the header can present an arbitrary source address (SEC-03, HIGH). Restated as BR-04
Source
clsADUserServices.asmx.vb:798-806
SEC-SVC-004 A denied request returns a normal response, never a fault¶
Field
Value
Statement
A caller denied by CheckAccess receives a normal SOAP response carrying a failure sentinel, not a SOAP fault
Trigger
Denial under SEC-SVC-001/SEC-SVC-002
Enforcement
AccessDenied — lines 781–789
Applies to
the eight gated operations
Outcome
ADServiceResponse{Response=Failure, Message="Access Denied"}. Restated as BR-05
Source
clsADUserServices.asmx.vb:781-789
SEC-SVC-005 CheckAccess itself is published and unauthenticated¶
Field
Value
Statement
The allow-list decision primitive is itself a published, unauthenticated operation that discloses the caller's observed remote address and whether it was accepted
Trigger
Any call to CheckAccess
Enforcement
No check precedes the body of CheckAccess — lines 752–776
HasAccessResponse{HasAccess, Local, EnvironmentApi, RemoteAddress} returned to any caller, providing an oracle for probing the allow-list (SEC-04, MEDIUM)
Mark inferred statements with * and unverifiable statements with **. Do not use bold inline confidence labels.
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.