How can I extend the Authorize attribute's logic in ASP.NET Core 3.1? - asp.net-core-mvc

I'm working on an ASP.NET Core MVC application and I want to be able to better handle what happens when an unauthenticated user runs an action through AJAX.
I found this solution that essentially extends the Authorize attribute's logic and sets an AJAX request's status code to 401 when user is no longer authenticated. The status code is returned to a global AJAX error handler and the appropriate action can be performed.
I'm trying to create the attribute in Core 3.1 but I cannot find a way to first run the base logic of the filter. base.OnAuthorization method is not there. I specifically don't want to rewrite the Authorize attribute's functionality - only to extend it.
How can I extend the Authorize attribute's logic in Core 3.1?
The authorize attribute I'm writing:
public class AuthorizeUserAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
base.OnAuthorization(context); //method does not exist
OnAjaxAuthorization(context);
}
internal void OnAjaxAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.User.Identity.IsAuthenticated == false)
{
if (context.HttpContext.Request.IsAjaxRequest())
{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
}
}
}
Just as a side note, I was thinking of writing a ActionFilterAttribute to run the additional code instead, but I can't do that because the Authorize attribute always runs first.

Related

How to validate arbitrary conditions when authenticating an user?

Let's say I have an ASP.NET MVC Core application, and I want to validate certain custom conditions when allowing authentication. For example, an user that provides a valid pair of credentials, but is disabled by the application's administrator, or a flag that indicates the user is up-to-date with his payments, or any other arbitrary condition. Is there a place in ASP.NET Core Identity where I can hook this custom validation? I have to make this work for local and external authentication.
For you case, you could create custom user validators.
Writing a custom validator for ASP.NET Core Identity
Custom User Validator That Requires E-Mail Addresses to End in #example.com
This follows a common pattern within Identity, where you can either implement the interface (in this case IValidator<T>) and provide all of the validation code, or override the base implementation of it and add additional logic by overriding methods as I did here.
public class CustomUserValidator : UserValidator<ApplicationUser>
{
public override async Task<IdentityResult> ValidateAsync(UserManager<ApplicationUser> manager,
ApplicationUser user)
{
IdentityResult baseResult = await base.ValidateAsync(manager, user);
List<IdentityError> errors = new List<IdentityError>(baseResult.Errors);
if (!user.Email.EndsWith("#example.com"))
{
IdentityError invalidEmailError = Describer.InvalidEmail(user.Email);
invalidEmailError.Description += " Email address must end with #example.com";
errors.Add(invalidEmailError);
}
return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success;
}
}
Then to plug this in, head over to the Startup.cs file and find the ConfigureServices method. Somewhere before the line starting with services.AddIdentity, add the following line:
services.AddTransient<IUserValidator<ApplicationUser>, CustomUserValidator>();
This will add the implementation of CustomUserValidator to the internal services collection, allowing it to be injected anywhere that an IUserValidator<ApplicationUser> is required.

override serve resource of user account in Liferay using hooks

