예제 #1
0
        private static object GetEntityResourceToModify(System.Data.Services.RequestDescription description, IDataService service, bool allowCrossReferencing, out ResourceSetWrapper entityContainer, out int entityResourceIndex)
        {
            if (!allowCrossReferencing && (description.RequestExpression == null))
            {
                throw DataServiceException.CreateBadRequestError(System.Data.Services.Strings.BadRequest_ResourceCanBeCrossReferencedOnlyForBindOperation);
            }
            entityResourceIndex = GetIndexOfEntityResourceToModify(description);
            entityContainer     = description.SegmentInfos[entityResourceIndex].TargetContainer;
            DataServiceHostWrapper host = service.OperationContext.Host;

            if (host.HttpVerb == HttpVerbs.PUT)
            {
                DataServiceConfiguration.CheckResourceRights(entityContainer, EntitySetRights.WriteReplace);
            }
            else if ((host.HttpVerb == HttpVerbs.MERGE) || (host.HttpVerb == HttpVerbs.PATCH))
            {
                DataServiceConfiguration.CheckResourceRights(entityContainer, EntitySetRights.WriteMerge);
            }
            else
            {
                DataServiceConfiguration.CheckResourceRights(entityContainer, EntitySetRights.WriteMerge | EntitySetRights.WriteReplace);
            }
            object obj2 = service.GetResource(description, entityResourceIndex, null);

            if (obj2 == null)
            {
                throw DataServiceException.CreateBadRequestError(System.Data.Services.Strings.BadRequest_DereferencingNullPropertyValue(description.SegmentInfos[entityResourceIndex].Identifier));
            }
            return(obj2);
        }
예제 #2
0
        internal object HandlePostRequest()
        {
            object targetResourceToBind;

            System.Data.Services.RequestDescription requestDescription = this.RequestDescription;
            if (requestDescription.LinkUri)
            {
                Uri referencedUri = (Uri)this.Deserialize(null);
                targetResourceToBind = this.GetTargetResourceToBind(referencedUri, true);
                HandleBindOperation(requestDescription, targetResourceToBind, this.Service, this.Tracker);
                return(targetResourceToBind);
            }
            if (requestDescription.LastSegmentInfo.TargetContainer != null)
            {
                DataServiceConfiguration.CheckResourceRights(requestDescription.LastSegmentInfo.TargetContainer, EntitySetRights.WriteAppend);
            }
            targetResourceToBind = this.ReadEntity();
            if (requestDescription.TargetSource == RequestTargetSource.Property)
            {
                HandleBindOperation(requestDescription, targetResourceToBind, this.Service, this.Tracker);
                return(targetResourceToBind);
            }
            this.Tracker.TrackAction(targetResourceToBind, requestDescription.LastSegmentInfo.TargetContainer, UpdateOperations.Add);
            return(targetResourceToBind);
        }
