In my MVC application I have a few different roles: Admin, General User, etc., etc.
I know that I can apply security to my Controllers via the Authorize attribute:
[Authorize(Roles="Admin")]
public ActionResult Create()
{
return View();
}
But I also need to apply some security to the Views to not display certain sections of the View to certain roles:
#if( User.IsInRole("Admin") )
{
#Html.ActionLink("Create", "Create")
}
Is it better to do it the above way, or handle this sort of security in a ViewModel:
public ActionResult Index()
{
var model = new IndexViewModel();
model.CanCreate = User.IsInRole("Admin");
return View(model);
}
View:
#( Model.CanCreate )
{
#Html.ActionLink("Create", "Create")
}
Does the second method have any benefits compared to the first or is it just a preference thing?
The second way is more preferred, as your business logic will stay at model level.
In your example, business logic is very simple. However, imagine that requirements have changed and now not only Admins can create content, but also General Users that signed up more than 1 month ago. With business logic in view you'd have to update all your views.
One way I have done this before is creating an action filter that inherits from the AuthorizeAttribute. The filter can be called something like DisplayIfAuthorizedAttribute, and in addition to the standard AuthorizeAttribute properties, has a property called ViewNameIfNotAuthorized.
The attribute calls the base method to do authorization, and if it fails, returns the ViewNameIfNotAuthorized view. Otherwise, it allows the action method to proceed normally.
You would then render these partial views via action methods, and call the action methods through Html.RenderAction or Html.Action in your parent view. Those action methods would be decorated with the attribute.
You now have a standardized way to do this and no authorization code polluting the internals of your action methods.
This is what the filter would look like:
public class DisplayIfAuthorizedAttribute : System.Web.Mvc.AuthorizeAttribute
{
private string _ViewNameIfNotAuthorized;
public DisplayIfAuthorizedAttribute(string viewNameIfNotAuthorized = null)
{
_ViewNameIfNotAuthorized = viewNameIfNotAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
bool isAuthorized = base.AuthorizeCore(filterContext.HttpContext);
if (!isAuthorized)
{
filterContext.Result = GetFailedResult();
}
}
private ActionResult GetFailedResult()
{
if (!String.IsNullOrEmpty(_ViewNameIfNotAuthorized))
{
return new ViewResult { ViewName = _ViewNameIfNotAuthorized };
}
else
return new EmptyResult();
}
}
Your action method would be decorate as:
[DisplayIfAuthorized("EmptyView", Roles="Admin")]
public ViewResult CreateLink()
{
return View("CreateLink");
}
You may need both...
Note that the 2nd one alone would not be secure, a user might be able to construct the URL for the actionlink in the browsers addressbar. So you absolutely need the attribute for security.
The second one is more a matter of user-friendliness or UI design. Maybe you want the user to be able to click Create and then have a choice to login differently.
Check the authorization in your controller and prepare the Viewmodel for the view according to your role's rules.
The views are used to simply show data. So, imo, they don't have to do roles check etc..
So prepare the ViewModel with the data it should have and let the View only render it. (the boolean property you're using it's enough imo)
Related
I have a hidden field that renders an integer siteId defined in my viewbag
Sometimes this is not present im assuming it may be related to sessions in MVC
When this happens how can I redirect to my login page? At present this is coming up with a cast error in my cshtml page
If the error happened in my controller it’s easy but I don’t know how to deal with errors in cshtml files
My cshtml has this
<div>
#(Html.Hidden("SiteID", (int) ViewBag.SiteID))
</div>
It appears as though if my user stays on the site for a while then tries to refresh or do something else I get cannot convert null to 'int' because it is a non nullable type
Paul
As others have commented, this is not really the responsibility of the View to manage. You should have access to all the variables whilst still in your Controller level code, and you should handle it accordingly.
One way to do this would be with a Filter. In the OnActionExecuted method you could check to see if the value is populated and then redirect to the login page. Maybe something like this:
public class SiteIdFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
bool shouldRedirect = SomeMethodToCheckViewBag();
if (shouldRedirect)
{
filterContext.Result = new new RedirectToRouteResult("SystemLogin", routeValues);
}
}
}
This answer Checking to see if ViewBag has a property or not, to conditionally inject JavaScript has and extension method for checking the view bag that looks promising
You could use an Actionfilter before you get to an action or after you process an action and inspect the value of the viewbag and if it's not what you want then redirect from there.
Something like this:
public class ViewBagValuePresentAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var s = filterContext.Controller.ViewBag.SiteID;
if (s == null)
{
UrlHelper helper = new UrlHelper(filterContext.RequestContext);
filterContext.Result = new RedirectResult(helper.Action("login", "Account"));
}
base.OnActionExecuted(filterContext);
}
You then just put the filter attribute over the Action or controller you want it to be active on like:
[ViewBagValuePresent]
public class EditController : Controller
{}
You can read a bit more about action filters here
I work with asp.net c# mvc framework. I need a way to 'turn-off' my web app for all users except administrator (i. e. all pages should return to something like "The application is closed" for all the roles except Admin).
I already create a button in order to save the status of the web app (ON/OFF) in a DB.
Do I have to check on each page the status of the application ?
Is-it possible to have a global redirection except for one role ?
I don't know how to properly do this global closure. Any suggestions are welcomed.
I can think of three approaches to check and do a redircet
An HttpModule hooked into the appropriate, post-authorisation event. Presumably PostAuthorizeRequest of HttpApplication.
In your "global" (Global.aspx.cs) subscribe to that same event.
An MVC Action filter, overriding OnActionExecuting. (Ensure you make it global, to avoid needing to apply to every controller: add to GlobalFilters.Filters in your Application_Start.)
Of these 3 is part of MVC, but is much later in the pipeline (much more work will have been done, to be thrown away when the filter fails).
Use of a module is controlled by configuration which would make is easier to switch on and off.
option 2 is likely easiest to implement, but I would tend to prefer the modularity that 1 gives.
You can accomplish your requirement with the help of custom filters shown below :-
[CheckUserRole]
public class YourController : Controller
{
public ActionResult YourAction()
{
}
}
public class CheckUserRoleAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext 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)
// if user is not authenticated then do as :
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "Error", action = "AccessDenied" }));
}
}
Did you tryActionFilterAttribute ?
Here is a basic example:
Your controller:
[IsAdmin]
public class YourController
{
}
Your attribute
public class IsAdminAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if () // Check that your user is not an Admin and that your application is "turn-off"
{
filterContext.Result = new HttpStatusCodeResult(403); // or whatever you want
}
}
}
Add [IsAdmin] on top of all your controllers.
You can write in all other Controllers which are used as follows..
public class HomeController : Controller
{
public ActionResult Index()
{
if (User.IsInRole("Administrator"))
return RedirectToAction("PagetoRedirect");
else
return RedirectToAction("CommonPagetoShowApplicationAsClosed");
}
}
Or
Action Filter, you can create on your own and look for named action like IndexRolename
My asp.net MVC app has an area "Company" and an area "Admin".
A company can do CRUD on its users in the company area. I've created a UsersController for this in the Company area.
An admin can do CRUD on a company's users in the Admin area. I've created a CompanyUsersControllers for this in the Admin area.
Both controllers have extremely similar code and I was wondering what the cleanest way is to reuse most of the code.
While writing this question, I figured I could create an abstract UsersControllerBase class with virtual ActionResults. I did this and it works for the Company area. I define attributes on the overriding methods in the UsersController class and call the corresponding abstract method in every overriding method.
Here is an example from the base class:
[UsersControllerBase.cs]
public virtual ActionResult Edit(string slug)
{
var user = UserRepository.GetBySlug(slug);
if (user.CompanyId != CurrentUser.CompanyId)
{
throw new SecurityException(CurrentUser.Id + " attempted to edit a user that does not belong to his company");
}
var model = user.ToViewModel();
AddListsTo(model);
return View(model);
}
And the corresponding override:
[Company/UsersController.cs]
[HttpGet, GET("/company/users/{slug}/edit")]
public override ActionResult Edit(string slug)
{
return base.Edit(slug);
}
The problem is that the Edit in Admin/CompanyUsersController.cs has an extra parameter "companySlug" which is used to find the company for which we are currently editing users.
As you can see in the code above, in Company/Userscontroller.cs we simply derive the company from the CurrentUser.
What would be the best approach to handle this problem?
td;dr
I have 2 controllers with identically named actions that have near-identical method bodies but different parameters. I want to reuse the code as much as possible. pls how do I c#.
If the two methods have different signatures, I don't think it's really worth implementing it as a base class method, though it's not impossible. I would create a protected helper method on the base class and put the shared code in that. Like this (making a few assumptions about your Repository API):
[UsersControllerBase.cs]
protected virtual ActionResult Edit(User user)
{
var model = user.ToViewModel();
AddListsTo(model);
return View(model);
}
[Admin/CompanyUsersController.cs]
[HttpGet, GET("/admin/users/{companySlug}/{slug}/edit")]
public ActionResult Edit(string companySlug, string slug)
{
var user = UserRepository.GetBySlug(companySlug, slug);
return base.Edit(user);
}
[Company/UsersController.cs]
[HttpGet, GET("/company/users/{slug}/edit")]
public ActionResult Edit(string slug)
{
var user = UserRepository.GetBySlug(slug);
if (user.CompanyId != CurrentUser.CompanyId)
{
throw new SecurityException(CurrentUser.Id + " attempted to edit a user that does not belong to his company");
}
return base.Edit(user);
}
If the Edit action in the other controller has an extra parameter, then it shouldn't be an override of the base Edit action, in my opinion. I would create a separate Edit action in the derived controller with two parameters, and make the override Edit action return 404.
[HttpGet]
public override ActionResult Edit(string slug)
{
return HttpNotFound();
}
[HttpGet]
public ActionResult Edit(string slug, string companySlug)
{
// some code...
}
It is not worth implementing a basecontroller because the similar methods have different parameters.
Even if they would have the same signature, the code is cleaner, more readable, understandable and maintainable when focusing on keeping the controllers as lightweight as possible instead of adding so much complexity to save a few duplicate lines of code.
Any given model in my application might implement the IHasOpenGraphMetadata, if that's the case, a call to #Html.Partial("_OpenGraphMetadata") in my layout will render the related metadata, by accessing the model through that interface.
All's good and clean. That is until I wanted to do that from a partial action.
In /Home/Index I have the following call: #Html.Action("List", "Posts")
This returns:
public ActionResult List(long? timestamp = null, int count = 8)
{
IEnumerable<Post> posts = postService.GetLatest(timestamp, count);
PostListModel model = mapper.Map<IEnumerable<Post>, PostListModel>(posts);
return PartialView(model);
}
The issue becomes apparent here: the actual model for /Home/Index is null, while the actual metadata is in the /Posts/List partial view result
I went with this in my partial which renders the og:data ("_OpenGraphMetadata"):
#{
OpenGraphModel openGraph = GetOpenGraphMetadata();
if (openGraph != null)
{
#OpenGraphMetaProperty("title", openGraph.Title)
#OpenGraphMetaProperty("description", openGraph.Description)
#OpenGraphMetaProperty("url", openGraph.Url)
#OpenGraphMetaProperty("image", openGraph.Image)
}
}
#helper OpenGraphMetaProperty(string property, string value)
{
if (!value.NullOrBlank())
{
<meta property="og:#property" content="#value" />
}
}
#functions
{
private OpenGraphModel GetOpenGraphMetadata()
{
IHasOpenGraphMetadata model = Model as IHasOpenGraphMetadata;
if (model != null)
{
return model.OpenGraph;
}
return Context.Items[Constants.OpenGraphContextItem] as OpenGraphModel;
}
}
I figure this is pretty decent, try to extract the metadata from the actual model, and if the interface is not implemented, then access the HttpContext and look for the metadata there. The question now is, how should I be putting this metadata in the HttpContext?
I don't really want to pollute my action methods with calls like:
HttpContext.Items[Constants.OpenGraphContextItem] = model.OpenGraph;
But what other options do I have? Should I be doing this in a global action filter? a result filter? Is there an alternative to these options?
Update
It doesn't seem possible to do this in an action filter, the way I figured out I could do this was overriding the base controller's PartialView, and both View overloads, but this seems like a really unpolished approach, there must be a better way to accomplish this.
My current project is an internal web application built using ASP.Net MVC which I am adding authentication to. I have a pre-built HTTPModule which creates a IPrincipal with the appropriate roles. If the user isn't authenticated I get a user object with the role "Public"
As this is an internal application most of the pages are private and only viewable to the role "Admin". As I have a base controller I can do this:
[Authorize(Roles="Admin")]
public abstract class MyControllerBase : Controller
{
...
}
I have a problem though as some of the actions are viewable on a public website and if I attribute them like so:
[Authorize(Roles="Public")]
public class LoginController : MyController
{
public ActionResult Index()
{
}
}
The page fails to load as the user isn't authenticated. It would seem the Role of "Public is being ignored on the inherited class. Does anyone know if the roles can be overridden by inherited classes?
I am also trying to avoid attributing all the controllers with Roles="Admin"
Thanks, Keith.
You can derive a new attribute from AuthorizeAttribute and override the OnAuthorization method, then apply your customized attribute instead of Authorize. Below is the OnAuthorization method from one of my customized attributes that redirects to an error page if the privileges aren't sufficient instead of redirecting to the logon page.
I'm not sure exactly what this will buy you, though. When you decorate your class with the attribute, presumably you'll have to allow both Admin and Public (so who are you restricting since Public is anyone who is not authenticated?). You'd then have to decorate each of the controller methods that need to be restricted to Admin individually since the class attribute would allow access otherwise. You can achieve this behavior with the regular Authorize attribute by simply decorating just those non-publicly available methods (or classes that have no publicly available methods).
I suppose you could have your attribute check to see if the method being called is also decorated with the attribute and simply approve the authorization, which would effectively defer the authorization to the method level. You'd probably have to peek into the RouteData on the AuthorizationContext to get the action and use reflection to try and find the appropriate method based on parameters and request type.
public override void OnAuthorization( AuthorizationContext filterContext )
{
if (filterContext == null)
{
throw new ArgumentNullException( "filterContext" );
}
if (AuthorizeCore( filterContext.HttpContext ))
{
SetCachePolicy( filterContext );
}
else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
}
else
{
ViewDataDictionary viewData = new ViewDataDictionary();
viewData.Add( "Message", "You do not have sufficient privileges for this operation." );
filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
}
}
protected void SetCachePolicy( AuthorizationContext filterContext )
{
// ** 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 */);
}
Well in the end I think my answer was in the question. Instead of putting the Authorize attribute on my base controller I have derived a new AdminBaseController.
[HandleError]
public abstract class MyControllerBase : Controller
{
...
}
[Authorize(Roles="Admin")]
public abstract class AdminControllerBase : MyControllerBase
{
....
}
Now any controllers that require authentication can derive from AdminControllerBase while my public controllers can derive from MyControllerBase. OO to the rescue.