Skip to content

SAIF.Web.API business rules

This page captures the 20 business rules enforced by SAIF.Web.API, covering response normalization, correlation and observability, HTTP-status mapping, transport security, and delegated role-based authorization.

BR-WEBAPI-001 Server-Name header on every response

Field Value
Statement Every response emitted through the handler must carry a Server-Name header identifying the serving machine.
Trigger Any response reaching the end of BuildApiResponse.
Enforcement ApiResponseHandler.BuildApiResponse
Outcome Header Server-Name: {Environment.MachineName} added.
Operations OP-01 (step 16)
Source src/SAIF.Web.API/ApiResponseHandler.cs

BR-WEBAPI-002 Normalize non-success responses into ErrorResponse

Field Value
Statement An unsuccessful response whose content is an HttpError must be replaced by a normalized ErrorResponse body (ErrorCode, ErrorMessage, ErrorId, ErrorUrl) with the original response headers preserved.
Trigger !IsSuccessStatusCode and content is HttpError.
Enforcement ApiResponseHandler.BuildApiResponse / GetHttpResponse / GetErrorResponse
Outcome Client receives a uniform error contract instead of the raw Web API error shape.
Operations OP-01 (steps 10, 12), OP-04
Source src/SAIF.Web.API/ApiResponseHandler.cs

BR-WEBAPI-003 Client-fault exception types map to 400 Bad Request

Field Value
Statement An error whose ExceptionType name ends with InvalidDataException, ODataException, or ArgumentException is a client fault and must be reported as 400 Bad Request.
Trigger GetErrorResponse evaluation.
Enforcement ApiResponseHandler.GetErrorResponse
Outcome ErrorResponse.ErrorCode = 400.
Operations OP-01 (step 10), OP-04 (step 2)
Source src/SAIF.Web.API/ApiResponseHandler.cs

BR-WEBAPI-004 Transport status code matches mapped error code 400

Field Value
Statement When the mapped ErrorResponse.ErrorCode is 400, the transport status code must also be set to 400 so body and status agree.
Trigger After BR-WEBAPI-003 fires.
Enforcement ApiResponseHandler.BuildApiResponse
Outcome response.StatusCode = BadRequest.
Operations OP-01 (step 11)
Source src/SAIF.Web.API/ApiResponseHandler.cs

BR-WEBAPI-005 Unique correlation identifier per response

Field Value
Statement Each processed response must receive a unique, time-ordered correlation identifier composed of a local timestamp (yyyyMMddHHmmssffffzzz) and a GUID, with : removed.
Trigger Every BuildApiResponse invocation.
Enforcement ApiResponseHandler.GenerateId (protected virtual)
Outcome MessageID set; surfaced to the client as ErrorResponse.ErrorId.
Operations OP-01 (step 6), OP-02
Source src/SAIF.Web.API/ApiResponseHandler.cs

BR-WEBAPI-006 Splunk lookup URL always produced

Field Value
Statement A Splunk lookup URL for the correlation ID must always be produced; when the environment service is unavailable or the splunksearch setting is blank, hard-coded defaults are used.
Trigger Every BuildApiResponse invocation.
Enforcement ApiResponseHandler.BuildMessageURL
Outcome MessageIDUrl set; surfaced as ErrorResponse.ErrorUrl.
Operations OP-01 (step 7), OP-03
Source src/SAIF.Web.API/ApiResponseHandler.cs

BR-WEBAPI-007 Redact password material from debug traces

Field Value
Statement Password material in the logged request input stream must be redacted before the trace message is emitted.
Trigger BuildTraceMessage with debugMode = true.
Enforcement ApiResponseHandler.BuildTraceMessage
Outcome Text matching password[^,\n]* (case-insensitive) is replaced with "Password": "********". Scope limit: applied only to the input-stream copy, not to Request Content / Response Content.
Operations OP-01 (steps 13–15), OP-05 (step 5)
Source src/SAIF.Web.API/ApiResponseHandler.cs

