/// <summary> /// Token request for refresh /// </summary> public OAuthTokenRequest(TokenClaimsPrincipal current, String scope) { this.GrantType = "refresh_token"; this.RefreshToken = current.RefreshToken; this.Scope = scope; }
/// <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> /// 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> /// 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); } }