/// <summary>
        /// Runs a request. Caches it. Secures it.
        /// </summary>
        /// <param name="request">The entity member request string.</param>
        /// <returns></returns>
        public static IEnumerable <EntityData> GetEntitiesData(EntityRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }
            if (request.Entities == null)
            {
                throw new ArgumentNullException("request", "request.Entities cannot be null");
            }

            if (!request.EntityIDs.Any())
            {
                return(Enumerable.Empty <EntityData>());
            }

            // Check cache and/or get unsecured result
            BulkRequestResult unsecuredResult = BulkResultCache.GetBulkResult(request);

            // Bail out if we're not interested in cached results.
            if (request.DontProcessResultIfFromCache && request.ResultFromCache)
            {
                return(Enumerable.Empty <EntityData>());
            }

            // Secure the result
            IEnumerable <EntityData> results = BulkRequestResultConverter.BuildAndSecureResults(unsecuredResult, request.EntityIDs,
                                                                                                request.QueryType == QueryType.BasicWithDemand ? SecurityOption.DemandAll : SecurityOption.SkipDenied);

            return(results);
        }
 /// <summary>
 /// Constructor
 /// </summary>
 public CachingBulkRequestRunnerValue(BulkRequestResult bulkRequestResult)
 {
     if (bulkRequestResult == null)
     {
         throw new ArgumentNullException("bulkRequestResult");
     }
     BulkRequestResult = bulkRequestResult;
 }
Exemple #3
0
        /// <summary>
        /// Generates the query.
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        private BulkRequestResult CreateResult(EntityRequest request)
        {
            // Get/create a query object
            BulkSqlQuery query = BulkSqlQueryCache.GetBulkSqlQuery(request);

            // Run query on SQL server
            BulkRequestResult unsecuredResult = BulkRequestSqlRunner.RunQuery(query, request.EntityIDsCanonical);

            return(unsecuredResult);
        }
Exemple #4
0
        /// <summary>
        /// Read field values from the database result
        /// </summary>
        private static void ReadFieldValues(IDataReader reader, BulkSqlQuery query, BulkRequestResult result)
        {
            // select d.EntityId, d.FieldId, d.Data, [d.Namespace]
            while (reader.Read())
            {
                // Caution: We're in a database-read context, so don't touch the entity model or things will crash.

                // Load row
                long   entityId = reader.GetInt64(0);
                long   fieldId  = reader.GetInt64(1);
                object data     = reader.GetValue(2);

                // Get alias namespace
                if (data != null && reader.FieldCount == 4)
                {
                    string aliasNamespace = reader.GetString(3);
                    data = aliasNamespace + ":" + data;
                }

                // Get/convert the type info for the field
                FieldInfo fieldInfo;
                if (!query.FieldTypes.TryGetValue(fieldId, out fieldInfo))
                {
                    throw new InvalidOperationException("Assert false: encountered a field type in the result that was not part of the request.");
                }

                if (fieldInfo.IsWriteOnly)
                {
                    data = null;
                }

                // Prepare field value
                var typedValue = new TypedValue(DateTimeKind.Utc);
                typedValue.Type  = fieldInfo.DatabaseType;
                typedValue.Value = data;
                var fieldValue = new FieldValue(data, typedValue);

                // Add to dictionary
                var key = new FieldKey(entityId, fieldId);
                result.FieldValues [key] = fieldValue;
            }
        }
        /// <summary>
        /// Creates an entity ref for the ID to be checked.
        /// </summary>
        /// <remarks>
        /// The BulkRequestResult graph typically already has type information for the entities being loaded.
        /// The security engine requires type information to determine applicable rules.
        /// So instead of having it attempt to activate the entities (additional DB trips) to determine the types, just
        /// pass the type information along instead.
        /// This is done by creating a EntityTypeOnly implementation of IEntity, and packing it into the EntityRef that we pass.
        /// </remarks>
        /// <param name="data"></param>
        /// <param name="entityId"></param>
        /// <returns></returns>
        private static EntityRef CreateEntityRefForSecurityCheck(BulkRequestResult data, long entityId)
        {
            EntityRef result;
            long      isOfTypeRelId = WellKnownAliases.CurrentTenant.IsOfType;

            // Look up type information
            RelationshipKey key = new RelationshipKey(entityId, isOfTypeRelId);

            List <long> typeIds;

            if (data.Relationships.TryGetValue(key, out typeIds))
            {
                IEntity entity = new EntityTypeOnly(entityId, typeIds);
                result = new EntityRef(entity);
            }
            else
            {
                result = new EntityRef(entityId);
            }

            return(result);
        }
