Skip to content

Commit

Permalink
Filter operations on OpenAPI and GraphQL Schema (#3018)
Browse files Browse the repository at this point in the history
* Add filtering of CRUD based on permission NONE

* Update table type

* Cater to filtering id operations

* Support GraphQL

* Add tests

* Fix incorrect permission

* Fix use exclude annotation instead of permission annotations to hide id operations

* Fix confusing method

* Fix permissions

* Generate a more accurate error message

* Fix openapi permissions
  • Loading branch information
justin-tay committed Jul 16, 2023
1 parent 7cb7875 commit ac3d8ea
Show file tree
Hide file tree
Showing 11 changed files with 1,316 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1313,14 +1313,42 @@ public Collection<Annotation> getIdAnnotations(Object value) {
return null;
}

AccessibleObject idField = getEntityBinding(getType(value)).getIdField();
return getIdAnnotations(getType(value));
}

/**
* Returns annotations applied to the ID field.
*
* @param type the type
* @return Collection of Annotations
*/
public Collection<Annotation> getIdAnnotations(Type<?> type) {
AccessibleObject idField = getEntityBinding(type).getIdField();
if (idField != null) {
return Arrays.asList(idField.getDeclaredAnnotations());
}

return Collections.emptyList();
}

/**
* Searches for a specific annotation on the ID field.
*
* @param <A> the annotation type to search for
* @param recordClass the record type
* @param annotationClass the annotation to search for
* @return
*/
public <A extends Annotation> A getIdAnnotation(Type<?> recordClass, Class<A> annotationClass) {
Collection<Annotation> annotations = getIdAnnotations(recordClass);
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(annotationClass)) {
return annotationClass.cast(annotation);
}
}
return null;
}

/**
* Follow for this class or super-class for JPA {@link Entity} annotation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@
import static com.yahoo.elide.modelconfig.model.Type.TEXT;
import static com.yahoo.elide.modelconfig.model.Type.TIME;

import com.yahoo.elide.annotation.CreatePermission;
import com.yahoo.elide.annotation.DeletePermission;
import com.yahoo.elide.annotation.Exclude;
import com.yahoo.elide.annotation.Include;
import com.yahoo.elide.annotation.ReadPermission;
import com.yahoo.elide.annotation.UpdatePermission;
import com.yahoo.elide.core.security.checks.prefab.Role;
import com.yahoo.elide.core.type.Field;
import com.yahoo.elide.core.type.Method;
import com.yahoo.elide.core.type.Package;
Expand Down Expand Up @@ -392,7 +397,12 @@ public ArgumentDefinition[] arguments() {
return getArgumentDefinitions(table.getArguments());
}
});
putPermissionAnnotations(table, annotations);
return annotations;
}

private static void putPermissionAnnotations(Table table,
Map<Class<? extends Annotation>, Annotation> annotations) {
String readPermission = table.getReadAccess();
if (StringUtils.isNotEmpty(readPermission)) {
annotations.put(ReadPermission.class, new ReadPermission() {
Expand All @@ -408,7 +418,45 @@ public String expression() {
}
});
}
return annotations;

annotations.put(CreatePermission.class, new CreatePermission() {

@Override
public Class<? extends Annotation> annotationType() {
return CreatePermission.class;
}

@Override
public String expression() {
return Role.NONE_ROLE;
}
});

annotations.put(UpdatePermission.class, new UpdatePermission() {

@Override
public Class<? extends Annotation> annotationType() {
return UpdatePermission.class;
}

@Override
public String expression() {
return Role.NONE_ROLE;
}
});

annotations.put(DeletePermission.class, new DeletePermission() {

@Override
public Class<? extends Annotation> annotationType() {
return DeletePermission.class;
}

@Override
public String expression() {
return Role.NONE_ROLE;
}
});
}

private static ArgumentDefinition[] getArgumentDefinitions(List<Argument> arguments) {
Expand Down Expand Up @@ -857,6 +905,14 @@ public CardinalitySize size() {
}
});

// This disables get by id
annotations.put(Exclude.class, new Exclude() {
@Override
public Class<? extends Annotation> annotationType() {
return Exclude.class;
}
});

return new FieldType("id", LONG_TYPE, annotations);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2137,7 +2137,7 @@ public void testUpsertWithDynamicModel() throws IOException {
)
).toGraphQLSpec();

String expected = "Invalid operation: SalesNamespace_orderDetails is read only.";
String expected = "Invalid operation: UPSERT is not permitted on SalesNamespace_orderDetails.";

runQueryWithExpectedError(graphQLRequest, expected);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
Expand All @@ -60,6 +62,7 @@ public class GraphQLConversionUtils {
private final Map<Type, GraphQLObjectType> outputConversions = new HashMap<>();
private final Map<Type, GraphQLInputObjectType> inputConversions = new HashMap<>();
private final Map<Type, GraphQLEnumType> enumConversions = new HashMap<>();
private final Map<String, GraphQLEnumType> namedEnumConversions = new HashMap<>();
private final Map<String, GraphQLList> mapConversions = new HashMap<>();
private final GraphQLNameUtils nameUtils;

Expand Down Expand Up @@ -134,11 +137,11 @@ public GraphQLEnumType classToEnumType(Type<?> enumClazz) {
return enumConversions.get(enumClazz);
}

Enum [] values = (Enum []) enumClazz.getEnumConstants();
Enum<?> [] values = (Enum []) enumClazz.getEnumConstants();

GraphQLEnumType.Builder builder = newEnum().name(nameUtils.toOutputTypeName(enumClazz));

for (Enum value : values) {
for (Enum<?> value : values) {
builder.value(value.toString(), value);
}

Expand All @@ -149,6 +152,40 @@ public GraphQLEnumType classToEnumType(Type<?> enumClazz) {
return enumResult;
}

/**
* Converts an enum to a GraphQLEnumType where the enum needs to be filtered and
* will be identified by its name and not enum type as it is not possible to
* define a subset of values of an existing GraphQLEnum Type.
*
* @param enumClazz the Enum to convert
* @param nameProcessor to determine the enum name
* @param filter the predicate to filter the enum values
* @return A GraphQLEnum type for class.
*/
public GraphQLEnumType classToNamedEnumType(Type<?> enumClazz, Function<String, String> nameProcessor,
Predicate<Enum<?>> filter) {
String name = nameProcessor.apply(nameUtils.toOutputTypeName(enumClazz));
if (namedEnumConversions.containsKey(name)) {
return namedEnumConversions.get(name);
}

Enum<?> [] values = (Enum []) enumClazz.getEnumConstants();

GraphQLEnumType.Builder builder = newEnum().name(name);

for (Enum<?> value : values) {
if (filter.test(value)) {
builder.value(value.toString(), value);
}
}

GraphQLEnumType enumResult = builder.build();

namedEnumConversions.put(name, enumResult);

return enumResult;
}

/**
* Creates a GraphQL Map Query type. GraphQL doesn't support this natively. We mimic
* maps by creating a list of key/value pairs.
Expand Down

0 comments on commit ac3d8ea

Please sign in to comment.