RestService.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. using System.Reflection;
  2. using GenHTTP.Api.Protocol;
  3. using GenHTTP.Modules.IO.Streaming;
  4. using InABox.Clients;
  5. using InABox.Core;
  6. using InABox.Database;
  7. using Microsoft.Exchange.WebServices.Data;
  8. using NPOI.SS.Formula.Functions;
  9. namespace InABox.API
  10. {
  11. public class RestService
  12. {
  13. protected static void GarbageCollection()
  14. {
  15. //DateTime now = DateTime.Now;
  16. //GC.Collect();
  17. //GC.WaitForPendingFinalizers();
  18. //GC.Collect();
  19. //Logger.Send(LogType.Information, "", String.Format("Garbage Collection takes {0:F2}ms", (DateTime.Now - now).TotalMilliseconds));
  20. }
  21. private static IQueryDef GetQuery<T>(string filterString, string columnsString, string sortString)
  22. where T : Entity, IRemotable, IPersistent, new()
  23. {
  24. var filter = Core.Serialization.Deserialize<Filter<T>>(filterString);
  25. var columns = Core.Serialization.Deserialize<Columns<T>>(columnsString);
  26. var sort = Core.Serialization.Deserialize<SortOrder<T>>(sortString);
  27. return new QueryDef<T>(filter, columns, sort);
  28. }
  29. protected static Guid ValidateRequest(Request request, out string? userID)
  30. {
  31. var session = request.Credentials.Session;
  32. if(session != Guid.Empty)
  33. {
  34. var valid = CredentialsCache.Validate(session, out userID);
  35. if (valid != Guid.Empty)
  36. {
  37. CredentialsCache.RefreshSessionExpiry(session);
  38. }
  39. return valid;
  40. }
  41. else if(request.Credentials.UserID is not null
  42. && request.Credentials.Password is not null
  43. && CredentialsCache.IsBypassed(request.Credentials.UserID, request.Credentials.Password))
  44. {
  45. userID = "";
  46. return CoreUtils.FullGuid;
  47. }
  48. userID = null;
  49. return Guid.Empty;
  50. }
  51. public static MultiQueryResponse QueryMultiple(MultiQueryRequest request, bool isSecure)
  52. {
  53. var start = DateTime.Now;
  54. var response = new MultiQueryResponse();
  55. var userguid = ValidateRequest(request, out var userid);
  56. if (userguid == Guid.Empty)
  57. return response;
  58. Logger.Send(LogType.Information, userid, string.Format("[{0} {1}] QueryMultiple({2})",
  59. request.Credentials.Platform,
  60. request.Credentials.Version,
  61. request.TableTypes.Count));
  62. try
  63. {
  64. var getQueryMethod = typeof(RestService).GetMethod(nameof(RestService.GetQuery), BindingFlags.NonPublic | BindingFlags.Static);
  65. var queries = new Dictionary<string, IQueryDef>();
  66. foreach (var item in request.TableTypes)
  67. {
  68. var type = CoreUtils.GetEntity(item.Value);
  69. if(type.IsAssignableTo(typeof(ISecure)) && !isSecure)
  70. {
  71. Logger.Send(LogType.Error, userid, $"{type} is a secure entity. Request failed");
  72. }
  73. else
  74. {
  75. queries.Add(item.Key, getQueryMethod.MakeGenericMethod(type).Invoke(null, new object[]
  76. {
  77. request.Filters[item.Key],
  78. request.Columns[item.Key],
  79. request.Sorts[item.Key]
  80. }) as IQueryDef);
  81. }
  82. }
  83. response.Tables = DbFactory.QueryMultiple(queries, userguid, userid, request.Credentials.Platform, request.Credentials.Version);
  84. response.Status = StatusCode.OK;
  85. Logger.Send(LogType.Information, userid,
  86. string.Format("[{0} {1}] [{2:D8}] QueryMultiple Complete", request.Credentials.Platform, request.Credentials.Version,
  87. (int)DateTime.Now.Subtract(start).TotalMilliseconds));
  88. }
  89. catch (Exception err)
  90. {
  91. response.Status = StatusCode.Error;
  92. response.Messages.Add(err.Message);
  93. Logger.Send(LogType.Error, userid,
  94. string.Format("[{0} {1}] [{2:D8}] QueryMultiple Failed: {3} ", request.Credentials.Platform, request.Credentials.Version,
  95. (int)DateTime.Now.Subtract(start).TotalMilliseconds, err.Message));
  96. }
  97. GarbageCollection();
  98. return response;
  99. }
  100. public static ValidateResponse Validate(ValidateRequest request)
  101. {
  102. var response = new ValidateResponse();
  103. User? user = null;
  104. bool reLogin = false;
  105. if (request.Credentials.Session != Guid.Empty)
  106. {
  107. var session = request.Credentials.Session;
  108. user = CredentialsCache.Validate(session);
  109. if (user != null)
  110. {
  111. Logger.Send(LogType.Information, "", $"{session} re-logged in!");
  112. CredentialsCache.RefreshSessionExpiry(session);
  113. reLogin = true;
  114. }
  115. }
  116. if(user == null)
  117. {
  118. if (request.UsePIN)
  119. {
  120. Logger.Send(LogType.Information, "", $"Login request with PIN {request.PIN}");
  121. user = CredentialsCache.ValidateUser(request.PIN);
  122. }
  123. else
  124. {
  125. var userID = request.UserID ?? request.Credentials.UserID;
  126. var password = request.Password ?? request.Credentials.Password;
  127. if (userID == null || password == null)
  128. return response.Status(StatusCode.Error);
  129. user = CredentialsCache.ValidateUser(userID, password);
  130. if (user?.ID != CoreUtils.FullGuid)
  131. {
  132. Logger.Send(LogType.Information, userID, $"Login request for {userID}");
  133. }
  134. }
  135. }
  136. response.Status = StatusCode.OK;
  137. if (user == null)
  138. {
  139. Logger.Send(LogType.Information, "", $"Login failed!");
  140. response.ValidationResult = ValidationResult.INVALID;
  141. }
  142. else if (!request.UsePIN && user.PasswordExpiration > DateTime.MinValue && user.PasswordExpiration < DateTime.Now)
  143. {
  144. Logger.Send(LogType.Information, user.UserID, $"Password for ({user.UserID}) has expired!");
  145. response.ValidationResult = ValidationResult.PASSWORD_EXPIRED;
  146. }
  147. else if (reLogin)
  148. {
  149. Logger.Send(LogType.Information, user.UserID, $"Login ({user.UserID}) success!");
  150. response.ValidationResult = ValidationResult.VALID;
  151. response.UserGuid = user.ID;
  152. response.UserID = user.UserID;
  153. response.SecurityID = user.SecurityGroup.ID;
  154. response.Session = request.Credentials.Session;
  155. response.PasswordExpiration = user.PasswordExpiration;
  156. }
  157. else if (user.ID == CoreUtils.FullGuid || !user.Use2FA)
  158. {
  159. Logger.Send(LogType.Information, user.UserID, $"Login ({user.UserID}) success!");
  160. response.ValidationResult = ValidationResult.VALID;
  161. response.UserGuid = user.ID;
  162. response.UserID = user.UserID;
  163. response.SecurityID = user.SecurityGroup.ID;
  164. response.Session = user.ID == CoreUtils.FullGuid ?
  165. CredentialsCache.NewSession(user, true, DateTime.MaxValue) :
  166. CredentialsCache.NewSession(user, true);
  167. response.PasswordExpiration = user.PasswordExpiration;
  168. }
  169. else
  170. {
  171. Logger.Send(LogType.Information, user.UserID, $"Login ({user.UserID}) requires 2FA. Sending code...");
  172. var session = CredentialsCache.SendCode(user.ID, out var recipient);
  173. if (session != null)
  174. {
  175. response.ValidationResult = ValidationResult.REQUIRE_2FA;
  176. response.UserGuid = user.ID;
  177. response.UserID = user.UserID;
  178. response.SecurityID = user.SecurityGroup.ID;
  179. response.Session = session ?? Guid.Empty;
  180. response.Recipient2FA = recipient;
  181. response.PasswordExpiration = user.PasswordExpiration;
  182. }
  183. else
  184. {
  185. response.Status = StatusCode.Error;
  186. response.Messages.Add("Code failed to send");
  187. }
  188. }
  189. return response;
  190. }
  191. public static Check2FAResponse Check2FA(Check2FARequest request)
  192. {
  193. var response = new Check2FAResponse();
  194. Logger.Send(LogType.Information, "", $"2FA check for session {request.Credentials.Session} and code {request.Code}");
  195. response.Valid = CredentialsCache.ValidateCode(request.Credentials.Session, request.Code);
  196. response.Status = StatusCode.OK;
  197. return response;
  198. }
  199. public static InfoResponse Info(InfoRequest request)
  200. {
  201. var response = new InfoResponse()
  202. {
  203. Info = new DatabaseInfo()
  204. {
  205. ColorScheme = DbFactory.ColorScheme,
  206. Version = CoreUtils.GetVersion(),
  207. Logo = DbFactory.Logo
  208. }
  209. };
  210. response.Status = StatusCode.OK;
  211. return response;
  212. }
  213. }
  214. public class RestService<TEntity> : RestService where TEntity : Entity, new()
  215. {
  216. private static string SimpleName(Type t)
  217. {
  218. var names = t.Name.Split('.');
  219. return names[names.Length - 1];
  220. }
  221. public static QueryResponse<TEntity> List(QueryRequest<TEntity> request)
  222. {
  223. var start = DateTime.Now;
  224. var response = new QueryResponse<TEntity>();
  225. var userguid = ValidateRequest(request, out var userid);
  226. if (userguid == Guid.Empty)
  227. return response.Status(StatusCode.Unauthenticated);
  228. Logger.Send(LogType.Information, userid,
  229. string.Format("[{0} {1}] Query{2}: Filter=[{3}] Sort=[{4}]", request.Credentials.Platform, request.Credentials.Version,
  230. SimpleName(typeof(TEntity)), request.Filter != null ? request.Filter.AsOData() : "",
  231. request.Sort != null ? request.Sort.AsOData() : ""));
  232. try
  233. {
  234. var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);
  235. response.Items = store.Query(request.Filter, request.Columns, request.Sort);
  236. response.Status = StatusCode.OK;
  237. Logger.Send(LogType.Information, userid,
  238. string.Format("[{0} {1}] [{2:D8}] Query{3} Complete: {4} records / {5} columns returned", request.Credentials.Platform,
  239. request.Credentials.Version, (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)),
  240. response.Items.Rows.Count, response.Items.Columns.Count));
  241. }
  242. catch (Exception err)
  243. {
  244. response.Status = StatusCode.Error;
  245. response.Messages.Add(err.Message);
  246. Logger.Send(LogType.Error, userid,
  247. string.Format("[{0} {1}] [{2:D8}] Query{3} Failed: {4} ", request.Credentials.Platform, request.Credentials.Version,
  248. (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message));
  249. }
  250. GarbageCollection();
  251. return response;
  252. }
  253. public static LoadResponse<TEntity> Load(LoadRequest<TEntity> request)
  254. {
  255. var start = DateTime.Now;
  256. var response = new LoadResponse<TEntity>();
  257. var userguid = ValidateRequest(request, out var userid);
  258. if (userguid == Guid.Empty)
  259. return response.Status(StatusCode.Unauthenticated);
  260. Logger.Send(LogType.Information, userid,
  261. string.Format("[{0} {1}] Load{2}: Filter=[{3}]", request.Credentials.Platform, request.Credentials.Version,
  262. SimpleName(typeof(TEntity)),
  263. request.Filter != null ? request.Filter.AsOData() : ""));
  264. try
  265. {
  266. var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);
  267. response.Items = store.Load(request.Filter, request.Sort);
  268. response.Status = StatusCode.OK;
  269. Logger.Send(LogType.Information, userid,
  270. string.Format("[{0} {1}] [{2:D8}] Load{3} Complete: {4} records returned", request.Credentials.Platform,
  271. request.Credentials.Version,
  272. (uint)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), response.Items.Count()));
  273. }
  274. catch (Exception err)
  275. {
  276. response.Status = StatusCode.Error;
  277. response.Messages.Add(err.Message);
  278. Logger.Send(LogType.Error, userid,
  279. string.Format("[{0} {1}] [{2:D8}] Load{3} Failed: {4} ", request.Credentials.Platform, request.Credentials.Version,
  280. (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message));
  281. }
  282. GarbageCollection();
  283. return response;
  284. }
  285. public static SaveResponse<TEntity> Save(SaveRequest<TEntity> request)
  286. {
  287. var start = DateTime.Now;
  288. var response = new SaveResponse<TEntity>();
  289. var userguid = ValidateRequest(request, out var userid);
  290. if (userguid == Guid.Empty)
  291. return response.Status(StatusCode.Unauthenticated);
  292. Logger.Send(LogType.Information, userid,
  293. string.Format("[{0} {1}] Save{2}: Data=[{3}]", request.Credentials.Platform, request.Credentials.Version, SimpleName(typeof(TEntity)),
  294. request.Item != null ? request.Item.ToString() : request + " (null)"));
  295. try
  296. {
  297. var e = request.Item;
  298. var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);
  299. store.Save(e, request.AuditNote);
  300. response.Item = e;
  301. response.Status = StatusCode.OK;
  302. Logger.Send(LogType.Information, userid,
  303. string.Format("[{0} {1}] [{2:D8}] Save{3} Data=[{4}] Complete", request.Credentials.Platform, request.Credentials.Version,
  304. (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), request.Item));
  305. CredentialsCache.Refresh(typeof(TEntity) == typeof(User));
  306. }
  307. catch (Exception err)
  308. {
  309. response.Status = StatusCode.Error;
  310. response.Messages.Add(err.Message);
  311. Logger.Send(LogType.Error, userid,
  312. string.Format("[{0} {1}] [{2:D8}] Save{3} Failed: {4}\n\n{5} ", request.Credentials.Platform, request.Credentials.Version,
  313. (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message, err.StackTrace));
  314. }
  315. GarbageCollection();
  316. return response;
  317. }
  318. public static MultiSaveResponse<TEntity> MultiSave(MultiSaveRequest<TEntity> request)
  319. {
  320. var start = DateTime.Now;
  321. var response = new MultiSaveResponse<TEntity>();
  322. var userguid = ValidateRequest(request, out var userid);
  323. if (userguid == Guid.Empty)
  324. return response.Status(StatusCode.Unauthenticated);
  325. Logger.Send(LogType.Information, userid,
  326. string.Format("[{0} {1}] MultiSave{2}({3}) Data=[{4}]", request.Credentials.Platform, request.Credentials.Version,
  327. SimpleName(typeof(TEntity)), request.Items.Length,
  328. request.Items != null ? string.Join(", ", request.Items.Select(x => x.ToString())) : request + " (null)"));
  329. try
  330. {
  331. var es = request.Items;
  332. var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);
  333. store.Save(es, request.AuditNote);
  334. response.Items = es;
  335. response.Status = StatusCode.OK;
  336. Logger.Send(LogType.Information, userid,
  337. string.Format("[{0} {1}] [{2:D8}] MultiSave{3} Count=[{4}] Complete", request.Credentials.Platform, request.Credentials.Version,
  338. (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), response.Items.Length));
  339. CredentialsCache.Refresh(typeof(TEntity) == typeof(User));
  340. }
  341. catch (Exception err)
  342. {
  343. response.Status = StatusCode.Error;
  344. response.Messages.Add(err.Message);
  345. Logger.Send(LogType.Error, userid,
  346. string.Format("[{0} {1}] [{2:D8}] MultiSave{3} Failed: {4}\n\n{5} ", request.Credentials.Platform, request.Credentials.Version,
  347. (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message, err.StackTrace));
  348. }
  349. GarbageCollection();
  350. return response;
  351. }
  352. public static DeleteResponse<TEntity> Delete(DeleteRequest<TEntity> request)
  353. {
  354. var start = DateTime.Now;
  355. var response = new DeleteResponse<TEntity>();
  356. var userguid = ValidateRequest(request, out var userid);
  357. if (userguid == Guid.Empty)
  358. return response.Status(StatusCode.Unauthenticated);
  359. Logger.Send(LogType.Information, userid,
  360. string.Format("[{0} {1}] Delete{2} Data=[{3}]", request.Credentials.Platform, request.Credentials.Version,
  361. SimpleName(typeof(TEntity)),
  362. request.Item));
  363. try
  364. {
  365. var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);
  366. store.Delete(request.Item, request.AuditNote);
  367. response.Status = StatusCode.OK;
  368. Logger.Send(LogType.Information, userid,
  369. string.Format("[{0} {1}] [{2:D8}] Delete{3} Complete", request.Credentials.Platform, request.Credentials.Version,
  370. (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity))));
  371. CredentialsCache.Refresh(typeof(TEntity) == typeof(User));
  372. }
  373. catch (Exception err)
  374. {
  375. response.Status = StatusCode.Error;
  376. response.Messages.Add(err.Message);
  377. Logger.Send(LogType.Error, userid,
  378. string.Format("[{0} {1}] [{2:D8}] Delete{3} Failed: {4} ", request.Credentials.Platform, request.Credentials.Version,
  379. (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message));
  380. }
  381. GarbageCollection();
  382. return response;
  383. }
  384. public static MultiDeleteResponse<TEntity> MultiDelete(MultiDeleteRequest<TEntity> request)
  385. {
  386. var start = DateTime.Now;
  387. var response = new MultiDeleteResponse<TEntity>();
  388. var userguid = ValidateRequest(request, out var userid);
  389. if (userguid == Guid.Empty)
  390. return response.Status(StatusCode.Unauthenticated);
  391. Logger.Send(LogType.Information, userid,
  392. string.Format("[{0} {1}] MultiDelete{2}({3}) Data=[{4}]", request.Credentials.Platform, request.Credentials.Version,
  393. SimpleName(typeof(TEntity)), request.Items.Length,
  394. request.Items != null ? string.Join(", ", request.Items.Select(x => x.ToString())) : request + " (null)"));
  395. try
  396. {
  397. var es = request.Items;
  398. var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);
  399. store.Delete(es, request.AuditNote);
  400. response.Status = StatusCode.OK;
  401. Logger.Send(LogType.Information, userid,
  402. string.Format("[{0} {1}] [{2:D8}] MultiDelete{3} Count=[{4}] Complete", request.Credentials.Platform, request.Credentials.Version,
  403. (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), request.Items.Length));
  404. CredentialsCache.Refresh(typeof(TEntity) == typeof(User));
  405. }
  406. catch (Exception err)
  407. {
  408. response.Status = StatusCode.Error;
  409. response.Messages.Add(err.Message);
  410. Logger.Send(LogType.Error, userid,
  411. string.Format("[{0} {1}] [{2:D8}] MultiDelete{3} Failed: {4}\n\n{5} ", request.Credentials.Platform, request.Credentials.Version,
  412. (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message, err.StackTrace));
  413. }
  414. GarbageCollection();
  415. return response;
  416. }
  417. }
  418. }