using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using InABox.Client.WebSocket; using InABox.Core; using InABox.WebSocket.Shared; using RestSharp; using RestSharp.Extensions; namespace InABox.Clients { internal static class LocalCache { public static string? Password { get; set; } } public static class URLCache { private static string? Domain { get; set; } private static int? Port { get; set; } private static string URL { get; set; } = ""; private static bool isHTTPS; public static bool IsHTTPS { get => isHTTPS; } public static void Clear() { Domain = null; Port = null; URL = ""; isHTTPS = false; } public static string GetURL(string domain, int port) { domain = domain.Split(new[] { "://" }, StringSplitOptions.RemoveEmptyEntries).Last(); if (domain != Domain || port != Port) { var client = new HttpClient { BaseAddress = new Uri($"https://{domain}:{port}") }; try { client.GetAsync("operations").Wait(); URL = $"https://{domain}"; isHTTPS = true; } catch (Exception) { URL = $"http://{domain}"; isHTTPS = false; } Port = port; Domain = domain; } return URL; } } public static class WebSocketFactory { private static Dictionary Clients = new Dictionary(); public static void StartWebSocket(string url, int port, Guid session) { url = url.Split(new[] { "://" }, StringSplitOptions.RemoveEmptyEntries).Last(); var key = $"{url}:{port}${session}"; if (!Clients.ContainsKey(key)) { Clients[key] = new WebSocketClient(url, port, session); } } } public abstract class RemoteClient : BaseClient where TEntity : Entity, new() { public RemoteClient(string url, int port, bool useSimpleEncryption = false) { URL = URLCache.GetURL(url, port); Port = port; UseSimpleEncryption = useSimpleEncryption; } public string URL { get; set; } public int Port { get; set; } public bool UseSimpleEncryption { get; set; } private void PrepareRequest(Request request) { request.Credentials.Platform = ClientFactory.Platform; request.Credentials.Version = ClientFactory.Version; request.Credentials.Session = ClientFactory.SessionID; Request.BeforeRequest?.Invoke(request); } protected override ValidationData DoValidate(Guid session) { return Validate( null, null, false, session); } protected override ValidationData DoValidate(string pin, Guid session) { return Validate( null, pin, true, session); } protected override ValidationData DoValidate(string userid, string password, Guid session) { return Validate( userid, password, false, session); } private ValidationData Validate(string? userid, string? password, bool usePin, Guid session = default) { var ticks = DateTime.Now.ToUniversalTime().Ticks.ToString(); var request = new ValidateRequest(); request.UsePIN = usePin; if (usePin) { request.UserID = Encryption.Encrypt(ticks, "wCq9rryEJEuHIifYrxRjxg", UseSimpleEncryption); request.Password = Encryption.Encrypt(ticks, "7mhvLnqMwkCAzN+zNGlyyg", UseSimpleEncryption); request.PIN = password; } else { request.UserID = userid; request.Password = password; } PrepareRequest(request); if (session != Guid.Empty) { request.Credentials.Session = session; } var response = SendRequest(request, "validate", false); if (response != null) if (response.Status.Equals(StatusCode.OK)) { if(response.Session != Guid.Empty) { var notifyRequest = new NotifyRequest(); // Session is required so that the server can exclude any requests from bad actors notifyRequest.Credentials.Session = response.Session; var notifyResponse = SendRequest(notifyRequest, "notify", false); if(notifyResponse != null && notifyResponse.Status.Equals(StatusCode.OK)) { if (notifyResponse.SocketPort.HasValue) { WebSocketFactory.StartWebSocket(URL, notifyResponse.SocketPort.Value, response.Session); } } } LocalCache.Password = password; return new ValidationData( response.ValidationResult, response.UserID, response.UserGuid, response.SecurityID, response.Session, response.Recipient2FA, response.PasswordExpiration ); } else if(response.Status == StatusCode.BadServer) { throw new RemoteException(response.Messages, request); } return new ValidationData( ValidationResult.INVALID, "", Guid.Empty, Guid.Empty, Guid.Empty, null, DateTime.MinValue ); } protected abstract TResponse SendRequest(TRequest request, string Action, bool includeEntity = true) where TRequest : Request, new() where TResponse : Response, new(); #region Query Data protected override CoreTable DoQuery(Filter? filter, Columns? columns, SortOrder? sort = null) { var request = new QueryRequest { Columns = columns, Filter = filter, Sort = sort }; PrepareRequest(request); var response = SendRequest, QueryResponse>(request, "List"); if (response != null) { return response.Status switch { StatusCode.OK => response.Items, StatusCode.Unauthenticated => throw new RemoteException("Client not authenticated", StatusCode.Unauthenticated, request), _ => throw new RemoteException(response.Messages, request), }; } return null; //throw new Exception("Response is null"); } #endregion #region Load protected override TEntity[] DoLoad(Filter? filter = null, SortOrder? sort = null) { var result = new List(); var request = new QueryRequest { Filter = filter, Sort = sort }; PrepareRequest(request); var response = SendRequest, QueryResponse>(request, "List"); if (response.Items != null) foreach (var row in response.Items.Rows) result.Add(row.ToObject()); return result.ToArray(); } #endregion #region MultipleTables protected override Dictionary DoQueryMultiple(Dictionary queries) { var request = new MultiQueryRequest(); request.TableTypes = new Dictionary(); request.Filters = new Dictionary(); request.Columns = new Dictionary(); request.Sorts = new Dictionary(); foreach (var item in queries) { request.TableTypes[item.Key] = item.Value.Type.EntityName(); request.Filters[item.Key] = Serialization.Serialize(item.Value.Filter); request.Columns[item.Key] = Serialization.Serialize(item.Value.Columns); request.Sorts[item.Key] = Serialization.Serialize(item.Value.SortOrder); } PrepareRequest(request); var response = SendRequest(request, "QueryMultiple", false); if (response != null) { return response.Status switch { StatusCode.OK => response.Tables, StatusCode.Unauthenticated => throw new RemoteException("Client not authenticated", request), _ => throw new RemoteException(response.Messages, request), }; } return null; //throw new Exception("Response is null"); } #endregion #region Save protected override void DoSave(TEntity entity, string auditnote) { var request = new SaveRequest(); request.Item = entity; request.AuditNote = auditnote; PrepareRequest(request); var response = SendRequest, SaveResponse>(request, "Save"); switch (response.Status) { case StatusCode.OK: var props = CoreUtils.PropertyList(typeof(TEntity), x => true, true); entity.SetObserving(false); foreach (var prop in props.Keys) { var value = CoreUtils.GetPropertyValue(response.Item, prop); CoreUtils.SetPropertyValue(entity, prop, value); } entity.CommitChanges(); entity.SetObserving(true); break; case StatusCode.Unauthenticated: throw new RemoteException("Client not authenticated", request); default: throw new RemoteException(response.Messages, request); } } protected override void DoSave(IEnumerable entities, string auditnote) { var items = entities.ToArray(); var request = new MultiSaveRequest(); request.Items = items; request.AuditNote = auditnote; PrepareRequest(request); var response = SendRequest, MultiSaveResponse>(request, "MultiSave"); switch (response.Status) { case StatusCode.OK: var props = CoreUtils.PropertyList(typeof(TEntity), x => true, true); for (var i = 0; i < items.Length; i++) { items[i].SetObserving(false); foreach (var prop in props.Keys) { var value = CoreUtils.GetPropertyValue(response.Items[i], prop); CoreUtils.SetPropertyValue(items[i], prop, value); } //CoreUtils.DeepClone(response.Items[i], items[i]); items[i].CommitChanges(); items[i].SetObserving(true); } break; case StatusCode.Unauthenticated: throw new RemoteException("Client not authenticated", request); default: throw new RemoteException(response.Messages, request); } } #endregion #region Delete protected override void DoDelete(TEntity entity, string auditnote) { var request = new DeleteRequest(); request.Item = entity; PrepareRequest(request); var response = SendRequest, DeleteResponse>(request, "Delete"); switch (response.Status) { case StatusCode.OK: break; case StatusCode.Unauthenticated: throw new RemoteException("Client not authenticated", request); default: throw new RemoteException(response.Messages, request); } } protected override void DoDelete(IList entities, string auditnote) { var items = entities.ToArray(); var request = new MultiDeleteRequest(); request.Items = items; request.AuditNote = auditnote; PrepareRequest(request); var response = SendRequest, MultiDeleteResponse>(request, "MultiDelete"); switch (response.Status) { case StatusCode.OK: break; case StatusCode.Unauthenticated: throw new RemoteException("Client not authenticated", request); default: throw new RemoteException(response.Messages, request); } } #endregion #region 2FA protected override bool DoCheck2FA(string code, Guid? session) { var request = new Check2FARequest { Code = code }; PrepareRequest(request); var response = SendRequest(request, "check_2fa", false); if (response != null) { return response.Status switch { StatusCode.OK => response.Valid, StatusCode.Unauthenticated => false, _ => throw new RemoteException(response.Messages, request), }; } return false; } #endregion #region Ping protected override bool DoPing() { var uri = new Uri(string.Format("{0}:{1}", URL, Port)); var cli = new RestClient(uri); var req = new RestRequest("/classes", Method.GET) { Timeout = 20000 }; try { var res = cli.Execute(req); if (res.StatusCode != HttpStatusCode.OK || res.ErrorException != null) return false; return true; } catch { return false; } } #endregion } }