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

Filter operations on OpenAPI and GraphQL Schema #3018

Merged
merged 11 commits into from
Jul 16, 2023

Conversation

justin-tay
Copy link
Collaborator

Resolves #2641

I wasn't sure what to use to determine whether or not to expose operations by id for OpenAPI so it's currently using Permission annotations on the id which is rather odd.

Description

  • For OpenAPI document filter paths based on Permission annotations on the entity / id
  • For GraphQL schema add per entity RelationshipOp enum with the valid values
  • Update TableType to only expose the get collection endpoint on OpenAPI

Motivation and Context

More accurate documentation on what operations are supported on the backend.

How Has This Been Tested?

Added unit tests.

License

I confirm that this contribution is made under an Apache 2.0 license and that I have the authority necessary to make this contribution on behalf of its copyright owner.

@justin-tay justin-tay marked this pull request as draft June 22, 2023 08:17
case FETCH:
return canRead(clazz);
case DELETE:
return canDelete(clazz);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would require UpdatePermission - not Delete.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to UpdatePermission

@@ -16,7 +18,7 @@
/**
* Assign custom Delete permission checks.
*/
@Target({TYPE, PACKAGE})
@Target({TYPE, PACKAGE, FIELD, METHOD})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no way to Delete a field or method in JSON-API. You can update relationships (by deleting elements) but that is an UpdatePermission. I recommend we continue to use UpdatePermission for relationship management.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed these changes. This was actually done so that the DeletePermission could be applied to the id field to filter off the operations by id on OpenAPI which I think was requested for HJSON as I think those endpoints to query by id don't work. I have changed it to use the Exclude annotation instead.

}

GraphQLEnumType enumResult = builder.build();

enumConversions.put(enumClazz, enumResult);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we remove this line?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree this method is a bit confusing. From what I gather it's not possible to set a graphql enum and indicate that only a subset of values are acceptable. Instead I needed to create a graphql enum per entity. eg. instead of just ElideRelationshipOp I needed to create one per entity, eg. ElideRelationshipOpDownloads, ElideRelationshipOpGroup, ElideRelationshipOpProduct and then indicate the acceptable values for each. In this case the enums needed to be deduplicated based on the graphql enum name and not the enum type. I have changed this to create a specific method for this use case instead of trying to modify the existing method to cater to this.

return existing;
}

String entityName = entityDictionary.getJsonAliasFor(clazz);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really confused what this code is doing. It looks like it is changing the value of relationship op to include the entity name. I'm not following the intent here, but we don't want to change the api contract. I also feel like we really need two distinct methods here - one to get the relationship ops for a root level entity and one to get the relationship ops for a relationship.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually not that clear on the logic for determining the relationship ops from the permissions for graphql. Is there a difference in allowable values in the relationship ops for the root entity vs the relationship?

Copy link
Member

@aklish aklish Jun 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Permissions are not as straightforward given that graphql mutations behave more like patch extension requests in JSON-API. Whether you can perform the operation or not depends not just on the operation being performed but on the data passed along with the operation.

Also some operations like UPSERT and REPLACE are really doing different things depending on the data.

Root entities:

  • UPSERT - CreatePermission OR UpdatePermission (depending on whether the object already exists or not)
  • DELETE - DeletePermission
  • REMOVE - DeletePermission
  • REPLACE - CreatePermission OR UpdatePermission OR DeletePermission
  • FETCH - ReadPermission
  • UPDATE - UpdatePermission

Relationships:

  • FETCH - ReadPermission (on the field)
  • REMOVE - UpdatePermission (on the field)
  • DELETE - DeletePermission (on the type being deleted)
  • UPSERT - CreatePermission (on the type being created) OR UpdatePermission(on the field)
  • REPLACE - CreatePermission OR UpdatePermission (field and type being manipulated) OR DeletePermission
  • UPDATE - UpdatePermission (on the field)

We can possibly simplify this though to the following:
Root entities:

  • UPSERT - CreatePermission
  • DELETE - DeletePermission
  • REMOVE - DeletePermission
  • REPLACE - Too complex to put a restriction on. Just always allow it.
  • FETCH - ReadPermission
  • UPDATE - UpdatePermission

Relationships:

  • FETCH - ReadPermission (on the field)
  • REMOVE - UpdatePermission (on the field)
  • DELETE - DeletePermission (on the type being deleted)
  • UPSERT - UpdatePermission (on the field)
  • REPLACE - UpdatePermission (on the field)
  • UPDATE - UpdatePermission (on the field)

Alternatively, we don't solve for GraphQL at all this way and punt. Maybe we need a more explicit way to filter out operations in the graphql schema.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to at least attempt to filter the operations as that is better than nothing. I retained some criteria of restricting REPLACE as I think the read only use case should be relatively common and exposing REPLACE is misleading.

@justin-tay
Copy link
Collaborator Author

relationshipop

case FETCH:
return canRead(clazz);
case DELETE:
return canUpdate(clazz);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is actually canDelete as it deletes the underlying objects as opposed to simply removing them from the relationship.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have changed it to canDelete.

String queryPath = String.join(" ", validationError.getQueryPath());
result.add(ValidationError.newValidationError()
.description(
"Invalid operation: " + queryPath + " is read only.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Under what circumstances does this error get generated? How do we know it is an attempt to write something that is read only?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gets generated in the event an invalid value is used that couldn't map to the GraphQL enum.

I was trying to get the pre-existing test in NoCacheAggregationDataStoreIntegrationTest to pass but I guess I should change the expectation instead.

@@ -248,28 +254,46 @@ public PathItem getRelationshipPath() {
RelationshipType relationshipType = dictionary.getRelationshipType(parentClass, name);

if (relationshipType.isToMany()) {
path.get(new Operation().tags(getTags()).description("Returns the relationship identifiers for " + name)
.responses(new ApiResponses().addApiResponse("200", okPluralResponse)));
if (canCreate(type)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to GraphQL, when you are POSTING to a relationship, we are not creating the type but just updating the relationship (which is Update permission). All of the relationship edits are effectively Updates.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have made the changes.

@justin-tay justin-tay marked this pull request as ready for review July 6, 2023 06:29
@aklish aklish merged commit ac3d8ea into yahoo:master Jul 16, 2023
2 checks passed
@justin-tay justin-tay deleted the crudfilter branch July 18, 2023 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Option to expose specific CRUD methods based on a config
2 participants