So I'm setting up my permission for an mvc website. And I'm doing a role based permission, having actions in a controller would require different Roles depending on the purpose of the action.
I know that the most recommended would be authorizeattribute (as i want the roles cached) but is it possible to have the same with the actionfilterattribute?
Currently I have an actionfilterattribute similar to this:
public class PermissionRequired : ActionFilterAttribute{
private readonly Role reqrole;
public PermissionRequired(Role reqRole)
{
reqrole = reqRole;
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var ctrl = (GeneralController)filterContext.Controller;
if (!ctrl.CurrentUser.InRole(reqrole)) {
//some code to redirect this to a certain page
}
base.OnActionExecuting(filterContext);
}
}
and on the GeneralController to get the current User
public class GeneralController : Controller
private User currentUser;
public User CurrentUser {
get {
if (currentUser != null)
return currentUser;
int currentUserId = Convert.ToInt32(httpContext.User.identity.Name);
if (currentUserId != 0) {
this.currentUser = Tds.Users.FirstOrDefault(u => u.Id == currentUserId)
}
return currentUser;
}
}
and on the controllers that will inherit this attribute
[PermissionRequired(Role.Moderator)]
public class SomeControllerThatNeedsPermission
{
[PermissionRequired(Role.SuperAdmin)]
public ActionResult SomeActionThatNeedsPermission()
{
}
}
so, anybody help is appreciated.. even comments or thoughts are welcome :D
Thanks much!
It seems like you are not using custom membership here. In which case doing this with a actionfilterattribute is somewhat pointless, but nonetheless do able.
This is an excellent article on the same subject - extending the AuthorizeAttribute to perform role based validation and return custom errors...
The value in doing that also only comes across (as explained in the article) when you wish to show users whats going on when the Authorization fails (the 401 is not shown it turns into a 302 internally in the mvc plumbing)
Related
In all of my asp.net mvc projects up to this point, permissions to controllers are set by using a custom class based on the [Authorize] attribute.
However, what if I wanted an administrator role who could grant access to views instead of going through the trouble of having to touch the controller to add/remove roles, re-complile, and push the changes to production. How would I go about doing this?
as discussed. Try the below.
in the controller as I'm sure you're aware.
[PermissionsFilter("CanAccessMyView")]
public ActionResult ReturnMyView ()
{
//etc..
}
Then, in your custom class
public class PermissionsFilter : AuthorizeAttribute
{
private readonly PermissionManager _permissionsManager;
public PermissionsFilter(string permissionName)
{
_permissionName = permissionName;
_permissionsManager = new PermissionServiceManager();
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!_permissionServiceManager.CanAccessPermission(_permissionName))
{
var urlHelper = new UrlHelper(filterContext.RequestContext);
var url = urlHelper.Action("Unauthorised", "Home");
filterContext.Result = new RedirectResult(url);
}
}
}
Where the permissions manager is querying the database or perhaps session info to see if the user user has access.
Hope that helps.
In my ASP.net MVC project I've got (among other roles) moderators and users. I want to give the moderators the option to "see current page as user".
My approach is to create a ActionFilterAttribute and overload OnActionExecuting & OnResultExecuted as the page is then rendered for the given user.
The first idea there was to juggle with the Roles:
OnActionExecuting {
... //various checks, if role exist, if user want to switch
var tempRoles = Roles.getRolesForUser(user);
filterContext.HttpContext.Items["tempRole"] = tempRoles;
Roles.RemoveUserFromRoles(user, tempRoles)
Roles.AddUserToRole(user, targetRole);
}
and then
OnResultExecuted {
//if switched view
{
Roles.RemoveUserFromRole(user,targetRole)
Roles.AddUserToRoles(filterContext.HttpContext.Items["tempRole"])
}
This works, but in a worst case scenario the roles are gone, so i prefer to not touch them...
My second idea was to create a dummy user add him to the userroles sign the moderator into this account with FormsAuthentication.SetAuthCookie(dummyUser, true) and revert everything in the OnResultExecuted, so in a worst case scenario the user is in the dummyRole (where he can logout) and the dummyUser is in the Database.
After debugging and researching I realised that SetAuthCookie requires a Redirect to come into effect - so it doesn't work this way.
The questions:
Is there a way to force SetAuthCookie to come into affect without a redirect
Any other suggestion/approaches how to accomplish this "see page as other user"?
If my first idea is the only solution, how do i make it foolproof?
Ahoi Christian,
you could decorate the class SqlRoleProvider and add it to the role manager.
See Sample Role-Provider Implementation:
http://msdn.microsoft.com/en-us/library/tksy7hd7%28v=vs.100%29.aspx
The decorated SqlRoleProvider could overwrite the IsUserInRole method and thereby implement impersonation functionality.
edit: I have added the code below:
public class MyRoleProvider : SqlRoleProvider
{
private static ConcurrentDictionary<string, string> impersonationList;
public MyRoleProvider() : base()
{
impersonationList = new ConcurrentDictionary<string, string>();
}
public static void startImpersonate(string username, string rolename)
{
impersonationList.TryAdd(username,rolename);
}
public override string[] GetRolesForUser(string username) {
if (impersonationList.ContainsKey(username))
return new string[] { impersonationList[username] };
else
return base.GetRolesForUser(username);
}
public static void stopImpersonate(string username)
{
string rolename;
impersonationList.TryRemove(username, out rolename);
}
}
I am trying to make it easier to the developer,
Because there are multiple options of Authentication, and not all pages require authentication or full authentication.
In WebForms I can have a generic page,
Now each page inherits GenericPage, and only need to implemenet AuthorizationType (see code below)
Can I do anything similiar with Razor?
Or maybe I should somthing entirely different...
Here is the code in WebForms:
public class GenericPage : Page
{
public enum AuthorizationType
{
Users = 1,
AuthUsers = 2,
Admins = 4
}
public virtual bool IsAuth()
{
return Authenticator.IsAuth();
}
public virtual bool IsAdmin()
{
AuthUser authUser = Authenticator.GetAuthenticatedUser();
return (authUser != null && authUser.IsAdmin)
}
protected abstract AuthorizationType Authorization
{
get;
}
protected virtual string OnAuthorizationFailedUrl
{
get
{
return HomePageUrl;
}
}
protected void CheckAuthentication()
{
if (!IsUserAuthroized())
Response.Redirect(OnAuthorizationFailedUrl);
}
protected bool IsUserAuthroized()
{
AuthorizationType authorization = Authorization;
return (Authorization.Contains(AuthorizationType.Users) ||
(Authorization.Contains(AuthorizationType.AuthUsers) && IsAuth()) ||
(Authorization.Contains(AuthorizationType.Admins) && IsAdmin()));
}
override OnPageLoad()
{
CheckAuthentication();
}
}
Thanks in advance.
In your scenario, it can be implemented as 3 types of Roles. When you authenticate users, assign the roles correctly.
Then in your controller, or controller action methods, you can put Authorize attribute
[Authorize(Roles = "Users")]
or multiple roles
[Authorize(Roles = "Users,Admins")]
If you want to filter globally, you can create a BaseController and give the Authorize attribute to the controller. Then all of your controllers need to implement the BaseController.
Here is my blog post about how to implement Authentication with customized identity.
https://kevww.wordpress.com/2011/11/18/implement-formsauthentication-with-custom-identity-in-asp-net-mvc/
Hope this may help you
You can add this type of authorization as a global filter.
Filtering in asp.net mvc
http://bradwilson.typepad.com/blog/2010/07/service-location-pt4-filters.html
I'm currently writing an Admin MVC 3 site, and each user only has access to certain parts of the site.
The areas of my site are the same as the user Roles, so what I would like to do is the put the AuthorizeAttribute on each area, using the area's name as the parameter in the Role.
So far I've got this to work when I'm hard coding the checking of each area, but I would like to just loop through all areas and apply the Authorize filter.
(i'm using this as my custom FilterProvider - http://www.dotnetcurry.com/ShowArticle.aspx?ID=578)
My code so far ("Gcm" is one of my areas, and is also a Role) :
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
// for all controllers, run AdminAuthorizeAttribute to make sure they're at least logged in
filters.Add(ObjectFactory.GetInstance<AdminAuthorizeAttribute>());
AdminAuthorizeAttribute gcmAuthroizeAttribute = ObjectFactory.GetInstance<AdminAuthorizeAttribute>();
gcmAuthroizeAttribute.Roles = "Gcm";
var provider = new FilterProvider();
provider.Add(
x =>
x.RouteData.DataTokens["area"] != null && x.RouteData.DataTokens["area"].ToString() == "Gcm"
? gcmAuthroizeAttribute
: null);
FilterProviders.Providers.Add(provider);
}
Does anyone know how to get all the areas of my application, so I can just loop through them, rather than hard coding each area?
Or if anyone has a better idea of how to Authorize per area, that would be appreciated to.
Thanks for your help
Saan
You could you make a base controller for each area, and put the authorize attribute over the base class. That way you can pass the area parameter in for each area's base controller.
Here is an example of a Authorize Attribute override i have created. I needed my authorize function to support to types of member ship so you might not want to get too into the inner workings of the functions, but AuthorizeCore is where the main logic occures. In my case i am checking it against a entity datacontext.
Usage:
[AjaxAuthorize(AjaxRole = "Administrators")]
public JsonResult SaveAdministrativeUser(v.... )
Code:
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
private class HttpAuthorizeFailedResult : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
// Set the response code to 403. Membership.Provider.Name == "UnitArchiveMembershipProvider"
context.HttpContext.Response.StatusCode = context.HttpContext. User.Identity is WindowsIdentity ? 401 : 403;
}
}
public string AjaxRole { get; set;}
public AjaxAuthorizeAttribute()
{
AjaxRole = "Users";
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (string.IsNullOrEmpty(MvcApplication.Config.DBSettings.Database))
{
return true;
}
//When authorize parameter is set to false, not authorization should be performed.
UnitArchiveData db = DataContextFactory.GetWebRequestScopedDataContext<UnitArchiveData>(MvcApplication.Config.DBSettings.GetConnectionString());
if (httpContext.User.Identity.IsAuthenticated)
{
login_data user = db.login_datas.Where(n => n.EmailAddress == httpContext.User.Identity.Name).FirstOrDefault();
if (user != null)
{
return user.cd_login_role.RoleName == "Administrators" || user.cd_login_role.RoleName == AjaxRole;
}
}
return false;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
//Ajax request doesn't return to login page, it just returns 403 error.
filterContext.Result = new HttpAuthorizeFailedResult();
}
else
base.HandleUnauthorizedRequest(filterContext);
}
}
When I was investigating a separate issue, I came across How to pass parameters to a custom ActionFilter in ASP.NET MVC 2?
That attribute example can be altered to check for the current Controller's area.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
RouteData routeData = filterContext.RouteData;
// check if user is allowed on this page
if (SessionFactory.GetSession().Contains(SessionKey.User))
{
User user = (User)SessionFactory.GetSession().Get(SessionKey.User);
string thisArea = routeData.DataTokens["area"].ToString();
// if the user doesn't have access to this area
if (!user.IsInRole(thisArea))
{
HandleUnauthorizedRequest(filterContext);
}
}
// do normal OnAuthorization checks too
base.OnAuthorization(filterContext);
}
}
I then apply my custom authorize attribute to all controllers like this in Global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
// for all controllers, run CustomAuthorizeAttribute to make sure they're at logged in and have access to area
filters.Add(ObjectFactory.GetInstance<CustomAuthorizeAttribute>());
}
Thanks for all who replied
Saan
If I want only administrator to access the action called "ManagerUser", I know I can do this:
[Authorize( Roles = Constants.ROLES_ADMINISTRATOR )]
public ActionResult ManageUser( string id )
{
}
What if I want to give everyone access except to administrator? I do not want to write all roles up there on function :|.
Any recommendations/way outs?
You can create your own custom Authorize attribute, something like "AuthorizeAllExceptAdmin." Within that class you would simply need to check whether or not the current user was an admin, and if they were reject it, otherwise accept it.
Here's a good tutorial, but you'll probably end up with something like:
public class AuthorizeAllExceptAdmin : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return !httpContext.User.IsInRole(Constants.ROLES_ADMINISTRATOR);
}
}
Then your controller method becomes:
[AuthorizeAllExceptAdmin]
public ActionResult SomethingOnlyNonAdminsCanDo()
{
}
Here's an example of the custom attribute that takes in roles to deny.
public class DoNotAuthorize : AuthorizeAttribute
{
private IEnumerable<string> _rolesToReject;
public DoNotAuthorize(IEnumerable<string> rolesToReject)
{
_rolesToReject = rolesToReject;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
foreach (var role in _rolesToReject)
{
if (httpContext.User.IsInRole(role))
return false;
}
return true;
}
}
Then your controller method becomes:
[DoNotAuthorize(new [] {Constants.ROLES_ADMINISTRATOR})]
public ActionResult SomethingOnlyNonAdminsCanDo()
{
}
I would put some thought into it before choosing one of the above options. If you think you'll have several methods (or entire controllers) with similar authorization requirements (i.e, several actions an admin can not perform) then I would stick with the non-parameterized custom attribute. This way, you can evolve them all together (by only changing the custom attribute) later on. For example, maybe later on you want admins to be able to go into a special mode where they can perform these actions.
Alternatively, if the autorization is more varied amongst the actions, then using the parameterized list makes sense, since they'll evolve relatively independently.
Besides creating a custom AuthorizeAttribute, suggested by manu, you could use PrincipalPermission, with a Deny-SecurityAction:
[PrincipalPermission(SecurityAction.Deny, Role="Administrator")]
In my app I don't use roles so I have to query the database to determine whether the user has access or not. The benefits of the code below is that you can redirect the user to a certain action very easily. I explained the code in my blog post at http://blog.athe.la/2009/12/implementing-permission-via-windows-authentication-in-asp-mvc-using-action-filters/
public class DatabaseRepository()
{
private readonly DatabaseDataContext db = new DatabaseDataContext();
public bool UserHasPermission(string userLogon) {
return (from permission this.db.Permissions
where permission.HasPermissionSw == true
select permission).Contains(userLogon);
}
}
public class UserHasPermission: ActionFilterAttribute
{
private readonly DatabaseRepository databaseRepository = new DatabaseRepository();
private readonly string redirectAction;
public UserHasPermission(string redirectTo)
{
this.redirectAction = redirectTo;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string userLogon = filterContext.HttpContext.User.Identity.Name;
if (!this.databaseRepository.UserHasPermission(userLogon))
{
string routeController = filterContext.Controller.ControllerContext.RouteData.Values["controller"];
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = routeController, action = this.redirectAction }));
}
}
}
Your controller would then look something like this:
[UserHasPermission("NoAccess")]
public ActionResult SecretArea()
{
// run all the logic
return View();
}
public ActionResult NoAccess()
{
return View();
}