Skip to content

Faults and error pipeline

clsADUserServices has no fault contract mechanism of its own — ASMX offers none — and no SoapException is ever constructed in source. Most operations catch a directory or password failure and return an ADServiceResponse sentinel instead of throwing; a smaller set of operations have no Try/Catch at all, so an escaping .NET exception reaches ASP.NET's default wrapping and surfaces as a SoapException. Several sentinel-returning operations catch and swallow the failure entirely, producing a response indistinguishable from success.

Fault surface summary

Aspect Finding
Fault contracts Not applicable — ASMX has no fault contract mechanism
Exception wrapping ASP.NET wraps an escaping exception in SoapException
Custom SoapException throwing None — no SoapException is constructed anywhere in clsADUserServices.asmx.vb
Global handler None — no Global.asax exists in the repository, so there is no Application_Error
Detail exposure No <customErrors> element appears in src\ADUserServices\Web.config — the effective detail-exposure mode is a framework default and is not determined from source.** <compilation debug="true"> is present in the checked-in Web.config and is removed only by the Web.Release.config transform (see tech-debt.md TD-13)
Sentinel-value failures 14 of 19 operations return a sentinel value on failure instead of throwing (see below)
Effect on callers A caller cannot rely on a uniform failure signal: some operations fault, most return ADServiceResponse{Response=Failure} with ex.Message copied into Message, and a subset swallow the failure and return Success or an empty collection indistinguishable from a legitimate empty result

Exception handling by operation

Operation Handling What the caller receives Logged
HelloWorld No failure path exists — constant return "Hello World" No
AddUser Caught and converted to a return value inside two Try blocks; unhandled outside them ADServiceResponse{Failure} | SoapException (context/New UserPrincipal/property-assignment failures) No
UnlockAccount Caught and converted — whole body in one Try/Catch ADServiceResponse{Failure, ex.Message} No
AddUserToGroup Caught and converted ADServiceResponse{UserAlreadyInGroup \| Failure, ex.Message} No
DeleteUser Caught and converted; missing-user path falls through with no exception at all ADServiceResponse{Success \| Failure, ex.Message} No
DeleteUserFromGroup Caught and swallowed — authored comment 'gobble - user is pribably not in the group ADServiceResponse{Success} — indistinguishable from a genuine removal No
DoesUserExist Caught and swallowed inside the shared GetUserPrincipal helper (H2), into m_LastMessage, never read False — indistinguishable from "user absent" No
DoesUserHaveResource Unhandled — no Try/Catch around GetAuthorizationGroups() SoapException No
ChangeUserAccount Caught and converted ADServiceResponse{UserDoesNotExist \| Failure, ex.Message} No
ChangeUserPassword Caught and converted — wraps the private ChangePassword helper (H6) ADServiceResponse{Failure, ex.Message} No
ChangeUserPasswordOrGetException Unhandled — no Try/Catch around H6; deliberately faulting by design SoapException No
GetUserEmail Caught and converted ADServiceResponse{UserDoesNotExist \| Failure, ex.Message} No
GetUserGroups Caught and swallowed — empty Catch block Empty ArrayList — indistinguishable from "no groups" No
GetUserResources Caught and swallowed — empty Catch block Empty ArrayList — indistinguishable from "no matching groups" No
GetUserFAX Caught and converted ADServiceResponse{UserDoesNotExist \| Failure, ex.Message} No
GetUserLastLogon Caught and converted ADServiceResponse{UserDoesNotExist \| InfoNotAvailable \| Failure, ex.Message} No
GetUserPrinter Caught and converted ADServiceResponse{UserDoesNotExist \| Failure, ex.Message} No
IsUserInGroup Caught and converted ADServiceResponse{UserDoesNotExist \| GroupDoesNotExist \| Failure, ex.Message} No
CheckAccess Unhandled — no Try/Catch around GetAllowedIPs() SoapException No

No operation logs on failure — see Logging on failure below. The "caught and swallowed" rows are the most consequential findings on this page: the caller receives an apparently successful (or empty) response that is indistinguishable from a genuine result. Each is raised in tech-debt.md (DeleteUserFromGroup TD-20, GetUserGroups/GetUserResources TD-21, DoesUserExist TD-22).

