RemoteClient.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Net;
  5. using System.Net.Http;
  6. using InABox.Client.WebSocket;
  7. using InABox.Core;
  8. using InABox.Remote.Shared;
  9. using InABox.WebSocket.Shared;
  10. using RestSharp;
  11. using RestSharp.Extensions;
  12. namespace InABox.Clients
  13. {
  14. internal static class LocalCache
  15. {
  16. public static string? Password { get; set; }
  17. }
  18. public static class URLCache
  19. {
  20. private static string URL { get; set; } = "";
  21. private static bool isHTTPS;
  22. public static bool IsHTTPS { get => isHTTPS; }
  23. public static void Clear()
  24. {
  25. URL = "";
  26. isHTTPS = false;
  27. }
  28. public static string GetURL(string url)
  29. {
  30. url = url.Split(new[] { "://" }, StringSplitOptions.RemoveEmptyEntries).Last();
  31. if (!String.Equals(url,URL))
  32. {
  33. var client = new HttpClient { BaseAddress = new Uri($"https://{url}") };
  34. try
  35. {
  36. client.GetAsync("operations").Wait();
  37. URL = $"https://{url}";
  38. isHTTPS = true;
  39. }
  40. catch (Exception)
  41. {
  42. URL = $"http://{url}";
  43. isHTTPS = false;
  44. }
  45. }
  46. return URL;
  47. }
  48. }
  49. public static class WebSocketFactory
  50. {
  51. private static Dictionary<string, WebSocketClient> Clients = new Dictionary<string, WebSocketClient>();
  52. public static void StartWebSocket(string url, int port, Guid session)
  53. {
  54. url = url.Split(new[] { "://" }, StringSplitOptions.RemoveEmptyEntries).Last();
  55. var key = $"{url}:{port}${session}";
  56. if (!Clients.ContainsKey(key))
  57. {
  58. Clients[key] = new WebSocketClient(url, port, session);
  59. }
  60. }
  61. }
  62. public abstract class RemoteClient<TEntity> : BaseClient<TEntity> where TEntity : Entity, new()
  63. {
  64. public RemoteClient(string url, bool useSimpleEncryption = false)
  65. {
  66. URL = URLCache.GetURL(url);
  67. UseSimpleEncryption = useSimpleEncryption;
  68. }
  69. public string URL { get; set; }
  70. public bool UseSimpleEncryption { get; set; }
  71. private void PrepareRequest(Request request)
  72. {
  73. request.Credentials.Platform = ClientFactory.Platform;
  74. request.Credentials.Version = ClientFactory.Version;
  75. request.Credentials.Session = ClientFactory.SessionID;
  76. Request.BeforeRequest?.Invoke(request);
  77. }
  78. protected override ValidationData DoValidate(Guid session)
  79. {
  80. return Validate(
  81. null, null, false, session);
  82. }
  83. protected override ValidationData DoValidate(string pin, Guid session)
  84. {
  85. return Validate(
  86. null, pin, true, session);
  87. }
  88. protected override ValidationData DoValidate(string userid, string password, Guid session)
  89. {
  90. return Validate(
  91. userid, password, false, session);
  92. }
  93. private ValidationData Validate(string? userid, string? password, bool usePin, Guid session = default)
  94. {
  95. var ticks = DateTime.Now.ToUniversalTime().Ticks.ToString();
  96. var request = new ValidateRequest();
  97. request.UsePIN = usePin;
  98. if (usePin)
  99. {
  100. request.UserID = Encryption.Encrypt(ticks, "wCq9rryEJEuHIifYrxRjxg", UseSimpleEncryption);
  101. request.Password = Encryption.Encrypt(ticks, "7mhvLnqMwkCAzN+zNGlyyg", UseSimpleEncryption);
  102. request.PIN = password;
  103. }
  104. else
  105. {
  106. request.UserID = userid;
  107. request.Password = password;
  108. }
  109. PrepareRequest(request);
  110. if (session != Guid.Empty)
  111. {
  112. request.Credentials.Session = session;
  113. }
  114. var response = SendRequest<ValidateRequest, ValidateResponse>(request, "validate", SerializationFormat.Json, SerializationFormat.Json, false);
  115. if (response != null)
  116. if (response.Status.Equals(StatusCode.OK))
  117. {
  118. if(response.Session != Guid.Empty)
  119. {
  120. var notifyRequest = new NotifyRequest();
  121. // Session is required so that the server can exclude any requests from bad actors
  122. notifyRequest.Credentials.Session = response.Session;
  123. var notifyResponse = SendRequest<NotifyRequest, NotifyResponse>(notifyRequest, "notify", SerializationFormat.Json, SerializationFormat.Json, false);
  124. if(notifyResponse != null && notifyResponse.Status.Equals(StatusCode.OK))
  125. {
  126. if (notifyResponse.SocketPort.HasValue)
  127. {
  128. WebSocketFactory.StartWebSocket(URL, notifyResponse.SocketPort.Value, response.Session);
  129. }
  130. }
  131. }
  132. LocalCache.Password = password;
  133. return new ValidationData(
  134. response.ValidationResult,
  135. response.UserID,
  136. response.UserGuid,
  137. response.SecurityID,
  138. response.Session,
  139. response.Recipient2FA,
  140. response.PasswordExpiration
  141. );
  142. }
  143. else if(response.Status == StatusCode.BadServer)
  144. {
  145. throw new RemoteException(response.Messages, request);
  146. }
  147. return new ValidationData(
  148. ValidationResult.INVALID,
  149. "",
  150. Guid.Empty,
  151. Guid.Empty,
  152. Guid.Empty,
  153. null,
  154. DateTime.MinValue
  155. );
  156. }
  157. protected abstract TResponse SendRequest<TRequest, TResponse>(TRequest request, string Action, SerializationFormat requestFormat, SerializationFormat responseFormat, bool includeEntity = true)
  158. where TRequest : Request, new() where TResponse : Response, new();
  159. #region Query Data
  160. protected override CoreTable DoQuery(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? sort = null)
  161. {
  162. var request = new QueryRequest<TEntity>
  163. {
  164. Columns = columns,
  165. Filter = filter,
  166. Sort = sort
  167. };
  168. PrepareRequest(request);
  169. var response = SendRequest<QueryRequest<TEntity>, QueryResponse<TEntity>>(request, "List", SerializationFormat.Json, SerializationFormat.Binary);
  170. if (response != null)
  171. {
  172. return response.Status switch
  173. {
  174. StatusCode.OK => response.Items,
  175. StatusCode.Unauthenticated => throw new RemoteException("Client not authenticated", StatusCode.Unauthenticated, request),
  176. _ => throw new RemoteException(response.Messages, request),
  177. };
  178. }
  179. return null;
  180. //throw new Exception("Response is null");
  181. }
  182. #endregion
  183. #region Load
  184. protected override TEntity[] DoLoad(Filter<TEntity>? filter = null, SortOrder<TEntity>? sort = null)
  185. {
  186. var result = new List<TEntity>();
  187. var request = new QueryRequest<TEntity>
  188. {
  189. Filter = filter,
  190. Sort = sort
  191. };
  192. PrepareRequest(request);
  193. var response = SendRequest<QueryRequest<TEntity>, QueryResponse<TEntity>>(request, "List", SerializationFormat.Json, SerializationFormat.Binary);
  194. if (response.Items != null)
  195. foreach (var row in response.Items.Rows)
  196. result.Add(row.ToObject<TEntity>());
  197. return result.ToArray();
  198. }
  199. #endregion
  200. #region MultipleTables
  201. protected override Dictionary<string, CoreTable> DoQueryMultiple(Dictionary<string, IQueryDef> queries)
  202. {
  203. var request = new MultiQueryRequest();
  204. request.TableTypes = new Dictionary<string, string>();
  205. request.Filters = new Dictionary<string, string>();
  206. request.Columns = new Dictionary<string, string>();
  207. request.Sorts = new Dictionary<string, string>();
  208. foreach (var item in queries)
  209. {
  210. request.TableTypes[item.Key] = item.Value.Type.EntityName();
  211. request.Filters[item.Key] = Serialization.Serialize(item.Value.Filter);
  212. request.Columns[item.Key] = Serialization.Serialize(item.Value.Columns);
  213. request.Sorts[item.Key] = Serialization.Serialize(item.Value.SortOrder);
  214. }
  215. PrepareRequest(request);
  216. var response = SendRequest<MultiQueryRequest, MultiQueryResponse>(request, "QueryMultiple", SerializationFormat.Json, SerializationFormat.Binary, false);
  217. if (response != null)
  218. {
  219. return response.Status switch
  220. {
  221. StatusCode.OK => response.Tables,
  222. StatusCode.Unauthenticated => throw new RemoteException("Client not authenticated", request),
  223. _ => throw new RemoteException(response.Messages, request),
  224. };
  225. }
  226. return null;
  227. //throw new Exception("Response is null");
  228. }
  229. #endregion
  230. #region Save
  231. protected override void DoSave(TEntity entity, string auditnote)
  232. {
  233. var request = new SaveRequest<TEntity>();
  234. request.Item = entity;
  235. request.AuditNote = auditnote;
  236. request.ReturnOnlyChanged = true;
  237. PrepareRequest(request);
  238. var response = SendRequest<SaveRequest<TEntity>, SaveResponse<TEntity>>(request, "Save", SerializationFormat.Binary, SerializationFormat.Json);
  239. switch (response.Status)
  240. {
  241. case StatusCode.OK:
  242. /*var props = CoreUtils.PropertyList(typeof(TEntity), x => true, true);
  243. entity.SetObserving(false);
  244. foreach (var prop in props.Keys)
  245. {
  246. var value = CoreUtils.GetPropertyValue(response.Item, prop);
  247. CoreUtils.SetPropertyValue(entity, prop, value);
  248. }
  249. entity.CommitChanges();
  250. entity.SetObserving(true);*/
  251. entity.SetObserving(false);
  252. foreach (var (key, value) in response.ChangedValues)
  253. {
  254. if(CoreUtils.TryGetProperty<TEntity>(key, out var property))
  255. {
  256. CoreUtils.SetPropertyValue(entity, key, CoreUtils.ChangeType(value, property.PropertyType));
  257. }
  258. }
  259. entity.CommitChanges();
  260. entity.SetObserving(true);
  261. break;
  262. case StatusCode.Unauthenticated:
  263. throw new RemoteException("Client not authenticated", request);
  264. default:
  265. throw new RemoteException(response.Messages, request);
  266. }
  267. }
  268. protected override void DoSave(IEnumerable<TEntity> entities, string auditnote)
  269. {
  270. var items = entities.ToArray();
  271. var request = new MultiSaveRequest<TEntity>();
  272. request.Items = items;
  273. request.AuditNote = auditnote;
  274. request.ReturnOnlyChanged = true;
  275. PrepareRequest(request);
  276. var response = SendRequest<MultiSaveRequest<TEntity>, MultiSaveResponse<TEntity>>(request, "MultiSave", SerializationFormat.Binary, SerializationFormat.Json);
  277. switch (response.Status)
  278. {
  279. case StatusCode.OK:
  280. for(int i = 0; i < items.Length; ++i)
  281. {
  282. var entity = items[i];
  283. var changedValues = response.ChangedValues[i];
  284. entity.SetObserving(false);
  285. foreach (var (key, value) in changedValues)
  286. {
  287. if (CoreUtils.TryGetProperty<TEntity>(key, out var property))
  288. {
  289. CoreUtils.SetPropertyValue(entity, key, CoreUtils.ChangeType(value, property.PropertyType));
  290. }
  291. }
  292. entity.CommitChanges();
  293. entity.SetObserving(true);
  294. }
  295. /*var props = CoreUtils.PropertyList(typeof(TEntity), x => true, true);
  296. for (var i = 0; i < items.Length; i++)
  297. {
  298. items[i].SetObserving(false);
  299. foreach (var prop in props.Keys)
  300. {
  301. var value = CoreUtils.GetPropertyValue(response.Items[i], prop);
  302. CoreUtils.SetPropertyValue(items[i], prop, value);
  303. }
  304. //CoreUtils.DeepClone<TEntity>(response.Items[i], items[i]);
  305. items[i].CommitChanges();
  306. items[i].SetObserving(true);
  307. }*/
  308. break;
  309. case StatusCode.Unauthenticated:
  310. throw new RemoteException("Client not authenticated", request);
  311. default:
  312. throw new RemoteException(response.Messages, request);
  313. }
  314. }
  315. #endregion
  316. #region Delete
  317. protected override void DoDelete(TEntity entity, string auditnote)
  318. {
  319. var request = new DeleteRequest<TEntity>();
  320. request.Item = entity;
  321. PrepareRequest(request);
  322. var response = SendRequest<DeleteRequest<TEntity>, DeleteResponse<TEntity>>(request, "Delete", SerializationFormat.Json, SerializationFormat.Json);
  323. switch (response.Status)
  324. {
  325. case StatusCode.OK:
  326. break;
  327. case StatusCode.Unauthenticated:
  328. throw new RemoteException("Client not authenticated", request);
  329. default:
  330. throw new RemoteException(response.Messages, request);
  331. }
  332. }
  333. protected override void DoDelete(IList<TEntity> entities, string auditnote)
  334. {
  335. var items = entities.ToArray();
  336. var request = new MultiDeleteRequest<TEntity>();
  337. request.Items = items;
  338. request.AuditNote = auditnote;
  339. PrepareRequest(request);
  340. var response = SendRequest<MultiDeleteRequest<TEntity>, MultiDeleteResponse<TEntity>>(request, "MultiDelete", SerializationFormat.Json, SerializationFormat.Json);
  341. switch (response.Status)
  342. {
  343. case StatusCode.OK:
  344. break;
  345. case StatusCode.Unauthenticated:
  346. throw new RemoteException("Client not authenticated", request);
  347. default:
  348. throw new RemoteException(response.Messages, request);
  349. }
  350. }
  351. #endregion
  352. #region 2FA
  353. protected override bool DoCheck2FA(string code, Guid? session)
  354. {
  355. var request = new Check2FARequest { Code = code };
  356. PrepareRequest(request);
  357. var response = SendRequest<Check2FARequest, Check2FAResponse>(request, "check_2fa", SerializationFormat.Json, SerializationFormat.Json, false);
  358. if (response != null)
  359. {
  360. return response.Status switch
  361. {
  362. StatusCode.OK => response.Valid,
  363. StatusCode.Unauthenticated => false,
  364. _ => throw new RemoteException(response.Messages, request),
  365. };
  366. }
  367. return false;
  368. }
  369. #endregion
  370. #region Ping
  371. protected override bool DoPing()
  372. {
  373. var uri = new Uri(URL);
  374. var cli = new RestClient(uri);
  375. var req = new RestRequest("/classes", Method.Get) { Timeout = 20000 };
  376. try
  377. {
  378. var res = cli.Execute(req);
  379. if (res.StatusCode != HttpStatusCode.OK || res.ErrorException != null)
  380. return false;
  381. return true;
  382. }
  383. catch
  384. {
  385. return false;
  386. }
  387. }
  388. #endregion
  389. }
  390. }