public async Task WhenPasswordSignInAsyncIsCalled_AndEverythingIsSetup_ThenASignInResultSucceededShouldBeReturnedAsync() { //arrange var userId = "bo8w3d32q9b98"; MemberSignInManager sut = CreateSut(); var fakeUser = new MemberIdentityUser(777) { UserName = "******", }; var password = "******"; var lockoutOnFailure = false; var isPersistent = true; _memberManager.Setup(x => x.GetUserIdAsync(It.IsAny <MemberIdentityUser>())).ReturnsAsync(userId); _memberManager.Setup(x => x.GetUserNameAsync(It.IsAny <MemberIdentityUser>())).ReturnsAsync(fakeUser.UserName); _memberManager.Setup(x => x.FindByNameAsync(It.IsAny <string>())).ReturnsAsync(fakeUser); _memberManager.Setup(x => x.CheckPasswordAsync(fakeUser, password)).ReturnsAsync(true); _memberManager.Setup(x => x.IsEmailConfirmedAsync(fakeUser)).ReturnsAsync(true); _memberManager.Setup(x => x.IsLockedOutAsync(fakeUser)).ReturnsAsync(false); //act SignInResult actual = await sut.PasswordSignInAsync(fakeUser, password, isPersistent, lockoutOnFailure); //assert Assert.IsTrue(actual.Succeeded); }
public async Task PostSaveMember_SaveExisting_WhenAllIsSetupCorrectly_ExpectSuccessResponse( [Frozen] IMemberManager umbracoMembersUserManager, IMemberService memberService, IMemberTypeService memberTypeService, IMemberGroupService memberGroupService, IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IBackOfficeSecurity backOfficeSecurity, IPasswordChanger <MemberIdentityUser> passwordChanger, IOptions <GlobalSettings> globalSettings, IUser user) { // arrange var member = SetupMemberTestData(out var fakeMemberData, out var memberDisplay, ContentSaveAction.Save); var membersIdentityUser = new MemberIdentityUser(123); Mock.Get(umbracoMembersUserManager) .Setup(x => x.FindByIdAsync(It.IsAny <string>())) .ReturnsAsync(() => membersIdentityUser); Mock.Get(umbracoMembersUserManager) .Setup(x => x.ValidatePasswordAsync(It.IsAny <string>())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) .Setup(x => x.UpdateAsync(It.IsAny <MemberIdentityUser>())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) .Setup(x => x.GetRolesAsync(It.IsAny <MemberIdentityUser>())) .ReturnsAsync(() => Array.Empty <string>()); Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); Mock.Get(globalSettings); SetupUserAccess(backOfficeSecurityAccessor, backOfficeSecurity, user); SetupPasswordSuccess(umbracoMembersUserManager, passwordChanger); Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny <string>())).Returns(() => member); Mock.Get(memberService).Setup(x => x.GetById(It.IsAny <int>())).Returns(() => member); Mock.Get(memberService).SetupSequence( x => x.GetByEmail(It.IsAny <string>())) .Returns(() => null) .Returns(() => member); var sut = CreateSut( memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor, passwordChanger, globalSettings); // act var result = await sut.PostSave(fakeMemberData); // assert Assert.IsNull(result.Result); Assert.IsNotNull(result.Value); AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value); }
public async Task GivenICreateANewUser_AndTheUserIsPopulatedCorrectly_ThenIShouldGetASuccessResultAsync() { // arrange var sut = CreateSut(); var fakeUser = new MemberIdentityUser(); IMemberType fakeMemberType = new MemberType(new MockShortStringHelper(), 77); var mockMember = Mock.Of <IMember>(m => m.Name == "fakeName" && m.Email == "*****@*****.**" && m.Username == "fakeUsername" && m.RawPasswordValue == "fakePassword" && m.Comments == "hello" && m.ContentTypeAlias == fakeMemberType.Alias && m.HasIdentity == true); _mockMemberService .Setup(x => x.CreateMember(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>())) .Returns(mockMember); _mockMemberService.Setup(x => x.Save(mockMember)); // act var identityResult = await sut.CreateAsync(fakeUser, CancellationToken.None); // assert Assert.IsTrue(identityResult.Succeeded); Assert.IsTrue(!identityResult.Errors.Any()); _mockMemberService.Verify(x => x.CreateMember(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>())); _mockMemberService.Verify(x => x.Save(mockMember)); }
public async Task GivenIDeleteUser_AndTheUserIsDeletedCorrectly_ThenIShouldGetASuccessResultAsync() { // arrange var sut = CreateSut(); var fakeUser = new MemberIdentityUser(777); var fakeCancellationToken = CancellationToken.None; IMemberType fakeMemberType = new MemberType(new MockShortStringHelper(), 77); IMember mockMember = new Member(fakeMemberType) { Id = 777, Name = "fakeName", Email = "*****@*****.**", Username = "******", RawPasswordValue = "fakePassword", }; _mockMemberService.Setup(x => x.GetById(mockMember.Id)).Returns(mockMember); _mockMemberService.Setup(x => x.Delete(mockMember)); // act var identityResult = await sut.DeleteAsync(fakeUser, fakeCancellationToken); // assert Assert.IsTrue(identityResult.Succeeded); Assert.IsTrue(!identityResult.Errors.Any()); _mockMemberService.Verify(x => x.GetById(mockMember.Id)); _mockMemberService.Verify(x => x.Delete(mockMember)); _mockMemberService.VerifyNoOtherCalls(); }
public async Task GivenICreateUser_AndTheUserDoesNotHaveIdentity_ThenIShouldGetAFailedResultAsync() { // arrange var sut = CreateSut(); var fakeUser = new MemberIdentityUser(); IMemberType fakeMemberType = new MemberType(new MockShortStringHelper(), 77); var mockMember = Mock.Of <IMember>(m => m.Name == "fakeName" && m.Email == "*****@*****.**" && m.Username == "fakeUsername" && m.RawPasswordValue == "fakePassword" && m.ContentTypeAlias == fakeMemberType.Alias && m.HasIdentity == false); _mockMemberService .Setup(x => x.CreateMember(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>())) .Returns(mockMember); _mockMemberService.Setup(x => x.Save(mockMember)); // act var actual = await sut.CreateAsync(null); // assert Assert.IsFalse(actual.Succeeded); Assert.IsTrue(actual.Errors.Any(x => x.Code == "IdentityErrorUserStore" && x.Description == "Value cannot be null. (Parameter 'user')")); _mockMemberService.VerifyNoOtherCalls(); }
public void DefineMaps(IUmbracoMapper mapper) { mapper.Define <IUser, BackOfficeIdentityUser>( (source, context) => { var target = new BackOfficeIdentityUser(_globalSettings, source.Id, source.Groups); target.DisableChangeTracking(); return(target); }, (source, target, context) => { Map(source, target); target.ResetDirtyProperties(true); target.EnableChangeTracking(); }); mapper.Define <IMember, MemberIdentityUser>( (source, context) => { var target = new MemberIdentityUser(source.Id); target.DisableChangeTracking(); return(target); }, (source, target, context) => { Map(source, target); target.ResetDirtyProperties(true); target.EnableChangeTracking(); }); }
public async Task <ProfileModel> BuildForCurrentMemberAsync() { IMemberManager memberManager = _httpContextAccessor.HttpContext?.RequestServices.GetRequiredService <IMemberManager>(); if (memberManager == null) { return(null); } MemberIdentityUser member = await memberManager.GetUserAsync(_httpContextAccessor.HttpContext.User); if (member == null) { return(null); } var model = new ProfileModel { Name = member.Name, Email = member.Email, UserName = member.UserName, Comments = member.Comments, IsApproved = member.IsApproved, IsLockedOut = member.IsLockedOut, LastLockoutDate = member.LastLockoutDateUtc?.ToLocalTime(), CreatedDate = member.CreatedDateUtc.ToLocalTime(), LastLoginDate = member.LastLoginDateUtc?.ToLocalTime(), LastPasswordChangedDate = member.LastPasswordChangeDateUtc?.ToLocalTime(), RedirectUrl = _redirectUrl, Key = member.Key }; IMemberType memberType = MemberTypeService.Get(member.MemberTypeAlias); if (memberType == null) { throw new InvalidOperationException($"Could not find a member type with alias: {member.MemberTypeAlias}."); } // TODO: This wouldn't be required if we support exposing custom member properties on the MemberIdentityUser at the ASP.NET Identity level. IMember persistedMember = _memberService.GetByKey(member.Key); if (persistedMember == null) { // should never happen throw new InvalidOperationException($"Could not find a member with key: {member.Key}."); } if (_lookupProperties) { model.MemberProperties = GetMemberPropertiesViewModel(memberType, persistedMember); } return(model); }
private static IMember CreateMember(MemberIdentityUser fakeUser) { var builder = new MemberTypeBuilder(); MemberType memberType = builder.BuildSimpleMemberType(); return(new Member(memberType) { Id = 777, Username = fakeUser.UserName, }); }
public async Task GivenICreateUser_AndTheIdentityResultFailed_ThenIShouldGetAFailedResultAsync() { // arrange var sut = CreateSut(); var fakeUser = new MemberIdentityUser { PasswordConfig = "testConfig" }; // act var identityResult = await sut.CreateAsync(fakeUser); // assert Assert.IsFalse(identityResult.Succeeded); Assert.IsFalse(!identityResult.Errors.Any()); }
public async Task GivenISetNormalizedUserName_ThenIShouldGetASuccessResult() { // arrange var sut = CreateSut(); var fakeUser = new MemberIdentityUser { UserName = "******" }; // act await sut.SetNormalizedUserNameAsync(fakeUser, "NewName", CancellationToken.None); // assert Assert.AreEqual("NewName", fakeUser.UserName); Assert.AreEqual("NewName", await sut.GetNormalizedUserNameAsync(fakeUser, CancellationToken.None)); }
public PasswordVerificationResult VerifyHashedPassword(string password, string encryptedPassword, string decryptionKey) { var member = new MemberIdentityUser { PasswordConfig = null }; var sut = new MemberPasswordHasher( new LegacyPasswordSecurity(), new JsonNetSerializer(), Options.Create(new LegacyPasswordMigrationSettings { MachineKeyDecryptionKey = decryptionKey }), NullLoggerFactory.Instance.CreateLogger <MemberPasswordHasher>()); return(sut.VerifyHashedPassword(member, encryptedPassword, password)); }
/// <summary> /// Registers a new member. /// </summary> /// <param name="model">Register member model.</param> /// <param name="logMemberIn">Flag for whether to log the member in upon successful registration.</param> /// <returns>Result of registration operation.</returns> private async Task <IdentityResult> RegisterMemberAsync(RegisterModel model, bool logMemberIn = true) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); // U4-10762 Server error with "Register Member" snippet (Cannot save member with empty name) // If name field is empty, add the email address instead. if (string.IsNullOrEmpty(model.Name) && string.IsNullOrEmpty(model.Email) == false) { model.Name = model.Email; } model.Username = (model.UsernameIsEmail || model.Username == null) ? model.Email : model.Username; var identityUser = MemberIdentityUser.CreateNew(model.Username, model.Email, model.MemberTypeAlias, true, model.Name); IdentityResult identityResult = await _memberManager.CreateAsync( identityUser, model.Password); if (identityResult.Succeeded) { // Update the custom properties // TODO: See TODO in MembersIdentityUser, Should we support custom member properties for persistence/retrieval? IMember?member = _memberService.GetByKey(identityUser.Key); if (member == null) { // should never happen throw new InvalidOperationException($"Could not find a member with key: {member?.Key}."); } if (model.MemberProperties != null) { foreach (MemberPropertyModel property in model.MemberProperties.Where(p => p.Value != null) .Where(property => member.Properties.Contains(property.Alias))) { member.Properties[property.Alias]?.SetValue(property.Value); } } _memberService.Save(member); if (logMemberIn) { await _memberSignInManager.SignInAsync(identityUser, false); } } return(identityResult); }
public async Task GivenICreateANewUser_AndTheUserIsPopulatedCorrectly_ThenIShouldGetASuccessResultAsync() { //arrange MemberManager sut = CreateSut(); MemberIdentityUser fakeUser = CreateValidUser(); IMember fakeMember = CreateMember(fakeUser); MockMemberServiceForCreateMember(fakeMember); //act IdentityResult identityResult = await sut.CreateAsync(fakeUser); //assert Assert.IsTrue(identityResult.Succeeded); Assert.IsTrue(!identityResult.Errors.Any()); }
private async Task <IdentityResult> UpdateMemberAsync(ProfileModel model, MemberIdentityUser currentMember) { using IScope scope = _scopeProvider.CreateScope(autoComplete: true); currentMember.Email = model.Email; currentMember.Name = model.Name; currentMember.UserName = model.UserName; currentMember.Comments = model.Comments; IdentityResult saveResult = await _memberManager.UpdateAsync(currentMember); if (!saveResult.Succeeded) { return(saveResult); } // now we can update the custom properties // TODO: Ideally we could do this all through our MemberIdentityUser IMember member = _memberService.GetByKey(currentMember.Key); if (member == null) { // should never happen throw new InvalidOperationException($"Could not find a member with key: {member.Key}."); } IMemberType memberType = _memberTypeService.Get(member.ContentTypeId); if (model.MemberProperties != null) { foreach (MemberPropertyModel property in model.MemberProperties //ensure the property they are posting exists .Where(p => memberType.PropertyTypeExists(p.Alias)) .Where(property => member.Properties.Contains(property.Alias)) //needs to be editable .Where(p => memberType.MemberCanEditProperty(p.Alias))) { member.Properties[property.Alias].SetValue(property.Value); } } _memberService.Save(member); return(saveResult); }
public async Task WhenPasswordSignInAsyncIsCalled_AndTheResultFails_ThenASignInFailedResultShouldBeReturnedAsync() { //arrange MemberSignInManager sut = CreateSut(); var fakeUser = new MemberIdentityUser(777) { UserName = "******", }; var password = "******"; var lockoutOnFailure = false; var isPersistent = true; //act SignInResult actual = await sut.PasswordSignInAsync(fakeUser, password, isPersistent, lockoutOnFailure); //assert Assert.IsFalse(actual.Succeeded); }
public async Task GivenAUserExists_AndIncorrectCredentialsAreProvided_ThenACheckOfCredentialsShouldFail() { //arrange var password = "******"; MemberManager sut = CreateSut(); MemberIdentityUser fakeUser = CreateValidUser(); IMember fakeMember = CreateMember(fakeUser); MockMemberServiceForCreateMember(fakeMember); _mockMemberService.Setup(x => x.GetByUsername(It.Is <string>(y => y == fakeUser.UserName))).Returns(fakeMember); _mockPasswordHasher.Setup(x => x.VerifyHashedPassword(It.IsAny <MemberIdentityUser>(), It.IsAny <string>(), It.IsAny <string>())).Returns(PasswordVerificationResult.Failed); //act await sut.CreateAsync(fakeUser); var result = await sut.ValidateCredentialsAsync(fakeUser.UserName, password); //assert Assert.IsFalse(result); }
public async Task <IActionResult> HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model) { if (ModelState.IsValid == false) { return(CurrentUmbracoPage()); } MergeRouteValuesToModel(model); MemberIdentityUser currentMember = await _memberManager.GetUserAsync(HttpContext.User); if (currentMember == null) { // this shouldn't happen, we also don't want to return an error so just redirect to where we came from return(RedirectToCurrentUmbracoPage()); } IdentityResult result = await UpdateMemberAsync(model, currentMember); if (!result.Succeeded) { AddErrors(result); return(CurrentUmbracoPage()); } TempData["FormSuccess"] = true; // If there is a specified path to redirect to then use it. if (model.RedirectUrl.IsNullOrWhiteSpace() == false) { return(Redirect(model.RedirectUrl)); } // Redirect to current page by default. return(RedirectToCurrentUmbracoPage()); }
public async Task <PublicAccessStatus> HasMemberAccessToContentAsync(int publishedContentId) { HttpContext httpContext = _httpContextAccessor.GetRequiredHttpContext(); IMemberManager memberManager = httpContext.RequestServices.GetRequiredService <IMemberManager>(); if (httpContext.User.Identity == null || !httpContext.User.Identity.IsAuthenticated) { return(PublicAccessStatus.NotLoggedIn); } MemberIdentityUser currentMember = await memberManager.GetUserAsync(httpContext.User); if (currentMember == null) { return(PublicAccessStatus.NotLoggedIn); } var username = currentMember.UserName; IList <string> userRoles = await memberManager.GetRolesAsync(currentMember); if (!currentMember.IsApproved) { return(PublicAccessStatus.NotApproved); } if (currentMember.IsLockedOut) { return(PublicAccessStatus.LockedOut); } if (!_publicAccessService.HasAccess(publishedContentId, _contentService, username, userRoles)) { return(PublicAccessStatus.AccessDenied); } return(PublicAccessStatus.AccessAccepted); }
// Umbraco.Code.MapAll -Id -LockoutEnabled -PhoneNumber -PhoneNumberConfirmed -TwoFactorEnabled -ConcurrencyStamp -NormalizedEmail -NormalizedUserName -Roles private void Map(IMember source, MemberIdentityUser target) { target.Email = source.Email; target.UserName = source.Username; target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate?.ToUniversalTime(); target.LastLoginDateUtc = source.LastLoginDate?.ToUniversalTime(); target.EmailConfirmed = source.EmailConfirmedDate.HasValue; target.Name = source.Name; target.AccessFailedCount = source.FailedPasswordAttempts; target.PasswordHash = GetPasswordHash(source.RawPasswordValue); target.PasswordConfig = source.PasswordConfiguration; target.IsApproved = source.IsApproved; target.SecurityStamp = source.SecurityStamp; target.LockoutEnd = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null; target.Comments = source.Comments; target.LastLockoutDateUtc = source.LastLockoutDate == DateTime.MinValue ? null : source.LastLockoutDate?.ToUniversalTime(); target.CreatedDateUtc = source.CreateDate.ToUniversalTime(); target.Key = source.Key; target.MemberTypeAlias = source.ContentTypeAlias; // NB: same comments re AutoMapper as per BackOfficeUser }
/// <summary> /// Create a member from the supplied member content data /// /// All member password processing and creation is done via the identity manager /// </summary> /// <param name="contentItem">Member content data</param> /// <returns>The identity result of the created member</returns> private async Task <ActionResult <bool> > CreateMemberAsync(MemberSave contentItem) { IMemberType?memberType = _memberTypeService.Get(contentItem.ContentTypeAlias); if (memberType == null) { throw new InvalidOperationException($"No member type found with alias {contentItem.ContentTypeAlias}"); } var identityMember = MemberIdentityUser.CreateNew( contentItem.Username, contentItem.Email, memberType.Alias, contentItem.IsApproved, contentItem.Name); IdentityResult created = await _memberManager.CreateAsync(identityMember, contentItem.Password?.NewPassword); if (created.Succeeded == false) { MemberDisplay?forDisplay = _umbracoMapper.Map <MemberDisplay>(contentItem.PersistedContent); foreach (IdentityError error in created.Errors) { switch (error.Code) { case nameof(IdentityErrorDescriber.InvalidUserName): ModelState.AddPropertyError( new ValidationResult(error.Description, new[] { "value" }), string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); break; case nameof(IdentityErrorDescriber.PasswordMismatch): case nameof(IdentityErrorDescriber.PasswordRequiresDigit): case nameof(IdentityErrorDescriber.PasswordRequiresLower): case nameof(IdentityErrorDescriber.PasswordRequiresNonAlphanumeric): case nameof(IdentityErrorDescriber.PasswordRequiresUniqueChars): case nameof(IdentityErrorDescriber.PasswordRequiresUpper): case nameof(IdentityErrorDescriber.PasswordTooShort): ModelState.AddPropertyError( new ValidationResult(error.Description, new[] { "value" }), string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); break; case nameof(IdentityErrorDescriber.InvalidEmail): ModelState.AddPropertyError( new ValidationResult(error.Description, new[] { "value" }), string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); break; case nameof(IdentityErrorDescriber.DuplicateUserName): ModelState.AddPropertyError( new ValidationResult(error.Description, new[] { "value" }), string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); break; case nameof(IdentityErrorDescriber.DuplicateEmail): ModelState.AddPropertyError( new ValidationResult(error.Description, new[] { "value" }), string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); break; } } return(ValidationProblem(forDisplay, ModelState)); } // now re-look up the member, which will now exist IMember?member = _memberService.GetByEmail(contentItem.Email); if (member is null) { return(false); } // map the save info over onto the user member = _umbracoMapper.Map <MemberSave, IMember>(contentItem, member); int creatorId = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1; member.CreatorId = creatorId; // assign the mapped property values that are not part of the identity properties string[] builtInAliases = ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper).Select(x => x.Key).ToArray(); foreach (ContentPropertyBasic property in contentItem.Properties) { if (builtInAliases.Contains(property.Alias) == false) { member.Properties[property.Alias]?.SetValue(property.Value); } } // now the member has been saved via identity, resave the member with mapped content properties _memberService.Save(member); contentItem.PersistedContent = member; ActionResult <bool> rolesChanged = await AddOrUpdateRoles(contentItem.Groups, identityMember); if (!rolesChanged.Value && rolesChanged.Result != null) { return(rolesChanged.Result); } return(true); }
public async Task GivenIUpdateAUser_ThenIShouldGetASuccessResultAsync() { // arrange var sut = CreateSut(); var fakeUser = new MemberIdentityUser { Id = "123", Name = "fakeName", Email = "*****@*****.**", UserName = "******", Comments = "hello", LastLoginDateUtc = DateTime.UtcNow, LastPasswordChangeDateUtc = DateTime.UtcNow, EmailConfirmed = true, AccessFailedCount = 3, LockoutEnd = DateTime.UtcNow.AddDays(10), IsApproved = true, PasswordHash = "abcde", SecurityStamp = "abc", }; fakeUser.Roles.Add(new IdentityUserRole <string> { RoleId = "role1", UserId = "123" }); fakeUser.Roles.Add(new IdentityUserRole <string> { RoleId = "role2", UserId = "123" }); IMemberType fakeMemberType = new MemberType(new MockShortStringHelper(), 77); var mockMember = Mock.Of <IMember>(m => m.Id == 123 && m.Name == "a" && m.Email == "*****@*****.**" && m.Username == "c" && m.RawPasswordValue == "d" && m.Comments == "e" && m.ContentTypeAlias == fakeMemberType.Alias && m.HasIdentity == true && m.EmailConfirmedDate == DateTime.MinValue && m.FailedPasswordAttempts == 0 && m.LastLockoutDate == DateTime.MinValue && m.IsApproved == false && m.RawPasswordValue == "xyz" && m.SecurityStamp == "xyz"); _mockMemberService.Setup(x => x.Save(mockMember)); _mockMemberService.Setup(x => x.GetById(123)).Returns(mockMember); // act var identityResult = await sut.UpdateAsync(fakeUser, CancellationToken.None); // assert Assert.IsTrue(identityResult.Succeeded); Assert.IsTrue(!identityResult.Errors.Any()); Assert.AreEqual(fakeUser.Name, mockMember.Name); Assert.AreEqual(fakeUser.Email, mockMember.Email); Assert.AreEqual(fakeUser.UserName, mockMember.Username); Assert.AreEqual(fakeUser.Comments, mockMember.Comments); Assert.AreEqual(fakeUser.LastPasswordChangeDateUtc.Value.ToLocalTime(), mockMember.LastPasswordChangeDate); Assert.AreEqual(fakeUser.LastLoginDateUtc.Value.ToLocalTime(), mockMember.LastLoginDate); Assert.AreEqual(fakeUser.AccessFailedCount, mockMember.FailedPasswordAttempts); Assert.AreEqual(fakeUser.IsLockedOut, mockMember.IsLockedOut); Assert.AreEqual(fakeUser.IsApproved, mockMember.IsApproved); Assert.AreEqual(fakeUser.PasswordHash, mockMember.RawPasswordValue); Assert.AreEqual(fakeUser.SecurityStamp, mockMember.SecurityStamp); Assert.AreNotEqual(DateTime.MinValue, mockMember.EmailConfirmedDate.Value); _mockMemberService.Verify(x => x.Save(mockMember)); _mockMemberService.Verify(x => x.GetById(123)); _mockMemberService.Verify(x => x.ReplaceRoles(new[] { 123 }, new[] { "role1", "role2" })); }
private void MockGetUserAsync(IMemberManager memberManager, MemberIdentityUser memberIdentityUser) => Mock.Get(memberManager).Setup(x => x.GetUserAsync(It.IsAny <ClaimsPrincipal>())).Returns(Task.FromResult(memberIdentityUser));
public async Task<IActionResult> ExternalLoginCallback(string returnUrl) { var errors = new List<string>(); ExternalLoginInfo loginInfo = await _memberSignInManager.GetExternalLoginInfoAsync(); if (loginInfo is null) { errors.Add("Invalid response from the login provider"); } else { SignInResult result = await _memberSignInManager.ExternalLoginSignInAsync(loginInfo, false); if (result == SignInResult.Success) { // Update any authentication tokens if succeeded await _memberSignInManager.UpdateExternalAuthenticationTokensAsync(loginInfo); return RedirectToLocal(returnUrl); } if (result == SignInResult.TwoFactorRequired) { MemberIdentityUser attemptedUser = await _memberManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey); if (attemptedUser == null) { return new ValidationErrorResult( $"No local user found for the login provider {loginInfo.LoginProvider} - {loginInfo.ProviderKey}"); } // create a with information to display a custom two factor send code view var verifyResponse = new ObjectResult(new { userId = attemptedUser.Id }) { StatusCode = StatusCodes.Status402PaymentRequired }; return verifyResponse; } if (result == SignInResult.LockedOut) { errors.Add( $"The local member {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} is locked out."); } else if (result == SignInResult.NotAllowed) { // This occurs when SignInManager.CanSignInAsync fails which is when RequireConfirmedEmail , RequireConfirmedPhoneNumber or RequireConfirmedAccount fails // however since we don't enforce those rules (yet) this shouldn't happen. errors.Add( $"The member {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} has not confirmed their details and cannot sign in."); } else if (result == SignInResult.Failed) { // Failed only occurs when the user does not exist errors.Add("The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked before it can be used."); } else if (result == MemberSignInManager.ExternalLoginSignInResult.NotAllowed) { // This occurs when the external provider has approved the login but custom logic in OnExternalLogin has denined it. errors.Add( $"The user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} has not been accepted and cannot sign in."); } else if (result == MemberSignInManager.AutoLinkSignInResult.FailedNotLinked) { errors.Add("The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked from the back office."); } else if (result == MemberSignInManager.AutoLinkSignInResult.FailedNoEmail) { errors.Add( $"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked."); } else if (result is MemberSignInManager.AutoLinkSignInResult autoLinkSignInResult && autoLinkSignInResult.Errors.Count > 0) { errors.AddRange(autoLinkSignInResult.Errors); }
/// <summary> /// Update existing member data /// </summary> /// <param name="contentItem">The member to save</param> /// <remarks> /// We need to use both IMemberService and ASP.NET Identity to do our updates because Identity is responsible for passwords/security. /// When this method is called, the IMember will already have updated/mapped values from the http POST. /// So then we do this in order: /// 1. Deal with sensitive property values on IMember /// 2. Use IMemberService to persist all changes /// 3. Use ASP.NET and MemberUserManager to deal with lockouts /// 4. Use ASP.NET, MemberUserManager and password changer to deal with passwords /// 5. Deal with groups/roles /// </remarks> private async Task <ActionResult <bool> > UpdateMemberAsync(MemberSave contentItem) { if (contentItem.PersistedContent is not null) { contentItem.PersistedContent.WriterId = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1; } // If the user doesn't have access to sensitive values, then we need to check if any of the built in member property types // have been marked as sensitive. If that is the case we cannot change these persisted values no matter what value has been posted. // There's only 3 special ones we need to deal with that are part of the MemberSave instance: Comments, IsApproved, IsLockedOut // but we will take care of this in a generic way below so that it works for all props. if (!_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() ?? true) { IMemberType?memberType = contentItem.PersistedContent is null ? null : _memberTypeService.Get(contentItem.PersistedContent.ContentTypeId); var sensitiveProperties = memberType? .PropertyTypes.Where(x => memberType.IsSensitiveProperty(x.Alias)) .ToList(); if (sensitiveProperties is not null) { foreach (IPropertyType sensitiveProperty in sensitiveProperties) { // TODO: This logic seems to deviate from the logic that is in v8 where we are explitly checking // against 3 properties: Comments, IsApproved, IsLockedOut, is the v8 version incorrect? ContentPropertyBasic?destProp = contentItem.Properties.FirstOrDefault(x => x.Alias == sensitiveProperty.Alias); if (destProp != null) { // if found, change the value of the contentItem model to the persisted value so it remains unchanged object?origValue = contentItem.PersistedContent?.GetValue(sensitiveProperty.Alias); destProp.Value = origValue; } } } } if (contentItem.PersistedContent is not null) { // First save the IMember with mapped values before we start updating data with aspnet identity _memberService.Save(contentItem.PersistedContent); } bool needsResync = false; MemberIdentityUser identityMember = await _memberManager.FindByIdAsync(contentItem.Id?.ToString()); if (identityMember == null) { return(ValidationProblem("Member was not found")); } // Handle unlocking with the member manager (takes care of other nuances) if (identityMember.IsLockedOut && contentItem.IsLockedOut == false) { IdentityResult unlockResult = await _memberManager.SetLockoutEndDateAsync(identityMember, DateTimeOffset.Now.AddMinutes(-1)); if (unlockResult.Succeeded == false) { return(ValidationProblem( $"Could not unlock for member {contentItem.Id} - error {unlockResult.Errors.ToErrorMessage()}")); } needsResync = true; } else if (identityMember.IsLockedOut == false && contentItem.IsLockedOut) { // NOTE: This should not ever happen unless someone is mucking around with the request data. // An admin cannot simply lock a user, they get locked out by password attempts, but an admin can unlock them return(ValidationProblem("An admin cannot lock a member")); } // If we're changing the password... // Handle changing with the member manager & password changer (takes care of other nuances) if (contentItem.Password != null) { IdentityResult validatePassword = await _memberManager.ValidatePasswordAsync(contentItem.Password.NewPassword); if (validatePassword.Succeeded == false) { return(ValidationProblem(validatePassword.Errors.ToErrorMessage())); } if (!int.TryParse(identityMember.Id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) { return(ValidationProblem("Member ID was not valid")); } var changingPasswordModel = new ChangingPasswordModel { Id = intId, OldPassword = contentItem.Password.OldPassword, NewPassword = contentItem.Password.NewPassword, }; // Change and persist the password Attempt <PasswordChangedModel?> passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _memberManager); if (!passwordChangeResult.Success) { foreach (string memberName in passwordChangeResult.Result?.ChangeError?.MemberNames ?? Enumerable.Empty <string>()) { ModelState.AddModelError(memberName, passwordChangeResult.Result?.ChangeError?.ErrorMessage ?? string.Empty); } return(ValidationProblem(ModelState)); } needsResync = true; } // Update the roles and check for changes ActionResult <bool> rolesChanged = await AddOrUpdateRoles(contentItem.Groups, identityMember); if (!rolesChanged.Value && rolesChanged.Result != null) { return(rolesChanged.Result); } else { needsResync = true; } // If there have been underlying changes made by ASP.NET Identity, then we need to resync the // IMember on the PersistedContent with what is stored since it will be mapped to display. if (needsResync && contentItem.PersistedContent is not null) { contentItem.PersistedContent = _memberService.GetById(contentItem.PersistedContent.Id) !; } return(true); }
public async Task <IActionResult> ExternalLoginCallback(string returnUrl) { var errors = new List <string>(); ExternalLoginInfo?loginInfo = await _memberSignInManager.GetExternalLoginInfoAsync(); if (loginInfo is null) { errors.Add("Invalid response from the login provider"); } else { SignInResult result = await _memberSignInManager.ExternalLoginSignInAsync(loginInfo, false, _securitySettings.Value.MemberBypassTwoFactorForExternalLogins); if (result == SignInResult.Success) { // Update any authentication tokens if succeeded await _memberSignInManager.UpdateExternalAuthenticationTokensAsync(loginInfo); return(RedirectToLocal(returnUrl)); } if (result == SignInResult.TwoFactorRequired) { MemberIdentityUser attemptedUser = await _memberManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey); if (attemptedUser == null) { return(new ValidationErrorResult( $"No local user found for the login provider {loginInfo.LoginProvider} - {loginInfo.ProviderKey}")); } var providerNames = await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(attemptedUser.Key); ViewData.SetTwoFactorProviderNames(providerNames); return(CurrentUmbracoPage()); } if (result == SignInResult.LockedOut) { errors.Add( $"The local member {loginInfo.Principal.Identity?.Name} for the external provider {loginInfo.ProviderDisplayName} is locked out."); } else if (result == SignInResult.NotAllowed) { // This occurs when SignInManager.CanSignInAsync fails which is when RequireConfirmedEmail , RequireConfirmedPhoneNumber or RequireConfirmedAccount fails // however since we don't enforce those rules (yet) this shouldn't happen. errors.Add( $"The member {loginInfo.Principal.Identity?.Name} for the external provider {loginInfo.ProviderDisplayName} has not confirmed their details and cannot sign in."); } else if (result == SignInResult.Failed) { // Failed only occurs when the user does not exist errors.Add("The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked before it can be used."); } else if (result == MemberSignInManager.ExternalLoginSignInResult.NotAllowed) { // This occurs when the external provider has approved the login but custom logic in OnExternalLogin has denined it. errors.Add( $"The user {loginInfo.Principal.Identity?.Name} for the external provider {loginInfo.ProviderDisplayName} has not been accepted and cannot sign in."); } else if (result == MemberSignInManager.AutoLinkSignInResult.FailedNotLinked) { errors.Add("The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked from the back office."); } else if (result == MemberSignInManager.AutoLinkSignInResult.FailedNoEmail) { errors.Add( $"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked."); } else if (result is MemberSignInManager.AutoLinkSignInResult autoLinkSignInResult && autoLinkSignInResult.Errors.Count > 0) { errors.AddRange(autoLinkSignInResult.Errors); }
/// <summary> /// Add or update the identity roles /// </summary> /// <param name="groups">The groups to updates</param> /// <param name="identityMember">The member as an identity user</param> private async Task <ActionResult <bool> > AddOrUpdateRoles(IEnumerable <string>?groups, MemberIdentityUser identityMember) { var hasChanges = false; // We're gonna look up the current roles now because the below code can cause // events to be raised and developers could be manually adding roles to members in // their handlers. If we don't look this up now there's a chance we'll just end up // removing the roles they've assigned. IEnumerable <string> currentRoles = await _memberManager.GetRolesAsync(identityMember); // find the ones to remove and remove them IEnumerable <string> roles = currentRoles.ToList(); string[] rolesToRemove = roles.Except(groups ?? Enumerable.Empty <string>()).ToArray(); // Now let's do the role provider stuff - now that we've saved the content item (that is important since // if we are changing the username, it must be persisted before looking up the member roles). if (rolesToRemove.Any()) { IdentityResult identityResult = await _memberManager.RemoveFromRolesAsync(identityMember, rolesToRemove); if (!identityResult.Succeeded) { return(ValidationProblem(identityResult.Errors.ToErrorMessage())); } hasChanges = true; } // find the ones to add and add them string[]? toAdd = groups?.Except(roles).ToArray(); if (toAdd?.Any() ?? false) { // add the ones submitted IdentityResult identityResult = await _memberManager.AddToRolesAsync(identityMember, toAdd); if (!identityResult.Succeeded) { return(ValidationProblem(identityResult.Errors.ToErrorMessage())); } hasChanges = true; } return(hasChanges); }
public async Task PostSaveMember_SaveExistingMember_WithNoRoles_Add1Role_ExpectSuccessResponse( [Frozen] IMemberManager umbracoMembersUserManager, IMemberService memberService, IMemberTypeService memberTypeService, IMemberGroupService memberGroupService, IDataTypeService dataTypeService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IBackOfficeSecurity backOfficeSecurity, IPasswordChanger <MemberIdentityUser> passwordChanger, IOptions <GlobalSettings> globalSettings, IUser user) { // arrange var roleName = "anyrole"; IMember member = SetupMemberTestData(out MemberSave fakeMemberData, out MemberDisplay memberDisplay, ContentSaveAction.Save); fakeMemberData.Groups = new List <string>() { roleName }; var membersIdentityUser = new MemberIdentityUser(123); Mock.Get(umbracoMembersUserManager) .Setup(x => x.FindByIdAsync(It.IsAny <string>())) .ReturnsAsync(() => membersIdentityUser); Mock.Get(umbracoMembersUserManager) .Setup(x => x.ValidatePasswordAsync(It.IsAny <string>())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) .Setup(x => x.UpdateAsync(It.IsAny <MemberIdentityUser>())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(umbracoMembersUserManager) .Setup(x => x.AddToRolesAsync(It.IsAny <MemberIdentityUser>(), It.IsAny <IEnumerable <string> >())) .ReturnsAsync(() => IdentityResult.Success); Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity); Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny <string>())).Returns(() => member); Mock.Get(memberService).Setup(x => x.GetById(It.IsAny <int>())).Returns(() => member); SetupUserAccess(backOfficeSecurityAccessor, backOfficeSecurity, user); SetupPasswordSuccess(umbracoMembersUserManager, passwordChanger); Mock.Get(memberService).SetupSequence( x => x.GetByEmail(It.IsAny <string>())) .Returns(() => null) .Returns(() => member); MemberController sut = CreateSut(memberService, memberTypeService, memberGroupService, umbracoMembersUserManager, dataTypeService, backOfficeSecurityAccessor, passwordChanger, globalSettings, user); // act ActionResult <MemberDisplay> result = await sut.PostSave(fakeMemberData); // assert Assert.IsNull(result.Result); Assert.IsNotNull(result.Value); Mock.Get(umbracoMembersUserManager) .Verify(u => u.GetRolesAsync(membersIdentityUser)); Mock.Get(umbracoMembersUserManager) .Verify(u => u.AddToRolesAsync(membersIdentityUser, new[] { roleName })); Mock.Get(memberService) .Verify(m => m.Save(It.IsAny <Member>())); AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value); }
public async Task <IActionResult> HandleLogin([Bind(Prefix = "loginModel")] LoginModel model) { if (ModelState.IsValid == false) { return(CurrentUmbracoPage()); } MergeRouteValuesToModel(model); // Sign the user in with username/password, this also gives a chance for developers to // custom verify the credentials and auto-link user accounts with a custom IBackOfficePasswordChecker SignInResult result = await _signInManager.PasswordSignInAsync( model.Username, model.Password, isPersistent : model.RememberMe, lockoutOnFailure : true); if (result.Succeeded) { TempData["LoginSuccess"] = true; // If there is a specified path to redirect to then use it. if (model.RedirectUrl.IsNullOrWhiteSpace() == false) { // Validate the redirect URL. // If it's not a local URL we'll redirect to the root of the current site. return(Redirect(Url.IsLocalUrl(model.RedirectUrl) ? model.RedirectUrl : CurrentPage !.AncestorOrSelf(1) !.Url(PublishedUrlProvider))); } // Redirect to current URL by default. // This is different from the current 'page' because when using Public Access the current page // will be the login page, but the URL will be on the requested page so that's where we need // to redirect too. return(RedirectToCurrentUmbracoUrl()); } if (result.RequiresTwoFactor) { MemberIdentityUser attemptedUser = await _memberManager.FindByNameAsync(model.Username); if (attemptedUser == null) { return(new ValidationErrorResult( $"No local member found for username {model.Username}")); } var providerNames = await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(attemptedUser.Key); ViewData.SetTwoFactorProviderNames(providerNames); } else if (result.IsLockedOut) { ModelState.AddModelError("loginModel", "Member is locked out"); } else if (result.IsNotAllowed) { ModelState.AddModelError("loginModel", "Member is not allowed"); } else { ModelState.AddModelError("loginModel", "Invalid username or password"); } return(CurrentUmbracoPage()); }