Exemple #6
0
        /// <summary>
        /// Remove duplicate entries from relationship lists.
        /// (that could result if two different nodes point to the same node along the same relationship)
        /// </summary>
        /// <param name="result"></param>
        private static void RemoveDuplicateRelationshipEntries(BulkRequestResult result)
        {
            // Note: this is a bit messy
            // Ideally we could remove the duplicates during the main ReadRelationships loop.
            // However, I don't want to return the result sorted, as it will unnecessarily slow down the SQL.
            // And I don't want to store individual HashSets in the object if I can help it.

            foreach (var pair in result.Relationships)
            {
                RelationshipKey key             = pair.Key;
                List <long>     targetEntityIds = pair.Value;

                // Only consolidate if there are multiple entries
                // (And do so, without recreating the list, otherwise we'll break our enumeration)
                if (targetEntityIds.Count > 1)
                {
                    HashSet <long> distinct = new HashSet <long>(targetEntityIds);
                    targetEntityIds.Clear( );
                    targetEntityIds.AddRange(distinct);
                }
            }
        }
Exemple #7
0
        /// <summary>
        /// Converts an unsecured BulkRequestResult to a secured EntityData structure.
        /// </summary>
        public static IEnumerable <EntityData> BuildAndSecureResults(BulkRequestResult data, IEnumerable <long> entities, SecurityOption securityOption)
        {
            // Get readability (security) for all entities in the unsecured result set
            var readability = BulkRequestResultSecurityHelper.GetEntityReadability(Factory.EntityAccessControlService, data);

            // Prepare for recursive walk
            var context = new Context
            {
                Readability  = readability,
                RawData      = data,
                BulkSqlQuery = data.BulkSqlQuery
            };

            if (securityOption == SecurityOption.DemandAll)
            {
                if (context.RawData.RootEntitiesList.Any(id => !context.Readability(id)))
                {
                    throw new PlatformSecurityException(
                              RequestContext.GetContext().Identity.Name,
                              new [] { Permissions.Read },
                              context.RawData.RootEntitiesList
                              .Where(id => !context.Readability(id))
                              .Select(id => new EntityRef(id)));
                }
            }
            else if (securityOption != SecurityOption.SkipDenied)
            {
                throw new ArgumentException(
                          string.Format("Unknown security option {0}", securityOption),
                          "securityOption");
            }

            IEnumerable <EntityData> result = GetRootEntities(context, entities);

            return(result);
        }
        /// <summary>
        /// Pre-fetches the can-read security for the list of entities.
        /// </summary>
        /// <param name="entityAccessControlService">The security service to use.</param>
        /// <param name="data">The data.</param>
        /// <returns>
        /// A predicate backed by a dictionary that can quickly return whether an entity is readable.
        /// </returns>
        public static Predicate <long> GetEntityReadability(IEntityAccessControlService entityAccessControlService, BulkRequestResult data)
        {
            if (entityAccessControlService == null)
            {
                throw new ArgumentNullException("entityAccessControlService");
            }

            Predicate <long> predicate;

            if (SecurityBypassContext.IsActive)
            {
                predicate = (long entityId) => true;
            }
            else
            {
                // Get readable entities
                List <EntityRef> entitiesToExplicitlyCheck = data.AllEntities
                                                             .Where(pair => !pair.Value.ImplicitlySecured)
                                                             .Select(pair => CreateEntityRefForSecurityCheck(data, pair.Key)) // Stop! If you change .Check to take longs, then discuss with Pete first.
                                                             .ToList();
                IDictionary <long, bool> readableEntities = entityAccessControlService.Check(entitiesToExplicitlyCheck, new [] { Permissions.Read });

                // Lookup predicate
                predicate = (long entityId) =>
                {
                    // Check if implicitly secured by relationship
                    EntityValue ev;
                    if (!data.AllEntities.TryGetValue(entityId, out ev))
                    {
                        return(false);   // assert false
                    }
                    if (ev.ImplicitlySecured)
                    {
                        return(true);
                    }

                    // Check if explicitly secured
                    bool canRead;
                    if (readableEntities.TryGetValue(entityId, out canRead))
                    {
                        return(canRead);
                    }
                    return(false);
                };
            }

            return(predicate);
        }
Exemple #9
0
 /// <summary>
 /// Extract the list of relationship types referenced in the query.
 /// </summary>
 /// <param name="result"></param>
 /// <returns></returns>
 private IEnumerable <long> GetRelationshipTypesUsed(BulkRequestResult result)
 {
     return(result.BulkSqlQuery.Relationships.Keys.Select(Math.Abs));
 }