I am using Liferay 6.2. I made a hook to add some extra fields in User-My Acccount page. On key press of these fields, an ajax call needs to be invoked. I read lifery service override and trying to follow the same approach: It works for updating user with new fields.
However, for ajax call, i need to override serve resource method somewhere. But i am not sure where exactly to call serve resource().
My approach is as follows:
In details.jsp
<portlet:resourceURL var ="userProfileURL"></portlet:resourceURL>
In js:
I call the ajax by using AUI io request: I pass mode as a parameter to check if it goes inside serveResource or not..
But before going to serve resource, it gives me an error saying userProfileURL is not defined. I have also included needed imports for it in jsp.
In userServiceImpl class that extends UserServiceWrapper, i tried to override serveResource:
public void serveResource(ResourceRequest resourceRequest, ResourceResponse resourceResponse) throws PortletException {
String mode = ParamUtil.getString(resourceRequest,"mode")
if(mode.equals("getData")
{
// do needed processings & return data
}
else {
// super.serveResource(resourceRequest, resourceResponse);
}
But i get an error that says:
The method serveResource(ResourceRequest, ResourceResponse) is undefined for the type UserServiceWrapper
Is there any way to make ajax calls in hooks for user account page or am i oveririding on the wrong place?

Grails + RESTful URL mapping + Filters + Routes

Member have many jobs. A member can add, delete or update Jobs. Currently there are actions (add, delete or update) defined in a controller which are called through jQuery.ajax(). We are sending job id and member id to perform the operation. Member id is necessary because there is a role admin who can modify the job on behalf of members, so we need to identify the member. But sending member id is dangerous as anyone can send the request by modifying the member id.
I know, we can add constraint do restrict that only admin can modify the jobs or a member can modify only his jobs. My question is, Do I need to add these constraints in the action of the controller or Is there any Grails way to do that. I have google, the same thing is handled in Ruby and Rails by using routes. And in grails I have skim through RESTful URL mapping, which is perhaps used for this purpose.
Can anyone points me to right direction, thanks. I am using Grails 2.1.1.
You can implement some realization of AbstractPersistenceEventListenerService to not allow perform actions with entity that constains id of not logged in user. Example:
class MultiTenantPersistenceEventListenerService extends AbstractPersistenceEventListenerService {
def springSecurityService
#Override
protected AbstractPersistenceEventListener createPersistenceEventListener(Datastore datastore) {
return new MultiTenantPersistenceEventListener(datastore)
}
}
class MultiTenantPersistenceEventListener extends AbstractPersistenceEventListener {
MultiTenantPersistenceEventListener(final Datastore datastore) {
super(datastore)
}
#Override
protected void onPersistenceEvent(AbstractPersistenceEvent event) {
def entity = event.getEntityObject() // could be your Job domain entity
def user = springSecurityService.getCurrentUser() //current logged in user
if(entity.hasProperty('userId')){ // every job belongs to User
if(entity.userId != user.id){
throw new AccessDeniedException("Acces Denied !")
}
}
}
}
I'd recomment to use grails spring-security-plugin. There is a lot of information in web about plugin and it's easy configurable. Plugin allows you to perfrom controller's action in secure way. For example:
#Secured(['ROLE_USER'])
def followAjax = { ... }
#Secured(['IS_AUTHENTICATED_REMEMBERED'])
def personal = { ... }
For more information - plugin and spring-security with grails.
You can use Authorize attribute to authorize the user,
e.g
[CustomAuthorize(Roles=SiteRoles.Admin|SiteRoles.HelpDesk)]
public ActionResult Index()
{
return View();
}
This is a nice approach for making website secure.
go through these link, this will help you.
custom authorization with asp.net mvc
asp.net mvc authorization

Time based authentification and actions in asp.net mvc

Is there an integrated way in asp.net mvc 3, to permit authentification and actions based on the time of the day ?
For example, if it's 18:00 o'clock, users that belong to a specific role are not allowed to log in or if they are already authenticated, they will be automatically logged out or not being able to do actions.
I guess in the log in method I could check for user role and time of day and then on each action, I will also check for role and time of day and permit but is there and easier way to accomplish this ?
UPDATE:
I guess there is no easier way to just set the time and user/roles so I ended up implementing the answer(solution).
You could write a custom Authorize attribute and override the AuthorizeCore method in which you would perform the necessary check:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
return false;
}
// At this stage standard authorization passed =>
// you could now check the user roles in the database
// and the time of the day and return true or false from here
...
}
}
and now all that's left is decorate your controllers/actions with this custom attribute.

Why can't I combine [Authorize] and [OutputCache] attributes when using Azure cache (.NET MVC3 app)?

