// CacheAuthorizationHandler is only attached to the CachePolicy after first time success authorization /// <summary> /// When output is cached, the OnAuthorization will be skipped /// </summary> /// <param name="context">The context</param> /// <param name="data">The data</param> /// <param name="validationStatus">The validation status</param> private void CacheAuthorizationHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus) { // when output is cached, the cachePolicy will trigger the CacheValidateHandler to authorize user if (data == null) { throw new ArgumentNullException(nameof(data), "data should be AuthorizationContext"); } AuthorizationContext filterContext = (AuthorizationContext)data; if (filterContext == null) { throw new InvalidCastException("data should be AuthorizationContext"); } AuthorizeResultEnum authorizeResult = AuthorizeUser(filterContext); // HttpValidationStatus.Valid => return cache data validationStatus = authorizeResult == AuthorizeResultEnum.Success ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest; }
/// <summary> /// Stop processing the action and return HttpUnauthorizedResult /// </summary> /// <param name="filterContext">The filter context</param> /// <param name="authorizeResult">The authorize result</param> protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext, AuthorizeResultEnum authorizeResult) { switch (authorizeResult) { case AuthorizeResultEnum.Failed_NotLoggedIn: // User not logged in => redirect to login page by setting the error 401 // Owin will automatically redirect all 401 errors to login page filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Unauthorized); break; case AuthorizeResultEnum.PermissionNotDefined: ProcessUnauthorizedError(filterContext, "Permission is not defined"); break; case AuthorizeResultEnum.Failed_SuperAdminOnly: ProcessUnauthorizedError(filterContext, "This functionality is available for system admin only"); break; // AuthorizeResultEnum.NotAuthorized default: ProcessUnauthorizedError(filterContext, $"You need to be granted permission '{this.AuthorizingPermissionId}' to access this feature. Please contact administrator for support"); break; } }
/// <summary> /// Called when authorization is required. /// </summary> /// <param name="filterContext">The filter context</param> /// <exception cref="System.ArgumentNullException">filterContext</exception> /// <exception cref="System.InvalidOperationException">ActivePermissionAttribute: Cannot Use Within Child Action Cache</exception> public void OnAuthorization(AuthorizationContext filterContext) { //filterContext.Controller.ControllerContext. if (filterContext == null) { throw new ArgumentNullException(nameof(filterContext)); } if (OutputCacheAttribute.IsChildActionCacheActive(filterContext)) { // If a child action cache block is active, we need to fail immediately, even if authorization // would have succeeded. The reason is that there's no way to hook a callback to rerun // authorization before the fragment is served from the cache, so we can't guarantee that this // filter will be re-run on subsequent requests. // MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache throw new InvalidOperationException("ActivePermissionAttribute: Cannot Use Within Child Action Cache"); } #region Skip Authorization /* * Skip the authorization if * - Controller / Action has AllowAnonymousAttribute * - Action is NonAction * - Action is child action * * */ if (filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true) || filterContext.ActionDescriptor.IsDefined(typeof(NonActionAttribute), inherit: true) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)) { return; } // Authorization must be done at Root action if (filterContext.IsChildAction) { return; } #endregion Skip Authorization AuthorizeResultEnum authorizeResult = AuthorizeUser(filterContext); if (authorizeResult == AuthorizeResultEnum.Success) { // ** IMPORTANT ** // Since we're performing authorization at the action level, the authorization code runs // after the output caching module. In the worst case this could allow an authorized user // to cause the page to be cached, then an unauthorized user would later be served the // cached page. We work around this by telling proxies not to cache the sensitive page, // then we hook our custom authorization code into the caching mechanism so that we have // the final say on whether a page should be served from the cache. HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache; cachePolicy.SetProxyMaxAge(new TimeSpan(0)); // add the authorization handler for cache // data is filterContext cachePolicy.AddValidationCallback(CacheAuthorizationHandler, filterContext); } else { HandleUnauthorizedRequest(filterContext, authorizeResult); } }