Exemple #10
0
        /// <summary>
        /// Execute the query held in the BulkSqlQuery object for the specified entities, and capture the results in a fairly raw format.
        /// </summary>
        /// <param name="query">The query object.</param>
        /// <param name="entities">IDs of root-level entities to load. Must not contain duplicates.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">
        /// query
        /// or
        /// entities
        /// </exception>
        /// <exception cref="System.ArgumentException">No entities were loaded;entities</exception>
        public static BulkRequestResult RunQuery(BulkSqlQuery query, IEnumerable <long> entities)
        {
            if (query == null)
            {
                throw new ArgumentNullException("query");
            }
            if (entities == null)
            {
                throw new ArgumentNullException("entities");
            }

            var entitiesList = entities.ToList( );

            if (entitiesList.Count <= 0)
            {
                throw new ArgumentException("No entities were loaded", "entities");
            }

            var result = new BulkRequestResult();

            result.BulkSqlQuery = query;

            /////
            // HACK:TODO: 'LastLogin' is handled differently to all other fields on an entity. See UserAccountValidator
            /////
            long lastLogonId = WellKnownAliases.CurrentTenant.LastLogon;

            if (query.FieldTypes.ContainsKey(lastLogonId))
            {
                using (CacheContext cacheContext = new CacheContext( ))
                {
                    cacheContext.Entities.Add(lastLogonId);
                }
            }

            using (DatabaseContext ctx = DatabaseContext.GetContext())
                using (IDbCommand command = ctx.CreateCommand())
                {
                    // If single entity, then pass via parameter (to allow SQL to cache execution plan)
                    if (entitiesList.Count == 1)
                    {
                        ctx.AddParameter(command, "@entityId", DbType.Int64, entitiesList[0]);
                    }
                    else
                    {
                        command.AddIdListParameter("@entityIds", entitiesList);
                    }

                    ctx.AddParameter(command, "@tenantId", DbType.Int64, RequestContext.TenantId);

                    foreach (KeyValuePair <string, DataTable> tvp in query.TableValuedParameters)
                    {
                        command.AddTableValuedParameter(tvp.Key, tvp.Value);
                    }

                    command.CommandText = "dbo.spExecBulkRequest";
                    command.CommandType = CommandType.StoredProcedure;

                    using (IDataReader reader = command.ExecuteReader())
                    {
                        if (reader != null)
                        {
                            ReadRelationships(reader, query, result);
                            while (reader.NextResult())
                            {
                                ReadFieldValues(reader, query, result);
                            }
                        }
                    }
                }

            return(result);
        }
Exemple #11
0
        /// <summary>
        /// Read relationships, and top level entities, from the database result.
        /// </summary>
        private static void ReadRelationships(IDataReader reader, BulkSqlQuery query, BulkRequestResult result)
        {
            // select distinct EntityId, RelSrcId, RelTypeId from #process
            while (reader.Read())
            {
                // Caution: We're in a database-read context, so don't touch the entity model or things will crash.

                long toId          = reader.GetInt64(0);
                int  nodeTag       = reader.GetInt32(1);    // tag of the request node that returned this entity
                long fromId        = reader.GetInt64(2);    // zero for root-level entities
                long typeIdWithNeg = reader.GetInt64(3);    // relationship type-id, with reverse being indicated with negative values

                // Root result entity
                if (fromId == 0)
                {
                    result.RootEntities.Add(toId);
                }
                else
                {
                    // Add to dictionary
                    var key   = new RelationshipKey(fromId, typeIdWithNeg);
                    var value = toId;

                    List <long> list;
                    if (!result.Relationships.TryGetValue(key, out list))
                    {
                        list = new List <long>();
                        result.Relationships[key] = list;
                    }
                    list.Add(value);
                }

                // Implicit relationship security
                bool implicitlySecured = false;
                if (fromId != 0)
                {
                    var relInfo = query.Relationships[typeIdWithNeg];
                    implicitlySecured = relInfo.ImpliesSecurity;
                }

                // Store entity
                EntityValue ev;
                if (!result.AllEntities.TryGetValue(toId, out ev))
                {
                    ev = new EntityValue {
                        ImplicitlySecured = implicitlySecured
                    };
                    result.AllEntities[toId] = ev;
                }
                else
                {
                    ev.ImplicitlySecured = ev.ImplicitlySecured && implicitlySecured;
                }

                // Store the request node that specified members to load for this entity
                RequestNodeInfo requestNode = query.RequestNodes [nodeTag];
                ev.Nodes.Add(requestNode);
            }

#if DEBUG
            if (result.RootEntitiesList.Count != 0)
            {
                throw new InvalidOperationException("Assert false .. expected RootEntityList to be empty.");
            }
#endif
            result.RootEntitiesList.AddRange(result.RootEntities);

            RemoveDuplicateRelationshipEntries(result);
        }