/// <summary> /// Notifies clients of the current authentication state. /// </summary> /// <param name="action"> /// The authentication command which reflects the current auth state. /// </param> public void NotifyAuthenticationStatus(AuthenticationAction action, string username = null, AuthenticationResultObject authenticationResult = null) { // KF - I edited this function to take two arguments instead of one and then refactored all the code that calls it to pass in an AuthenticationResultObject switch (m_waitingForAuth) { case true: { m_waitingForAuth = action == AuthenticationAction.Authenticated ? false : true; } break; case false: { m_waitingForAuth = action == AuthenticationAction.Required; } break; } var authResult = new AuthenticationResultObject(); if (authenticationResult != null) { authResult = authenticationResult; } var msg = new AuthenticationMessage(action, authResult, username); // KF - Also added a constructor to AuthenticationMessage); PushMessage(msg); }
/// <summary> /// Constructs a new AuthenticationMessage instance. Used by the IPC server to return more detailed information about the /// </summary> /// <param name="action"> /// The action directive of this message. /// </param> /// <param name="authenticationResult"> /// The authentication result returned by the web server to the service. /// </param> public AuthenticationMessage(AuthenticationAction action, AuthenticationResultObject authenticationResult) { Action = action; Username = string.Empty; Password = new byte[0]; AuthenticationResult = authenticationResult; }
/// <summary> /// Constructs a new AuthenticationMessage instance. Used by the IPC server to return more detailed information about the /// </summary> /// <param name="action"> /// The action directive of this message. /// </param> /// <param name="authenticationResult"> /// The authentication result returned by the web server to the service. /// </param> public AuthenticationMessage(AuthenticationAction action, AuthenticationResultObject authenticationResult, string username = null) { Action = action; Username = username; Password = new byte[0]; AuthenticationResult = authenticationResult; }
public AuthenticationResultObject Authenticate(string username, byte[] unencryptedPassword) { m_logger.Error(nameof(Authenticate)); AuthenticationResultObject ret = new AuthenticationResultObject(); // Enforce parameters are valid. Debug.Assert(StringExtensions.Valid(username)); if (!StringExtensions.Valid(username)) { throw new ArgumentException("Supplied username cannot be null, empty or whitespace.", nameof(username)); } Debug.Assert(unencryptedPassword != null && unencryptedPassword.Length > 0); if (unencryptedPassword == null || unencryptedPassword.Length <= 0) { throw new ArgumentException("Supplied password byte array cannot be null and must have a length greater than zero.", nameof(unencryptedPassword)); } // // Don't bother if we don't have internet. var hasInternet = NetworkStatus.Default.HasIpv4InetConnection || NetworkStatus.Default.HasIpv6InetConnection; if (hasInternet == false) { m_logger.Info("Aborting authentication attempt because no internet connection could be detected."); ret.AuthenticationResult = AuthenticationResult.ConnectionFailed; ret.AuthenticationMessage = "Aborting authentication attempt because no internet connection could be detected."; return(ret); } // Will be set if we get any sort of web exception. bool connectionFailure = false; // Try to send the device name as well. Helps distinguish between clients under the same account. string deviceName = string.Empty; byte[] formData = null; try { deviceName = Environment.MachineName; } catch { deviceName = "Unknown"; } HttpWebRequest authRequest = null; try { authRequest = GetApiBaseRequest(m_namedResourceMap[ServiceResource.GetToken], new ResourceOptions()); // Build out username and password as post form data. We need to ensure that we mop // up any decrypted forms of our password when we're done, and ASAP. formData = System.Text.Encoding.UTF8.GetBytes(string.Format("email={0}&identifier={1}&device_id={2}", username, FingerprintService.Default.Value, deviceName)); // Don't forget to the set the content length to the total length of our form POST data! authRequest.ContentLength = formData.Length; authRequest.ContentType = "application/x-www-form-urlencoded"; // XXX TODO - This is naughty, because we're putting our password into a string, so // it will linger in memory. However, it appears that the provided API doesn't give // us any choice. // KF NOTE: Why System.String is insecure. https://stackoverflow.com/questions/1166952/net-secure-memory-structures var encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + Encoding.UTF8.GetString(unencryptedPassword))); authRequest.Headers.Add("Authorization", "Basic " + encoded); // Grab the request stream so we can POST our login form data to it. using (var requestStream = authRequest.GetRequestStream()) { // Write and close. requestStream.Write(formData, 0, formData.Length); requestStream.Close(); } // Now that our login form data has been POST'ed, get a response. using (var response = (HttpWebResponse)authRequest.GetResponse()) { // Get the response code as an int so we can range check it. var code = (int)response.StatusCode; // Check if the response status code is outside the "success" range of codes // defined in HTTP. If so, we failed. We include redirect codes (3XX) as success, // since our server side will just redirect us if we're already authed. if (code > 199 && code < 299) { using (var tr = new StreamReader(response.GetResponseStream())) { WebServiceUtil.Default.AuthToken = tr.ReadToEnd(); WebServiceUtil.Default.UserEmail = username; } response.Close(); authRequest.Abort(); ret.AuthenticationResult = AuthenticationResult.Success; return(ret); } else { if (code == 401 || code == 403) { m_logger.Info("Authentication failed with code: {0}.", code); WebServiceUtil.Default.AuthToken = string.Empty; ret.AuthenticationResult = AuthenticationResult.Failure; return(ret); } else if (code > 399 && code < 499) { m_logger.Info("Authentication failed with code: {0}.", code); ret.AuthenticationResult = AuthenticationResult.Failure; return(ret); } } } } catch (WebException e) { // XXX TODO - Is this sufficient? if (e.Status == WebExceptionStatus.Timeout) { m_logger.Info("Authentication failed due to timeout."); connectionFailure = true; } try { using (WebResponse response = e.Response) { HttpWebResponse httpResponse = (HttpWebResponse)response; m_logger.Error("Error code: {0}", httpResponse.StatusCode); string errorText = string.Empty; using (Stream data = response.GetResponseStream()) using (var reader = new StreamReader(data)) { errorText = reader.ReadToEnd(); // GS Just cleans up the punctuation at the end of string string excpList = "$@*!."; var chRemoved = errorText .Select(ch => excpList.Contains(ch) ? (char?)null : ch); errorText = string.Concat(chRemoved.ToArray()) + "!"; m_logger.Error("Stream errorText: " + errorText); } int code = (int)httpResponse.StatusCode; if (code == 401 || code == 403) { AuthToken = string.Empty; } if (code > 399 && code < 499) { m_logger.Info("Authentication failed with code: {0}.", code); m_logger.Info("Athentication failure text: {0}", errorText); ret.AuthenticationMessage = errorText; ret.AuthenticationResult = AuthenticationResult.Failure; return(ret); } } } catch (Exception iex) { LoggerUtil.RecursivelyLogException(m_logger, iex); } // Log the exception. m_logger.Error(e.Message); m_logger.Error(e.StackTrace); } catch (Exception e) { while (e != null) { m_logger.Error(e.Message); m_logger.Error(e.StackTrace); e = e.InnerException; } m_logger.Info("Authentication failed due to a failure to process the request and response. Attempted URL {0}", authRequest?.RequestUri); WebServiceUtil.Default.AuthToken = string.Empty; ret.AuthenticationResult = AuthenticationResult.Failure; return(ret); } finally { // This finally block is guaranteed TM to be run, so this is where we're going to // clean up any decrypted versions of the user's password held in memory. if (unencryptedPassword != null && unencryptedPassword.Length > 0) { Array.Clear(unencryptedPassword, 0, unencryptedPassword.Length); } if (formData != null && formData.Length > 0) { Array.Clear(formData, 0, formData.Length); } } m_logger.Info("Authentication failed due to a complete failure to process the request and response. Past-catch attempted url {0}", authRequest?.RequestUri); // If we had success, we should/would have returned by now. if (!connectionFailure) { WebServiceUtil.Default.AuthToken = string.Empty; } ret.AuthenticationResult = connectionFailure ? AuthenticationResult.ConnectionFailed : AuthenticationResult.Failure; return(ret); }