Using Windows Azure's Microsoft.Web.DistributedCache.DistributedCacheOutputCacheProvider as the outputCache provider for an MVC3 app. Here is the relevant action method:
[ActionName("sample-cached-page")]
[OutputCache(Duration = 300, VaryByCustom = "User",
Location = OutputCacheLocation.Server)]
[Authorize(Users = "me#mydomain.tld,another#otherdomain.tld")]
public virtual ActionResult SampleCachedPage()
{
return View();
}
I get the following exception when loading this view from a web browser:
System.Configuration.Provider.ProviderException: When using a custom output cache provider like 'DistributedCache', only the following expiration policies and cache features are supported: file dependencies, absolute expirations, static validation callbacks and static substitution callbacks.
System.Configuration.Provider.ProviderException: When using a custom output cache provider like 'DistributedCache', only the following expiration policies and cache features are supported: file dependencies, absolute expirations, static validation callbacks and static substitution callbacks.
at System.Web.Caching.OutputCache.InsertResponse(String cachedVaryKey, CachedVary cachedVary, String rawResponseKey, CachedRawResponse rawResponse, CacheDependency dependencies, DateTime absExp, TimeSpan slidingExp)
at System.Web.Caching.OutputCacheModule.OnLeave(Object source, EventArgs eventArgs)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
If I remove the [Authorize] attribute, the view caches as would be expected. Does this mean I cannot put [OutputCache] on an action method that must have [Authorize]? Or, do I need to override AuthorizeAttribute with a custom implementation that uses a static validation callback method for the cache?
Update 1
After Evan's answer, I tested the above action method in IIS Express (outside of Azure). Here is my override for the VaryByCustom = "User" property on the OutputCache attribute:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
return "User".Equals(custom, StringComparison.OrdinalIgnoreCase)
? Thread.CurrentPrincipal.Identity.Name
: base.GetVaryByCustomString(context, custom);
}
When I visit the sample cached page as me#mydomain.tld, the output of the page is cached, and the view displays "This page was cached at 12/31/2011 11:06:12 AM (UTC)". If I then sign out and sign in as another#otherdomain.tld and visit the page, it displays "This page was cached at 12/31/2011 11:06:38 AM (UTC)". Signing back in as me#mydomain.tld and revisiting the page causes the cache to display "This page was cached at 12/31/2011 11:06:12 AM (UTC)" again. Further sign in/out attempts show that different output is being cached & returned depending on the user.
This is leading me to believe that the output is being cached separately based on the user, which is the intention with my VaryByCustom = "User" setting & override. The problem is that it doesn't work with Azure's distributed cache provider. Evan, does you answer about only caching public content still stand?
Update 2
I dug up the source, and found that the out-of-box AuthorizeAttribute does in fact have a non-static validation callback. Here is an excerpt from OnAuthorization:
if (AuthorizeCore(filterContext.HttpContext)) {
// ** 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));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else {
HandleUnauthorizedRequest(filterContext);
}
CacheValidationHandler delegates the cache validation to protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase), which of course is not static. One reason why it is not static is because, as noted in the IMPORTANT comment above, it invokes protected virtual bool AuthorizeCore(HttpContextBase).
In order to do any of the AuthorizeCore logic from a static cache validation callback method, it would need to know the Users and Roles properties of the AuthorizeAttribute instance. However there doesn't seem to be an easy way to plug in. I would have to override OnAuthorization to put these 2 values into the HttpContext (Items collection?) and then override OnCacheAuthorization to get them back out. But that smells dirty.
If we are careful to use the VaryByCustom = "User" property in the OutputCache attribute, can we just override OnCacheAuthorization to always return HttpValidationStatus.Valid? When the action method does not have an OutputCache attribute, we would not need to worry about this callback ever being invoked, correct? And if we do have an OutputCache attribute without VaryByCustom = "User", then it should be obvious that the page could return any cached version regardless of which user request created the cached copy. How risky is this?
Caching happens before the Action. You will likely need to customize your authorization mechanics to handle cache scenarios.
Check out a question I posted a while back - MVC Custom Authentication, Authorization, and Roles Implementation.
The part I think would help you is a custom Authorize Attribute who's OnAuthorize() method deals with caching.
Below is a code block for example:
/// <summary>
/// Uses injected authorization service to determine if the session user
/// has necessary role privileges.
/// </summary>
/// <remarks>As authorization code runs at the action level, after the
/// caching module, our authorization code is hooked into the caching
/// mechanics, to ensure unauthorized users are not served up a
/// prior-authorized page.
/// Note: Special thanks to TheCloudlessSky on StackOverflow.
/// </remarks>
public void OnAuthorization(AuthorizationContext filterContext)
{
// User must be authenticated and Session not be null
if (!filterContext.HttpContext.User.Identity.IsAuthenticated || filterContext.HttpContext.Session == null)
HandleUnauthorizedRequest(filterContext);
else {
// if authorized, handle cache validation
if (_authorizationService.IsAuthorized((UserSessionInfoViewModel)filterContext.HttpContext.Session["user"], _authorizedRoles)) {
var cache = filterContext.HttpContext.Response.Cache;
cache.SetProxyMaxAge(new TimeSpan(0));
cache.AddValidationCallback((HttpContext context, object o, ref HttpValidationStatus status) => AuthorizeCache(context), null);
}
else
HandleUnauthorizedRequest(filterContext);
}
}
/// <summary>
/// Ensures that authorization is checked on cached pages.
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public HttpValidationStatus AuthorizeCache(HttpContext httpContext)
{
if (httpContext.Session == null)
return HttpValidationStatus.Invalid;
return _authorizationService.IsAuthorized((UserSessionInfoViewModel) httpContext.Session["user"], _authorizedRoles)
? HttpValidationStatus.Valid
: HttpValidationStatus.IgnoreThisRequest;
}
I've come back to this issue and, after a bit of tinkering, have concluded that you cannot use the out of the box System.Web.Mvc.AuthorizeAttribute along with the out of the box System.Web.Mvc.OutputCacheAttribute when using the Azure DistributedCache. The main reason is because, as the error message in the original question states, the validation callback method must be static in order to use it with Azure's DistributedCache. The cache callback method in the MVC Authorize attribute is an instance method.
I went about trying to figure out how to make it work by making a copy of the AuthorizeAttribute from the MVC source, renaming it, hooking it up to an action with OutputCache connected to Azure, and debugging. The reason the cache callback method is not static is because, in order to authorize, the attribute needs to check the HttpContext's User against the Users and Roles property values that are set when the attribute is constructed. Here is the relevant code:
OnAuthorization
public virtual void OnAuthorization(AuthorizationContext filterContext) {
//... code to check argument and child action cache
if (AuthorizeCore(filterContext.HttpContext)) {
// 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));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else {
HandleUnauthorizedRequest(filterContext);
}
}
Cache Validation Callback
private void CacheValidateHandler(HttpContext context, object data,
ref HttpValidationStatus validationStatus) {
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
// This method must be thread-safe since it is called by the caching module.
protected virtual HttpValidationStatus OnCacheAuthorization
(HttpContextBase httpContext) {
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
bool isAuthorized = AuthorizeCore(httpContext);
return (isAuthorized)
? HttpValidationStatus.Valid
: HttpValidationStatus.IgnoreThisRequest;
}
As you can see, the cache validation callback ultimately invokes AuthorizeCore, which is another instance method (protected virtual). AuthorizeCore, which was also called during OnAuthorization, does 3 main things:
Checks that the HttpContextBase.User.Identity.IsAuthenticated == true
If the attribute has a non-empty Users string property, checks that the HttpContextBase.User.Identity.Name matches one of the comma-separated values.
If the attribute has a non-empty Roles string property, checks that the HttpContextBase.User.IsInRole for one of the comma-separated values.
AuthorizeCore
// This method must be thread-safe since it is called by the thread-safe
// OnCacheAuthorization() method.
protected virtual bool AuthorizeCore(HttpContextBase httpContext) {
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated) {
return false;
}
if (_usersSplit.Length > 0 && !_usersSplit.Contains
(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) {
return false;
}
if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)) {
return false;
}
return true;
}
When you simply try to make the validation callback method static, the code won't compile because it needs access to these _rolesSplit and _usersSplit fields, which are based on the public Users and Roles properties.
My first attempt was to pass these values to the callback using the object data argument of the CacheValidateHandler. Even after introducing static methods, this still did not work, and resulted in the same exception. I was hoping that the object data would be serialized, then passed back to the validate handler during the callback. Apparently this is not the case, and when you try to do this, Azure's DistributedCache still considers it a non-static callback, resulting in the same exception & message.
// this won't work
cachePolicy.AddValidationCallback(CacheValidateHandler, new object() /* data */);
My second attempt was to add the values to the HttpContext.Items collection, since an instance of HttpContext is automatically passed to the handler. This didn't work either. The HttpContext that is passed to the CacheValidateHandler is not the same instance that existed on the filterContext.HttpContext property. In fact, when the CacheValidateHandler executes, it has a null Session and always has an empty Items collection.
// this won't work
private void CacheValidateHandler(HttpContext context, object data,
ref HttpValidationStatus validationStatus) {
Debug.Assert(!context.Items.Any()); // even after I put items into it
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
However...
Even though there seems to be no way to pass the Users & Roles property values back to the cache validation callback handler, the HttpContext passed to it does in fact have the correct User Principal. Also, none of the actions where I currently want to combine [Authorize] and [OutputCache] ever pass a Users or Roles property to the AuthorizeAttribute constructor.
So, it is possible to create a custom AuthenticateAttribute which ignores these properties, and only checks to make sure the User.Identity.IsAuthenticated == true. If you need to authenticate against a specific role, you could also do so and combine with OutputCache... however, you would need a distinct attribute for each (set of) Role(s) in order to make the cache validation callback method static. I will come back and post the code after I've polished it a bit.
You are correct olive. Caching works by caching the entire output of the Action (including all attributes) then returning the result to subsequent calls without actually calling any of your code.
Because of this you cannot cache and check authorization because by caching you are not going to call any of your code (including authorization). Therefore anything that is cached must be public.

Resources