Error pipeline diagram

flowchart TD
    OP["Operation body raises<br>a .NET exception"] --> CATCH{"Caught in<br>the operation?"}
    CATCH -->|"No — 4 operations"| WRAP["ASP.NET wraps it<br>in SoapException"]
    CATCH -->|"Yes — 14 operations"| HANDLED{"Handling"}
    HANDLED -->|"Convert to sentinel"| SENTINEL["ADServiceResponse<br>Failure plus ex.Message"]
    HANDLED -->|"Swallow<br>DeleteUserFromGroup /<br>GetUserGroups / GetUserResources /<br>DoesUserExist"| SILENT["Response indistinguishable<br>from success or empty result"]
    WRAP --> DETAIL{"customErrors mode"}
    DETAIL -->|"Not declared in Web.config"| UNKNOWN["Detail exposure is a<br>framework default -<br>not determined from source"]
    UNKNOWN --> CALLER["Caller receives SOAP fault"]

Custom SOAP faults

No operation constructs a SoapException anywhere in clsADUserServices.asmx.vb. Every fault a caller observes on this service is framework-generated from an unhandled .NET exception escaping one of the four operations listed in Exception handling by operation that carry no Try/Catch: AddUser (outside its two guarded blocks), DoesUserHaveResource, ChangeUserPasswordOrGetException, and CheckAccess.

Sentinel-value failure conventions

Operation Sentinel Meaning Ambiguity
AddUser ADServiceResponse{Response=Failure} Access denied, existing user, or a caught save/commit failure The existing-user code path (UserExists) is set then immediately overwritten to Failure, so a duplicate-user attempt is indistinguishable from any other failure (tech-debt.md TD-17)
UnlockAccount Response=Success with free-text Message Unlocked, already-unlocked, or unlock-did-not-take-effect The three outcomes are distinguished only in free text, not in Response
DeleteUser Response=Success Deleted, or the user was never found A missing user and a successful delete are indistinguishable; a directory-lookup failure inside GetUserPrincipal is also swallowed into this same Success
DeleteUserFromGroup Response=Success Removed, was not a member, or a genuine directory failure All three collapse to Success via an explicit swallow ('gobble) (tech-debt.md TD-20, HIGH)
DoesUserExist False User absent, or directory lookup failed Both collapse to False; the exception is captured in m_LastMessage, which no caller can read (tech-debt.md TD-22)
GetUserGroups Empty ArrayList No groups, no such user, or directory failure All three collapse to an empty list via an empty Catch block (tech-debt.md TD-21, HIGH)
GetUserResources Empty ArrayList No matching resources, no such user, or directory failure Same collapse as GetUserGroups (tech-debt.md TD-21, HIGH)
ChangeUserAccount Response=Success A request that changes only inCompany/inInfo reports success but commits nothing, because those two writes never set the modified flag Indistinguishable from a genuine successful update (tech-debt.md TD-19, HIGH)
All other sentinel-returning operations (AddUserToGroup, ChangeUserPassword, GetUserEmail, GetUserFAX, GetUserLastLogon, GetUserPrinter, IsUserInGroup) ADServiceResponse{Response=Failure, Message=ex.Message} Raw .NET exception text is flattened into free text The distinction between a directory-connectivity failure and a business-rule rejection (for example a password-policy failure) is not preserved in Response, only in the free-text Message*

Logging on failure

Surface Location Written content Failure of the logger itself
None — No logging framework is initialized or invoked anywhere in clsADUserServices.asmx.vb, despite the log4net 2.0.5 package reference in packages.config (referenced, never used — tech-debt.md TD-08). No Trace.WriteLine, EventLog write, or StreamWriter exists in source Not applicable

No mutating operation writes an audit trail of any kind — see tech-debt.md SEC-09. m_LastMessage, the one field that captures directory-failure detail, is written by the three shared helpers (GetContext, GetUserPrincipal, GetGroupPrincipal) but is never read or returned by any operation (tech-debt.md TD-07).

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
  • src\ADUserServices\Web.config
  • src\ADUserServices\Web.Release.config
  • .saifdocgen\state\runs\20260922-101326-aduserservices\analysis\aduserservices-analysis.md