I have some simple DbContext:
public class AuthContext : DbContext
{
public AuthContext() : base("AuthContext")
{
}
public DbSet<User> Users { get; set; }
}
And simple User model:
[Table("User")]
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string Login { get; set; }
}
What I need is to seed data to WebSecurity always or after model creating. I have tried:
Database.SetInitializer(new AuthDbSeeder());
//--------------------------------------------------------
<add name="AuthContext" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=ChatAuth; Integrated Security=SSPI; MultipleActiveResultSets=True;" providerName="System.Data.SqlClient" />
and in <system.web> I added:
<roleManager enabled="true" defaultProvider="SimpleRoleProvider">
<providers>
<clear/>
<add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/>
</providers>
</roleManager>
<membership defaultProvider="SimpleMembershipProvider">
<providers>
<clear/>
<add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
</providers>
</membership>
//--------------------------------------------------------
public class AuthDbSeeder : DropCreateDatabaseAlways<AuthContext>
{
protected override void Seed(AuthContext context)
{
WebSecurity.InitializeDatabaseConnection("AuthContext", "User", "UserId", "Login",
autoCreateTables: true);
WebSecurity.CreateUserAndAccount("Sergey", "1234");
But after all I have error that Database can't be droped because it is already in use. I need some working method to seed data to WebSecurity.
Also very important for me is: how I can add my custom models in the same DbContext and seed data to this context properly.
Also any ideas how I can Unit test WebSecurity?
There is an example on how to seed and customize WebSecurity here.
I have found the solution for the seed question
I reworked SimpleMembershipInitializer:
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
try
{
WebSecurity.InitializeDatabaseConnection("AuthContext", "User", "UserId", "Login",
autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
And in my seed method call
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
LazyInitializer.EnsureInitialized(ref initializer, ref isInitialized, ref initializerLock);
}
with null as the parameter.
Unit testing is still a problem
Related
I have the following problem with using MVC4 SimpleMembership: Register and Login methods work fine in local debugg mode, but after publish they crash the site. The cause from what i saw is that for example when i register an user the information is stored in my UserProfile table but nothing in webpages_Membership table. So it would appear that the WebSecurity can't access for some reason the webpages_Membership table.
I configured a basic MVC4 project to use SimpleMembership by enabling it in my WebConfig file like this:
<membership defaultProvider="DefaultMembershipProvider">
<providers>
<add name="DefaultMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData" />
</providers>
</membership>
<roleManager defaultProvider="DefaultRoleProvider">
<providers>
<add name="DefaultRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData" />
</providers>
</roleManager>
Added my connectionstring:
<add name="RMBase" providerName="System.Data.SqlClient" connectionString="Data Source=SQL5003.Smarterasp.net;Initial Catalog=DB_9C0558_test;User Id=DB_9C0558_test_admin;Password=*****" />
Initialised the connection in Global.asax:
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
WebSecurityConfig.InitializeDatabase();
And in my App_Start folder i added the class for WebSecurityConfig
public static void InitializeDatabase()
{
WebSecurity.InitializeDatabaseConnection("RMBase", "UserProfile", "UserID", "UserName", true);
}
Added my DBContext class
public class RMDatabase : DbContext
{
public RMDatabase() : base("RMBase") { }
}
And my Login/Register methods look like this:
[HttpGet]
public ActionResult Login()
{
return View(new Authentication());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(Authentication model)
{
if (!ModelState.IsValid)
{
return View(model);
}
if (WebSecurity.Login(model.Username, model.Password))
{
return RedirectToAction("Index","Home");
}
else
{
ViewBag.ErrorMessage = "incorect";
return View(model);
}
}
[HttpGet]
public ActionResult Register()
{
return View(new Registration());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Register(Registration model)
{
if (ModelState.IsValid)
{
if (!WebSecurity.UserExists(model.Username))
{
WebSecurity.CreateUserAndAccount(model.Username, model.Password);
WebSecurity.Login(model.Username, model.Password);
return RedirectToAction("Index","Home");
}
ViewBag.ErrorMessage = "incorect";
}
return View(model);
}
Anyone see something that i did wrong that may be the cause of this?
Install Elmah.MVC, then in the Application_Error event in Global.asax, add
var exception = Server.GetLastError();
Elmah.ErrorSignal.FromCurrentContext().Raise(exception);
Then, on the site, navigate to /elmah, which should now have the exception logged for you.
After following Tieson T. advice i found that i had System.Web.Helpers, Version=2.0.0.0 missing from my web project referencess. Added it and all is good. Still don't understand how it was working in local debugg but i am glad that it was fixed.
I've created my own Membership Provider where I have below method:
public override bool ValidateUser(string username, string password)
{
if (username == "John")
return true;
else
return false;
}
I've also added below lines to web.config file:
<authentication mode="Windows" />
<authorization>
<deny users="?" />
</authorization>
<membership defaultProvider="MembershipProviter">
<providers>
<clear />
<add name="cls_MembershipProvider" type="App.cls_MembershipProvider"
enablePasswordRetrieval="false"
enablePasswordReset="false"
requiresQuestionAndAnswer="false"
requiresUniqueEmail="false"
maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="5"
minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="10"
applicationName="App"
/>
</providers>
</membership>
As you may notice I am using Windows authentication and I don't have Log In page. By default all users from Active Directory has access to the page. My goal is to check if user exist in my database.
Everywhere I searched, there is Log In page, where ValidateUser is launched. My question is where should I implement ValidateUser method as I don't have Log In page. I just want to have control on each Controler method so I could add [Authorize] so only users from my database can actually access the page.
You can define your own CustomAuthorizeAttribute deriving from AuthorizeAttribute. Override OnAuthorization method to perform validation using details in context. Apply your custom filter on top of each controller or define a BaseController and derive your controllers from BaseController. For example you can define a class like:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class RdbiAuthorizationAttribute : AuthorizeAttribute
{
/// <summary>
/// Verifies that the logged in user is a valid organization user.
/// </summary>
/// <param name="filterContext"></param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
Guard.ArgumentNotNull(filterContext, "filterContext");
Guard.ArgumentNotNull(filterContext.Controller, "filterContext.Controller");
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(
typeof(AllowAnonymousAttribute), inherit: true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(
typeof(AllowAnonymousAttribute), inherit: true);
if (skipAuthorization)
{
return;
}
if (string.IsNullOrEmpty(filterContext.HttpContext.User.Identity.Name))
throw new AuthenticationException("User must be logged in to access this page.");
var controller = filterContext.Controller as BaseController;
if (controller != null)
{
var user = controller.GetUser();
if (user == null)
{
throw new InvalidOperationException(string.Format("Logged in user {0} is not a valid user", filterContext.HttpContext.User.Identity.Name));
}
}
base.OnAuthorization(filterContext);
}
}
Then you can define controller like:
[RdbiAuthorization]
public class BaseController : Controller
{
}
public class MyTestController : BaseController
{
}
I have ASP.NET MVC4 project with custom AuthorizeAttribute to control the authorization. In order to explain my situation easier I created a dummy project to illustrate the issue.
I have one single controller
using System.Web.Mvc;
using MvcApplication2.Helper;
using MvcApplication2.Models;
namespace MvcApplication2.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new ViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(ViewModel model)
{
Session["ModelValue"] = model.InputValue;
return RedirectToAction("Step2");
}
[MyAuthorize]
public ActionResult Step2()
{
return View();
}
}
}
The purpose is very simple, From Index I accept some input, store the value in a session and redirect to Step2. I have my authorize attribute on step 2. The code for my attribute is
using System;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication2.Helper
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Session["ModelValue"] == null)
{
return false;
}
else
{
string inputValue = httpContext.Session["ModelValue"] as string;
if (inputValue != "1")
{
return false;
}
else
{
return true;
}
}
}
}
}
The purpose is very simple, check if the session value is 1 or not.
Run the application, you input 1 in textbox and you see step2 view, if you input anything else you get the default 401.0 page.
Now I opened the web.config file to include
<system.web>
<customErrors mode="On" defaultRedirect="~/Error">
<error statusCode="401" redirect="~/401.htm" />
</customErrors>
<compilation debug="true" targetFramework="4.0" />
</system.web>
I hope when the application captures 401 code, I should see the content of 401.htm. But the reality is that I still get the default 401 error display from server.
Any idea why?
In addition use this:
<system.webServer>
<httpErrors>
<error statusCode="401" path="~/Home/Login"/>
<error statusCode="404" path="~/Error/NotFound"/>
</httpErrors>
</system.webServer>
I have spent some time searching and found a lot of confusing answers, so I will post here for clarification.
I am using MVC4 VS2012 created an Intranet site using domain authentication. Everything works. However, to manage the users that have access to different areas of this webapp I prefer not to use AD groups that I cannot manage and nor can the users of the webapp.
Is there an alternative? I assume this would involve associating/storing domain names belonging to custom roles and using the Authorize attribute to control access.
[Authorize(Roles = "Managers")]
Can anyone suggest the best pattern for this or point me in the right direction?
I see a similar solution link, but I am still not sure how to use this against a stored list of roles and validate the user against those roles. Can anyone elaborate if this solution would work?
protected void Application_AuthenticateRequest(object sender, EventArgs args)
{
if (HttpContext.Current != null)
{
String[] roles = GetRolesFromSomeDataTable(HttpContext.Current.User.Identity.Name);
GenericPrincipal principal = new GenericPrincipal(HttpContext.Current.User.Identity, roles);
Thread.CurrentPrincipal = HttpContext.Current.User = principal;
}
}
I'm using this configuration with SQL Server and MVC3.
Web.config:
<system.web>
<roleManager enabled="true" defaultProvider="SqlRoleManager">
<providers>
<clear />
<add name="SqlRoleManager" type="System.Web.Security.SqlRoleProvider" connectionStringName="SqlRoleManagerConnection" applicationName="YourAppName" />
</providers>
</roleManager>
....
<authentication mode="Windows" />
....
<connectionStrings>
<add name="SqlRoleManagerConnection" connectionString="Data Source=YourDBServer;Initial Catalog=AppServices;Integrated Security=True;" providerName=".NET Framework Data Provider for OLE DB" />
</connectionStrings>
To inicialize roles:
Global.asax.cs
using System.Web.Security;
////
protected void Application_Start()
{
//You could run this code one time and then manage the rest in your application.
// For example:
// Roles.CreateRole("Administrator");
// Roles.AddUserToRole("YourDomain\\AdminUser", "Administrator");
Roles.CreateRole("CustomRole");
Roles.AddUserToRole("YourDomain\\DomainUser", "CustomRole");
}
In your Controller
[Authorize(Roles ="CustomRole")]
public class HomeController : Controller
{
To manage users
public class Usuario
{
public string UserName { get; set; }
public string RoleName { get; set; }
public string Name { get; set; }
public const string Domain = "YourDomain\\";
public void Delete()
{
Roles.RemoveUserFromRole(this.UserName, this.RoleName);
}
public void Save()
{
if (Roles.IsUserInRole(Usuario.Domain + this.UserName, this.RoleName) == false)
{
Roles.AddUserToRole(Usuario.Domain + this.UserName, this.RoleName);
}
}
}
Users Class
public class Usuarios : List<Usuario>
{
public void GetUsuarios() //Get application's users
{
if (Roles.RoleExists("CustomRole"))
{
foreach (string _usuario in Roles.GetUsersInRole("CustomRole"))
{
var usuario = new Usuario();
usuario.UserName = _usuario;
usuario.RoleName = "CustomRole";
this.Add(usuario);
}
}
//
public void GetUsuariosRed() //Get Network Users (AD)
{
var domainContext = new PrincipalContext(ContextType.Domain);
var groupPrincipal = GroupPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, "Domain Users");
foreach (var item in groupPrincipal.Members)
{
var usuario = new Usuario();
usuario.UserName = item.SamAccountName;
usuario.Name = item.Name;
this.Add(usuario);
}
}
You can create an "Admin" controller like this, to manage the users:
[Authorize(Roles = "AdminCustomRole")]
public class AdminController : Controller
{
//
public ActionResult Index()
{
var Usuarios = new Usuarios();
Usuarios.GetUsuarios();
return View(Usuarios);
}
[HttpGet]
public ActionResult CreateUser()
{
var Usuarios = new Usuarios();
Usuarios.GetUsuariosRed();
return View(Usuarios);
}
//
In my application, custom roles are fixed.
Hey it's my first post so I'm ask for understanding. I've looked many posts but I didn't find solution.
I want to implement custom membershiprovider class with:
namespace Mvc_car.Authorization
{
public class SimpleMembershipProvider : MembershipProvider
{
private NHibernateRepository<Uzytkownik> repo;
ISession session;
[Inject]
public SimpleMembershipProvider(ISession session)
{
this.session = session;
}
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
repo = new NHibernateRepository<Uzytkownik>(session);
base.Initialize(name, config);
}
my bindings:
kernel.Bind<ISession>().ToMethod(x => MvcApplication.SessionFactory.OpenSession()).InRequestScope();
kernel.Bind(typeof(IRepository<>)).To(typeof(NHibernateRepository<>));
kernel.Inject(Membership.Provider); //either with or without that
I've changed in web.config:
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear/>
<add name="MyMembershipProvider" type="Mvc_car.Authorization.SimpleMembershipProvider"/>
</providers>
</membership>
after try of logging following error occurs:
This method cannot be called during the application's pre-start initialization stage.
The solution to this is pretty simple. In your class containing the PreApplicationStartMethod attribute, add a static method like this.
public static class NinjectWebCommon
{
public static void InjectProviders()
{
Bootstrapper.Kernel.Inject(Membership.Provider);
Bootstrapper.Kernel.Inject(Roles.Provider);
}
...
}
When setting up your Provider, dont inject the dependencies through the constructor. Instead decorate the properties with an [Inject] attribute like this.
public class DefaultMembershipProvider : MembershipProvider
{
[Inject]
public IUserRepository UserRepository { get; set; }
}
After that, its as simple as calling NinjectWebCommon.InjectProviders() from your global.asax Application_Start() method.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
NinjectWebCommon.InjectProviders();
...
}
}