BR-WEBAPI-008 Log every normalized error

Field Value
Statement Every normalized error must be logged through the available log service; when the service also implements ILogServiceForApi, the correlation ID is passed so the log entry can be joined to the client's ErrorId.
Trigger Error branch of BuildApiResponse, logService != null.
Enforcement ApiResponseHandler.BuildApiResponse
Outcome Exception logged with full debug trace; logged twice when the service implements both contracts.
Operations OP-01 (steps 13–14)
Source src/SAIF.Web.API/ApiResponseHandler.cs

BR-WEBAPI-009 Trace every request with debug-gated verbosity

Field Value
Statement Every request (successful or not) must produce a trace entry when ILogService is resolved (no trace is emitted otherwise); the verbose payload (bodies, headers, input stream) is included only when the log service threshold is at debug.
Trigger End of BuildApiResponse, logService != null.
Enforcement ApiResponseHandler.BuildApiResponse / BuildTraceMessage
Outcome ILogService.TraceInfo invoked with a message sized by logService.IsThresholdAtDebug.
Operations OP-01 (step 15), OP-05
Source src/SAIF.Web.API/ApiResponseHandler.cs

BR-WEBAPI-010 Developer guidance text when stack trace is unavailable

Field Value
Statement When an error carries no stack trace and IncludeErrorDetailPolicy is not Always, the logged exception text must be replaced with a fixed developer-guidance message explaining that customErrors is suppressing detail.
Trigger GetExceptionMessage evaluation.
Enforcement ApiResponseHandler.GetExceptionMessage
Outcome Guidance text instructing the developer to set config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;.
Operations OP-01 (step 9)
Source src/SAIF.Web.API/ApiResponseHandler.cs

BR-WEBAPI-011 SAIF exception types map to fixed HTTP status codes

Field Value
Statement SAIF API exception types map to fixed HTTP status codes: InternalServerError→500, NoMatchingDataException→404, UnauthorizedAccessException→403, InvalidDataException→400; anything else→500.
Trigger ExceptionHandler.ToHttpError called by a consumer.
Enforcement ExceptionHandler.ToHttpError
Outcome HttpStatusCode returned.
Operations OP-06
Source src/SAIF.Web.API/ExceptionHandler.cs (types from SAIF.API.Exceptions)

BR-WEBAPI-012 HTTPS required unless local exemption applies

Field Value
Statement HTTPS is required for every decorated action unless the request is local and either no RequireSSLForLocal app-setting exists or its value parses to false.
Trigger Action filter execution.
Enforcement RequireSSLAttribute.RequiresSSL
Outcome requireSSL boolean decision; default true.
Operations OP-07 (steps 2–4)
Source src/SAIF.Web.API/RequireSSLAttribute.cs

BR-WEBAPI-013 Redirect non-HTTPS requests to HTTPS

Field Value
Statement A non-HTTPS request that requires SSL must be redirected to the same URI over HTTPS on port 443.
Trigger Scheme is not https and BR-WEBAPI-012 evaluates true.
Enforcement RequireSSLAttribute.OnActionExecuting
Outcome HttpContext.Current.Response.Redirect(httpsUri).
Operations OP-07 (step 5)
Source src/SAIF.Web.API/RequireSSLAttribute.cs

BR-WEBAPI-014 Authorization fails closed on missing dependencies

Field Value
Statement Authorization must fail closed: a missing ISecurityApiClient or IEnvironmentService in the request scope is an error, not an implicit allow; actionContext and (in the unauthorized path) ILogService must be non-null.
Trigger Every authorization-filter evaluation.
Enforcement SaifAuthorizeAttribute.IsAuthorized / HandleUnauthorizedRequest
Outcome MissingDependencyException / Enforce.ArgumentNotNull throw.
Operations OP-08 (steps 1–4), OP-09 (steps 1–2)
Source src/SAIF.Web.API/SaifAuthorizeAttribute.cs (Enforce / MissingDependencyException — SAIF.Framework.Core)

