SaifAuthorizeAttribute¶
SaifAuthorizeAttribute is a sealed System.Web.Http.AuthorizeAttribute subclass that a consuming Web API application decorates controllers or actions with to gate access by SAIF security-resource role names; no such decoration exists inside this library.* Unlike the base AuthorizeAttribute, it never calls base.IsAuthorized, so the framework's own Identity.IsAuthenticated check is bypassed entirely; instead it resolves ISecurityApiClient and IEnvironmentService per request from the Web API dependency scope and delegates the authorization decision to ISecurityApiClient.IsAuthorized(user, role) for each configured role, with OR semantics across roles. It fails closed (throws) when either dependency is unregistered, and it is the concrete implementation of this library's attribute-based authorization enforcement point — see security-authorization.md.
Purpose¶
- Provides declarative, role-name-based authorization for Web API controllers/actions, delegating the actual decision to a remote
ISecurityApiClientrather than evaluating roles locally — see BR-WEBAPI-017. - Fails closed when its required collaborators (
ISecurityApiClient,IEnvironmentService) are not registered with the host's dependency resolver, surfacing as an unhandled exception rather than silently allowing or denying — see BR-WEBAPI-014. - Authorizes every request unconditionally when constructed with no roles, and denies requests where the resolved user identity is null/blank — see BR-WEBAPI-015 and BR-WEBAPI-016.
- Logs unauthorized-request details (when the user is known) and returns a fixed
401 Unauthorizedresponse, rather than403 Forbidden, on denial — see BR-WEBAPI-018. - Normalizes constructor-supplied role names by trimming each entry before joining them into the inherited
Rolesproperty — see BR-WEBAPI-020.
Context¶
- Namespace:
SAIF.Web.API - Assembly/package:
SAIF.Web.API(NuGet) - Base type:
System.Web.Http.AuthorizeAttribute - Sealed class; decorated with
[SuppressMessage("Microsoft.Design", "CA1018:MarkAttributesWithAttributeUsage")], meaning no explicitAttributeUsageis declared and the default (all targets, single use, inherited) applies.
Public members¶
| Member | Signature | Purpose |
|---|---|---|
.ctor |
public SaifAuthorizeAttribute(params string[] roles) |
Convenience constructor; delegates to the two-argument constructor with forceProductionRole: false. |
.ctor |
public SaifAuthorizeAttribute(bool forceProductionRole, params string[] roles) |
Trims each supplied role and joins them with , into the inherited AuthorizeAttribute.Roles property; stores forceProductionRole in the private _ForceProductionRole field, which is never subsequently read anywhere in this library — see BR-WEBAPI-020. |
Private and protected members¶
| Member | Signature | Purpose |
|---|---|---|
IsAuthorized |
protected override bool IsAuthorized(HttpActionContext actionContext) |
Core authorization decision. Validates actionContext is non-null; resolves ISecurityApiClient and IEnvironmentService from the request dependency scope, throwing MissingDependencyException for either if unresolved; returns true immediately if no roles were configured; reads environmentService.User.Identity.Name (dereferenced without a null check) and returns false if the resolved user name is null/blank; otherwise splits the configured roles on , and returns true if ISecurityApiClient.IsAuthorized(user, role) succeeds for any role (short-circuiting on the first success) — see BR-WEBAPI-014 through BR-WEBAPI-017. |
HandleUnauthorizedRequest |
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) |
Invoked by the base AuthorizeAttribute when IsAuthorized returns false. Validates actionContext; resolves ILogService and IEnvironmentService from the request dependency scope, both Enforce.ArgumentNotNull-checked (unlike IsAuthorized, a missing ILogService here throws rather than being tolerated); reads the resolved user ID; if the user ID is known, logs a SecurityException describing the denial via ILogService.LogException; sets actionContext.Response to 401 Unauthorized with a body that echoes the resolved user name — see BR-WEBAPI-018. |
GetContext |
private string GetContext(HttpActionContext actionContext, IEnvironmentService environmentService) |
Formats the log state string as User: {userId}\nPage: {requestUri}, used as the state argument to ILogService.LogException in HandleUnauthorizedRequest. |
_ForceProductionRole |
private bool field |
Set from the forceProductionRole constructor parameter but never read anywhere in this library — dead state with no observable runtime effect. |
Behavioral notes¶
IsAuthorizednever callsbase.IsAuthorized(actionContext), so the baseAuthorizeAttribute's built-inIdentity.IsAuthenticatedcheck andUsersproperty filtering are bypassed entirely; an instance constructed with no roles authorizes every request that reaches the filter, including unauthenticated ones — see BR-WEBAPI-015.*- Missing-dependency failures (
MissingDependencyException) are unhandled at this layer and propagate up the pipeline, typically surfacing to the caller as a500-class response rather than a401/403.* - In
HandleUnauthorizedRequest, logging is deliberately skipped when the resolved user ID is null/empty — a source comment notes the user may be null before credentials are demanded, i.e. the initial unauthenticated challenge round-trip is not logged as a security event. - The response body set in
HandleUnauthorizedRequestdiscloses the resolved account name ("The user '{user}' is not authorized.") to the caller. - Authorization is evaluated against
IEnvironmentService.User.Identity.Name, a separately resolved identity source, rather thanactionContext.RequestContext.Principal; whether the host guarantees these represent the same principal could not be determined from this repository.** _ForceProductionRoleis dead code: the two-argument constructor'sforceProductionRoleparameter has no effect on any runtime behavior in this library.
Constraints and usage notes¶
- Both
ISecurityApiClientandIEnvironmentServicemust be registered with the hosting application'sIDependencyResolver; if either is absent, every decorated action throws on first authorization attempt rather than denying gracefully — see security-authorization.md. - No AD resource/role names are hard-coded in this library; all resource names are supplied by the consuming application via the constructor's
params string[] roles— see security-authorization.md. - Role matching uses OR semantics: a caller authorized against any one of the configured roles passes.
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/SAIF.Web.API/SaifAuthorizeAttribute.cs