I have the following requirement to implement the Access Control list
public class SecurityObject{
public string Key{get;set;}
public string DisplayName{get;set;}
public bool isAllowed{get;set;}
}
public class Role{
List<SecurityObject> AccessibleObjects{get;set;}
}
Currently I use forms authentication for basic authorization. Below is my code
Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
public override void Init()
{
this.PostAuthenticateRequest += new
EventHandler(MvcApplication_PostAuthenticateRequest);
base.Init();
}
void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
HttpCookie authCookie =
HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
string encTicket = authCookie.Value;
if (!String.IsNullOrEmpty(encTicket))
{
FormsAuthenticationTicket ticket =
FormsAuthentication.Decrypt(encTicket);
string[] userData = ticket.UserData.Split(new string[] { "___" },
StringSplitOptions.None);
string[] roles = null;
if (userData.Length > 1)
{
roles = userData[1].Split(',');
}
MyCustomIdentity identity = new MyCustomIdentity(ticket);
GenericPrincipal principle = new GenericPrincipal(identity, roles);
HttpContext.Current.User = principle;
}
}
}}
My current controller class
public class AdminController : Controller
{
[HttpPost, Authorize, ValidateAntiForgeryToken]
public ActionResult SaveUser(UserDetailViewModel viewModel)
{
}
}
My Target controller class
public class AdminController : Controller
{
[HttpPost, Authorize(ACLKey="USR_SAVE"), ValidateAntiForgeryToken]
public ActionResult SaveUser(UserDetailViewModel viewModel)
{
}
}
I want my action method to be decorated with ACLKey and I would like to check whether the User Role has the given key and based on that I need to execute or return HttpUnauthorizedResult page, even for Ajax requests from jQuery.
I referred many like Customizing authorization in ASP.NET MVC But i didnt find a way to execute both forms authentication and my custom ACLKey check.
How do i parse the value USR_SAVE and process custom authentication using CustomAuthorizeFilter?
You can try like this
public class FeatureAuthenticationAttribute : FilterAttribute, IAuthorizationFilter
{
public string AllowFeature { get; set; }
public void OnAuthorization(AuthorizationContext filterContext)
{
var filterAttribute = filterContext.ActionDescriptor.GetFilterAttributes(true)
.Where(a => a.GetType() ==
typeof(FeatureAuthenticationAttribute));
if (filterAttribute != null)
{
foreach (FeatureAuthenticationAttribute attr in filterAttribute)
{
AllowFeature = attr.AllowFeature;
}
List<Role> roles =
((User)filterContext.HttpContext.Session["CurrentUser"]).Roles;
bool allowed = SecurityHelper.IsAccessible(AllowFeature, roles);
if (!allowed)
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
}
In you action method
[FeatureAuthentication(AllowFeature="USR_SAVE")]
public ActionResult Index()
{
}
Hope this will help you!
You can use a filter attribute:
public class ACLCheckAttribute : FilterAttribute, IActionFilter
In OnActionExecuting, you can grab USR_SAVE. Without knowing where it comes from, I would assume that it comes from:
The Form: you can grab any form values from the context passed into ONActionExecuting, by navigating to the HttpContext.Request.Form collection
Session, etc.: HttpContext would also have these.
The action method: From an attribute, using the context passed in for the action, it has a list of ActionParameters that can be accessed like a dictionary, allowing you to check and extract your value
If somewhere else, please comment where. You can apply this attribute to a controller or method, or globally set it by adding it to the globalfilters collection (GlobalFilters.Filters.Add()), or in the FilterConfig file in the App_Start folder.
Related
I have a C# MVC Framework being passed objects by an Angular UI. On the C# side I am trying to check if the user is allowed to create the object by checking the passed in object properties. Is there a cleaner way to do this- like not having to call identity every time? Right now I have:
Object validating code from my C# controller:
[HttpGet]
public ActionResult<AppUser> UpdateUser([FromBody]AppUser au)
var identity = (ClaimsIdentity)User.Identity;
var auclaims = new ApplicationUser(identity);
if (!auclaims.CanModifyUser(au))
return StatusCode(401, "invalid permissions");
.....
}
you can create new class :
public class CheckPermission : ActionFilterAttribute {
public string ArgName { get; }
public CheckPermission(string argName)
{
this.ArgName = argName;
}
public override void OnActionExecuting(ActionExecutingContext context) {
var controller = context.Controller as Controller;
var identity = (ClaimsIdentity)controller.User.Identity;
var au = context.ActionArguments[ArgName] as AppUser;
var auclaims = new ApplicationUser(identity);
if (!auclaims.CanModifyUser(au))
context.Result = controller.StatusCode(401, "You do not have permission to add a user to this company");
base.OnActionExecuting(context);
}
}
Then put this attribute upon you action:
[HttpGet]
[CheckPermission("au")]
public ActionResult<AppUser> UpdateUser([FromBody]AppUser au)
{
// focus on your business logic
}
you can reuse this attribute for any action to check user permission.
This is to apply the concept of AOP.
Refer to https://msdn.microsoft.com/en-us/magazine/dn574804.aspx
I've tried creating an easy filter to see if the user is in a role called "System Administrator", basically short hand for having to do [Authorize(Roles = "System Administrator")]. I thought it would be fairly simple, but I'm also fairly new to MVC so perhaps I'm overlooking something.
Here's my code:
using System.Web.Mvc;
namespace site_redesign_web.Filters
{
public class SystemAdminFilter : ActionFilterAttribute
{
string SysAdminRole = "System Administrator";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.RequestContext.HttpContext.User != null)
{
var userSysAdmin = filterContext.RequestContext.HttpContext.User.IsInRole(SysAdminRole) == true;
filterContext.ActionParameters["IsSysAdmin"] = userSysAdmin;
}
}
}
}
Can some one suggest where am I going wrong? A huge plus would be if the person isn't a System Administrator it would direct them to Home/NoPermissions.
Thank you!
Updated: Fixing all issues.
AJ. Here you go...
Finally fixed the problem
using ActionFilterAttribute
using System.Web.Mvc;
namespace site_redesign_web.Filters
{
public class SystemAdminFilter : ActionFilterAttribute
{
string SysAdminRole = "System Administrator";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.RequestContext.HttpContext.User != null)
{
var userSysAdmin = filterContext.RequestContext.HttpContext.User.IsInRole(SysAdminRole) == true;
if(!userSysAdmin)
{
filterContext.Result = new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary{
{"controller", "Home"},
{"action", "Index"}
});
}
}
}
}
}
and your Controller should be
[SystemAdminFilter] // at controller level
public SomeController : Controller
{
}
or you can also use it for a particular Action by annotating like this
public SomeController : Controller
{
[SystemAdminFilter] // at Action level
public ActionResult SomeAction()
{
// perform your actions
}
It will work because I manually passed in the User with his role in the Application_AuthorizeRequest in Global.asax
protected void Application_AuthorizeRequest(Object sender, EventArgs e)
{
FormsAuthenticationTicket formsAuthenticationTicket = new FormsAuthenticationTicket("Aravind", true, 30);
FormsIdentity formsIdentityId = new FormsIdentity(formsAuthenticationTicket);
GenericPrincipal genericPrincipal = new GenericPrincipal(formsIdentityId, new string[] { "SystemUser" }); //TEST with this redirected to Home Index place
HttpContext.Current.User = genericPrincipal ;
}
The next test I made was with this
GenericPrincipal genericPrincipal = new GenericPrincipal(formsIdentityId, new string[] { "System Administrator" }); //TEST with this did not perform an action
Since you are dealing with authorization, I would extend AuthorizeAttribute instead of ActionFilterAttribute which is mode general. You need to override only one method - HandleUnauthorizedRequest which is executed when authorization fails. Default implementation of AuthorizeAttribute already handles role based authorization for you.
public class SystemAdminAttribute : AuthorizeAttribute
{
private const string SysAdminRole = "System Administrator";
public SystemAdminFilter()
{
//this defines the role that will be used to authorize the user:
this.Roles = SysAdminRole;
}
//if user is not authorized redirect to "Home/NoPermissions" page
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if(!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
base.HandleUnauthorizedRequest(filterContext);
}
else
{
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "Home", action = "NoPermissions" }));
}
}
}
Once your attribute is implemented, decorate corresponding Controllers or Actions with it:
[SystemAdmin]
public SysAdminController : Controller
{
}
Hello I have a web api controller inside a mvc web site.
I'm trying to allow access to the controller using 2 rules:
User is admin or the request came from local computer;
I'm new to AuthorizationFilterAttribute but I tried to write one that limit access
to local request only:
public class WebApiLocalRequestAuthorizationFilter : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw new ArgumentNullException("httpContext");
}
if (actionContext.Request.IsLocal())
{
return;
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Content = new StringContent("Username and password are missings or invalid");
}
}
Then I decorated my controller with 2 attributes as
[Authorize(Roles = "Admin")]
[WebApiLocalRequestAuthorizationFilter]
public class ContactController : ApiController
{
public ContactModel Get(int id)
{
ContactsService contactsService = new ContactsService();
return contactsService.GetContactById(id).Map<ContactModel>();
}
}
But as I suspected , now, in order to access the controller I need to be admin and the request should be made from localhost. How can I do it?
Kind regards,
Tal Humy
One solution is to create a class that inherits from AuthorizeAttribute
e.g. something like this
public class MyAuthorizeAttribute: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool accessAllowed = false;
bool isInGroup = false;
List<string> roleValues = Roles.Split(',').Select(rValue => rValue.Trim().ToUpper()).ToList();
foreach (string role in roleValues)
{
isInGroup = IdentityExtensions.UserHasRole(httpContext.User.Identity, role);
if (isInGroup)
{
accessAllowed = true;
break;
}
}
//add any other validation here
//if (actionContext.Request.IsLocal()) accessAllowed = true;
if (!accessAllowed)
{
//do some logging
}
return accessAllowed;
}
...
}
Then you can use it like so:
[MyAuthorizeAttribute(Roles = "Support,Admin")]
In the above code, IdentityExtensions checks for, and caches, ActiveDirectory roles which also allows us to fake the current user having roles by changing the cache.
I have the following BaseApiController:
public class BaseApiController : ApiController
{
public readonly Current _current { get; private set; }
}
All my ApiControllers inherit from this one.
I need to do a validation in all of my methods inside my ApiControllers, that checks if the userId passed match the current HttpContext.Current.User.Identity.
[Route("{userId}/cars")]
public HttpResponseMessage GetCars(int userId)
{
if (userId != _current.UserId)
{
return Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Unauthorized");
}
}
Is there anyway to do it on the BaseApiController, so that I can avoid doing this validation on all of the endpoints that receive the userId as argument?
You can create custom validation attribute based on ActionFilterAttribute to achieve what you need.
For your case it can look like this:
public class UserAccessCheckAttribute: ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var controller = actionContext.ControllerContext.Controller as BaseApiController;
object requestUserIdObj;
if (controller != null && actionContext.ActionArguments.TryGetValue("userId", out requestUserIdObj))
{
var userId = (int) requestUserIdObj;
if (userId != controller._current.UserId)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Unauthorized");
}
}
}
}
After that you can decorate either controllers or specific actions where you need to perform user id check with this attribute:
[Authorize]
[RoutePrefix("api/Account")]
[UserAccessCheck] //check user id for all actions in controller
public class AccountController : BaseApiController
{
//....
}
public class ValuesController : BaseApiController
{
//....
[UserAccessCheck] //check user id for specific action only
public IEnumerable<string> Get()
{
//...
}
}
After that no additional code in your actions required
I'm using an ActionFilterAttribute to do custom authentication logic. The Attribute will only be used on a derived Controller class that contains my authentication logic.
Here's my Controller, derived from my custom controller class, and a sample attribute:
public class MyController : CustomControllerBase
{
[CustomAuthorize(UserType = UserTypes.Admin)]
public ActionResult DoSomethingSecure()
{
return View();
}
}
Here's an example of my ActionFilterAttribute:
public class CustomAuthorizeAttribute : ActionFilterAttribute
{
public MyUserTypes UserType { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
myUser user = ((CustomControllerBase)filterContext.Controller).User;
if(!user.isAuthenticated)
{
filterContext.RequestContext.HttpContext.Response.StatusCode = 401;
}
}
}
Works great.
Here's the question: Can I demand that this attribute ONLY be used on Actions in my custom controller type?
You can put the ActionFilter on the class itself. All actions in the class will realize the ActionFilter.
[CustomAuthorize]
public class AuthorizedControllerBase : CustomControllerBase
{
}
public class OpenAccessControllerBase : CustomControllerBase
{
}
public class MyRealController : AuthorizedControllerBase
{
// GET: /myrealcontroller/index
public ActionResult Index()
{
return View();
}
}
Based on the comments and the constraints of my system, I took a hybrid approach. Basically, if the request comes through via a cached route or the "User" is not set for any reason, authentication fails in the proper way.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private MyUser User { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
//Lazy loads the user in the controller.
User = ((MyControllerBase)filterContext.Controller).User;
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isAuthorized = false;
string retLink = httpContext.Request.Url.AbsolutePath;
if(User != null)
{
isAuthorized = User.IsValidated;
}
if (!isAuthorized)
{
//If the current request is coming in via an AJAX call,
//simply return a basic 401 status code, otherwise,
//redirect to the login page.
if (httpContext.Request.IsAjaxRequest())
{
httpContext.Response.StatusCode = 401;
}
else
{
httpContext.Response.Redirect("/login?retlink=" + retLink);
}
}
return isAuthorized;
}
}