I am trying to use the HttpContext.Current.User with my own class that inherits from IPrincipal. I am not exactly sure why..
var lIdentity = new UserIdentity(lFormsTicket);
var lRoles = lAuthUser.Roles.Select(x => x.Role.ToString()).ToArray();
var lPrincipal = new GenericPrincipal(lIdentity, lRoles);
System.Web.HttpContext.Current.User = lIdentity;
Right after I am setting this it works. But if I do another request on the site, it says that it cannot cast it. Am I missing something here?
You are setting User as IIdentity when it should be IPrincipal
var lIdentity = new UserIdentity(lFormsTicket);
var lRoles = lAuthUser.Roles.Select(x => x.Role.ToString()).ToArray();
var lPrincipal = new GenericPrincipal(lIdentity, lRoles);
System.Web.HttpContext.Current.User = lPrincipal;
You will probably need to use your own custom IPrincipal
public class UserPrincipal : IPrincipal {
string[] roles;
public UserPrincipal (IIdentity identity, param string[] roles) {
this.Identity = identity;
this.roles = roles ?? new string[]{ };
}
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) {
return roles.Any(s => s == role);
}
}
I'm not sure if the application will complain as it may be expecting a ClaimsPrincipal derived type.
Related
I have a class that takes an AuthenticationStateProvider in the constructor. I'd like to write unit test and mock this.
I'd like to be able to set what user is returned from the call GetAuthenticationStateAsync.
const string userId = "123";
const string userName = "John Doe";
const string email = "john.doe#test.com";
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Email, email),
};
var identity = new Mock<ClaimsIdentity>(claims);
var principal = new Mock<ClaimsPrincipal>(identity.Object);
var mockOfAuthenticationStateProvider = new Mock<AuthenticationStateProvider>();
var mockOfAuthState = new Mock<AuthenticationState>();
mockOfAuthenticationStateProvider.Setup(p =>
p.GetAuthenticationStateAsync()).Returns(Task.FromResult(mockOfAuthState.Object));
I get this errormessage:
Testfunction threw exception: System.ArgumentException: Can not
instantiate proxy of class:
Microsoft.AspNetCore.Components.Authorization.AuthenticationState.
Could not find a parameterless constructor. (Parameter
'constructorArguments') ---> System.MissingMethodException:
Constructor on type 'Castle.Proxies.AuthenticationStateProxy' not
found.
AuthenticationStateProvider is abstract, so if you can't mock it you can create an implementation of it, like this:
public class FakeAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly ClaimsPrincipal _principal;
public FakeAuthenticationStateProvider(ClaimsPrincipal principal)
{
_principal = principal;
}
// This static method isn't really necessary. You could call the
// constructor directly. I just like how it makes it more clear
// what the fake is doing within the test.
public static FakeAuthenticationStateProvider ForPrincipal(ClaimsPrincipal principal)
{
return new FakeAuthenticationStateProvider(principal);
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
return Task.FromResult(new AuthenticationState(_principal));
}
}
You can set it up like this:
const string userId = "123";
const string userName = "John Doe";
const string email = "john.doe#test.com";
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Email, email),
};
// These don't need to be mocks. If they are the test likely
// won't behave correctly.
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
var authenticationStateProvider =
FakeAuthenticationStateProvider.ForPrincipal(principal);
and then pass it to any other class that depends on AuthenticationStateProvider.
When we create a fake implementation instead of a mock it's reusable and also easier to set up, so it keeps the test a little bit smaller.
For example, if the reason you're setting this up is so that the mock/fake returns a user with certain claims, you could have the fake just take a set of claims in its constructor. Then the constructor does the rest of the work, making your test even smaller.
For example,
public class FakeAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly ClaimsPrincipal _principal;
public FakeAuthenticationStateProvider(ClaimsPrincipal principal)
{
_principal = principal;
}
public static FakeAuthenticationStateProvider ForPrincipal(ClaimsPrincipal principal)
{
return new FakeAuthenticationStateProvider(principal);
}
public static FakeAuthenticationStateProvider ThatReturnsClaims(params Claim[] claims)
{
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
return new FakeAuthenticationStateProvider(principal);
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
return Task.FromResult(new AuthenticationState(_principal));
}
}
Now your test can have less clutter:
var claims = new []
{
new Claim(ClaimTypes.NameIdentifier, "123"),
new Claim(ClaimTypes.Name, "John Doe"),
new Claim(ClaimTypes.Email, "john.doe#test.com"),
};
var authenticationStateProvider = FakeAuthenticationStateProvider.ThatReturnsClaims(claims);
I usually end up with a folder called "Fakes" in my test project for them.
I have a controller that checks the claims to get the current user's username, role, like so:
var identity = ClaimsPrincipal.Current.Identities.First();
bUsername = identity.Claims.First(c => c.Type == "Username").Value;
role = identity.Claims.First(c => c.Type == "Role").Value;
These details are set when the user first logs in. When I try to test this method, if falls over at the lines of code above because there was no login. So I tried creating a principal in the TestInitialization so it would always be available for the controllers asking for the username in the above way:
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, "Test Name"),
new Claim(ClaimTypes.NameIdentifier, "test"),
new Claim("Username", "test"),
new Claim("Role","ADMIN")
};
var identity = new ClaimsIdentity(claims, "TestAuthType");
var claimsPrincipal = new ClaimsPrincipal(identity);
I breakpointed the code, and it hits this code before it hits the above code, so it seems like it is creating the principal before checking for the username, however it still falls over on the username check, it's still not finding anything.
Can anybody advise me on what I'm doing wrong?
So, based on the advice of #DavidG, I ended up abstracting the claim stuff out of the controller. Posting this here for anyone else that needs help in the future. I created the following classes:
ICurrentUser
public interface ICurrentUser
{
User GetUserDetails();
}
CurrentUser
public class CurrentUser : ICurrentUser
{
public User GetUserDetails()
{
var user = new User();
try
{
var identity = ClaimsPrincipal.Current.Identities.First();
user.Username = identity.Claims.First(c => c.Type == "Username").Value;
user.Role = identity.Claims.First(c => c.Type == "Role").Value;
user.LoggedIn = identity.Claims.First(c => c.Type == "LoggedIn").Value;
return user;
}
catch (Exception)
{
return user;
}
}
}
Then I added it to my constructor like so:
public readonly ICurrentUser currentUser;
public readonly IUnitOfWork unitOfWork;
public HomeController(IUnitOfWork unitOfWork, ICurrentUser currentUser)
{
this.unitOfWork = unitOfWork;
this.currentUser = currentUser;
}
Then I use Ninject to inject it:
kernel.Bind<ICurrentUser>().To<CurrentUser>().InRequestScope();
Now it is loosely coupled to my controller, allowing me to mock it, like so:
[TestClass]
public class HomeControllerTest
{
private Mock<IUnitOfWork> _unitOfWorkMock;
private Mock<ICurrentUser> _currentUserMock;
private HomeController _objController;
[TestInitialize]
public void Initialize()
{
_unitOfWorkMock = new Mock<IUnitOfWork>();
_currentUserMock = new Mock<ICurrentUser>();
_currentUserMock.Setup(x => x.GetUserDetails()).Returns(new User { Username = "TEST", Role = "ADMIN", LoggedIn = "Y" });
_objController = new HomeController(_unitOfWorkMock.Object, _currentUserMock.Object);
}
}
Currently, I am authenticating users in my application using role based authentication with OAuth and WebApi. I've set this up like so:
public override async Task GrantResourceOwnerCredentials (OAuthGrantResourceOwnerCredentialsContext context)
{
var user = await AuthRepository.FindUser(context.UserName, context.Password);
if (user === null)
{
context.SetError("invalid_grant", "The username or password is incorrect");
return;
}
var id = new ClaimsIdentity(context.Options.AuthenticationType);
id.AddClaim(New Claim(ClaimTypes.Name, context.UserName));
foreach (UserRole userRole in user.UserRoles)
{
id.AddClaim(new Claim(ClaimTypes.Role, userRole.Role.Name));
}
context.Validated(id);
}
Protecting my API routes with the <Authorize> tag.
I've since, however, run into an issue where my users can hold different roles for different clients. For example:
User A can be associated to multiple clients: Client A and Client B.
User A can have different "roles" when accessing information from either client. So User A may be an Admin for Client A and a basic User for Client B.
Which means, the following example:
[Authorize(Roles = "Admin")]
[Route("api/clients/{clientId}/billingInformation")]
public IHttpActionResult GetBillingInformation(int clientId)
{
...
}
User A may access billing information for Client A, but not for Client B.
Obviously, what I have now won't work for this type of authentication. What would be the best way to set up Client specific Role based authentication? Can I simply change up what I have now, or would I have to set it up a different way entirely?
You could remove the authorize tag and do the role validation inside the function instead.
Lambda solution:
Are there roles that are added based on CustomerID and UserID?
If so you could do something like the example below where you get the customer based of the values you have and then return the response.
string userID = RequestContext.Principal.Identity.GetUserId();
var customer = Customer.WHERE(x => x.UserID == userID && x.clientId == clientId && x.Roles == '1')
Can you provide us with abit more information about what you use to store the connection/role between the Customer and User.
EDIT:
Here is an example on how you could use the ActionFilterAttribute. It gets the CustomerId from the request and then takes the UserId of the identity from the request. So you can replace [Authorize] with [UserAuthorizeAttribute]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class UserAuthorizeAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
try
{
var authHeader = actionContext.Request.Headers.GetValues("Authorization").First();
if (string.IsNullOrEmpty(authHeader))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Missing Authorization-Token")
};
return;
}
ClaimsPrincipal claimPrincipal = actionContext.Request.GetRequestContext().Principal as ClaimsPrincipal;
if (!IsAuthoticationvalid(claimPrincipal))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Invalid Authorization-Token")
};
return;
}
if (!IsUserValid(claimPrincipal))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Invalid User name or Password")
};
return;
}
//Finally role has perpession to access the particular function
if (!IsAuthorizationValid(actionContext, claimPrincipal))
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Permission Denied")
};
return;
}
}
catch (Exception ex)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Missing Authorization-Token")
};
return;
}
try
{
//AuthorizedUserRepository.GetUsers().First(x => x.Name == RSAClass.Decrypt(token));
base.OnActionExecuting(actionContext);
}
catch (Exception)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
Content = new StringContent("Unauthorized User")
};
return;
}
}
private bool IsAuthoticationvalid(ClaimsPrincipal claimPrincipal)
{
if (claimPrincipal.Identity.AuthenticationType.ToLower() == "bearer"
&& claimPrincipal.Identity.IsAuthenticated)
{
return true;
}
return false;
}
private bool IsUserValid(ClaimsPrincipal claimPrincipal)
{
string userID = claimPrincipal.Identity.GetUserId();
var securityStamp = claimPrincipal.Claims.Where(c => c.Type.Equals("AspNet.Identity.SecurityStamp", StringComparison.OrdinalIgnoreCase)).Single().Value;
var user = _context.AspNetUsers.Where(x => x.userID.Equals(userID, StringComparison.OrdinalIgnoreCase)
&& x.SecurityStamp.Equals(securityStamp, StringComparison.OrdinalIgnoreCase));
if (user != null)
{
return true;
}
return false;
}
private bool IsAuthorizationValid(HttpActionContext actionContext, ClaimsPrincipal claimPrincipal)
{
string userId = claimPrincipal.Identity.GetUserId();
string customerId = (string)actionContext.ActionArguments["CustomerId"];
return AllowedToView(userId, customerId);
}
private bool AllowedToView(string userId, string customerId)
{
var customer = _context.WHERE(x => x.UserId == userId && x.CustomerId == customerId && x.RoleId == '1')
return false;
}
}
Personally I think you need to move away from using the [Authorize] attribute entirely. It's clear that your requirements for authorisation are more complex than that method "out-the-box" was intended for.
Also in the question about I think Authentication and Authorisation are being used interchangably. What we are dealing with here is Authorisation.
Since you are using Identity and claims based authorisation. I would look at doing this "on-the-fly" so to speak. Along with claims you could make use of dynamic policy generation as well as service based Authorisation using IAuthorizationRequirement instances to build up complex rules and requirements.
Going into depth on the implementation of this is a big topic but there are some very good resources available. The original approach (that I have used myself) was orginally detailed by Dom and Brock of IdentityServer fame.
They did a comprehensive video presentation on this at NDC last year which you can watch here.
Based closely on the concepts discussed in this video Jerrie Pelser blogged about a close implementation which you can read here.
The general components are:
The [Authorize] attributes would be replaced by policy generator such as:
public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
private readonly IConfiguration _configuration;
public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options, IConfiguration configuration) : base(options)
{
_configuration = configuration;
}
public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
// Check static policies first
var policy = await base.GetPolicyAsync(policyName);
if (policy == null)
{
policy = new AuthorizationPolicyBuilder()
.AddRequirements(new HasScopeRequirement(policyName, $"https://{_configuration["Auth0:Domain"]}/"))
.Build();
}
return policy;
}
}
And then you would author any instances of IAuthorizationRequirement required to ensure users are authroised properly, an example of that would be something like:
public class HasScopeRequirement : IAuthorizationRequirement
{
public string Issuer { get; }
public string Scope { get; }
public HasScopeRequirement(string scope, string issuer)
{
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
}
}
Dom and Brock then also detail a client implementation that ties all of this together which might look something like this:
public class AuthorisationProviderClient : IAuthorisationProviderClient
{
private readonly UserManager<ApplicationUser> userManager;
private readonly RoleManager<IdentityRole> roleManager;
public AuthorisationProviderClient(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager)
{
this.userManager = userManager;
this.roleManager = roleManager;
}
public async Task<bool> IsInRole(ClaimsPrincipal user, string role)
{
var appUser = await GetApplicationUser(user);
return await userManager.IsInRoleAsync(appUser, role);
}
public async Task<List<Claim>> GetAuthorisationsForUser(ClaimsPrincipal user)
{
List<Claim> claims = new List<Claim>();
var appUser = await GetApplicationUser(user);
var roles = await userManager.GetRolesAsync(appUser);
foreach (var role in roles)
{
var idrole = await roleManager.FindByNameAsync(role);
var roleClaims = await roleManager.GetClaimsAsync(idrole);
claims.AddRange(roleClaims);
}
return claims;
}
public async Task<bool> HasClaim(ClaimsPrincipal user, string claimValue)
{
Claim required = null;
var appUser = await GetApplicationUser(user);
var userRoles = await userManager.GetRolesAsync(appUser);
foreach (var userRole in userRoles)
{
var identityRole = await roleManager.FindByNameAsync(userRole);
// this only checks the AspNetRoleClaims table
var roleClaims = await roleManager.GetClaimsAsync(identityRole);
required = roleClaims.FirstOrDefault(x => x.Value == claimValue);
if (required != null)
{
break;
}
}
if (required == null)
{
// this only checks the AspNetUserClaims table
var userClaims = await userManager.GetClaimsAsync(appUser);
required = userClaims.FirstOrDefault(x => x.Value == claimValue);
}
return required != null;
}
private async Task<ApplicationUser> GetApplicationUser(ClaimsPrincipal user)
{
return await userManager.GetUserAsync(user);
}
}
Whilst this implementation doesn't address your exact requirements (which would be hard to do anyway), this is almost certainly the approach that I would adopt given the scenario you illustrated in the question.
One solution would be to add the clients/user relationship as part of the ClaimsIdentity, and then check that with a derived AuthorizeAttribute.
You would extend the User object with a Dictionary containing all their roles and the clients that they are authorized for in that role - presumably contained in your db:
public Dictionary<string, List<int>> ClientRoles { get; set; }
In your GrantResourceOwnerCredentials method, you would add these as individual Claims with the Client Ids as the value:
foreach (var userClientRole in user.ClientRoles)
{
oAuthIdentity.AddClaim(new Claim(userClientRole.Key,
string.Join("|", userClientRole.Value)));
}
And then create a custom attribute to handle reading the claims value. The slightly tricky part here is getting the clientId value. You have given one example where it is in the route, but that may not be consistent within your application. You could consider passing it explicitly in a header, or derive whatever URL / Route parsing function works in all required circumstances.
public class AuthorizeForCustomer : System.Web.Http.AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var isAuthorized = base.IsAuthorized(actionContext);
string clientId = ""; //Get client ID from actionContext.Request;
var user = actionContext.ControllerContext.RequestContext.Principal as ClaimsPrincipal;
var claim = user.FindFirst(this.Roles);
var clientIds = claim.Value.Split('|');
return isAuthorized && clientIds.Contains(clientId);
}
}
And you would just swap
[Authorize(Roles = "Admin")] for [AuthorizeForCustomer(Roles = "Admin")]
Note that this simple example would only work with a single role, but you get the idea.
The requirement is about having users with different authorizations. Don't feel oblige to strictly match a user permissions/authorizations with his roles. Roles are part of user identity and should not depend from client.
I will suggest to decompose the requirement:
Only Admin user can access Billing domain/subsystem
//Role-based authorization
[Authorize(Roles = "Admin")]
public class BillingController {
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-2.1
User A may access billing information for Client A, but not for Client B.
Attribute could not work here because we need at least to load the concerned client.
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-2.1&tabs=aspnetcore2x
There is not a built-in way to solve this. You have to define your own merchant access right configuration system.
It might be a simple many to many table (1 user can access N merchants, 1 merchant can be accessed by N users)
public IHttpActionResult GetBillingInformation(int clientId)
{
var merchant = clientRepository.Get(clientId);
if(!UserIsConfiguredToAccessMerchant(User, merchant))
return Unauthorized();
}
NB: Claims should contain only user identity data (name, email, roles, ...). Adding authorizations, access rights claims in the token is not a good choice in my opion:
The token size might increase drastically
The user might have different authorizations regarding the domain
context or the micro service
Below some usefuls links:
https://learn.microsoft.com/en-us/dotnet/framework/security/claims-based-identity-model
https://leastprivilege.com/2016/12/16/identity-vs-permissions/
https://leastprivilege.com/2014/06/24/resourceaction-based-authorization-for-owin-and-mvc-and-web-api/
My company is moving to a more centralized model, and we want to have a service that checks the user logged into the system again groups in AD. The service should act like a plug-in, in that when we create or update an application, we should be able to add the service to the application with little to no configuration. I have some experience with Active Directory, but I just need to figure out the best way to start the project.
In your question, you have given a response that this is an MVC application. You can do something like this:
[Authorize(Role = "role 1, role2")]
public ActionResult Index()
{
//your code here
return //your return object
}
This is a class file I created that will do that checking for you and more and should do everything you need.
public class IsUserInRole
{
public bool IsInGroup(string groupName)
{
var myIdentity = GetUserIdWithDomain();
var myPrincipal = new WindowsPrincipal(myIdentity);
return myPrincipal.IsInRole(groupName);
}
public WindowsIdentity GetUserIdWithDomain()
{
var myIdentity = WindowsIdentity.GetCurrent();
return myIdentity;
}
public string GetUserId()
{
return GetUserInformation().Name;
}
public string GetUserDisplayName()
{
return GetUserInformation().DisplayName;
}
public UserPrincipal GetUserInformation()
{
var id = GetUserIdWithDomain().Name.Split('\\');
var dc = new PrincipalContext(ContextType.Domain, id[0]);
return UserPrincipal.FindByIdentity(dc, id[1]);
}
public UserPrincipal GetUserInformation(string domain, string lanId)
{
var dc = new PrincipalContext(ContextType.Domain, domain);
return UserPrincipal.FindByIdentity(dc, lanId);
}
}
I have an ASP.NET MVC Core application that I am writing unit tests for. One of the action methods uses User name for some functionality:
SettingsViewModel svm = _context.MySettings(User.Identity.Name);
which obviously fails in the unit test. I looked around and all suggestions are from .NET 4.5 to mock HttpContext. I am sure there is a better way to do that. I tried to inject IPrincipal, but it threw an error; and I even tried this (out of desperation, I suppose):
public IActionResult Index(IPrincipal principal = null) {
IPrincipal user = principal ?? User;
SettingsViewModel svm = _context.MySettings(user.Identity.Name);
return View(svm);
}
but this threw an error as well.
Couldn't find anything in the docs either...
The controller’s User is accessed through the HttpContext of the controller. The latter is stored within the ControllerContext.
The easiest way to set the user is by assigning a different HttpContext with a constructed user. We can use DefaultHttpContext for this purpose, that way we don’t have to mock everything. Then we just use that HttpContext within a controller context and pass that to the controller instance:
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, "example name"),
new Claim(ClaimTypes.NameIdentifier, "1"),
new Claim("custom-claim", "example claim value"),
}, "mock"));
var controller = new SomeController(dependencies…);
controller.ControllerContext = new ControllerContext()
{
HttpContext = new DefaultHttpContext() { User = user }
};
When creating your own ClaimsIdentity, make sure to pass an explicit authenticationType to the constructor. This makes sure that IsAuthenticated will work correctly (in case you use that in your code to determine whether a user is authenticated).
In previous versions you could have set User directly on the controller, which made for some very easy unit tests.
If you look at the source code for ControllerBase you will notice that the User is extracted from HttpContext.
/// <summary>
/// Gets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
/// </summary>
public ClaimsPrincipal User => HttpContext?.User;
and the controller accesses the HttpContext via ControllerContext
/// <summary>
/// Gets the <see cref="Http.HttpContext"/> for the executing action.
/// </summary>
public HttpContext HttpContext => ControllerContext.HttpContext;
You will notice that these two are read only properties. The good news is that ControllerContext property allows for setting it's value so that will be your way in.
So the target is to get at that object. In Core HttpContext is abstract so it is a lot easier to mock.
Assuming a controller like
public class MyController : Controller {
IMyContext _context;
public MyController(IMyContext context) {
_context = context;
}
public IActionResult Index() {
SettingsViewModel svm = _context.MySettings(User.Identity.Name);
return View(svm);
}
//...other code removed for brevity
}
Using Moq, a test could look like this
public void Given_User_Index_Should_Return_ViewResult_With_Model() {
//Arrange
var username = "FakeUserName";
var identity = new GenericIdentity(username, "");
var mockPrincipal = new Mock<ClaimsPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity);
mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);
var model = new SettingsViewModel() {
//...other code removed for brevity
};
var mockContext = new Mock<IMyContext>();
mockContext.Setup(m => m.MySettings(username)).Returns(model);
var controller = new MyController(mockContext.Object) {
ControllerContext = new ControllerContext {
HttpContext = mockHttpContext.Object
}
};
//Act
var viewResult = controller.Index() as ViewResult;
//Assert
Assert.IsNotNull(viewResult);
Assert.IsNotNull(viewResult.Model);
Assert.AreEqual(model, viewResult.Model);
}
There is also the possibility to use the existing classes, and mock only when needed.
var user = new Mock<ClaimsPrincipal>();
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext
{
User = user.Object
}
};
In my case, I needed to make use of Request.HttpContext.User.Identity.IsAuthenticated, Request.HttpContext.User.Identity.Name and some business logic sitting outside of the controller. I was able to use a combination of Nkosi's, Calin's and Poke's answer for this:
var identity = new Mock<IIdentity>();
identity.SetupGet(i => i.IsAuthenticated).Returns(true);
identity.SetupGet(i => i.Name).Returns("FakeUserName");
var mockPrincipal = new Mock<ClaimsPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity.Object);
var mockAuthHandler = new Mock<ICustomAuthorizationHandler>();
mockAuthHandler.Setup(x => x.CustomAuth(It.IsAny<ClaimsPrincipal>(), ...)).Returns(true).Verifiable();
var controller = new MyController(...);
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);
controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext()
{
User = mockPrincipal.Object
};
var result = controller.Get() as OkObjectResult;
//Assert results
mockAuthHandler.Verify();
I want to hit my Controllers directly and just use DI like AutoFac. To do this I first registering ContextController.
var identity = new GenericIdentity("Test User");
var httpContext = new DefaultHttpContext()
{
User = new GenericPrincipal(identity, null)
};
var context = new ControllerContext { HttpContext = httpContext};
builder.RegisterInstance(context);
Next I enable property injection when I register the Controllers.
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.Name.EndsWith("Controller")).PropertiesAutowired();
Then User.Identity.Name is populated, and I do not need to do anything special when calling a method on my Controller.
public async Task<ActionResult<IEnumerable<Employee>>> Get()
{
var requestedBy = User.Identity?.Name;
..................
I would look to implement an Abstract Factory Pattern.
Create an interface for a factory specifically for providing user names.
Then provide concrete classes, one which provides User.Identity.Name, and one that provides some other hard coded value that works for your tests.
You can then use the appropriate concrete class depending on production versus test code. Perhaps looking to pass the factory in as a parameter, or switching to the correct factory based on some configuration value.
interface IUserNameFactory
{
string BuildUserName();
}
class ProductionFactory : IUserNameFactory
{
public BuildUserName() { return User.Identity.Name; }
}
class MockFactory : IUserNameFactory
{
public BuildUserName() { return "James"; }
}
IUserNameFactory factory;
if(inProductionMode)
{
factory = new ProductionFactory();
}
else
{
factory = new MockFactory();
}
SettingsViewModel svm = _context.MySettings(factory.BuildUserName());
I got a brownfield .net 4.8 project that I needed to convert to .net 5.0 and I wanted to keep as much of the original code as possible, including the unit-/integration tests. The test for Controllers relied on the Context a lot so I created this Extension method to enable setting tokens, claims and headers:
public static void AddContextMock(
this ControllerBase controller,
IEnumerable<(string key, string value)> claims = null,
IEnumerable<(string key, string value)> tokens = null,
IEnumerable<(string key, string value)> headers = null)
{
HttpContext mockContext = new DefaultHttpContext();
if(claims != null)
{
mockContext.User = SetupClaims(claims);
}
if(tokens != null)
{
mockContext.RequestServices = SetupTokens(tokens);
}
if(headers != null)
{
SetupHeaders(mockContext, headers);
}
controller.ControllerContext = new ControllerContext()
{
HttpContext = mockContext
};
}
private static void SetupHeaders(HttpContext mockContext, IEnumerable<(string key, string value)> headers)
{
foreach(var header in headers)
{
mockContext.Request.Headers.Add(header.key, header.value);
}
}
private static ClaimsPrincipal SetupClaims(IEnumerable<(string key, string value)> claimValues)
{
var claims = claimValues.Select(c => new Claim(c.key, c.value));
return new ClaimsPrincipal(new ClaimsIdentity(claims, "mock"));
}
private static IServiceProvider SetupTokens(IEnumerable<(string key, string value)> tokenValues)
{
var mockServiceProvider = new Mock<IServiceProvider>();
var authenticationServiceMock = new Mock<IAuthenticationService>();
var authResult = AuthenticateResult.Success(
new AuthenticationTicket(new ClaimsPrincipal(), null));
var tokens = tokenValues.Select(t => new AuthenticationToken { Name = t.key, Value = t.value });
authResult.Properties.StoreTokens(tokens);
authenticationServiceMock
.Setup(x => x.AuthenticateAsync(It.IsAny<HttpContext>(), null))
.ReturnsAsync(authResult);
mockServiceProvider.Setup(_ => _.GetService(typeof(IAuthenticationService))).Returns(authenticationServiceMock.Object);
return mockServiceProvider.Object;
}
This uses Moq but can be adapted to other mocking frameworks. The authentication type is hardcoded to "mock" since I rely on default authentication but this could be supplied as well.
It is used as such:
_controllerUnderTest.AddContextMock(
claims: new[]
{
(ClaimTypes.Name, "UserName"),
(ClaimTypes.MobilePhone, "1234"),
},
tokens: new[]
{
("access_token", "accessTokenValue")
},
headers: new[]
{
("header", "headerValue")
});
If you're using Razor pages and want to override the claims:
[SetUp]
public void Setup()
{
var user = new ClaimsPrincipal(new ClaimsIdentity(
new Claim[] {
new("dateofbirth", "2000-10-10"),
new("surname", "Smith") },
"mock"));
_razorModel = new RazorModel()
{
PageContext = new PageContext
{
HttpContext = new DefaultHttpContext() { User = user }
}
};
}