/// <summary> /// Authenticate this user with a local PIN number /// </summary> /// <param name="userName">The name of the user</param> /// <param name="password">The password of the user</param> /// <param name="pin">The PIN number for PIN based authentication</param> /// <returns>The authenticated principal</returns> private IPrincipal AuthenticateInternal(String userName, string password, byte[] pin) { var config = ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>(); // Pre-event AuthenticatingEventArgs e = new AuthenticatingEventArgs(userName) { }; this.Authenticating?.Invoke(this, e); if (e.Cancel) { this.m_tracer.TraceWarning("Pre-Event hook indicates cancel {0}", userName); return(e.Principal); } IPrincipal retVal = null; try { // Connect to the db var connection = this.CreateConnection(); using (connection.Lock()) { // Password service IPasswordHashingService passwordHash = ApplicationContext.Current.GetService(typeof(IPasswordHashingService)) as IPasswordHashingService; DbSecurityUser dbs = connection.Table <DbSecurityUser>().FirstOrDefault(o => o.UserName.ToLower() == userName.ToLower() && !o.ObsoletionTime.HasValue); if (dbs == null) { throw new SecurityException(Strings.locale_invalidUserNamePassword); } else if (dbs.Lockout.HasValue && dbs.Lockout > DateTime.Now) { throw new SecurityException(Strings.locale_accountLocked); } else if (dbs.ObsoletionTime != null) { throw new SecurityException(Strings.locale_accountObsolete); } else if (!String.IsNullOrEmpty(password) && passwordHash.ComputeHash(password) != dbs.Password || pin != null && passwordHash.ComputeHash(Encoding.UTF8.GetString(pin.Select(o => (byte)(o + 48)).ToArray(), 0, pin.Length)) != dbs.PinHash) { dbs.InvalidLoginAttempts++; connection.Update(dbs); throw new SecurityException(Strings.locale_invalidUserNamePassword); } else if (config?.MaxInvalidLogins.HasValue == true && dbs.InvalidLoginAttempts > config?.MaxInvalidLogins) { //s TODO: Make this configurable dbs.Lockout = DateTime.Now.AddSeconds(30 * (dbs.InvalidLoginAttempts - config.MaxInvalidLogins.Value)); connection.Update(dbs); throw new SecurityException(Strings.locale_accountLocked); } // TODO: Lacks login permission else { dbs.LastLoginTime = DateTime.Now; dbs.InvalidLoginAttempts = 0; connection.Update(dbs); var roles = connection.Query <DbSecurityRole>("SELECT security_role.* FROM security_user_role INNER JOIN security_role ON (security_role.uuid = security_user_role.role_id) WHERE lower(security_user_role.user_id) = lower(?)", dbs.Uuid).Select(o => o.Name).ToArray(); var additionalClaims = new List <IClaim>() { new SanteDBClaim(SanteDBClaimTypes.NameIdentifier, dbs.Key.ToString()), new SanteDBClaim(SanteDBClaimTypes.DefaultNameClaimType, dbs.UserName), new SanteDBClaim(SanteDBClaimTypes.SanteDBApplicationIdentifierClaim, ApplicationContext.Current.Application.Key.ToString()), // Local application only allows new SanteDBClaim(SanteDBClaimTypes.SanteDBDeviceIdentifierClaim, ApplicationContext.Current.Device.Key.ToString()) }; // Create the principal retVal = new SQLitePrincipal(new SQLiteIdentity(dbs.UserName, true, DateTime.Now, DateTime.Now.Add(config?.MaxLocalSession ?? new TimeSpan(0, 15, 0)), additionalClaims), roles); ApplicationServiceContext.Current.GetService <IPolicyEnforcementService>().Demand(PermissionPolicyIdentifiers.Login, retVal); } } // Post-event this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(userName, retVal, true)); } catch (Exception ex) { this.m_tracer.TraceError("Error establishing session: {0}", ex); this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(userName, retVal, false)); throw new DataPersistenceException($"Error establishing session", ex); } return(retVal); }
/// <summary> /// Authenticate the device /// </summary> public IPrincipal Authenticate(string deviceId, string deviceSecret, AuthenticationMethod authMethod = AuthenticationMethod.Any) { var config = ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>(); if (!authMethod.HasFlag(AuthenticationMethod.Local)) { throw new InvalidOperationException("Identity provider only supports local auth"); } // Pre-event AuthenticatingEventArgs e = new AuthenticatingEventArgs(deviceId) { }; this.Authenticating?.Invoke(this, e); if (e.Cancel) { this.m_tracer.TraceWarning("Pre-Event hook indicates cancel {0}", deviceId); return(e.Principal); } IPrincipal retVal = null; try { // Connect to the db var connection = this.CreateConnection(); using (connection.Lock()) { // Password service IPasswordHashingService passwordHash = ApplicationContext.Current.GetService(typeof(IPasswordHashingService)) as IPasswordHashingService; DbSecurityDevice dbd = connection.Table <DbSecurityDevice>().FirstOrDefault(o => o.PublicId.ToLower() == deviceId.ToLower()); if (dbd == null) { throw new SecurityException(Strings.locale_authenticationFailure); } else if (config?.MaxInvalidLogins.HasValue == true && dbd.Lockout.HasValue && dbd.Lockout > DateTime.Now) { throw new SecurityException(Strings.locale_accountLocked); } else if (dbd.ObsoletionTime != null) { throw new SecurityException(Strings.locale_accountObsolete); } else if (!String.IsNullOrEmpty(deviceSecret) && passwordHash.ComputeHash(deviceSecret) != dbd.DeviceSecret) { dbd.InvalidAuthAttempts++; connection.Update(dbd); throw new SecurityException(Strings.locale_authenticationFailure); } else if (config?.MaxInvalidLogins.HasValue == true && dbd.InvalidAuthAttempts > config?.MaxInvalidLogins) { //s TODO: Make this configurable dbd.Lockout = DateTime.Now.AddSeconds(30 * (dbd.InvalidAuthAttempts - config.MaxInvalidLogins.Value)); connection.Update(dbd); throw new SecurityException(Strings.locale_accountLocked); } // TODO: Lacks login permission else { dbd.LastAuthTime = DateTime.Now; dbd.InvalidAuthAttempts = 0; connection.Update(dbd); IPolicyDecisionService pdp = ApplicationContext.Current.GetService <IPolicyDecisionService>(); IPolicyInformationService pip = ApplicationContext.Current.GetService <IPolicyInformationService>(); List <IClaim> additionalClaims = new List <IClaim>(); additionalClaims.AddRange(pip.GetPolicies(dbd).Where(o => o.Rule == PolicyGrantType.Grant).Select(o => new SanteDBClaim(SanteDBClaimTypes.SanteDBGrantedPolicyClaim, o.Policy.Oid))); additionalClaims.Add(new SanteDBClaim(SanteDBClaimTypes.SanteDBDeviceIdentifierClaim, dbd.Key.ToString())); additionalClaims.Add(new SanteDBClaim(SanteDBClaimTypes.SanteDBApplicationIdentifierClaim, ApplicationContext.Current.Application.Key.ToString())); // Local application only for SQLite // Create the principal retVal = new SQLitePrincipal(new SQLiteDeviceIdentity(dbd.PublicId, true, DateTime.Now, DateTime.Now.Add(config?.MaxLocalSession ?? new TimeSpan(0, 15, 0)), additionalClaims), new string[] { }); ApplicationServiceContext.Current.GetService <IPolicyEnforcementService>().Demand(PermissionPolicyIdentifiers.LoginAsService, retVal); } } // Post-event this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(deviceId, retVal, true)); } catch (Exception ex) { this.m_tracer.TraceError("Error establishing device session ({1}): {0}", ex, deviceSecret); this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(deviceId, retVal, false)); throw; } return(retVal); }
/// <summary> /// Authenticates the specified user in the database against the selected challenge and selected response /// </summary> public IPrincipal Authenticate(string userName, Guid challengeKey, string response, string tfaSecret) { 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 var conn = this.CreateConnection(); using (conn.Lock()) { var query = "select * from security_user inner join security_user_challenge on (security_user.uuid = security_user_challenge.user_uuid) where lower(security_user.username) = lower(?) and challenge_uuid = ? and security_user.obsoletionTime is null and (security_user_challenge.expiration is null or security_user_challenge.expiration > ?)"; var dbUser = conn.Query <DbSecurityUserChallengeAssoc.QueryResult>(query, userName, challengeKey.ToByteArray(), DateTime.Now).FirstOrDefault(); // User found? if (dbUser == null) { throw new SecurityException("AUTH_INV"); } // TFA? if (dbUser.Lockout > DateTime.Now) { throw new SecurityException("AUTH_LCK"); } else if (dbUser.ChallengeResponse != responseHash || dbUser.Lockout.GetValueOrDefault() > DateTime.Now) // Increment invalid { dbUser.InvalidLoginAttempts++; if (dbUser.InvalidLoginAttempts > this.m_securityConfiguration.MaxInvalidLogins) { dbUser.Lockout = DateTime.Now.Add(new TimeSpan(0, 0, dbUser.InvalidLoginAttempts * 30)); } dbUser.UpdatedByKey = Guid.Parse(AuthenticationContext.SystemUserSid); dbUser.UpdatedTime = DateTimeOffset.Now; conn.Update(dbUser, typeof(DbSecurityUser)); if (dbUser.Lockout > DateTime.Now) { throw new AuthenticationException("AUTH_LCK"); } else { throw new AuthenticationException("AUTH_INV"); } } else { dbUser.LastLoginTime = DateTime.Now; dbUser.InvalidLoginAttempts = 0; conn.Update(dbUser, typeof(DbSecurityUser)); var roles = conn.Query <DbSecurityRole>("SELECT security_role.* FROM security_user_role INNER JOIN security_role ON (security_role.uuid = security_user_role.role_id) WHERE lower(security_user_role.user_id) = lower(?)", dbUser.Uuid).Select(o => o.Name).ToArray(); var additionalClaims = new List <IClaim>() { new SanteDBClaim(SanteDBClaimTypes.NameIdentifier, dbUser.Key.ToString()), new SanteDBClaim(SanteDBClaimTypes.DefaultNameClaimType, dbUser.UserName), new SanteDBClaim(SanteDBClaimTypes.SanteDBApplicationIdentifierClaim, ApplicationContext.Current.Application.Key.ToString()), // Local application only allows new SanteDBClaim(SanteDBClaimTypes.SanteDBDeviceIdentifierClaim, ApplicationContext.Current.Device.Key.ToString()) }; // Create the principal var principal = new SQLitePrincipal(new SQLiteIdentity(dbUser.UserName, true, DateTime.Now, DateTime.Now.Add(this.m_securityConfiguration.MaxLocalSession), additionalClaims), roles); ApplicationServiceContext.Current.GetService <IPolicyEnforcementService>().Demand(PermissionPolicyIdentifiers.Login, principal); new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, PermissionPolicyIdentifiers.Login, principal).Demand(); // 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"); } }