예제 #3
0
        /// <summary>
        /// Creates or gets an entity resource token instance based on the data from entry in the payload.
        /// The resource is then set on the entry annotation.
        /// </summary>
        /// <param name="segmentInfo">The segment info describing the entity in question.</param>
        /// <param name="entry">The OData entry instance read from the payload.</param>
        /// <param name="entryAnnotation">The entry annotation for the entry to process.</param>
        /// <param name="topLevel">true if this is a top-level entry, false otherwise.</param>
        private void CreateEntityResource(SegmentInfo segmentInfo, ODataEntry entry, ODataEntryAnnotation entryAnnotation, bool topLevel)
        {
            Debug.Assert(segmentInfo != null, "segmentInfo != null");
            Debug.Assert(entry != null, "Null entries should not be tried to translated to entity instances, instead they should be handled separately.");
            Debug.Assert(entryAnnotation != null, "entryAnnotation != null");

            // Note that this method does not call RecurseEnter and RecurseLeave.
            // It is going to be called when reading the top-level entry during entry reading (in which case it would count 1, and not fail anyway)
            // and then during entity materialization, but then we're going to call the ApplyEntityProperties on all the entities
            // and so we will count the recursion limits there instead.

            // Update the object count everytime you encounter a new resource.
            this.CheckAndIncrementObjectCount();

            // Get the type from the entry.
            ResourceType entityResourceType = this.GetEntryResourceType(entry, segmentInfo.TargetResourceType);

            Debug.Assert(entityResourceType != null && entityResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "Each entity must have an entity type.");

            // We have the actual type info from the payload. Update the request/response DSV based on the particular type.
            this.UpdateAndCheckRequestResponseDSV(entityResourceType, topLevel);

            if (segmentInfo.TargetKind == RequestTargetKind.OpenProperty)
            {
                // Open navigation properties are not supported on OpenTypes.
                throw DataServiceException.CreateBadRequestError(Microsoft.OData.Service.Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(segmentInfo.Identifier));
            }

            Debug.Assert(segmentInfo.TargetKind == RequestTargetKind.Resource, "Only resource targets can accept entity payloads.");

            object entityResource;

            if (this.Update)
            {
                Debug.Assert(topLevel, "Updates don't allow deep payload, only deep links.");

                // If it's a top level resource, then it cannot be null.
                // [Astoria-ODataLib-Integration] WCF DS Server doesn't check ETags if an ATOM payload entry has no content and no links (and it's a V1 entry)
                // We decided to break WCF DS and always check ETag - seems like a security issue.
                entityResource = this.GetObjectFromSegmentInfo(
                    entityResourceType,
                    segmentInfo,
                    true /*verifyETag*/,
                    true /*checkForNull*/,
                    this.Service.OperationContext.RequestMessage.HttpVerb == HttpVerbs.PUT /*replaceResource*/);
            }
            else
            {
                // Check for append rights whenever we need to create a resource.
                DataServiceConfiguration.CheckResourceRights(segmentInfo.TargetResourceSet, EntitySetRights.WriteAppend);

                // Create new instance of the entity.
                entityResource = this.Updatable.CreateResource(segmentInfo.TargetResourceSet.Name, entityResourceType.FullName);
            }

            entryAnnotation.EntityResource     = entityResource;
            entryAnnotation.EntityResourceType = entityResourceType;
        }
예제 #4
0
        internal static object GetResource(System.Data.Services.SegmentInfo segmentInfo, string fullTypeName, IDataService service, bool checkForNull)
        {
            if (segmentInfo.TargetContainer != null)
            {
                DataServiceConfiguration.CheckResourceRights(segmentInfo.TargetContainer, EntitySetRights.ReadSingle);
            }
            segmentInfo.RequestEnumerable = (IEnumerable)service.ExecutionProvider.Execute(segmentInfo.RequestExpression);
            object resource = service.Updatable.GetResource((IQueryable)segmentInfo.RequestEnumerable, fullTypeName);

            if ((resource == null) && (segmentInfo.HasKeyValues || checkForNull))
            {
                throw DataServiceException.CreateResourceNotFound(segmentInfo.Identifier);
            }
            return(resource);
        }
예제 #5
0
        private void CreateEntityResource(System.Data.Services.SegmentInfo segmentInfo, ODataEntry entry, ODataEntryAnnotation entryAnnotation, bool topLevel)
        {
            object obj2;

            base.CheckAndIncrementObjectCount();
            ResourceType entryResourceType = this.GetEntryResourceType(entry, segmentInfo.TargetResourceType);

            base.UpdateAndCheckRequestResponseDSV(entryResourceType, topLevel);
            if (segmentInfo.TargetKind == RequestTargetKind.OpenProperty)
            {
                throw DataServiceException.CreateBadRequestError(System.Data.Services.Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(segmentInfo.Identifier));
            }
            if (base.Update)
            {
                obj2 = base.GetObjectFromSegmentInfo(entryResourceType, segmentInfo, true, true, base.Service.OperationContext.Host.HttpVerb == HttpVerbs.PUT);
            }
            else
            {
                DataServiceConfiguration.CheckResourceRights(segmentInfo.TargetContainer, EntitySetRights.WriteAppend);
                obj2 = base.Updatable.CreateResource(segmentInfo.TargetContainer.Name, entryResourceType.FullName);
            }
            entryAnnotation.EntityResource     = obj2;
            entryAnnotation.EntityResourceType = entryResourceType;
        }
