/// <summary> /// Authenticate the user /// </summary> public IPrincipal Authenticate(string userName, string password) { var evt = new AuthenticatingEventArgs(userName); this.Authenticating?.Invoke(this, evt); if (evt.Cancel) { throw new SecurityException("Authentication cancelled"); } try { var principal = AdoClaimsIdentity.Create(userName, password).CreateClaimsPrincipal(); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, principal, true)); return(principal); } catch (AuthenticationException e) when(e.Message.Contains("AUTH_LCK")) { throw new AuthenticationException($"Account is locked please contact an administrator"); } catch (AuthenticationException e) when(e.Message.Contains("AUTH_INV")) { throw new AuthenticationException($"Invalid username or password"); } catch (Exception e) { this.m_traceSource.TraceEvent(EventLevel.Verbose, "Invalid credentials : {0}/{1}", userName, password); this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString()); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false)); throw new AuthenticationException($"General exception authenticating {userName}", e); } }
/// <summary> /// Authenticate the user /// </summary> public IPrincipal Authenticate(string userName, string password) { var evt = new AuthenticatingEventArgs(userName); this.Authenticating?.Invoke(this, evt); if (evt.Cancel) { throw new SecurityException("Authentication cancelled"); } try { var principal = AdoClaimsIdentity.Create(userName, password).CreateClaimsPrincipal(); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, principal, true)); return(principal); } catch (Exception e) { this.m_traceSource.TraceEvent(EventLevel.Verbose, "Invalid credentials : {0}/{1}", userName, password); this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString()); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false)); throw; } }
/// <summary> /// Create a basic user /// </summary> public IIdentity CreateIdentity(string userName, string password, IPrincipal principal) { this.VerifyPrincipal(principal, PermissionPolicyIdentifiers.CreateIdentity); if (String.IsNullOrEmpty(userName)) { throw new ArgumentNullException(nameof(userName)); } else if (String.IsNullOrEmpty(password)) { throw new ArgumentNullException(nameof(password)); } this.m_traceSource.TraceInfo("Creating identity {0} ({1})", userName, principal); try { using (var dataContext = this.m_configuration.Provider.GetWriteConnection()) { dataContext.Open(); using (var tx = dataContext.BeginTransaction()) try { var hashingService = ApplicationServiceContext.Current.GetService <IPasswordHashingService>(); var pdpService = ApplicationServiceContext.Current.GetService <IPolicyDecisionService>(); // Demand create identity new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, PermissionPolicyIdentifiers.CreateIdentity).Demand(); // Does this principal have the ability to DbSecurityUser newIdentityUser = new DbSecurityUser() { UserName = userName, Password = hashingService.ComputeHash(password), SecurityHash = Guid.NewGuid().ToString(), UserClass = UserClassKeys.HumanUser, InvalidLoginAttempts = 0 }; newIdentityUser.CreatedByKey = dataContext.EstablishProvenance(principal, null); dataContext.Insert(newIdentityUser); var retVal = AdoClaimsIdentity.Create(newIdentityUser); tx.Commit(); return(retVal); } catch { tx.Rollback(); throw; } } } catch (Exception e) { this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString()); throw; } }
/// <summary> /// Authenticate this user based on the session /// </summary> /// <param name="session">The session for which authentication is being saught</param> /// <returns>The authenticated principal</returns> public IPrincipal Authenticate(ISession session) { try { using (var context = this.m_configuration.Provider.GetReadonlyConnection()) { context.Open(); var sessionId = new Guid(session.Id.Take(16).ToArray()); var sql = context.CreateSqlStatement <DbSession>().SelectFrom(typeof(DbSession), typeof(DbSecurityApplication), typeof(DbSecurityUser), typeof(DbSecurityDevice)) .InnerJoin <DbSecurityApplication>(o => o.ApplicationKey, o => o.Key) .Join <DbSession, DbSecurityUser>("LEFT", o => o.UserKey, o => o.Key) .Join <DbSession, DbSecurityDevice>("LEFT", o => o.DeviceKey, o => o.Key) .Where <DbSession>(s => s.Key == sessionId); var auth = context.FirstOrDefault <CompositeResult <DbSession, DbSecurityApplication, DbSecurityUser, DbSecurityDevice> >(sql); // Identities List <IClaimsIdentity> identities = new List <IClaimsIdentity>(3); if (auth.Object1.NotAfter < DateTime.Now) { throw new AuthenticationException("Session is expired"); } if (auth.Object2?.Key != null) { identities.Add(new Core.Security.ApplicationIdentity(auth.Object2.Key, auth.Object2.PublicId, true)); } if (auth.Object1.DeviceKey.HasValue) { identities.Add(new DeviceIdentity(auth.Object4.Key, auth.Object4.PublicId, true)); } var principal = auth.Object1.UserKey.GetValueOrDefault() == Guid.Empty ? new SanteDBClaimsPrincipal(identities) : AdoClaimsIdentity.Create(auth.Object3, true, "SESSION").CreateClaimsPrincipal(identities); identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.AuthenticationInstant, session.NotBefore.ToString("o"))); identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.Expiration, session.NotAfter.ToString("o"))); identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBSessionIdClaim, auth.Object1.Key.ToString())); // Add claims from session foreach (var clm in session.Claims) { identities.First().AddClaim(clm); } // TODO: Load additional claims made about the user on the session return(principal); } } catch (Exception e) { this.m_traceSource.TraceEvent(EventLevel.Verbose, "Invalid session auth: {0}", e.Message); this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString()); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(null, null, false)); throw; } }
/// <summary> /// Get the specified identities from the session /// </summary> public IIdentity[] GetIdentities(ISession session) { try { using (var context = this.m_configuration.Provider.GetReadonlyConnection()) { context.Open(); var sessionId = new Guid(session.Id.Take(16).ToArray()); var sql = context.CreateSqlStatement <DbSession>().SelectFrom(typeof(DbSession), typeof(DbSecurityApplication), typeof(DbSecurityDevice), typeof(DbSecurityUser)) .Join <DbSession, DbSecurityDevice>("LEFT", o => o.DeviceKey, o => o.Key) .Join <DbSession, DbSecurityApplication>("LEFT", o => o.ApplicationKey, o => o.Key) .Join <DbSession, DbSecurityUser>("LEFT", o => o.UserKey, o => o.Key) .Where <DbSession>(o => o.Key == sessionId); var sessionData = context.FirstOrDefault <CompositeResult <DbSession, DbSecurityDevice, DbSecurityApplication, DbSecurityUser> >(sql); if (sessionData == null) { throw new KeyNotFoundException($"Session {sessionId} not found"); } else { List <IIdentity> retVal = new List <IIdentity>(4); retVal.Add(new Core.Security.ApplicationIdentity(sessionData.Object1.ApplicationKey, sessionData.Object3.PublicId, false)); if (sessionData.Object1.DeviceKey.HasValue) { retVal.Add(new DeviceIdentity(sessionData.Object2.Key, sessionData.Object2.PublicId, false)); } if (sessionData.Object1.UserKey.HasValue) { retVal.Add(AdoClaimsIdentity.Create(sessionData.Object4, false)); } return(retVal.ToArray()); } } } catch (Exception e) { this.m_traceSource.TraceError("Error getting identities for session {0}", session.Id); throw new DataPersistenceException($"Error getting identities for session {BitConverter.ToString(session.Id)}"); } }
/// <summary> /// Gets an un-authenticated identity /// </summary> public IIdentity GetIdentity(Guid sid) { try { using (var dataContext = this.m_configuration.Provider.GetReadonlyConnection()) { dataContext.Open(); var name = dataContext.FirstOrDefault <DbSecurityUser>(o => o.Key == sid)?.UserName; if (name == null) { return(null); } else { return(AdoClaimsIdentity.Create(name)); } } } catch { return(null); } }
/// <summary> /// Authenticate using a refresh token /// </summary> public IPrincipal Authenticate(byte[] refreshToken) { if (refreshToken == null) { throw new ArgumentNullException(nameof(refreshToken)); } else if (refreshToken.Length != 32) { throw new ArgumentOutOfRangeException(nameof(refreshToken), "Invalid refresh token"); } string trokenName = BitConverter.ToString(refreshToken).Replace("-", ""); var evt = new AuthenticatingEventArgs(trokenName); this.Authenticating?.Invoke(this, evt); if (evt.Cancel) { throw new SecurityException("Authentication cancelled"); } try { var principal = AdoClaimsIdentity.Create(refreshToken).CreateClaimsPrincipal(); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(trokenName, principal, true)); return(principal); } catch (SecurityException e) { this.m_traceSource.TraceEvent(TraceEventType.Verbose, e.HResult, "Invalid credentials : {0}", refreshToken); this.m_traceSource.TraceEvent(TraceEventType.Error, e.HResult, e.ToString()); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(trokenName, null, false)); throw; } }
/// <summary> /// Authenticate the user using a TwoFactorAuthentication secret /// </summary> public IPrincipal Authenticate(string userName, string password, string tfaSecret) { // First, let's verify the TFA if (String.IsNullOrEmpty(userName)) { throw new ArgumentNullException(nameof(userName)); } else if (String.IsNullOrEmpty(tfaSecret)) { throw new ArgumentNullException(nameof(tfaSecret)); } // Authentication event args var evt = new AuthenticatingEventArgs(userName); this.Authenticating?.Invoke(this, evt); if (evt.Cancel) { throw new SecurityException("Authentication cancelled"); } // Password hasher var hashingService = ApplicationServiceContext.Current.GetService <IPasswordHashingService>(); tfaSecret = hashingService.ComputeHash(tfaSecret); // Try to authenticate try { using (var dataContext = this.m_configuration.Provider.GetWriteConnection()) { dataContext.Open(); using (var tx = dataContext.BeginTransaction()) { try { var user = dataContext.FirstOrDefault <DbSecurityUser>(o => o.UserName.ToLower() == userName.ToLower()); if (user == null) { throw new KeyNotFoundException(userName); } var claims = dataContext.Query <DbUserClaim>(o => o.SourceKey == user.Key && (o.ClaimType == SanteDBClaimTypes.SanteDBOTAuthCode || o.ClaimType == SanteDBClaimTypes.SanteDBCodeAuth) && (!o.ClaimExpiry.HasValue || o.ClaimExpiry > DateTime.Now)); DbUserClaim tfaClaim = claims.FirstOrDefault(o => o.ClaimType == SanteDBClaimTypes.SanteDBOTAuthCode), noPassword = claims.FirstOrDefault(o => o.ClaimType == SanteDBClaimTypes.SanteDBCodeAuth); if (tfaClaim == null) { throw new InvalidOperationException("Cannot find appropriate claims for TFA"); } // Expiry check IClaimsPrincipal retVal = null; if (String.IsNullOrEmpty(password) && Boolean.Parse(noPassword?.ClaimValue ?? "false") && tfaSecret == tfaClaim.ClaimValue) // TFA password hash sent as password, this is a password reset token - It will be set to expire ASAP { retVal = AdoClaimsIdentity.Create(user, true, "Tfa+LastPasswordHash").CreateClaimsPrincipal(); (retVal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.PurposeOfUse, PurposeOfUseKeys.SecurityAdmin.ToString())); (retVal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBScopeClaim, PermissionPolicyIdentifiers.LoginPasswordOnly)); (retVal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBScopeClaim, PermissionPolicyIdentifiers.ReadMetadata)); (retVal.Identity as IClaimsIdentity).RemoveClaim(retVal.FindFirst(SanteDBClaimTypes.Expiration)); (retVal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.Expiration, DateTime.Now.AddMinutes(5).ToString("o"))); // Special case, passwoird } else if (!String.IsNullOrEmpty(password)) { if (!user.TwoFactorEnabled || tfaSecret == tfaClaim.ClaimValue) { retVal = this.Authenticate(userName, password) as IClaimsPrincipal; } else { throw new AuthenticationException("TFA_MISMATCH"); } } else { throw new PolicyViolationException(new GenericPrincipal(new GenericIdentity(userName), new string[0]), PermissionPolicyIdentifiers.Login, PolicyGrantType.Deny); } // Now we want to fire authenticated this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, retVal, true)); tx.Commit(); return(retVal); } catch { tx.Rollback(); throw; } } } } catch (Exception e) { this.m_traceSource.TraceEvent(EventLevel.Verbose, "Invalid credentials : {0}/{1}", userName, password); this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString()); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false)); throw; } }
/// <summary> /// Gets an un-authenticated identity /// </summary> public IIdentity GetIdentity(string userName) { return(AdoClaimsIdentity.Create(userName)); }
public IIdentity CreateIdentity(string userName, string password, IPrincipal authContext) { if (String.IsNullOrEmpty(userName)) { throw new ArgumentNullException(nameof(userName)); } else if (String.IsNullOrEmpty(password)) { throw new ArgumentNullException(nameof(password)); } else if (authContext == null) { throw new ArgumentNullException(nameof(authContext)); } this.m_traceSource.TraceInformation("Creating identity {0} ({1})", userName, authContext); try { using (var dataContext = this.m_configuration.Provider.GetWriteConnection()) { dataContext.Open(); using (var tx = dataContext.BeginTransaction()) try { var hashingService = ApplicationContext.Current.GetService <IPasswordHashingService>(); var pdpService = ApplicationContext.Current.GetService <IPolicyDecisionService>(); // Demand create identity new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, PermissionPolicyIdentifiers.CreateIdentity, authContext).Demand(); // Does this principal have the ability to DbSecurityUser newIdentityUser = new DbSecurityUser() { UserName = userName, PasswordHash = hashingService.EncodePassword(password), SecurityHash = Guid.NewGuid().ToString(), UserClass = UserClassKeys.HumanUser }; if (authContext != null) { newIdentityUser.CreatedByKey = authContext.GetUserKey(dataContext).Value; } dataContext.Insert(newIdentityUser); var retVal = AdoClaimsIdentity.Create(newIdentityUser); tx.Commit(); return(retVal); } catch { tx.Rollback(); throw; } } } catch (Exception e) { this.m_traceSource.TraceEvent(TraceEventType.Error, e.HResult, e.ToString()); throw; } }
/// <summary> /// Authenticate the user using a TwoFactorAuthentication secret /// </summary> public IPrincipal Authenticate(string userName, string password, string tfaSecret) { // First, let's verify the TFA if (String.IsNullOrEmpty(userName)) { throw new ArgumentNullException(nameof(userName)); } else if (String.IsNullOrEmpty(tfaSecret)) { throw new ArgumentNullException(nameof(tfaSecret)); } // Authentication event args var evt = new AuthenticatingEventArgs(userName); this.Authenticating?.Invoke(this, evt); if (evt.Cancel) { throw new SecurityException("Authentication cancelled"); } // Password hasher var hashingService = ApplicationContext.Current.GetService <IPasswordHashingService>(); tfaSecret = hashingService.EncodePassword(tfaSecret); // Try to authenticate try { using (var dataContext = this.m_configuration.Provider.GetWriteConnection()) { dataContext.Open(); using (var tx = dataContext.BeginTransaction()) { try { var user = dataContext.FirstOrDefault <DbSecurityUser>(o => o.UserName.ToLower() == userName.ToLower()); if (user == null) { throw new KeyNotFoundException(userName); } var claims = dataContext.Query <DbUserClaim>(o => o.SourceKey == user.Key && (o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretClaim || o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretExpiry || o.ClaimType == OpenIzClaimTypes.OpenIZPasswordlessAuth)); DbUserClaim tfaClaim = claims.FirstOrDefault(o => o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretClaim), tfaExpiry = claims.FirstOrDefault(o => o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretExpiry), noPassword = claims.FirstOrDefault(o => o.ClaimType == OpenIzClaimTypes.OpenIZPasswordlessAuth); if (tfaClaim == null || tfaExpiry == null) { throw new InvalidOperationException("Cannot find appropriate claims for TFA"); } // Expiry check ClaimsPrincipal retVal = null; DateTime expiryDate = DateTime.Parse(tfaExpiry.ClaimValue); if (expiryDate < DateTime.Now) { throw new SecurityException("TFA secret expired"); } else if (String.IsNullOrEmpty(password) && Boolean.Parse(noPassword?.ClaimValue ?? "false") && tfaSecret == tfaClaim.ClaimValue) // Last known password hash sent as password, this is a password reset token - It will be set to expire ASAP { retVal = AdoClaimsIdentity.Create(user, true, "Tfa+LastPasswordHash").CreateClaimsPrincipal(); (retVal.Identity as ClaimsIdentity).AddClaim(new Claim(OpenIzClaimTypes.OpenIzGrantedPolicyClaim, PermissionPolicyIdentifiers.ChangePassword)); (retVal.Identity as ClaimsIdentity).RemoveClaim(retVal.FindFirst(ClaimTypes.Expiration)); // TODO: Add to configuration (retVal.Identity as ClaimsIdentity).AddClaim(new Claim(ClaimTypes.Expiration, DateTime.Now.AddMinutes(5).ToString("o"))); } else if (!String.IsNullOrEmpty(password)) { retVal = this.Authenticate(userName, password) as ClaimsPrincipal; } else { throw new PolicyViolationException(PermissionPolicyIdentifiers.Login, PolicyDecisionOutcomeType.Deny); } // Now we want to fire authenticated this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, retVal, true)); tx.Commit(); return(retVal); } catch { tx.Rollback(); throw; } } } } catch (Exception e) { this.m_traceSource.TraceEvent(TraceEventType.Verbose, e.HResult, "Invalid credentials : {0}/{1}", userName, password); this.m_traceSource.TraceEvent(TraceEventType.Error, e.HResult, e.ToString()); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false)); throw; } }
/// <summary> /// Authenticate this user based on the session /// </summary> /// <param name="session">The session for which authentication is being saught</param> /// <returns>The authenticated principal</returns> public IPrincipal Authenticate(ISession session) { try { var sessionId = new Guid(session.Id.Take(16).ToArray()); var adhocCache = ApplicationServiceContext.Current.GetService <IAdhocCacheService>(); var authCache = adhocCache?.Get <object[]>($"s{sessionId}"); using (var context = this.m_configuration.Provider.GetReadonlyConnection()) { context.Open(); var sql = context.CreateSqlStatement <DbSession>().SelectFrom(typeof(DbSession), typeof(DbSecurityApplication), typeof(DbSecurityUser), typeof(DbSecurityDevice)) .InnerJoin <DbSecurityApplication>(o => o.ApplicationKey, o => o.Key) .Join <DbSession, DbSecurityUser>("LEFT", o => o.UserKey, o => o.Key) .Join <DbSession, DbSecurityDevice>("LEFT", o => o.DeviceKey, o => o.Key) .Where <DbSession>(s => s.Key == sessionId); DbSecurityApplication securityApplication = null; DbSecurityDevice securityDevice = null; DbSecurityUser securityUser = null; DbSession securitySession = null; if (authCache == null) { var auth = context.FirstOrDefault <CompositeResult <DbSession, DbSecurityApplication, DbSecurityUser, DbSecurityDevice> >(sql); auth.Object2.Secret = null; auth.Object3.Password = null; auth.Object3.SecurityHash = null; auth.Object4.DeviceSecret = null; adhocCache?.Add($"s{sessionId}", auth.Values, new TimeSpan(0, 5, 0)); securityApplication = auth.Object2; securityDevice = auth.Object4; securityUser = auth.Object3; securitySession = auth.Object1; } else { securitySession = authCache[0] as DbSession ?? (authCache[0] as JObject).ToObject <DbSession>(); securityApplication = authCache[1] as DbSecurityApplication ?? (authCache[1] as JObject).ToObject <DbSecurityApplication>(); securityUser = authCache[2] as DbSecurityUser ?? (authCache[2] as JObject).ToObject <DbSecurityUser>(); securityDevice = authCache[3] as DbSecurityDevice ?? (authCache[3] as JObject).ToObject <DbSecurityDevice>(); } // Identities List <IClaimsIdentity> identities = new List <IClaimsIdentity>(3); if (securitySession == null) { throw new SecuritySessionException(SessionExceptionType.NotEstablished, "Session not found", null); } if (securitySession.NotAfter < DateTimeOffset.Now) { throw new SecuritySessionException(SessionExceptionType.Expired, "Session expired", null); } else if (securitySession.NotBefore > DateTimeOffset.Now) { throw new SecuritySessionException(SessionExceptionType.NotYetValid, "Session not yet valid", null); } if (securitySession.ApplicationKey != null) { identities.Add(new Server.Core.Security.ApplicationIdentity(securityApplication.Key, securityApplication.PublicId, true)); } if (securitySession.DeviceKey.HasValue) { identities.Add(new DeviceIdentity(securityDevice.Key, securityDevice.PublicId, true)); } var principal = securitySession.UserKey.GetValueOrDefault() == Guid.Empty ? new SanteDBClaimsPrincipal(identities) : AdoClaimsIdentity.Create(context, securityUser, true, "SESSION").CreateClaimsPrincipal(identities); identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.AuthenticationInstant, securitySession.NotBefore.ToString("o"))); identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.Expiration, securitySession.NotAfter.ToString("o"))); identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBSessionIdClaim, securitySession.Key.ToString())); // Add claims from session foreach (var clm in session.Claims.Where(o => o.Type != SanteDBClaimTypes.Sid && o.Type != SanteDBClaimTypes.SanteDBApplicationIdentifierClaim && o.Type != SanteDBClaimTypes.SanteDBDeviceIdentifierClaim)) { identities.First().AddClaim(clm); } // TODO: Load additional claims made about the user on the session return(principal); } } catch (Exception e) { this.m_traceSource.TraceEvent(EventLevel.Verbose, "Invalid session auth: {0}", e.Message); this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString()); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(null, null, false)); throw new SecuritySessionException(SessionExceptionType.Other, $"Could not perform session authentication", e); } }
/// <summary> /// Authenticate a user using their challenge response /// </summary> public IPrincipal Authenticate(string userName, Guid challengeKey, string response, String tfa) { try { var authArgs = new AuthenticatingEventArgs(userName); this.Authenticating?.Invoke(this, authArgs); if (authArgs.Cancel) { throw new SecurityException("Authentication cancelled"); } var hashService = ApplicationServiceContext.Current.GetService <IPasswordHashingService>(); var responseHash = hashService.ComputeHash(response); // Connection to perform auth using (var context = this.m_configuration.Provider.GetWriteConnection()) { context.Open(); var query = context.CreateSqlStatement <DbSecurityUser>().SelectFrom(typeof(DbSecurityUser), typeof(DbSecurityUserChallengeAssoc)) .InnerJoin <DbSecurityUserChallengeAssoc>(o => o.Key, o => o.UserKey) .Where(o => o.UserName.ToLower() == userName.ToLower() && o.ObsoletionTime == null) .And <DbSecurityUserChallengeAssoc>(o => o.ExpiryTime > DateTimeOffset.Now); var dbUser = context.FirstOrDefault <CompositeResult <DbSecurityUser, DbSecurityUserChallengeAssoc> >(query); // User found? if (dbUser == null) { throw new SecurityException("AUTH_INV"); } // TFA? if (!String.IsNullOrEmpty(tfa)) { var tfaSecret = ApplicationServiceContext.Current.GetService <IPasswordHashingService>().ComputeHash(tfa); var claims = context.Query <DbUserClaim>(o => o.SourceKey == dbUser.Object1.Key && (!o.ClaimExpiry.HasValue || o.ClaimExpiry > DateTimeOffset.Now)).ToList(); DbUserClaim tfaClaim = claims.FirstOrDefault(o => o.ClaimType == SanteDBClaimTypes.SanteDBOTAuthCode); if (tfaClaim == null || !tfaSecret.Equals(tfaClaim.ClaimValue, StringComparison.OrdinalIgnoreCase)) { throw new SecurityException("TFA_MISMATCH"); } } if (dbUser.Object1.Lockout > DateTimeOffset.Now) { throw new SecurityException("AUTH_LCK"); } else if (dbUser.Object2.ChallengeResponse != responseHash || dbUser.Object1.Lockout.GetValueOrDefault() > DateTimeOffset.Now) // Increment invalid { dbUser.Object1.InvalidLoginAttempts++; if (dbUser.Object1.InvalidLoginAttempts > this.m_securityConfiguration.GetSecurityPolicy <Int32>(SecurityPolicyIdentification.MaxInvalidLogins, 5)) { dbUser.Object1.Lockout = DateTimeOffset.Now.Add(new TimeSpan(0, 0, dbUser.Object1.InvalidLoginAttempts.Value * 30)); } dbUser.Object1.UpdatedByKey = Guid.Parse(AuthenticationContext.SystemUserSid); dbUser.Object1.UpdatedTime = DateTimeOffset.Now; context.Update(dbUser.Object1); if (dbUser.Object1.Lockout > DateTimeOffset.Now) { throw new AuthenticationException("AUTH_LCK"); } else { throw new AuthenticationException("AUTH_INV"); } } else { var principal = AdoClaimsIdentity.Create(context, dbUser.Object1, true, "Secret=" + challengeKey.ToString()).CreateClaimsPrincipal(); this.m_policyEnforcementService.Demand(PermissionPolicyIdentifiers.Login, principal); // must still be allowed to login (principal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.PurposeOfUse, PurposeOfUseKeys.SecurityAdmin.ToString())); (principal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBScopeClaim, PermissionPolicyIdentifiers.ReadMetadata)); (principal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBScopeClaim, PermissionPolicyIdentifiers.LoginPasswordOnly)); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, principal, true)); return(principal); } } } catch (Exception e) { this.m_tracer.TraceError("Challenge authentication failed: {0}", e); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false)); throw new AuthenticationException($"Challenge authentication failed"); } }