I have a project called Authorization with CodeFirstRoleProvider class that inherits from default RoleProvider
public class CodeFirstRoleProvider : RoleProvider
{
public override void CreateRole(string roleName)
{
if(string.IsNullOrEmpty(roleName)) return;
using(var context = new SISContext())
{
var role = context.Roles.SingleOrDefault(rl => rl.RoleName == roleName);//Roles table exists in database
if(role == null)
{
var newRole = new Role
{
RoleId = Guid.NewGuid(),
RoleName = roleName
};
context.Roles.Add(newRole);
context.SaveChanges();
}
}
}
}
In my other project WebPortal I want to use above method in let's say following way
var _role = new CodeFirstRoleProvider();
_role.CreateRole("Admin");
_role.CreateRole("NonAdmin");
now where do I need to place this code in my webportal? so that these roles gets added to database for the first time when the application runs.
Suppose this program runs for first time and someone clicks the register button I want to have a dropdownlist with above roles mentioned. So, these roles need to be in database before register is clicked.
Once the roles are in database I don't need to worry for the accessing these values in future.
One way would be manually writing the value in database but I don't want to do that, since I have this function I can use.
What If I do something like this?
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
var _role = new CodeFirstRoleProvider();//remove after role is added to database
_role.CreateRole("Admiin");//remove after role is added to database
_role.CreateRole("NonAdmin");//remove after role is added to database
ViewBag.ReturnUrl = returnUrl;
return View();
}
and once the roles are added to database i remove those code that does role adding.
Take a look at this article on seeding the SimpleMembership database. Even if you are not using the SimpleRoleProvider the same principals should apply since you are using code-first.
Related
I have an application which requires role authorization using custom database. The database is set up with a tblUsers table that has a reference to a tblRoles table. The users are also already assigned to their roles.
I also want to use the [Authorize(Role = "RoleName")] attribute on each action to check if an authenticated user is assigned to "RoleName" in the database. I'm having a lot of trouble figuring out where I need to make a modification to the [Authorize] attribute so it behaves that way. I just want to see if a username has a role, I won't have a page to manage roles in the database.
I have tried implementing custom storage providers for ASP.NET Core Identity, but it's starting to look like this is not what I need because I'm not gonna be managing roles within the application, and I can't tell how it affects the behavior of [Authorize] attribute.
Also, it's likely that I have a false assumption in my understanding on how the [Authorize] attribute even works. If you notice it, I would appreciate if you could point it out.
I had a similar problem when my client asked for granular permissions for each role. I couldn't find a way to modify the Authorize attribute but was able to implement the solution with a custom attribute. But it depends on one thing i.e can you get the userId of the calling user? I used cookie authentication so I just include the userId in my claims when someone logs in so when a request comes I can always get it from there. I think the built-in Session logic in asp.net might get the job done too, I can't say for sure though. Anyways the logic for custom authorization goes like this:
Load users and roles from database to cache on startup. If you haven't set up a cache in your program (and don't want to) you can simply make your own for this purpose by making a UserRoleCache class with 2 static lists in it. Also there are several ways of loading data from db on startup but I found it easy to do that directly in Program.cs as you'll see below.
Define your custom attribute to check if the calling user has the required role by iterating over lists in cache and return 403 if not.
Modify your Program class like:
public class Program
{
public static async Task Main(string[] args)
{
IWebHost webHost = CreateWebHostBuilder(args).Build();
using (var scope = webHost.Services.CreateScope())
{
//Get the DbContext instance. Replace MyDbContext with the
//actual name of the context in your program
var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
List<User> users = await context.User.ToListAsync();
List<Role> roles = await context.Role.ToListAsync();
//You may make getters and setters, this is just to give you an idea
UserRoleCache.users = users;
UserRoleCache.roles = roles;
}
webHost.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Then comes the logic for checking if user has a role. Notice I've used an array of roles because sometimes you'll want to allow access to multiple roles.
public class RoleRequirementFilter : IAuthorizationFilter
{
private readonly string[] _roles;
public PermissionRequirementFilter(string[] roles)
{
_roles = roles;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
bool hasRole = false;
//Assuming there's a way you can get the userId
var userId = GetUserId();
User user = UserRoleCache.users.FirstOrDefault(x => x.Id == userId);
//Where roleType is the name of the role like Admin, Manager etc
List<Role> roles = UserRoleCache.roles.FindAll(x => _roles.Contains(x.RoleType))
foreach(var role in roles)
{
if(user.RoleId == role.Id)
{
hasRole = true;
break;
}
}
if (!hasRole)
context.Result = new StatusCodeResult(403);
}
}
Finally make the Role attribute
public class RoleAttribute : TypeFilterAttribute
{
public RoleAttribute(params string[] roles) : base(typeof(RoleRequirementFilter))
{
Arguments = new object[] { roles };
}
}
Now you can use the Role attribute in your controllers:
public class SampleController : ControllerBase
{
[HttpGet]
[Role("Admin", "Manager")]
public async Task<ActionResult> Get()
{
}
[HttpPost]
[Role("Admin")]
public async Task<ActionResult> Post()
{
}
}
In my project I am trying to retrieve all the users and their roles in MVC 5 using Entity Framework.
The DBContext is set up using Database first. But it is wearied that the AspNetUserRoles Table is not populated automatically and the relationship between AspNetUser and AspNetRole is many-to-many.
Below is the link to the screenshot.
Can anyone give me some hints on this? Many thanks.
If you want to manage the AspNetUsersRoles, you must make the following modification.
Remove the primary key, actually is conformed by UserId and RoleId
Add a new Id field in the table and check like primary key.
Update your model.
Now you can scaffolding this entity and create the controller and the view for manage this.
Note: Remember that by changing the primary key of this table on the DB side means you have to control how AspNetUserRoles records are created on the Application Side, considering you now could have record redundancy on the table, for instance:
MyRoles=1 (PK), UserID=1, RoleID=1, Comment= User1 is Admin
MyRoles=2 (PK), UserID=1, RoleID=2, Comment= User1 is Admin again
Which means the same, so manage this logic upon AspNetUserRoles creation!
I assume that there is no problem that you cannot see AspNetUserRoles table under EDMX at all.
You have access to this table via code and in most cases is enough.
Normally under ASP.NET MVC 5 you have something like
public class AccountController : Controller
{
private ApplicationUserManager _userManager;
...
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager)
{
UserManager = userManager;
...
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
So under Login method you can get user roles like this
[System.Web.Mvc.HttpPost]
[System.Web.Mvc.AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
...
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, false, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
{
ApplicationUser user = UserManager.FindByName(model.Email);
var roles = (AppUserRoles[])Enum.GetValues(typeof(AppUserRoles));
foreach (var role in roles)
{
if (UserManager.IsInRole(user.Id, role.ToString()) && role == AppUserRoles.Administrators)
{
// Do what you need here
// logger.Info("Login attempt is success.");
return RedirectToAction("Index", "Admin");
}
You need also to have this enum in your code
public enum AppUserRoles
{
Administrators = 1,
Managers = 2,
Users = 3,
Others = 4
}
Another approach is to use STORED PROCEDURE that you can use in EDMX.
CREATE PROCEDURE GetUserRoleByUserId
#UserId nvarchar(128)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT [UserId],[RoleId] FROM [AspNetUserRoles] WHERE [UserId] LIKE #UserId
END
GO
Hope this will help you.
Because the roles are not seeded by default until you explicitly add them in your seed method
Read about Asp.Net Identity modules
http://www.asp.net/identity
http://www.codeproject.com/Articles/762428/ASP-NET-MVC-and-Identity-Understanding-the-Basics
I've implemented a custom RoleProvider for a project. The RoleProvider works, but the repository I use to fetch the user roles is only populated after build. When I logOff, change the role of the user, logOn again, the user still holds the old roles.
public class CmsRoleProvider : RoleProvider
{
private EntityDB _db { get; set; }
public CmsRoleProvider()
{
_db = new EntityDB();
}
public override string[] GetRolesForUser(string username)
{
var user = _db.Users.Where(u => u.EmailAddress == username).SingleOrDefault();
var roles = user.UserRoles.Select(u => u.Role.Name).ToList<string>();
return roles.ToArray();
}
}
In the sample above, the user only get the correct roles after building the project. When I create the repository inside the GetRolesForUser function, it works fine.
Is there a caching problem going on? Who can help me with this.
ASP.NET creates only single instance of the RoleProvider. Because of that your context is also long lived. Better to have short lived contexts.
public class CmsRoleProvider : RoleProvider
{
private EntityDB GetContext()
{
return new EntityDB();
}
public override string[] GetRolesForUser(string username)
{
using(var db = GetContext())
{
var user = db.Users.Where(u => u.EmailAddress == username)
.SingleOrDefault();
var roles = user.UserRoles.Select(u => u.Role.Name).ToList<string>();
return roles.ToArray();
}
}
}
Problem with your approach is that context keeps track of the loaded users. When you ask for a user that is already tracked by the context the existing instance is returned. Hence the UserRoles associated with that is returned.
The problem is a context reference. When you create a reference of the context (EntityDB), out of the method that get the roles from your context, this references still the same, in other words, every data that you select will be the same because the select will be made in the context not in database (this is a way to the EF not be going all the time to the database). The change that you do (in the roles), was made in another context, so to get the right context you have to create a new instance of you context. Do it inside of the method using the keywork using:
using (var database = new EntityDB())
{
// Get your roles and return them
}
I am designing an MVC 3 application where multiple tenants reside in a single database.
What is the best way to prevent users from editing/viewing other tenants data in MVC? (i.e. someone could type in '/People/Edit/1' and edit the person with Id of 1- regardless of wether they are part of the tenants data or not).
I know I can override 'OnActionExecuting(ActionExecutingContext filterContext)' for each controller- but it sounds crazy to have to handle each action seperately, get the ID or OBJECT depending on if its a POST or GET and then check if the operation is allowed.
Any better ideas?
Also, I do not want to go down the route of creating a different database or schema for each tenant.
Thanks in advance.
Instead of passing ids to your controller actions write a custom model binder for your entities which will fetch it from the database. So for example let's assume that you have the following model:
public class Person
{
public string Id { get; set; }
... some other properties
}
Now instead of having:
[HttpPost]
public ActionResult Edit(string id)
{
...
}
write:
[HttpPost]
public ActionResult Edit(Person person)
{
...
}
and then write a custom model binder for Person:
public class PersonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var id = bindingContext.ValueProvider.GetValue("id");
// check if an id was provided and if the user is authenticated
if (!controllerContext.HttpContext.User.Identity.IsAuthenticated || id == null)
{
throw new HttpException(403, "Forbidden");
}
var currentUser = controllerContext.HttpContext.User.Identity.Name;
// fetch the person from your repository given the id and belonging
// to the currently authenticated user
var person = _repository.GetPerson(id.AttemptedValue, currentUser);
if (person == null)
{
// no person found matching
throw new HttpException(403, "Forbidden");
}
return person;
}
}
which you would register in Application_Start:
ModelBinders.Binders.Add(typeof(Person), new PersonModelBinder());
Original answer
To solve the problem quickly use guids instead of a auto-increasing integer. However this is just delaying the problem.
One of the things you can do is to role your own authorize attribuut http://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute.aspx Or you can chose to create a global actionfilter. http://www.asp.net/mvc/tutorials/understanding-action-filters-cs
Addition information on how you could do it based on request in comment
public class MySuperFilter : ActionFilterAttribute
{
//Called by the MVC framework before the action method executes.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
String user = filterContext.HttpContext.User.Identity.Name;
int id = int.Parse(filterContext.RouteData.GetRequiredString("Id"));
if (!IsValidUser(user,id))
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary {{ "Controller", "YourController" },
{ "Action", "YourAction" } });
}
base.OnActionExecuting(filterContext);
}
private bool IsValidUser(string user,int id)
{
//Check if the user has acces to the page
return true;
}
}
It's a non-elegant solution but depending on scope it could be easiest. Having designed a similar system that has relatively few points of entry for multiple tenants data we just merely check that the CurrentUser is the Owner of the object that is being queried. Our use objects a common base interface that has the owner field so the check is made not carrying about the specific object but just from the interface. If there's a mismatch we throw a security exception and log that a user is probably playing around the query string seeing if they get our website to leak data the way many production websites do.
I am trying to follow the Nerd Dinner MVC application as a base to learn the correct way to develop MVC applications.
I have created Interfaces and Repositories as the reference code suggests and am using Entity Framework for data access.
If I want to insert data when a user registers into a table [dbo].[Users], I do not have a controller for Users, how do I do it?
AccountController.cs
[HandleError]
public class AccountController : BaseController
{
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName, model.Password, model.Email);
if (createStatus == MembershipCreateStatus.Success)
{
// TODO: Enter record into [Users] get reference to [Aspnet_UserId]
// How do I do this??
//FormsService.SignIn(model.UserName, false /* createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
If I create a UsersController to display views based on the Users table, how would I then add a new record when the user is registering?
I have a separate table [Users] that I wish to populate when a new user registers adding the [Aspnet_UserId] Guid.
You don't need to have a controller for each table in your database. In the code above, the MembershipService is the code that is actually creating the record (via the Repository for users).
The controllers should represent various areas and groups of functionality your website provides. While in many cases, you might have a controller with View, Create, and Update actions that do relate to a specific entity, that does relate to a specific database table, that isn't and shouldn't always be the case.
If it makes sense to have a UsersController because you want to view a list of users, or a specific users profile, that's fine, but the form for creating a user doesn't have to be a part of that controller. Having it be a part of a membership, or admin, or account, or registration controller is ok too.
Update
I'll try to provide you sample code of how I would expect the code to look. But you might have something else in mind, which is fine too, there's no true single way to do these things.
In your code above, I'm not sure what your MembershipService class is doing. It appears there is a static method on it that does something related to User Creation. I would expect that your MembershipService class should be calling your UserRepository to actually do the user creation. But you probably wouldn't want a static class/method for this.
public class MembershipCreationResult
{
public User Member { get; private set; }
public MembershipCreateStatus MembershipCreateStatus { get; private set; }
public MembershipCreationResult(User user, MembershipCreateStatus status)
{
Member = user;
MembershipCreateStatus = status;
}
public bool Success
{
get { return MembershipCreateStatus == MembershipCreateStatus.Success; }
}
}
public class MembershipService
{
public IUserRepository { get; private set; }
public MembershipService(IUserRepository userRepository)
{
UserRepository = userRepository;
}
public MembershipCreateResult CreateUser(string name, string password, string email)
{
User member = UserRepository.Create(name, password, email);
bool status = member != null ? MembershipCreateStatus.Success : MembershipCreateStatus.Failure;
return new MembershipCreationResult(status, member)
}
}
I haven't taken a very close look at the NerdDinner sample, and I haven't used the ASP.NET membership provider, but the concept I have outlined above should work. If MembershipService does something way different from what I have outlined, then you could create a new service to wrap the functionality and leave the existing MembershipService alone.