using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Security; using System.Text; using System.Text.RegularExpressions; using System.Web.Script.Serialization; using System.Xml; using Laserfiche.RepositoryAccess; namespace LFFolderSyncUtil { class CloudUtil { // Sign in to a Laserfiche Cloud repository. If cloudUser is not specified, ADFS is used to authenticate public static Session CloudLogin(string cloudCustomer, string cloudUser, SecureString cloudPassword, string MFAToken, string cloudDomain, string userAgent) { CloudTicket cloudTicket; if (string.IsNullOrEmpty(cloudUser)) { // Make sure ADFS is enabled (Note: this check is not necessary) if (!IsAdfsEnabled(cloudCustomer, cloudDomain, userAgent)) throw new Exception("ADFS is not enabled for this user, please specify a user name."); // Retrieve the ADFS endpoint url string federatedLoginUrl; if (!string.IsNullOrEmpty(cloudDomain)) federatedLoginUrl = "https://acs." + cloudDomain + "/acs/federatedlogin/?customerID=" + cloudCustomer + "&targetUrl="; else federatedLoginUrl = "https://acs.laserfiche.com/acs/federatedlogin/?customerID=" + cloudCustomer + "&targetUrl="; HttpWebRequest federatedLoginRequest = (HttpWebRequest)WebRequest.Create(federatedLoginUrl); federatedLoginRequest.Accept = "*/*"; federatedLoginRequest.AllowAutoRedirect = true; federatedLoginRequest.UseDefaultCredentials = true; federatedLoginRequest.UserAgent = String.Format("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0) ({0})", userAgent); federatedLoginRequest.Timeout = 60000; federatedLoginRequest.Method = "POST"; federatedLoginRequest.ContentLength = 0; HttpWebResponse federatedLoginResponse = (System.Net.HttpWebResponse)federatedLoginRequest.GetResponse(); if (federatedLoginResponse.StatusCode != HttpStatusCode.OK) throw new Exception(String.Format("HTTP {0} {1}", (int)federatedLoginResponse.StatusCode, federatedLoginResponse.StatusCode.ToString())); string[] values = federatedLoginResponse.Headers.GetValues("Location"); if (values == null || values.Length == 0 || String.IsNullOrEmpty(values[0])) throw new Exception("No Location header returned by federated login response"); string location = values[0]; // Navigate to the ADFS endpoint to perform the domain authentication handshake string sessionKey = CloudUtil.ADFSLogin(location, userAgent); Uri cloudTicketEndpoint; if (!string.IsNullOrEmpty(cloudDomain)) cloudTicketEndpoint = new Uri("https://acs." + cloudDomain + "/ACS"); else cloudTicketEndpoint = new Uri("https://acs.laserfiche.com/ACS"); cloudTicket = new CloudTicket(sessionKey, cloudTicketEndpoint); } else { // Perform cloud login CloudTicketRequestSettings cloudTicketSettings = new CloudTicketRequestSettings(); cloudTicketSettings.AccountId = cloudCustomer; cloudTicketSettings.UserName = cloudUser; cloudTicketSettings.Password = Util.SecureStringToString(cloudPassword); // Optional multi-factor authentication token if (!string.IsNullOrEmpty(MFAToken)) cloudTicketSettings.OneTimePassword = MFAToken; if (!string.IsNullOrEmpty(cloudDomain)) cloudTicketSettings.CustomEndpoint = new Uri("https://acs." + cloudDomain + "/ACS"); cloudTicket = CloudTicket.GetTicket(cloudTicketSettings); } string samlTokenXml = cloudTicket.GetSamlToken(); string repositoryName = String.Empty; // Examine the SAML to get the repository name using (XmlReader reader = XmlReader.Create(new StringReader(samlTokenXml))) { while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { if (reader.LocalName == "Attribute" && reader.GetAttribute("Name") == "http://laserfiche.com/identity/claims/catalyst/roles") { int depth = reader.Depth; while (reader.Read() && reader.Depth > depth) { if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "AttributeValue") { reader.Read(); string roleValue = reader.Value; if (!string.IsNullOrEmpty(roleValue) && roleValue.Contains(":")) { int colonPos = roleValue.IndexOf(":"); string roleDomain = roleValue.Substring(0, colonPos); string role = roleValue.Substring(colonPos + 1); if (role == "CreateSession") repositoryName = roleDomain; } } } } } } } string repositoryHost; if (!string.IsNullOrEmpty(cloudDomain)) repositoryHost = repositoryName + "." + cloudDomain; else repositoryHost = repositoryName + ".laserfiche.com"; // Connect to the repository return Session.Create(repositoryHost, cloudTicket); } // Sign in using ADFS, returning the session key public static string ADFSLogin(string origUrl, string userAgent) { CookieContainer cookies = new CookieContainer(); StringBuilder url = new StringBuilder(origUrl); // Navigate to the ADFS endpoint, continue redirecting the Location header until it isn't returned do { HttpWebRequest getRequest = (HttpWebRequest)WebRequest.Create(url.ToString()); getRequest.CookieContainer = cookies; getRequest.Accept = "*/*"; getRequest.AllowAutoRedirect = true; // Keep redirecting until the ADFS login completes getRequest.UseDefaultCredentials = true; // .NET handles the domain authentication getRequest.UserAgent = String.Format("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0) ({0})", userAgent); getRequest.Timeout = 60000; getRequest.Method = "GET"; HttpWebResponse getResponse = (System.Net.HttpWebResponse)getRequest.GetResponse(); string getResponseString = ReadResponseBody(getResponse); if (getResponse.StatusCode != HttpStatusCode.OK) throw new Exception(String.Format("HTTP {0} {1}", (int)getResponse.StatusCode, getResponse.StatusCode.ToString())); string[] values = getResponse.Headers.GetValues("Location"); if (values != null && values.Length > 0) { foreach (string s in values) { if (s.StartsWith("/")) { string newUrl = url.ToString().Substring(0, url.ToString().IndexOf("/adfs/ls")); url.Clear(); url.Append(newUrl); url.Append(s); } else { url.Clear(); url.Append(s); } } } else url.Clear(); // Finished the ADFS login, now parse the response and POST to the hidden form returned by the ACS signin page if (url.Length == 0) { string samlXml = ExtractSamlResponseXml(getResponseString); string relayState = ExtractRelayState(getResponseString); string postUrl = ExtractDestinationUrl(getResponseString); HttpWebRequest postRequest = (HttpWebRequest)WebRequest.Create(postUrl.ToString()); postRequest.CookieContainer = cookies; postRequest.Accept = "text/plain;application/json"; postRequest.AllowAutoRedirect = true; postRequest.UseDefaultCredentials = true; postRequest.UserAgent = String.Format("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0) ({0})", userAgent); postRequest.Timeout = 60000; postRequest.Method = "POST"; postRequest.ContentType = "application/x-www-form-urlencoded"; StringBuilder postData = new StringBuilder(); postData.AppendFormat("SAMLResponse={0}&RelayState={1}", Uri.EscapeDataString(samlXml), Uri.EscapeDataString(relayState)); var rawPostData = Encoding.ASCII.GetBytes(postData.ToString()); postRequest.ContentLength = rawPostData.Length; using (var stream = postRequest.GetRequestStream()) { stream.Write(rawPostData, 0, rawPostData.Length); } HttpWebResponse postResponse = null; try { postResponse = (HttpWebResponse)postRequest.GetResponse(); } catch (WebException e) { postResponse = (HttpWebResponse)e.Response; } string postResponseString = ReadResponseBody(postResponse); if (postResponse.ContentType.Contains("text/plain") && !string.IsNullOrEmpty(postResponseString)) throw new Exception(postResponseString); if (postResponse.StatusCode != HttpStatusCode.OK) throw new Exception(String.Format("HTTP {0} {1}", (int)postResponse.StatusCode, postResponse.StatusCode.ToString())); // Final response should be the session key encoded in a JSON object if (!postResponse.ContentType.Contains("application/json")) throw new Exception(String.Format("Invalid server response (expected json, got {0})", postResponse.ContentType)); return ExtractSessionKey(postResponseString); } } while (url.Length > 0); throw new InvalidOperationException("Unable to get a SAML response from ADFS"); } // Returns whether ADFS is enabled for the specified customer public static bool IsAdfsEnabled(string cloudCustomer, string cloudDomain, string userAgent) { string isAdfsEnabledUrl; if (!string.IsNullOrEmpty(cloudDomain)) isAdfsEnabledUrl = "https://acs." + cloudDomain + "/acs/IsAdfsEnabled/?customerID=" + cloudCustomer; else isAdfsEnabledUrl = "https://acs.laserfiche.com/acs/IsAdfsEnabled/?customerID=" + cloudCustomer; HttpWebRequest getRequest = (HttpWebRequest)WebRequest.Create(isAdfsEnabledUrl.ToString()); getRequest.Accept = "*/*"; getRequest.AllowAutoRedirect = true; getRequest.UseDefaultCredentials = true; getRequest.UserAgent = String.Format("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0) ({0})", userAgent); getRequest.Timeout = 60000; getRequest.Method = "GET"; HttpWebResponse getResponse = (System.Net.HttpWebResponse)getRequest.GetResponse(); string getResponseString = ReadResponseBody(getResponse); if (getResponseString != "true") return false; return true; } // Returns the SAML embedded in the page after ADFS authentication completes static string ExtractSamlResponseXml(string responseBody) { Regex reg = new Regex("SAMLResponse\\W+value\\=\\\"([^\\\"]+)\\\""); MatchCollection matches = reg.Matches(responseBody); foreach (Match match in matches) { return match.Groups[1].Value; } throw new Exception("Error parsing response (SAMLResponse)"); } // Returns the RelayState parameter in the page after ADFS authentication completes static string ExtractRelayState(string responseBody) { Regex reg = new Regex("RelayState\\W+value\\=\\\"([^\\\"]+)\\\""); MatchCollection matches = reg.Matches(responseBody); foreach (Match match in matches) { return match.Groups[1].Value; } throw new Exception("Error parsing response (RelayState)"); } // Returns the destination url embedded in the page after ADFS authentication completes static string ExtractDestinationUrl(string responseBody) { Regex reg = new Regex("action=\\\"([^\\\"]+)\\\""); MatchCollection matches = reg.Matches(responseBody); foreach (Match match in matches) { return match.Groups[1].Value; } throw new Exception("Error parsing response (action url)"); } // {"SessionKey":"DRh0WuzXiOyIBrkUkyprHidxCE1L+mwrYNMmuPwwuEneDiN9FChLzMt0d+GsgPeo"} static string ExtractSessionKey(string responseBody) { JavaScriptSerializer jss = new JavaScriptSerializer(); Dictionary response = jss.Deserialize>(responseBody); return response["SessionKey"]; } static string ReadResponseBody(HttpWebResponse response) { try { string responseString = String.Empty; using (Stream getStream = response.GetResponseStream()) { StreamReader reader = new StreamReader(getStream, Encoding.UTF8); responseString = reader.ReadToEnd(); return responseString; } } catch (Exception) { return ""; } } } }