I want to be able to globally access the logged-in user in (Controllers, HtmlHelpers and other helper classes) throughout the whole application without getting exceptions that User is null and with the ability to handle guests (not-logged-in users).
Should this be handled within my UsersRepository? If so, how to? or is there a better approach?
You can create custom identity and principal classes. Your custom identity class can extend whatever you are currently using and can then simply add the extra information you need.
Then in a global action filter, simply overwrite the current principal with your customized one. Then you can access it from anywhere like a normal identity, but if you need your additional information, you simply cast it to your custom identity class. Which will grant you access to your additional information.
You can write a custom action filter that is executed on every request (you register it as a global filter). This filter would load the user (from the user´s repository for example) and put it the http context for example or in the ViewData.
EDIT:
Ok, the code for the filter could look like this (in this case, it loads the user to the ViewData collection). I didn´t consider anonymous users here.
public class LoadUserToViewDataAttribute : ActionFilterAttribute
{
private IUserRepository _userRepository;
public LoadUserToViewDataAttribute(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.Controller;
var userName = filterContext.HttpContext.User.Identity.Name;
var user = _repository.GetUser(userName);
controller.ViewData.Add("CurrentUser", user);
}
}
Related
I have been looking for answer to this question for a few days now, but I have not found any success. I would post the links, but it would probably take up the entire page.
So here is what I have...
I have an MVC application, which uses the WC-Federation protocol. I have been able to configure the application, so that it authenticates the users, and returns the claims from ADFS. This works perfect. I can also extract all the claims with no issues. But I am doing this within one of the actions in the controller.
And here is what I want to do...
I want to use ADFS to authenticate the user, but I want to use my own internal roles to authorize the user to have access to specific controllers (e.g. [Authorize(Roles = "CoolRole")]). I want to be able to do this, because I already have a Web API that uses OAuth 2.0, with a backend SQL Server database to manage users and roles (internal and external user.) I now want a secure portal that will allow internal users to access the data with a single-sign-on experience. Looking at the Controller model, I noticed there are some properties associated with the authentication process (OnAuthentication, OnAuthenticationChallenge) and one for the authorization process (OnAuthorization.)
I don't necessarily need the code, but I feel like I've hit a brick all, and I need to be pointed in the right direction.
UPDATE
I tried this:
protected override void OnAuthorization(
System.Web.Mvc.AuthorizationContext filterContext)
{
//Private class to create a new IPrincipal based on my AppUserMgr
var user = _setCurrentUser(
(ClaimsIdentity)filterContext.HttpContext.User.Identity);
filterContext.HttpContext.User = user;
base.OnAuthorization(filterContext);
}
This returned a 401 (Unauthorized) response.
and...
protected override void OnAuthentication(
System.Web.Mvc.Filters.AuthenticationContext filterContext)
{
//Private class to create a new IPrincipal based on my AppUserMgr
var user = _setCurrentUser(
(ClaimsIdentity)filterContext.HttpContext.User.Identity);
filterContext.Principal = user;
base.OnAuthorization(filterContext);
}
This just calls the STS numerous times, before it fails. I even tried swapping after the assignment to after the base is called in both. No luck.
Prior to the previous ones, I also tried to add an AuthorizeFilter to the control, but that didn't help:
http://pratapreddypilaka.blogspot.in/2012/03/custom-filters-in-mvc-authorization.html
I found this link: http://brockallen.com/2013/01/17/adding-custom-roles-to-windows-roles-in-asp-net-using-claims/
From there, I guessed my way through
Here is the basics of what I did:
I ended up overriding the OnAuthentication method of the Controller, but still made sure to call the base. I did this from within an extended class. Here is the concept:
public class AdfsController : Controller
{
//Some code for adding the AppUserManager (used Unity)
protected override void OnAuthentication(
System.Web.Mvc.Filters.AuthenticationContext filterContext)
{
base.OnAuthentication(filterContext);
//Private method to set the Principal
_setCurrentUser(filterContext.Principal);
}
private void _setCurrentUser(IPrincipal principal)
{
//Put code to find to use your ApplicationUserManager or
//dbContext. roles is a string array
foreach(var role in roles)
{
((ClaimsIdentity)((ClaimsPrincipal)principal).Identity)
.AddClaim(new Claim(ClaimTypes.Role, role));
}
}
}
In the Controller, you can now add the follow:
public class HomeController : AdfsController
{
//I used a magic string for demo, but store these in my globals class
[Authorize(Roles = "CoolRole")]
public ActionResult Index()
{
return View();
}
}
I tested this by checking a role assigned to the current user, and that worked! Then I changed the role to something like "reject", which the user was not assigned; and I received a 401 Unauthorized.
ADFS is the authentication/token service in Azure. to enable the Roles Based Authentication, you can use Azure RBAC (Role Based Access Controll) service to basically Augment the claims that you get back from the ADFS and add the roles that you get back from RBAC to the token, and use the same token in your API so lock down or secure the backend with that augmented token...
here is the reference for RBAC:
http://azure.microsoft.com/en-in/documentation/articles/role-based-access-control-configure/
I wonder what is the best practise for checking actually logged user's rightsfor in MVC appliaction. I would like to do this on the views and on the controllers.
Actually I can check if actual logged user is in the role like that:
User.IsInRole("roleName");
I have table with rights. Every right contain name and Enum right representation. What I want to accomplish is extension method for IPrincipal or IIdentity so I could check rights of the user like that:
Example extension method:
public static class MyPrincipal
{
public static string HasRight(this IPrincipal principal, EnumRight right)
{
// THIS would be place where I would check if a user have right with specific enum.
return true/false;
}
}
and then on the view or controller I could use this like that:
if(User.HasRight(EnumRight.AddDocuments) )
{
//DO SOMETHING
}
Is it good practise or how can I do similar mechanism simpler? Maybe independent static class not extension?
How can i get actual user Id in my extension method to get from database any data which is needed to check if user has rights?
Normally, you would implement an authorization filter, and apply it to your action method. Here is the link how you can implement action filters.
I am implementing a collaborative web gallery, and I have a few roles for each user:
Admin
DeleteImage
DeleteOwnImage
etc..
For any controller-action, we can apply [Authorize] tag to them plus which roles we want to allow, right? It is fine for Admin/DeleteImage since these two are global; but my question is, like DeleteOwnImage is kind of contextual, in order to determine whether it is valid, we need:
To know what image it is trying to delete (from request)
Retrieve the owner of that image (from service or repository)
Compare current user = that owner
Obviously [Authorize] is not enough to do so, but is it possible to do that on custom ActionFilters? Any hint?
Yes, this is possible with a custom action filter. You can extend from AuthorizeAttribute, the most basic implementation being something like:
public class OwnImageAuthorizeAttribute : AuthorizeAttribute {
public string ImageIdKey { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext) {
bool authorized = false;
// Get the current user
var currentUser = ...;
// Get the image ID, whether it is in the route or querystring
int imageId
if(int.TryParse(httpContext.RouteData.Values(ImageIdKey), out imageId)) {
// From querystring: httpContext.Request.Querystring[ImageIdKey]
// Authorize the user
authorized = YourMethodToCheckIfUserIsOwner(currentUser, imageId);
}
return authorized;
}
Then, decorate your method:
[OwnImageAuthorize(ImageIdKey = "imageId")]
public ActionResult MyAction() { }
You can find some more details here.
You can easily add something like this in an ActionFilter, Just add an action filter with OnActionExecuting implemented.
Although, depending on your DB schema this could be achieved on the DB level with your query. You could just delete when owner equals recieved recieved id. (I mean, inside the action method and not in the filter)
EDIT:
If you're using some kind of IOC container for the repositories, you should look around for the new IOC features in MVC3 (if you're using MVC3) to inject dependencies into your action filters.
http://bradwilson.typepad.com/blog/2010/07/service-location-pt4-filters.html
EDIT2:
BTW, I myself don't really like doing too much business logic in ActionFilters, especially involving calls to the DB. Even more when it's something very specific that'll be used for one action.
With the default membership provider for MVC 2 projects, I can reference User.Identity.UserName as per whatever it creates by default. I don't want to do this--want to use my own tables. So, I implemented the System.Web.Security.MembershipProvider class. The only thing I've implemented, as per many posts on the web and SO, is ValidateUser(string username, string password)
I can easily see this accepts username and password strings, I access my database context (LINQ to Entities, actually the dotConnect provider for MySQL), and check the username and password--simple enough.
But, how can I make the Controller.User object use the properties of my own table. For example, my table is called Staff. It has StaffID (PK), FirstName, LastName, etc. How can I access the StaffID of the current contextual user as follows?
int id = User.StaffID;
I found this post by Matt Wrock, but I can't really make sense of implementing the User class, specifically the IsInRole method. When is this object constructed, and where do the constructor arguments come from?
I don't like the Membership provider so I try to avoid dealing with it at all cost. In case you rather not use the User from the controller, you can do the following.
Create a base controller and have all your controllers inherit from it.
In your base controller, add a property called CurrentUser of whatever type you want to use (probably Linq 2 Sql type).
Override the Initialize method on your base controller and run the logic to get the proper user if the user is authenticated. Set CurrentUser to the proper user. Now you have access to that property in any controller that inherits from your base controller.
public BaseController : Controller
{
public MyUserEntity CurrentUser {get; set;}
protected override void Initialize(RequestContext requestContext)
{
CurrentUser = null;
if (requestContext.HttpContext.User.Identity.IsAuthenticated)
{
var userRepository = new UserRepository();
CurrentUser = userRepository.GetUser(requestContext.HttpContext.User.Identity.Name);
}
}
}
And to use it:
public StaffController : BaseController
{
public ActionResult View()
{
var staffRepository = new StaffRepository();
var staff = staffRepository(CurrentUser.StaffID);
return View(staff);
}
}
You'll have to do quite a bit if you want to get that to work.
you'll need to define your own class that descend from
GenericIdentity
Next you'll need define your own class that descends from
GenericPrincipal
This class can have the properties you'd like to reference (StaffID for example).
Thne you'll need to hook into the actual authentication process and assign the instance of your GenericPrincipal descendant to the "User" property.
Then each time you reference if you'll have to cast it to be your type in order to get access to the additional properties.
((MyPrincipal)User).StaffID
That's quite a bit more work as compared to what you've had to do so far.
But it's doable.
I'm trying to implement a custom RoleProvider in my ASP.NET MVC application.
I've created a custom MembershipProvider and it works, in that I'm able to successfully validate the user. The next step is to implement the RoleProvider to restrict access to certian Controllers to Admin users only.
Can anyone provide me with a quick outline of the steps I need to take?
The point that I'm at now is I have my controller with the Authorize filter, like so:
[Authorize(Roles="Admin")]
public class AdminOnlyController : Controller
{
// stuff
}
and I have my CustomRoleProvider class, with the following method along with a load of not-implemented Methods:
public override string[] GetRolesForUser(string username)
{
if (username == "dave")
{
return new string[] { "Admin" };
}
}
I think I need to add the user to the Role somehow but I don't know how to do that. Ideally the end result would be a scenario where unauthorized users can't access certain controllers, and I in my Views I could determine whether to show links with something like:
if (User.IsInRole("Admin"))
{
// show links to Admin Controllers
}
Can anyone point me in the right direction?
I used this as as base line for a custom role manager: http://davidhayden.com/blog/dave/archive/2007/10/17/CreateCustomRoleProviderASPNETRolePermissionsSecurity.aspx
Should work in MVC or Web Forms.
UPDATE: Since that web page no longer exists, you could try this one instead. The basic requirements are that you need to implement the RoleProvider interface, namely:
void AddUsersToRoles(string[] usernames, string[] roleNames)
string[] GetRolesForUser(string id)
bool RoleExists(string roleName)
For the not-implemented methods, be sure to throw a NotImplementedException. This should help you figure out which methods are needed in your custom provider to get the job done.
I suspect you'll have to implement IsUserInRole.