private static string GetRootElementName(ODataPath path) { if (path != null) { ODataPathSegment lastSegment = path.Segments.LastOrDefault(); if (lastSegment != null) { OperationSegment actionSegment = lastSegment as OperationSegment; if (actionSegment != null) { IEdmAction action = actionSegment.Operations.Single() as IEdmAction; if (action != null) { return(action.Name); } } PropertySegment propertyAccessSegment = lastSegment as PropertySegment; if (propertyAccessSegment != null) { return(propertyAccessSegment.Property.Name); } } } return(null); }
// Determine action from readContext // This function is copied from default ODataActionPayloadDeserializer because it cannot be inherited private static IEdmAction GetAction(ODataDeserializerContext readContext) { if (readContext == null) { throw new ArgumentNullException(nameof(readContext)); } ODataPath path = readContext.Path; if (path == null || path.Segments.Count == 0) { throw new SerializationException("OData Path is missing"); } IEdmAction action = null; if (path.PathTemplate == "~/unboundaction") { // only one segment, it may be an unbound action OperationImportSegment unboundActionSegment = path.Segments.Last() as OperationImportSegment; if (unboundActionSegment != null) { IEdmActionImport actionImport = unboundActionSegment.OperationImports.First() as IEdmActionImport; if (actionImport != null) { action = actionImport.Action; } } } else { // otherwise, it may be a bound action OperationSegment actionSegment = path.Segments.Last() as OperationSegment; if (actionSegment != null) { action = actionSegment.Operations.First() as IEdmAction; } } if (action == null) { throw new SerializationException("Request is not an action invocation"); } return(action); }
// This function is used to determine whether an OData path includes operation (import) path segments. // We use this function to make sure the value of ODataUri.Path in ODataMessageWriterSettings is null // when any path segment is an operation. ODL will try to calculate the context URL if the ODataUri.Path // equals to null. private static bool IsOperationPath(ODataPath path) { if (path == null) { return(false); } foreach (ODataPathSegment segment in path.Segments) { if (segment is OperationSegment || segment is OperationImportSegment) { return(true); } } return(false); }
static bool IsNextSegmentOfType <T>(AspNet.OData.Routing.ODataPath path, int currentPos) { var maxPos = path.Segments.Count - 1; if (maxPos <= currentPos) { return(false); } var nextSegment = path.Segments[currentPos + 1]; if (nextSegment is T) { return(true); } if (nextSegment is TypeSegment && maxPos >= currentPos + 2 && path.Segments[currentPos + 2] is T) { return(true); } return(false); }
static bool IsNextSegmentKey(AspNet.OData.Routing.ODataPath path, int currentPos) { return(IsNextSegmentOfType <KeySegment>(path, currentPos)); }
internal static IScopesEvaluator ExtractPermissionsForRequest(this IEdmModel model, string method, AspNet.OData.Routing.ODataPath odataPath) { var template = odataPath.PathTemplate; ODataPathSegment prevSegment = null; var segments = new List <ODataPathSegment>(); // this combines the permission scopes across path segments // with a logical AND var permissionsChain = new WithAndScopesCombiner(); var lastSegmentIndex = odataPath.Segments.Count - 1; if (template.EndsWith("$ref", StringComparison.OrdinalIgnoreCase)) { // for ref segments, we apply the permission of the entity that contains the navigation property // e.g. for GET Customers(10)/Products/$ref, we apply the read key permissions of Customers // for GET TopCustomer/Products/$ref, we apply the read permissions of TopCustomer // for DELETE Customers(10)/Products(10)/$ref we apply the update permissions of Customers lastSegmentIndex = odataPath.Segments.Count - 2; while (!(odataPath.Segments[lastSegmentIndex] is KeySegment || odataPath.Segments[lastSegmentIndex] is SingletonSegment || odataPath.Segments[lastSegmentIndex] is NavigationPropertySegment) && lastSegmentIndex > 0) { lastSegmentIndex--; } } for (int i = 0; i <= lastSegmentIndex; i++) { var segment = odataPath.Segments[i]; if (segment is EntitySetSegment || segment is SingletonSegment || segment is NavigationPropertySegment || segment is OperationSegment || segment is OperationImportSegment || segment is KeySegment || segment is PropertySegment) { var parent = prevSegment; var isPropertyAccess = IsNextSegmentOfType <PropertySegment>(odataPath, i) || IsNextSegmentOfType <NavigationPropertyLinkSegment>(odataPath, i) || IsNextSegmentOfType <NavigationPropertySegment>(odataPath, i); prevSegment = segment; segments.Add(segment); // if nested segment, extract navigation restrictions of root // else extract entity/set restrictions if (segment is EntitySetSegment entitySetSegment) { // if Customers(key), then we'll handle it when we reach the key segment // so that we can properly handle ReadByKeyRestrictions if (IsNextSegmentKey(odataPath, i)) { continue; } // if Customers/UnboundFunction, then we'll handle it when we reach the operation segment if (IsNextSegmentOfType <OperationSegment>(odataPath, i)) { continue; } IScopesEvaluator permissions; permissions = GetNavigationPropertyCrudPermisions( segments, false, model, method); if (permissions is DefaultScopesEvaluator) { permissions = GetNavigationSourceCrudPermissions(entitySetSegment.EntitySet, model, method); } var handler = new WithOrScopesCombiner(permissions); permissionsChain.Add(handler); } else if (segment is SingletonSegment singletonSegment) { // if Customers/UnboundFunction, then we'll handle it when we reach the operation segment if (IsNextSegmentOfType <OperationSegment>(odataPath, i)) { continue; } if (isPropertyAccess) { var propertyPermissions = GetSingletonPropertyOperationPermissions(singletonSegment.Singleton, model, method); permissionsChain.Add(new WithOrScopesCombiner(propertyPermissions)); } else { var permissions = GetNavigationSourceCrudPermissions(singletonSegment.Singleton, model, method); permissionsChain.Add(new WithOrScopesCombiner(permissions)); } } else if (segment is KeySegment keySegment) { // if Customers/UnboundFunction, then we'll handle it when we reach the operation segment if (IsNextSegmentOfType <OperationSegment>(odataPath, i)) { continue; } var entitySet = keySegment.NavigationSource as IEdmEntitySet; var permissions = isPropertyAccess ? GetEntityPropertyOperationPermissions(entitySet, model, method) : GetEntityCrudPermissions(entitySet, model, method); var evaluator = new WithOrScopesCombiner(permissions); if (parent is NavigationPropertySegment) { var nestedPermissions = isPropertyAccess ? GetNavigationPropertyPropertyOperationPermisions(segments, isTargetByKey: true, model, method) : GetNavigationPropertyCrudPermisions(segments, isTargetByKey: true, model, method); evaluator.Add(nestedPermissions); } permissionsChain.Add(evaluator); } else if (segment is NavigationPropertySegment navSegment) { // if Customers/UnboundFunction, then we'll handle it when we reach there if (IsNextSegmentOfType <OperationSegment>(odataPath, i)) { continue; } // if Customers(key), then we'll handle it when we reach the key segment // so that we can properly handle ReadByKeyRestrictions if (IsNextSegmentKey(odataPath, i)) { continue; } var topLevelPermissions = GetNavigationSourceCrudPermissions(navSegment.NavigationSource as IEdmVocabularyAnnotatable, model, method); var segmentEvaluator = new WithOrScopesCombiner(topLevelPermissions); var nestedPermissions = GetNavigationPropertyCrudPermisions( segments, isTargetByKey: false, model, method); segmentEvaluator.Add(nestedPermissions); permissionsChain.Add(segmentEvaluator); } else if (segment is OperationImportSegment operationImportSegment) { var annotations = operationImportSegment.OperationImports.First().Operation.VocabularyAnnotations(model); var permissions = GetOperationPermissions(annotations); permissionsChain.Add(new WithOrScopesCombiner(permissions)); } else if (segment is OperationSegment operationSegment) { var annotations = operationSegment.Operations.First().VocabularyAnnotations(model); var operationPermissions = GetOperationPermissions(annotations); permissionsChain.Add(new WithOrScopesCombiner(operationPermissions)); } } } return(permissionsChain); }
/// <summary> /// Read and serialize outgoing object to HTTP request stream. /// </summary> internal static void WriteToStream( Type type, object value, IEdmModel model, Uri baseAddress, MediaTypeHeaderValue contentType, IUrlHelper internalUrlHelper, HttpRequest internalRequest, IHeaderDictionary internalRequestHeaders, Func <IServiceProvider, ODataMigrationMessageWrapper> getODataMessageWrapper, Func <IEdmTypeReference, ODataSerializer> getEdmTypeSerializer, Func <Type, ODataSerializer> getODataPayloadSerializer, Func <ODataSerializerContext> getODataSerializerContext) { if (model == null) { throw new InvalidOperationException("Request must have model"); } ODataSerializer serializer = GetSerializer(type, value, internalRequest, getEdmTypeSerializer, getODataPayloadSerializer); // special case: if the top level serializer is an ODataPrimitiveSerializer then swap it out for an ODataMigrationPrimitiveSerializer // This only applies to the top level because inline primitives are translated but top level primitives are not, unless we use a customized serializer. if (serializer is ODataPrimitiveSerializer) { serializer = new ODataMigrationPrimitiveSerializer(); } ODataPath path = internalRequest.ODataFeature().Path; IEdmNavigationSource targetNavigationSource = path == null ? null : path.NavigationSource; // serialize a response string preferHeader = GetRequestPreferHeader(internalRequestHeaders); string annotationFilter = null; if (!String.IsNullOrEmpty(preferHeader)) { ODataMigrationMessageWrapper messageWrapper = getODataMessageWrapper(null); messageWrapper.SetHeader("Prefer", preferHeader); annotationFilter = messageWrapper.PreferHeader().AnnotationFilter; } ODataMigrationMessageWrapper responseMessageWrapper = getODataMessageWrapper(internalRequest.GetRequestContainer()); IODataResponseMessage responseMessage = responseMessageWrapper; if (annotationFilter != null) { responseMessage.PreferenceAppliedHeader().AnnotationFilter = annotationFilter; } ODataMessageWriterSettings writerSettings = internalRequest.GetWriterSettings(); writerSettings.BaseUri = baseAddress; writerSettings.Version = ODataVersion.V4; // Todo how to specify v3? Maybe don't because reading as v4 writerSettings.Validations = writerSettings.Validations & ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType; string metadataLink = internalUrlHelper.CreateODataLink(MetadataSegment.Instance); if (metadataLink == null) { throw new SerializationException("Unable to determine metadata url"); } writerSettings.ODataUri = new ODataUri { ServiceRoot = baseAddress, SelectAndExpand = internalRequest.ODataFeature()?.SelectExpandClause, Apply = internalRequest.ODataFeature().ApplyClause, Path = (path == null || IsOperationPath(path)) ? null : path.Path, }; ODataMetadataLevel metadataLevel = ODataMetadataLevel.MinimalMetadata; if (contentType != null) { IEnumerable <KeyValuePair <string, string> > parameters = contentType.Parameters.Select(val => new KeyValuePair <string, string>(val.Name.Value, val.Value.Value)); metadataLevel = ODataMediaTypes.GetMetadataLevel(contentType.MediaType.Value, parameters); } using (ODataMessageWriter messageWriter = new ODataMessageWriter(responseMessage, writerSettings, model)) { ODataSerializerContext writeContext = getODataSerializerContext(); writeContext.NavigationSource = targetNavigationSource; writeContext.Model = model; writeContext.RootElementName = GetRootElementName(path) ?? "root"; writeContext.SkipExpensiveAvailabilityChecks = serializer.ODataPayloadKind == ODataPayloadKind.ResourceSet; writeContext.Path = path; writeContext.SelectExpandClause = internalRequest.ODataFeature()?.SelectExpandClause; writeContext.MetadataLevel = metadataLevel; // Substitute stream to swap @odata.context Stream substituteStream = new MemoryStream(); Stream originalStream = messageWriter.SubstituteResponseStream(substituteStream); serializer.WriteObject(value, type, messageWriter, writeContext); StreamReader reader = new StreamReader(substituteStream); substituteStream.Seek(0, SeekOrigin.Begin); JToken responsePayload = JToken.Parse(reader.ReadToEnd()); // If odata context is present, replace with odata metadata if (responsePayload["@odata.context"] != null) { responsePayload["odata.metadata"] = responsePayload["@odata.context"].ToString().Replace("$entity", "@Element"); ((JObject)responsePayload).Property("@odata.context").Remove(); } // Write to actual stream // We cannot dispose of the stream because this method does not own the stream (subsequent methods will close the streamwriter) StreamWriter streamWriter = new StreamWriter(originalStream); JsonTextWriter writer = new JsonTextWriter(streamWriter); JsonSerializer jsonSerializer = new JsonSerializer(); jsonSerializer.Serialize(writer, responsePayload); writer.Flush(); messageWriter.SubstituteResponseStream(originalStream); } }