User roles and authorization - asp.net-mvc-3

So I want to create a login page where when you enter your login credentials as a admin you get acces. If you are not a admin you get redirected back to the login page. In my database I have a field of boolean type:
isAdmin <--datatype(byte")
So how can you the best way do this?! I would like to do this in the repository pattern way as it gets easier to unit test it then.
I have googled this a lot and starting to get a bit confused on the matter. How many classes, models etc should I have?! I'm guessing one controller would do. Anyone got any good ideas?! I've read some on the DCI pattern about user roles but as it basically "only" to check that boolean in the database maybe it is overkill? Thankful for all feedback.

If I understand correctly, I had a similar issue. It seems from your question that you are not using the default membership provider (at least as is). I didn't either. So what I did was create a new authorization attribute. In your case it could look something like this:
public class AdminOnlyAttribute : AuthorizeAttribute {
IUserRepository _UserRepository;
public SimpleUser SimpleUser { get; set; }
public AdminOnlyAttribute() {
_UserRepository = new SqlUserRepository(new DbContext());
}
protected override bool AuthorizeCore(HttpContextBase httpContext) {
bool baseAuthorized = base.AuthorizeCore(httpContext);
if (!baseAuthorized) {
return false;
}
//Here you use your repository to check if a user is an admin or not
bool isAdmin = _UserRepository.IsAdmin(int.Parse(httpContext.User.Identity.Name));
if (!isAdmin) {
return false;
}
return true;
}
}
The repository method IsAdmin could be as simple as a query to check the boolean corresponding to the supplied user's ID. Something like this (please double check if SingleOrDefault() is necessary or not):
public bool IsAdmin(int userID) {
bool isAdmin = (from user in db.Users
where user.ID == userID
select user.isAdmin).SingleOrDefault();
return isAdmin;
}
And then use this in the action you want like so:
[AdminOnly]
public ActionResult Index(){
//Code here...
}
When this returns false, your ActionResult will be an HttpUnauthorizedResult which in theory should redirect to the login page.

You should create a custom Membership Provider and check the user isAdmin as part of ValidateUser.
Alternatively if other users are allowed in, use a custom role provider.
The following link is a good place to start
http://theintegrity.co.uk/2010/11/asp-net-mvc-2-custom-membership-provider-tutorial-part-1/

Is your isAdmin column a bit or a byte? It should probably be a bit. You could just create a query that checks the credentials and the IsAdmin column. If a row is returned then the login was successful.

Related

Required ModelValidation just for new objects ASP.NET MVC

I have this issue since yesterday.
In my User model I have a [NotMapped] called "ConfirmPassword". I don´t save it on the database but I use it on my Create form as a always to validate the data input for new users.
Since than, it´s ok. The problem is on my [HttpPost] Edit action. I should be able to edit some user's data without type and confirm the password. I use both Password and ConfirmPassword as a way to confirm the old password and informe the new one, if I wanna change the password. But, if I don´t, I leave them blank.
I have used already the code below to be able to pass the ModelState.IsValid() condition and it worked:
ModelState["Password"].Errors.Clear();
ModelState["ConfirmPassword"].Errors.Clear();
But, just before the db.SaveChanges(), as User user view model is considered, it has both properties empty and I got:
Property: ConfirmPassword Error: The field ConfirmPassword is invalid.
The question is: How could I skip de Required model validation when I want to update an object?
I read already about custom ModelValidations with classes extending ValidationAttribute and
DataAnnotationsModelValidator but I am not doing it right.
Any idea? How could I create a custom model validation that checks if the UserId property is null or not. It´s a nice way to check if I'm in Create or Edit action.
Thanks,
Paulo
Using the domain objects as your ViewModel will leads you to a condition of less scalability. I would opt for seperate ViewModels specific for the Views. When i have to save the data i map the ViewModel to the Domain model and save that. In your speciific case, i would create 2 ViewModels
public class CustomerViewModel
{
public string FirstName { set;get;}
public string LastName { set;get;}
}
And i will Have another ViewModel which inherits from the above class, for the Create View
public class CustomerCreateViewModel :CustomerViewModel
{
[Required]
public string Password { set;get;}
[Required]
public string ConfirmPassword { set;get;}
}
Now in my Get actions, i use this ViewModel
public ActionResult Create()
{
var vm=new CustomerCreateViewModel();
return View(vm);
}
and of course my View(create.cshtml) is now binded to this ViewModel
#model CustomerCreateViewModel
<h2>Create Csustomer</h2/>
//Other form stuff
Similarly for My Edit Action,
public ActionResult Edit(int id)
{
var vm=new CustomerViewModel();
var domainCustomer=repo.GetCustomerFromID(id);
if(domainCustomer!=null)
{
//This manual mapping can be replaced by AutoMapper.
vm.FirstName=domainCustomer.FirstName;
vm.LastName=domainCustomer.LastName;
}
return View(vm);
}
This view is bounded to CustomerViewModel
#model CustomerViewModel
<h2>Edit Info of #Model.FirstName</h2>
//Other form stuff
In your POST Actions, Map it back to the Domain object and Save
[HttpPost]
public ActionResult Create(CustomerCreateViewModel model)
{
if(ModelState.IsValid)
{
var domainCust=new Customer();
domainCust.FirstName=model.FirstName;
repo.InsertCustomer(domainCust);
//Redirect if success (to follow PRG pattern)
}
return View(model);
}
Instead of writing the Mapping yourself, you may consider using AutoMapper library to do it for you.

is there a better way to implement role based acton and view in mvc than if/else?

So I have three roles, administrators, companies and employees in my mvc .net application that uses asp.net membership in a separate database. I moved the .net membership in a different database for now because everytime I modify the model, the .net membership tables are getting deleted.
Anyway, I am handling different roles using if/else in the action method. For example, in Index() action, I check if the user is in administrators role, then create model and linq query based on that. If user in companies role, different query and if user in employees role, different query. check code below. The model created after the if condition is passed to the View.
I feel like this is not the best way to handle roles. Is this the best way to handle roles? I am considering different areas as well, but I use same views for the different roles, i think it may not be productive.
Any suggestion/idea greatly appreciated.
[Authorize]
public class CompanyController : Controller
{
private MyDBContext db = new MyDBContext();
//
// GET: /Company/
public ViewResult Index()
{
var viewModel = new CompanyIndexViewModel();
if (Roles.IsUserInRole("administrators")) {
viewModel = new CompanyIndexViewModel { Companies = db.Companies.ToList() };
}
else if (Roles.IsUserInRole("companies")) {
viewModel = new CompanyIndexViewModel { Companies = db.Companies.Where(c => c.Username.ToLower().Equals(this.User.Identity.Name.ToLower())).ToList() };
}
else if (Roles.IsUserInRole("employees")) {
string userName = this.User.Identity.Name.ToLower();
var companies = db.Companies.Where(c => c.Appointments.Any(a =>
a.Employee.Username.ToLower() == userName)).ToList();
viewModel = new CompanyIndexViewModel { Companies = companies.ToList() };
}
return View(viewModel);
}
....
There is two things I would do:
Firstly, what StanK said and move it out of the controller action. However, I would move it out of the Controller all together. This sort of logic shouldn't really reside in the Controller to begin with (whether it be in an action, or a private method in the controller).
Think about it this way: What if your logic for who gets to see what companies changes.. you'll have to change it in all sorts of different places.
Secondly, I would create a constructor for the CompanyIndexViewModel that took in a list of Company instead of initializing it inline like that. Does the CompanyIndexViewModel contain anything else besides companies?
// your controller
public ViewResult Index()
{
var viewModel = CompanyIndexViewModel(CompanyService.GetCompaniesForCurrentUser());
return View(viewModel);
}
Ideally, you would also have your controller depend on an interface representing the "CompanyService", and have that injected into your controller.
Take a look at this blog which outlines using Ninject with MVC 3. It's ridiculously simple to setup for something that is so powerful for you later on.
If you take one thing away from what I've said above, it's probably best to start with moving your logic out of the controller.
I would move the code that builds the list of Companies to it's own method to tidy up the controller action , which would also make the logic that determines the list of Companies for the current user re-useable.
e.g.
private List<Company> GetCompaniesForCurrentUser()
{
var userName = this.User.Identity.Name.ToLower();
if (Roles.IsUserInRole("administrators"))
return db.Companies.ToList();
if (Roles.IsUserInRole("companies"))
return db.Companies.Where(c => c.Username.ToLower().Equals(userName)).ToList();
if (Roles.IsUserInRole("employees"))
return db.Companies.Where(c => c.Appointments.Any(a =>
a.Employee.Username.ToLower() == userName)).ToList();
throw new AuthorizationException("User " + userName + " is not authorised.");
}
public ViewResult Index()
{
var viewModel = new CompanyIndexViewModel { Companies = GetCompaniesForCurrentUser() };
return View(viewModel);
}

MVC3 authentication and anonymous users

I have an MVC3 application that uses the standard accountcontroller that comes with visual studio to authenticate users.
I want to share specific parts of my program with people, much like google shares documents in google docs when you do that via email; in other words I don't want to give anyone access to the pages (In which case I could just remove the Authorize attributes) but I do want users to share pages based on a url with a hash in it, and have them skip the login.
I think I would like to generate the hash based on a page and link it to an anonymous user, which would then have to be auto-logged in if the hash is correct
How would I do that?
Create a table in database with shared pages information(controller, action, documentId, hash, expiresAt, etc)
Override Authorize attribute and in OnAuthorizationvalidate url params in your database.
public class SharedAuthorize:AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var documentHash = int.Parse(filterContext.RouteData.Values["hash"].ToString());
if (!HashRepository.CanWeRead(documentHash,controller, action, documentId))
{
return false;
}
return true;
}
}
This is just an idea=))

