I know in MVC at the top of a controller you can use the [Authorize()] attribute to restrict access to that entire controller to certain authenticated users and/or roles, but not by IP, but this must be done on a per controller instance. Is there a way to restrict access to an entire MVC Area to an authenticated User/Role or by the request Source IP?
Create a Base Controller in your area:
[AuthorizeArea(AllowIpAddresses = new [] {"1.1.1.1", "1.2.3.4"})]
public class CustomAreaBaseController : Controller
{
public CustomAreaBaseController()
{
// possibly any other common code that you want to run for all controllers in this area
}
}
Have all controllers in your area derive from base controller:
public class HomeController : CustomAreaBaseController
{
// actions for this controller
}
Create custom Authorize Attribute:
public class AuthorizeArea : AuthorizeAttribute
{
public string[] AllowIpAddresses { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isValid = false;
if (httpContext == null)
throw new ArgumentNullException("httpContext");
// get current ip address
var ipAddress = httpContext.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (string.IsNullOrEmpty(ipAddress))
ipAddress = httpContext.Request.ServerVariables["remote_host"];
if (AllowIpAddresses.Contains(ipAddress)) isValid = true;
return base.AuthorizeCore(httpContext) && isValid;
}
}
Related
I did see this stackoverflowQuestion but this revolves around using the, Authorize attribute. I am using a custom authorize attribute, by extending AuthorizeAttribute.
I want to be able to place this custom filter at the top level of the controller class, but for a couple of methods enforce only a specific role, not both the top level and action method role.
so,
[AuthorizeUser("Transact")]
public class HomeController : Controller
{
//
// GET: /Search/Home/
public ActionResult Index()
{
return View();
}
[AuthorizeUser("Search")]
public ActionResult Search()
{
return View();
}
}
Doing this, the framework will check to see if a user has both the Transact, and Search role.. I just want to check for the search role in this scenario.
I am reusing this search functionality and partialview in another Area.
Going back to the link I posted: stackoverflowQuestion I was able to make it work in my situation. What seems to be happening is the call to my action first looks at the attribute from the Controller level, but using filterContext.ActionDescriptor.IsDefined inside the OnAuthroization method will tell me if the called action has my override attribute attached. If it does it skips calling the base.OnAuthorization method, then the override attribute will be invoked.
So, what I had to do was create the override class and extend my custom authorization class. I have a flag declared in the parent and set in the override class's constructor to tell me if the override authorization method is calling the authorization methods of my custom authorization class.
Here is the example to make sense of it all.
public class AuthorizeUserAttribute : AuthorizeAttribute
{
protected bool isOverrideAuthorize = false;
public AuthorizeUserAttribute(params...)
{
}
public AuthorizeUserAttribute(MenuItems...)
{
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var action = filterContext.ActionDescriptor;
if (action.IsDefined(typeof(OverrideAuthorizeUserAttribute), true) && !isOverrideAuthorize)
{
return;
}
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
}
}
The override class:
public class OverrideAuthorizeUserAttribute : AuthorizeUserAttribute
{
public OverrideAuthorizeUserAttribute(params...) : base(roles)
{
base.isOverrideAuthorize = true;
}
public OverrideAuthorizeUserAttribute(MenuItems...) : base(item)
{
base.isOverrideAuthorize = true;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return base.AuthorizeCore(httpContext);
}
}
This allowed me to do authorization on the action solely on the authorization attribute declared on that action, rather than the authorization attribute on the controller AND action method, as defaulted by the framework.
Also, MVC5 seems to have this problem covered by including a, "OverrideAuthorization" attribute.. Unfortunately I am still on MVC4.
You can try at the top of controller like bellow this will support multiple role for single controller or you can use same Authorize(Roles = "Admin") filter top of every action....
[Authorize(Roles = "Admin,HRManager,Finance")]
Public class MyController:Controller{
// inside controller action methods
}
I'm trying to implement authorization in my ASP.NET Core 2.0 Web app.
This app has like 20 models, each with a controller implementing at least a CRUD. I found these two pages and I liked the idea of using a handler to authorize requisitions. I would like initially to implement authorization by user, i.e., a user has only permission to see/edit his own entities. All my database entities have an OwnerId field.
These examples I found seem to only work for one specific controller.
So, my question is: is it possible to create one authorization handler for all controllers?
Have you found a solution or workaround yet that works with the authorization handler or authorization attributes? I have the exact same setup as you do.
I was trying to create a generic attribute to serve all may Entity CRUD owner checks, but generic attributes are not allowed by design.
The only two (unsatisfying) solutions that I came up with are:
Within the controller action, get the ownerId from the User, forward it all the way to your CRUD and include there a check for the ownerId. However, the code must be duplicated for every action in every controller.
[HttpGet("{id}"]
public async Task<IActionResult> GetById(int id)
{
var stringGuid = User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
if (String.IsNullOrWhiteSpace(stringGuid)) return Unauthorized();
var ownerGuid = new Guid(stringGuid);
var entity = _yourCrudInstance.GetById(id, ownerGuid);
return Ok(entity);
}
Add a method to your CRUD repository like bool IsOwner(Guid ownerId) and use this method when creating the custom authorization handler (by creating a custom requirement together with a custom handler). This eliminates code duplication in the controller, because you can create a new policy with this custom authorization handler and consequently you can simply decorate every action with a [Authorize(Policy = "yourOwnershipPolicy")]. But still, there must be a service created for each and every controller. Moreover, the IsOwner(...) method adds an additional database call compared to solution 1 - one db call for checking the ownership (during authorization check) and one db call for actually getting the entity (by working through the controller action).
[Authorize(Policy = "yourOwnershipPolicy")]
public async Task<IActionResult> GetById(int id)
{
var entity = _yourCrudInstance.GetById(id);
return Ok(entity);
}
I am going with the first solution until I found a way to create a generic authorization handling for my generic CRUD repository, because one may forget creating the required authorization policy for a new entity, but one cannot forget to supply the parameter ownerId to .GetById(id, ownerGuid), provided there is no overload method, or the code doesn't compile.
Update:
I found a third solution in which was able to create a kind of generic authorization attribute. The trick was to use the type of concrete repository as input parameter in the authorization attribute. Yet, there is still a limitation: The authorization attribute must be copied for every type of Id, for example int Id, Guid id, etc. But still, this reduces repeated code to the types of ids. In most cases, people only have one type of id, probably int or Guid.
Here some code that demonstrates my architecture. It is heavily summarized and redacted, but should compile successfully. My original code is working and in production:
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
[Route("api/yourcontroller")]
public class YourApiController : Controller
{
private readonly YourEntityXYZRepository _repo;
public YourApiController(YourDbContext yourDbContext)
{
_repo = new YourEntityXYZRepository(yourDbContext);
}
[HttpGet("{id}")]
[AuthorizeOwnerIntId(typeof(YourEntityXYZRepository), Policy = "YourCustomPolicy")]
public async Task<IActionResult> GetById(int id)
{
var entity = _repo.GetById(id);
return Ok(entity);
}
}
// The "generic" authorization attribute for type int id
// Similar authorization attributes for every type of id must be created additionally, for example Guid
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class AuthorizeOwnerIntIdAttribute : AuthorizeAttribute, IAuthorizationFilter
{
private object _entityRepositoryObject;
private IAsyncOwnerIntId _entityRepository;
private readonly Type _TCrudRepository;
public AuthorizeOwnerIntIdAttribute(Type TCrudRepository)
{
_TCrudRepository = TCrudRepository;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var yourDbContext = context.HttpContext.RequestServices.GetService<YourDbContext>();
_entityRepositoryObject = Activator.CreateInstance(_TCrudRepository, yourDbContext);
_entityRepository = _entityRepositoryObject as IAsyncOwnerIntId;
var user = context.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
// it isn't needed to set unauthorized result
// as the base class already requires the user to be authenticated
// this also makes redirect to a login page work properly
// context.Result = new UnauthorizedResult();
return;
}
// get entityId from uri
var idString = context.RouteData.Values["id"].ToString();
if (!int.TryParse(idString, out var entityId))
{
context.Result = new UnauthorizedResult();
return;
}
// get subjectId from user claims
var ownerIdString = context.HttpContext.User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
if (!Guid.TryParse(ownerIdString, out var ownerGuid))
{
context.Result = new UnauthorizedResult();
return;
}
if (!_entityRepository.IsEntityOwner(entityId, ownerGuid))
{
context.Result = new UnauthorizedResult();
}
}
}
// Your concrete repository
public class YourEntityXYZRepository : AsyncCrud<YourEntityXYZ, int>,
IAsyncOwnerIntId // Note that type concrete IAsyncOwnerIntId is only implemented in concrete repository
{
public YourEntityXYZRepository(YourDbContext yourDbContext) : base(yourDbContext)
{
}
}
// Your generic Crud repository
public abstract class AsyncCrud<TEntity, TId> : IAsyncCrud<TEntity, TId>
where TEntity : class, IEntityUniqueIdentifier<TId>, IEntityOwner
where TId : struct
{
protected YourDbContext YourDbContext;
public AsyncCrud(YourDbContext yourDbContext)
{
YourDbContext = yourDbContext;
}
// Note that the following single concrete implementation satisfies both interface members
// bool IsEntityOwner(TId id, Guid ownerGuid); from IAsyncCrud<TEntity, TId> and
// bool IsEntityOwner(int id, Guid ownerGuid); from IAsyncOwnerIntId
public bool IsEntityOwner(TId id, Guid ownerGuid)
{
var entity = YourDbContext.Set<TEntity>().Find(id);
if (entity != null && entity.OwnerGuid == ownerGuid)
{
return true;
}
return false;
}
// Further implementations (redacted)
public Task<bool> SaveContext() { throw new NotImplementedException(); }
public Task<TEntity> Update(TEntity entity){ throw new NotImplementedException(); }
public Task<TEntity> Create(TEntity entity, Guid ownerGuid) { throw new NotImplementedException(); }
public Task<bool> Delete(TId id) { throw new NotImplementedException(); }
public Task<bool> DoesEntityExist(TId id) { throw new NotImplementedException(); }
public virtual Task<TEntity> GetById(TId id) { throw new NotImplementedException(); }
}
// The interface for the Crud operations
public interface IAsyncCrud<TEntity, TId>
where TEntity : class, IEntityUniqueIdentifier<TId>
where TId : struct
{
bool IsEntityOwner(TId id, Guid ownerGuid);
Task<bool> DoesEntityExist(TId id);
Task<TEntity> GetById(TId id);
Task<TEntity> Create(TEntity entity, Guid ownerGuid);
Task<TEntity> Update(TEntity entity);
Task<bool> Delete(TId id);
Task<bool> SaveContext();
}
// The interface for the concrete type method for int id
// Similar interfaces for every type of id must be created additionally, for example Guid
public interface IAsyncOwnerIntId
{
bool IsEntityOwner(int id, Guid ownerGuid);
}
// Typical db context
public class YourDbContext : DbContext
{
public YourDbContext(DbContextOptions<YourDbContext> options) : base(options)
{
}
public DbSet<YourEntityXYZ> YourEntityXYZ { get; set; }
}
public class YourEntityXYZ : IEntityUniqueIdentifier<int>, IEntityOwner
{
public int Id { get; set; }
public Guid? OwnerGuid { get; set; }
// ... Additonal custom properties
}
public interface IEntityUniqueIdentifier<TId>
where TId : struct
{
TId Id { get; set; }
}
public interface IEntityOwner
{
Guid? OwnerGuid { get; set; }
}
I would like to implement the following in MVC6:
[Authorize(Roles = "Shopper")]
public class HomeController
{
[Authorize(Roles = "Editor"), OverrideAuthorization]
public IActionResult EditPage() {}
}
But OverrideAuthorizationAttribute no longer exists. So how do you set it so that a user only needs to be in the Editor role and not Editor and Shopper role to access EditPage in MVC6?
I found this blog post from Filip W that explains how write your own solution using the filter providers.
However the framework has changed a lot and his solution has to be updated to take into account the changes in the framework up to beta8.
First you will create a new attribute where you can specify the type of the filter that you want to override. (In your case this would be the AuthorizeFilter)
public class OverrideFilter : ActionFilterAttribute
{
public Type Type { get; set; }
}
If you want. you could create more specific filters like:
public class OverrideAuthorization : OverrideFilter
{
public OverrideAuthorization()
{
this.Type = typeof(AuthorizeFilter);
}
}
Then you need to create a new IFilterProvider.
This filter provider will be executed after the default providers in
the framework have run.
You can inspect the
FilterProviderContext.Results and search for your OverrideFilter
If found, you can then inspect the rest of the filters, and delete
any filter that is of the filtered type and a lower scope
For example create a new OverrideFriendlyFilterProvider following this idea:
public class OverrideFriendlyFilterProvider : IFilterProvider
{
//all framework providers have negative orders, so ours will come later
public int Order => 1;
public void OnProvidersExecuting(FilterProviderContext context)
{
if (context.ActionContext.ActionDescriptor.FilterDescriptors != null)
{
//Does the action have any OverrideFilter?
var overrideFilters = context.Results.Where(filterItem => filterItem.Filter is OverrideFilter).ToArray();
foreach (var overrideFilter in overrideFilters)
{
context.Results.RemoveAll(filterItem =>
//Remove any filter for the type indicated in the OverrideFilter attribute
filterItem.Descriptor.Filter.GetType() == ((OverrideFilter)overrideFilter.Filter).Type &&
//Remove filters with lower scope (ie controller) than the override filter (i.e. action method)
filterItem.Descriptor.Scope < overrideFilter.Descriptor.Scope);
}
}
}
public void OnProvidersExecuted(FilterProviderContext context)
{
}
}
You need to register it on the ConfigureServices of your startup class:
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IFilterProvider, OverrideFriendlyFilterProvider>());
With all this pieces you will be able to override the authorization filter (or any other filter).
For example in the default HomeController of a new mvc application, any logged in user will be able to access the Home action, but only the ones with the admin role will be able to access the About action:
[Authorize]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize(Roles = "admin"), OverrideAuthorization]
public IActionResult About()
{
return View();
}
I think it would be better to use the new policy based authorization approach instead of using roles directly.
There is not a lot of documentation yet about policy based authorization but this article is a good start
I have got a task to make Custom BreadCrumbs which will keep historical info of where the user got to the current point in an application. I have made a class for this purpose which Inherits from ActionFilterAttribute class and decorated the actions of the controller with that class, then in my OnActionExecuting override i save the historical view in a cookie and get the information out of the cookie in my customBreadCrumb partial view.
Moreover, my customBreadCrumb partial view is inside of another partail view which in turn is in the Layout page.
The problem is that every time the code executes it returns old cookie value. To be more specific it returns previous value in current view.
Here is my sample code.
public class CustomBreadCrumb : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
WriteCookie(string cValue);
base.OnActionExecuting(filterContext);
}
private void WriteCookie(string CookieValue)
{
HttpCookie breadCrumbCookie = new HttpCookie("MyCookie");
breadCrumbCookie.Value = CookieValue;
breadCrumbCookie.Expires = DateTime.Now.AddYears(1);
HttpContext.Current.Response.Cookies.Add(breadCrumbCookie);
}
public static string GetBreadCrumb()
{
if (HttpContext.Current.Request.Cookies["MyCookie"] != null)
{
return HttpContext.Current.Request.Cookies["MyCookie"].Value;
}
return string.Empty;
}
}
Here is my View
#{
#CustomBreadCrumb.GetBreadCrumbs()
}
Here is my sample controller
public class HomeController : BaseController
{
[CustomBreadCrumb]
public ActionResult Index(int ID = 1)
{
//My logic
}
}
I am working on a project where we are using Amazon SimpleDB as a data storage. In this application user can create roles at run time. While creating role, user can give Read/Write/Update permission for specific feature.
The code I have tried;
using System;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MyAuthorization : ActionFilterAttribute
{
public string Model { get; set; }
public string Action { get; set; }
public override void OnActionExecuting(HttpActionContext filterContext)
{
//My code will go here
base.OnActionExecuting(filterContext);
}
}
In Web API controller I have written as;
// GET api/values
[MyAuthorization(Action = "Edit", Model = "Rack")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
Now in OnActionExecuting, I want to fetch Action and Model attributes which I have specified over action method in APIController.
How to handle it through code, since role names and rights are not known at design time.
I assume that each feature you will be implementing in a certain controller and each action method designates the type of operation you are performing (ex Read, Write etc).
If my assumption is correct, you may have to first extend the AuthorzeAttribute ASP.NET MVC framework like below.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public string Operation;
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
//Get the User Id from the session
// Get Role associated with the user (probably from database)
// get the permission associated with the role (like Read, write etc)
// Let assume the retrieved operations are in the form of list of strings
List<string> retrievedOperations =RetrieveRoleOperations(userId)
if (!retrievedOperations.Contains(Operation)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
After creating this class, you have to specify the extended authorize filter in required action methods like below.
Class MyFeatureController:Controller
{
[MyCustomAuthorize(Operation="Read")]
public ActionResult MyReadMethod()
{
//
}
}
I hope this will solve your problem.