Skip to content

Commit

Permalink
fixed bug where app-to-JSON serialization can break if user supplies …
Browse files Browse the repository at this point in the history
…bad resource permissions data; restricted the updating of the field
  • Loading branch information
albogdano committed Apr 29, 2022
1 parent e9b09ff commit 7a39f98
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 23 deletions.
36 changes: 34 additions & 2 deletions para-core/src/main/java/com/erudika/para/core/App.java
Expand Up @@ -114,7 +114,7 @@ public class App implements ParaObject, Serializable {
@Stored @Locked private boolean sharingTable;
@Stored @Locked private String secret;
@Stored @Locked private Boolean readOnly;
@Stored private Map<String, String> datatypes;
@Stored @Locked private Map<String, String> datatypes;
// type -> field -> constraint -> property -> value
@Stored private Map<String, Map<String, Map<String, Map<String, ?>>>> validationConstraints;
// subject_id -> resource_name -> [http_methods_allowed]
Expand Down Expand Up @@ -280,6 +280,22 @@ public void setSettings(Map<String, Object> settings) {
if (validationConstraints == null) {
validationConstraints = new LinkedHashMap<>();
}
try {
// hacky, but works! perform validation here, so that even if user supplied bad map data,
// the serialization should not break.
if (validationConstraints != null && !validationConstraints.isEmpty()
&& !validationConstraints.values().iterator().next().isEmpty()
&& !validationConstraints.values().iterator().next().values().isEmpty()
&& !validationConstraints.values().iterator().next().values().iterator().next().isEmpty()
&& !validationConstraints.values().iterator().next().values().iterator().next().values().isEmpty()
&& !validationConstraints.values().iterator().next().values().iterator().next().values().iterator().next().isEmpty()
&& validationConstraints.values().iterator().next().values().iterator().next().values().iterator().
next().values().iterator().next() != null) {
return validationConstraints;
}
} catch (Exception e) {
validationConstraints = new LinkedHashMap<>();
}
return validationConstraints;
}

Expand All @@ -299,6 +315,19 @@ public Map<String, Map<String, List<String>>> getResourcePermissions() {
if (resourcePermissions == null) {
resourcePermissions = new LinkedHashMap<>();
}
try {
// hacky, but works! perform validation here, so that even if user supplied bad map data,
// the serialization should not break.
if (resourcePermissions != null && !resourcePermissions.isEmpty()
&& !resourcePermissions.values().iterator().next().isEmpty()
&& !resourcePermissions.values().iterator().next().values().isEmpty()
&& !resourcePermissions.values().iterator().next().values().iterator().next().isEmpty()
&& !resourcePermissions.values().iterator().next().values().iterator().next().iterator().next().isBlank()) {
return resourcePermissions;
}
} catch (Exception e) {
resourcePermissions = new LinkedHashMap<>();
}
return resourcePermissions;
}

Expand Down Expand Up @@ -910,8 +939,10 @@ public void addDatatype(String pluralDatatype, String datatype) {
/**
* Adds unknown types to this app's list of data types. Called on create().
* @param objects a list of new objects
* @return true if a new data type was added to the list
*/
public void addDatatypes(ParaObject... objects) {
public boolean addDatatypes(ParaObject... objects) {
int typesCount = getDatatypes().size();
// register a new data type
if (objects != null && objects.length > 0) {
for (ParaObject obj : objects) {
Expand All @@ -920,6 +951,7 @@ public void addDatatypes(ParaObject... objects) {
}
}
}
return typesCount < getDatatypes().size();
}

/**
Expand Down
Expand Up @@ -242,26 +242,23 @@ public Response apply(ContainerRequestContext ctx) {
}
}
});
custom.addMethod(POST).produces(JSON).consumes(JSON).
handledBy(new Inflector<ContainerRequestContext, Response>() {
custom.addMethod(POST).produces(JSON).consumes(JSON).handledBy(new Inflector<ContainerRequestContext, Response>() {
public Response apply(ContainerRequestContext ctx) {
String appid = ParaObjectUtils.getAppidFromAuthHeader(ctx.getHeaders().getFirst(HttpHeaders.AUTHORIZATION));
try (Metrics.Context context = Metrics.time(appid, handler.getClass(), "handlePost")) {
return handler.handlePost(ctx);
}
}
});
custom.addMethod(PATCH).produces(JSON).consumes(JSON).
handledBy(new Inflector<ContainerRequestContext, Response>() {
custom.addMethod(PATCH).produces(JSON).consumes(JSON).handledBy(new Inflector<ContainerRequestContext, Response>() {
public Response apply(ContainerRequestContext ctx) {
String appid = ParaObjectUtils.getAppidFromAuthHeader(ctx.getHeaders().getFirst(HttpHeaders.AUTHORIZATION));
try (Metrics.Context context = Metrics.time(appid, handler.getClass(), "handlePatch")) {
return handler.handlePatch(ctx);
}
}
});
custom.addMethod(PUT).produces(JSON).consumes(JSON).
handledBy(new Inflector<ContainerRequestContext, Response>() {
custom.addMethod(PUT).produces(JSON).consumes(JSON).handledBy(new Inflector<ContainerRequestContext, Response>() {
public Response apply(ContainerRequestContext ctx) {
String appid = ParaObjectUtils.getAppidFromAuthHeader(ctx.getHeaders().getFirst(HttpHeaders.AUTHORIZATION));
try (Metrics.Context context = Metrics.time(appid, handler.getClass(), "handlePut")) {
Expand Down
Expand Up @@ -310,8 +310,6 @@ public static Response getCreateResponse(App app, String type, InputStream is) {
if (app != null && content != null && isNotAnApp(type)) {
content.setAppid(app.getAppIdentifier());
setCreatorid(app, content);
int typesCount = app.getDatatypes().size();
app.addDatatypes(content);
// The reason why we do two validation passes is because we want to return
// the errors through the API and notify the end user.
// This is the primary validation pass (validates not only core POJOS but also user defined objects).
Expand All @@ -322,8 +320,8 @@ public static Response getCreateResponse(App app, String type, InputStream is) {
String id = content.create();
if (id != null) {
// new type added so update app object
if (typesCount < app.getDatatypes().size()) {
app.update();
if (app.addDatatypes(content)) {
CoreUtils.getInstance().overwrite(app);
}
return Response.created(URI.create(Utils.urlEncode(content.getObjectURI()))).
entity(content).build();
Expand Down Expand Up @@ -361,8 +359,6 @@ public static Response getOverwriteResponse(App app, String id, String type, Inp
content.setAppid(app.getAppIdentifier());
content.setId(id);
setCreatorid(app, content);
int typesCount = app.getDatatypes().size();
app.addDatatypes(content);
// The reason why we do two validation passes is because we want to return
// the errors through the API and notify the end user.
// This is the primary validation pass (validates not only core POJOS but also user defined objects).
Expand All @@ -372,8 +368,8 @@ public static Response getOverwriteResponse(App app, String id, String type, Inp
// See: IndexAndCacheAspect.java
CoreUtils.getInstance().overwrite(app.getAppIdentifier(), content);
// new type added so update app object
if (typesCount < app.getDatatypes().size()) {
app.update();
if (app.addDatatypes(content)) {
CoreUtils.getInstance().overwrite(app);
}
return Response.ok(content).build();
}
Expand Down Expand Up @@ -421,6 +417,10 @@ public static Response getUpdateResponse(App app, ParaObject object, InputStream
return getStatusResponse(Response.Status.PRECONDITION_FAILED,
"Update failed due to 'version' mismatch.");
}
// new type added so update app object
if (app.addDatatypes(object)) {
CoreUtils.getInstance().overwrite(app);
}
return Response.ok(object).build();
}
}
Expand Down Expand Up @@ -510,13 +510,9 @@ public static Response getBatchCreateResponse(final App app, InputStream is) {

Para.getDAO().createAll(app.getAppIdentifier(), newObjects);

Para.asyncExecute(new Runnable() {
public void run() {
int typesCount = app.getDatatypes().size();
app.addDatatypes(newObjects.toArray(new ParaObject[0]));
if (typesCount < app.getDatatypes().size()) {
app.update();
}
Para.asyncExecute(() -> {
if (app.addDatatypes(newObjects.toArray(new ParaObject[0]))) {
CoreUtils.getInstance().overwrite(app);
}
});
} else {
Expand Down
Expand Up @@ -49,6 +49,7 @@ public AWSDynamoDAOIT() {
@BeforeClass
public static void setUpClass() throws InterruptedException {
System.setProperty("para.prepend_shared_appids_with_space", "true");
System.setProperty("para.dynamodb.provisioned_mode_enabled", "true");
System.setProperty("para.app_name", ROOT_APP_NAME);
AWSDynamoUtils.createTable(Para.getConfig().getRootAppIdentifier());
AWSDynamoUtils.createTable(appid1);
Expand Down

0 comments on commit 7a39f98

Please sign in to comment.