RemoteClient.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  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, 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, 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 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.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.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.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. PrepareRequest(request);
  245. var response = SendRequest<SaveRequest<TEntity>, SaveResponse<TEntity>>(request, "Save", SerializationFormat.Json);
  246. switch (response.Status)
  247. {
  248. case StatusCode.OK:
  249. var props = CoreUtils.PropertyList(typeof(TEntity), x => true, true);
  250. entity.SetObserving(false);
  251. foreach (var prop in props.Keys)
  252. {
  253. var value = CoreUtils.GetPropertyValue(response.Item, prop);
  254. CoreUtils.SetPropertyValue(entity, prop, value);
  255. }
  256. entity.CommitChanges();
  257. entity.SetObserving(true);
  258. break;
  259. case StatusCode.Unauthenticated:
  260. throw new RemoteException("Client not authenticated", request);
  261. default:
  262. throw new RemoteException(response.Messages, request);
  263. }
  264. }
  265. protected override void DoSave(IEnumerable<TEntity> entities, string auditnote)
  266. {
  267. var items = entities.ToArray();
  268. var request = new MultiSaveRequest<TEntity>();
  269. request.Items = items;
  270. request.AuditNote = auditnote;
  271. PrepareRequest(request);
  272. var response = SendRequest<MultiSaveRequest<TEntity>, MultiSaveResponse<TEntity>>(request, "MultiSave", SerializationFormat.Json);
  273. switch (response.Status)
  274. {
  275. case StatusCode.OK:
  276. var props = CoreUtils.PropertyList(typeof(TEntity), x => true, true);
  277. for (var i = 0; i < items.Length; i++)
  278. {
  279. items[i].SetObserving(false);
  280. foreach (var prop in props.Keys)
  281. {
  282. var value = CoreUtils.GetPropertyValue(response.Items[i], prop);
  283. CoreUtils.SetPropertyValue(items[i], prop, value);
  284. }
  285. //CoreUtils.DeepClone<TEntity>(response.Items[i], items[i]);
  286. items[i].CommitChanges();
  287. items[i].SetObserving(true);
  288. }
  289. break;
  290. case StatusCode.Unauthenticated:
  291. throw new RemoteException("Client not authenticated", request);
  292. default:
  293. throw new RemoteException(response.Messages, request);
  294. }
  295. }
  296. #endregion
  297. #region Delete
  298. protected override void DoDelete(TEntity entity, string auditnote)
  299. {
  300. var request = new DeleteRequest<TEntity>();
  301. request.Item = entity;
  302. PrepareRequest(request);
  303. var response = SendRequest<DeleteRequest<TEntity>, DeleteResponse<TEntity>>(request, "Delete", SerializationFormat.Json);
  304. switch (response.Status)
  305. {
  306. case StatusCode.OK:
  307. break;
  308. case StatusCode.Unauthenticated:
  309. throw new RemoteException("Client not authenticated", request);
  310. default:
  311. throw new RemoteException(response.Messages, request);
  312. }
  313. }
  314. protected override void DoDelete(IList<TEntity> entities, string auditnote)
  315. {
  316. var items = entities.ToArray();
  317. var request = new MultiDeleteRequest<TEntity>();
  318. request.Items = items;
  319. request.AuditNote = auditnote;
  320. PrepareRequest(request);
  321. var response = SendRequest<MultiDeleteRequest<TEntity>, MultiDeleteResponse<TEntity>>(request, "MultiDelete", SerializationFormat.Json);
  322. switch (response.Status)
  323. {
  324. case StatusCode.OK:
  325. break;
  326. case StatusCode.Unauthenticated:
  327. throw new RemoteException("Client not authenticated", request);
  328. default:
  329. throw new RemoteException(response.Messages, request);
  330. }
  331. }
  332. #endregion
  333. #region 2FA
  334. protected override bool DoCheck2FA(string code, Guid? session)
  335. {
  336. var request = new Check2FARequest { Code = code };
  337. PrepareRequest(request);
  338. var response = SendRequest<Check2FARequest, Check2FAResponse>(request, "check_2fa", SerializationFormat.Json, false);
  339. if (response != null)
  340. {
  341. return response.Status switch
  342. {
  343. StatusCode.OK => response.Valid,
  344. StatusCode.Unauthenticated => false,
  345. _ => throw new RemoteException(response.Messages, request),
  346. };
  347. }
  348. return false;
  349. }
  350. #endregion
  351. #region Ping
  352. protected override bool DoPing()
  353. {
  354. var uri = new Uri(string.Format("{0}:{1}", URL, Port));
  355. var cli = new RestClient(uri);
  356. var req = new RestRequest("/classes", Method.GET) { Timeout = 20000 };
  357. try
  358. {
  359. var res = cli.Execute(req);
  360. if (res.StatusCode != HttpStatusCode.OK || res.ErrorException != null)
  361. return false;
  362. return true;
  363. }
  364. catch
  365. {
  366. return false;
  367. }
  368. }
  369. #endregion
  370. }
  371. }