MVC3 URL parameters - avoiding malicious attacks/security flaws

When navigating to a new webpage, is there a "Best Practice" for passing Ids around.
For example, a person registers to use a website, they get given an Id, this needs to be passed around the rest of the website/pages where it is used to retrieve relevant data from a database.
If the Id is passed in the url: http://myWebsite.com/User/Details/1234, the user could change it to
http://myWebsite.com/User/Details/4567 and potentially retireve a different user's details.
Putting this value in a hidden field and then POSTing wouldn't be great either as "view source" would display the value.
Many thanks
That's why you should always verify that this id belongs to the currently authenticated user. The currently authenticated user is stored in the forms authentication cookie and is something that the user cannot change because the value is encrypted. This cookie is emitted when the user logs in and you can access it everywhere where you have an instance to HttpContextBase (which is pretty much almost anywhere in the V and C parts of the MVC pattern).
For example, like this:
[Authorize]
public ActionResult Foo(int id)
{
string currentUser = httpContext.User.Identity.Name;
// TODO: go ahead and check in your backed that the id
// belongs to the currently connected user
...
}
Obviously writing those checks over and over again in all controller actions could quickly become boring, not to mention the DRYness of the approach. That's why it is recommended to write a custom Authorize attribute which will perform those checks before even entering into the controller action. Then you will decorate your controller actions with this custom attribute and you will know for sure that if the code has reached inside the action it means that the current user is the owner of the id passed as parameter. The way this id is passed as parameter doesn't really matter. Could be route data, query string, POST, whatever. The user can modify it as much as he likes. The important part is that you ensure that the value he entered is coherent with your domain authorization logic.
So:
public class AuthorizeOwnerAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
// the user is either not authenticated or not authorized
// no need to continue any further
return false;
}
// at this stage we know that the user is authenticated and
// authorized (in roles), so let's go ahead and see who this
// user is
string username = httpContext.User.Identity.Name;
// now let's read the id. In this example I fetch it from
// the route data but you could adapt according to your needs
string id = httpContext.Request.RequestContext.RouteData.Values["id"] as string;
// Now that we know the user and the id let's go ahead and
// check in our backend if the user is really the owner
// of this id:
return IsOwner(username, id);
}
private bool IsOwner(string username, string id)
{
// go ahead and hit the backend
throw new NotImplementedException();
}
}
and then:
[AuthorizeOwner]
public ActionResult Foo(int id)
{
...
}

