/// <summary> /// Authenticates the specified device identifier. /// </summary> /// <param name="deviceId">The device identifier.</param> /// <param name="deviceSecret">The device secret.</param> /// <returns>Returns the authenticated device principal.</returns> public IPrincipal Authenticate(string deviceId, string deviceSecret, AuthenticationMethod authMethod = AuthenticationMethod.Any) { if (!authMethod.HasFlag(AuthenticationMethod.Local)) { throw new InvalidOperationException("ADO.NET provider only supports local authentication"); } using (var dataContext = this.m_configuration.Provider.GetWriteConnection()) { try { dataContext.Open(); var hashService = ApplicationServiceContext.Current.GetService <IPasswordHashingService>(); // TODO - Allow configuation of max login attempts var client = dataContext.ExecuteProcedure <DbSecurityDevice>("auth_dev", deviceId, hashService.ComputeHash(deviceSecret), 5); if (client == null) { throw new SecurityException("Invalid device credentials"); } else if (client.Key == Guid.Empty) { throw new AuthenticationException(client.PublicId); } IPrincipal devicePrincipal = new DevicePrincipal(new DeviceIdentity(client.Key, client.PublicId, true)); this.m_policyService.Demand(PermissionPolicyIdentifiers.LoginAsService, devicePrincipal); return(devicePrincipal); } catch (Exception e) { this.m_tracer.TraceEvent(EventLevel.Error, "Error authenticating {0} : {1}", deviceId, e); throw new AuthenticationException("Error authenticating application", e); } } }
/// <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 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); } }