RemoteClient.cs 15 KB

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