/// <summary> /// Deletes the user from the membership table. /// This implementation ignores the deleteAllRelatedData argument /// </summary> public override bool DeleteUser(string username, bool deleteAllRelatedData) { SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username"); try { TableServiceContext svc = this.CreateDataServiceContext(); var user = new MembershipRow(this.applicationName, username); svc.AttachTo(this.tableName, user, "*"); svc.DeleteObject(user); svc.SaveChangesWithRetries(); return true; } catch (InvalidOperationException e) { if (e.InnerException is DataServiceClientException) { var dsce = e.InnerException as DataServiceClientException; if (dsce.StatusCode == (int)HttpStatusCode.NotFound) { return false; } else { throw new ProviderException("Error accessing the data source.", e); } } else { throw; } } }
/// <summary> /// Creates a new user and stores it in the membership table. We do not use retry policies in this /// highly security-related function. All error conditions are directly exposed to the user. /// </summary> public override MembershipUser CreateUser( string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { if (!SecUtility.ValidateParameter(ref password, true, true, false, MaxTablePasswordSize)) { status = MembershipCreateStatus.InvalidPassword; return null; } string salt = GenerateSalt(); string pass = this.EncodePassword(password, (int)this.passwordFormat, salt); if (pass.Length > MaxTablePasswordSize) { status = MembershipCreateStatus.InvalidPassword; return null; } string encodedPasswordAnswer; if (passwordAnswer != null) { passwordAnswer = passwordAnswer.Trim(); } if (!string.IsNullOrEmpty(passwordAnswer)) { if (passwordAnswer.Length > MaxTablePasswordSize) { status = MembershipCreateStatus.InvalidAnswer; return null; } encodedPasswordAnswer = this.EncodePassword(passwordAnswer.ToLowerInvariant(), (int)this.passwordFormat, salt); } else { encodedPasswordAnswer = passwordAnswer; } if (!SecUtility.ValidateParameter(ref encodedPasswordAnswer, this.RequiresQuestionAndAnswer, true, false, MaxTablePasswordSize)) { status = MembershipCreateStatus.InvalidAnswer; return null; } if (!SecUtility.ValidateParameter(ref username, true, true, true, Constants.MaxTableUsernameLength)) { status = MembershipCreateStatus.InvalidUserName; return null; } if (!SecUtility.ValidateParameter( ref email, this.RequiresUniqueEmail, this.RequiresUniqueEmail, false, Constants.MaxTableUsernameLength)) { status = MembershipCreateStatus.InvalidEmail; return null; } if (!SecUtility.ValidateParameter(ref passwordQuestion, this.RequiresQuestionAndAnswer, true, false, Constants.MaxTableUsernameLength)) { status = MembershipCreateStatus.InvalidQuestion; return null; } if (providerUserKey != null) { if (!(providerUserKey is Guid)) { status = MembershipCreateStatus.InvalidProviderUserKey; return null; } } if (!this.EvaluatePasswordRequirements(password)) { status = MembershipCreateStatus.InvalidPassword; return null; } ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, password, true); OnValidatingPassword(e); if (e.Cancel) { status = MembershipCreateStatus.InvalidPassword; return null; } // Check whether a user with the same email address already exists. // The danger here is (as we don't have transaction support here) that // there are overlapping requests for creating two users with the same email // address at the same point in time. // A solution for this would be to have a separate table for email addresses. // At this point here in the code we would try to insert this user's email address into the // table and thus check whether the email is unique (the email would be the primary key of the // separate table). There are quite some problems // associated with that. For example, what happens if the user creation fails etc., stale data in the // email table etc. // Another solution is to already insert the user at this point and then check at the end of this // funcation whether the email is unique. if (this.RequiresUniqueEmail && !this.IsUniqueEmail(email)) { status = MembershipCreateStatus.DuplicateEmail; return null; } if (!this.IsUniqueUserName(username)) { status = MembershipCreateStatus.DuplicateUserName; return null; } try { TableServiceContext svc = this.CreateDataServiceContext(); var newUser = new MembershipRow(this.applicationName, username); if (providerUserKey == null) { providerUserKey = Guid.NewGuid(); } newUser.UserId = (Guid)providerUserKey; newUser.Password = pass; newUser.PasswordSalt = salt; newUser.Email = email ?? string.Empty; newUser.PasswordQuestion = passwordQuestion ?? string.Empty; newUser.PasswordAnswer = encodedPasswordAnswer ?? string.Empty; newUser.IsApproved = isApproved; newUser.PasswordFormat = (int)this.passwordFormat; DateTime now = DateTime.UtcNow; newUser.CreateDateUtc = now; newUser.LastActivityDateUtc = now; newUser.LastPasswordChangedDateUtc = now; newUser.LastLoginDateUtc = now; newUser.IsLockedOut = false; svc.AddObject(this.tableName, newUser); svc.SaveChanges(); status = MembershipCreateStatus.Success; return new MembershipUser( this.Name, username, providerUserKey, email, passwordQuestion, null, isApproved, false, now.ToLocalTime(), now.ToLocalTime(), now.ToLocalTime(), now.ToLocalTime(), ProvidersConfiguration.MinSupportedDateTime); } catch (InvalidOperationException ex) { if (ex.InnerException is DataServiceClientException && (ex.InnerException as DataServiceClientException).StatusCode == (int)HttpStatusCode.Conflict) { // in this case, some membership providers update the last activity time of the user // we don't do this in this implementation because it would add another roundtrip status = MembershipCreateStatus.DuplicateUserName; return null; } else if (ex.InnerException is DataServiceClientException) { throw new ProviderException("Cannot add user to membership data store because of problems when accessing the data store.", ex); } else { throw; } } }
private bool CheckPassword(DataServiceContext svc, string username, string password, bool updateLastLoginActivityDate, bool failIfNotApproved, out MembershipRow member) { bool createContextAndWriteState = false; try { if (svc == null) { svc = this.CreateDataServiceContext(); createContextAndWriteState = true; } member = this.GetUserFromTable(svc, username); if (member == null) { return false; } if (member.IsLockedOut) { return false; } if (!member.IsApproved && failIfNotApproved) { return false; } DateTime now = DateTime.UtcNow; string encodedPasswd = this.EncodePassword(password, member.PasswordFormat, member.PasswordSalt); bool isPasswordCorrect = member.Password.Equals(encodedPasswd); if (isPasswordCorrect && member.FailedPasswordAttemptCount == 0 && member.FailedPasswordAnswerAttemptCount == 0) { if (createContextAndWriteState) { svc.UpdateObject(member); svc.SaveChanges(); } return true; } if (!isPasswordCorrect) { if (now > member.FailedPasswordAttemptWindowStartUtc.Add(TimeSpan.FromMinutes(this.PasswordAttemptWindow))) { member.FailedPasswordAttemptWindowStartUtc = now; member.FailedPasswordAttemptCount = 1; } else { member.FailedPasswordAttemptWindowStartUtc = now; member.FailedPasswordAttemptCount++; } if (member.FailedPasswordAttemptCount >= this.MaxInvalidPasswordAttempts) { member.IsLockedOut = true; member.LastLockoutDateUtc = now; } } else { if (member.FailedPasswordAttemptCount > 0 || member.FailedPasswordAnswerAttemptCount > 0) { member.FailedPasswordAnswerAttemptWindowStartUtc = ProvidersConfiguration.MinSupportedDateTime; member.FailedPasswordAnswerAttemptCount = 0; member.FailedPasswordAttemptWindowStartUtc = ProvidersConfiguration.MinSupportedDateTime; member.FailedPasswordAttemptCount = 0; member.LastLockoutDateUtc = ProvidersConfiguration.MinSupportedDateTime; } } if (isPasswordCorrect && updateLastLoginActivityDate) { member.LastActivityDateUtc = now; member.LastLoginDateUtc = now; } if (createContextAndWriteState) { svc.UpdateObject(member); svc.SaveChanges(); } return isPasswordCorrect; } catch (Exception e) { if (e.InnerException is DataServiceClientException && (e.InnerException as DataServiceClientException).StatusCode == (int)HttpStatusCode.PreconditionFailed) { // this element was changed between read and writes Log.Write(EventKind.Warning, "A membership element has been changed between read and writes."); member = null; return false; } else { throw new ProviderException("Error accessing the data store!", e); } } }
private bool IsUniqueEmail(string email, out MembershipRow member) { member = null; SecUtility.ValidateParameter(ref email, true, true, true, ProvidersConfiguration.MaxStringPropertySizeInChars); TableServiceContext svc = this.CreateDataServiceContext(); DataServiceQuery<MembershipRow> queryObj = svc.CreateQuery<MembershipRow>(this.tableName); var query = (from user in queryObj where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(this.applicationName)) > 0 && user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(this.applicationName))) < 0 && user.Email == email && user.ProfileIsCreatedByProfileProvider == false select user).AsTableServiceQuery(); IEnumerable<MembershipRow> allUsers = query.Execute(); if (allUsers == null) { return true; } IEnumerator<MembershipRow> e = allUsers.GetEnumerator(); // e.Reset() throws a not implemented exception // according to the spec, the enumerator is at the beginning of the collections after a call to GetEnumerator() if (!e.MoveNext()) { return true; } else { member = e.Current; } return false; }
private bool CheckPassword(string username, string password, bool updateLastLoginActivityDate, bool failIfNotApproved, out MembershipRow member) { return CheckPassword(null, username, password, updateLastLoginActivityDate, failIfNotApproved, out member); }