I'm mixing classic Membership/RoleManager security setup with new WIF 4.5 API for testing purposes. I have implemented two classes that I have breakpoints set on:
public class CustomAuthenticationManager : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
// Breakpoint here is hit 1st
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
return TransformPrincipal(incomingPrincipal);
}
private ClaimsPrincipal TransformPrincipal(ClaimsPrincipal incomingPrincipal)
{
// this breakpoint is hit last
ClaimsIdentity newIdentity = new ClaimsIdentity("Custom");
newIdentity.AddClaims(incomingPrincipal.Claims);
// I add some additional claims
ClaimsPrincipal newPrincipal = new ClaimsPrincipal(newIdentity);
return newPrincipal;
}
}
public class CustomRoleProvider : RoleProvider
{
public override string[] GetRolesForUser(string username)
{
// breakpoint here is hit 2nd
if(username == "me") return new string [] { "Lead", "Developer" };
return new string[] {};
}
#region Not implemented
// bunch of not implemented methods
#endregion
}
Now result is quite fine, I get mixed ClaimsPrincipal that has both Name claim, Role claims and claims that I've added in TransformPrincipal method.
However, debugging breakpoints are hit in completely weird order:
1) Breakpoint at the beginning of Authenticate method is hit first
2) Breakpoint at the beginning of GetRolesForUser is hit second
3) Breakpoing at the beginning of TransformPrincipal is hit last
Is this just Visual Studio issue or there is an atomic chance that Authenticate might complete before GetRolesForUser is called?
How are RoleManagerModule and ClaimsAuthenticationManager working in the pipeline? In parallel or there is sequential order? Can mixing the two be an issue?
EDIT:
void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
ClaimsPrincipal transformedPrincipal = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager.Authenticate(null, ClaimsPrincipal.Current);
Thread.CurrentPrincipal = transformedPrincipal;
HttpContext.Current.User = transformedPrincipal;
}
EDIT:
<membership defaultProvider="CustomMembershipProvider">
<providers>
<add name="CustomMembershipProvider" type="Tests.CustomMembershipProvider" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider="CustomRoleProvider">
<providers>
<add name="CustomRoleProvider" type="Tests.CustomRoleProvider" />
</providers>
</roleManager>
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
<system.identityModel>
<identityConfiguration>
<claimsAuthenticationManager type="Tests.CustomAuthenticationManager, Tests"/>
</identityConfiguration>
</system.identityModel>
These two are rather not meant to be used together. SAM role management suppresses forms authentcation role management and as I suspect, your role provider is just enabled in web.config.
I haven't tested this under 4.5 but I don't believe it changed. In 4.0 the authentication manager fires only if a user is not yet authenticated or current user is established with another authentication module. The latter could happen unexpectedly when you issue both cookies, the forms cookie and the sam cookie. Take a look at my blog post on that
http://www.wiktorzychla.com/2012/09/sessionauthenticationmodule-and-dynamic.html
Although I don't know why the breakpoint at the authenticate gets hit first, SAM would notice that your user is RolePrincipal with FormsIdentity and would try to make a ClaimsPrincipal out of it and then call the authentication manager.
Edit: after all you provided, I still have a slight problem regarding the order. Although Dominick suspects roles are lazy loaded, I can't confirm this by recompiling the BCL. Rather, the RolePrincipal seems to eagerly add role claims in the constructor (the internal AttachRoleClaims method in the RolePrincipal class).
No matter how I look into it, I believe the GetRolesForUser should be called first, as this is ClaimsPrincipal.Current passed to the Authenticate.
If your RoleProvider creates the RolePrincipal in advance, the AttachRoleClaims would be called and roles would be enumerated with the RoleProvider.GetRolesForUser.
If your RoleProvider doesn't create the RolePrincipal in advance, the ClaimsPrincipal.Current would and this leads to the same execution path.
Edit2: I've found the possible culprit, it is the RoleClaimProvider::Claims method that is internally used by the AttachRoleClaims. It is implemented lazily with yield which means that until someone actually enumerates role claims, they are not created. This "someone" is your code from the TransformPrincipal:
ClaimsIdentity newIdentity = new ClaimsIdentity("Custom");
/* this forces the enumeration */
newIdentity.AddClaims(incomingPrincipal.Claims);
However, this would also mean that the GetRolesForUser would be called last, from within the TransformPrincipal.
Related
I have an ASP.NET MVC 4 application which was required to use a pre-existing membership model of users/roles. The way I did this was to implement a custom ASP.NET RoleProvider to manage access, this uses Entity Framework repositories to read user data from the database. The method to read user roles is shown below, but all the method implementations follow this pattern:
public class OurRoleProvider : RoleProvider
{
private IUserRepository _userRepository;
public OurRoleProvider() : this(Container.Resolve<IUserRepository>())
{
}
public OurRoleProvider(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public override string[] GetRolesForUser(string username)
{
var user = _userRepository.GetUserByUserName(username);
if (user.Roles.IsNullOrEmpty())
return new string[0];
return user.Roles.Select(r => r.RoleName).ToArray();
}
}
I have now come across the problem described in this post. Because a single instance of a RoleProvider is re-used for the lifetime of the application, and all other functionality creates it's own per-request DbContext to persist data, changes made to a User profile are not reflected by the RoleProvider until a restart in the application, because it's underlying DbContext is not being refreshed. This means you can remove a User from a Role, and they will still have access to that Role's functionality until an app restart.
I have tried creating a new repository instance within the RoleProvider methods, i.e. in GetRoleForUser():
var user = _userRepository.GetUserByUserName(username);
becomes
var userRepository = Container.Resolve<IUserRepository>();
var user = userRepository.GetUserByUserName(username);
This fixes the issue but breaks unit tests which don't use the DI container and inject a mock repository via the constructor. There would be a lot of unit tests to re-write.
I'd like to stick with a custom RoleProvider if possible to make use of features such as the Authorize atrribute. What I really need to do is re-instantiate the RoleProvider on a per-request basis OR force the EF repository to always update from the database. So far I haven't found a way to do this. Is this possible? Or is there a better solution?
You shouldn't define EF Services' life time as a singleton (it should be per-request life time). Because DbContext is not thread safe and also it's designed to have a short life. Activating the caching of a role provider is done somewhere else, in the web.config file:
<roleManager defaultProvider="CustomRoleProvider" cacheRolesInCookie="true" enabled="true">
<providers>
<clear />
<add name="CustomRoleProvider" type="Security.CustomRoleProvider" />
</providers>
</roleManager>
The above custom role provider is cached, beacuse it's using cacheRolesInCookie="true" here.
In my MVC 4 Web API project, I have a custom role provider that works as designed via System.Web.Mvc.Authorize attribute on my Home System.Web.Mvc.Controller.
On any System.Web.Http.ApiController with System.Web.Http.Authorize the custom role provider never gets called, always returning false. Is there a way to specify that the Web API AuthorizeAttribute pick up my custom role provider like the MVC AuthorizeAttribute?
Role Provider:
public class CustomRoleProvider : RoleProvider
{
//Overriden methods
public override string[] GetRolesForUser(string username)
{
//Always return "Master" for testing purposes
return new string[] { "Master" };
}
public override bool IsUserInRole(string username, string roleName)
{
//Always return true for testing purposes
return true;
}
//Other overridden method stubs...
}
Web.config:
<roleManager defaultProvider="CustomRoleProvider" enabled="true" cacheRolesInCookie="false" >
<providers>
<clear />
<add name="CustomRoleProvider" type="MyApp.SecurityExtensions.CustomRoleProvider, MyApp" />
</providers>
</roleManager>
This is not really an answer, but this might help:
Both attributes work by querying the current pricipal. The MVC attribute uses HTTPContent.User, while the System.Web.http version uses Thread.CurrentPrincipal, but that difference is minor.
I'm not really familar with Web API, but I suspect that the RoleManagerModule is not running by the time the attribute fires, or you have not yet reached the PostAuthenticateRequest event, because in that event the Module replaces the Pricipal.
Are you sure you have some form of ASP authentication required for your WebAPI usage? If you don't have your WebAPI project configured to require some form of authentication, then obviously you will never reach the PostAuthenticateRequest event, and thus the RoleManagerModule will never kick-in.
The last possibility that comes to mind is that someting else is replacing the Principal after the RoleManagerModule does so. If possible, temporarally remove the System.Web.Http.AuthorizeAttribute, set a breakpoint in the controller, and detemine what class Thread.CurrentPrincipal has. That might give you a hint as to where it went wrong.
You would need to use System.Web.Http.AuthorizeAttribute for Web API's controllers. Sample: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/
Well I am having following code written in master page: -
<authentication mode="Forms">
<forms loginUrl="Loginpage.aspx" />
</authentication>
Now it will redirect to "Loginpage.aspx" if authentication fails.
Now what If I would like to override this authentication for few pages. Also note that the number of pages and page names are not available at design time, so cannot include the aspx page names in configuration file.
Is there anyway to override authentication for few aspx pages?
-Anil
Henrik's answer is a good one and should work if properly implemented. However, this is another option which tackles the problem more from a configuration standpoint. I know that you mentioned that you won't know the page names ahead of time so you can't include an entry in web.config for each page BUT web.config allows you to secure folders too. You could have all pages that require authentication placed in a folder called "AuthRequired" and all pages that don't require authentication placed in a folder called "Anonymous", for example. Then in your web config you could have the following entries:
<location path="AuthRequired">
<system.web>
<authorization>
<deny users="?" />
<allow users="*" />
</authorization>
</system.web>
</location>
<location path="Anonymous">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
You can listen to the AuthorizeRequest event and act accordingly. Create your own Http Module to do this.
Three options:
use the configuration settings above together with generating folders with web.config entries. This is a pretty shoddy way of doing it.
listen to the event AuthenticateRequest, the code looks something like this:
public class UserAuthenticationModule : IHttpModule
{
private HttpApplication _Context;
private RoleManagerModule _RoleManager;
public void Init(HttpApplication context)
{
_Context = context;
context.AuthenticateRequest += AuthenticateUser;
_RoleManager = (RoleManagerModule)context.Modules["RoleManager"];
_RoleManager.GetRoles += roleManager_GetRoles;
}
// http://stackoverflow.com/questions/1727960/how-to-keep-roleprovider-from-overriding-custom-roles
private void roleManager_GetRoles(object sender, RoleManagerEventArgs e)
{
if (_Context.User is UserPrincipal)
e.RolesPopulated = true; // allows roles set in AuthenticateUser to stick.
}
private static void AuthenticateUser(object sender, EventArgs e)
{
var app = (HttpApplication) sender;
if (app.Context == null) return;
var user = app.Context.User;
// not signed in, forms authentication module takes care of redirecting etc.
if (user == null) return;
// we're done then.
if (user is IUser) return;
var userEntity = IoC.Resolve<IUserRepository>().FindByUserName(user.Identity.Name);
// we can't find the user in the database.
if (userEntity == null)
throw new ApplicationException(string.Format("User \"{0}\" deleted from, or renamed in, database while logged into application.",
user.Identity.Name));
// signed in, assigning user, which should assign Thread.CurrentPrincipal as well (it wouldn't do this on PostAuthenticateRequest).
app.Context.User = new UserPrincipal(userEntity);
userEntity.SetAuthenticated();
}
//Implement IDisposable.
public void Dispose()
{
}
}
If your UserPrincipal implements IPrincipal, then IsInRole is used to give role-based access to your pages.
The service-oriented way; set up a small transparent proxy server. List your endpoints/uri-s in a dynamic store, like what you are describing. Set up an authorization service such as Rhino Security; expose its service interfaces as a REST-API or a request/reply interface or something. Assume from the web app's perspective that every request is allowed and take care of where you redirect. In the proxy server, e.g. nginx which is a very nice asynchronous C-based proxy server on Linux, call your authorization service from a filter/module. 'Security in depth' and you can share configuration for security in the Authorization Service.
The principle that you follow is that if something is not allowed in a web application you do throw new HttpException(405, "The current operation you are trying to perform is now allowed for your role or user or chosen path in life") in the AuthorizeRequest event. Note that there's a AuthenticateRequest and another AuthorizeRequest event
You should usually have one point where users can be authenticated - get confirmed that they are who they claim they are. Next, you are probably talking about authorisation, which is a matter of allowing/denying performing certain operation to the user, like sending a GET request. Authorisation rules in a simple scenarios can be configured in the web.config through location element, as presented by Tom.
I'm trying to write a unit test for creating a new user and verifying that the desired redirect occurs. Here's my Register action, which is pretty much out-of-the-box code from a VS template:
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the user
MembershipCreateStatus createStatus;
Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError(String.Empty, ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
And the following is my test, using Moq. No matter what I set, I always get an error from one of the default MembershipCreateStatus error messages. For example:
The password provided is invalid. Please enter a valid password value
or
The password retrieval answer provided is invalid
I've tried changing the CreateUser method to only call the username, password, and email overload, but it doesn't matter. It's like there's a check somewhere that is enforcing a password policy.
public void RegisterPost_WithAuthenticatedUser_RedirectsToHomeControllerIfSuccessful()
{
// Arrange
var accountController = new AccountController();
var mockContext = GetMockRequestContext();
ControllerContext controllerContext = new ControllerContext(mockContext.Object, accountController);
accountController.ControllerContext = controllerContext;
RegisterModel registerModel = new RegisterModel() { UserName = "someone", Email = "someone#example.com", Password = "user", ConfirmPassword = "password" };
// Act
var result = accountController.Register(registerModel);
// Assert
Assert.That(result.RouteData.Values["Controller"], Is.EqualTo("Home"));
Assert.That(result.RouteData.Values["Action"], Is.EqualTo("Index"));
}
Can someone tell me what's going on here?
The static class/method issue strikes again!
the static method Membership.CreateUser is a hidden dependency here as it looks like your controller does not have a dependency, but actually it is dependent on this method, and you have not replaced (or been able to replace) this dependency in your test so you can control the interaction.
What you need to do, is to make this dependency explicit. Do this by introducing an interface which model the interaction required with the membership provider (say called IMembershipService).
Create a default implementation of this which just delegates to the existing static methods like Membership.CreateUser(). In your controller require an instance of this interface in the constructor (or create an instance of the default implementation in the default constructor if you must - not the preferred option, but...)
Then in your test create a mock of this interface and set the required expectations, and pass this mock to your controller, and verify it does what you would expect.
If you don't use a mock then you will keep creating new users in your db every time your test runs. this might be ok, if you are resetting your db every time, but using a mock is simpler and quicker in the long run, although it takes a bit more effort to set up as you have to create a no null arg constructor controller and introduce a few interfaces to break up the implicit dependencies into explicit dependencies.
The Membership.CreateUser is just a static wrapper, the task is actually delegated to the configured MembershipProvider implementation.
If you didn't configure a membership provider the default provider is used that is configured in the machine.config. For .NET v4.0 it looks like this on my machine:
<membership>
<providers>
<add name="AspNetSqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="LocalSqlServer"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="true"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="7"
minRequiredNonalphanumericCharacters="1"
passwordAttemptWindow="10"
passwordStrengthRegularExpression=""/>
</providers>
</membership>
Please note the attributes requiresQuestionAndAnswer and minRequiredPasswordLength that are enforcing certain rules by default.
I guess you do configure your own MembershipProvider in the web application, but most probably you forgot to take over this configuration for the unit test (into the app.config of the test project), and hence these default settings kick in.
I've written custom role provider, which internally uses web service methods for getting roles or usernames. This provider inherits from System.Web.Security.RoleProvider. In the web.config file I switched on .NET provided caching feature which uses cookies.
Here is how looks web.config section for this provider:
<system.web>
<roleManager defaultProvider="MyProvider"
enabled="true"
cacheRolesInCookie="true"
cookieName=".MYROLES"
cookieTimeout="30"
cookiePath="/"
cookieRequireSSL="false"
cookieSlidingExpiration="true"
cookieProtection="All">
<providers>
<clear/>
<add name="MYProvider"
type="MYProvider.MyRoleProvider, MYProvider"
Service1URL="http://localhost:54013/service1.asmx"
Service2URL="http://localhost:54013/service2.asmx"
rolesPrefix="ABC_"
domainName="abc.corp"
specialUserForAllRoles="abc"
applicationURL="http://example.com"
logCommunication="true"/>
</providers>
</roleManager>
</system.web>
Now, it comes to test if the cache is working or not. I've written simple method which looks like that:
public void TestCache()
{
string[] roles = Roles.GetAllRoles();
roles = Roles.GetAllRoles();
string[] rolesForUser1 = Roles.GetRolesForUser("user1");
rolesForUser1 = Roles.GetRolesForUser("user1");
string[] usersInRole = Roles.GetUsersInRole("ABC_DEV");
usersInRole = Roles.GetUsersInRole("ABC_DEV");
Roles.IsUserInRole("user1", "ABC_DEV");
Roles.IsUserInRole("user1", "ABC_DEV");
}
While debugging this piece of code (from test web site), debugger enters each of shown method in provider and executes all logic inside, despite the fact method induction is redundant or not. I thought that second invoke of each method should not execute because the result will be returned without asking of my provider directly from cache.
What I'm doing/thinking wrong and how to fix the caching feature ?
Regards
The role cache works only for roles of the current user. This should be cached:
var isInRole = User.IsInRole("ABC_DEV")
http://msdn.microsoft.com/en-us/library/ms164660(VS.80).aspx