Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Adds support for RequiresExplicitBinding and ExplicitOperationBindings annotations for operations #378

Merged
merged 11 commits into from
May 2, 2023
Merged
25 changes: 25 additions & 0 deletions src/Microsoft.OpenApi.OData.Reader/Common/EdmModelHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Runtime;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Csdl;
using Microsoft.OData.Edm.Vocabularies;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Edm;
using Microsoft.OpenApi.OData.Vocabulary.Capabilities;
Expand Down Expand Up @@ -391,5 +392,29 @@ internal static string StripOrAliasNamespacePrefix(IEdmSchemaElement element, Op

return segmentName;
}

/// <summary>
/// Checks whether an operation is allowed on a model element.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="edmOperation">The target operation.</param>
/// <param name="annotatable">The model element.</param>
/// <returns>true if the operation is allowed, otherwise false.</returns>
internal static bool IsOperationAllowed(IEdmModel model, IEdmOperation edmOperation, IEdmVocabularyAnnotatable annotatable)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(edmOperation, nameof(edmOperation));
Utils.CheckArgumentNull(annotatable, nameof(annotatable));

var requiresExplicitBinding = model.FindVocabularyAnnotations(edmOperation).FirstOrDefault(x => x.Term.Name == CapabilitiesConstants.RequiresExplicitBindingName);

if (requiresExplicitBinding == null)
{
return true;
}

var boundOperations = model.GetCollection(annotatable, CapabilitiesConstants.ExplicitOperationBindings)?.ToList();
return boundOperations != null && boundOperations.Contains(edmOperation.FullName());
}
}
}
34 changes: 32 additions & 2 deletions src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class ODataPathProvider : IODataPathProvider
private readonly IDictionary<IEdmEntityType, IList<ODataPath>> _dollarCountPaths =
new Dictionary<IEdmEntityType, IList<ODataPath>>();


/// <summary>
/// Can filter the <see cref="IEdmElement"/> or not.
/// </summary>
Expand Down Expand Up @@ -728,7 +729,7 @@ private void RetrieveBoundOperationPaths(OpenApiConvertSettings convertSettings)
}

var firstEntityType = bindingType.AsEntity().EntityDefinition();

