/// <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> /// 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 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 = SqlClaimsIdentity.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 /// </summary> /// <param name="principal">Principal.</param> /// <param name="password">Password.</param> public IPrincipal Authenticate(IPrincipal principal, String password) { var config = ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>(); if (principal == null) { throw new ArgumentNullException(nameof(principal)); } else if (String.IsNullOrEmpty(password)) { if (principal.Identity.IsAuthenticated) { // Refresh if (principal is SQLitePrincipal) /// extend the existing session { (principal as SQLitePrincipal).Expires = DateTime.Now.Add(config?.MaxLocalSession ?? new TimeSpan(0, 15, 0)); } else if (principal is ClaimsPrincipal) // switch them to a SQLitePrincipal { var sid = (principal as ClaimsPrincipal).FindClaim(ClaimTypes.Sid)?.Value; var uname = (principal as ClaimsPrincipal).FindClaim(ClaimsIdentity.DefaultNameClaimType)?.Value; if (!String.IsNullOrEmpty(uname)) { ApplicationContext.Current.GetService <ITickleService>()?.SendTickle(new Tickler.Tickle(Guid.Parse(sid), Tickler.TickleType.SecurityInformation | Tickler.TickleType.Toast, Strings.locale_securitySwitchedMode, DateTime.Now.AddSeconds(10))); return(new SQLitePrincipal(new SQLiteIdentity(uname, true), (principal as ClaimsPrincipal).Claims.Where(o => o.Type == ClaimsIdentity.DefaultRoleClaimType).Select(o => o.Value).ToArray()) { Expires = DateTime.Now.Add(config?.MaxLocalSession ?? new TimeSpan(0, 15, 0)), IssueTime = DateTime.Now }); } else { throw new SecurityException(Strings.locale_sessionError); } } return(principal); } else { throw new ArgumentNullException(nameof(password)); } } // Pre-event AuthenticatingEventArgs e = new AuthenticatingEventArgs(principal.Identity.Name, password) { Principal = principal }; this.Authenticating?.Invoke(this, e); if (e.Cancel) { this.m_tracer.TraceWarning("Pre-Event hook indicates cancel {0}", principal.Identity.Name); 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 == principal.Identity.Name); if (dbs == null) { throw new SecurityException(Strings.locale_invalidUserNamePassword); } else if (config?.MaxInvalidLogins.HasValue == true && 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 (passwordHash.ComputeHash(password) != dbs.PasswordHash) { 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); // Create the principal retVal = new SQLitePrincipal(new SQLiteIdentity(dbs.UserName, true), connection.Query <DbSecurityRole>("SELECT security_role.* FROM security_user_role INNER JOIN security_role ON (security_role.uuid = security_user_role.role_id) WHERE security_user_role.user_id = ?", dbs.Uuid).Select(o => o.Name).ToArray()) { IssueTime = DateTime.Now, Expires = DateTime.Now.Add(config?.MaxLocalSession ?? new TimeSpan(0, 15, 0)) }; } } // Post-event this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(principal.Identity.Name, password, true) { Principal = retVal }); } catch (Exception ex) { this.m_tracer.TraceError("Error establishing session: {0}", ex); this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(principal.Identity.Name, password, false) { Principal = retVal }); throw; } return(retVal); }
/// <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> /// 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 user /// </summary> /// <param name="principal">Principal.</param> /// <param name="password">Password.</param> public System.Security.Principal.IPrincipal Authenticate(System.Security.Principal.IPrincipal principal, string password, String tfaSecret) { AuthenticatingEventArgs e = new AuthenticatingEventArgs(principal.Identity.Name, password) { Principal = principal }; this.Authenticating?.Invoke(this, e); if (e.Cancel) { this.m_tracer.TraceWarning("Pre-Event ordered cancel of auth {0}", principal); return(e.Principal); } var localIdp = new LocalIdentityService(); // Get the scope being requested String scope = "*"; if (principal is ClaimsPrincipal) { scope = (principal as ClaimsPrincipal).Claims.FirstOrDefault(o => o.Type == ClaimTypes.OpenIzScopeClaim)?.Value ?? scope; } else if (principal is SQLitePrincipal && password == null) { return(localIdp.Authenticate(principal, password)); } else { scope = ApplicationContext.Current.GetRestClient("imsi")?.Description.Endpoint[0].Address ?? ApplicationContext.Current.GetRestClient("ami")?.Description.Endpoint[0].Address ?? "*"; } // Authenticate IPrincipal retVal = null; try { using (IRestClient restClient = ApplicationContext.Current.GetRestClient("acs")) { try { // Set credentials restClient.Credentials = new OAuthTokenServiceCredentials(principal); // Create grant information OAuthTokenRequest request = null; if (!String.IsNullOrEmpty(password)) { request = new OAuthTokenRequest(principal.Identity.Name, password, scope); } else if (principal is TokenClaimsPrincipal) { request = new OAuthTokenRequest(principal as TokenClaimsPrincipal, scope); } else { request = new OAuthTokenRequest(principal.Identity.Name, null, scope); } try { restClient.Requesting += (o, p) => { p.AdditionalHeaders.Add("X-OpenIZClient-Claim", Convert.ToBase64String(Encoding.UTF8.GetBytes(String.Format("{0}={1}", ClaimTypes.OpenIzScopeClaim, scope)))); if (!String.IsNullOrEmpty(tfaSecret)) { p.AdditionalHeaders.Add("X-OpenIZ-TfaSecret", tfaSecret); } }; // Invoke if (ApplicationContext.Current.GetService <INetworkInformationService>().IsNetworkAvailable) { if (principal.Identity.Name == ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DeviceName) { restClient.Description.Endpoint[0].Timeout = restClient.Description.Endpoint[0].Timeout * 2; } else { restClient.Description.Endpoint[0].Timeout = (int)(restClient.Description.Endpoint[0].Timeout * 0.6666f); } OAuthTokenResponse response = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-urlform-encoded", request); retVal = new TokenClaimsPrincipal(response.AccessToken, response.TokenType, response.RefreshToken); } else { this.m_tracer.TraceWarning("Network unavailable, trying local"); try { retVal = localIdp.Authenticate(principal, password); } catch (Exception ex2) { this.m_tracer.TraceError("Error falling back to local IDP: {0}", ex2); throw new SecurityException(String.Format(Strings.err_offline_use_cache_creds, ex2.Message), ex2); } } this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, password, true) { Principal = retVal }); } catch (WebException ex) // Raw level web exception { // Not network related, but a protocol level error if (ex.Status == WebExceptionStatus.ProtocolError) { throw; } this.m_tracer.TraceWarning("Original OAuth2 request failed trying local. {0}", ex.Message); try { retVal = localIdp.Authenticate(principal, password); } catch (Exception ex2) { this.m_tracer.TraceError("Error falling back to local IDP: {0}", ex2); throw new SecurityException(String.Format(Strings.err_offline_use_cache_creds, ex2.Message), ex2); } } catch (SecurityException ex) { this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message); throw; } catch (Exception ex) // fallback to local { try { this.m_tracer.TraceWarning("Original OAuth2 request failed trying local. {0}", ex.Message); retVal = localIdp.Authenticate(principal, password); } catch (Exception ex2) { this.m_tracer.TraceError("Error falling back to local IDP: {0}", ex2); throw new SecurityException(String.Format(Strings.err_offline_use_cache_creds, ex2.Message), ex2); } } // We have a match! Lets make sure we cache this data // TODO: Clean this up try { if (!(retVal is SQLitePrincipal)) { ApplicationContext.Current.GetService <IThreadPoolService>().QueueUserWorkItem(o => this.SynchronizeSecurity(password, o as IPrincipal), retVal); } } catch (Exception ex) { try { this.m_tracer.TraceWarning("Failed to fetch remote security parameters - {0}", ex.Message); retVal = localIdp.Authenticate(principal, password); } catch (Exception ex2) { this.m_tracer.TraceError("Error falling back to local IDP: {0}", ex2); throw new SecurityException(String.Format(Strings.err_offline_use_cache_creds, ex2.Message)); } } } catch (RestClientException <OAuthTokenResponse> ex) { this.m_tracer.TraceError("REST client exception: {0}", ex.Message); var se = new SecurityException( String.Format("err_oauth2_{0}", ex.Result.Error), ex ); se.Data.Add("detail", ex.Result); throw se; } catch (SecurityTokenException ex) { this.m_tracer.TraceError("TOKEN exception: {0}", ex.Message); throw new SecurityException( String.Format("err_token_{0}", ex.Type), ex ); } catch (SecurityException ex) { this.m_tracer.TraceError("Security exception: {0}", ex.Message); throw; } catch (Exception ex) { this.m_tracer.TraceError("Generic exception: {0}", ex); throw new SecurityException( Strings.err_authentication_exception, ex); } } } catch { this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, password, false) { Principal = retVal }); throw; } return(retVal); }
public void On_pop3Client_Authenticating(object sender, AuthenticatingEventArgs e) { log.Debug(String.Format("In autenticazione. Host: {0} - User: {1} - Password: {2}", e.Host, e.Username, e.Password)); }
/// <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"); } }
/// <summary> /// Authenticate the user /// </summary> /// <param name="principal">Principal.</param> /// <param name="password">Password.</param> public System.Security.Principal.IPrincipal Authenticate(System.Security.Principal.IPrincipal principal, string password, String tfaSecret) { AuthenticatingEventArgs e = new AuthenticatingEventArgs(principal.Identity.Name); this.Authenticating?.Invoke(this, e); if (e.Cancel) { this.m_tracer.TraceWarning("Pre-Event ordered cancel of auth {0}", principal); return(null); } // Get the scope being requested String scope = "*"; // Authenticate IPrincipal retVal = null; try { using (IRestClient restClient = ApplicationContext.Current.GetRestClient(Core.Interop.ServiceEndpointType.AuthenticationService)) { // Set credentials restClient.Credentials = new OAuthTokenServiceCredentials(principal); // Create grant information OAuthTokenRequest request = null; if (!String.IsNullOrEmpty(password)) { request = new OAuthTokenRequest(principal.Identity.Name, password, scope); } else if (principal is TokenClaimsPrincipal) { request = new OAuthTokenRequest(principal as TokenClaimsPrincipal, scope); } else { request = new OAuthTokenRequest(principal.Identity.Name, null, scope); } // Set credentials if (restClient.Description.Binding.Security?.Mode == Core.Http.Description.SecurityScheme.Basic) { restClient.Credentials = new OAuthTokenServiceCredentials(principal); } else { request.ClientId = ApplicationContext.Current.ApplicationName; request.ClientSecret = ApplicationContext.Current.ApplicationSecret; } try { restClient.Requesting += (o, p) => { if (!String.IsNullOrEmpty(tfaSecret)) { p.AdditionalHeaders.Add("X-SanteDB-TfaSecret", tfaSecret); } }; OAuthTokenResponse response = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-form-urlencoded", request); retVal = new TokenClaimsPrincipal(response.AccessToken, response.IdToken, response.TokenType, response.RefreshToken); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, retVal, true)); } catch (RestClientException <OAuthTokenResponse> ex) { this.m_tracer.TraceWarning("OAUTH Server Responded: {0}", ex.Result.ErrorDescription); } catch (WebException ex) // Raw level web exception { this.m_tracer.TraceError("Error authenticating: {0}", ex.Message); } catch (SecurityException ex) { this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message); throw; } catch (Exception ex) // fallback to local { this.m_tracer.TraceError("General Authentication Error: {0}", ex.Message); } } } catch { this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, retVal, false)); throw; } return(retVal); }
/// <summary> /// Challenge key authentication for password change only /// </summary> public IPrincipal Authenticate(string userName, Guid challengeKey, string response, string tfaSecret) { AuthenticatingEventArgs e = new AuthenticatingEventArgs(userName); this.Authenticating?.Invoke(this, e); if (e.Cancel) { this.m_tracer.TraceWarning("Pre-Event ordered cancel of auth {0}", userName); return(e.Principal); } var localIdp = ApplicationContext.Current.GetService <IOfflineIdentityProviderService>(); IPrincipal retVal = null; // Get the scope being requested try { if (localIdp?.IsLocalUser(userName) == true) { var localScIdp = ApplicationServiceContext.Current.GetService <IOfflineSecurityChallengeIdentityService>(); retVal = localScIdp.Authenticate(userName, challengeKey, response, tfaSecret); } else { using (IRestClient restClient = ApplicationContext.Current.GetRestClient("acs")) { // Create grant information OAuthTokenRequest request = new OAuthResetTokenRequest(userName, challengeKey.ToString(), response); // Set credentials if (ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DomainAuthentication == DomainClientAuthentication.Basic) { restClient.Credentials = new OAuthTokenServiceCredentials(null); } else { request.ClientId = ApplicationContext.Current.Application.Name; request.ClientSecret = ApplicationContext.Current.Application.ApplicationSecret; } try { restClient.Requesting += (o, p) => { // Add device credential if (!String.IsNullOrEmpty(ApplicationContext.Current.Device.DeviceSecret)) { p.AdditionalHeaders.Add(HeaderTypes.HttpDeviceAuthentication, $"BASIC {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ApplicationContext.Current.Device.Name}:{ApplicationContext.Current.Device.DeviceSecret}"))}"); } if (!String.IsNullOrEmpty(tfaSecret)) { p.AdditionalHeaders.Add(HeaderTypes.HttpTfaSecret, tfaSecret); } }; // Invoke if (ApplicationServiceContext.Current.GetService <INetworkInformationService>().IsNetworkAvailable) { restClient.Description.Endpoint[0].Timeout = 5000; restClient.Invoke <Object, Object>("PING", "/", null, null); restClient.Description.Endpoint[0].Timeout = (int)(restClient.Description.Endpoint[0].Timeout * 0.6666f); // Swap out the endpoint and authenticate var configuration = this.GetConfigurationInfo(); if (configuration == null) // default action { var oauthResponse = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-form-urlencoded", request); retVal = new TokenClaimsPrincipal(oauthResponse.AccessToken, oauthResponse.IdToken ?? oauthResponse.AccessToken, oauthResponse.TokenType, oauthResponse.RefreshToken, configuration); } else { if (!configuration.GrantTypesSupported.Contains("password")) { throw new InvalidOperationException("Password grants not supported by this provider"); } var oauthResponse = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>(configuration.TokenEndpoint, "application/x-www-form-urlencoded", request); retVal = new TokenClaimsPrincipal(oauthResponse.AccessToken, oauthResponse.IdToken ?? oauthResponse.AccessToken, oauthResponse.TokenType, oauthResponse.RefreshToken, configuration); } return(retVal); } else { throw new InvalidOperationException("Cannot send reset credential while offline"); } } catch (RestClientException <OAuthTokenResponse> ex) { this.m_tracer.TraceError("REST client exception: {0}", ex.Message); var se = new SecurityException( String.Format("err_oauth2_{0}", ex.Result.Error), ex ); se.Data.Add("oauth_result", ex.Result); throw se; } catch (SecurityException ex) { this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message); throw; } catch (Exception ex) // fallback to local { throw new SecurityException($"General authentication error occurred: {ex.Message}", ex); } } } this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, retVal, true)); } catch (Exception ex) { this.m_tracer.TraceError("OAUTH Error: {0}", ex.ToString()); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false)); throw; } return(retVal); }
/// <summary> /// Do internal authentication /// </summary> private IPrincipal DoAuthenticationInternal(String userName = null, String password = null, string tfaSecret = null, bool isOverride = false, TokenClaimsPrincipal refreshPrincipal = null, string purposeOfUse = null, string[] policies = null) { AuthenticatingEventArgs e = new AuthenticatingEventArgs(userName); this.Authenticating?.Invoke(this, e); if (e.Cancel) { this.m_tracer.TraceWarning("Pre-Event ordered cancel of auth {0}", userName); return(e.Principal); } var localIdp = ApplicationContext.Current.GetService <IOfflineIdentityProviderService>(); IPrincipal retVal = null; // Authenticate try { // Is the user a LOCAL_USER only? if (localIdp?.IsLocalUser(userName) == true) { retVal = localIdp.Authenticate(userName, password, tfaSecret); } else { using (IRestClient restClient = ApplicationContext.Current.GetRestClient("acs")) { // Construct oauth req OAuthTokenRequest request = null; if (refreshPrincipal != null) { request = new OAuthTokenRequest(refreshPrincipal, "*"); } else if (!String.IsNullOrEmpty(password)) { request = new OAuthTokenRequest(userName, password, "*"); } else { request = new OAuthTokenRequest(userName, null, "*"); } // Explicit policies if (policies != null) { request.Scope = String.Join(" ", policies); } // Set credentials for oauth req if (ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DomainAuthentication == DomainClientAuthentication.Basic) { restClient.Credentials = new OAuthTokenServiceCredentials(null); } else { request.ClientId = ApplicationContext.Current.Application.Name; request.ClientSecret = ApplicationContext.Current.Application.ApplicationSecret; } restClient.Requesting += (o, p) => { if (!String.IsNullOrEmpty(tfaSecret)) { p.AdditionalHeaders.Add(HeaderTypes.HttpTfaSecret, tfaSecret); } if (isOverride) { p.AdditionalHeaders.Add(HeaderTypes.HttpClaims, Convert.ToBase64String(Encoding.UTF8.GetBytes( $"{SanteDBClaimTypes.PurposeOfUse}={purposeOfUse};{SanteDBClaimTypes.SanteDBOverrideClaim}=true" ))); } // Add device credential if (!String.IsNullOrEmpty(ApplicationContext.Current.Device.DeviceSecret)) { p.AdditionalHeaders.Add(HeaderTypes.HttpDeviceAuthentication, $"BASIC {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ApplicationContext.Current.Device.Name}:{ApplicationContext.Current.Device.DeviceSecret}"))}"); } }; if (ApplicationServiceContext.Current.GetService <INetworkInformationService>().IsNetworkAvailable) { // Try OAUTH server try { restClient.Description.Endpoint[0].Timeout = 5000; restClient.Invoke <Object, Object>("PING", "/", null, null); if (userName == ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DeviceName) { restClient.Description.Endpoint[0].Timeout = restClient.Description.Endpoint[0].Timeout * 2; } else { restClient.Description.Endpoint[0].Timeout = (int)(restClient.Description.Endpoint[0].Timeout * 0.6666f); } // GEt configuration var configuration = this.GetConfigurationInfo(); if (configuration == null) // default action { var oauthResponse = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-form-urlencoded", request); retVal = new TokenClaimsPrincipal(oauthResponse.AccessToken, oauthResponse.IdToken ?? oauthResponse.AccessToken, oauthResponse.TokenType, oauthResponse.RefreshToken, configuration); } else { if (!configuration.GrantTypesSupported.Contains("password")) { throw new InvalidOperationException("Password grants not supported by this provider"); } var oauthResponse = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>(configuration.TokenEndpoint, "application/x-www-form-urlencoded", request); retVal = new TokenClaimsPrincipal(oauthResponse.AccessToken, oauthResponse.IdToken ?? oauthResponse.AccessToken, oauthResponse.TokenType, oauthResponse.RefreshToken, configuration); } } catch (RestClientException <OAuthTokenResponse> ex) // there was an actual OAUTH problem { this.m_tracer.TraceError("REST client exception: {0}", ex.Message); throw new SecurityException($"err_oauth_{ex.Result.Error}", ex); } catch (SecurityException ex) { this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message); throw; } catch (Exception ex) // All others, try local { this.m_tracer.TraceWarning("Original OAuth2 request failed trying local - Original Exception : {0}", ex); } if (retVal == null) // Some error occurred, use local { this.m_tracer.TraceWarning("Network unavailable, trying local"); try { if (localIdp == null) { throw new SecurityException(Strings.err_offline_no_local_available); } retVal = localIdp.Authenticate(userName, password); } catch (Exception ex2) { this.m_tracer.TraceError("Error falling back to local IDP: {0}", ex2); throw new SecurityException(String.Format(Strings.err_offline_use_cache_creds, ex2.Message), ex2); } } // We have a match! Lets make sure we cache this data // TODO: Clean this up if (!(retVal is IOfflinePrincipal)) { try { ApplicationContext.Current.GetService <IThreadPoolService>().QueueUserWorkItem(o => this.SynchronizeSecurity(password, o as IPrincipal), retVal); } catch (Exception e2) { this.m_tracer.TraceError("An error occurred when inserting the local credential: {0}", e2); } } } else { retVal = localIdp?.Authenticate(userName, password); } } } this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, retVal, true)); } catch (Exception ex) { this.m_tracer.TraceError("OAUTH Error: {0}", ex.ToString()); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, retVal, false)); throw new SecurityException($"Error establishing authentication session - {ex.Message}", ex); } return(retVal); }
/// <summary> /// Authenticate the specified device /// </summary> public IPrincipal Authenticate(string deviceId, string deviceSecret, AuthenticationMethod authMethod = AuthenticationMethod.Any) { AuthenticatingEventArgs e = new AuthenticatingEventArgs(deviceId); this.Authenticating?.Invoke(this, e); if (e.Cancel) { this.m_tracer.TraceWarning("Pre-Event ordered cancel of auth {0}", deviceId); return(e.Principal); } // Authenticate IPrincipal retVal = null; using (IRestClient restClient = ApplicationContext.Current.GetRestClient("acs")) { // Create grant information OAuthTokenRequest request = OAuthTokenRequest.CreateClientCredentialRequest(ApplicationContext.Current.Application.Name, ApplicationContext.Current.Application.ApplicationSecret, "*"); try { restClient.Requesting += (o, p) => { // Add device credential p.AdditionalHeaders.Add("X-Device-Authorization", $"BASIC {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{deviceId}:{deviceSecret}"))}"); }; // Invoke if (authMethod.HasFlag(AuthenticationMethod.Online) && ApplicationServiceContext.Current.GetService <INetworkInformationService>().IsNetworkAvailable&& ApplicationServiceContext.Current.GetService <IAdministrationIntegrationService>()?.IsAvailable() != false) // Network may be on but internet is not available { restClient.Description.Endpoint[0].Timeout = (int)(restClient.Description.Endpoint[0].Timeout * 0.333f); var configuration = this.GetConfigurationInfo(); if (configuration == null) { OAuthTokenResponse response = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-form-urlencoded", request); retVal = new TokenClaimsPrincipal(response.AccessToken, response.IdToken ?? response.AccessToken, response.TokenType, response.RefreshToken, configuration); } else { OAuthTokenResponse response = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>(configuration.TokenEndpoint, "application/x-www-form-urlencoded", request); retVal = new TokenClaimsPrincipal(response.AccessToken, response.IdToken ?? response.AccessToken, response.TokenType, response.RefreshToken, configuration); } // HACK: Set preferred sid to device SID var cprincipal = retVal as IClaimsPrincipal; var devId = cprincipal.FindFirst(SanteDBClaimTypes.SanteDBDeviceIdentifierClaim); if (devId != null) { cprincipal.Identities.First().RemoveClaim(cprincipal.FindFirst(SanteDBClaimTypes.Sid)); cprincipal.Identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.Sid, devId.Value)); } // Synchronize the security devices try { this.SynchronizeSecurity(deviceSecret, deviceId, retVal); } catch (Exception e2) { this.m_tracer.TraceError("Error synchronizing the local device security: {0}", e2); } this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(deviceId, retVal, true) { Principal = retVal }); } else if (authMethod.HasFlag(AuthenticationMethod.Local)) { // Is this another device authenticating against me? return(ApplicationContext.Current.GetService <IOfflineDeviceIdentityProviderService>().Authenticate(deviceId, deviceSecret)); } else { throw new InvalidOperationException("Cannot determine authentication method"); } } catch (SecurityTokenException ex) { this.m_tracer.TraceError("TOKEN exception: {0}", ex.Message); throw new SecurityException( String.Format("err_token_{0}", ex.Type), ex ); } catch (SecurityException ex) { this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message); throw; } catch (RestClientException <OAuthTokenResponse> ex) { this.m_tracer.TraceError("REST client exception: {0}", ex.Message); // Not network related, but a protocol level error if (authMethod.HasFlag(AuthenticationMethod.Local) && deviceId != ApplicationContext.Current.Device.Name) { this.m_tracer.TraceWarning("Network unavailable falling back to local"); return(ApplicationContext.Current.GetService <IOfflineDeviceIdentityProviderService>().Authenticate(deviceId, deviceSecret)); } else { this.m_tracer.TraceWarning("Original OAuth2 request failed: {0}", ex.Message); throw new SecurityException(Strings.err_authentication_exception, ex); } } catch (Exception ex) // Raw level web exception { // Not network related, but a protocol level error if (authMethod.HasFlag(AuthenticationMethod.Local) && deviceId != ApplicationContext.Current.Device.Name) { this.m_tracer.TraceWarning("Network unavailable falling back to local"); return(ApplicationContext.Current.GetService <IOfflineDeviceIdentityProviderService>().Authenticate(deviceId, deviceSecret)); } else { this.m_tracer.TraceWarning("Original OAuth2 request failed: {0}", ex.Message); throw new SecurityException(Strings.err_authentication_exception, 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> /// 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 = new ModelDataContext(this.m_configuration.ReadWriteConnectionString)) { var user = dataContext.SecurityUsers.FirstOrDefault(o => o.UserName == userName); if (user == null) { throw new KeyNotFoundException(userName); } SecurityUserClaim tfaClaim = dataContext.SecurityUserClaims.FirstOrDefault(o => o.UserId == user.Id && o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretClaim), tfaExpiry = dataContext.SecurityUserClaims.FirstOrDefault(o => o.UserId == user.Id && o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretExpiry), noPassword = dataContext.SecurityUserClaims.FirstOrDefault(o => o.UserId == user.Id && 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 = SqlClaimsIdentity.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)); return(retVal); } } 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 the user /// </summary> /// <param name="principal">Principal.</param> /// <param name="password">Password.</param> public System.Security.Principal.IPrincipal Authenticate(System.Security.Principal.IPrincipal principal, string password, String tfaSecret) { AuthenticatingEventArgs e = new AuthenticatingEventArgs(principal.Identity.Name); this.Authenticating?.Invoke(this, e); if (e.Cancel) { this.m_tracer.TraceWarning("Pre-Event ordered cancel of auth {0}", principal); return(null); } // Get the scope being requested String scope = "*"; if (principal is ClaimsPrincipal) { scope = (principal as ClaimsPrincipal).Claims.FirstOrDefault(o => o.Type == OpenIzClaimTypes.OpenIzScopeClaim)?.Value ?? scope; } else { scope = ApplicationContext.Current.GetRestClient(Core.Interop.ServiceEndpointType.ImmunizationIntegrationService)?.Description.Endpoint[0].Address ?? ApplicationContext.Current.GetRestClient(Core.Interop.ServiceEndpointType.AdministrationIntegrationService)?.Description.Endpoint[0].Address ?? "*"; } // Authenticate IPrincipal retVal = null; try { using (IRestClient restClient = ApplicationContext.Current.GetRestClient(Core.Interop.ServiceEndpointType.AuthenticationService)) { // Set credentials restClient.Credentials = new OAuthTokenServiceCredentials(principal); // Create grant information OAuthTokenRequest request = null; if (!String.IsNullOrEmpty(password)) { request = new OAuthTokenRequest(principal.Identity.Name, password, scope); } else if (principal is TokenClaimsPrincipal) { request = new OAuthTokenRequest(principal as TokenClaimsPrincipal, scope); } else { request = new OAuthTokenRequest(principal.Identity.Name, null, scope); } try { restClient.Requesting += (o, p) => { p.AdditionalHeaders.Add("X-OpenIZClient-Claim", Convert.ToBase64String(Encoding.UTF8.GetBytes(String.Format("{0}={1}", OpenIzClaimTypes.OpenIzScopeClaim, scope)))); if (!String.IsNullOrEmpty(tfaSecret)) { p.AdditionalHeaders.Add("X-OpenIZ-TfaSecret", tfaSecret); } }; OAuthTokenResponse response = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-urlform-encoded", request); retVal = new TokenClaimsPrincipal(response.AccessToken, response.TokenType, response.RefreshToken); this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, retVal, true)); } catch (RestClientException <OAuthTokenResponse> ex) { this.m_tracer.TraceWarning("OAUTH Server Responded: {0}", ex.Result.ErrorDescription); } catch (WebException ex) // Raw level web exception { this.m_tracer.TraceError("Error authenticating: {0}", ex.Message); } catch (SecurityException ex) { this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message); throw; } catch (Exception ex) // fallback to local { this.m_tracer.TraceError("General Authentication Error: {0}", ex.Message); } } } catch { this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, retVal, false)); throw; } return(retVal); }
/// <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"); } }