Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Adding validation to GMS
  • Loading branch information
jjoyce0510 committed Jun 23, 2021
commit 59116611edba2459a3e5e0de3971527bc19cd0d8
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.linkedin.metadata.resources;

import com.linkedin.common.urn.UrnValidator;
import com.linkedin.data.schema.validation.ValidateDataAgainstSchema;
import com.linkedin.data.schema.validation.ValidationOptions;
import com.linkedin.data.schema.validation.ValidationResult;
import com.linkedin.data.schema.validator.DataSchemaAnnotationValidator;
import com.linkedin.data.template.RecordTemplate;
import com.linkedin.restli.common.HttpStatus;
import com.linkedin.restli.server.RestLiServiceException;


public class ResourceUtils {

private static final ValidationOptions DEFAULT_VALIDATION_OPTIONS = new ValidationOptions();

/**
* Validates a {@link RecordTemplate} and throws {@link com.linkedin.restli.server.RestLiServiceException}
* if validation fails.
*
* @param record record to be validated.
* @param status the status code to return to the client on failure.
*/
public static void validateRecord(RecordTemplate record, HttpStatus status) {
final ValidationResult result = ValidateDataAgainstSchema.validate(
record,
DEFAULT_VALIDATION_OPTIONS,
new UrnValidator());
if (!result.isValid())
{
throw new RestLiServiceException(status, result.getMessages().toString());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.linkedin.metadata.entity.EntityService;
import com.linkedin.metadata.restli.RestliUtils;
import com.linkedin.parseq.Task;
import com.linkedin.restli.common.HttpStatus;
import com.linkedin.restli.server.annotations.Optional;
import com.linkedin.restli.server.annotations.QueryParam;
import com.linkedin.restli.server.annotations.RestLiCollection;
Expand All @@ -18,6 +19,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.linkedin.metadata.resources.ResourceUtils.*;


/**
* Single unified resource for fetching, updating, searching, & browsing DataHub entities
*/
Expand Down Expand Up @@ -46,9 +50,10 @@ public Task<VersionedAspect> get(
final VersionedAspect aspect = _entityService.getVersionedAspect(urn, aspectName, version);
if (aspect == null) {
throw RestliUtils.resourceNotFoundException();
} else {
validateRecord(aspect, HttpStatus.S_500_INTERNAL_SERVER_ERROR);
}
return aspect;
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
import com.linkedin.metadata.search.SearchService;
import com.linkedin.metadata.search.utils.BrowsePathUtils;
import com.linkedin.parseq.Task;
import com.linkedin.restli.common.HttpStatus;
import com.linkedin.restli.common.validation.RestLiDataValidator;
import com.linkedin.restli.server.annotations.Action;
import com.linkedin.restli.server.annotations.ActionParam;
import com.linkedin.restli.server.annotations.Optional;
import com.linkedin.restli.server.annotations.QueryParam;
import com.linkedin.restli.server.annotations.RestLiCollection;
import com.linkedin.restli.server.annotations.RestMethod;
import com.linkedin.restli.server.annotations.ValidatorParam;
import com.linkedin.restli.server.resources.CollectionResourceTaskTemplate;
import java.net.URISyntaxException;
import java.time.Clock;
Expand All @@ -39,6 +42,7 @@
import org.slf4j.LoggerFactory;

import static com.linkedin.metadata.PegasusUtils.urnToEntityName;
import static com.linkedin.metadata.resources.ResourceUtils.*;
import static com.linkedin.metadata.restli.RestliConstants.ACTION_AUTOCOMPLETE;
import static com.linkedin.metadata.restli.RestliConstants.ACTION_BROWSE;
import static com.linkedin.metadata.restli.RestliConstants.ACTION_GET_BROWSE_PATHS;
Expand Down Expand Up @@ -95,6 +99,8 @@ public Task<Entity> get(@Nonnull String urnStr, @QueryParam(PARAM_ASPECTS) @Opti
final Entity entity = _entityService.getEntity(urn, projectedAspects);
if (entity == null) {
throw RestliUtils.resourceNotFoundException();
} else {
validateRecord(entity, HttpStatus.S_500_INTERNAL_SERVER_ERROR);
}
return entity;
});
Expand All @@ -116,13 +122,17 @@ public Task<Map<String, Entity>> batchGet(
return _entityService.getEntities(urns, projectedAspects)
.entrySet()
.stream()
.peek(entry -> validateRecord(entry.getValue(), HttpStatus.S_500_INTERNAL_SERVER_ERROR))
.collect(Collectors.toMap(entry -> entry.getKey().toString(), Map.Entry::getValue));
});
}

@Action(name = ACTION_INGEST)
@Nonnull
public Task<Void> ingest(@ActionParam(PARAM_ENTITY) @Nonnull Entity entity) throws URISyntaxException {
public Task<Void> ingest(@ActionParam(PARAM_ENTITY) @Nonnull Entity entity, @ValidatorParam RestLiDataValidator validator) throws URISyntaxException {

validateRecord(entity, HttpStatus.S_422_UNPROCESSABLE_ENTITY);

final Set<String> projectedAspects = new HashSet<>(Arrays.asList("browsePaths"));
final RecordTemplate snapshotRecord = RecordUtils.getSelectedRecordTemplateFromUnion(entity.getValue());
final Urn urn = com.linkedin.metadata.dao.utils.ModelUtils.getUrnFromSnapshot(snapshotRecord);
Expand All @@ -144,6 +154,10 @@ public Task<Void> ingest(@ActionParam(PARAM_ENTITY) @Nonnull Entity entity) thro
@Action(name = ACTION_BATCH_INGEST)
@Nonnull
public Task<Void> batchIngest(@ActionParam(PARAM_ENTITIES) @Nonnull Entity[] entities) throws URISyntaxException {
for (Entity entity : entities) {
validateRecord(entity, HttpStatus.S_422_UNPROCESSABLE_ENTITY);
}

final AuditStamp auditStamp =
new AuditStamp().setTime(_clock.millis()).setActor(Urn.createFromString(DEFAULT_ACTOR));
return RestliUtils.toTask(() -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.linkedin.common.urn;

import com.linkedin.data.message.Message;
import com.linkedin.data.schema.DataSchema;
import com.linkedin.data.schema.NamedDataSchema;
import com.linkedin.data.schema.validator.Validator;
import com.linkedin.data.schema.validator.ValidatorContext;
import java.net.URISyntaxException;


/**
* Rest.li Validator responsible for ensuring that {@link Urn} objects are well-formed.
*
* Note that this validator does not validate the integrity of strongly typed urns,
* or validate Urn objects against their associated key aspect.
*/
public class UrnValidator implements Validator {
@Override
public void validate(ValidatorContext context) {
if (DataSchema.Type.TYPEREF.equals(context.dataElement().getSchema().getType())
&& ((NamedDataSchema) context.dataElement().getSchema()).getName().endsWith("Urn")) {
try {
Urn.createFromString((String) context.dataElement().getValue());
} catch (URISyntaxException e) {
context.addResult(new Message(context.dataElement().path(), "\"Provided urn %s\" is invalid", context.dataElement().getValue()));
context.setHasFix(false);
}
}
}
}