| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 | using System.Reflection;using GenHTTP.Api.Protocol;using GenHTTP.Modules.IO.Streaming;using InABox.Clients;using InABox.Core;using InABox.Database;using Microsoft.Exchange.WebServices.Data;using NPOI.SS.Formula.Functions;namespace InABox.API{    public class RestService    {        public static bool CheckPasswordExpiration { get; set; } = true;        protected static void GarbageCollection()        {            //DateTime now = DateTime.Now;            //GC.Collect();            //GC.WaitForPendingFinalizers();            //GC.Collect();            //Logger.Send(LogType.Information, "", String.Format("Garbage Collection takes {0:F2}ms", (DateTime.Now - now).TotalMilliseconds));        }        private static IQueryDef GetQuery<T>(string filterString, string columnsString, string sortString)            where T : Entity, IRemotable, IPersistent, new()        {            var filter = Core.Serialization.Deserialize<Filter<T>>(filterString);            var columns = Core.Serialization.Deserialize<Columns<T>>(columnsString);            var sort = Core.Serialization.Deserialize<SortOrder<T>>(sortString);            return new QueryDef<T>(filter, columns, sort);        }        protected static Guid ValidateRequest(Request request, out string? userID)        {            var session = request.Credentials.Session;            if(session != Guid.Empty)            {                var valid = CredentialsCache.Validate(session, out userID);                if (valid != Guid.Empty)                {                    CredentialsCache.RefreshSessionExpiry(session);                }                return valid;            }            else if(request.Credentials.UserID is not null                && request.Credentials.Password is not null                && CredentialsCache.IsBypassed(request.Credentials.UserID, request.Credentials.Password))            {                userID = "";                return CoreUtils.FullGuid;            }            userID = null;            return Guid.Empty;        }        public static MultiQueryResponse QueryMultiple(MultiQueryRequest request, bool isSecure)        {            var start = DateTime.Now;            var response = new MultiQueryResponse();            var userguid = ValidateRequest(request, out var userid);            if (userguid == Guid.Empty)                return response;            Logger.Send(LogType.Information, userid, string.Format("[{0} {1}] QueryMultiple({2})",                request.Credentials.Platform,                request.Credentials.Version,                request.TableTypes.Count));            try            {                var getQueryMethod = typeof(RestService).GetMethod(nameof(RestService.GetQuery), BindingFlags.NonPublic | BindingFlags.Static);                var queries = new Dictionary<string, IQueryDef>();                foreach (var item in request.TableTypes)                {                    var type = CoreUtils.GetEntity(item.Value);                    if(type.IsAssignableTo(typeof(ISecure)) && !isSecure)                    {                        Logger.Send(LogType.Error, userid, $"{type} is a secure entity. Request failed");                    }                    else                    {                        queries.Add(item.Key, getQueryMethod.MakeGenericMethod(type).Invoke(null, new object[]                        {                            request.Filters[item.Key],                            request.Columns[item.Key],                            request.Sorts[item.Key]                        }) as IQueryDef);                    }                }                response.Tables = DbFactory.QueryMultiple(queries, userguid, userid, request.Credentials.Platform, request.Credentials.Version);                response.Status = StatusCode.OK;                Logger.Send(LogType.Information, userid,                    string.Format("[{0} {1}] [{2:D8}] QueryMultiple Complete", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds));            }            catch (Exception err)            {                response.Status = StatusCode.Error;                response.Messages.Add(err.Message);                Logger.Send(LogType.Error, userid,                    string.Format("[{0} {1}] [{2:D8}] QueryMultiple Failed: {3} ", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds, err.Message));            }            GarbageCollection();            return response;        }        public static ValidateResponse Validate(ValidateRequest request)        {            var response = new ValidateResponse();            User? user = null;            bool reLogin = false;            if (request.Credentials.Session != Guid.Empty)            {                var session = request.Credentials.Session;                user = CredentialsCache.Validate(session);                if (user != null)                {                    Logger.Send(LogType.Information, "", $"{session} re-logged in!");                    CredentialsCache.RefreshSessionExpiry(session);                    reLogin = true;                }            }            if(user == null)            {                if (request.UsePIN)                {                    Logger.Send(LogType.Information, "", $"Login request with PIN {request.PIN}");                    user = CredentialsCache.ValidateUser(request.PIN);                }                else                {                    var userID = request.UserID ?? request.Credentials.UserID;                    var password = request.Password ?? request.Credentials.Password;                    if (userID == null || password == null)                        return response.Status(StatusCode.Error);                    user = CredentialsCache.ValidateUser(userID, password);                    if (user?.ID != CoreUtils.FullGuid)                    {                        Logger.Send(LogType.Information, userID, $"Login request for {userID}");                    }                }            }            response.Status = StatusCode.OK;            if (user == null)            {                Logger.Send(LogType.Information, "", $"Login failed!");                response.ValidationResult = ValidationResult.INVALID;            }            else if (CheckPasswordExpiration && !request.UsePIN && user.PasswordExpiration > DateTime.MinValue && user.PasswordExpiration < DateTime.Now)            {                Logger.Send(LogType.Information, user.UserID, $"Password for ({user.UserID}) has expired!");                response.ValidationResult = ValidationResult.PASSWORD_EXPIRED;            }            else if (reLogin)            {                Logger.Send(LogType.Information, user.UserID, $"Login ({user.UserID}) success!");                response.ValidationResult = ValidationResult.VALID;                response.UserGuid = user.ID;                response.UserID = user.UserID;                response.SecurityID = user.SecurityGroup.ID;                response.Session = request.Credentials.Session;                response.PasswordExpiration = CheckPasswordExpiration ? user.PasswordExpiration : DateTime.MinValue;            }            else if (user.ID == CoreUtils.FullGuid || !user.Use2FA)            {                Logger.Send(LogType.Information, user.UserID, $"Login ({user.UserID}) success!");                response.ValidationResult = ValidationResult.VALID;                response.UserGuid = user.ID;                response.UserID = user.UserID;                response.SecurityID = user.SecurityGroup.ID;                response.Session = user.ID == CoreUtils.FullGuid ?                     CredentialsCache.NewSession(user, true, DateTime.MaxValue) :                    CredentialsCache.NewSession(user, true);                response.PasswordExpiration = CheckPasswordExpiration ? user.PasswordExpiration : DateTime.MinValue;            }            else            {                Logger.Send(LogType.Information, user.UserID, $"Login ({user.UserID}) requires 2FA. Sending code...");                var session = CredentialsCache.SendCode(user.ID, out var recipient);                if (session != null)                {                    response.ValidationResult = ValidationResult.REQUIRE_2FA;                    response.UserGuid = user.ID;                    response.UserID = user.UserID;                    response.SecurityID = user.SecurityGroup.ID;                    response.Session = session ?? Guid.Empty;                    response.Recipient2FA = recipient;                    response.PasswordExpiration = CheckPasswordExpiration ? user.PasswordExpiration : DateTime.MinValue;                }                else                {                    response.Status = StatusCode.Error;                    response.Messages.Add("Code failed to send");                }            }            return response;        }        public static Check2FAResponse Check2FA(Check2FARequest request)        {            var response = new Check2FAResponse();            Logger.Send(LogType.Information, "", $"2FA check for session {request.Credentials.Session} and code {request.Code}");            response.Valid = CredentialsCache.ValidateCode(request.Credentials.Session, request.Code);            response.Status = StatusCode.OK;            return response;        }                public static InfoResponse Info(InfoRequest request)        {            var response = new InfoResponse()            {                Info = new DatabaseInfo()                {                    ColorScheme = DbFactory.ColorScheme,                    Version = CoreUtils.GetVersion(),                    Logo = DbFactory.Logo                }            };            response.Status = StatusCode.OK;            return response;        }            }    public class RestService<TEntity> : RestService where TEntity : Entity, new()    {        private static string SimpleName(Type t)        {            var names = t.Name.Split('.');            return names[names.Length - 1];        }        public static QueryResponse<TEntity> List(QueryRequest<TEntity> request)        {            var start = DateTime.Now;            var response = new QueryResponse<TEntity>();            var userguid = ValidateRequest(request, out var userid);            if (userguid == Guid.Empty)                return response.Status(StatusCode.Unauthenticated);            Logger.Send(LogType.Information, userid,                string.Format("[{0} {1}] Query{2}: Filter=[{3}] Sort=[{4}]", request.Credentials.Platform, request.Credentials.Version,                    SimpleName(typeof(TEntity)), request.Filter != null ? request.Filter.AsOData() : "",                    request.Sort != null ? request.Sort.AsOData() : ""));            try            {                var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);                response.Items = store.Query(request.Filter, request.Columns, request.Sort);                response.Status = StatusCode.OK;                Logger.Send(LogType.Information, userid,                    string.Format("[{0} {1}] [{2:D8}] Query{3} Complete: {4} records / {5} columns returned", request.Credentials.Platform,                        request.Credentials.Version, (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)),                        response.Items.Rows.Count, response.Items.Columns.Count));            }            catch (Exception err)            {                response.Status = StatusCode.Error;                response.Messages.Add(err.Message);                Logger.Send(LogType.Error, userid,                    string.Format("[{0} {1}] [{2:D8}] Query{3} Failed: {4} ", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message));            }            GarbageCollection();            return response;        }        /*public static LoadResponse<TEntity> Load(LoadRequest<TEntity> request)        {            var start = DateTime.Now;            var response = new LoadResponse<TEntity>();            var userguid = ValidateRequest(request, out var userid);            if (userguid == Guid.Empty)                return response.Status(StatusCode.Unauthenticated);            Logger.Send(LogType.Information, userid,                string.Format("[{0} {1}] Load{2}: Filter=[{3}]", request.Credentials.Platform, request.Credentials.Version,                    SimpleName(typeof(TEntity)),                    request.Filter != null ? request.Filter.AsOData() : ""));            try            {                var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);                response.Items = store.Load(request.Filter, request.Sort);                response.Status = StatusCode.OK;                Logger.Send(LogType.Information, userid,                    string.Format("[{0} {1}] [{2:D8}] Load{3} Complete: {4} records returned", request.Credentials.Platform,                        request.Credentials.Version,                        (uint)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), response.Items.Count()));            }            catch (Exception err)            {                response.Status = StatusCode.Error;                response.Messages.Add(err.Message);                Logger.Send(LogType.Error, userid,                    string.Format("[{0} {1}] [{2:D8}] Load{3} Failed: {4} ", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message));            }            GarbageCollection();            return response;        }*/        public static SaveResponse<TEntity> Save(SaveRequest<TEntity> request)        {            var start = DateTime.Now;            var response = new SaveResponse<TEntity>();            var userguid = ValidateRequest(request, out var userid);            if (userguid == Guid.Empty)                return response.Status(StatusCode.Unauthenticated);            Logger.Send(LogType.Information, userid,                string.Format("[{0} {1}] Save{2}: Data=[{3}]", request.Credentials.Platform, request.Credentials.Version, SimpleName(typeof(TEntity)),                    request.Item != null ? request.Item.ToString() : request + " (null)"));            try            {                var e = request.Item;                var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);                store.Save(e, request.AuditNote);                response.Item = e;                response.Status = StatusCode.OK;                Logger.Send(LogType.Information, userid,                    string.Format("[{0} {1}] [{2:D8}] Save{3} Data=[{4}] Complete", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), request.Item));                CredentialsCache.Refresh(typeof(TEntity) == typeof(User));            }            catch (Exception err)            {                response.Status = StatusCode.Error;                response.Messages.Add(err.Message);                Logger.Send(LogType.Error, userid,                    string.Format("[{0} {1}] [{2:D8}] Save{3} Failed: {4}\n\n{5} ", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message, err.StackTrace));            }            GarbageCollection();            return response;        }        public static MultiSaveResponse<TEntity> MultiSave(MultiSaveRequest<TEntity> request)        {            var start = DateTime.Now;            var response = new MultiSaveResponse<TEntity>();            var userguid = ValidateRequest(request, out var userid);            if (userguid == Guid.Empty)                return response.Status(StatusCode.Unauthenticated);            Logger.Send(LogType.Information, userid,                string.Format("[{0} {1}] MultiSave{2}({3}) Data=[{4}]", request.Credentials.Platform, request.Credentials.Version,                    SimpleName(typeof(TEntity)), request.Items.Length,                    request.Items != null ? string.Join(", ", request.Items.Select(x => x.ToString())) : request + " (null)"));            try            {                var es = request.Items;                var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);                store.Save(es, request.AuditNote);                response.Items = es;                response.Status = StatusCode.OK;                Logger.Send(LogType.Information, userid,                    string.Format("[{0} {1}] [{2:D8}] MultiSave{3} Count=[{4}] Complete", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), response.Items.Length));                CredentialsCache.Refresh(typeof(TEntity) == typeof(User));            }            catch (Exception err)            {                response.Status = StatusCode.Error;                response.Messages.Add(err.Message);                Logger.Send(LogType.Error, userid,                    string.Format("[{0} {1}] [{2:D8}] MultiSave{3} Failed: {4}\n\n{5} ", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message, err.StackTrace));            }            GarbageCollection();            return response;        }        public static DeleteResponse<TEntity> Delete(DeleteRequest<TEntity> request)        {            var start = DateTime.Now;            var response = new DeleteResponse<TEntity>();            var userguid = ValidateRequest(request, out var userid);            if (userguid == Guid.Empty)                return response.Status(StatusCode.Unauthenticated);            Logger.Send(LogType.Information, userid,                string.Format("[{0} {1}] Delete{2} Data=[{3}]", request.Credentials.Platform, request.Credentials.Version,                    SimpleName(typeof(TEntity)),                    request.Item));            try            {                var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);                store.Delete(request.Item, request.AuditNote);                response.Status = StatusCode.OK;                Logger.Send(LogType.Information, userid,                    string.Format("[{0} {1}] [{2:D8}] Delete{3} Complete", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity))));                CredentialsCache.Refresh(typeof(TEntity) == typeof(User));            }            catch (Exception err)            {                response.Status = StatusCode.Error;                response.Messages.Add(err.Message);                Logger.Send(LogType.Error, userid,                    string.Format("[{0} {1}] [{2:D8}] Delete{3} Failed: {4} ", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message));            }            GarbageCollection();            return response;        }        public static MultiDeleteResponse<TEntity> MultiDelete(MultiDeleteRequest<TEntity> request)        {            var start = DateTime.Now;            var response = new MultiDeleteResponse<TEntity>();            var userguid = ValidateRequest(request, out var userid);            if (userguid == Guid.Empty)                return response.Status(StatusCode.Unauthenticated);            Logger.Send(LogType.Information, userid,                string.Format("[{0} {1}] MultiDelete{2}({3}) Data=[{4}]", request.Credentials.Platform, request.Credentials.Version,                    SimpleName(typeof(TEntity)), request.Items.Length,                    request.Items != null ? string.Join(", ", request.Items.Select(x => x.ToString())) : request + " (null)"));            try            {                var es = request.Items;                var store = DbFactory.FindStore<TEntity>(userguid, userid, request.Credentials.Platform, request.Credentials.Version);                store.Delete(es, request.AuditNote);                response.Status = StatusCode.OK;                Logger.Send(LogType.Information, userid,                    string.Format("[{0} {1}] [{2:D8}] MultiDelete{3} Count=[{4}] Complete", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), request.Items.Length));                CredentialsCache.Refresh(typeof(TEntity) == typeof(User));            }            catch (Exception err)            {                response.Status = StatusCode.Error;                response.Messages.Add(err.Message);                Logger.Send(LogType.Error, userid,                    string.Format("[{0} {1}] [{2:D8}] MultiDelete{3} Failed: {4}\n\n{5} ", request.Credentials.Platform, request.Credentials.Version,                        (int)DateTime.Now.Subtract(start).TotalMilliseconds, SimpleName(typeof(TEntity)), err.Message, err.StackTrace));            }            GarbageCollection();            return response;        }    }}
 |