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); }
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); }
/// <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; }
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); }
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; }
/// <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); }
/// <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); }