// // Program.cs // // Author: // beckermj <> // // Copyright (c) 2022 ${CopyrightHolder} // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . using System; using System.Configuration; using System.IO; using Mocha.Core; using Mocha.OMS; using Mocha.Web.Server.Sys; using UniversalEditor.IO; namespace Mocha.Web.Server { class MainClass : Sys.WebApplication { public MainClass() { ShortName = "mocha-web"; } public static void Main(string[] args) { (new MainClass()).Start(); } protected override void OnRequestReceived(RequestEventArgs e) { base.OnRequestReceived(e); System.IO.StreamReader sr = new System.IO.StreamReader(e.Stream); System.IO.StreamWriter sw = new System.IO.StreamWriter(e.Stream); SessionContext ctx = new SessionContext(); if (((HttpRequest)e.Request).Method == "POST") { System.Collections.Generic.Dictionary postData = new System.Collections.Generic.Dictionary(); if (e.Headers.ContainsKey("Content-Length")) { string strContentLength = e.Headers["Content-Length"]; int intContentLength = Int32.Parse(strContentLength); char[] buffer = new char[intContentLength]; sr.ReadBlock(buffer, 0, intContentLength); string buf = new string(buffer); string[] values = buf.Split(new char[] { '&' }); foreach (string val in values) { string[] kvp1 = val.Split(new char[] { '=' }); string name = kvp1[0]; string value = kvp1[1]; postData[name] = FormDecode(value); } } OnPost(new PostEventArgs(e.Request, e.Response, postData)); } else if (((HttpRequest)e.Request).Method == "GET") { string relativePath = ((HttpRequest)e.Request).Path; string[] pathSplit = relativePath.Split(new char[] { '/' }, StringSplitOptions.None); if (pathSplit.Length >= 2) { if (pathSplit[1] == String.Empty) { Write404(sw, ctx); } else if (pathSplit.Length >= 2) { if (pathSplit.Length >= 3) { if (pathSplit[2] == "attachment") { string tenantName = pathSplit[1]; ctx.TenantName = tenantName; string instanceId = pathSplit[3]; string key = pathSplit[4]; InstanceKey ik = InstanceKey.Parse(instanceId); Mocha.OMS.Oms oms = ctx.GetOms(); Instance instImage = oms.GetInstance(ik); if (oms.VerifyAccessKeyForOmsAttachment(instImage, key, ctx.GetOmsAttachmentEntropy())) { string physicalFilename = String.Format("{0}Images/Uploads/{1}.png", String.Empty, instImage.GlobalIdentifier.ToString("b")); if (System.IO.File.Exists(physicalFilename)) { byte[] data = System.IO.File.ReadAllBytes(physicalFilename); string mimetype = oms.GetAttributeValue(instImage, KnownAttributeGuids.Text.ContentType); sw.WriteLine("HTTP/1.1 200 OK"); sw.WriteLine(String.Format("Content-Type: {0}", mimetype)); sw.WriteLine(String.Format("Content-Length: {0}", data.Length)); sw.WriteLine(); sw.Flush(); sw.BaseStream.Write(data, 0, data.Length); sw.BaseStream.Flush(); // FIXME: !!!!!! figure out how to properly deal with large files and "connection reset" System.Threading.Thread.Sleep(1000); sw.Close(); return; } else { Write404(sw, ctx); return; } } else { Write401(sw, ctx); return; } } } if (pathSplit[1] == "css") { if (pathSplit[2] == "uwt.css") { sw.WriteLine("HTTP/1.1 200 OK"); sw.WriteLine("Content-Type: text/css"); sw.WriteLine("Server: Mocha User Interface Service"); sw.WriteLine(); sw.Flush(); System.IO.Stream st = typeof(MainClass).Assembly.GetManifestResourceStream("Mocha.Web.Server.Pages.css.css"); st.CopyTo(e.Stream); sw.Close(); return; } else if (pathSplit[2] == "uwt.css" || pathSplit[2] == "uwt.less") { sw.WriteLine("HTTP/1.1 200 OK"); sw.WriteLine("Content-Type: text/css"); sw.WriteLine("Server: Mocha User Interface Service"); sw.WriteLine(); sw.Flush(); string DefaultThemeName = ConfigurationManager.AppSettings["Mocha.Default.Theme"] ?? "Pleasanton"; string[] themeFiles = System.IO.Directory.GetFiles("Themes", "*", SearchOption.AllDirectories); System.Text.StringBuilder sb = new System.Text.StringBuilder(); foreach (string themeFile in themeFiles) { string[] pathParts = themeFile.Split(new char[] { System.IO.Path.DirectorySeparatorChar }, StringSplitOptions.None); if (pathParts.Length > 2) { string themeName = pathParts[1]; if (themeName == "Mobile" || themeName == DefaultThemeName) { sb.AppendLine(System.IO.File.ReadAllText(themeFile)); } } else { sb.AppendLine(System.IO.File.ReadAllText(themeFile)); } } if (pathSplit[2].EndsWith(".less")) { sw.WriteLine(sb.ToString()); } else { dotless.Core.configuration.DotlessConfiguration config = new dotless.Core.configuration.DotlessConfiguration(); config.MinifyOutput = true; sw.WriteLine(dotless.Core.Less.Parse(sb.ToString(), config).Replace('\n', ' ').Replace('\t', ' ').Replace(" ", " ")); } sw.Close(); return; } else if (pathSplit[2] == "Fonts") { string fontName = pathSplit[3]; string fontFile = pathSplit[4]; int indexOfQ = fontFile.IndexOf('?'); if (indexOfQ != -1) { fontFile = fontFile.Substring(0, indexOfQ); } sw.WriteLine("HTTP/1.1 200 OK"); sw.WriteLine("Content-Type: application/x-font-opentype"); sw.WriteLine(); sw.Flush(); e.Response.WriteFile("Fonts/" + fontName + "/" + fontFile); e.Stream.Close(); return; } } else { // assume tenant name ctx.TenantName = pathSplit[1]; if (pathSplit.Length >= 2) { if (pathSplit.Length == 5 && pathSplit[1] == "madi" && pathSplit[2] == "authgwy") { ctx.TenantName = pathSplit[3]; if (pathSplit[4] == "tenant-config.xml") { sw.WriteLine("HTTP/1.1 200 OK"); sw.WriteLine("Content-Type: application/xml"); sw.WriteLine("Server: Mocha User Interface Service"); sw.WriteLine(); sw.WriteLine(""); sw.WriteLine(" dict = new System.Collections.Generic.Dictionary(); dict["Allow_Attachments_And_Documents_To_Be_Shared_With_Other_Mobile_Apps"] = "0"; dict["Apns_Allowed"] = "0"; dict["Canvas_Is_Enabled"] = "1"; dict["Canvas_Hex_Code"] = "#005cb9,#0875e1"; dict["Clear_Mobile_SSO_Webview_Cookies_on_Login"] = "0"; dict["Deter_Screenshots"] = "0"; dict["Disable_Add_To_Contact"] = "0"; dict["Disable_Importing_Attachments_From_Third-Party_Cloud_Services"] = "0"; dict["Disable_Location_Service"] = "0"; dict["Disable_Mail_To"] = "0"; dict["Disable_Voice_in_Assistant_on_Mobile"] = "1"; dict["Enable_Blue_Primary_Buttons"] = "0"; dict["Enable_Certificate_Based_SSO"] = "0"; dict["Enable_DOM_Storage"] = "0"; dict["Enable_Email_Annotations"] = "1"; dict["Enable_export_to_Worksheets"] = "1"; dict["Enable_Fingerprint_Authentication"] = "1"; dict["Enable_Geospace_Visualization"] = "0"; dict["Enable_Mobile_Browser_SSO_for_Native_Apps"] = "0"; dict["Enable_New_Profile"] = "0"; dict["Error_Message_for_Browser"] = "This Browser is not supported by Workday. Please contact your IT department."; dict["Is_FedRAMP_Tenant"] = "0"; dict["Google_Cloud_Messaging_Project_Number"] = "739632724832"; dict["Hide_Links_to_Web_from_Deep_Linking_and_Inbox"] = "0"; dict["Login_URL"] = "https://wd5-impl.workday.com/wday/authgwy/cityoforlando1/login-saml2.htmld"; dict["Logout_URL"] = "https://cityoforlando.okta.com"; dict["OMS_Note"] = "IMPL - Powered by Workday"; dict["Pin_Auth_Enabled"] = "0"; dict["Pin_Max_Attempts"] = "0"; dict["Pin_Max_Length"] = "0"; dict["Pin_Min_Length"] = "0"; dict["Reset_Password_Online"] = "0"; dict["Show_Change_Password_Link"] = "1"; dict["Show_Forgotten_Password_Link"] = "0"; dict["SignOn_Custom_Message"] = "<p>City of Orlando - Production</p>"; dict["SignOn_Message_Locale"] = "en_US"; dict["SignOn_Note"] = "<p>City of Orlando - Production</p>"; dict["SignOn_Tenant_Logo_Url"] = "/cityoforlando1/images/signon.xml"; dict["System_Confidence_Level"] = "PROD"; dict["System_Note"] = "Your Implementation tenant will be unavailable for a maximum of 12 hours during the next Weekly Service Update; starting on Friday, December 30, 2022 at 6:00 PM Pacific Time (Los Angeles) (GMT-8) until Saturday, December 31, 2022 at 6:00 AM Pacific Time (Los Angeles) (GMT-8)."; dict["Use_One_Time_Use_Link"] = "0"; foreach (System.Collections.Generic.KeyValuePair kvp in dict) { sw.WriteLine(String.Format("\t{0}=\"{1}\"", kvp.Key, kvp.Value)); } sw.WriteLine(" />"); sw.Close(); return; } if (pathSplit[4] == "login.htmld") { // we are at the login page // here we find the current tenant, and ask it to retrieve the PageBuilder login page //FIXME: make this better Mocha.OMS.Oms oms = ctx.GetOms(); Instance instTenant = oms.GetTenantInstance(); if (instTenant == null) { WriteGenericMessage(sw, ctx, "Invalid Tenant", "The URL you have provided is invalid. Please contact your system administrator if you require assistance."); return; } // Tenant@get login Page for Tenant and Application parm sw.WriteLine("HTTP/1.1 200 OK"); sw.WriteLine("Content-Type: text/html"); sw.WriteLine(); PageBuilder2 pb = new PageBuilder2(oms, ctx); Instance instLoginPage = oms.GetInstance(KnownInstanceGuids.Pages.LoginPage); pb.RenderPage(instLoginPage, sw); sw.Close(); return; } else { Write404(sw, ctx); return; } } object instLoginUser = null; if (instLoginUser == null) { WriteLoginRedirect(sw, ctx); return; } } else { Write404(sw, ctx); return; } } } } } } private void OnPost(PostEventArgs e) { System.IO.StreamWriter sw = new StreamWriter(e.Response.Stream); sw.WriteLine("HTTP/1.1 200 OK"); sw.WriteLine(); sw.WriteLine(String.Format("Your stuff is {0}", e.PostData.Count)); sw.Flush(); e.Response.End(); } private static string FormDecode(string value) { string plusReplace = value.Replace('+', ' '); plusReplace = plusReplace.Replace("%2B", "+"); return plusReplace; } private static void WriteGenericMessage(StreamWriter sw, SessionContext ctx, string v1, string v2) { sw.WriteLine("HTTP/1.1 404 Not Found"); sw.WriteLine("Content-Type: text/html"); sw.WriteLine("Server: Mocha User Interface Service"); sw.WriteLine(); sw.WriteLine(String.Format("{0}", ctx.PageTitle)); sw.WriteLine(String.Format("

{0}

{1}

", v1, v2)); sw.Close(); } private static void WriteLoginRedirect(StreamWriter sw, SessionContext ctx) { /* var redirectUrl = 'https://{originalUrl}/wday/authgwy/{tenant}/login.htmld?returnTo=%2f{tenant}%2fhome.htmld'; var anchor = encodeURIComponent(window.location.hash); if (anchor.length > 0) { redirectUrl = redirectUrl.replace(/([?|&]returnTo=[^\&;]+)/, '$1' + anchor); } window.location = redirectUrl; */ sw.WriteLine("HTTP/1.1 302 Found"); sw.WriteLine(String.Format("Location: /madi/authgwy/{0}/login.htmld", ctx.TenantName)); sw.WriteLine(); sw.Close(); } private static void Write404(StreamWriter sw, SessionContext ctx) { WriteGenericMessage(sw, ctx, "Not Found", "The requested URL was not found on this server."); } private static void Write401(StreamWriter sw, SessionContext ctx) { WriteGenericMessage(sw, ctx, "Not Authorized", "The requested URL was not found on this server."); } } }