what i am testing.
This is an identity server project with a login to federated gateway. I do not control this gateway and am having issues with them not returning the proper claims back to me that i need to verify the users logins. I would like to be able to test that i can handle these errors.
For example email claim is missing without that i can not login a user.
I have created a test that tests the email claim is missing returns an error.(Works fine)
Now I am trying to test the other side of things. If the claims are in fact there it should return the user that matches to the claims returned.
The method we are testing
public static async Task<(ApplicationUser user, string provider, string providerUserUserName, IEnumerable<Claim> claims, string message)> FindUserFromExternalProvider(AuthenticateResult result, UserManager<ApplicationUser> userManager, ILogger<SegesExternalController> logger)
{
var externalUser = result.Principal;
// try to determine the unique id of the external user (issued by the provider)
var eMailClaim = externalUser.FindFirst(SegesSettingsConstants.SegesEmailClaimName);
if(eMailClaim == null) return (null, null, null, null, $"{SegesSettingsConstants.SegesEmailClaimName} claim not found.");
// remove the user id claim so we don't include it as an extra claim if/when we provision the user
var claims = externalUser.Claims.ToList();
claims.LogSegesClaims(logger);
claims.Remove(eMailClaim);
// Should we remove more claims
var provider = result.Properties.Items["scheme"];
var providerUserUserName = eMailClaim.Value;
var user = await userManager.FindByEmailAsync(providerUserUserName); // Test Breaks here
return (user, provider, providerUserUserName, claims, null);
}
Test
[Fact]
public async void Federated_login_with_email_claim_return_no_error()
{
// Arrange
var principal = new ClaimsPrincipal();
principal.AddIdentity(new ClaimsIdentity(
new Claim[] {
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "Testbruger til André"),
new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", #"PROD\Salg43"),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode", "8200"),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality", "Aarhus N"),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "test#email.com"),
},
"FakeScheme"));
var authenticateResult = AuthenticateResult.Success(new AuthenticationTicket(principal, new AuthenticationProperties() { Items = { { "scheme", "fed" } } }, "FakeScheme"));
var exprectUser = new ApplicationUser()
{
UserName = "test#email.com",
NormalizedUserName = "TEST#EMAIL.COM",
NormalizedEmail = "TEST#EMAIL.COM",
Email = "test#email.com",
Id = 123,
EmailConfirmed = true
};
var mockEmailStore = new Mock<IUserEmailStore<ApplicationUser>>();
var mockQueryableUserStore = new Mock<IQueryableUserStore<ApplicationUser>>();
var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null, null, null, null, null, null);
var logger = new Logger<ExternalController>(new LoggerFactory());
// Act
var (user, provider, providerUserUserName, claims, errorMessage) = await AuthorizationHelpers.FindUserFromExternalProvider(authenticateResult, userManager, logger);
// Assert
user.ShouldNotBeNull();
}
The issue with above.
I am trying to moq a usermanager for my unit test
var exprectUser = new ApplicationUser()
{
UserName = "test#email.com",
NormalizedUserName = "TEST#EMAIL.COM",
NormalizedEmail = "TEST#EMAIL.COM",
Email = "test#email.com",
Id = 123,
EmailConfirmed = true
};
var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null, null, null, null, null, null);
however when the method i am testing tries to find the user.
var findUser = await userManager.FindByEmailAsync("test#test.com");
it throws an error
Message: System.NotSupportedException : Store does not implement IUserEmailStore.
How do i implement IUserEmailStore in my moq usermanager?
My unit test project does contain the newest EntityFramework package.
Trying another way.
var founduser = userManager.Users.FirstOrDefault(e => e.Email.Equals("test#test.com", StringComparison.InvariantCultureIgnoreCase));
results in
System.NotSupportedException : Store does not implement IQueryableUserStore.
I think i must be moqing this wrong.
Update From comment
Ok i can moq the IUserEmailStore but I am not sure what i should do with it
var mockEmailStore = new Mock<IUserEmailStore<ApplicationUser>>();
I managed to create a full moq usermanager that lets me search on email
public class MoqUserManager : UserManager<ApplicationUser>
{
public MoqUserManager(IUserStore<ApplicationUser> userStore) : base(userStore,
new Mock<IOptions<IdentityOptions>>().Object,
new Mock<IPasswordHasher<ApplicationUser>>().Object,
new IUserValidator<ApplicationUser>[0],
new IPasswordValidator<ApplicationUser>[0],
new Mock<ILookupNormalizer>().Object,
new Mock<IdentityErrorDescriber>().Object,
new Mock<IServiceProvider>().Object,
new Mock<ILogger<UserManager<ApplicationUser>>>().Object)
{ }
public override Task<ApplicationUser> FindByEmailAsync(string email)
{
return Task.FromResult(new ApplicationUser { Email = email });
}
}
which gives me
var mockUserStore = new Mock<IUserStore<ApplicationUser>>();
mockUserStore.Setup(x => x.FindByIdAsync(exprectUser.Id.ToString(), CancellationToken.None)).ReturnsAsync(exprectUser);
var userManager = new FakeUserManager(mockUserStore.Object);
So now i can verify that the proper user is returned from my identity server matching the federated login user.
Okay your with the updated question the issue lies in
var userManager = new UserManager<ApplicationUser>(mockUserStore.Object, null, null, null, null, null, null, null, null);
This is not creating a mock, but an actual instance of UserManager<T>.
You will have to do
var userManagerMock = new Mock<UserManager<ApplicationUser>>(mockUserStore.Object, null, null, null, null, null, null, null, null);
then do an setup
userManagerMock.Setup(um => um.FindByEmailAsync("test#email.com)).Returns(exprectUser)
and pass userManagerMock.Object to your
var (user, provider, providerUserUserName, claims, errorMessage) = await AuthorizationHelpers.FindUserFromExternalProvider(authenticateResult, userManagerMock.Object, logger);
When mocking, you never want to call new on the external dependency and instead mock it, since then you can't change its behavior for a specific test. UserManager<T> should have all or most public properties as virtual, so you can override them.
Related
trying to authenticate a token through a post request, user always returns null when i check via the username.
public async Task<IActionResult> Login([FromBody] LoginModel model)
{
var user = await _userManager.FindByNameAsync(model.UserName); // user always equals null here
if (user !=null && await _userManager.CheckPasswordAsync(user,model.UserName))
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MySuperSecureKey"));
var token = new JwtSecurityToken(
issuer: "http://blah.com",
audience: "http://blah.com",
expires: DateTime.UtcNow.AddHours(1),
claims: claims,
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
return Unauthorized();
}
wondering if the problem is coming from how i seeded my database with this dummy data class?
public class DummyData
{
public static void Initialize(IApplicationBuilder app, IServiceProvider serviceProvider)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetService<HealthContext>();
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
context.Database.EnsureCreated();
//context.Database.Migrate();
// Look for any ailments
if (context.Ailments != null && context.Ailments.Any())
return; // DB has already been seeded
var ailments = GetAilments().ToArray();
context.Ailments.AddRange(ailments);
context.SaveChanges();
var medications = GetMedications().ToArray();
context.Medications.AddRange(medications);
context.SaveChanges();
var patients = GetPatients(context).ToArray();
context.Patients.AddRange(patients);
context.SaveChanges();
if (!context.Users.Any())
{
ApplicationUser user = new ApplicationUser()
{
Email = "test#test.com",
SecurityStamp = Guid.NewGuid().ToString(),
UserName = "testTest"
};
userManager.CreateAsync(user, "Password123");
context.Users.Add(user);
context.SaveChanges();
}
}
}
After I seed my database i can see my the user created in the fields, its defo there, so not understanding what seems to be the issue here!
The first thing is to check the database to confirm whether your user is successfully created . There are several issues you can check firstly :
For userManager.FindByNameAsync confirm that you are typing the AndyCosStav , not the email .
The CheckPasswordAsync returns a flag indicating whether the given password is valid for the specified user.
public virtual System.Threading.Tasks.Task<bool> CheckPasswordAsync (TUser user, string password);
The second parameter should be the password , i notice you are checking with username :
if (user !=null && await _userManager.CheckPasswordAsync(user,model.UserName))
Password123 may not match the defalut ASP.NET Core identity password
restrict , you must have at least one non alphanumeric character.
I'm currently trying to learn unit tests and I have created project in ASP.NET Core, so I can learn testing on real example. I want to test happy path for authenticate method in API Controller, so it will return OkObjectResult.
What I have so far.
Controller method i'd like to test
[AllowAnonymous]
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]User userParam)
{
var user = _userService.Authenticate(userParam.Nickname,
userParam.Password).Result;
if(user == null)
{
return BadRequest(
new { message = "Username or password is incorrect " }
);
}
return Ok(user);
}
Authenticate method in class that implements IUserService:
public async Task<User> Authenticate(string nickname, string password)
{
var user = _repository.User.GetAllUsersAsync().Result.SingleOrDefault(u => u.Nickname == nickname && u.Password == password);
if(user == null)
{
return null;
}
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescription = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.UserId.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescription);
user.Token = tokenHandler.WriteToken(token);
try
{
await _repository.User.UpdateUserAsync(user);
user.Password = null;
return user;
}
catch (Exception e)
{
return user;
}
}
And my unit test:
[Fact]
public void Authenticate_WhenCalled_ReturnsOk()
{
//Arrange
var mockService = new Mock<IUserService>();
var user = new User()
{
UserId = 4,
IsAdmin = true,
Token = "12983912803981",
IsLogged = true,
MessagesSent = null,
MessagesReceived = null,
Nickname = "test3",
Password = "Str0ngP#ssword123",
UserChannels = null
};
var controller = new UsersController(_repository, _logger, mockService.Object);
//Act
var result = controller.Authenticate(user);
//Assert
var okResult = result.Should().BeOfType<OkObjectResult>();
}
However, unit tests is returning BadRequest, not OkObjectResult as intended.
That means probably that user is actually null and it's throwing a BadRequest. Should I mock Repository instead of IUserService?
Actually, you are pretty good and doing everything perfectly (too few developers actually using AAA, which is very sad) but do remember that Mock by default returns default(T) value. So your Authenticate method is mocked and return default(User) which is null.
Just make it return your stub user:
var mockService = new Mock<IUserService>();
var user = new User()
{
UserId = 4,
IsAdmin = true,
Token = "12983912803981",
IsLogged = true,
MessagesSent = null,
MessagesReceived = null,
Nickname = "test3",
Password = "Str0ngP#ssword123",
UserChannels = null
};
mockService.Setup(x=> x.Authenticate(It.IsAny(), It.IsAny())).Returns(user);
Or more strict version proposed by #xander:
mockService.Setup(x=> x.Authenticate("test3", "Str0ngP#ssword123")).Returns(user);
It will also check that you actually using values from passed in User and not just blindly return Ok().
I am working with ASP.NET Core 2.0, using xUnit and Moq to create unit tests for administrative functions. I have an AdminController.cs that uses dependency injection for the following within its constructor
private UserManager<AppUser> userManager;
private IUserValidator<AppUser> userValidator;
private IPasswordValidator<AppUser> passwordValidator;
private IPasswordHasher<AppUser> passwordHasher;
private RoleManager<IdentityRole> roleManager;
private SignInManager<AppUser> signInManager;
I try to arrange them in the following manner in my unit test
// Arrange
Mock<EFRepository> mockRepo = new Mock<EFRepository>();
var userStoreMock = new Mock<IUserRoleStore<AppUser>>();
var userManager = new UserManager<AppUser>(userStoreMock.Object, null, null, null, null, null, null, null, null);
AppUser user = new AppUser();
var roleStoreMock = new Mock<IRoleStore<IdentityRole>>();
var userValidator = new Mock<IUserValidator<AppUser>>();
var passwordValidator = new Mock<IPasswordValidator<AppUser>>();
var passwordHasher = new Mock<IPasswordHasher<AppUser>>();
var roleManager = new RoleManager<IdentityRole>(roleStoreMock.Object, null, null, null, null, null);
var signInManager = new Mock<SignInManager<AppUser>>();
//THIS LINE CAUSES THE ERROR
AdminController controller = new AdminController(userManager, userValidator.Object, passwordValidator.Object, passwordHasher.Object, roleManager, signInManager.Object);
I get the following error:
Can not instantiate proxy of class: Microsoft.AspNetCore.Identity.SignInManager
Could not find a parameterless constructor.
I have not yet been able to find a proper way of mocking a SignInManager that works
I've been trying to work with the method below without success:
private Mock<SignInManager<AppUser>> GetMockSignInManager()
{
var mockUsrMgr = GetMockUserManager();
var mockAuthMgr = new Mock<AuthenticationManager>();
var mockContextAssosor = new Mock<IHttpContextAccessor>();
var mockClaimsFactory = new Mock<IUserClaimsPrincipalFactory<AppUser>>();
//i am unclear on how to mock the options
var opts = new Mock<IOptions<>>();
var mockLogger = new Mock<ILogger<SignInManager<AppUser>>>();
//namespace for IAuthenicationSchemeProvider is not recognized
var scheme = new Mock<IAuthenticationSchemeProvider>();
//return new Mock<SignInManager<AppUser>>(mockUsrMgr.Object, mockAuthMgr.Object...and so on);
}
private Mock<SignInManager<AppUser>> GetMockSignInManager()
{
var mockUsrMgr = new UserManager<AppUser>(userStoreMock.Object, null, null, null, null, null, null, null, null);
var ctxAccessor = new HttpContextAccessor();
var mockClaimsPrinFact = new Mock<IUserClaimsPrincipalFactory<AppUser>>();
var mockOpts = new Mock<IOptions<IdentityOptions>>();
var mockLogger = new Mock<ILogger<SignInManager<AppUser>>>();
return new Mock<SignInManager<AppUser>>(mockUsrMgr.Object, ctxAccessor, mockClaimsPrinFact.Object, mockOpts.Object, mockLogger.Object);
}
Code is as follows
startup auth:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AppId = "xxx",
AppSecret = "xxx",
BackchannelHttpHandler = new FacebookBackChannelHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.4/me?fields=id,name,email,first_name,last_name,location",
Scope = { "email" }
});
in the accountcontroler RegisterExternal class i call the following:
var info = await AuthenticationManager_GetExternalLoginInfoAsync_WithExternalBearer();
Which is this class:
private async Task<ExternalLoginInfo> AuthenticationManager_GetExternalLoginInfoAsync_WithExternalBearer()
{
ExternalLoginInfo loginInfo = null;
var result = await Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer);
if (result != null && result.Identity != null)
{
var idClaim = result.Identity.FindFirst(ClaimTypes.NameIdentifier);
if (idClaim != null)
{
loginInfo = new ExternalLoginInfo()
{
DefaultUserName = result.Identity.Name == null ? "" : result.Identity.Name.Replace(" ", ""),
Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value),
};
}
}
return loginInfo;
}
This is because the RegisterExternal class on default will use authentication type cookie. Whenever i use it it will return null, so after surfing the web I've noticed it is necessary add this code which in turn will use the bearer for authentication, this will result in a reply where the username and the identity are no longer null, thus authorized. (see picture below)
Return object (username and login)
But, when i want to claim the email, i cannot do this. It will always return null no matter what i do.
I was running into the same issue and solved it by using the Facebook nuget package to get extra fields.
In my application I have implemented FacebookAuthenticationProvider and overridden the Authenticated(...) method with the following:
public class FacebookAuthProvider : FacebookAuthenticationProvider
{
public override Task Authenticated(FacebookAuthenticatedContext context)
{
var accessTokenClaim = new Claim("ExternalAccessToken", context.AccessToken, "urn:facebook:access_token");
context.Identity.AddClaim(accessTokenClaim);
var extraClaims = GetAdditionalFacebookClaims(accessTokenClaim);
context.Identity.AddClaim(new Claim(ClaimTypes.Email, extraClaims.First(k => k.Key == "email").Value.ToString()));
context.Identity.AddClaim(new Claim("Provider", context.Identity.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Identity.FindFirstValue(ClaimTypes.Name)));
var userDetail = context.User;
var link = userDetail.Value<string>("link") ?? string.Empty;
context.Identity.AddClaim(new Claim("link", link));
context.Identity.AddClaim(new Claim("FacebookId", userDetail.Value<string>("id")));
return System.Threading.Tasks.Task.FromResult(0);
}
private static JsonObject GetAdditionalFacebookClaims(Claim accessToken)
{
var fb = new FacebookClient(accessToken.Value);
return fb.Get("me", new { fields = new[] { "email", "first_name", "last_name" } }) as JsonObject;
}
}
My Startup.cs has this within Configuration(IAppBuilder app):
FacebookAuthOptions = new Microsoft.Owin.Security.Facebook.FacebookAuthenticationOptions
{
AppId = "YOUR APP ID",
AppSecret = "YOUR APP SECRET",
Microsoft.Owin.PathString("/Account/ExternalCallBack"), // whatever your external callback url is
Provider = new FacebookAuthProvider()
};
FacebookAuthOptions.Scope.Add("email");
app.UseFacebookAuthentication(FacebookAuthOptions);
I've no idea why the email was not being populated in the first place, but this approach worked for me.
I came across a problem for seeding the database with Identity v2. I separated out the IdentityModel from the MVC5 project to my Data Access Layer where I setup EF Migrations as well. So I commented out the code which use inside "IdentityConfig.cs" to create initial user and put the code inside my seed database that looks like this
protected override void Seed(Repository.DataContext.IdentityDb context)
{
// var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
// var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
var owinContext = new OwinContext();
var userManager = owinContext.GetUserManager<ApplicationUserManager>();
var roleManager = owinContext.Get<ApplicationRoleManager>();
const string name = "admin#admin.com";
const string password = "Admin#123456";
const string roleName = "Admin";
// //Create Role Admin if it does not exist
var role = roleManager.FindByName(roleName);
if (role == null)
{
role = new IdentityRole(roleName);
var roleresult = roleManager.Create(role);
}
var user = userManager.FindByName(name);
if (user == null)
{
user = new ApplicationUser { UserName = name, Email = name };
var result = userManager.Create(user, password);
result = userManager.SetLockoutEnabled(user.Id, false);
}
// // Add user admin to Role Admin if not already added
var rolesForUser = userManager.GetRoles(user.Id);
if (!rolesForUser.Contains(role.Name))
{
var result = userManager.AddToRole(user.Id, role.Name);
}
}
Now when I am running command update-database, I got an error
Value cannot be null.
Parameter name: manager
It looks like, I am getting null in these two lines of code
var userManager = owinContext.GetUserManager<ApplicationUserManager>();
var roleManager = owinContext.Get<ApplicationRoleManager>();
Any suggestion please?
This is the way to avoid using an OWIN context:
protected override void Seed(Repository.DataContext.IdentityDb context)
var roleStore = new RoleStore<IdentityRole>(context);
var roleManager = new RoleManager<IdentityRole>(roleStore);
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new UserManager<ApplicationUser>(userStore);
var user = new ApplicationUser { UserName = "sallen" };
userManager.Create(user, "password");
roleManager.Create(new IdentityRole { Name = "admin" });
userManager.AddToRole(user.Id, "admin");
}
I got this working by using:
protected override void Seed(ApplicationDbContext context)
{
context.Configuration.LazyLoadingEnabled = true;
//var userManager = HttpContext.Current
// .GetOwinContext().GetUserManager<ApplicationUserManager>();
//var roleManager = HttpContext.Current
// .GetOwinContext().Get<ApplicationRoleManager>();
var roleStore = new RoleStore<ApplicationRole, int, ApplicationUserRole>(context);
var roleManager = new RoleManager<ApplicationRole, int>(roleStore);
var userStore = new UserStore<ApplicationUser, ApplicationRole, int, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>(context);
var userManager = new UserManager<ApplicationUser, int>(userStore);
...
Hi Under the Startup class please make sure that you have call
app.CreatePerOwinContext(ApplicationDbContext.Create); app.CreatePerOwinContextApplicationUserManager.Create);
app.CreatePerOwinContextApplicationSignInManager.Create);
app.CreatePerOwinContext(ApplicationRoleManager.Create);
Latest stuff is all async & uses Claims.
Here's what worked for me with migrations to add a super user if none exists ...
protected override void Seed(Holos.Service.Models.ApplicationDbContext context)
{
var email = "xxxx#xxxx.com";
var password = "xxxxx";
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new ApplicationUserManager(userStore);
var user = userManager.FindByEmailAsync(email).Result;
if (user == null)
{
var adminUser = new ApplicationUser() { Email = email, UserName = email };
var result = userManager.CreateAsync(adminUser, password);
result.Wait();
userManager.AddClaimAsync(adminUser.Id, new Claim("Read", "*")).Wait();
userManager.AddClaimAsync(adminUser.Id, new Claim("Create", "*")).Wait();
userManager.AddClaimAsync(adminUser.Id, new Claim("Update", "*")).Wait();
userManager.AddClaimAsync(adminUser.Id, new Claim("Delete", "*")).Wait();
userManager.AddClaimAsync(adminUser.Id, new Claim("UserType", "SuperUser")).Wait();
}
}