How do I plug a custom roles manager into the AuthorizeAttribute? - asp.net-mvc

I have an older database schema that I cannot change. It has a single user table with an integer field to designate user level where 1 is standard user and 5 is administrator. I'm writing an MVC front end and I want to use ASP.NET Identity. I've figured out everything else from research and the boilerplate code. I can't seem to figure out how to create a custom roles system. I realize it has something to do with implementing a role manager and role store. That's fine but how do I then connect that with MVC to get the AuthorizeAttribute to acknowledge my manager?
I apologize if this is obvious but I have done my research and I'm having trouble nailing it down.

From your question, I'm assuming you already figured out how to create your role manager and you are only missing the config to actually use it. If my assumptions are wrong, let me know and I will add explanation on how to create the CustomRoleManager.
Web.config
<configuration>
<system.web>
<roleManager enabled="true" defaultProvider="CustomRoleProvider">
<providers>
<clear/>
<add name="CustomRoleProvider"
type="MyNamespace.CustomRoleProvider,
MyNamespace, Version=1.0.0.0, Culture=neutral"
connectionStringName="MyConnectionString"
enablePasswordRetrieval="false"
enablePasswordReset="false"
requiresQuestionAndAnswer="false"
writeExceptionsToEventLog="false" />
</providers>
</roleManager>
</system.web>
</configuration>

Here is the RoleProvider that I used, if anyone has the same trivial requirements. If you know of any reason why this implementation is not secure, please let me know. I used #Pluc's answer in my Web.Config to connect this provider to my application. It worked wonderfully.
public class AppRole : IRole<int>
{
public AppRole(int a_id, string a_name)
{
Id = a_id;
Name = a_name;
}
public int Id { get; private set; }
public string Name { get; set; }
}
public class AppRoleProvider : RoleProvider
{
private readonly IServiceLocator _container = UnityConfig.GetServiceLocator();
private ITrainingRepository _repository; // Thin wrapper around my DbContext
private AppRole[] _roles = new[]
{
new AppRole(0, "User"),
new AppRole(5, "Admin"),
};
public AppRoleProvider()
{
ApplicationName = "TrainingCenter";
_repository = _container.GetInstance<ITrainingRepository>();
}
public override string ApplicationName { get; set; }
public override bool IsUserInRole(string username, string roleName)
{
var user = _repository.GetUserByUserName(username);
if (user == null)
return false;
var role = _roles.FirstOrDefault(i => i.Name.Equals(roleName, StringComparison.OrdinalIgnoreCase));
if (role == null)
return false;
if (user.UserLevel >= role.Id)
return true;
return false;
}
public override string[] GetRolesForUser(string username)
{
var user = _repository.GetUserByUserName(username);
if (user == null)
return new string[] {};
return _roles.Where(i => i.Id <= user.UserLevel).Select(i => i.Name).ToArray();
}
public override void CreateRole(string roleName)
{
// Does not create.
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
// Does not delete.
return false;
}
public override bool RoleExists(string roleName)
{
return _roles.Any(i => i.Name.Equals(roleName, StringComparison.OrdinalIgnoreCase));
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
// Does not add user to role.
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
// Does not remove users from roles.
}
public override string[] GetUsersInRole(string roleName)
{
// Does not get users in role.
return new string[] {};
}
public override string[] GetAllRoles()
{
return _roles.Select(i => i.Name).ToArray();
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
// Does not find users in role.
return new string[] { };
}
}

Related

How to change database name in dbcontext connection string at runtime

