using System.IO.Compression; using System.Net; using InABox.Core; using RestSharp; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; namespace InABox.Clients { public static class StaticRestClients { public static Dictionary Clients = new Dictionary(); public static RestClient GetClient(string url) { var uri = new Uri(url); if (!Clients.TryGetValue(url, out var cli)) { cli = new RestClient(uri); Clients.Add(url, cli); } return cli; } } public class RestClient : BaseClient where TEntity : Entity, new() { private bool _simpleencryption; private string _server; private bool _compression; private BinarySerializationSettings _binarysettings; public RestClient(string server, bool useSimpleEncryption, bool compression, BinarySerializationSettings binarySerializationSettings) { _server = server; _simpleencryption = useSimpleEncryption; _compression = compression; _binarysettings = binarySerializationSettings; RestClientCache.Check(server); } public RestClient(string server, bool simpleencryption, bool compression) : this(server, simpleencryption, compression, BinarySerializationSettings.Latest) { } public RestClient(string server, bool simpleencryption) : this(server, simpleencryption, false) { } public RestClient(string server) : this(server, false) { } public override bool IsConnected() => true; public static string Ping(string[] urls, out DatabaseInfo info) { var result = ""; info = new DatabaseInfo(); List>> pings = urls.Select(x => Task.Run( () => new Tuple(x, new RestClient(x).Info()) )).ToList(); while (pings.Count > 0) { var ping = Task.WhenAny(pings).Result; if (ping.Status == TaskStatus.RanToCompletion && !string.IsNullOrWhiteSpace(ping.Result.Item2.Version)) { result = ping.Result.Item1; info = ping.Result.Item2; break; } else pings.Remove(ping); } return result; } 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 IValidationData DoValidate(Guid session = default) { return Validate( null, null, false, session); } protected override IValidationData DoValidate(string pin, Guid session = default) { return Validate( null, pin, true, session); } protected override IValidationData DoValidate(string userid, string password, Guid session = default) { return Validate( userid, password, false, session); } private IValidationData 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", _simpleencryption); request.Password = Encryption.Encrypt(ticks, "7mhvLnqMwkCAzN+zNGlyyg", _simpleencryption); 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", SerializationFormat.Binary, SerializationFormat.Binary, false); if (response != null) if (response.Status.Equals(StatusCode.OK)) { PasswordCache.Password = password; return new ValidationData( response.ValidationStatus, 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( ValidationStatus.INVALID, "", Guid.Empty, Guid.Empty, Guid.Empty, null, DateTime.MinValue ); } protected TResponse SendRequest(TRequest request, string Action, SerializationFormat requestFormat, SerializationFormat responseFormat, bool includeEntity = true) where TRequest : Request where TResponse : Response, new() { var result = default(TResponse); var url = RestClientCache.URL(_server); if (string.IsNullOrEmpty(url)) { result = (TResponse)Activator.CreateInstance(typeof(TResponse)); result.Status = StatusCode.BadServer; result.Messages.Add("Server URL not set!"); return result; } var cli = StaticRestClients.GetClient(url); var cmd = string.Format( "{0}{1}?format={2}&responseFormat={3}&serializationVersion={4}", Action, includeEntity ? typeof(TEntity).Name : "", requestFormat, responseFormat, _binarysettings.Version ); var req = new RestRequest(cmd, Method.Post) { Timeout = Timeout.Milliseconds, }; //Log(" * {0}{1}() Creating Uri, Client and RestRequest took {2}ms", Action, typeof(TEntity).Name, sw.ElapsedMilliseconds); //sw.Restart(); //req.AdvancedResponseWriter = (stream, response) => //{ // //Log(" * {0}{1}() Response from Server took {2}ms ({3} bytes)", Action, typeof(TEntity).Name, sw.ElapsedMilliseconds, response.ContentLength); // //length = response.ContentLength; // //sw.Restart(); // try // { // if (responseFormat == SerializationFormat.Binary && typeof(TResponse).HasInterface()) // { // result = (TResponse)Serialization.ReadBinary(typeof(TResponse), stream, _binarysettings); // } // else // { // result = Serialization.Deserialize(stream, true); // } // } // catch (Exception e) // { // Logger.Send(LogType.Information, "", $"Error deserializing response: {e.Message}"); // } // //Log(" * {0}{1}() Deserializing Stream took {2}ms ({3} bytes)", Action, typeof(TEntity).Name, sw.ElapsedMilliseconds, response.ContentLength); //}; if(requestFormat == SerializationFormat.Binary && request is ISerializeBinary binary) { var data = binary.WriteBinary(_binarysettings); req.AddOrUpdateParameter("application/octet-stream", data, ParameterType.RequestBody); req.RequestFormat = DataFormat.None; } else { var json = Serialization.Serialize(request); req.AddOrUpdateParameter("application/json", json, ParameterType.RequestBody); req.RequestFormat = DataFormat.Json; } try { //sw.Restart(); var res = cli.Execute(req); //Log(" * {0}{1}() returns {2} bytes in {3}ms", Action, typeof(TEntity).Name, res.ContentLength, sw.ElapsedMilliseconds); if (result == null) { if (res.ErrorException == null) { if (res.StatusCode != HttpStatusCode.OK) throw new Exception(String.Format("HTTP Request returns {0} {1}" + (int)res.StatusCode, CoreUtils.SplitCamelCase(res.StatusCode.ToString()))); try { Stream stream; if (_compression) { //sw.Restart(); var comp = Serialization.Deserialize(res.Content, true); var bytes = Convert.FromBase64String(comp.Response); var ms = new MemoryStream(bytes); stream = new MemoryStream(); using (var decompressionStream = new DeflateStream(ms, CompressionMode.Decompress)) { decompressionStream.CopyTo(stream); } } else { stream = new MemoryStream(res.RawBytes); } if (responseFormat == SerializationFormat.Binary && typeof(TResponse).HasInterface()) { result = (TResponse)Serialization.ReadBinary(typeof(TResponse), stream, _binarysettings); } else { result = Serialization.Deserialize(stream, true); } stream.Dispose(); } catch (Exception eDeserialize) { throw new Exception(string.Format("Unable to deserialize response!\n\n{0}\n\n{1}", eDeserialize.Message, res.Content)); } } else { // Connectivity result = new TResponse(); result.Status = StatusCode.BadServer; result.Messages.Add(res.ErrorMessage); } } } catch (Exception err) { result = new TResponse(); result.Status = StatusCode.BadServer; result.Messages.Add(err.Message); if (err.InnerException != null) result.Messages.Add("- " + err.InnerException.Message); } return result; } #region Query Data protected override CoreTable DoQuery(Filter? filter, Columns? columns, SortOrder? sort = null) { var request = new QueryRequest(filter, columns, sort); PrepareRequest(request); var response = SendRequest, QueryResponse>(request, "List", SerializationFormat.Binary, SerializationFormat.Binary); 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, null, sort); PrepareRequest(request); var response = SendRequest, QueryResponse>(request, "List", SerializationFormat.Binary, SerializationFormat.Binary); 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(); foreach (var item in queries) { request.AddQuery(item.Key, item.Value); } PrepareRequest(request); var response = SendRequest(request, "QueryMultiple", SerializationFormat.Binary, SerializationFormat.Binary, 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(entity, auditnote); request.ReturnOnlyChanged = true; PrepareRequest(request); var response = SendRequest, SaveResponse>(request, "Save", SerializationFormat.Binary, SerializationFormat.Binary); 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);*/ entity.SetObserving(false); foreach (var (key, value) in response.ChangedValues) { if(CoreUtils.TryGetProperty(key, out var property)) { CoreUtils.SetPropertyValue(entity, key, CoreUtils.ChangeType(value, property.PropertyType)); } } 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(items, auditnote); request.ReturnOnlyChanged = true; PrepareRequest(request); var response = SendRequest, MultiSaveResponse>(request, "MultiSave", SerializationFormat.Binary, SerializationFormat.Binary); switch (response.Status) { case StatusCode.OK: for(int i = 0; i < items.Length; ++i) { var entity = items[i]; var changedValues = response.ChangedValues[i]; entity.SetObserving(false); foreach (var (key, value) in changedValues) { if (CoreUtils.TryGetProperty(key, out var property)) { CoreUtils.SetPropertyValue(entity, key, CoreUtils.ChangeType(value, property.PropertyType)); } } entity.CommitChanges(); entity.SetObserving(true); } /*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(entity, auditnote); PrepareRequest(request); var response = SendRequest, DeleteResponse>(request, "Delete", SerializationFormat.Binary, SerializationFormat.Binary); 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(items, auditnote); PrepareRequest(request); var response = SendRequest, MultiDeleteResponse>(request, "MultiDelete", SerializationFormat.Binary, SerializationFormat.Binary); 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); PrepareRequest(request); var response = SendRequest(request, "check_2fa", SerializationFormat.Binary, SerializationFormat.Binary, 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 cli = StaticRestClients.GetClient(RestClientCache.URL(_server)); var req = new RestRequest("/ping", 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 public override IEnumerable SupportedTypes() { var result = new List(); var cli = StaticRestClients.GetClient(RestClientCache.URL(_server)); var req = new RestRequest("/classes", Method.Get) { Timeout = 20000 }; try { var res = cli.Execute(req); if (res.ErrorException == null) { var list = res.Content.Trim('[', ']').Split(','); foreach (var operation in list) { var trimmed = operation.Trim('"'); if (!result.Contains(trimmed)) result.Add(trimmed); //if (svc.Equals("Comal_Classes_Login")) // result.Add("InABox_Core_Login"); } } } catch (Exception e) { } req = null; cli = null; return result.ToArray(); } public override DatabaseInfo Info() { return RestClientCache.Info(_server); } } }