예제 #6
0
        /// <summary>
        /// Create the object given the list of the properties. One of the properties will be __metadata property
        /// which will contain type information
        /// </summary>
        /// <param name="jsonObject">list of the properties and values specified in the payload</param>
        /// <param name="segmentInfo">info about the object being created</param>
        /// <param name="topLevel">true if the current object is a top level one, otherwise false</param>
        /// <param name="existingRelationship">does this resource already binded to its parent</param>
        /// <returns>instance of the object created</returns>
        private object CreateObject(object jsonObject, SegmentInfo segmentInfo, bool topLevel, out bool existingRelationship)
        {
            this.RecurseEnter();

            existingRelationship = true;
            bool         existingResource = true;
            object       resource         = null;
            ResourceType resourceType;

            JsonReader.JsonObjectRecords jsonObjectRecord;

            if (topLevel)
            {
                // Every top level json content must be JsonObjectRecords - primitive, complex or entity
                jsonObjectRecord = jsonObject as JsonReader.JsonObjectRecords;
                if (jsonObjectRecord == null)
                {
                    throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidResourceEntity);
                }

                object nonEntityResource;
                if (HandleTopLevelNonEntityProperty(jsonObjectRecord, segmentInfo, out nonEntityResource))
                {
                    // if the segment refers to primitive type, then return the value
                    if (segmentInfo.TargetKind == RequestTargetKind.Primitive ||
                        nonEntityResource == null ||
                        (segmentInfo.TargetKind == RequestTargetKind.OpenProperty && WebUtil.IsPrimitiveType(nonEntityResource.GetType())))
                    {
                        return(nonEntityResource);
                    }

                    jsonObject = nonEntityResource;
                }
            }
            else if (
                jsonObject == null ||
                (segmentInfo.TargetKind == RequestTargetKind.OpenProperty && WebUtil.IsPrimitiveType(jsonObject.GetType())) ||
                segmentInfo.TargetKind == RequestTargetKind.Primitive)
            {
                // For reference properties, we do not know if there was already some relationship setup
                // By setting it to null, we are unbinding the old relationship and hence existing relationship
                // is false
                // For open properties, if its null, there is no way we will be able to deduce the type
                existingRelationship = false;
                return(jsonObject);
            }

            // Otherwise top level json content must be JsonObjectRecords, since we don't allow multiple inserts
            // at the top level
            jsonObjectRecord = jsonObject as JsonReader.JsonObjectRecords;
            if (jsonObjectRecord == null)
            {
                throw DataServiceException.CreateBadRequestError(Strings.BadRequestStream_InvalidResourceEntity);
            }

            ResourceType targetResourceType = null;

            if (segmentInfo.TargetKind != RequestTargetKind.OpenProperty)
            {
                targetResourceType = segmentInfo.TargetResourceType;
                Debug.Assert(targetResourceType != null, "Should be able to resolve type for well known segments");
                Debug.Assert(
                    targetResourceType.ResourceTypeKind == ResourceTypeKind.ComplexType || targetResourceType.ResourceTypeKind == ResourceTypeKind.EntityType,
                    "targetType must be entity type or complex type");
            }

            // Get the type and uri from the metadata element, if specified
            string uri;
            bool   metadataElementSpecified;

            resourceType = this.GetTypeAndUriFromMetadata(
                jsonObjectRecord.Entries,
                targetResourceType,
                topLevel,
                out uri,
                out metadataElementSpecified);

            Debug.Assert((resourceType != null && resourceType.ResourceTypeKind != ResourceTypeKind.Primitive) || uri != null, "Either uri or resource type must be specified");

            if ((uri != null || resourceType.ResourceTypeKind == ResourceTypeKind.EntityType) && segmentInfo.TargetKind == RequestTargetKind.OpenProperty)
            {
                // Open navigation properties are not supported on OpenTypes.
                throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(segmentInfo.Identifier));
            }

            this.CheckAndIncrementObjectCount();
            if ((resourceType != null && resourceType.ResourceTypeKind != ResourceTypeKind.ComplexType) ||
                uri != null)
            {
                // For inserts/updates, its okay not to specify anything in the payload.
                // Someone might just want to create a entity with default values or
                // merge nothing or replace everything with default values.
                if (this.Update)
                {
                    if (!topLevel)
                    {
                        if (metadataElementSpecified && jsonObjectRecord.Count > 1 ||
                            !metadataElementSpecified)
                        {
                            throw DataServiceException.CreateBadRequestError(Strings.BadRequest_DeepUpdateNotSupported);
                        }
                        else if (uri == null)
                        {
                            throw DataServiceException.CreateBadRequestError(Strings.BadRequest_UriMissingForUpdateForDeepUpdates);
                        }
                    }

                    if (topLevel)
                    {
                        // Checking for merge vs replace semantics
                        // Only checking for top level resource entity
                        // since we don't support update of deep resources
                        resource = GetObjectFromSegmentInfo(
                            resourceType,
                            segmentInfo,
                            true /*checkETag*/,
                            true /*checkForNull*/,
                            this.Service.OperationContext.Host.AstoriaHttpVerb == AstoriaVerbs.PUT /*replaceResource*/);
                    }
                    else
                    {
                        // case of binding at the first level.
                        existingRelationship = false;
                        return(this.GetTargetResourceToBind(uri, false /*checkNull*/));
                    }
                }
                else
                {
                    // For insert, its a new resource that is getting created or an existing resource
                    // getting binded. Either case, its a new relationship.
                    existingRelationship = false;

                    // For POST operations, the following rules holds true:
                    // 1> If the uri is specified for navigation properties and no other property is specified, then its a bind operation.
                    // Otherwise, ignore the uri and insert the new resource.
                    if (uri != null)
                    {
                        if (segmentInfo.TargetSource == RequestTargetSource.Property && jsonObjectRecord.Count == 1)
                        {
                            this.RecurseLeave();
                            return(this.GetTargetResourceToBind(uri, false /*checkNull*/));
                        }
                    }
                }
            }

            Debug.Assert(resourceType != null, "resourceType != null");
            if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType)
            {
                Debug.Assert(resource == null, "resource == null");
                resource         = this.Updatable.CreateResource(null, resourceType.FullName);
                existingResource = false;
            }
            else if (!this.Update)
            {
                Debug.Assert(resource == null, "resource == null");
                if (segmentInfo.TargetKind == RequestTargetKind.Resource)
                {
                    // check for append rights whenever we need to create a resource
                    DataServiceConfiguration.CheckResourceRights(segmentInfo.TargetContainer, EntitySetRights.WriteAppend);

                    resource = this.Updatable.CreateResource(segmentInfo.TargetContainer.Name, resourceType.FullName);

                    // If resourceType is FF mapped with KeepInContent=false and the response format is Atom, we need to raise the response DSV version
                    // Note that we only need to do this for POST since PUT responds with 204 and DSV=1.0
                    //
                    // Errr, mismatching request and response formats don't meet the bar at this point, commenting out the fix...
                    //
                    //// this.UpdateAndCheckEpmRequestResponseDSV(resourceType, topLevel);

                    this.Tracker.TrackAction(resource, segmentInfo.TargetContainer, UpdateOperations.Add);
                }
                else
                {
                    Debug.Assert(segmentInfo.TargetKind == RequestTargetKind.OpenProperty, "segmentInfo.TargetKind == RequestTargetKind.OpenProperty");

                    // Open navigation properties are not supported on OpenTypes.
                    throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(segmentInfo.Identifier));
                }

                existingResource = false;
            }

            bool changed = this.PopulateProperties(jsonObjectRecord, resource, segmentInfo.TargetContainer, resourceType);

            // For put operations, you need not specify any property and that means reset all the properties.
            // hence for put operations, change is always true.
            changed = changed || this.Service.OperationContext.Host.AstoriaHttpVerb == AstoriaVerbs.PUT;
            if (changed && existingResource && segmentInfo.TargetContainer != null)
            {
                this.Tracker.TrackAction(resource, segmentInfo.TargetContainer, UpdateOperations.Change);
            }

            this.RecurseLeave();
            return(resource);
        }
