public ValidationResult Validate(UserAccountService service, UserAccount account, string value)
        {
            if (String.IsNullOrWhiteSpace(value))
            {
                return new ValidationResult(Resources.ValidationMessages.PasswordRequired);
            }
            
            if (value.Length < this.MinimumLength)
            {
                return new ValidationResult(String.Format(Resources.ValidationMessages.PasswordLength, this.MinimumLength));
            }

            var upper = value.Any(x => Char.IsUpper(x));
            var lower = value.Any(x => Char.IsLower(x));
            var digit = value.Any(x => Char.IsDigit(x));
            var other = value.Any(x => !Char.IsUpper(x) && !Char.IsLower(x) && !Char.IsDigit(x));

            var vals = new bool[] { upper, lower, digit, other };
            var matches = vals.Where(x => x).Count();
            if (matches < this.MinimumNumberOfComplexityRules)
            {
                return new ValidationResult(String.Format(Resources.ValidationMessages.PasswordComplexityRules, this.MinimumNumberOfComplexityRules));
            }

            return null;
        }
        public virtual void SignIn(UserAccount account, string method)
        {
            if (account == null) throw new ArgumentNullException("account");
            if (String.IsNullOrWhiteSpace(method)) throw new ArgumentNullException("method");

            if (!account.IsAccountVerified)
            {
                throw new ValidationException("Account not yet verified");
            }

            if (!account.IsLoginAllowed)
            {
                throw new ValidationException("Login not allowed for this account");
            }

            // gather claims
            var claims =
                (from uc in account.Claims
                 select new Claim(uc.Type, uc.Value)).ToList();

            if (!String.IsNullOrWhiteSpace(account.Email))
            {
                claims.Insert(0, new Claim(ClaimTypes.Email, account.Email));
            }
            claims.Insert(0, new Claim(ClaimTypes.AuthenticationMethod, method));
            claims.Insert(0, new Claim(ClaimTypes.AuthenticationInstant, DateTime.UtcNow.ToString("s")));
            claims.Insert(0, new Claim(ClaimTypes.Name, account.Username));
            claims.Insert(0, new Claim(MembershipRebootConstants.ClaimTypes.Tenant, account.Tenant));
            claims.Insert(0, new Claim(ClaimTypes.NameIdentifier, account.ID.ToString("D")));

            // create principal/identity
            var id = new ClaimsIdentity(claims, method);
            var cp = new ClaimsPrincipal(id);

            // claims transform
            cp = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager.Authenticate(String.Empty, cp);

            // issue cookie
            var sam = FederatedAuthentication.SessionAuthenticationModule;
            if (sam == null)
            {
                Tracing.Verbose("[ClaimsBasedAuthenticationService.Signin] SessionAuthenticationModule is not configured");
                throw new Exception("SessionAuthenticationModule is not configured and it needs to be.");
            }

            var handler = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.SecurityTokenHandlers[typeof(SessionSecurityToken)] as SessionSecurityTokenHandler;
            if (handler == null)
            {
                Tracing.Verbose("[ClaimsBasedAuthenticationService.Signin] SessionSecurityTokenHandler is not configured");
                throw new Exception("SessionSecurityTokenHandler is not configured and it needs to be.");
            }

            var token = new SessionSecurityToken(cp, handler.TokenLifetime);
            token.IsPersistent = FederatedAuthentication.FederationConfiguration.WsFederationConfiguration.PersistentCookiesOnPassiveRedirects;
            token.IsReferenceMode = sam.IsReferenceMode;

            sam.WriteSessionTokenToCookie(token);

            Tracing.Verbose(String.Format("[ClaimsBasedAuthenticationService.Signin] cookie issued: {0}", claims.GetValue(ClaimTypes.NameIdentifier)));
        }
 public string GetTwoFactorAuthToken(UserAccount account)
 {
     if (account == null) throw new ArgumentNullException("account");
     var result = GetCookie(MembershipRebootConstants.AuthenticationService.CookieBasedTwoFactorAuthPolicyCookieName + account.Tenant);
     Tracing.Information("[CookieBasedTwoFactorAuthPolicy.ClearTwoFactorAuthToken] getting cookie for {0}, {1}, found:{2}", account.Tenant, account.Username, result);
     return result;
 }
        public static UserAccount Create(string username, string password, string email)
        {
            if (SecuritySettings.Instance.EmailIsUsername && username != email)
            {
                throw new ValidationException("Username must be the same as the Email");
            }

            UserAccount account = new UserAccount
            {
                Username = username,
                Email = email,
                Created = DateTime.UtcNow,
                IsAccountVerified = !SecuritySettings.Instance.RequireAccountVerification,
                IsLoginAllowed = SecuritySettings.Instance.AllowLoginAfterAccountCreation,
                Claims = new List<UserClaim>()
            };

            account.SetPassword(password);
            if (SecuritySettings.Instance.RequireAccountVerification)
            {
                account.VerificationKey = StripUglyBase64(Crypto.GenerateSalt());
                account.VerificationKeySent = DateTime.UtcNow;
            }

            return account;
        }
        public void SendAccountVerified(UserAccount user)
        {
            Tracing.Information(String.Format("[NotificationService.SendAccountVerified] {0}, {1}, {2}", user.Tenant, user.Username, user.Email));

            var msg = GetAccountVerifiedFormat();
            var body = DoTokenReplacement(msg, user);
            DeliverMessage(user, "Account Verified", body);
        }
 public void Remove(UserAccount item)
 {
     foreach (var claim in item.Claims.ToArray())
     {
         item.Claims.Remove(claim);
     }
     db.Users.Remove(item);
 }
        public virtual void SignIn(UserAccount account, string method)
        {
            if (account == null) throw new ArgumentNullException("account");
            if (String.IsNullOrWhiteSpace(method)) throw new ArgumentNullException("method");

            if (!account.IsAccountVerified)
            {
                throw new ValidationException(Resources.ValidationMessages.AccountNotVerified);
            }

            if (!account.IsLoginAllowed)
            {
                throw new ValidationException(Resources.ValidationMessages.LoginNotAllowed);
            }

            if (account.RequiresTwoFactorAuthToSignIn || 
                account.RequiresPasswordReset || 
                this.UserAccountService.IsPasswordExpired(account))
            {
                Tracing.Verbose("[AuthenticationService.SignIn] detected account requires two factor or password reset to sign in: {0}", account.ID);
                IssuePartialSignInToken(account, method);
                return;
            }

            // gather claims
            var claims = GetBasicClaims(account, method);
            
            // get the rest
            if (!String.IsNullOrWhiteSpace(account.Email))
            {
                claims.Add(new Claim(ClaimTypes.Email, account.Email));
            }
            if (!String.IsNullOrWhiteSpace(account.MobilePhoneNumber))
            {
                claims.Add(new Claim(ClaimTypes.MobilePhone, account.MobilePhoneNumber));
            }
            var x509 = from c in account.Certificates
                       select new Claim(ClaimTypes.X500DistinguishedName, c.Subject);
            claims.AddRange(x509);
            var otherClaims =
                (from uc in account.Claims
                 select new Claim(uc.Type, uc.Value)).ToList();
            claims.AddRange(otherClaims);

            // create principal/identity
            var id = new ClaimsIdentity(claims, method);
            var cp = new ClaimsPrincipal(id);

            // claims transform
            if (this.ClaimsAuthenticationManager != null)
            {
                cp = ClaimsAuthenticationManager.Authenticate(String.Empty, cp);
            }

            // issue cookie
            IssueToken(cp);
        }
 internal protected void ValidateUsername(UserAccount account, string value)
 {
     var result = this.usernameValidator.Value.Validate(this, account, value);
     if (result != null && result != ValidationResult.Success)
     {
         Tracing.Error("ValidateUsername failed: " + result.ErrorMessage);
         throw new ValidationException(result.ErrorMessage);
     }
 }
        public virtual void SignIn(UserAccount account, string method)
        {
            if (account == null) throw new ArgumentNullException("account");
            if (String.IsNullOrWhiteSpace(method)) throw new ArgumentNullException("method");

            if (!account.IsAccountVerified)
            {
                throw new ValidationException("Account not yet verified.");
            }

            if (!account.IsLoginAllowed)
            {
                throw new ValidationException("Login not allowed for this account.");
            }

            if (account.RequiresTwoFactorAuthToSignIn)
            {
                Tracing.Verbose("[AuthenticationService.SignIn] detected account requires two factor to sign in: {0}", account.Id);
                IssuePartialSignInTokenForTwoFactorAuth(account, method);
                return;
            }

            // gather claims
            var claims = GetBasicClaims(account, method);
            
            // get the rest
            if (!String.IsNullOrWhiteSpace(account.Email))
            {
                claims.Add(new Claim(ClaimTypes.Email, account.Email));
            }
            if (!String.IsNullOrWhiteSpace(account.MobilePhoneNumber))
            {
                claims.Add(new Claim(ClaimTypes.MobilePhone, account.MobilePhoneNumber));
            }
            var x509 = from c in account.Certificates
                       select new Claim(ClaimTypes.X500DistinguishedName, c.Subject);
            claims.AddRange(x509);
            var otherClaims =
                (from uc in account.Claims
                 select new Claim(uc.Type, uc.Value)).ToList();
            claims.AddRange(otherClaims);

            // create principal/identity
            var id = new ClaimsIdentity(claims, method);
            var cp = new ClaimsPrincipal(id);

            // claims transform
            cp = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager.Authenticate(String.Empty, cp);

            // issue cookie
            IssueToken(cp);
        }
        protected virtual string DoTokenReplacement(string msg, UserAccount user)
        {
            msg = msg.Replace("{username}", user.Username);
            msg = msg.Replace("{email}", user.Email);

            msg = msg.Replace("{applicationName}", appInfo.ApplicationName);
            msg = msg.Replace("{emailSignature}", appInfo.EmailSignature);
            msg = msg.Replace("{loginUrl}", appInfo.LoginUrl);

            msg = msg.Replace("{confirmAccountCreateUrl}", appInfo.VerifyAccountUrl + user.VerificationKey);
            msg = msg.Replace("{cancelNewAccountUrl}", appInfo.CancelNewAccountUrl + user.VerificationKey);

            msg = msg.Replace("{confirmPasswordResetUrl}", appInfo.ConfirmPasswordResetUrl + user.VerificationKey);
            msg = msg.Replace("{confirmChangeEmailUrl}", appInfo.ConfirmChangeEmailUrl + user.VerificationKey);

            
            return msg; 
        }
        internal static UserAccount Create(string tenant, string username, string password, string email)
        {
            UserAccount account = new UserAccount
            {
                Tenant = tenant,
                Username = username,
                Email = email,
                Created = DateTime.UtcNow,
                IsAccountVerified = !SecuritySettings.Instance.RequireAccountVerification,
                IsLoginAllowed = SecuritySettings.Instance.AllowLoginAfterAccountCreation,
                Claims = new List<UserClaim>()
            };

            account.SetPassword(password);
            if (SecuritySettings.Instance.RequireAccountVerification)
            {
                account.VerificationKey = StripUglyBase64(Crypto.GenerateSalt());
                account.VerificationKeySent = DateTime.UtcNow;
            }

            return account;
        }
        public virtual void Update(UserAccount account)
        {
            if (account == null)
            {
                Tracing.Error("[UserAccountService.Update] called -- failed null account");
                throw new ArgumentNullException("account");
            }

            Tracing.Information("[UserAccountService.Update] called for account: {0}", account.ID);

            account.LastUpdated = account.UtcNow;
            this.userRepository.Update(account);
        }
        public virtual bool AuthenticateWithCertificate(Guid accountID, X509Certificate2 certificate, out UserAccount account)
        {
            Tracing.Information("[UserAccountService.AuthenticateWithCertificate] called for userID: {0}", accountID);
            
            certificate.Validate();

            account = this.GetByID(accountID);
            if (account == null) throw new ArgumentException("Invalid AccountID");

            var result = account.Authenticate(certificate);
            Update(account);

            Tracing.Verbose("[UserAccountService.AuthenticateWithCertificate] result: {0}", result);
            
            return result;
        }
        public virtual bool AuthenticateWithCertificate(X509Certificate2 certificate, out UserAccount account)
        {
            Tracing.Information("[UserAccountService.AuthenticateWithCertificate] called");

            certificate.Validate();

            account = this.GetByCertificate(certificate.Thumbprint);
            if (account == null) return false;

            var result = account.Authenticate(certificate);
            Update(account);

            Tracing.Verbose("[UserAccountService.AuthenticateWithCertificate] result {0}", result);

            return result;
        }
        public virtual bool AuthenticateWithCode(Guid accountID, string code, out UserAccount account)
        {
            Tracing.Information("[UserAccountService.AuthenticateWithCode] called {0}", accountID);

            account = this.GetByID(accountID);
            if (account == null) throw new ArgumentException("Invalid AccountID");

            var result = account.VerifyTwoFactorAuthCode(code);
            Update(account);

            Tracing.Verbose("[UserAccountService.AuthenticateWithCode] result {0}", result);

            return result;
        }
        protected internal virtual bool Authenticate(UserAccount account, string password, AuthenticationPurpose purpose)
        {
            Tracing.Verbose("[UserAccountService.Authenticate] for account: {0}", account.ID);
            
            int failedLoginCount = SecuritySettings.AccountLockoutFailedLoginAttempts;
            TimeSpan lockoutDuration = SecuritySettings.AccountLockoutDuration;

            var result = account.Authenticate(password, failedLoginCount, lockoutDuration);
            if (result && 
                purpose == AuthenticationPurpose.SignIn && 
                account.AccountTwoFactorAuthMode != TwoFactorAuthMode.None)
            {
                Tracing.Verbose("[UserAccountService.Authenticate] password authN successful, doing two factor auth checks: {0}, {1}", account.Tenant, account.Username);
                
                bool shouldRequestTwoFactorAuthCode = true;
                if (this.Configuration.TwoFactorAuthenticationPolicy != null)
                {
                    shouldRequestTwoFactorAuthCode = this.Configuration.TwoFactorAuthenticationPolicy.RequestRequiresTwoFactorAuth(account);
                    Tracing.Verbose("[UserAccountService.Authenticate] TwoFactorAuthenticationPolicy.RequestRequiresTwoFactorAuth called, result: {0}", shouldRequestTwoFactorAuthCode);
                }

                if (shouldRequestTwoFactorAuthCode)
                {
                    if (account.AccountTwoFactorAuthMode == TwoFactorAuthMode.Certificate)
                    {
                        Tracing.Verbose("[UserAccountService.Authenticate] requesting 2fa certificate: {0}, {1}", account.Tenant, account.Username);
                        result = account.RequestTwoFactorAuthCertificate();
                    }

                    if (account.AccountTwoFactorAuthMode == TwoFactorAuthMode.Mobile)
                    {
                        Tracing.Verbose("[UserAccountService.Authenticate] requesting 2fa mobile code: {0}, {1}", account.Tenant, account.Username);
                        result = account.RequestTwoFactorAuthCode();
                    }
                }
            }

            Update(account);

            Tracing.Verbose("[UserAccountService.Authenticate] authentication outcome: {0}", result ? "Successful Login" : "Failed Login");

            return result;
        }
        public virtual bool AuthenticateWithUsernameOrEmail(string tenant, string userNameOrEmail, string password, out UserAccount account)
        {
            account = null;

            if (!SecuritySettings.MultiTenant)
            {
                Tracing.Verbose("[UserAccountService.AuthenticateWithUsernameOrEmail] applying default tenant");
                tenant = SecuritySettings.DefaultTenant;
            }

            Tracing.Information("[UserAccountService.AuthenticateWithUsernameOrEmail] called {0}, {1}", tenant, userNameOrEmail);

            if (String.IsNullOrWhiteSpace(tenant)) return false;
            if (String.IsNullOrWhiteSpace(userNameOrEmail)) return false;
            if (String.IsNullOrWhiteSpace(password)) return false;

            if (!SecuritySettings.EmailIsUsername && userNameOrEmail.Contains("@"))
            {
                Tracing.Verbose("[UserAccountService.AuthenticateWithUsernameOrEmail] email detected");
                return AuthenticateWithEmail(tenant, userNameOrEmail, password, out account);
            }
            else
            {
                Tracing.Verbose("[UserAccountService.AuthenticateWithUsernameOrEmail] username detected");
                return Authenticate(tenant, userNameOrEmail, password, out account);
            }
        }
        private void IssuePartialSignInTokenForTwoFactorAuth(UserAccount account, string method)
        {
            if (account == null) throw new ArgumentNullException("account");

            Tracing.Verbose("[AuthenticationService.IssuePartialSignInCookieForTwoFactorAuth] Account ID: {0}", account.ID);

            var claims = GetBasicClaims(account, method);

            var ci = new ClaimsIdentity(claims); // no auth type param so user will not be actually authenticated
            var cp = new ClaimsPrincipal(ci);

            IssueToken(cp, MembershipRebootConstants.AuthenticationService.TwoFactorAuthTokenLifetime, false);
        }
 protected virtual void DeliverMessage(UserAccount user, string subject, string body)
 {
     DeliverMessage(user.Email, subject, body);
 }
        public void SendChangeUsernameRequestNotice(UserAccount user)
        {
            Tracing.Information(String.Format("[NotificationService.SendChangeUsernameRequestNotice] {0}, {1}, {2}", user.Tenant, user.Username, user.Email));

            var msg = GetUsernameChangedNoticeFormat();
            var body = DoTokenReplacement(msg, user);
            DeliverMessage(user, "Username Changed", body);
        }
        public virtual bool IsPasswordExpired(UserAccount account)
        {
            if (account == null) throw new ArgumentNullException("account");

            return account.GetIsPasswordExpired(SecuritySettings.PasswordResetFrequency);
        }
        public void SignInWithLinkedAccount(
            string tenant,
            string providerName,
            string providerAccountID,
            IEnumerable <Claim> claims)
        {
            if (!UserAccountService.Configuration.SecuritySettings.MultiTenant)
            {
                tenant = UserAccountService.Configuration.SecuritySettings.DefaultTenant;
            }

            if (String.IsNullOrWhiteSpace(tenant))
            {
                throw new ArgumentException("tenant");
            }
            if (String.IsNullOrWhiteSpace(providerName))
            {
                throw new ArgumentException("providerName");
            }
            if (String.IsNullOrWhiteSpace(providerAccountID))
            {
                throw new ArgumentException("providerAccountID");
            }
            if (claims == null)
            {
                throw new ArgumentNullException("claims");
            }

            UserAccount account = null;
            var         user    = ClaimsPrincipal.Current;

            if (user.Identity.IsAuthenticated)
            {
                // already logged in, so use the current user's account
                account = this.UserAccountService.GetByID(user.GetUserID());
            }
            else
            {
                // see if there's already an account mapped to this provider
                account = this.UserAccountService.GetByLinkedAccount(tenant, providerName, providerAccountID);
                if (account == null)
                {
                    // no account associated, so create one
                    // we need email
                    var email = claims.GetValue(ClaimTypes.Email);
                    if (String.IsNullOrWhiteSpace(email))
                    {
                        throw new ValidationException("Can't create an account because there was no email from the identity provider.");
                    }

                    // guess at a name to use
                    var name = claims.GetValue(ClaimTypes.Name);
                    if (name == null ||
                        this.UserAccountService.UsernameExists(tenant, name))
                    {
                        name = email;
                    }

                    // check to see if email already exists
                    if (this.UserAccountService.EmailExists(tenant, email))
                    {
                        throw new ValidationException("Can't login with this provider because the email is already associated with another account. Please login with your local account and then associate the provider.");
                    }

                    // auto-gen a password, they can always reset it later if they want to use the password feature
                    // this is slightly dangerous if we don't do email account verification, so if email account
                    // verification is disabled then we need to be very confident that the external provider has
                    // provided us with a verified email
                    var pwd = CryptoHelper.GenerateSalt();
                    account = this.UserAccountService.CreateAccount(tenant, name, pwd, email);
                }
            }

            if (account == null)
            {
                throw new Exception("Failed to locate account");
            }

            // add/update the provider with this account
            account.AddOrUpdateLinkedAccount(providerName, providerAccountID, claims);
            this.UserAccountService.Update(account);

            // signin from the account
            // if we want to include the provider's claims, then perhaps this
            // should be done in the claims transformer
            this.SignIn(account, providerName);
        }
 public virtual void SignIn(UserAccount account)
 {
     SignIn(account, AuthenticationMethods.Password);
 }
        public void SendResetPassword(UserAccount user)
        {
            Tracing.Information(String.Format("[NotificationService.SendResetPassword] {0}, {1}, {2}", user.Tenant, user.Username, user.Email));

            var msg = GetResetPasswordFormat();
            var body = DoTokenReplacement(msg, user);
            DeliverMessage(user, "Password Reset Request", body);
        }
        protected internal virtual void DeleteAccount(UserAccount account)
        {
            if (account == null) throw new ArgumentNullException("account");

            Tracing.Verbose("[UserAccountService.DeleteAccount] marking account as closed: {0}", account.ID);

            account.CloseAccount();
            Update(account);

            if (SecuritySettings.AllowAccountDeletion || !account.IsAccountVerified)
            {
                Tracing.Verbose("[UserAccountService.DeleteAccount] removing account record: {0}", account.ID);
                this.userRepository.Remove(account);
            }
        }
        public void SendEmailChangedNotice(UserAccount user, string oldEmail)
        {
            Tracing.Information(String.Format("[NotificationService.SendEmailChangedNotice] {0}, {1}, {2}, {3}", user.Tenant, user.Username, user.Email, oldEmail));

            var msg = GetEmailChangedNoticeFormat();
            var body = DoTokenReplacement(msg, user);
            body = body.Replace("{newEmail}", user.Email);
            body = body.Replace("{oldEmail}", oldEmail);
            DeliverMessage(user, "Email Changed", body);
        }
 public virtual bool Authenticate(string username, string password, out UserAccount account)
 {
     return Authenticate(null, username, password, out account);
 }
 protected virtual void DeliverMessage(UserAccount user, string subject, string body)
 {
     DeliverMessage(user.Email, subject, body);
 }
 public virtual bool AuthenticateWithEmail(string email, string password, out UserAccount account)
 {
     return AuthenticateWithEmail(null, email, password, out account);
 }
        private static List<Claim> GetBasicClaims(UserAccount account, string method)
        {
            if (account == null) throw new ArgumentNullException("account");

            var claims = new List<Claim>();
            claims.Add(new Claim(ClaimTypes.AuthenticationMethod, method));
            claims.Add(new Claim(ClaimTypes.AuthenticationInstant, DateTime.UtcNow.ToString("s")));
            claims.Add(new Claim(ClaimTypes.NameIdentifier, account.ID.ToString("D")));
            claims.Add(new Claim(ClaimTypes.Name, account.Username));
            claims.Add(new Claim(MembershipRebootConstants.ClaimTypes.Tenant, account.Tenant));

            return claims;
        }
        public virtual bool AuthenticateWithEmail(string tenant, string email, string password, out UserAccount account)
        {
            account = null;

            if (!SecuritySettings.MultiTenant)
            {
                Tracing.Verbose("[UserAccountService.AuthenticateWithEmail] applying default tenant");
                tenant = SecuritySettings.DefaultTenant;
            }

            Tracing.Information("[UserAccountService.AuthenticateWithEmail] called: {0}, {1}", tenant, email);

            if (String.IsNullOrWhiteSpace(tenant)) return false;
            if (String.IsNullOrWhiteSpace(email)) return false;
            if (String.IsNullOrWhiteSpace(password)) return false;

            account = this.GetByEmail(tenant, email);
            if (account == null) return false;

            return Authenticate(account, password, AuthenticationPurpose.SignIn);
        }
 public virtual void SignIn(UserAccount account)
 {
     SignIn(account, AuthenticationMethods.Password);
 }
 public virtual bool AuthenticateWithUsernameOrEmail(string userNameOrEmail, string password, out UserAccount account)
 {
     return AuthenticateWithUsernameOrEmail(null, userNameOrEmail, password, out account);
 }