public ValidateSendConfirmEmailMessageCommand(IQueryEntities entities, IStorePasswords passwords) { CascadeMode = CascadeMode.StopOnFirstFailure; RuleFor(x => x.EmailAddress) //cannot be empty .NotEmpty() .WithMessage(MustNotHaveEmptyEmailAddress.FailMessage) // must be valid against email address regular expression .EmailAddress() .WithMessage(MustBeValidEmailAddressFormat.FailMessageFormat, x => x.EmailAddress) // must match a person .MustFindPersonByEmail(entities, _person) .WithMessage(MustFindPersonByEmail.FailMessageFormat, x => x.EmailAddress) // must match an establishment .MustFindEstablishmentByEmail(entities, _establishment) .WithMessage(MustFindEstablishmentByEmail.FailMessageFormat, x => x.EmailAddress) // establishment must be a member .Must(x => _establishment.Entity.IsMember) .WithMessage(MustBeMemberEstablishment.FailMessageFormat, x => _establishment.Entity.RevisionId) ; // when person is not null and intent is to reset password, When(x => _person.Entity != null && x.Intent == EmailConfirmationIntent.ResetPassword, () => RuleFor(x => x.EmailAddress) // the establishment must not have saml sign on .Must(x => !_establishment.Entity.HasSamlSignOn()) .WithMessage(MustNotHaveSamlIntegration.FailMessageFormat, x => _establishment.Entity.RevisionId) // the matched person must have a user .Must(x => _person.Entity.User != null) .WithMessage(MustNotHaveNullUser.FailMessageFormat, x => _person.Entity.DisplayName) // the user must not have a SAML account .Must(x => string.IsNullOrWhiteSpace(_person.Entity.User.EduPersonTargetedId)) .WithMessage(MustNotHaveSamlMembershipAccount.FailMessageFormat, x => _person.Entity.User.Name) // the email address' person's user's name must match a local member account .Must(x => passwords.Exists(_person.Entity.User.Name)) .WithMessage(MustHaveLocalMembershipAccount.FailMessageFormat, x => _person.Entity.User.Name) // the email address must be confirmed .Must(x => _person.Entity.Emails.ByValue(x).IsConfirmed) .WithMessage(MustBeConfirmedEmailAddress.FailMessageFormat, x => x.EmailAddress) ); }
public ValidateSendCreatePasswordMessageCommand(IQueryEntities entities, IStorePasswords passwords) { CascadeMode = CascadeMode.StopOnFirstFailure; RuleFor(x => x.EmailAddress) // email address cannot be empty .NotEmpty() .WithMessage(MustNotHaveEmptyEmailAddress.FailMessage) // must be valid against email address regular expression .EmailAddress() .WithMessage(MustBeValidEmailAddressFormat.FailMessageFormat, x => x.EmailAddress) // the email address must match an establishment .MustFindEstablishmentByEmail(entities, _establishment) .WithMessage(MustFindEstablishmentByEmail.FailMessageFormat, x => x.EmailAddress) // establishment must be a member .Must(x => _establishment.Entity.IsMember) .WithMessage(MustBeMemberEstablishment.FailMessageFormat, x => _establishment.Entity.RevisionId) // establishment must not have saml sign on .Must(x => !_establishment.Entity.HasSamlSignOn()) .WithMessage(MustNotHaveSamlIntegration.FailMessageFormat, x => _establishment.Entity.RevisionId) // the email address MAY match a person .Must((command, x, context) => { var validator = new MustFindPersonByEmail(entities, _person); validator.Validate(context); return(true); }) ; // when person is not null, When(x => _person.Entity != null, () => RuleFor(x => x.EmailAddress) // it must not have a registered user .Must(x => _person.Entity.User == null || !_person.Entity.User.IsRegistered) .WithMessage(MustNotBeRegisteredUser.FailMessageFormat, x => _person.Entity.DisplayName) // it must not have a local member account .Must(x => _person.Entity.User == null || !passwords.Exists(_person.Entity.User.Name)) .WithMessage(MustNotHaveLocalMembershipAccount.FailMessageFormat, x => _person.Entity.User.Name) ); }
public void Handle(ReceiveSamlAuthnResponseCommand command) { if (command == null) { throw new ArgumentNullException("command"); } if (command.SamlResponse == null) { throw new InvalidOperationException("The SAML Response cannot be null."); } var samlResponse = command.SamlResponse; // get the trusted establishment for this saml 2 response var establishment = GetTrustedIssuingEstablishment(samlResponse); // verify the response's signature VerifySignature(samlResponse); // first try to find user by SAML person targeted id var user = GetUserByEduPersonTargetedId(samlResponse); // when saml user does not exist, search for person if (user == null) { var person = GetPerson(samlResponse); user = person.User ?? new User { Person = person }; } // delete local account if it exists if (!string.IsNullOrWhiteSpace(user.Name) && _passwords.Exists(user.Name)) { _passwords.Destroy(user.Name); } // enforce invariants on user user.Name = samlResponse.EduPersonPrincipalName; user.EduPersonTargetedId = samlResponse.EduPersonTargetedId; user.IsRegistered = true; // remove previous scoped affiliations and add new ones var oldScopedAffiliations = user.EduPersonScopedAffiliations.ToArray(); var newScopedAffiliations = samlResponse.EduPersonScopedAffiliations ?? new string[] {}; newScopedAffiliations = newScopedAffiliations.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray(); foreach (var oldScopedAffiliation in oldScopedAffiliations) { if (!newScopedAffiliations.Contains(oldScopedAffiliation.Value)) { user.EduPersonScopedAffiliations.Remove(oldScopedAffiliation); } } foreach (var newScopedAffiliation in newScopedAffiliations) { if (oldScopedAffiliations.ByValue(newScopedAffiliation) == null) { user.EduPersonScopedAffiliations.Add( new EduPersonScopedAffiliation { Value = newScopedAffiliation, Number = user.EduPersonScopedAffiliations.NextNumber(), }); } } // log the subject name id var subjectNameId = samlResponse.SubjectNameIdentifier; if (!string.IsNullOrWhiteSpace(subjectNameId)) { var subjectNameIdentifier = user.SubjectNameIdentifiers.ByValue(subjectNameId); if (subjectNameIdentifier == null) { user.SubjectNameIdentifiers.Add( new SubjectNameIdentifier { Value = subjectNameId, Number = user.SubjectNameIdentifiers.NextNumber(), }); } else { subjectNameIdentifier.UpdatedOnUtc = DateTime.UtcNow; } } // enforce invariants on person if (!user.Person.IsAffiliatedWith(establishment)) { user.Person.AffiliateWith(establishment); } // remove previous saml mails, add new ones, and update existing ones var oldSamlMails = user.Person.Emails.FromSaml().ToArray(); var newSamlMails = samlResponse.Mails ?? new string[] {}; newSamlMails = newSamlMails.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray(); foreach (var oldSamlMail in oldSamlMails) { if (!newSamlMails.Contains(oldSamlMail.Value)) { user.Person.Emails.Remove(oldSamlMail); } } foreach (var newSamlMail in newSamlMails) { if (user.Person.GetEmail(newSamlMail) == null) { user.Person.AddEmail(newSamlMail); } } foreach (var emailAddress in user.Person.Emails) { if (newSamlMails.Contains(emailAddress.Value)) { emailAddress.IsFromSaml = true; emailAddress.IsConfirmed = true; } } // make sure person has at least 1 confirmed email address var defaultEmail = user.Person.DefaultEmail; if (defaultEmail == null || !defaultEmail.IsConfirmed) { if (defaultEmail != null) { defaultEmail.IsDefault = false; } defaultEmail = user.Person.AddEmail(samlResponse.EduPersonPrincipalName); defaultEmail.IsDefault = true; defaultEmail.IsConfirmed = true; } // update db if (user.RevisionId == 0) { _entities.Create(user); } else { _entities.Update(user); } _unitOfWork.SaveChanges(); // sign on user _userSigner.SignOn(user.Name); }
public static bool NameMatchesLocalMember(string name, IStorePasswords passwords) { return(passwords.Exists(name)); }
public ValidateResetPasswordCommand(IQueryEntities entities, IStorePasswords passwords) { CascadeMode = CascadeMode.StopOnFirstFailure; RuleFor(x => x.Token) // token cannot be an empty guid .NotEmpty() .WithMessage(MustNotHaveEmptyConfirmationToken.FailMessageFormat, x => x.Token) // token must match a confirmation .MustFindConfirmationByToken(entities, _confirmation) .WithMessage(MustFindConfirmationByToken.FailMessageFormat, x => x.Token) // its intent must be to reset password .Must(x => _confirmation.Entity.Intent == EmailConfirmationIntent.ResetPassword) .WithMessage(MustHaveCorrectConfirmationIntent.FailMessageFormat, x => _confirmation.Entity.Intent, x => x.Token) // it cannot be expired .Must(x => !_confirmation.Entity.IsExpired) .WithMessage(MustNotBeExpiredConfirmation.FailMessageFormat, x => x.Token, x => _confirmation.Entity.ExpiresOnUtc) // it cannot be retired .Must(x => !_confirmation.Entity.IsRetired) .WithMessage(MustNotBeRetiredConfirmation.FailMessageFormat, x => x.Token, x => _confirmation.Entity.RetiredOnUtc) // it must be redeemed .Must(x => _confirmation.Entity.IsRedeemed) .WithMessage(MustBeRedeemedConfirmation.FailMessageFormat, x => x.Token) // email address must be confirmed .Must(x => _confirmation.Entity.EmailAddress.IsConfirmed) .WithMessage(MustBeConfirmedEmailAddress.FailMessageFormat, x => _confirmation.Entity.EmailAddress.Value) // it must be attached to a user .Must(x => _confirmation.Entity.EmailAddress.Person.User != null) .WithMessage(MustNotHaveNullUser.FailMessageFormat, x => _confirmation.Entity.EmailAddress.Person.DisplayName) // user cannot have a saml account .Must(x => string.IsNullOrWhiteSpace(_confirmation.Entity.EmailAddress.Person.User.EduPersonTargetedId)) .WithMessage(MustNotHaveSamlMembershipAccount.FailMessageFormat, x => _confirmation.Entity.EmailAddress.Person.User.Name) // user name must match local member account .Must(x => passwords.Exists(_confirmation.Entity.EmailAddress.Person.User.Name)) .WithMessage(MustHaveLocalMembershipAccount.FailMessageFormat, x => _confirmation.Entity.EmailAddress.Person.User.Name) ; RuleFor(x => x.Ticket) // ticket cannot be empty .NotEmpty() .WithMessage(MustNotHaveEmptyConfirmationTicket.FailMessage) // must match the entity ticket .Must(x => x == _confirmation.Entity.Ticket) .WithMessage(MustHaveCorrectConfirmationTicket.FailMessageFormat, x => x.Ticket, x => x.Token) ; RuleFor(x => x.Password) // cannot be empty .NotEmpty() .WithMessage(MustNotHaveEmptyPassword.FailMessage) // length must be between 6 and 100 characters .Length(passwords.MinimumPasswordLength, int.MaxValue) .WithMessage(MustHaveMinimumPasswordLength.FormatFailMessage(passwords.MinimumPasswordLength)) ; RuleFor(x => x.PasswordConfirmation) // cannot be empty .NotEmpty() .WithMessage(MustNotHaveEmptyPasswordConfirmation.FailMessage) // must match password .Equal(x => x.Password) .WithMessage(MustHaveTwoEqualPasswords.FailMessage) ; }
public SendCreatePasswordMessageValidator(IQueryEntities entities, IStorePasswords passwords) { CascadeMode = CascadeMode.StopOnFirstFailure; Establishment establishment = null; var loadEstablishment = new Expression <Func <Establishment, object> >[] { e => e.SamlSignOn, }; Person person = null; var loadPerson = new Expression <Func <Person, object> >[] { p => p.Emails, p => p.User }; RuleFor(p => p.EmailAddress) // email address cannot be empty .NotEmpty() .WithMessage(ValidateEmailAddress.FailedBecauseValueWasEmpty) // must be valid against email address regular expression .EmailAddress() .WithMessage(ValidateEmailAddress.FailedBecauseValueWasNotValidEmailAddress) // the email address must match a non-saml establishment .Must(p => ValidateEstablishment.EmailMatchesEntity(p, entities, loadEstablishment, out establishment)) .WithMessage(ValidateEstablishment.FailedBecauseEmailMatchedNoEntity, p => p.EmailAddress) // the email address must match a member establishment .Must(p => ValidateEstablishment.EmailMatchesEntity(p, entities, loadEstablishment, out establishment)) .WithMessage(ValidateEstablishment.FailedBecauseEmailMatchedNoEntity, p => p.EmailAddress) // the email address may match a person .Must(p => { ValidateEmailAddress.ValueMatchesPerson(p, entities, loadPerson, out person); return(true); }) ; // when establishment is not null, When(p => establishment != null, () => RuleFor(p => p.EmailAddress) // it must not have saml sign on .Must(p => !establishment.HasSamlSignOn()) .WithMessage(ValidateEstablishment.FailedBecauseEstablishmentHasSamlSignOn, p => establishment.RevisionId) // it must be a member .Must(p => establishment.IsMember) .WithMessage(ValidateEstablishment.FailedBecauseEstablishmentIsNotMember, p => person.User.Name) ); // when person is not null, When(p => person != null, () => RuleFor(p => p.EmailAddress) // it must not have a registered user .Must(p => person.User == null || !person.User.IsRegistered) .WithMessage(ValidatePerson.FailedBecauseUserIsRegistered, p => person.DisplayName) // it must not have a local member account .Must(p => person.User == null || !passwords.Exists(person.User.Name)) .WithMessage(ValidateUser.FailedBecauseNameMatchedLocalMember, p => person.User.Name) ); }
public CreatePasswordValidator(IQueryEntities entities, IStorePasswords passwords) { CascadeMode = CascadeMode.StopOnFirstFailure; EmailConfirmation confirmation = null; RuleFor(p => p.Token) // token cannot be an empty guid .NotEmpty() .WithMessage(ValidateEmailConfirmation.FailedBecauseTokenWasEmpty, p => p.Token) // token must match a confirmation .Must(p => ValidateEmailConfirmation.TokenMatchesEntity(p, entities, out confirmation)) .WithMessage(ValidateEmailConfirmation.FailedBecauseTokenMatchedNoEntity, p => p.Token) ; RuleFor(p => p.Ticket) // ticket cannot be empty .NotEmpty() .WithMessage(ValidateEmailConfirmation.FailedBecauseTicketWasEmpty) ; RuleFor(p => p.Password) // cannot be empty .NotEmpty() .WithMessage(ValidatePassword.FailedBecausePasswordWasEmpty) // length must be between 6 and 100 characters .Length(passwords.MinimumPasswordLength, int.MaxValue) .WithMessage(ValidatePassword.FailedBecausePasswordWasTooShort(passwords.MinimumPasswordLength)) ; RuleFor(p => p.PasswordConfirmation) // cannot be empty .NotEmpty() .WithMessage(ValidatePassword.FailedBecausePasswordConfirmationWasEmpty) ; RuleFor(p => p.PasswordConfirmation) // must match password unless password is invalid or password confirmation is empty .Equal(p => p.Password) .Unless(p => string.IsNullOrWhiteSpace(p.PasswordConfirmation) || string.IsNullOrWhiteSpace(p.Password) || p.Password.Length < passwords.MinimumPasswordLength) .WithMessage(ValidatePassword.FailedBecausePasswordConfirmationDidNotEqualPassword) ; // when confirmation is not null, When(p => confirmation != null, () => { RuleFor(p => p.Token) // its intent must be to sign up .Must(p => confirmation.Intent == EmailConfirmationIntent.CreatePassword) .WithMessage(ValidateEmailConfirmation.FailedBecauseIntentWasIncorrect, p => confirmation.Intent, p => confirmation.Token) // it cannot be expired .Must(p => !confirmation.IsExpired) .WithMessage(ValidateEmailConfirmation.FailedBecauseIsExpired, p => confirmation.Token, p => confirmation.ExpiresOnUtc) // it cannot be retired .Must(p => !confirmation.IsRetired) .WithMessage(ValidateEmailConfirmation.FailedBecauseIsRetired, p => confirmation.Token, p => confirmation.RetiredOnUtc) // it must be redeemed .Must(p => confirmation.IsRedeemed) .WithMessage(ValidateEmailConfirmation.FailedBecauseIsNotRedeemed, p => confirmation.Token) // email address must be confirmed .Must(p => ValidateEmailAddress.IsConfirmed(confirmation.EmailAddress)) .WithMessage(ValidateEmailAddress.FailedBecauseIsNotConfirmed, p => confirmation.EmailAddress.Value) // user, if present, cannot match local member account .Must(p => confirmation.EmailAddress.Person.User == null || !passwords.Exists(confirmation.EmailAddress.Person.User.Name)) .WithMessage(ValidateUser.FailedBecauseNameMatchedLocalMember, p => confirmation.EmailAddress.Person.User.Name) ; RuleFor(p => p.Ticket) // its ticket must match the command ticket .Must(p => ValidateEmailConfirmation.TicketIsCorrect(confirmation, p)) .WithMessage(ValidateEmailConfirmation.FailedBecauseTicketWasIncorrect, p => p.Ticket, p => p.Token) ; }); }
public static bool NameMatchesLocalMember(string name, IStorePasswords passwords) { return passwords.Exists(name); }