private void CreateOrUpdateUserAndProfile(string username, bool authenticated, DateTime now, string blobName, int size) { SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username"); providerRetry(() => { TableServiceContext context = CreateDataServiceContext(); DataServiceQuery<MembershipRow> queryObj = context.CreateQuery<MembershipRow>(tableName); var query = (from user in queryObj where user.PartitionKey == SecUtility.CombineToKey(applicationName, username) select user).AsTableServiceQuery(); IEnumerable<MembershipRow> users = query.Execute(); // instantiate results List<MembershipRow> userList = null; if (users != null) { userList = new List<MembershipRow>(users); } if (userList != null && userList.Count > 0) { MembershipRow current = userList.First(); if (current.IsAnonymous != !authenticated) { // this is an error because we would need to create a user with the same name // this should not happen throw new ProviderException( "A user with the same name but with a different authentication status already exists!"); } current.LastActivityDateUtc = now; current.ProfileBlobName = blobName; current.ProfileSize = size; current.ProfileLastUpdatedUtc = now; context.UpdateObject(current); } else { if (authenticated) { Log.Write(EventKind.Warning, "The authenticated user does not exist in the database."); } var member = new MembershipRow(applicationName, username) { LastActivityDateUtc = now, IsAnonymous = !authenticated, ProfileBlobName = blobName, ProfileSize = size, ProfileLastUpdatedUtc = now, ProfileIsCreatedByProfileProvider = true }; context.AddObject(tableName, member); } context.SaveChanges(); }); }
private bool DoesProfileExistAndUpdateUser(string username, out MembershipRow prof) { SecUtility.CheckParameter(ref username, true, true, true, Constants.MaxTableUsernameLength, "username"); int curRetry = 0; do { try { TableServiceContext context = CreateDataServiceContext(); DataServiceQuery<MembershipRow> queryObj = context.CreateQuery<MembershipRow>(tableName); var query = (from profile in queryObj where profile.PartitionKey == SecUtility.CombineToKey(applicationName, username) select profile).AsTableServiceQuery(); IEnumerable<MembershipRow> profiles = query.Execute(); if (profiles == null) { prof = null; return false; } // instantiate results var profileList = new List<MembershipRow>(profiles); if (profileList.Count > 1) { throw new ProviderException("Multiple profile rows for the same user!"); } if (profileList.Count == 1) { prof = profileList.First(); if (!string.IsNullOrEmpty(prof.ProfileBlobName)) { prof.LastActivityDateUtc = DateTime.UtcNow; context.UpdateObject(prof); context.SaveChangesWithRetries(); return true; } return false; } prof = null; return false; } catch (InvalidOperationException e) { if (e.InnerException is DataServiceClientException && (e.InnerException as DataServiceClientException).StatusCode == (int) HttpStatusCode.PreconditionFailed) { continue; } throw new ProviderException("Error accessing storage.", e); } } while (curRetry++ < NumRetries); prof = null; return false; }
/// <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 = CreateDataServiceContext(); MembershipRow user = new MembershipRow(_applicationName, username); svc.AttachTo(_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 = EncodePassword(password, (int) _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 = EncodePassword(passwordAnswer.ToLowerInvariant(), (int) _passwordFormat, salt); } else { encodedPasswordAnswer = passwordAnswer; } if (!SecUtility.ValidateParameter(ref encodedPasswordAnswer, 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, RequiresUniqueEmail, RequiresUniqueEmail, false, Constants.MaxTableUsernameLength)) { status = MembershipCreateStatus.InvalidEmail; return null; } if (!SecUtility.ValidateParameter(ref passwordQuestion, RequiresQuestionAndAnswer, true, false, Constants.MaxTableUsernameLength)) { status = MembershipCreateStatus.InvalidQuestion; return null; } if (providerUserKey != null) { if (!(providerUserKey is Guid)) { status = MembershipCreateStatus.InvalidProviderUserKey; return null; } } if (!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 (RequiresUniqueEmail && !IsUniqueEmail(email)) { status = MembershipCreateStatus.DuplicateEmail; return null; } try { TableServiceContext svc = CreateDataServiceContext(); MembershipRow newUser = new MembershipRow(_applicationName, username); if (providerUserKey == null) { providerUserKey = Guid.NewGuid(); } newUser.UserId = (Guid) providerUserKey; newUser.Password = pass; newUser.PasswordSalt = salt; newUser.Email = (email == null) ? string.Empty : email; ; newUser.PasswordQuestion = (passwordQuestion == null) ? string.Empty : passwordQuestion; newUser.PasswordAnswer = (encodedPasswordAnswer == null) ? string.Empty : encodedPasswordAnswer; newUser.IsApproved = isApproved; newUser.PasswordFormat = (int) _passwordFormat; DateTime now = DateTime.UtcNow; newUser.CreateDateUtc = now; newUser.LastActivityDateUtc = now; newUser.LastPasswordChangedDateUtc = now; newUser.LastLoginDateUtc = now; newUser.IsLockedOut = false; svc.AddObject(_tableName, newUser); svc.SaveChanges(); status = MembershipCreateStatus.Success; return new MembershipUser(Name, username, providerUserKey, email, passwordQuestion, null, isApproved, false, now.ToLocalTime(), now.ToLocalTime(), now.ToLocalTime(), now.ToLocalTime(), ProviderConfiguration.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 IsUniqueEmail(string email, out MembershipRow member) { member = null; SecUtility.ValidateParameter(ref email, true, true, true, ProviderConfiguration.MaxStringPropertySizeInChars); TableServiceContext svc = CreateDataServiceContext(); DataServiceQuery<MembershipRow> queryObj = svc.CreateQuery<MembershipRow>(_tableName); var query = (from user in queryObj where user.PartitionKey.CompareTo(SecUtility.EscapedFirst(_applicationName)) > 0 && user.PartitionKey.CompareTo(SecUtility.NextComparisonString(SecUtility.EscapedFirst(_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(); if (e == null) { return true; } // 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(DataServiceContext svc, string username, string password, bool updateLastLoginActivityDate, bool failIfNotApproved, out MembershipRow member) { bool createContextAndWriteState = false; try { if (svc == null) { svc = CreateDataServiceContext(); createContextAndWriteState = true; } member = 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 = 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(PasswordAttemptWindow))) { member.FailedPasswordAttemptWindowStartUtc = now; member.FailedPasswordAttemptCount = 1; } else { member.FailedPasswordAttemptWindowStartUtc = now; member.FailedPasswordAttemptCount++; } if (member.FailedPasswordAttemptCount >= MaxInvalidPasswordAttempts) { member.IsLockedOut = true; member.LastLockoutDateUtc = now; } } else { if (member.FailedPasswordAttemptCount > 0 || member.FailedPasswordAnswerAttemptCount > 0) { member.FailedPasswordAnswerAttemptWindowStartUtc = ProviderConfiguration.MinSupportedDateTime; member.FailedPasswordAnswerAttemptCount = 0; member.FailedPasswordAttemptWindowStartUtc = ProviderConfiguration.MinSupportedDateTime; member.FailedPasswordAttemptCount = 0; member.LastLockoutDateUtc = ProviderConfiguration.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 CheckPassword(string username, string password, bool updateLastLoginActivityDate, bool failIfNotApproved, out MembershipRow member) { return CheckPassword(null, username, password, updateLastLoginActivityDate, failIfNotApproved, out member); }