How to return more detailed error when user cannot be validated

I am working on creating a custom membership class for my asp.net project. In the MembershipProvider class, function for user validation is defined as follows.
public abstract bool ValidateUser(string username, string password);
The problem is validation can be failed for several reason, so I want to provide more detailed description as to why login failed (e.g., user is lockedout etc) but that cannnot be done via a bool return type. I certainly can create one of my method for validation as follows
public RichStatusDetailsEnum ValidateUser(string username, string password);
Problem with above is that I will not be able to call this method in a generic way (just by Membership.ValidateUser). Rather I will have to instantiate object of my custom membership provider etc etc. Does anyone else has encountered same issue and if there are any better ways of handling this type of situation?
As per this MSDN resource, the return will tell you if it is valid. There are only two parameters (username and password). If it is a false return, then you know one of them is off. You can always test if the UserName is correct by calling Membership.GetUser(). If the MembershipUser object returned is not null, then you know the UserName is correct. In which case, that would lead you to believe that if ValidateUser() fails, it is a bad password.
To see if the user is locked out: for the MembershipUser object that gets returned from GetUser(), you can test if the user is locked out by the property Membershipuser.IsLockedOut.
Description
One way to get this done is
Store the reason, why the validation failed, in the HttpContext.Current.Items Collection
If ValidateUser failed, get the data and do something (like add an error to ModelState.AddModelError("Property","Message");
Sample
public override bool ValidateUser(string username, string password)
{
// your custom logic goes here
HttpContext.Current.Items["ValidateUserResult"] = ValidateUserFailed.UserNameWasWrong;
return false;
}
public enum ValidateUserFailed
{
UserNameWasWrong, PasswordWasWrong, BecauseIDontLikeYou
}
your other class
More Information
MSDN - HttpContext.Items Property
MSDN - ViewDataDictionary.ModelState Property

Resources