RemoteClient.cs 17 KB

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