/// <summary> /// Execute a service from a client /// </summary> /// <param name="customerId"></param> /// <param name="userId"></param> /// <param name="profile"></param> /// <param name="area"></param> /// <param name="moduleId"></param> /// <param name="service"></param> /// <param name="record"></param> /// <param name="identity"></param> /// <returns>Data</returns> public JObject ExecuteService(int customerId, int userId, UserProfile.EUserProfile profile, string area, int moduleId, string service, JObject record, JObject identity) { JObject result = null; Info($"Executing the service: ['{service}', '{(record == null ? "null" : record.ToString(Formatting.None))}', '{(identity == null ? "null" : identity.ToString(Formatting.None))}'] ..."); // Lock database during the execution of the service using (DatabaseLock lockDatabase = Database.Lock(customerId)) { // Execute the request try { result = Database.ExecuteService(customerId, userId, profile, area, moduleId, service, record, identity); // Unlock the database lockDatabase.Commit(); } catch (System.Exception ex) { Exception("An exception occurs on executing the service", ex); // Rollback the requests done throw; } } Info($"The service has correctly been executed : {(result == null ? "null" : result.ToString(Formatting.None))}"); return(result); }
/// <summary> /// Load the content of the table and returns a list of columns matching within the area and the profile of the user /// </summary> /// <param name="customerId"></param> /// <param name="userId"></param> /// <param name="profile"></param> /// <param name="area"></param> /// <param name="table"></param> /// <param name="existingRecords">Define it to replace the loading into the database</param> /// <returns>The table records: /// Table = table name /// Records = List of tuple containing all data /// LastSequenceId = Last sequence id of the user in this table /// </returns> public IEnumerable <JArray> LoadTable(int customerId, int userId, UserProfile.EUserProfile profile, string area, string table, List <DSRecord> existingRecords) { List <JArray> records = new List <JArray>(); Info($"Loading content of the table '{table}' ..."); // Retrieve the database schema DSSchema.DSDatabase schema = ConfigurationManager.Schemas[area]; if (schema == null) { Error("No schema available!"); throw new ExceptionDefinitionRecord("ERR_SCHEMA"); } // Read the content of the given table System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); long lotSize = ConfigurationManager.ConnectionLotSize; long sizeTotal = 0; long sizeRecord = 0; long nbRecords = 0; foreach (object[] record in schema.ReadTable(Database, table, customerId, userId, profile, area, null, existingRecords)) { records.Add(new JArray(record)); using (System.IO.Stream s = new System.IO.MemoryStream()) { formatter.Serialize(s, record); sizeRecord = s.Length; } sizeTotal += sizeRecord; nbRecords++; if (sizeTotal >= lotSize) { yield return(new JArray(records)); records.Clear(); sizeTotal = 0; } } if (sizeTotal > 0) { yield return(new JArray(records)); } Info($"{nbRecords} records read from the table '{table}'"); }
/// <summary> /// Build a dynamic record containing properties depending on the area and the profile /// </summary> /// <param name="record"></param> /// <param name="area"></param> /// <param name="userId"></param> /// <param name="profile"></param> /// <param name="request"></param> /// <returns>true if the record is filtered</returns> public bool FilterRecord(DSRecord record, string area, int userId, UserProfile.EUserProfile profile, DSRequest request) { foreach (KeyValuePair <string, DSTable> table in Tables) { if (table.Value.Table != record.GetType()) { continue; } return(table.Value.FilterRecord(record, area, userId, profile, request)); } return(false); }
/// <summary> /// Build a dynamic record containing properties depending on the area and the profile /// </summary> /// <param name="record"></param> /// <param name="area"></param> /// <param name="userId"></param> /// <param name="profile"></param> /// <returns></returns> public JObject FilterRecord(DSRecord record, string area, int userId, UserProfile.EUserProfile profile) { foreach (KeyValuePair <string, DSTable> table in Tables) { if (table.Value.Table != record.GetType()) { continue; } return(table.Value.FilterRecord(record, area, userId, profile)); } return(null); }
/// <summary> /// Convert the schema into JSON /// </summary> /// <param name="area">if null, all tables, else tables and columns having the restriction to the area</param> /// <param name="profile">if None, all tables, else tables and columns having the restriction to the profile</param> /// <param name="cache">if null, no order, else list of tables in the expected order</param> /// <returns></returns> public JObject ToJSON(string area, UserProfile.EUserProfile profile, DSCache cache) { JObject result = new JObject(); if (cache == null) { foreach (KeyValuePair <string, DSTable> table in Tables) { JObject tableDescription = table.Value.ToJSON(area, profile); if (tableDescription != null) { result[table.Key] = tableDescription; } } } else { foreach (string table in cache.Tables) { if (!Tables.ContainsKey(table)) { continue; } JObject tableDescription = Tables[table].ToJSON(area, profile); if (tableDescription != null) { result[table] = tableDescription; } } foreach (KeyValuePair <string, DSTable> table in Tables) { if (cache.Tables.IndexOf(table.Key) >= 0) { continue; } JObject tableDescription = table.Value.ToJSON(area, profile); if (tableDescription != null) { result[table.Key] = tableDescription; } } } return(result); }
/// <summary> /// Execute a service /// </summary> /// <param name="customerId"></param> /// <param name="userId"></param> /// <param name="profile"></param> /// <param name="area"></param> /// <param name="moduleId"></param> /// <param name="service"></param> /// <param name="record"></param> /// <param name="identity"></param> /// <returns></returns> public override JObject ExecuteService(int customerId, int userId, UserProfile.EUserProfile profile, string area, int moduleId, string service, JObject record, JObject identity) { JObject result = base.ExecuteService(customerId, userId, profile, area, moduleId, service, record, identity); if (result != null) return result; Error($"The service '{service}' is unknown!"); throw new ExceptionDefinitionRecord("ERR_UNAUTHORIZED"); }
/// <summary> /// Retrieve a list of tuple (DSRecord, Table) attached to a given record (table, id) for the given profile /// This function is used to retrieve a list of records attached to the current update /// Example : /// The object A is not visible for the user Y /// The user X updates the object A /// The object A becomes visible for the user Y /// In that case, object A must be added and notified to the user Y /// This function builds this case /// </summary> /// <param name="cache"></param> /// <param name="table"></param> /// <param name="id"></param> /// <param name="customerId"></param> /// <param name="userId"></param> /// <param name="profile"></param> /// <param name="area"></param> /// <param name="deepUpdate"></param> /// <param name="recordAlreadyRead"></param> /// <param name="informationAlreadyRead"></param> /// <returns></returns> virtual public void GetListRecordsConcernedByUpdate(DSCache cache, string table, int id, int customerId, int userId, UserProfile.EUserProfile profile, string area, bool deepUpdate, DSRecord recordAlreadyRead, InformationRecord informationAlreadyRead) { if (id < 0 || profile != UserProfile.EUserProfile.None || cache.Is(table, id) != null) { return; } cache.Set(table, id, null); }
/// <summary> /// Convert the column into JSON /// </summary> /// <param name="area">if null, everything, else columns having the restriction to the area</param> /// <param name="profile">if None, everything, else columns having the restriction to the profile</param> /// <returns></returns> public JObject ToJSON(string area, UserProfile.EUserProfile profile) { if (DSRestrictedAttribute.IsRestricted(Restriction, area, profile, null)) { return(null); } JObject result = new JObject { ["Field"] = Field, ["Property"] = Property.Name, ["Type"] = GetJSONTypeName(), ["DefaultValue"] = DefaultValue == null?JValue.CreateNull() : new JValue(DefaultValue), ["IsNullable"] = IsNullable, ["Formats"] = new JArray(Formats.Select(format => format.ToJSON()).ToArray()), ["Controls"] = new JArray(Controls.Select(control => control.ToJSON()).ToArray()), ["Constraints"] = new JArray(Constraints.Select(constraint => constraint.ToJSON()).ToArray()) }; return(result); }
/// <summary> /// Execute a service /// </summary> /// <param name="customerId"></param> /// <param name="userId"></param> /// <param name="profile"></param> /// <param name="area"></param> /// <param name="moduleId"></param> /// <param name="service"></param> /// <param name="record"></param> /// <param name="identity"></param> /// <returns>null if not currently treated or JObject with the result</returns> virtual public JObject ExecuteService(int customerId, int userId, UserProfile.EUserProfile profile, string area, int moduleId, string service, JObject record, JObject identity) { switch (service) { case "ReleaseNotes": return(ExecuteServiceReleaseNotes()); case "Sequence": if (record["Key"] != null && record["Key"].Type == JTokenType.String) { return(ExecuteServiceSequence(customerId, record["Key"].ToObject <string>())); } return(new JObject { ["Value"] = 0 }); case "SetSequence": if (record["Key"] != null && record["Key"].Type == JTokenType.String) { return(ExecuteServiceSetSequence(customerId, record["Key"].ToObject <string>(), record["Value"].ToObject <int>())); } return(new JObject { ["Value"] = 0 }); } return(null); }
/// <summary> /// Retrieve a list of tuple (DSRecord, Table) attached to a given record (table, id) for the given profile /// </summary> /// <param name="cache"></param> /// <param name="table"></param> /// <param name="id"></param> /// <param name="customerId"></param> /// <param name="userId"></param> /// <param name="profile"></param> /// <param name="area"></param> /// <param name="deepUpdate"></param> /// <param name="recordAlreadyRead"></param> /// <param name="informationAlreadyRead"></param> /// <returns></returns> public override void GetListRecordsConcernedByUpdate(DSCache cache, string table, int id, int customerId, int userId, UserProfile.EUserProfile profile, string area, bool deepUpdate, DSRecord recordAlreadyRead, InformationRecord informationAlreadyRead) { if (id < 0 || profile == UserProfile.EUserProfile.None || cache.Is(table, id) != null) { return; } // Retrieve the current record DSRecord currentRecord = null; Tuple <DSRecord, InformationRecord> currentRecordFromCache = cache.GetRecord(this, table, id); if (currentRecordFromCache == null && recordAlreadyRead == null) { switch (table) { case "Customer": currentRecord = Customer.Find(id); break; case "Language": currentRecord = Language.Find(id); break; case "User": currentRecord = User.Find(id); break; case "Module": currentRecord = Module.Find(id); break; case "UserModule": currentRecord = UserModule.Find(id); break; default: base.GetListRecordsConcernedByUpdate(cache, table, id, customerId, userId, profile, area, deepUpdate, recordAlreadyRead, informationAlreadyRead); return; } if (currentRecord == null) { cache.Set(table, id, null); return; } currentRecordFromCache = cache.SetRecord(this, table, id, currentRecord, null); } else if (currentRecordFromCache == null && recordAlreadyRead != null) { currentRecordFromCache = cache.SetRecord(this, table, id, recordAlreadyRead, informationAlreadyRead); } if (currentRecordFromCache != null) { currentRecord = currentRecordFromCache.Item1; } // Check if the record is concerned by the current update if (currentRecord is DSRecordWithCustomerId currentCustomerRecord) { if (currentCustomerRecord.CustomerId != customerId) { cache.Set(table, id, null); return; } } if (currentRecord is CustomerRecord customer) { cache.Set(table, id, customer.Id != customerId ? null : currentRecord); return; } if (currentRecord as LanguageRecord != null || currentRecord as ModuleRecord != null || currentRecord as UserModuleRecord != null || currentRecord as UserRecord != null) { cache.Set(table, id, currentRecord); return; } base.GetListRecordsConcernedByUpdate(cache, table, id, customerId, userId, profile, area, deepUpdate, recordAlreadyRead, informationAlreadyRead); }
/// <summary> /// Execute a request on the database schema (other than Create, Update or Delete) /// </summary> /// <param name="database"></param> /// <param name="customerId"></param> /// <param name="userId"></param> /// <param name="area"></param> /// <param name="profile"></param> /// <param name="table"></param> /// <param name="lot"></param> /// <returns></returns> public List <Tuple <DSRecord, InformationRecord> > ExecuteRequestCustom(DatabaseContext database, int customerId, int userId, string area, UserProfile.EUserProfile profile, string table, List <DSRequest> lot) { if (_request == null) { return(null); } List <Tuple <DSRecord, InformationRecord> > result = new List <Tuple <DSRecord, InformationRecord> >(); foreach (DSRequest request in lot) { int tick = request.NewTick; string action = request.Action; int id = request.RecordId; JObject record = request.Record; JObject identity = request.Identity; result.Add(_request.ExecuteRequest(database, tick, customerId, userId, area, profile, table, action, id, record, identity)); } return(result); }
/// <summary> /// Execute a request on the database schema (Create, Update or Delete a record for an existing table) /// on depends on the restriction view for the area and the profile /// </summary> /// <param name="database"></param> /// <param name="tick"></param> /// <param name="customerId"></param> /// <param name="userId"></param> /// <param name="area"></param> /// <param name="profile"></param> /// <param name="table"></param> /// <param name="action"></param> /// <param name="id"></param> /// <param name="record"></param> /// <returns></returns> public void OnAfterExecuteRequest(DatabaseContext database, int tick, int customerId, int userId, string area, UserProfile.EUserProfile profile, string table, string action, int id, DSRecord record) { if (!Tables.ContainsKey(table)) { throw new ExceptionDefinitionRecord("ERR_REQUEST_UNKNOWN"); } Tables[table].OnAfterExecuteRequest(database, tick, customerId, userId, area, profile, action, id, record); }
/// <summary> /// Retrieve for each row in the database for the given table, /// Each columns within only the expected columns due to the restriction in area and profile /// </summary> /// <param name="database"></param> /// <param name="table"></param> /// <param name="customerId"></param> /// <param name="userId"></param> /// <param name="profile"></param> /// <param name="area"></param> /// <param name="recordId"></param> /// <param name="existingRecords">Define it to replace the loading into the database</param> /// <returns></returns> public IEnumerable <object[]> ReadTable(DatabaseContext database, string table, int customerId, int userId, UserProfile.EUserProfile profile, string area, int?recordId, List <DSRecord> existingRecords) { if (!Tables.ContainsKey(table)) { return(Enumerable.Empty <object[]>()); } // Retrieve the list of columns to return return(Tables[table].ReadTable(database, customerId, userId, profile, area, recordId, existingRecords)); }
/// <summary> /// Convert a list of requests from the client into a transaction /// </summary> /// <param name="eventId"></param> /// <param name="connectionId"></param> /// <param name="customerId"></param> /// <param name="userId"></param> /// <param name="profile"></param> /// <param name="area"></param> /// <param name="moduleId"></param> /// <param name="requestId"></param> /// <param name="label"></param> /// <param name="requests">List of requests from the client</param> /// <param name="transaction"></param> /// <param name="notify"></param> public DSTransaction(int eventId, string connectionId, int customerId, int userId, UserProfile.EUserProfile profile, string area, int moduleId, int requestId, JObject label, JObject[] requests, bool transaction, bool notify) { EventId = eventId; ConnectionId = connectionId; CustomerId = customerId; UserId = userId; Profile = profile; Area = area; ModuleId = moduleId; RequestId = requestId; Label = label; CompressedRequests = requests; Transaction = transaction; Notify = notify; // Uncompress transaction int index = 0; Requests = new List <DSRequest>(); LotRequests = new List <List <DSRequest> >(); foreach (JObject request in requests) { // Retrieve the current request string table = null; if (request["table"] != null && request["table"].Type == JTokenType.String) { table = request["table"].ToObject <string>(); } string action = null; if (request["action"] != null && request["action"].Type == JTokenType.String) { action = request["action"].ToObject <string>(); } if (table == null || action == null || !(request["identity"] is JObject identity) || !(request["record"] is JObject record)) { Logger.LoggerManager.Instance.Error("DSTransaction", $"The request[{index}] isn't correctly formatted!"); throw new ExceptionDefinitionRecord("ERR_UNAUTHORIZED"); } // Build a request or a list of requests JToken tick = request["tick"]; int? tickValue = null; List <DSRequest> currentLot = new List <DSRequest>(); LotRequests.Add(currentLot); if (tick == null || tick.Type != JTokenType.Array) { if (tick != null && tick.Type != JTokenType.Undefined && tick.Type != JTokenType.Null) { tickValue = tick.ToObject <int>(); } DSRequest newRequest = new DSRequest(index, table, action, record, identity, tickValue, request, -1); Requests.Add(newRequest); currentLot.Add(newRequest); index++; continue; } JArray ticks = tick as JArray; for (int i = 0; i < ticks.Count; i++) { JObject transactionRecord; JObject transactionIdentity; if (ticks[i].Type != JTokenType.Undefined && ticks[i].Type != JTokenType.Null) { tickValue = ticks[i].ToObject <int>(); } if (action == "Update") { transactionRecord = new JObject(); transactionIdentity = new JObject(); transactionRecord["New"] = GetArrayIndex(record["New"] as JObject, i); transactionRecord["Old"] = GetArrayIndex(record["Old"] as JObject, i); transactionIdentity["New"] = GetArrayIndex(identity["New"] as JObject, i); transactionIdentity["Old"] = GetArrayIndex(identity["Old"] as JObject, i); } else { transactionRecord = GetArrayIndex(record, i); transactionIdentity = GetArrayIndex(identity, i); } DSRequest newRequest = new DSRequest(index, table, action, transactionRecord, transactionIdentity, tickValue, request, i); Requests.Add(newRequest); currentLot.Add(newRequest); index++; } } }
/// <summary> /// This function returns a record corresponding to the request /// If something is wrong, throw ExceptionDefinitionRecord("ERR_REQUEST_UNKNOWN") /// </summary> /// <param name="database"></param> /// <param name="tick"></param> /// <param name="customerId"></param> /// <param name="userId"></param> /// <param name="area"></param> /// <param name="profile"></param> /// <param name="table"></param> /// <param name="action"></param> /// <param name="id"></param> /// <param name="record"></param> /// <param name="identity"></param> /// <returns></returns> public Tuple <DSRecord, InformationRecord> ExecuteRequest(Common.Database.DatabaseContext database, int tick, int customerId, int userId, string area, UserProfile.EUserProfile profile, string table, string action, int id, JObject record, JObject identity) { if (table.Equals("User") && action.Equals("NewPassword") && record != null) { Info($"Executing request of new password for the user {record.ToString(Formatting.None)} ..."); int currentUserId = -1; // Retrieve the userId if (record["CustomerId"] == null || record["CustomerId"].Type != JTokenType.Integer || record["UserId"] == null || record["UserId"].Type != JTokenType.Integer) { throw new ExceptionDefinitionRecord("ERR_REQUEST_UPDATE_MISSING"); } if (record["CustomerId"].ToObject <int>() != customerId) { Error($"The record is defined for the customer '{record["CustomerId"].ToObject<int>()}' but it doesn't match with the user's customer '{customerId}' !"); throw new ExceptionDefinitionRecord("ERR_CONNECTION"); } currentUserId = record["UserId"].ToObject <int>(); if (currentUserId < 0) { if (identity["UserId"] == null || identity["UserId"].Type != JTokenType.Integer) { throw new ExceptionDefinitionRecord("ERR_REQUEST_UPDATE_MISSING"); } currentUserId = identity["UserId"].ToObject <int>(); if (currentUserId >= 0) { InformationRecord information = database._Information.FirstOrDefault(info => info.CreateId == currentUserId && info.CreateUserId == userId && info.Table.Equals(table)); if (information != null) { currentUserId = information.Id; } } } // check if the user has the right to send a new password if (currentUserId < 0 || (userId != currentUserId && profile != UserProfile.EUserProfile.Administrator)) { throw new ExceptionDefinitionRecord("ERR_UNAUTHORIZED"); } // send an email to change the password UserManager manager = new UserManager(database as Module.Administration.DatabaseContext); if (!(manager.GetById(currentUserId) is UserRecord currentUser)) { throw new ExceptionDefinitionRecord("ERR_UNAUTHORIZED"); } using (UserController controller = new UserController(manager)) return(Tuple.Create(controller.SendNewPassword(currentUser.Login) as DSRecord, database._Information.Find("User", currentUserId))); } throw new ExceptionDefinitionRecord("ERR_REQUEST_UNKNOWN"); }
/// <summary> /// This static function checks if the list of restrictions limits the access to the element /// on depends on the current area and the current profile /// </summary> /// <param name="allows"></param> /// <param name="area">Null, "Create", "Read", "Update" or "Delete"</param> /// <param name="profile"></param> /// <param name="action"></param> /// <param name="userId"></param> /// <param name="record"></param> public static bool IsAllowed(IEnumerable <DSAllowAttribute> allows, string area, UserProfile.EUserProfile profile, string action, int userId, DSRecord record) { // everything is allowed ? if (!allows.Any()) { return(true); } if (area == null && profile == UserProfile.EUserProfile.None) { return(true); } // if the record is null, not allowed! if (record == null) { return(false); } foreach (DSAllowAttribute allow in allows) { // By default: Nobody can access to this table if (allow.Area == null && allow.Profile == UserProfile.EUserProfile.None && allow.Action == null) { continue; } // Check area (if restriction.Area = "*" : allow all areas) if (allow.Area != null && !allow.Area.Equals("*") && area != null && !allow.Area.Equals(area)) { continue; } // Check profile if (allow.Profile != UserProfile.EUserProfile.None && profile != UserProfile.EUserProfile.None && !UserProfile.IsInRole(profile, allow.Profile)) { continue; } // Check action if (allow.Action != null && !allow.Action.Equals("*") && action != null && !allow.Action.Equals(action)) { continue; } // retrieve the field if it doesn't exist if (allow.FieldUserId != null) { int?currentUserId = allow.GetUserId(record); if (currentUserId == null || currentUserId.Value != userId) { continue; } } return(true); } return(false); }
/// <summary> /// This static function checks if the list of restrictions limits the access to the element /// on depends on the current area and the current profile /// </summary> /// <param name="restrictions"></param> /// <param name="area">Null, "Create", "Update" or "Delete"</param> /// <param name="profile"></param> /// <param name="action"></param> public static bool IsRestricted(IEnumerable <DSRestrictedAttribute> restrictions, string area, UserProfile.EUserProfile profile, string action) { // no restriction ? if (!restrictions.Any()) { return(false); } if (area == null && profile == UserProfile.EUserProfile.None) { return(false); } foreach (DSRestrictedAttribute restriction in restrictions) { // By default: Nobody can access to this table if (restriction.Area == null && restriction.Profile == UserProfile.EUserProfile.None && restriction.Action == null) { continue; } // Check area (if restriction.Area = "*" : allow all areas) if (restriction.Area != null && !restriction.Area.Equals("*") && area != null && !restriction.Area.Equals(area)) { continue; } // Check profile if (restriction.Profile != UserProfile.EUserProfile.None && profile != UserProfile.EUserProfile.None && !UserProfile.IsInRole(profile, restriction.Profile)) { continue; } // Check action if (restriction.Action != null && !restriction.Action.Equals("*") && action != null && !restriction.Action.Equals(action)) { continue; } return(false); } return(true); }