ASP.MVC: Separation of concerns and custom attribute class w/ authorization - c#

I'm working on separating concerns in MVC. Currently everything is mixed up (Library functions, extensions, custom filter attributes, data access level), but I want to have 2 separate projects for those (MySolution.Lib and MySolution.DBModel). I want to separate my custom filter attribute to Lib project. DBModel is already referencing Lib, so I've done everything beside this one. What's the best way to implement this? Is it possible to pass to the attribute values in another custom property and work only on text inside Attribute implementation. So that the Attribute would be used something like this:
[AuthorizeUser(AccessLevel = "Admin", UserRights = DBModel.User.GetUserRights(HttpContext.User.Identity.Name)]
public ActionResult MyAction()
Here's the current implementation
public class AuthorizeUserAttribute : AuthorizeAttribute
{
// Custom property
public string AccessLevel { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized)
{
return false;
}
List<string> accessLevels = AccessLevel.Split(new[] {','}).ToList();
//this is taken from DB by GetUserRights() method from MySecurity static class
var privilegeLevels = MySecurity.GetUserRights(httpContext.User.Identity.Name);
return accessLevels.Any(privilegeLevels.Contains)
|| privilegeLevels.Contains("Admin") || privilegeLevels.Contains("Super User");
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var helper = new UrlHelper(filterContext.RequestContext);
filterContext.HttpContext.Response.Redirect(helper.Action("Index", "Unauthorized"));
}
}

Related

How to pass parameter of different type to a generic ServiceFilter or ActionFilter

