Java tutorial
/* Copyright "Open Digital Education", 2014 * * This program is published by "Open Digital Education". * You must indicate the name of the software and the company in any production /contribution * using the software and indicate on the home page of the software industry in question, * "powered by Open Digital Education" with a reference to the website: https://opendigitaleducation.com/. * * This program is free software, licensed under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, version 3 of the License. * * You can redistribute this application and/or modify it since you respect the terms of the GNU Affero General Public License. * If you modify the source code and then use this modified source code in your creation, you must make available the source code of your modifications. * * You should have received a copy of the GNU Affero General Public License along with the software. * If not, please see : <http://www.gnu.org/licenses/>. Full compliance requires reading the terms of this license and following its directives. * */ package org.entcore.workspace.security; import fr.wseduc.webutils.request.RequestUtils; import org.entcore.common.neo4j.Neo4j; import org.entcore.common.user.DefaultFunctions; import org.entcore.workspace.Workspace; import org.entcore.workspace.controllers.QuotaController; import io.vertx.core.Handler; import io.vertx.core.eventbus.Message; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import fr.wseduc.mongodb.MongoDb; import fr.wseduc.webutils.http.Binding; import org.entcore.common.http.filter.ResourcesProvider; import org.entcore.common.user.UserInfos; import org.entcore.workspace.dao.DocumentDao; import org.entcore.workspace.service.WorkspaceService; import java.util.Arrays; import java.util.Map; public class WorkspaceResourcesProvider implements ResourcesProvider { private MongoDb mongo = MongoDb.getInstance(); @Override public void authorize(final HttpServerRequest request, final Binding binding, final UserInfos user, final Handler<Boolean> handler) { final String serviceMethod = binding.getServiceMethod(); if (serviceMethod != null && serviceMethod.startsWith(WorkspaceService.class.getName())) { String method = serviceMethod.substring(WorkspaceService.class.getName().length() + 1); switch (method) { case "getDocumentProperties": case "getDocument": authorizeGetDocument(request, user, binding.getServiceMethod(), handler); break; case "commentDocument": case "commentFolder": case "updateDocument": case "moveDocument": case "moveTrash": case "copyDocument": case "deleteDocument": case "restoreTrash": case "shareJson": case "shareResource": case "shareJsonSubmit": case "removeShare": case "getRevision": case "listRevisions": authorizeDocument(request, user, binding.getServiceMethod(), handler); break; case "deleteRevision": authorizeRevisionOwner(request, user, binding.getServiceMethod(), new Handler<Boolean>() { public void handle(Boolean check) { if (check) { authorizeDocument(request, user, serviceMethod.substring(0, WorkspaceService.class.getName().length() + 1) + "updateDocument", handler); } else { authorizeDocument(request, user, binding.getServiceMethod(), handler); } } }); break; case "deleteComment": authorizeCommentOwner(request, user, binding.getServiceMethod(), new Handler<Boolean>() { public void handle(Boolean check) { if (check) { authorizeDocument(request, user, serviceMethod.substring(0, WorkspaceService.class.getName().length() + 1) + "commentDocument", handler); } else { authorizeDocument(request, user, binding.getServiceMethod(), handler); } } }); break; case "moveDocuments": case "copyDocuments": authorizeDocuments(request, user, binding.getServiceMethod(), handler); break; case "renameDocument": case "renameFolder": authorizeOwner(request, user, binding.getServiceMethod(), handler); break; default: handler.handle(false); } } else if (serviceMethod != null && serviceMethod.startsWith(QuotaController.class.getName())) { String method = serviceMethod.substring(QuotaController.class.getName().length() + 1); switch (method) { case "getQuota": isUserOrAdmin(request, user, handler); break; case "update": isAdminFromUsers(request, user, handler); break; case "getQuotaStructure": isAdmin(request, user, handler); break; case "updateDefault": case "getQuotaGlobal": isSuperAdmin(user, handler); break; case "getDefault": UserInfos.Function adminLocal = getFunction(user, handler); if (adminLocal != null) handler.handle(true); break; default: handler.handle(false); } } } private void isUserOrAdmin(HttpServerRequest request, UserInfos user, final Handler<Boolean> handler) { final String userId = request.params().get("userId"); if (user.getUserId().equals(userId)) { handler.handle(true); return; } final UserInfos.Function adminLocal = getFunction(user, handler); if (adminLocal == null) return; String query = "MATCH (s:Structure)<-[:DEPENDS]-(:ProfileGroup)<-[:IN]-(u:User {id : {userId}}) " + "WHERE s.id IN {structures} " + "RETURN count(*) > 0 as exists "; JsonObject params = new JsonObject() .put("structures", new fr.wseduc.webutils.collections.JsonArray(adminLocal.getScope())) .put("userId", userId); Neo4j.getInstance().execute(query, params, new Handler<Message<JsonObject>>() { @Override public void handle(Message<JsonObject> message) { JsonArray res = message.body().getJsonArray("result"); handler.handle("ok".equals(message.body().getString("status")) && res != null && res.size() == 1 && res.getJsonObject(0).getBoolean("exists", false)); } }); } private void isAdminFromUsers(HttpServerRequest request, UserInfos user, final Handler<Boolean> handler) { final UserInfos.Function adminLocal = getFunction(user, handler); if (adminLocal == null) return; RequestUtils.bodyToJson(request, new Handler<JsonObject>() { @Override public void handle(JsonObject object) { String query = "MATCH (s:Structure)<-[:DEPENDS]-(:ProfileGroup)<-[:IN]-(u:User) " + "WHERE s.id IN {structures} AND u.id IN {users} " + "RETURN count(distinct u) as nb "; final JsonArray users = object.getJsonArray("users", new fr.wseduc.webutils.collections.JsonArray()); JsonObject params = new JsonObject() .put("structures", new fr.wseduc.webutils.collections.JsonArray(adminLocal.getScope())) .put("users", users); Neo4j.getInstance().execute(query, params, new Handler<Message<JsonObject>>() { @Override public void handle(Message<JsonObject> message) { JsonArray res = message.body().getJsonArray("result"); handler.handle( "ok".equals(message.body().getString("status")) && res != null && res.size() == 1 && res.getJsonObject(0).getInteger("nb", -1).equals(users.size())); } }); } }); } private void isAdmin(HttpServerRequest request, UserInfos user, Handler<Boolean> handler) { UserInfos.Function adminLocal = getFunction(user, handler); if (adminLocal == null) return; String structureId = request.params().get("structureId"); handler.handle(adminLocal.getScope().contains(structureId)); } private UserInfos.Function getFunction(UserInfos user, Handler<Boolean> handler) { Map<String, UserInfos.Function> functions = user.getFunctions(); if (functions == null || functions.isEmpty()) { handler.handle(false); return null; } if (functions.containsKey(DefaultFunctions.SUPER_ADMIN)) { handler.handle(true); return null; } UserInfos.Function adminLocal = functions.get(DefaultFunctions.ADMIN_LOCAL); if (adminLocal == null || adminLocal.getScope() == null) { handler.handle(false); return null; } return adminLocal; } private void isSuperAdmin(UserInfos user, Handler<Boolean> handler) { Map<String, UserInfos.Function> functions = user.getFunctions(); if (functions == null || functions.isEmpty()) { handler.handle(false); return; } handler.handle(functions.containsKey(DefaultFunctions.SUPER_ADMIN)); } private void authorizeDocuments(HttpServerRequest request, UserInfos user, String serviceMethod, Handler<Boolean> handler) { String ids = request.params().get("ids"); if (ids != null && !ids.trim().isEmpty()) { JsonArray idsArray = new fr.wseduc.webutils.collections.JsonArray(Arrays.asList(ids.split(","))); String query = "{ \"_id\": { \"$in\" : " + idsArray.encode() + "}, " + "\"$or\" : [{ \"owner\": \"" + user.getUserId() + "\"}, {\"shared\" : { \"$elemMatch\" : " + orSharedElementMatch(user, serviceMethod) + "}}]}"; executeCountQuery(request, DocumentDao.DOCUMENTS_COLLECTION, new JsonObject(query), idsArray.size(), handler); } else { handler.handle(false); } } private String orSharedElementMatch(UserInfos user, String serviceMethod) { String s = serviceMethod.replaceAll("\\.", "-"); StringBuilder sb = new StringBuilder(); if (user.getGroupsIds() != null) { for (String groupId : user.getGroupsIds()) { sb.append(", { \"groupId\": \"" + groupId + "\", \"" + s + "\": true }"); } } return "{ \"$or\" : [" + "{ \"userId\": \"" + user.getUserId() + "\", \"" + s + "\": true }" + sb.toString() + "]}"; } private void authorizeGetDocument(HttpServerRequest request, UserInfos user, String serviceMethod, Handler<Boolean> handler) { String id = request.params().get("id"); if (id != null && !id.trim().isEmpty()) { String query = "{ \"_id\": \"" + id + "\", \"$or\" : [{ \"owner\": \"" + user.getUserId() + "\"}, { \"protected\" : true}, { \"public\" : true}, {\"shared\" : { \"$elemMatch\" : " + orSharedElementMatch(user, serviceMethod) + "}}]}"; executeCountQuery(request, DocumentDao.DOCUMENTS_COLLECTION, new JsonObject(query), 1, handler); } else { handler.handle(false); } } private void authorizeDocument(HttpServerRequest request, UserInfos user, String serviceMethod, Handler<Boolean> handler) { String id = request.params().get("id"); if (id != null && !id.trim().isEmpty()) { String query = "{ \"_id\": \"" + id + "\", \"$or\" : [{ \"owner\": \"" + user.getUserId() + "\"}, {\"shared\" : { \"$elemMatch\" : " + orSharedElementMatch(user, serviceMethod) + "}}]}"; executeCountQuery(request, DocumentDao.DOCUMENTS_COLLECTION, new JsonObject(query), 1, handler); } else { handler.handle(false); } } private void authorizeOwner(HttpServerRequest request, UserInfos user, String serviceMethod, Handler<Boolean> handler) { String id = request.params().get("id"); if (id != null && !id.trim().isEmpty()) { String query = "{ \"_id\": \"" + id + "\", \"owner\": \"" + user.getUserId() + "\" }"; executeCountQuery(request, DocumentDao.DOCUMENTS_COLLECTION, new JsonObject(query), 1, handler); } else { handler.handle(false); } } private void authorizeRevisionOwner(HttpServerRequest request, UserInfos user, String serviceMethod, final Handler<Boolean> handler) { final String revisionId = request.params().get("revisionId"); JsonObject query = new JsonObject( "{ \"_id\": \"" + revisionId + "\", \"userId\": \"" + user.getUserId() + "\" }"); if (revisionId != null && !revisionId.trim().isEmpty()) { executeCountQuery(request, Workspace.REVISIONS_COLLECTION, query, 1, handler); } else { handler.handle(false); } } private void authorizeCommentOwner(HttpServerRequest request, UserInfos user, String serviceMethod, final Handler<Boolean> handler) { final String id = request.params().get("id"); final String commentId = request.params().get("commentId"); JsonObject query = new JsonObject("{ \"_id\": \"" + id + "\", \"comments.id\": \"" + commentId + "\", \"comments.author\": \"" + user.getUserId() + "\" }"); if (commentId != null && !commentId.trim().isEmpty()) { executeCountQuery(request, DocumentDao.DOCUMENTS_COLLECTION, query, 1, handler); } else { handler.handle(false); } } private void executeCountQuery(final HttpServerRequest request, String collection, JsonObject query, final int expectedCountResult, final Handler<Boolean> handler) { request.pause(); mongo.count(collection, query, new Handler<Message<JsonObject>>() { @Override public void handle(Message<JsonObject> event) { request.resume(); JsonObject res = event.body(); handler.handle(res != null && "ok".equals(res.getString("status")) && expectedCountResult == res.getInteger("count")); } }); } }