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)
                 );
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
 public static bool NameMatchesLocalMember(string name, IStorePasswords passwords)
 {
     return(passwords.Exists(name));
 }
Beispiel #5
0
        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)
            ;
        }
Beispiel #6
0
        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)
                ;
            });
        }
Beispiel #8
0
 public static bool NameMatchesLocalMember(string name, IStorePasswords passwords)
 {
     return passwords.Exists(name);
 }