I am not very experienced on C# and ASP, and I am not sure I am taking the right approach to this problem.
This is a brief representation of it:
I have several endpoints related to an user but with different parameters (eg. string or UserDTO)
Here are a couple of them:
[AllowAnonymous]
[HttpPut("{username}")]
// I want to pass [FromBody]UserUpdateDTO to the filter
[ServiceFilter(typeof(UserChangeManagerFilter<UserUpdateDTO>))]
public ActionResult<InternalStatus> UpdateUser([FromBody]UserUpdateDTO userDto)
{
...
}
[AllowAnonymous]
[HttpDelete("{username}")]
// I want to pass {username} to the filter
[ServiceFilter(typeof(UserChangeManagerFilter<string>))]
public ActionResult<InternalStatus> DeleteUser(string username)
{
...
}
Now, I need to execute some code after each action is executed. For this, I have created a generic filter
My problem is that the filter needs to know the user details, so the PUT endpoint has to pass the UserDTO element to the filter, and the DELETE endpoint has to pass the username. I don't know how to do that exactly, but researching, I have seen that using a generic sercive filter was an option. So I created this one:
namespace ActionFilters.ActionFilters
{
public class UserChangeManagerFilter<T> : IActionFilter
{
private UserInfo _user;
public UserChangeManagerFilter(UserUpdateDTO userContext)
{
_user.Username = userContext.Username;
_user.Firstname = userContext.FirstName;
...
}
public UserChangeManagerFilter(string username)
{
_user.Username = username;
_user.Firstname = "";
...
}
public void OnActionExecuting(ActionExecutingContext context)
{
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do whatever I need to do here
}
}
I am adding the service in Startup.cs as below:
services.AddScoped<UserChangeManagerFilter<UserUpdateDTO>>();
services.AddScoped<UserChangeManagerFilter<string>>();
When I execute the PUT endpoint, I get
System.InvalidOperationException: No constructor for type 'ActionFilters.ActionFilters.UserChangeManagerFilter`1[xxx.Services.UserUpdateDTO]' can be instantiated using services from the service container and default values.
When I execute the DELETE endpoint I get
System.InvalidOperationException: No constructor for type 'ActionFilters.ActionFilters.UserChangeManagerFilter`1[System.String]' can be instantiated using services from the service container and default values.
I know this is not a direct answer to your question but how about using a non generic filter and then using the OnActionExecuting to inspect the model being passed and extracting the data as needed depending on the type of object received:
public void OnActionExecuting(ActionExecutingContext context)
{
var userDto = actionContext.ActionArguments.Values.OfType<UserUpdateDTO>().Single();
if(userDto !=null)
{
_user.Username = userContext.Username;
_user.Firstname = userContext.FirstName;
...
}else
{
//look for the string value
}
}

Custom Authorize attribute role is not working web api authentication

I'm facing an issue while working with web api azure ad authentication
I'm having controller like below, the one which having giving proper response, But the one which having customauthorization roles throwing error as "Authentication has been for this request".
[RoutePrefix("api/hospitals")]
public class hospitals : ApiController
{
[Route("GetAll")]
[HttpGet]
[Authorize]
public async Task<IEnumerable<Hospitals>> GetAll()
{
// return ok;
}
[Route("Getbeds")]
[HttpGet]
[SmAuthorize(Constants.Roles.Admin,
Constants.Roles.HotSpitalAdmin,
Constants.Roles.QA)]
public async Task<IEnumerable<Hospitals>> Getbeds()
{
// return ok;
}
}
The Getbeds method is throwing an error as "Authorization has been request".
Please find me Custom attribute class as well
public class SmAuthorizeAttribute : AuthorizeAttribute
{
public SmAuthorizeAttribute(params string[] roles)
{
this.Roles = string.Join(",", roles.Select(s => s.Trim()).ToArray());
}
}
Can anyone help on this ?
You can refer to this SO question's answer by Derek Greer for Dot Net core, additionally I will reiterate the answer below -
The approach recommended by the ASP.Net Core team is to use the new policy design which is fully documented here. The basic idea behind the new approach is to use the new [Authorize] attribute to designate a "policy" (e.g. [Authorize( Policy = "YouNeedToBe18ToDoThis")] where the policy is registered in the application's Startup.cs to execute some block of code (i.e. ensure the user has an age claim where the age is 18 or older).
The policy design is a great addition to the framework and the ASP.Net Security Core team should be commended for its introduction. That said, it isn't well-suited for all cases. The shortcoming of this approach is that it fails to provide a convenient solution for the most common need of simply asserting that a given controller or action requires a given claim type. In the case where an application may have hundreds of discrete permissions governing CRUD operations on individual REST resources ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder", etc.), the new approach either requires repetitive one-to-one mappings between a policy name and a claim name (e.g. options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), or writing some code to perform these registrations at run time (e.g. read all claim types from a database and perform the aforementioned call in a loop). The problem with this approach for the majority of cases is that it's unnecessary overhead.
While the ASP.Net Core Security team recommends never creating your own solution, in some cases this may be the most prudent option with which to start.
The following is an implementation which uses the IAuthorizationFilter to provide a simple way to express a claim requirement for a given controller or action:
public class ClaimRequirementAttribute : TypeFilterAttribute
{
public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] {new Claim(claimType, claimValue) };
}
}
public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly Claim _claim;
public ClaimRequirementFilter(Claim claim)
{
_claim = claim;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
if (!hasClaim)
{
context.Result = new ForbidResult();
}
}
}
[Route("api/resource")]
public class MyController : Controller
{
[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
[HttpGet]
public IActionResult GetResource()
{
return Ok();
}
}
Part of this answer for .NET Framework-
Recommended Custom Attribute class:
public class CustomAuthorize : System.Web.Http.AuthorizeAttribute
{
private readonly PermissionAction[] permissionActions;
public CustomAuthorize(PermissionItem item, params PermissionAction[] permissionActions)
{
this.permissionActions = permissionActions;
}
protected override Boolean IsAuthorized(HttpActionContext actionContext)
{
var currentIdentity = actionContext.RequestContext.Principal.Identity;
if (!currentIdentity.IsAuthenticated)
return false;
var userName = currentIdentity.Name;
using (var context = new DataContext())
{
var userStore = new UserStore<AppUser>(context);
var userManager = new UserManager<AppUser>(userStore);
var user = userManager.FindByName(userName);
if (user == null)
return false;
foreach (var role in permissionActions)
if (!userManager.IsInRole(user.Id, Convert.ToString(role)))
return false;
return true;
}
}
}

Ninject to Simple Injector: Register ActionFilter with Controller Attribute (with params)

I have an WebApi application that uses Simple Injector and I'm trying to configure a particular filter with controller attribute (with parameters). I have this configuration working in another project that uses Ninject, but I don't know how to do this on Simple Injector.
public enum UserType {
Director,
Developer,
Leader
}
My controller:
[RequiresAtLeastOneOfUserTypes(UserType.Developer, UserType.Leader)]
public class MyController : Controller
{
...
}
My Attribute:
public sealed class RequiresAtLeastOneOfUserTypesAttribute : Attribute
{
public UserType[] TypesToBeVerified { get; set; }
public RequiresAtLeastOneOfUserTypesAttribute(params UserType[] typesToBeVerified)
{
TypesToBeVerified = typesToBeVerified;
}
}
My Filter:
public class RequiresAtLeastOneOfUserTypesFilter : IActionFilter
{
private readonly IUser _user;
private readonly UserType[] _typesToBeVerified;
protected RequiresAtLeastOneOfUserTypesFilter(IUser user, params UserType[] typesToBeVerified)
{
_user = user;
_typesToBeVerified = typesToBeVerified;
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
bool authorized = _user.HasAtLeastOneOfTypes(_typesToBeVerified);
if (!authorized)
{
throw new ForbiddenUserException();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// do nothing
}
}
And finally my Ninject configuration:
this.BindFilter<RequiresAtLeastOneOfUserTypesFilter>(FilterScope.Controller, 0)
.WhenControllerHas<RequiresAtLeastOneOfUserTypesAttribute>()
.WithConstructorArgumentFromControllerAttribute<RequiresAtLeastOneOfUserTypesAttribute>(
"typesToBeVerified",
attribute => attribute.typesToBeVerified);
My question is: How can I do this configuration using Simple Injector?
The Simple Injector Web API integration packages don't contain an integration feature for action filters as Ninject's integration package does. But such integration can be built in a few lines of code.
There are a few options here. The first option is to revert to resolving services directly from inside your action filter, as demonstrated inside the documentation. This approach is fine when you have a single filter class, but isn't the cleanest approach, and would force you to make changes to your already created filter attribute.
As a second option you can, therefore, create a action filter proxy class, that is able to forward the call to your real filter class, which can than be resolved by Simple Injector:
public class ActionFilterProxy<T> : IActionFilter
where T : IActionFilter
{
public ActionFilterProxy(Container container) => _container = container;
public void OnActionExecuting(ActionExecutingContext filterContext) =>
_container.GetInstance<T>().OnActionExecuting(filterContext);
public void OnActionExecuted(ActionExecutedContext filterContext) =>
_container.GetInstance<T>().OnActionExecuted(filterContext);
}
Using this proxy, you can make the following configuration:
GlobalConfiguration.Configuration.Filters.Add(
new ActionFilterProxy<RequiresAtLeastOneOfUserTypesFilter>(container));
container.Register<RequiresAtLeastOneOfUserTypesFilter>();
This still forces you to make a change to RequiresAtLeastOneOfUserTypesFilter, because Simple Injector can't provide the attribute's information (the UserType[]) to RequiresAtLeastOneOfUserTypesFilter's constructor. Instead,you can change RequiresAtLeastOneOfUserTypesFilter to the following:
public class RequiresAtLeastOneOfUserTypesFilter : IActionFilter
{
private readonly IUser _user;
public RequiresAtLeastOneOfUserTypesFilter(IUser user) => _user = user;
public void OnActionExecuting(ActionExecutingContext filterContext)
{
// Get the attribute from the controller here
var attribute = filterContext.ActionDescriptor.ControllerDescriptor
.GetCustomAttribute<RequiresAtLeastOneOfUserTypesAttribute>();
bool authorized = _user.HasAtLeastOneOfTypes(attribute.TypesToBeVerified);
if (!authorized)
{
throw new ForbiddenUserException();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
A third option to use is the one referred to in the documentation, which is described in this blog post, which discusses a model where you place your filters behind an application-specific abstraction and allow them to be Auto-Registered. It uses the a similar proxy approach. This method is useful when you have multiple/many filters that need to be applied (where their order of execution is irrelevant).

How to implement Permission Based Access Control with Asp.Net Core

I am trying to implement permission based access control with aspnet core. For dynamically managing user roles and permissions(create_product, delete_product etc.), they are stored in the database. Data Model is like http://i.stack.imgur.com/CHMPE.png
Before aspnet core (in MVC 5) i was using custom AuthorizeAttribute like below to handle the issue:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _permissionName { get; set; }
[Inject]
public IAccessControlService _accessControlService { get; set; }
public CustomAuthorizeAttribute(string permissionName = "")
{
_permissionName = permissionName;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var user = _accessControlService.GetUser();
if (PermissionName != "" && !user.HasPermission(_permissionName))
{
// set error result
filterContext.HttpContext.Response.StatusCode = 403;
return;
}
filterContext.HttpContext.Items["CUSTOM_USER"] = user;
}
}
Then i was using it in action method like below:
[HttpGet]
[CustomAuthorize(PermissionEnum.PERSON_LIST)]
public ActionResult Index(PersonListQuery query){ }
Additionally, i was using HttpContext.Items["CUSTOM_USER"] in views to show or hide html part:
#if (CurrentUser.HasPermission("<Permission Name>"))
{
}
When i decided to switch aspnet core, all my plan was failed. Because there was no virtual OnAuthorization method in the AuthorizeAttribute. I tried some ways to solve problem. Those are below:
Using new policy based authorization(i think it is not suitable for
my scenerio)
Using custom AuthorizeAttribute and AuthorizationFilter(i read this
post https://stackoverflow.com/a/35863514/5426333 but i couldn’t change it properly)
Using custom middleware(how to get AuthorizeAttribute of current
action?)
Using ActionFilter(is it correct for security purpose?)
I couldn’t decide which way is the best for my scenerio and how to implement it.
First question: Is MVC5 implementation bad practice?
Second question: Do you have any suggest to implement aspnet core?
Based on the comments, here an example on how to use the policy based authorization:
public class PermissionRequirement : IAuthorizationRequirement
{
public PermissionRequirement(PermissionEnum permission)
{
Permission = permission;
}
public PermissionEnum Permission { get; }
}
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IUserPermissionsRepository permissionRepository;
public PermissionHandler(IUserPermissionsRepository permissionRepository)
{
if(permissionRepository == null)
throw new ArgumentNullException(nameof(permissionRepository));
this.permissionRepository = permissionRepository;
}
protected override void Handle(AuthorizationContext context, PermissionRequirement requirement)
{
if(context.User == null)
{
// no user authorizedd. Alternatively call context.Fail() to ensure a failure
// as another handler for this requirement may succeed
return null;
}
bool hasPermission = permissionRepository.CheckPermissionForUser(context.User, requirement.Permission);
if (hasPermission)
{
context.Succeed(requirement);
}
}
}
And register it in your Startup class:
services.AddAuthorization(options =>
{
UserDbContext context = ...;
foreach(var permission in context.Permissions)
{
// assuming .Permission is enum
options.AddPolicy(permission.Permission.ToString(),
policy => policy.Requirements.Add(new PermissionRequirement(permission.Permission)));
}
});
// Register it as scope, because it uses Repository that probably uses dbcontext
services.AddScope<IAuthorizationHandler, PermissionHandler>();
And finally in the controller
[HttpGet]
[Authorize(Policy = PermissionEnum.PERSON_LIST.ToString())]
public ActionResult Index(PersonListQuery query)
{
...
}
The advantage of this solution is that you can also have multiple handlers for a requirement, i.e. if first one succeed the second handler can determine it's a fail and you can use it with resource based authorization with little extra effort.
The policy based approach is the preferred way to do it by the ASP.NET Core team.
From blowdart:
We don't want you writing custom authorize attributes. If you need to do that we've done something wrong. Instead you should be writing authorization requirements.
I had same requirement and i have done it as below and it works fine for me. I am using .Net Core 2.0 Webapi
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Method
, AllowMultiple = true
, Inherited = true)]
public class CheckAccessAttribute : AuthorizeAttribute, IAuthorizationFilter
{
private string[] _permission;
public CheckAccessAttribute(params string[] permission)
{
_permission = permission;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
return;
}
IRepository service =
(IRepositoryWrapper)context.HttpContext.RequestServices.GetService(typeof(IRepository));
var success = service.CheckAccess(userName, _permission.ToList());
if (!success)
{
context.Result = JsonFormatter.GetErrorJsonObject(
CommonResource.error_unauthorized,
StatusCodeEnum.Forbidden);
return;
}
return;
}
}
In Controller use it like below
[HttpPost]
[CheckAccess(Permission.CreateGroup)]
public JsonResult POST([FromBody]Group group)
{
// your code api code here.
}
For a solution that doesn't require you to add a policy for each permission see my answer for another question.
It lets you decorate your Controllers and Actions with any custom attributes you wish, and access them in your AuthorizationHandler.

ASP MVC C#: Is it possible to pass dynamic values into an attribute?

Okay I'm very new to C# and i'm trying to create a little website using ASP MVC2.
I want to create my own authorization attribute. but i need to pass some values if this is possible.
For example:
[CustomAuthorize(GroupID = Method Parameter?]
public ActionResult DoSomething(int GroupID)
{
return View("");
}
I want to authorize the access to a page. but it depends on the value passed to the controller. So the authorization depends on the groupID. Is this possible to achieve this in any way?.
Thanks in advance.
Use the value provider:
public class CustomAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
var result = filterContext.Controller.ValueProvider.GetValue("GroupId"); //groupId should be of type `ValueProviderResult`
if (result != null)
{
int groupId = int.Parse(result.AttemptedValue);
//Authorize the user using the groupId
}
}
}
This article may help you.
HTHs,
Charles
You get it from Request.Form
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//httpContext.Request.Form["groupid"]
return base.AuthorizeCore(httpContext);
}
}
You get it from Request.Form
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//httpContext.Request.Form["groupid"]
return base.AuthorizeCore(httpContext);
}
}

Categories

Resources