BR-WEBAPI-015 No roles specified authorizes unconditionally

Field Value
Statement Applying [SaifAuthorize] with no role arguments authorizes the request unconditionally.
Trigger Roles string is empty.
Enforcement SaifAuthorizeAttribute.IsAuthorized
Outcome Returns true without contacting the security API.
Operations OP-08 (step 5)
Source src/SAIF.Web.API/SaifAuthorizeAttribute.cs

BR-WEBAPI-016 Unknown user is not authorized

Field Value
Statement A request whose resolved user name is null, empty, or whitespace is not authorized.
Trigger Roles are present.
Enforcement SaifAuthorizeAttribute.IsAuthorized
Outcome Returns false → OP-09.
Operations OP-08 (steps 6–7)
Source src/SAIF.Web.API/SaifAuthorizeAttribute.cs (uses SAIF.Common.Extensions)

BR-WEBAPI-017 Role evaluation uses OR semantics

Field Value
Statement Role evaluation is OR semantics: membership in any one of the declared roles authorizes the request.
Trigger Roles present and user resolved.
Enforcement SaifAuthorizeAttribute.IsAuthorized
Outcome true on the first ISecurityApiClient.IsAuthorized(user, role) that returns true.
Operations OP-08 (step 8)
Source src/SAIF.Web.API/SaifAuthorizeAttribute.cs (ISecurityApiClient — SAIF.Framework.Core)

BR-WEBAPI-018 Unauthorized requests are logged and answered with 401

Field Value
Statement An unauthorized request must be logged as a SecurityException (only when the user is known) and answered with HTTP 401 naming the user.
Trigger IsAuthorized returned false.
Enforcement SaifAuthorizeAttribute.HandleUnauthorizedRequest
Outcome Log entry (conditional) + 401 Unauthorized with message The user '{user}' is not authorized.
Operations OP-09 (steps 3–6)
Source src/SAIF.Web.API/SaifAuthorizeAttribute.cs

BR-WEBAPI-019 Header reads never throw

Field Value
Statement Reading a request header must never throw; an absent header yields an empty string.
Trigger TryGetFirstHeader call.
Enforcement HttpRequestExtensions.TryGetFirstHeader
Outcome "" returned.
Operations OP-05 (step 2), OP-10
Source src/SAIF.Web.API/HttpRequestExtensions.cs

BR-WEBAPI-020 Role names are trimmed and normalized

Field Value
Statement Role names supplied to [SaifAuthorize] are whitespace-trimmed at construction and again space-stripped at evaluation, so callers may pass padded role strings.
Trigger Attribute construction / evaluation.
Enforcement SaifAuthorizeAttribute constructor and IsAuthorized
Outcome Normalized comma-delimited role list.
Operations OP-08 (steps 0, 8)
Source src/SAIF.Web.API/SaifAuthorizeAttribute.cs

Notes

  • The forceProductionRole constructor parameter on SaifAuthorizeAttribute implies an intended rule ("evaluate against production role names even in non-production"), but the stored _ForceProductionRole field is never read anywhere in the repository. This is recorded as an open question rather than a rule, per the analysis's convention that unreached behavior is not a rule.
  • ExceptionHandler.ToHttpError maps SAIF.API.Exceptions.UnauthorizedAccessException → 403 Forbidden (BR-WEBAPI-011), while SaifAuthorizeAttribute.HandleUnauthorizedRequest returns 401 Unauthorized (BR-WEBAPI-018) — the two authorization-denial paths disagree on status code. This is an observed inconsistency, not a defect claim.*

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.

Sources

  • src/SAIF.Web.API/ApiResponseHandler.cs
  • src/SAIF.Web.API/ExceptionHandler.cs
  • src/SAIF.Web.API/HttpRequestExtensions.cs
  • src/SAIF.Web.API/RequireSSLAttribute.cs
  • src/SAIF.Web.API/SaifAuthorizeAttribute.cs