bool filter(IEdmNavigationSource z) =>
z.EntityType() != firstEntityType &&
z.EntityType().FindAllBaseTypes().Contains(firstEntityType);
Expand Down Expand Up @@ -790,6 +791,20 @@ secondLastPathSegment is not ODataKeySegment &&
{
if (lastPathSegment is ODataTypeCastSegment && !convertSettings.AppendBoundOperationsOnDerivedTypeCastSegments) continue;
if (lastPathSegment is ODataKeySegment segment && segment.IsAlternateKey) continue;

var annotatable = (lastPathSegment as ODataNavigationSourceSegment)?.NavigationSource as IEdmVocabularyAnnotatable;
annotatable ??= (lastPathSegment as ODataKeySegment)?.EntityType;

if (annotatable != null && !EdmModelHelper.IsOperationAllowed(_model, edmOperation, annotatable))
{
// Check whether the navigation source is allowed to have an operation on the entity type
annotatable = (secondLastPathSegment as ODataNavigationSourceSegment)?.NavigationSource as IEdmVocabularyAnnotatable;
if (annotatable != null && !EdmModelHelper.IsOperationAllowed(_model, edmOperation, annotatable))
{
continue;
}
}

ODataPath newPath = subPath.Clone();
newPath.Push(new ODataOperationSegment(edmOperation, isEscapedFunction, _model));
AppendPath(newPath);
Expand All @@ -814,6 +829,11 @@ private void AppendBoundOperationOnNavigationPropertyPath(IEdmOperation edmOpera
{
continue;
}

if (!EdmModelHelper.IsOperationAllowed(_model, edmOperation, npSegment.NavigationProperty))
{
continue;
}

bool isLastKeySegment = path.LastSegment is ODataKeySegment;

Expand Down Expand Up @@ -866,6 +886,11 @@ private void AppendBoundOperationOnDerived(
continue;
}

if (!EdmModelHelper.IsOperationAllowed(_model, edmOperation, ns as IEdmVocabularyAnnotatable))
{
continue;
}

if (isCollection)
{
if (ns is IEdmEntitySet)
Expand Down Expand Up @@ -934,6 +959,11 @@ private void AppendBoundOperationOnDerivedNavigationPropertyPath(
continue;
}

if (!EdmModelHelper.IsOperationAllowed(_model, edmOperation, npSegment.NavigationProperty))
{
continue;
}

bool isLastKeySegment = path.LastSegment is ODataKeySegment;

if (isCollection)
Expand All @@ -958,7 +988,7 @@ private void AppendBoundOperationOnDerivedNavigationPropertyPath(
}

if (HasUnsatisfiedDerivedTypeConstraint(
npSegment.NavigationProperty as IEdmVocabularyAnnotatable,
npSegment.NavigationProperty,
baseType,
convertSettings))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<PackageId>Microsoft.OpenApi.OData</PackageId>
<SignAssembly>true</SignAssembly>
<Version>1.4.0-preview6</Version>
<Version>1.4.0-preview7</Version>
<Description>This package contains the codes you need to convert OData CSDL to Open API Document of Model.</Description>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<PackageTags>Microsoft OpenApi OData EDM</PackageTags>
Expand All @@ -27,6 +27,7 @@
- Use directly annotated CountRestriction annotations when creating $count segments for collection-valued navigation properties #328
- Use MediaType annotation to set the content types of operations with Edm.Stream return types #342
- Retrieves navigation properties from base types #371
- Adds support for RequiresExplicitBinding and ExplicitOperationBindings annotations for operations #323 #232
</PackageReleaseNotes>
<AssemblyName>Microsoft.OpenApi.OData.Reader</AssemblyName>
<AssemblyOriginatorKeyFile>..\..\tool\Microsoft.OpenApi.OData.snk</AssemblyOriginatorKeyFile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,15 @@ internal class CapabilitiesConstants
/// Org.OData.Capabilities.V1.KeyAsSegmentSupported
/// </summary>
public const string KeyAsSegmentSupported = "Org.OData.Capabilities.V1.KeyAsSegmentSupported";

/// <summary>
/// RequiresExplicitBinding
/// </summary>
public const string RequiresExplicitBindingName = "RequiresExplicitBinding";

/// <summary>
/// Org.OData.Capabilities.V1.ExplicitOperationBindings
/// </summary>
public const string ExplicitOperationBindings = "Org.OData.Core.V1.ExplicitOperationBindings";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void GetPathsForGraphBetaModelReturnsAllPaths()

// Assert
Assert.NotNull(paths);
Assert.Equal(18409, paths.Count());
Assert.Equal(18264, paths.Count());
AssertGraphBetaModelPaths(paths);
}

Expand All @@ -70,6 +70,19 @@ private void AssertGraphBetaModelPaths(IEnumerable<ODataPath> paths)

// Test that navigation properties on base types are created
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/print/printers({id})/jobs")));

// Test that RequiresExplicitBinding and ExplicitOperationBindings annotations work
Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.checkMemberGroups")));
Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.checkMemberObjects")));
Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.getMemberGroups")));
Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.getMemberObjects")));
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directory/deletedItems({id})/microsoft.graph.restore")));

Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.checkMemberGroups")));
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.checkMemberObjects")));
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.getMemberGroups")));
Assert.NotNull(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.getMemberObjects")));
Assert.Null(paths.FirstOrDefault(p => p.GetPathItemName().Equals("/directoryObjects({id})/microsoft.graph.restore")));
}

[Fact]
Expand All @@ -90,7 +103,7 @@ public void GetPathsForGraphBetaModelWithDerivedTypesConstraintReturnsAllPaths()

// Assert
Assert.NotNull(paths);
Assert.Equal(19060, paths.Count());
Assert.Equal(18915, paths.Count());
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53506,6 +53506,11 @@
<String>microsoft.graph.group</String>
<String>microsoft.graph.application</String>
</Collection>
</Annotation>
<Annotation Term="Org.OData.Core.V1.ExplicitOperationBindings">
<Collection>
<String>microsoft.graph.restore</String>
</Collection>
</Annotation>
</NavigationProperty>
<NavigationProperty Name="federationConfigurations" Type="Collection(graph.identityProviderBase)" ContainsTarget="true">
Expand Down Expand Up @@ -103443,6 +103448,7 @@
</Annotation>
</Action>
<Action Name="restore" IsBound="true">
<Annotation Term="Org.OData.Core.V1.RequiresExplicitBinding"/>
<Parameter Name="bindingParameter" Type="graph.directoryObject" Nullable="false" />
<ReturnType Type="graph.directoryObject" />
<Annotation Term="Org.OData.Capabilities.V1.InsertRestrictions">
Expand Down Expand Up @@ -104558,6 +104564,7 @@
<ReturnType Type="graph.exactMatchSession" />
</Action>
<Action Name="checkMemberGroups" IsBound="true">
<Annotation Term="Org.OData.Core.V1.RequiresExplicitBinding"/>
<Parameter Name="bindingParameter" Type="graph.directoryObject" Nullable="false" />
<Parameter Name="groupIds" Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
<ReturnType Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
Expand All @@ -104577,11 +104584,13 @@
</Annotation>
</Action>
<Action Name="checkMemberObjects" IsBound="true">
<Annotation Term="Org.OData.Core.V1.RequiresExplicitBinding"/>
<Parameter Name="bindingParameter" Type="graph.directoryObject" Nullable="false" />
<Parameter Name="ids" Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
<ReturnType Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
</Action>
<Action Name="getMemberGroups" IsBound="true">
<Annotation Term="Org.OData.Core.V1.RequiresExplicitBinding"/>
<Parameter Name="bindingParameter" Type="graph.directoryObject" Nullable="false" />
<Parameter Name="securityEnabledOnly" Type="Edm.Boolean" />
<ReturnType Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
Expand All @@ -104601,6 +104610,7 @@
</Annotation>
</Action>
<Action Name="getMemberObjects" IsBound="true">
<Annotation Term="Org.OData.Core.V1.RequiresExplicitBinding"/>
<Parameter Name="bindingParameter" Type="graph.directoryObject" Nullable="false" />
<Parameter Name="securityEnabledOnly" Type="Edm.Boolean" />
<ReturnType Type="Collection(Edm.String)" Nullable="false" Unicode="false" />
Expand Down Expand Up @@ -108921,7 +108931,7 @@
</Action>
<Action Name="stop" IsBound="true">
<Parameter Name="bindingParameter" Type="graph.accessReviewInstance" />
<Annotation Term="Org.OData.Capabilities.V1.InsertRestrictions">
<Annotation Term="Org.OData.Capabilities.V1.InsertRestrictions">
<Record>
<PropertyValue Property="Description" String="Stop accessReviewInstance" />
<PropertyValue Property="LongDescription" String="Stop a currently active accessReviewInstance. After the access review instance stops, the instance status will be `Completed`, the reviewers can no longer give input, and the access review decisions can be applied. Stopping an instance will not effect future instances. To prevent a recurring access review from starting future instances, update the schedule definition to change its scheduled end date." />
Expand Down Expand Up @@ -109111,7 +109121,7 @@
</Annotation>
</Action>
<Action Name="sendReminder" IsBound="true">
<Parameter Name="bindingParameter" Type="graph.accessReviewInstance" />
<Parameter Name="bindingParameter" Type="graph.accessReviewInstance" />
<Annotation Term="Org.OData.Capabilities.V1.InsertRestrictions">
<Record>
<PropertyValue Property="Description" String="accessReviewInstance: sendReminder" />
Expand Down Expand Up @@ -112362,7 +112372,17 @@
</Term>
<Term Name="teamCreationMode" Type="Edm.String" AppliesTo="microsoft.graph.team">
<Annotation Term="Org.OData.Core.V1.LongDescription" String="Indicates that the team is in migration state and is currently being used for migration purposes. It accepts one value: migration. Note: In the future, Microsoft may require you or your customers to pay additional fees based on the amount of data imported." />
</Term>
</Term>
<Annotations Target="microsoft.graph.GraphService/directoryObjects">
<Annotation Term="Org.OData.Core.V1.ExplicitOperationBindings">
<Collection>
<String>microsoft.graph.checkMemberGroups</String>
<String>microsoft.graph.checkMemberObjects</String>
<String>microsoft.graph.getMemberGroups</String>
<String>microsoft.graph.getMemberObjects</String>
</Collection>
</Annotation>
</Annotations>
<Annotations Target="microsoft.graph.user/drives">
<Annotation Term="Org.OData.Capabilities.V1.CountRestrictions">
<Record>
Expand Down