예제 #7
0
        /// <summary>Reads the current object from the <paramref name="item"/>.</summary>
        /// <param name="segmentInfo">segmentinfo containing information about the current element that is getting processes</param>
        /// <param name="topLevel">true if the element currently pointed by the xml reader refers to a top level element</param>
        /// <param name="item">Item to read from.</param>
        /// <returns>returns the clr object with the data populated</returns>
        private object CreateObject(SegmentInfo segmentInfo, bool topLevel, SyndicationItem item)
        {
            Debug.Assert(item != null, "item != null");
            Debug.Assert(topLevel || !this.Update, "deep updates not supported");

            this.RecurseEnter();
            object result;

            // update the object count everytime you encounter a new resource
            this.CheckAndIncrementObjectCount();

            // Process the type annotation.
            ResourceType currentResourceType = this.GetResourceType(item, segmentInfo.TargetResourceType);

            if (currentResourceType.ResourceTypeKind != ResourceTypeKind.EntityType)
            {
                throw DataServiceException.CreateBadRequestError(Strings.BadRequest_OnlyEntityTypesMustBeSpecifiedInEntryElement(currentResourceType.FullName));
            }

            // We have the actual type info from the payload. Update the request/response DSV if any property is FF mapped with KeepInContent=false.
            this.UpdateAndCheckEpmRequestResponseDSV(currentResourceType, topLevel);

            // Get a resource cookie from the provider.
            ResourceSetWrapper container;

            if (segmentInfo.TargetKind == RequestTargetKind.OpenProperty)
            {
                // Open navigation properties are not supported on OpenTypes.
                throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(segmentInfo.Identifier));
            }
            else
            {
                Debug.Assert(segmentInfo.TargetKind == RequestTargetKind.Resource, "segmentInfo.TargetKind == RequestTargetKind.Resource");
                container = segmentInfo.TargetContainer;
            }

            DataServiceHostWrapper host = this.Service.OperationContext.Host;

            if (this.Update)
            {
                Debug.Assert(currentResourceType.ResourceTypeKind == ResourceTypeKind.EntityType, "only expecting entity types");

                // Only verify ETag if there is going to be some update applied (that's the idea)
                // In reality:
                //   - for normal entities (V1 compatible) - don't check ETags if there is no content element. (Same as in V1)
                //   - for V2 stuff - check ETags always as we can't tell if there's going to be something modified or not
                //       with EPM properties can be anywhere in the payload and thus even without content there still can be updates
                //       with MLE the properties are not in the content element but in their own element
                // It's hard to recognize if there's going to be update up front and so this below is an approximation
                //   which seems to be good enough. Note that if we add new ways of handling properties in the content
                //   the condition below might need to change.
                bool verifyETag =
                    topLevel &&
                    (HasContent(item) || currentResourceType.HasEntityPropertyMappings || currentResourceType.IsMediaLinkEntry);
                bool replaceResource = topLevel && host.AstoriaHttpVerb == AstoriaVerbs.PUT;

                // if its a top level resource, then it cannot be null
                result = this.GetObjectFromSegmentInfo(currentResourceType, segmentInfo, verifyETag, topLevel /*checkForNull*/, replaceResource);
                if (this.Tracker != null)
                {
                    this.Tracker.TrackAction(result, container, UpdateOperations.Change);
                }
            }
            else
            {
                if (segmentInfo.TargetKind == RequestTargetKind.Resource)
                {
                    DataServiceConfiguration.CheckResourceRights(segmentInfo.TargetContainer, EntitySetRights.WriteAppend);
                }

                result = this.Updatable.CreateResource(container.Name, currentResourceType.FullName);
                if (this.Tracker != null)
                {
                    this.Tracker.TrackAction(result, container, UpdateOperations.Add);
                }
            }

            // Process the content in the entry.
            EpmContentDeSerializer.EpmAppliedPropertyInfo propertiesApplied = new EpmContentDeSerializer.EpmAppliedPropertyInfo();
            this.ApplyProperties(item, currentResourceType, propertiesApplied, result);

            // Perform application of epm properties here
            if (currentResourceType.HasEntityPropertyMappings)
            {
                new EpmContentDeSerializer(currentResourceType, result).DeSerialize(
                    item,
                    new EpmContentDeSerializer.EpmContentDeserializerState {
                    IsUpdateOperation = this.Update, Updatable = this.Updatable, Service = this.Service, PropertiesApplied = propertiesApplied
                });
            }

            // Process the links in the entry.
            foreach (SyndicationLink link in item.Links)
            {
                string navigationPropertyName = UriUtil.GetNameFromAtomLinkRelationAttribute(link.RelationshipType);

                if (null == navigationPropertyName)
                {
                    continue;
                }

                Deserializer.CheckForBindingInPutOperations(host.AstoriaHttpVerb);
                Debug.Assert(segmentInfo.TargetContainer != null, "segmentInfo.TargetContainer != null");
                this.ApplyLink(link, segmentInfo.TargetContainer, currentResourceType, result, navigationPropertyName);
            }

            this.RecurseLeave();
            return(result);
        }