/* * Copyright (c) 2014. * * BaasBox - info@baasbox.com * * 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 com.baasbox.commands; import com.baasbox.commands.exceptions.CommandException; import com.baasbox.commands.exceptions.CommandExecutionException; import com.baasbox.commands.exceptions.CommandParsingException; import com.baasbox.controllers.actions.exceptions.RidNotFoundException; import com.baasbox.dao.exception.*; import com.baasbox.enumerations.Permissions; import com.baasbox.exception.AclNotValidException; import com.baasbox.exception.RoleNotFoundException; import com.baasbox.exception.UserNotFoundException; import com.baasbox.service.scripting.base.JsonCallback; import com.baasbox.service.scripting.js.Json; import com.baasbox.service.storage.DocumentService; import com.baasbox.util.JSONFormats; import com.baasbox.util.QueryParams; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.BooleanNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.orientechnologies.orient.core.exception.ODatabaseException; import com.orientechnologies.orient.core.exception.OSecurityException; import com.orientechnologies.orient.core.record.impl.ODocument; import org.apache.commons.lang.exception.ExceptionUtils; import com.baasbox.service.logging.BaasBoxLogger; import java.io.IOException; import java.util.List; import java.util.UUID; /** * {resource: 'documents', * name: <'get'|'list'|'post'|'put'|'delete', * params; { * collection: <collection>, * * query: <query>,* * * id: <id>*, * * grants: {} * }} * * * {resource: 'documents', * name: 'get', * params: { * collection: <collection>, * id: <uuid>, * * }} * * * {resource: 'documents', * name: 'grant', * params: { * collection: <collection>, * id: <uuid>, * users: {read: [,,,], * update: [,,,,], * delete: [....], * all: [,,,,], * }, * roles: {read: [,,,,], * update: [,,,,], * delete: [,,,,,], * all: [,,,,,] * }} * * * * Created by Andrea Tortorella on 30/06/14. */ class DocumentsResource extends BaseRestResource { public static final Resource INSTANCE = new DocumentsResource(); private static final String RESOURCE_NAME = "documents"; private static final String COLLECTION = "collection"; private static final String QUERY = "query"; private static final String DATA = "data"; private static final String AUTHOR = "author"; @Override protected ImmutableMap.Builder<String, ScriptCommand> baseCommands() { return super.baseCommands().put("grant", new ScriptCommand() { @Override public JsonNode execute(JsonNode command, JsonCallback callback) throws CommandException { return grant(command,true); } }).put("revoke", new ScriptCommand() { @Override public JsonNode execute(JsonNode command, JsonCallback callback) throws CommandException { return grant(command,false); } }); } private JsonNode grant(JsonNode command, boolean grant) throws CommandException { validateHasParams(command); String coll = getCollectionName(command); String id = getDocumentId(command); try { try { String rid = DocumentService.getRidByString(id, true); alterGrants(command, coll, rid, true, grant); alterGrants(command, coll, rid, false, grant); } catch (Exception e){ BaasBoxLogger.error("error",e); throw e; } } catch (UserNotFoundException e) { throw new CommandExecutionException(command,"user not found exception"); } catch (DocumentNotFoundException e) { throw new CommandExecutionException(command,"document not found exception"); } catch (InvalidCollectionException e) { throw new CommandExecutionException(command,"invalid colleciton exception"); } catch (InvalidModelException e) { throw new CommandExecutionException(command,"invalid model exception"); } catch (RoleNotFoundException e) { throw new CommandExecutionException(command,"role not found exception"); } catch (RidNotFoundException e) { throw new CommandExecutionException(command,"document "+id+" not found"); } return BooleanNode.getTrue(); } @Override protected JsonNode delete(JsonNode command) throws CommandException { validateHasParams(command); String coll = getCollectionName(command); String id = getDocumentId(command); String rid; try { rid = DocumentService.getRidByString(id,true); } catch (RidNotFoundException e) { return null; } try { DocumentService.delete(coll,rid); } catch (OSecurityException e) { throw new CommandExecutionException(command, "you don't have permissions to delete: "+id); } catch (ODatabaseException e){ return null; } catch (Throwable e){ throw new CommandExecutionException(command,"error executing delete command on "+id+ " message: "+ExceptionUtils.getMessage(e)); } return null; } private String getCollectionName(JsonNode command) throws CommandParsingException { JsonNode params = command.get(ScriptCommand.PARAMS); JsonNode collNode = params.get(COLLECTION); if (collNode == null|| !collNode.isTextual()){ throw new CommandParsingException(command,"invalid collection param: "+(collNode==null?"null":collNode.toString())); } return collNode.asText(); } @Override protected JsonNode put(JsonNode command) throws CommandException{ validateHasParams(command); String coll = getCollectionName(command); JsonNode data = getData(command); String id = getDocumentId(command); try { String rid=null; try{ rid = DocumentService.getRidByString(id, true); } catch (RidNotFoundException e) { //swallow } ODocument doc=null; if (rid!=null) //update doc = DocumentService.update(coll, rid, (ObjectNode)data); else // save doc = DocumentService.create(coll, (ObjectNode)data); String json = JSONFormats.prepareDocToJson(doc, JSONFormats.Formats.DOCUMENT_PUBLIC); ObjectNode node = (ObjectNode)Json.mapper().readTree(json); node.remove(TO_REMOVE); //node.remove("@rid"); return node; } catch (RidNotFoundException e) { throw new CommandExecutionException(command,"document: "+id+" does not exists"); } catch (UpdateOldVersionException e) { throw new CommandExecutionException(command,"document: "+id+" has a more recent version"); } catch (DocumentNotFoundException e) { throw new CommandExecutionException(command,"document: "+id+" does not exists"); } catch (InvalidCollectionException e) { throw new CommandExecutionException(command,"invalid collection: "+coll); } catch (InvalidModelException e) { throw new CommandExecutionException(command,"error updating document (is the provided ID belonging to the provided collection?): "+id+" message: "+ExceptionUtils.getMessage(e)); } catch (JsonProcessingException e) { throw new CommandExecutionException(command,"data do not represents a valid document, message: "+ExceptionUtils.getMessage(e)); } catch (IOException e) { throw new CommandExecutionException(command,"error updating document: "+id+" message:"+ExceptionUtils.getMessage(e)); } catch (AclNotValidException e) { throw new CommandExecutionException(command,"error updating document (check the ACL fields): "+id+" message:"+ExceptionUtils.getMessage(e)); } catch (Throwable e){ BaasBoxLogger.error("error updating document: "+id,e); throw new CommandExecutionException(command," error updating document: "+id+" message:"+ExceptionUtils.getMessage(e)); } } @Override protected JsonNode post(JsonNode command) throws CommandException{ validateHasParams(command); String collection = getCollectionName(command); JsonNode data = getData(command); String authorOverride = getAuthorOverride(command); try { ODocument doc = DocumentService.create(collection, (ObjectNode)data); // DocumentService.createOnBehalf(collection,authorOverride,data); if (doc == null){ return null; } String fmt = JSONFormats.prepareDocToJson(doc, JSONFormats.Formats.DOCUMENT_PUBLIC); JsonNode node = Json.mapper().readTree(fmt); ObjectNode n =(ObjectNode)node; n.remove(TO_REMOVE); // n.remove("@rid"); return n; } catch (InvalidCollectionException throwable) { throw new CommandExecutionException(command,"invalid collection: "+collection); } catch (InvalidModelException e) { throw new CommandExecutionException(command,"error creating document: "+ExceptionUtils.getMessage(e)); } catch (Throwable e) { throw new CommandExecutionException(command,"error creating document: "+ExceptionUtils.getMessage(e)); } } private String getAuthorOverride(JsonNode command) throws CommandParsingException{ JsonNode node = command.get(ScriptCommand.PARAMS).get(AUTHOR); if (node != null && !node.isTextual()){ throw new CommandParsingException(command,"author must be a string"); } else if (node != null){ return node.asText(); } return null; } private JsonNode getData(JsonNode command) throws CommandParsingException { JsonNode node = command.get(ScriptCommand.PARAMS).get(DATA); if (node == null||(!node.isObject())){ throw new CommandParsingException(command,"missing required data parameter"); } return node; } @Override protected JsonNode list(JsonNode command) throws CommandException { String collection= getCollectionName(command); QueryParams params = QueryParams.getParamsFromJson(command.get(ScriptCommand.PARAMS).get(QUERY)); try { List<ODocument> docs = DocumentService.getDocuments(collection, params); String s = JSONFormats.prepareDocToJson(docs, JSONFormats.Formats.DOCUMENT_PUBLIC); ArrayNode lst = (ArrayNode)Json.mapper().readTree(s); lst.forEach((j)->((ObjectNode)j).remove(TO_REMOVE));//.remove("@rid")); return lst; } catch (SqlInjectionException | IOException e) { throw new CommandExecutionException(command,"error executing command: "+ExceptionUtils.getMessage(e),e); } catch (InvalidCollectionException e) { throw new CommandExecutionException(command,"invalid collection: "+collection,e); } } @Override protected JsonNode get(JsonNode command) throws CommandException{ String collection = getCollectionName(command); String id = getDocumentId(command); try { String rid = DocumentService.getRidByString(id, true); ODocument document = DocumentService.get(collection, rid); if (document == null){ return null; } else { String s = JSONFormats.prepareDocToJson(document, JSONFormats.Formats.DOCUMENT_PUBLIC); ObjectNode node = (ObjectNode)Json.mapper().readTree(s); //necessary to create relations between documents node.remove(TO_REMOVE);//.remove("@rid"); return node; } } catch (RidNotFoundException e) { return null; } catch (DocumentNotFoundException e) { return null; } catch (InvalidCollectionException e) { throw new CommandExecutionException(command,"invalid collection: "+collection); } catch (InvalidModelException e) { throw new CommandExecutionException(command,"error executing command: "+ExceptionUtils.getMessage(e)); } catch (JsonProcessingException e) { throw new CommandExecutionException(command,"error executing command: "+ExceptionUtils.getMessage(e)); } catch (IOException e) { throw new CommandExecutionException(command,"error executing command: "+ExceptionUtils.getMessage(e)); } } private void alterGrants(JsonNode command,String collection,String docId,boolean users,boolean grant ) throws CommandParsingException, UserNotFoundException, DocumentNotFoundException, InvalidCollectionException, InvalidModelException, RoleNotFoundException { JsonNode params = command.get(ScriptCommand.PARAMS); JsonNode node = users?params.get("users"):params.get("roles"); if (node != null){ JsonNode read = node.get("read"); if (read!=null){ alterGrantsTo(command, read, collection, docId, grant, users, Permissions.ALLOW_READ); } //DEPRECATED JsonNode write = node.get("write"); if (write!=null){ alterGrantsTo(command,write,collection,docId,grant,users,Permissions.ALLOW_UPDATE); } //issue 682 - field to grant/revoke update permission is "write" instead of "update" into the plugin engine //we now have to maintain both due retro-compatibility JsonNode update = node.get("update"); if (update!=null){ alterGrantsTo(command,update,collection,docId,grant,users,Permissions.ALLOW_UPDATE); } //------ JsonNode delete = node.get("delete"); if (delete!=null){ alterGrantsTo(command,delete,collection,docId,grant,users,Permissions.ALLOW_DELETE); } JsonNode all= node.get("all"); if (all!=null){ alterGrantsTo(command,all,collection,docId,grant,users,Permissions.ALLOW); } } } private void alterGrantsTo(JsonNode command, JsonNode to, String collection, String docId, boolean isGrant,boolean users, Permissions permission) throws CommandParsingException, UserNotFoundException, DocumentNotFoundException, InvalidCollectionException, InvalidModelException, RoleNotFoundException { if (!to.isArray()) throw new CommandParsingException(command,"targets of permissions must be an array"); if (isGrant){ if (users){ for (JsonNode u: to){ if (!u.isTextual()) throw new CommandParsingException(command,"invalid user name specified: "+u); DocumentService.grantPermissionToUser(collection,docId,permission,u.asText()); } } else { for (JsonNode r:to){ if (!r.isTextual()) throw new CommandParsingException(command,"invalid role name specified: "+r); DocumentService.grantPermissionToRole(collection,docId,permission,r.asText()); } } } else { if (users){ for (JsonNode u: to){ if (!u.isTextual()) throw new CommandParsingException(command,"invalid user name specified: "+u); DocumentService.revokePermissionToUser(collection, docId, permission, u.asText()); } } else { for (JsonNode r:to){ if (!r.isTextual()) throw new CommandParsingException(command,"invalid role name specified: "+r); DocumentService.revokePermissionToRole(collection, docId, permission, r.asText()); } } } } private String getDocumentId(JsonNode command) throws CommandException{ JsonNode params = command.get(ScriptCommand.PARAMS); JsonNode id = params.get("id"); if (id==null||!id.isTextual()){ throw new CommandParsingException(command,"missing document id"); } String idString = id.asText(); return idString; } @Override public String name() { return RESOURCE_NAME; } }