/* Copyright 2014 MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mitre.provenance.services;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.Consumes;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.mitre.provenance.Metadata;
import org.mitre.provenance.PLUSException;
import org.mitre.provenance.dag.ViewedCollection;
import org.mitre.provenance.db.neo4j.Neo4JPLUSObjectFactory;
import org.mitre.provenance.db.neo4j.Neo4JStorage;
import org.mitre.provenance.plusobject.PLUSObject;
import org.mitre.provenance.plusobject.ProvenanceCollection;
import org.mitre.provenance.plusobject.marking.Taint;
import org.mitre.provenance.user.User;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;
/**
* Object services encompass RESTful services for access to individual provenance objects.
* @author moxious
*/
@Path("/object")
@Api(value = "/object", description = "Provenance Objects: data, invocations, workflows, activities, etc.")
public class ObjectServices {
protected static Logger log = Logger.getLogger(ObjectServices.class.getName());
@Context
UriInfo uriInfo;
@Path("/search")
@POST
@Produces(MediaType.APPLICATION_JSON)
// @Consumes("application/x-javascript")
@ApiOperation(value = "Search for provenance objects by a given search term", notes="", response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message="Error processing search")
})
public Response search(@Context HttpServletRequest req,
@ApiParam(value="the search term to use", required=true)
@FormParam("searchTerm") String searchTerm,
@ApiParam(value="maximum items to return", required=true) @DefaultValue("50") @QueryParam("n") int n) {
log.info("SEARCH POST '" + searchTerm + "'");
try {
//TODO : user
ProvenanceCollection col = Neo4JPLUSObjectFactory.searchFor(searchTerm, User.DEFAULT_USER_GOD, n);
return ServiceUtility.OK(col, req);
} catch(Exception exc) {
exc.printStackTrace();
return ServiceUtility.ERROR(exc.getMessage());
}
} // End search
@Path("/search/{term:.*}")
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Search for provenance objects by a given search term", notes="", response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message="Error processing search")
})
public Response searchTerm(@Context HttpServletRequest req,
@ApiParam(value = "The ID of the actor", required=true)
@PathParam("term") String term,
@ApiParam(value="maximum items to return", required=true) @DefaultValue("50") @QueryParam("n") int n) {
log.info("SEARCH GET '" + term + "'");
try {
//TODO
ProvenanceCollection col = Neo4JPLUSObjectFactory.searchFor(term, ServiceUtility.getUser(req), n);
return ServiceUtility.OK(col, req);
} catch(Exception exc) {
exc.printStackTrace();
return ServiceUtility.ERROR(exc.getMessage());
}
}
@Path("/taint/marktaintandfling/{id:.*}")
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Mark taint and return a provenance graph containing contaminated FLING", notes = "", response = ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message = "Invalid data provided")
})
/**
* Given a node id or (ASIAS-specific) meta_id value in node Metadata, mark node as tainted and return the FLING of that marking.
* @param id the oid or meta_id (ASIAS-specific) of the node that should be marked tainted.
* @return a D3 JSON formatted provenance collection
* @author piekut
*/
public Response taintFLING(@Context HttpServletRequest req,
@ApiParam(value="oid or meta_id (ASIAS-specific) of node to be marked tainted", required=true)
@PathParam("id") String id,
@ApiParam(value="maximum items to return", required=true) @DefaultValue("50") @QueryParam("n") int n) {
int limit = 100;
if(id == null || "".equals(id)) {
return ServiceUtility.BAD_REQUEST("No node oid or meta_id (ASIAS-specific) value specified");
}
boolean use_meta_id = false; // Generic case, mark by node OID.
if (!id.startsWith("urn:uuid:")) { use_meta_id=true; } // ASIAS-specific (meta_id) if not recognized OID format.
ProvenanceCollection col = new ProvenanceCollection();
try {
PLUSObject node;
if (use_meta_id) { // ASIAS-specific
// find node that was identified by meta_id.
ProvenanceCollection matchSet = org.mitre.provenance.db.neo4j.Neo4JPLUSObjectFactory
.loadBySingleMetadataField(ServiceUtility.getUser(req), "meta_id", id);
int numMatches = matchSet.getNodes().size();
if (numMatches==0) { return ServiceUtility.NOT_FOUND("No object found with meta_id='"+id+"'."); }
else if (numMatches!=1) { return ServiceUtility.ERROR("ERROR: duplicate matches found for meta_id='"+id+"'."); }
node = matchSet.getNodes().iterator().next();
}
else { // default condition, retrieve node be OID.
node = org.mitre.provenance.db.neo4j.Neo4JPLUSObjectFactory.load(id, ServiceUtility.getUser(req));
}
// Mark the specified node as tainted.
try {
org.mitre.provenance.db.neo4j.Neo4JPLUSObjectFactory.taint(node, ServiceUtility.getUser(req), "using REST service to taint node and return FLING");
}
catch (Exception e) {
return ServiceUtility.ERROR("Failed to mark node with meta_id='"+id+"': " + e.getMessage());
}
// Finally retrieve collection of tainted nodes downstream (i.e., FLING).
try {
col.addNode(node); // add affected node, for completeness.
col.addAll(org.mitre.provenance.db.neo4j.Neo4JPLUSObjectFactory.getFullFLING(node.getId(), ServiceUtility.getUser(req)));
}
catch (Exception e) {
return ServiceUtility.ERROR("Taint mark successful for node with id value '"+id+"', but FLING could not be retrieved: " + e.getMessage());
}
} catch(Exception exc) {
exc.printStackTrace();
return ServiceUtility.ERROR(exc.getMessage());
}
return ServiceUtility.OK(col, req);
} // End
@Path("/taint/{oid:.*}")
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get a list of taint nodes that are upstream influencers for a given OID",
notes="Returns only nodes that represent taint (no edges). It is assumed that these are non-local.",
response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 404, message="Object not found"),
@ApiResponse(code = 400, message="Error processing request")
})
/**
* Get a list of taint nodes that are upstream influencers for a given OID.
* @param oid the OID you want to know the taints for
* @return a D3 JSON document containing only the nodes that represent taint (no edges). It is assumed
* that these are non-local.
* @throws PLUSException
*/
public Response getTaint(@Context HttpServletRequest req,
@ApiParam(value = "Object OID", required=true) @PathParam("oid") String oid) {
// log.info("GET TAINT " + oid);
User user = ServiceUtility.getUser(req);
try {
Node n = Neo4JStorage.oidExists(oid);
if(n == null) return ServiceUtility.NOT_FOUND("Invalid/non-existant oid");
PLUSObject obj = Neo4JPLUSObjectFactory.newObject(n);
ProvenanceCollection col = Neo4JPLUSObjectFactory.getAllTaintSources(obj, user);
return ServiceUtility.OK(col, req);
} catch(PLUSException exc) {
exc.printStackTrace();
return ServiceUtility.ERROR(exc.getMessage());
} // End catch
} // End getTaint
@Path("/taint/{oid:.*}")
@POST
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Mark an item as tainted",
notes="This modifies the graph to assert that a given item is tainted",
response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 404, message="Object not found"),
@ApiResponse(code = 400, message="Error processing request")
})
public Response setTaint(@ApiParam(value = "Object OID", required=true) @PathParam("oid") String oid,
@ApiParam(value = "Description of reason for taint", required=true) @FormParam("reason") String reason,
@Context HttpServletRequest req,
MultivaluedMap<String, String> queryParams) {
User user = ServiceUtility.getUser(req);
// String reason = queryParams.getFirst("reason");
// log.info("SET TAINT " + oid + " reason '" + reason + "'");
if(oid == null || "".equals(oid)) return ServiceUtility.BAD_REQUEST("Missing oid");
if(reason == null || "".equals(reason.trim())) return ServiceUtility.BAD_REQUEST("Must specify non-empty reason");
Node n = Neo4JStorage.oidExists(oid);
if(n == null) return ServiceUtility.BAD_REQUEST("Cannot POST taint to a non-existant node");
try {
PLUSObject object = Neo4JPLUSObjectFactory.newObject(n);
Taint t = Neo4JPLUSObjectFactory.taint(object, user, reason);
ProvenanceCollection c = new ProvenanceCollection();
c.addNode(t);
return ServiceUtility.OK(c, req);
} catch(PLUSException exc) {
exc.printStackTrace();
return ServiceUtility.ERROR(exc.getMessage());
} // End catch
} // End setTaint
@Path("/taint/{oid:.*}")
@DELETE
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value="Remove taint marking from an object.",
notes="This modifies the graph to remove the assertion that a given item is tainted",
response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 404, message="Object not found"),
@ApiResponse(code = 400, message="Error processing request or no oid provided")
})
public Response removeTaint(@Context HttpServletRequest req,
@ApiParam(value = "Object OID", required=true) @PathParam("oid") String oid) {
User user = ServiceUtility.getUser(req);
// log.info("REMOVE TAINT " + oid);
if(oid == null || "".equals(oid)) return ServiceUtility.BAD_REQUEST("Must specify oid");
try {
Node n = Neo4JStorage.oidExists(oid);
if(n == null) return ServiceUtility.NOT_FOUND("No such object by oid " + oid);
PLUSObject obj = Neo4JPLUSObjectFactory.newObject(n);
if(obj.isHeritable() && obj instanceof Taint)
return ServiceUtility.BAD_REQUEST("Remove taint from the tainted object, do not remove the taint object itself.");
n = null;
Neo4JPLUSObjectFactory.removeTaints(obj);
// Return the list of taints found.
return ServiceUtility.OK(Neo4JPLUSObjectFactory.getAllTaintSources(obj, user), req);
} catch(PLUSException exc) {
exc.printStackTrace();
return ServiceUtility.ERROR(exc.getMessage());
} // End catch
} // End removeTaint
@Path("/edges/{oid:.*}")
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value="Get the set of edges associated with an object",
notes="Returns a collection of edges incident to an object.",
response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 404, message="Object not found"),
@ApiResponse(code = 400, message="Error processing request or no oid provided")
})
public Response getEdges(@Context HttpServletRequest req,
@ApiParam(value = "Object OID", required=true) @PathParam("oid") String oid) {
// log.info("EDGES " + oid);
try {
ArrayList<String>oids = new ArrayList<String>();
oids.add(oid);
if(Neo4JStorage.oidExists(oid) == null)
return ServiceUtility.NOT_FOUND("No object by that oid");
ProvenanceCollection col = Neo4JPLUSObjectFactory.getIncidentEdges(oids, ServiceUtility.getUser(req), "both", true, false);
return ServiceUtility.OK(col, req);
} catch(PLUSException exc) {
exc.printStackTrace();
return ServiceUtility.ERROR(exc.getMessage());
} // End catch
} // End getEdges
@Path("/metadata/{field:.*}/{value:.*}")
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value="Get objects with a particular metadata field value",
notes="Returns a collection of objects with the specified metadata field name and value.",
response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message="Error processing request or no invalid fields provided")
})
public Response getObjectBySingleMetadataField(@Context HttpServletRequest req,
@ApiParam(value="Metadata field name", required=true) @PathParam("field") String field,
@ApiParam(value="Metadata field value", required=true) @PathParam("value") String value) {
if(field == null || "".equals(field) || field.length() > 128)
return ServiceUtility.BAD_REQUEST("Invalid field specified.");
if(value == null || "".equals(value) || value.length() > 256)
return ServiceUtility.BAD_REQUEST("Invalid value specified.");
User user = ServiceUtility.getUser(req);
try {
ProvenanceCollection col = Neo4JPLUSObjectFactory.loadBySingleMetadataField(user, field, value);
return ServiceUtility.OK(col, req);
} catch (PLUSException e) {
e.printStackTrace();
return ServiceUtility.ERROR(e.getMessage());
}
}
@Path("/npid/{npid:.*}")
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value="Get a list of provenance objects by their available non-provenance IDs, if any",
notes="Returns a collection of objects",
response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message="Error processing request or no invalid fields provided")
})
public Response getObjectByNPID(@Context HttpServletRequest req,
@ApiParam(value = "Non-provenance ID", required=true) @PathParam("npid") String npid) {
// log.info("OBJECT BY NPID " + npid);
if(npid == null || "".equals(npid)) return ServiceUtility.BAD_REQUEST("Must specify npid");
try (Transaction tx = Neo4JStorage.beginTx()) {
ViewedCollection col = new ViewedCollection(ServiceUtility.getUser(req));
Node n = Neo4JStorage.getNPID(npid, false);
if(n == null) {
tx.success();
return ServiceUtility.NOT_FOUND("No such npid " + npid);
}
Iterator<Relationship> rels = n.getRelationships(Direction.BOTH, Neo4JStorage.NPE).iterator();
int x=0;
while(rels.hasNext()) {
Relationship r = rels.next();
if(r.getStartNode().equals(n)) {
col.addNode(Neo4JPLUSObjectFactory.newObject(r.getEndNode()));
} else {
col.addNode(Neo4JPLUSObjectFactory.newObject(r.getStartNode()));
} // End else
x++;
if(x >= 10) break;
} // End while
tx.success();
return ServiceUtility.OK(col, req);
} catch(PLUSException exc) {
exc.printStackTrace();
return ServiceUtility.ERROR(exc.getMessage());
} // End catch
} // End getObjectByNPID
@Path("/{oid:.*}")
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value="Get an individual provenance object",
notes="Returns a collection containing one object",
response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message="Error processing request or invalid fields provided"),
@ApiResponse(code = 404, message="No such object")
})
public Response getObject(@Context HttpServletRequest req,
@ApiParam(value = "Object ID", required=true) @PathParam("oid") String oid) {
// log.info("GET OBJECT " + oid);
if(oid == null || "".equals(oid)) return ServiceUtility.BAD_REQUEST("Must specify oid");
try {
Node n = Neo4JStorage.oidExists(oid);
if(n == null) return ServiceUtility.NOT_FOUND("No such oid " + oid);
PLUSObject obj = Neo4JPLUSObjectFactory.newObject(n);
ViewedCollection col = new ViewedCollection(ServiceUtility.getUser(req));
col.addNode(obj);
return ServiceUtility.OK(col, req);
} catch(PLUSException exc) {
exc.printStackTrace();
return ServiceUtility.ERROR(exc.getMessage());
} // End catch
} // End getObject
} // End ObjectServices