My Asp.Net MVC application is setup as follows.
There are 4 projects in solution.
ge.Web
ge.BLL
ge.Core
ge.Entities
Controller in ge.Web initializes a repository object present in ge.Core
public class MapsController : Controller
{
private AssessmentRepository repAssessments = new AssessmentRepository("name=GEContext", schoolCode);
public ActionResult DisplaySearchResults()
{
.....
}
}
Assessments Repository
public class AssessmentRepository : Repository<Assessment>, IAssessmentRepository
{
public AssessmentRepository(string connString, string schoolCode)
:base(connString, schoolCode)
{ }
}
Repository
public class Repository<TEntity> : IRepository<TEntity> where TEntity:class
{
protected readonly GEContext context;
public Repository(string connString, string schoolCode) {
context = new GEContext(connString);
}
}
GEContext
public class GEContext : DbContext
{
public GEContext(string connString):base(connString)
{
this.Configuration.LazyLoadingEnabled = false;
Database.SetInitializer(new MySqlInitializer());
}
}
DbContext
public class DbContext : IDisposable, IObjectContextAdapter
{
public DbContext(string nameOrConnectionString);
}
Web.Config
<add name="GEContext" connectionString="server=localhost;port=4040;uid=root;pwd=xxx;database=ge" providerName="MySql.Data.MySqlClient" />
now i want to replace "database=ge" present in web.config with database=ge_[schoolCode]. at runtime How can i go about it?
UPDATE
My solution did not work. so i am stating the problem once again.
Web.Config
I have changed My config file to the following (previously GEContext was the only connection string)
<connectionStrings>
<add name="GEContext_sc001" connectionString="server=localhost;port=4040;uid=root;pwd=blabla;database=db_sc001" providerName="MySql.Data.MySqlClient" />
<add name="GEContext_sc002" connectionString="server=localhost;port=4040;uid=root;pwd=blabla;database=db" providerName="MySql.Data.MySqlClient" />
<appSettings>
<add key="SchoolCodes" value="sc001,sc002"/>
these are the allowed schoolCodes
Now when the user enters schoolcode at login screen, it is validated against the codes present in SchoolCodes key. and if yes, then it should try to connect to the connectionString for that particular connection. Now when my code comes to
UserManager.FindAsync
in Login function of AccountController, it crashes trying to find GEContext. Where is that set? and how can i change it?
I have changed the repository calling in controller as follows
private static string schoolCode = (string)System.Web.HttpContext.Current.Session["SchoolCode"];
private AssessmentRepository repAssessments = new AssessmentRepository("name=GEContext_" + schoolCode);
UPDATE-2
Following is present in ge.Web
IdentityConfig.cs
public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser, Role, int, UserLogin, UserRole, UserClaim>(context.Get<ApplicationDbContext>()));
...........
}
The following is present in ge.Core
ApplicationDbContext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, Role, int, UserLogin, UserRole, UserClaim>
{
public ApplicationDbContext(string connString)
: base(connString)
{
Database.SetInitializer(new MySqlInitializer());
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext("name=GEContext_");
}
}
How can i pass schoolCode from ge.web to ge.Core (answer should be straight forward but currently i cant get my head around it)
UPDATE-3
As told by itikhomi and taking help from this post I have changed my code as follows
in ApplicationDbContext class added the following
public static ApplicationDbContext Create(string scCode){
return new ApplicationDbContext("name=GEContext_" + scCode);
}
in AccountController Login
var appDbContext = ApplicationDbContext.Create(model.SchoolCode);
Request.GetOwinContext().Set<ApplicationDbContext>(appDbContext);
it still does not hit the correct database
You have two ways
1)
using System.Data.SqlClient;
public class Repository<TEntity> : IRepository<TEntity> where TEntity:class
{
protected readonly GEContext context;
public Repository(string connString, string schoolCode) {
context = new GEContext(connString);
var connection = new SqlConnectionStringBuilder(context.Database.Connection.ConnectionString);
connection.InitialCatalog = "YOUR_PREFIX_FROMSOMEWHERE"+schoolCode;
context.Database.Connection.ConnectionString = connection.ConnectionString;
}
}
2) if you wants to switch connection when it opened before use ChangeDatabase:
//open connection if it close
context.Database.Connection.ChangeDatabase("DATABASE-NAME");
NOTE: if use ChangeDatabase connection should be already opened
FOR UPDATE3:
You need to do somethink like this:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public ApplicationDbContext(string schoolCode)
: base(schoolCode)
{
var connection = new SqlConnectionStringBuilder(this.Database.Connection.ConnectionString);
connection.InitialCatalog = "YOUR_PREFIX_FROMSOMEWHERE" + schoolCode;
this.Database.Connection.ConnectionString = connection.ConnectionString;
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
in account controller:
public ApplicationSignInManager SignInManager
{
get
{
if (_signInManager == null)
{
var code = HttpContext.Request.Form.Get("SchoolCode");//Get from FORM\QueryString\Session whatever you wants
if (code != null)
{
HttpContext.GetOwinContext().Set<ApplicationSignInManager>(new ApplicationSignInManager(_userManager, HttpContext.GetOwinContext().Authentication));
}
_signInManager = HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
return _signInManager;
}
private set
{
_signInManager = value;
}
}
public ApplicationUserManager UserManager
{
get
{
if (_userManager == null)
{
var code = HttpContext.Request.Form.Get("SchoolCode");//Get from FORM\QueryString\Session whatever you wants
if (code != null)
{
var appDbContext = new ApplicationDbContext(code);
HttpContext.GetOwinContext().Set<ApplicationDbContext>(appDbContext);
HttpContext.GetOwinContext().Set<ApplicationUserManager>(new ApplicationUserManager(new UserStore<ApplicationUser>(appDbContext))); //OR USE your specified create Method
}
_userManager = HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
return _userManager;
}
private set
{
_userManager = value;
}
}
Your problem is in Store of UserManager is created before you change your OWIN context, in this case better to use DI like here
You can change the database for an open connection
context.Database.GetDbConnection().ChangeDatabase("");
I resolved it with the help of itikhomi..Posting the final code..
ApplicationDbContext
public static ApplicationDbContext Create()
{
return new ApplicationDbContext("name=GEContext");
}
AccountController
public ApplicationUserManager UserManager {
get
{
if (System.Web.HttpContext.Current.Session["SchoolCode"] == null)
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
else
{
var appDbContext = ApplicationDbContext.Create(System.Web.HttpContext.Current.Session["SchoolCode"].ToString());//new ApplicationDbContext("name=GEContext", System.Web.HttpContext.Current.Session["SchoolCode"].ToString());
HttpContext.GetOwinContext().Set<ApplicationDbContext>(appDbContext);
HttpContext.GetOwinContext().Set<ApplicationUserManager>(new ApplicationUserManager(new UserStore<ApplicationUser, Role, int, UserLogin, UserRole, UserClaim>(appDbContext)));
return HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
}
private set
{
_userManager = value;
}
}

Issue in Connecting Web App with localdb using DataContext file and webConfig file

I have created MVC web application using Repository & DI approach. I have used Code First approach too.
Here is my DataContext file:
namespace EfRepPatTest.Data
{
public class DataContext : DbContext, IDbContext
{
public new IDbSet<TEntity> Set<TEntity>() where TEntity: class
{
return base.Set<TEntity>();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
.Where(type => !String.IsNullOrEmpty(type.Namespace))
.Where(type => type.BaseType != null && type.BaseType.IsGenericType &&
type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
foreach (var type in typesToRegister)
{
dynamic configurationInstance = Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
base.OnModelCreating(modelBuilder);
}
}
}
I have defined connecting string in Web.Config file like below:
<add name="DataContext"
connectionString="Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\eCommerce.mdf;Integrated Security=True"
providerName="System.Data.SqlClient"
/>
Please note here I have mentioned same name to the Connection string and my context file.
Here is my post method:
[HttpPost]
public ActionResult Create(CategoryModel model)//FormCollection collection
{
try
{
// TODO: Add insert logic here
if (model == null)
return View(model);
var category = new Category();
category.Name = model.Name;
categoryService.Insert(category);
return RedirectToAction("Index");
}
catch
{
return View(model);
}
}
CategoryService:
public class CategoryService : ICategoryService
{
private IRepository<Category> _categoryRepository;
public CategoryService(IRepository<Category> categoryRepository)
{
this._categoryRepository = categoryRepository;
}
public void Insert(Category category)
{
if (category == null)
throw new ArgumentNullException("Category");
_categoryRepository.Insert(category);
}
}
RepositoryService:
public class RepositoryService<TEntity> : IRepository<TEntity> where TEntity: class
{
private IDbContext _context;
private IDbSet<TEntity> Entities
{
get { return this._context.Set<TEntity>(); }
}
public RepositoryService(IDbContext context)
{
this._context = context;
}
public void Insert(TEntity entity)
{
Entities.Add(entity);
}
}
When I run application on the first time it will create local db. But when I going to insert data, I did not get any error from the application and it does not insert my data to the DB.
What cause this? What I have done wrong here?
Any help is appreciated!
You should call SaveChanges() on _context after all changes like this for ex.:
public void Insert(TEntity entity)
{
Entities.Add(entity);
_context.SaveChanges();
}

AutoMapper+xUnit: Missing type map configuration or unsupported mapping

I cannot figure this one out. I have a N-Tier ASP.MVC application and I am writing my first Unit Test and it seems to fail on my AutoMapper configuration. I have used AutoMapper a million times and never had any problems using it.
I'm sure I am missing something simple, but I have been staring at this for 24 hours now.
Class Library: APP.DOMAIN
public class User : IEntity<int>
{
public int Id { get; set; }
[StringLength(20), Required]
public string UserName { get; set; }
}
Class Library: APP.SERVICE
References App.Domain
public class UserViewModel
{
public int Id { get; set; }
public string UserName { get; set; }
}
I have my AutoMapper bootstrapper in the service layer.
public static class AutoMapperBootstrapper
{
public static void RegisterMappings()
{
Mapper.CreateMap<User, UserViewModel>();
}
}
UserService.cs
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public List<UserViewModel> GetUsers()
{
var users = _userRepository.GetAll();
if (users == null)
{
throw new Exception("No users found.");
}
return Mapper.Map<List<UserViewModel>>(users); // FAILS ON AUTOMAPPER
}
}
ASP.MVC Layer: APP.WEB
References App.Service
private void Application_Start(object sender, EventArgs e)
{
// Register AutoMapper
AutoMapperBootstrapper.RegisterMappings();
Mapper.AssertConfigurationIsValid();
// Code that runs on application startup
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
Unit Test Layer:
public class TestUserRepository :IUserRepository
{
public IEnumerable<User> GetAll()
{
var users = new List<User>()
{
new User { Id = 1, UserName = "Mary"},
new User { Id = 2, UserName = "Joe"}
};
return users;
}
}
public class UserServiceTest
{
private IUserService _userService;
private readonly IUserRepository _userRepository;
public UserServiceTest()
{
_userRepository = new TestUserRepository();
}
[Fact]
public void GetUsers_Should_Return_Correct_Number_Of_Users()
{
// Arrange
_userService = new UserService(_userRepository);
// Act
var result = _userService.GetUsers(); // FAILS ON AUTOMAPPER
// Assert
Assert.True(result.Any(u => u.UserName == "Mary"));
}
}
Failing Test Message:
*** Failures ***
Exception
AutoMapper.AutoMapperMappingException: AutoMapper.AutoMapperMappingException : Missing type map configuration or unsupported mapping.
Mapping types:
User -> UserViewModel
App.Data.Model.User -> App.Service.ViewModels.UserViewModel
Destination path:
List`1[0]
Source value:
App.Data.Model.User
at App.Service.Services.UserService.GetUsers() in D:\Repositories\App\App.Service\Services\UserService.cs:line 36
at App.Tests.Service.Tests.UserServiceTest.GetUsers_Should_Return_Correct_Number_Of_Users() in D:\Repositories\App\App.Tests\Service.Tests\UserServiceTest.cs:line 34
A little late to the party but have you tried setting the mapping before running the test?
public class UserServiceTest
{
public UserServiceTest()
{
// register the mappings before running the test
AutoMapperBootstrapper.RegisterMappings();
}
...
}
What we would need to do is Inject Custom Mapper Mock as given below. Add all those custom profiles that you have used for that particular class that you are unit testing and inject ConfigureMapper() in the Constructor of that class which is expecting IMapper Object
public IMapper ConfigureMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<CustomProfile>();
cfg.AddProfile<UserCustomProfile>();
cfg.AddProfile<UserWorkProfile>();
});
return config.CreateMapper();
}
Hope this solves the issue.
I'm not sure what the problem is, it's been a while since I've last used AutoMapper, but I'm quite sure that the following will work:
return users.Select(Mapper.Map<UserViewModel>);
I have a problem with this line:
var authorDTO = mapper.Map<AuthorCreationDTO>(AuthorinsideDB);
So I change the version of Autormapper
from:
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="7.0.0" />
to
Version="6.0.0"
and it worked.

MVC custom roleprovider how to hook it up to HttpContext.Current.User.IsInRole("myrole")

I have an MVC app and I wrote a custom roleprovider for it as shown:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using VectorCheck.Models;
namespace VectorCheck.Security
{
public class MyRoleProvider : RoleProvider
{
private VectorCheckRepository<User> _repository { get; set; }
public MyRoleProvider()
{
_repository = new VectorCheckRepository<User>();
}
public MyRoleProvider(VectorCheckRepository<User> repository)
{
_repository = repository;
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
public override string ApplicationName
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override void CreateRole(string roleName)
{
throw new NotImplementedException();
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
throw new NotImplementedException();
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
throw new NotImplementedException();
}
public override string[] GetAllRoles()
{
throw new NotImplementedException();
}
public override string[] GetRolesForUser(string username)
{
var user = _repository.GetUser(username);
return new string[] { user.Role.Name };
}
public override string[] GetUsersInRole(string roleName)
{
throw new NotImplementedException();
}
public override bool IsUserInRole(string username, string roleName)
{
var user = _repository.GetUser(username);
return string.Compare(user.Role.Name, roleName, true) == 0;
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
public override bool RoleExists(string roleName)
{
throw new NotImplementedException();
}
}
}
This works really well with restricting access to controllers and actions using:
[Authorize(Roles = "Administrator")]
above the controller or action.
I also want restricted access to some things in the view though using:
HttpContext.Current.User.IsInRole("Administrator")
This method isn't part of my roleprovider though so isn't getting overridden.
Does anyone know how to do it for this method as well?
If you've hooked your RoleProvider as the role provider for the application in web.config, then this should work automatically; the framework will create a RolePrincipal for an authenticated user at the start of the request that will call the GetRolesForUser method on your role provider, passing the name from the IIdentity as the user name.
The framework implementation of RolePrincipal's IsInRole(string role) method is something like this (I've added comments)
public bool IsInRole(string role)
{
if (_Identity == null)
throw new ProviderException(SR.GetString(SR.Role_Principal_not_fully_constructed));
if (!_Identity.IsAuthenticated || role == null)
return false;
role = role.Trim();
if (!IsRoleListCached) {
_Roles.Clear();
// here the RoleProvider is used to get the roles for the user
// and are cached in a collection on the RolePrincipal so that
// they are only fetched once per request
string[] roles = Roles.Providers[_ProviderName].GetRolesForUser(Identity.Name);
foreach(string roleTemp in roles)
if (_Roles[roleTemp] == null)
_Roles.Add(roleTemp, String.Empty);
_IsRoleListCached = true;
_CachedListChanged = true;
}
return _Roles[role] != null;
}
Set a breakpoint inside of your RoleProvider GetRolesForUser method to ensure that it is being called correctly and also inspect the IPrincipal (HttpContext.Current.User) to ensure that it is of type RolePrincipal for an authenticated user.
Sorry I am late to the party here;
For the benefit of other people with the same problem - Russ Cam's answer is spot on to finding the answer.
In my case, my custom roleManager did not have 'enabled="true" and cacheRolesInCookie="true". This seemed to stop the GetRolesForUser being called.
Working Code For the web.config:
<roleManager defaultProvider="CustomUserRolesMVCRoleProvider" enabled="true" cacheRolesInCookie="true">
Really Good Tutorial on this topic at http://www.brianlegg.com/post/2011/05/09/Implementing-your-own-RoleProvider-and-MembershipProvider-in-MVC-3.aspx

How do I restrict access to certain pages in ASP.NET MVC?

I wish to lock out access to a user's EDIT page (eg. /user/pure.krome/edit) if
a) Identity.IsAuthenticated = false
or they are authenticated but
b) Idenitity.Name != user name of the user page they are trying to edit
c) Identity.UserType() != UserType.Administrator // This is like a Role, without using RoleProviders.
I'm assuming u can decorate a controller or a controller's action method with something(s), but i'm just not sure what?
Look at the AuthorizeAttribute.
ASP.Net MVC: Can the AuthorizeAttribute be overriden?
A custom attribute derived from AuthorizeAttribute is what I use to do this. Override the OnAuthorize method and implement your own logic.
public class OnlyUserAuthorizedAttribute : AuthorizeAttribute
{
public override void OnAuthorize( AuthorizationContext filterContext )
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizeResult();
}
...
}
}
I implemented the following ActionFilterAttribute and it works to handle both authentication and roles. I am storing roles in my own DB tables like this:
User
UserRole (contains UserID and RoleID foreign keys)
Role
public class CheckRoleAttribute : ActionFilterAttribute
{
public string[] AllowedRoles { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string userName = filterContext.HttpContext.User.Identity.Name;
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (AllowedRoles.Count() > 0)
{
IUserRepository userRepository = new UserRepository();
User user = userRepository.GetUser(userName);
bool userAuthorized = false;
foreach (Role userRole in user.Roles)
{
userAuthorized = false;
foreach (string allowedRole in AllowedRoles)
{
if (userRole.Name == allowedRole)
{
userAuthorized = true;
break;
}
}
}
if (userAuthorized == false)
{
filterContext.HttpContext.Response.Redirect("/Account/AccessViolation", true);
}
}
else
{
filterContext.HttpContext.Response.Redirect("/Account/AccessViolation", true);
}
}
else
{
filterContext.HttpContext.Response.Redirect(FormsAuthentication.LoginUrl + String.Format("?ReturnUrl={0}", filterContext.HttpContext.Request.Url.AbsolutePath), true);
}
}
I call this like this...
[CheckRole(AllowedRoles = new string[] { "admin" })]
public ActionResult Delete(int id)
{
//delete logic here
}

Resources