From ef16561272dd3c5cf3630e7727b66b4f84f16afc Mon Sep 17 00:00:00 2001 From: jendib Date: Thu, 24 Mar 2016 00:35:53 +0100 Subject: [PATCH 01/42] Fix PDF export if description is null --- .../src/main/java/com/sismics/docs/core/util/pdf/PdfPage.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/pdf/PdfPage.java b/docs-core/src/main/java/com/sismics/docs/core/util/pdf/PdfPage.java index af9aafb2..0adae219 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/pdf/PdfPage.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/pdf/PdfPage.java @@ -99,6 +99,10 @@ public class PdfPage implements Closeable { * @throws IOException */ private void drawText(float paragraphWidth, PDFont font, int fontSize, String text, boolean centered) throws IOException { + if (text == null) { + return; + } + pdContent.setFont(font, fontSize); int start = 0; int end = 0; From 274512a58e52df2852adc314e3eb286409659264 Mon Sep 17 00:00:00 2001 From: jendib Date: Thu, 24 Mar 2016 00:41:31 +0100 Subject: [PATCH 02/42] Fix if a file is deleted before text extraction is finished --- .../main/java/com/sismics/docs/core/dao/jpa/FileDao.java | 6 ++++-- .../docs/core/listener/async/FileCreatedAsyncListener.java | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java index 96fe6eab..ed2036f8 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/FileDao.java @@ -155,10 +155,12 @@ public class FileDao { * @param id File ID * @return File */ - public File getById(String id) { + public File getActiveById(String id) { EntityManager em = ThreadLocalContext.get().getEntityManager(); + Query q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null"); + q.setParameter("id", id); try { - return em.find(File.class, id); + return (File) q.getSingleResult(); } catch (NoResultException e) { return null; } diff --git a/docs-core/src/main/java/com/sismics/docs/core/listener/async/FileCreatedAsyncListener.java b/docs-core/src/main/java/com/sismics/docs/core/listener/async/FileCreatedAsyncListener.java index a5301769..e2244ad5 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/listener/async/FileCreatedAsyncListener.java +++ b/docs-core/src/main/java/com/sismics/docs/core/listener/async/FileCreatedAsyncListener.java @@ -54,7 +54,7 @@ public class FileCreatedAsyncListener { @Override public void run() { FileDao fileDao = new FileDao(); - if (fileDao.getById(file.getId()) == null) { + if (fileDao.getActiveById(file.getId()) == null) { // The file has been deleted since the text extraction started, ignore the result return; } From 8ad9c529b68d1502ba055225da43d60e9f2da0f4 Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 9 Apr 2016 21:23:55 +0200 Subject: [PATCH 03/42] #79: Resource to generate a dynamic CSS --- .../docs/rest/resource/ThemeResource.java | 29 +++++++++++++++++++ docs-web/src/main/webapp/src/index.html | 1 + docs-web/src/main/webapp/src/share.html | 1 + .../sismics/docs/rest/TestThemeResource.java | 21 ++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java create mode 100644 docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java new file mode 100644 index 00000000..a8f72deb --- /dev/null +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java @@ -0,0 +1,29 @@ +package com.sismics.docs.rest.resource; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +/** + * Theme REST resources. + * + * @author bgamard + */ +@Path("/theme") +public class ThemeResource extends BaseResource { + /** + * Returns custom CSS stylesheet. + * + * @return Response + */ + @GET + @Path("/stylesheet") + @Produces("text/css") + public Response stylesheet() { + StringBuilder sb = new StringBuilder(); + sb.append("body {\n"); + sb.append("}"); + return Response.ok().entity(sb.toString()).build(); + } +} diff --git a/docs-web/src/main/webapp/src/index.html b/docs-web/src/main/webapp/src/index.html index 7772ef24..15e59c41 100644 --- a/docs-web/src/main/webapp/src/index.html +++ b/docs-web/src/main/webapp/src/index.html @@ -10,6 +10,7 @@ + + diff --git a/docs-web/src/main/webapp/src/partial/docs/settings.html b/docs-web/src/main/webapp/src/partial/docs/settings.html index e9799ae9..6594764f 100644 --- a/docs-web/src/main/webapp/src/partial/docs/settings.html +++ b/docs-web/src/main/webapp/src/partial/docs/settings.html @@ -15,6 +15,7 @@ Users Groups Vocabularies + Theme Server logs diff --git a/docs-web/src/main/webapp/src/partial/docs/settings.theme.html b/docs-web/src/main/webapp/src/partial/docs/settings.theme.html new file mode 100644 index 00000000..093a8521 --- /dev/null +++ b/docs-web/src/main/webapp/src/partial/docs/settings.theme.html @@ -0,0 +1,8 @@ +

Theme customization

+
+
+ +   +
+
\ No newline at end of file From 542ab737a24e71c844182aab729864d403345fe1 Mon Sep 17 00:00:00 2001 From: jendib Date: Wed, 27 Apr 2016 00:05:25 +0200 Subject: [PATCH 15/42] #79: POST /theme, GET /theme --- .../docs/rest/resource/ThemeResource.java | 51 ++++++++++++++++++- .../sismics/docs/rest/TestThemeResource.java | 19 +++++-- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java index d1576c44..abf6ee13 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java @@ -10,8 +10,11 @@ import com.sismics.docs.core.dao.jpa.ConfigDao; import com.sismics.docs.core.model.jpa.Config; import com.sismics.docs.rest.constant.BaseFunction; import com.sismics.rest.exception.ForbiddenClientException; +import com.sismics.rest.util.JsonUtil; import com.sismics.rest.util.ValidationUtil; import com.sismics.util.css.Selector; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataParam; import java.io.StringReader; import java.util.Map; @@ -38,13 +41,36 @@ public class ThemeResource extends BaseResource { StringBuilder sb = new StringBuilder(); sb.append(new Selector(".navbar") .rule("background-color", themeConfig.getString("color", "#263238"))); + sb.append(themeConfig.getString("css", "")); return Response.ok().entity(sb.toString()).build(); } + /** + * Returns the theme configuration. + * + * @return Response + */ + @GET + public Response get() { + JsonObject themeConfig = getThemeConfig(); + JsonObjectBuilder json = Json.createObjectBuilder(); + json.add("name", themeConfig.getString("name", "Sismics Docs")); + return Response.ok().entity(json.build()).build(); + } + + /** + * Change the theme configuration. + * + * @param color Theme color + * @param name Application name + * @param css Custom CSS + * @return Response + */ @POST - @Path("/color") - public Response color(@FormParam("color") String color) { + public Response theme(@FormParam("color") String color, + @FormParam("name") String name, + @FormParam("css") String css) { if (!authenticate()) { throw new ForbiddenClientException(); } @@ -52,6 +78,7 @@ public class ThemeResource extends BaseResource { // Validate input data ValidationUtil.validateHexColor(color, "color", true); + name = ValidationUtil.validateLength(name, "name", 3, 30, true); // Update the JSON JsonObjectBuilder json = getMutableThemeConfig(); @@ -60,6 +87,12 @@ public class ThemeResource extends BaseResource { } else { json.add("color", color); } + if (Strings.isNullOrEmpty(name)) { + json.add("name", JsonValue.NULL); + } else { + json.add("name", name); + } + json.add("css", JsonUtil.nullable(css)); // Persist the new configuration ConfigDao configDao = new ConfigDao(); @@ -71,6 +104,20 @@ public class ThemeResource extends BaseResource { return Response.ok().entity(response.build()).build(); } + @PUT + @Path("images") + @Consumes("multipart/form-data") + public Response images( + @FormDataParam("logo") FormDataBodyPart logoBodyPart, + @FormDataParam("background") FormDataBodyPart backgrounBodyPart) { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + checkBaseFunction(BaseFunction.ADMIN); + + return Response.ok().build(); + } + /** * Returns the theme configuration object. * diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java index 2de11477..61c06cef 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java @@ -25,17 +25,30 @@ public class TestThemeResource extends BaseJerseyTest { // Get the stylesheet anonymously String stylesheet = target().path("/theme/stylesheet").request() .get(String.class); - Assert.assertTrue(stylesheet.contains("background-color: inherit;")); + Assert.assertTrue(stylesheet.contains("background-color: #263238;")); + + // Get the theme configuration anonymously + JsonObject json = target().path("/theme").request() + .get(JsonObject.class); + Assert.assertEquals("Sismics Docs", json.getString("name")); // Update the main color as admin - target().path("/theme/color").request() + target().path("/theme").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) .post(Entity.form(new Form() - .param("color", "#ff0000")), JsonObject.class); + .param("color", "#ff0000") + .param("name", "My App") + .param("css", ".body { content: 'Custom CSS'; }")), JsonObject.class); // Get the stylesheet anonymously stylesheet = target().path("/theme/stylesheet").request() .get(String.class); Assert.assertTrue(stylesheet.contains("background-color: #ff0000;")); + Assert.assertTrue(stylesheet.contains("Custom CSS")); + + // Get the theme configuration anonymously + json = target().path("/theme").request() + .get(JsonObject.class); + Assert.assertEquals("My App", json.getString("name")); } } \ No newline at end of file From 09a53d5c4e18a492bd3b3b12cbdbb386cb362773 Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 30 Apr 2016 01:52:24 +0200 Subject: [PATCH 16/42] #83: Handles tags as source ACL in GET /document/list --- .../com/sismics/docs/core/dao/jpa/AclDao.java | 16 +++++-------- .../docs/core/dao/jpa/DocumentDao.java | 23 ++++++++++--------- .../docs/rest/resource/AclResource.java | 2 +- .../sismics/docs/rest/TestAclResource.java | 6 ++--- .../docs/rest/TestDocumentResource.java | 12 +++++----- 5 files changed, 28 insertions(+), 31 deletions(-) diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java index 96989163..1338770c 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java @@ -28,7 +28,6 @@ public class AclDao { * @param acl ACL * @param userId User ID * @return New ID - * @throws Exception */ public String create(Acl acl, String userId) { // Create the UUID @@ -82,7 +81,7 @@ public class AclDao { List l = q.getResultList(); // Assemble results - List aclDtoList = new ArrayList(); + List aclDtoList = new ArrayList<>(); for (Object[] o : l) { int i = 0; AclDto aclDto = new AclDto(); @@ -92,7 +91,7 @@ public class AclDao { String userName = (String) o[i++]; String shareId = (String) o[i++]; String shareName = (String) o[i++]; - String groupName = (String) o[i++]; + String groupName = (String) o[i]; if (userName != null) { aclDto.setTargetName(userName); aclDto.setTargetType(AclTargetType.USER.name()); @@ -114,11 +113,12 @@ public class AclDao { * Check if a source is accessible to a target. * * @param sourceId ACL source entity ID - * @parm perm Necessary permission - * @param targetId ACL target entity ID + * @param perm Necessary permission + * @param targetIdList List of targets * @return True if the document is accessible */ public boolean checkPermission(String sourceId, PermType perm, List targetIdList) { + // TODO Handle tags as source for ACL EntityManager em = ThreadLocalContext.get().getEntityManager(); Query q = em.createQuery("select a from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId in (:targetIdList) and a.deleteDate is null"); q.setParameter("sourceId", sourceId); @@ -126,11 +126,7 @@ public class AclDao { q.setParameter("targetIdList", targetIdList); // We have a matching permission - if (q.getResultList().size() > 0) { - return true; - } - - return false; + return q.getResultList().size() > 0; } /** diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java index 95c9b579..4ed2c9d4 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java @@ -40,7 +40,6 @@ public class DocumentDao { * @param document Document * @param userId User ID * @return New ID - * @throws Exception */ public String create(Document document, String userId) { // Create the UUID @@ -87,10 +86,11 @@ public class DocumentDao { * * @param id Document ID * @param perm Permission needed - * @param userId User ID + * @param targetIdList List of targets * @return Document */ public DocumentDto getDocument(String id, PermType perm, List targetIdList) { + // TODO Handle tags as source for ACL EntityManager em = ThreadLocalContext.get().getEntityManager(); StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C, d.DOC_TITLE_C, d.DOC_DESCRIPTION_C, d.DOC_SUBJECT_C, d.DOC_IDENTIFIER_C, d.DOC_PUBLISHER_C, d.DOC_FORMAT_C, d.DOC_SOURCE_C, d.DOC_TYPE_C, d.DOC_COVERAGE_C, d.DOC_RIGHTS_C, d.DOC_CREATEDATE_D, d.DOC_LANGUAGE_C, "); sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null), "); @@ -106,7 +106,7 @@ public class DocumentDao { q.setParameter("perm", perm.name()); q.setParameter("targetIdList", targetIdList); - Object[] o = null; + Object[] o; try { o = (Object[]) q.getSingleResult(); } catch (NoResultException e) { @@ -130,7 +130,7 @@ public class DocumentDao { documentDto.setLanguage((String) o[i++]); documentDto.setShared(((Number) o[i++]).intValue() > 0); documentDto.setFileCount(((Number) o[i++]).intValue()); - documentDto.setCreator((String) o[i++]); + documentDto.setCreator((String) o[i]); return documentDto; } @@ -200,12 +200,11 @@ public class DocumentDao { * @param paginatedList List of documents (updated by side effects) * @param criteria Search criteria * @param sortCriteria Sort criteria - * @return List of documents - * @throws Exception + * @throws Exception */ public void findByCriteria(PaginatedList paginatedList, DocumentCriteria criteria, SortCriteria sortCriteria) throws Exception { - Map parameterMap = new HashMap(); - List criteriaList = new ArrayList(); + Map parameterMap = new HashMap<>(); + List criteriaList = new ArrayList<>(); StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3, d.DOC_LANGUAGE_C c4, "); sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null) c5, "); @@ -215,7 +214,9 @@ public class DocumentDao { // Adds search criteria if (criteria.getTargetIdList() != null) { // Read permission is enough for searching - sb.append(" join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C in (:targetIdList) and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null "); + sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null "); + sb.append(" left join T_ACL a2 on a2.ACL_TARGETID_C in (:targetIdList) and a2.ACL_SOURCEID_C in (select dta.DOT_IDTAG_C from T_DOCUMENT_TAG dta where dta.DOT_IDDOCUMENT_C = d.DOC_ID_C) and a2.ACL_PERM_C = 'READ' and a2.ACL_DELETEDATE_D is null "); + criteriaList.add("(a.ACL_ID_C is not null or a2.ACL_ID_C is not null)"); parameterMap.put("targetIdList", criteria.getTargetIdList()); } if (!Strings.isNullOrEmpty(criteria.getSearch()) || !Strings.isNullOrEmpty(criteria.getFullSearch())) { @@ -239,7 +240,7 @@ public class DocumentDao { if (criteria.getTagIdList() != null && !criteria.getTagIdList().isEmpty()) { int index = 0; for (String tagId : criteria.getTagIdList()) { - sb.append(" join T_DOCUMENT_TAG dt" + index + " on dt" + index + ".DOT_IDDOCUMENT_C = d.DOC_ID_C and dt" + index + ".DOT_IDTAG_C = :tagId" + index + " and dt" + index + ".DOT_DELETEDATE_D is null "); + sb.append(String.format(" join T_DOCUMENT_TAG dt%d on dt%d.DOT_IDDOCUMENT_C = d.DOC_ID_C and dt%d.DOT_IDTAG_C = :tagId%d and dt%d.DOT_DELETEDATE_D is null ", index, index, index, index, index)); parameterMap.put("tagId" + index, tagId); index++; } @@ -278,7 +279,7 @@ public class DocumentDao { documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime()); documentDto.setLanguage((String) o[i++]); documentDto.setShared(((Number) o[i++]).intValue() > 0); - documentDto.setFileCount(((Number) o[i++]).intValue()); + documentDto.setFileCount(((Number) o[i]).intValue()); documentDtoList.add(documentDto); } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java index 71afdf40..59be252e 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java @@ -48,7 +48,7 @@ public class AclResource extends BaseResource { * @param sourceId Source ID * @param permStr Permission * @param targetName Target name - * @param type ACL type + * @param typeStr ACL type * @return Response */ @PUT diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java index bace64ef..35cf277f 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java @@ -71,7 +71,7 @@ public class TestAclResource extends BaseJerseyTest { String acl2Id = json.getString("id"); // Add an ACL WRITE for acl2 with acl1 - json = target().path("/acl").request() + target().path("/acl").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl1Token) .put(Entity.form(new Form() .param("source", document1Id) @@ -80,7 +80,7 @@ public class TestAclResource extends BaseJerseyTest { .param("type", "USER")), JsonObject.class); // Add an ACL WRITE for acl2 with acl1 (again) - json = target().path("/acl").request() + target().path("/acl").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl1Token) .put(Entity.form(new Form() .param("source", document1Id) @@ -99,7 +99,7 @@ public class TestAclResource extends BaseJerseyTest { String aclGroup2Id = json.getString("id"); // Add an ACL WRITE for aclGroup2 with acl1 - json = target().path("/acl").request() + target().path("/acl").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl1Token) .put(Entity.form(new Form() .param("source", document1Id) diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java index 4f7b4ee8..d507c74a 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java @@ -87,7 +87,7 @@ public class TestDocumentResource extends BaseJerseyTest { Assert.assertNotNull(document2Id); // Add a file - String file1Id = null; + String file1Id; try (InputStream is = Resources.getResource("file/Einstein-Roosevelt-letter.png").openStream()) { StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "Einstein-Roosevelt-letter.png"); try (FormDataMultiPart multiPart = new FormDataMultiPart()) { @@ -103,7 +103,7 @@ public class TestDocumentResource extends BaseJerseyTest { } // Share this document - json = target().path("/share").request() + target().path("/share").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, document1Token) .put(Entity.form(new Form().param("id", document1Id)), JsonObject.class); @@ -148,7 +148,7 @@ public class TestDocumentResource extends BaseJerseyTest { Assert.assertNotNull(document3Id); // Add a file - String file3Id = null; + String file3Id; try (InputStream is = Resources.getResource("file/Einstein-Roosevelt-letter.png").openStream()) { StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "Einstein-Roosevelt-letter.png"); try (FormDataMultiPart multiPart = new FormDataMultiPart()) { @@ -393,7 +393,7 @@ public class TestDocumentResource extends BaseJerseyTest { Assert.assertNotNull(document1Id); // Add a PDF file - String file1Id = null; + String file1Id; try (InputStream is = Resources.getResource("file/document.odt").openStream()) { StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "document.odt"); try (FormDataMultiPart multiPart = new FormDataMultiPart()) { @@ -452,7 +452,7 @@ public class TestDocumentResource extends BaseJerseyTest { Assert.assertNotNull(document1Id); // Add a PDF file - String file1Id = null; + String file1Id; try (InputStream is = Resources.getResource("file/document.docx").openStream()) { StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "document.docx"); try (FormDataMultiPart multiPart = new FormDataMultiPart()) { @@ -511,7 +511,7 @@ public class TestDocumentResource extends BaseJerseyTest { Assert.assertNotNull(document1Id); // Add a PDF file - String file1Id = null; + String file1Id; try (InputStream is = Resources.getResource("file/wikipedia.pdf").openStream()) { StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "wikipedia.pdf"); try (FormDataMultiPart multiPart = new FormDataMultiPart()) { From 0f661e5a34107b4b60607ff094823fc849d8257e Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 30 Apr 2016 02:17:04 +0200 Subject: [PATCH 17/42] #83: Handles tags as source ACL for single document --- .../com/sismics/docs/core/dao/jpa/DocumentDao.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java index 4ed2c9d4..3b908076 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java @@ -90,7 +90,6 @@ public class DocumentDao { * @return Document */ public DocumentDto getDocument(String id, PermType perm, List targetIdList) { - // TODO Handle tags as source for ACL EntityManager em = ThreadLocalContext.get().getEntityManager(); StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C, d.DOC_TITLE_C, d.DOC_DESCRIPTION_C, d.DOC_SUBJECT_C, d.DOC_IDENTIFIER_C, d.DOC_PUBLISHER_C, d.DOC_FORMAT_C, d.DOC_SOURCE_C, d.DOC_TYPE_C, d.DOC_COVERAGE_C, d.DOC_RIGHTS_C, d.DOC_CREATEDATE_D, d.DOC_LANGUAGE_C, "); sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null), "); @@ -98,8 +97,10 @@ public class DocumentDao { sb.append(" u.USE_USERNAME_C "); sb.append(" from T_DOCUMENT d "); sb.append(" join T_USER u on d.DOC_IDUSER_C = u.USE_ID_C "); - sb.append(" left join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C in (:targetIdList) and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null "); - sb.append(" where d.DOC_ID_C = :id and a.ACL_ID_C is not null and d.DOC_DELETEDATE_D is null "); + sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null "); + sb.append(" left join T_DOCUMENT_TAG dta on dta.DOT_IDDOCUMENT_C = d.DOC_ID_C and dta.DOT_DELETEDATE_D is null "); + sb.append(" left join T_ACL a2 on a2.ACL_TARGETID_C in (:targetIdList) and a2.ACL_SOURCEID_C = dta.DOT_IDTAG_C and a2.ACL_PERM_C = 'READ' and a2.ACL_DELETEDATE_D is null "); + sb.append(" where d.DOC_ID_C = :id and (a.ACL_ID_C is not null or a2.ACL_ID_C is not null) and d.DOC_DELETEDATE_D is null "); Query q = em.createNativeQuery(sb.toString()); q.setParameter("id", id); @@ -215,7 +216,8 @@ public class DocumentDao { if (criteria.getTargetIdList() != null) { // Read permission is enough for searching sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null "); - sb.append(" left join T_ACL a2 on a2.ACL_TARGETID_C in (:targetIdList) and a2.ACL_SOURCEID_C in (select dta.DOT_IDTAG_C from T_DOCUMENT_TAG dta where dta.DOT_IDDOCUMENT_C = d.DOC_ID_C) and a2.ACL_PERM_C = 'READ' and a2.ACL_DELETEDATE_D is null "); + sb.append(" left join T_DOCUMENT_TAG dta on dta.DOT_IDDOCUMENT_C = d.DOC_ID_C and dta.DOT_DELETEDATE_D is null "); + sb.append(" left join T_ACL a2 on a2.ACL_TARGETID_C in (:targetIdList) and a2.ACL_SOURCEID_C = dta.DOT_IDTAG_C and a2.ACL_PERM_C = 'READ' and a2.ACL_DELETEDATE_D is null "); criteriaList.add("(a.ACL_ID_C is not null or a2.ACL_ID_C is not null)"); parameterMap.put("targetIdList", criteria.getTargetIdList()); } From ddf9e83a9b9873ccf45950dc5ae13cf00160287e Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 1 May 2016 22:03:39 +0200 Subject: [PATCH 18/42] #83: Permission check for tags --- .../com/sismics/docs/core/dao/jpa/AclDao.java | 13 ++++++--- .../docs/core/dao/jpa/DocumentDao.java | 14 +++++----- .../sismics/docs/core/dao/jpa/GroupDao.java | 14 +++++----- .../com/sismics/docs/core/dao/jpa/TagDao.java | 12 ++++++++- .../sismics/docs/core/dao/jpa/UserDao.java | 27 ++++--------------- .../sismics/docs/rest/TestGroupResource.java | 6 ++--- 6 files changed, 41 insertions(+), 45 deletions(-) diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java index 1338770c..7ecbf61c 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java @@ -118,13 +118,18 @@ public class AclDao { * @return True if the document is accessible */ public boolean checkPermission(String sourceId, PermType perm, List targetIdList) { - // TODO Handle tags as source for ACL EntityManager em = ThreadLocalContext.get().getEntityManager(); - Query q = em.createQuery("select a from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId in (:targetIdList) and a.deleteDate is null"); + StringBuilder sb = new StringBuilder("select a.ACL_ID_C from T_ACL a "); + sb.append(" where a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = :sourceId and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null "); + sb.append(" union all "); + sb.append(" select a.ACL_ID_C from T_ACL a, T_DOCUMENT_TAG dt "); + sb.append(" where a.ACL_SOURCEID_C = dt.DOT_IDTAG_C and dt.DOT_IDDOCUMENT_C = :sourceId and dt.DOT_DELETEDATE_D is null "); + sb.append(" and a.ACL_TARGETID_C in (:targetIdList) and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null "); + Query q = em.createNativeQuery(sb.toString()); q.setParameter("sourceId", sourceId); - q.setParameter("perm", perm); + q.setParameter("perm", perm.name()); q.setParameter("targetIdList", targetIdList); - + // We have a matching permission return q.getResultList().size() > 0; } diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java index 3b908076..7a28b2ab 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java @@ -90,6 +90,11 @@ public class DocumentDao { * @return Document */ public DocumentDto getDocument(String id, PermType perm, List targetIdList) { + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(id, perm, targetIdList)) { + return null; + } + EntityManager em = ThreadLocalContext.get().getEntityManager(); StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C, d.DOC_TITLE_C, d.DOC_DESCRIPTION_C, d.DOC_SUBJECT_C, d.DOC_IDENTIFIER_C, d.DOC_PUBLISHER_C, d.DOC_FORMAT_C, d.DOC_SOURCE_C, d.DOC_TYPE_C, d.DOC_COVERAGE_C, d.DOC_RIGHTS_C, d.DOC_CREATEDATE_D, d.DOC_LANGUAGE_C, "); sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null), "); @@ -97,16 +102,11 @@ public class DocumentDao { sb.append(" u.USE_USERNAME_C "); sb.append(" from T_DOCUMENT d "); sb.append(" join T_USER u on d.DOC_IDUSER_C = u.USE_ID_C "); - sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null "); - sb.append(" left join T_DOCUMENT_TAG dta on dta.DOT_IDDOCUMENT_C = d.DOC_ID_C and dta.DOT_DELETEDATE_D is null "); - sb.append(" left join T_ACL a2 on a2.ACL_TARGETID_C in (:targetIdList) and a2.ACL_SOURCEID_C = dta.DOT_IDTAG_C and a2.ACL_PERM_C = 'READ' and a2.ACL_DELETEDATE_D is null "); - sb.append(" where d.DOC_ID_C = :id and (a.ACL_ID_C is not null or a2.ACL_ID_C is not null) and d.DOC_DELETEDATE_D is null "); + sb.append(" where d.DOC_ID_C = :id and d.DOC_DELETEDATE_D is null "); Query q = em.createNativeQuery(sb.toString()); q.setParameter("id", id); - q.setParameter("perm", perm.name()); - q.setParameter("targetIdList", targetIdList); - + Object[] o; try { o = (Object[]) q.getSingleResult(); diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/GroupDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/GroupDao.java index a4f6735b..b15e8934 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/GroupDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/GroupDao.java @@ -71,7 +71,6 @@ public class GroupDao { * @param group Group * @param userId User ID * @return New ID - * @throws Exception */ public String create(Group group, String userId) { // Create the UUID @@ -127,9 +126,8 @@ public class GroupDao { /** * Add an user to a group. * - * @param group Group + * @param userGroup User group * @return New ID - * @throws Exception */ public String addMember(UserGroup userGroup) { // Create the UUID @@ -170,8 +168,8 @@ public class GroupDao { * @return List of groups */ public List findByCriteria(GroupCriteria criteria, SortCriteria sortCriteria) { - Map parameterMap = new HashMap(); - List criteriaList = new ArrayList(); + Map parameterMap = new HashMap<>(); + List criteriaList = new ArrayList<>(); StringBuilder sb = new StringBuilder("select g.GRP_ID_C as c0, g.GRP_NAME_C as c1, g.GRP_IDPARENT_C as c2, gp.GRP_NAME_C as c3, g.GRP_IDROLE_C "); if (criteria.getUserId() != null) { @@ -187,8 +185,8 @@ public class GroupDao { } if (criteria.getUserId() != null) { // Left join and post-filtering for recursive groups - sb.append((criteria.isRecursive() ? " left " : "") - + " join T_USER_GROUP ug on ug.UGP_IDGROUP_C = g.GRP_ID_C and ug.UGP_IDUSER_C = :userId and ug.UGP_DELETEDATE_D is null "); + sb.append(criteria.isRecursive() ? " left " : ""); + sb.append(" join T_USER_GROUP ug on ug.UGP_IDGROUP_C = g.GRP_ID_C and ug.UGP_IDUSER_C = :userId and ug.UGP_DELETEDATE_D is null "); parameterMap.put("userId", criteria.getUserId()); } @@ -216,7 +214,7 @@ public class GroupDao { .setParentName((String) o[i++]) .setRoleId((String) o[i++]); groupDtoList.add(groupDto); - if (criteria.getUserId() != null && o[i++] != null) { + if (criteria.getUserId() != null && o[i] != null) { userGroupDtoList.add(groupDto); } } diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java index 66f3e20d..ac5a9b59 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java @@ -46,6 +46,7 @@ public class TagDao { */ @SuppressWarnings("unchecked") public List getByUserId(String userId) { + // TODO Use ACLs EntityManager em = ThreadLocalContext.get().getEntityManager(); Query q = em.createQuery("select t from Tag t where t.userId = :userId and t.deleteDate is null order by t.name"); q.setParameter("userId", userId); @@ -96,12 +97,13 @@ public class TagDao { /** * Returns tag list on a document. - * + * * @param documentId Document ID * @return List of tags */ @SuppressWarnings("unchecked") public List getByDocumentId(String documentId, String userId) { + // TODO Use ACLs EntityManager em = ThreadLocalContext.get().getEntityManager(); StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C, t.TAG_IDPARENT_C from T_DOCUMENT_TAG dt "); sb.append(" join T_TAG t on t.TAG_ID_C = dt.DOT_IDTAG_C "); @@ -196,6 +198,7 @@ public class TagDao { * @return Tag */ public Tag getByName(String userId, String name) { + // TODO Use ACLs EntityManager em = ThreadLocalContext.get().getEntityManager(); Query q = em.createQuery("select t from Tag t where t.name = :name and t.userId = :userId and t.deleteDate is null"); q.setParameter("userId", userId); @@ -215,6 +218,7 @@ public class TagDao { * @return Tag */ public Tag getByTagId(String userId, String tagId) { + // TODO Use ACLs EntityManager em = ThreadLocalContext.get().getEntityManager(); Query q = em.createQuery("select t from Tag t where t.id = :tagId and t.userId = :userId and t.deleteDate is null"); q.setParameter("userId", userId); @@ -249,6 +253,11 @@ public class TagDao { q.setParameter("dateNow", dateNow); q.setParameter("tagId", tagId); q.executeUpdate(); + + q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :tagId and a.deleteDate is null"); + q.setParameter("tagId", tagId); + q.setParameter("dateNow", dateNow); + q.executeUpdate(); // Create audit log AuditLogUtil.create(tagDb, AuditLogType.DELETE, userId); @@ -262,6 +271,7 @@ public class TagDao { */ @SuppressWarnings("unchecked") public List findByName(String userId, String name) { + // TODO Use ACLs EntityManager em = ThreadLocalContext.get().getEntityManager(); Query q = em.createQuery("select t from Tag t where t.name like :name and t.userId = :userId and t.deleteDate is null"); q.setParameter("userId", userId); diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java index bc392947..216177cd 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java @@ -188,23 +188,6 @@ public class UserDao { } } - /** - * Gets an active user by its password recovery token. - * - * @param passwordResetKey Password recovery token - * @return User - */ - public User getActiveByPasswordResetKey(String passwordResetKey) { - EntityManager em = ThreadLocalContext.get().getEntityManager(); - try { - Query q = em.createQuery("select u from User u where u.passwordResetKey = :passwordResetKey and u.deleteDate is null"); - q.setParameter("passwordResetKey", passwordResetKey); - return (User) q.getSingleResult(); - } catch (NoResultException e) { - return null; - } - } - /** * Deletes a user. * @@ -258,7 +241,7 @@ public class UserDao { * @param password Clear password * @return Hashed password */ - protected String hashPassword(String password) { + private String hashPassword(String password) { return BCrypt.hashpw(password, BCrypt.gensalt()); } @@ -270,8 +253,8 @@ public class UserDao { * @return List of users */ public List findByCriteria(UserCriteria criteria, SortCriteria sortCriteria) { - Map parameterMap = new HashMap(); - List criteriaList = new ArrayList(); + Map parameterMap = new HashMap<>(); + List criteriaList = new ArrayList<>(); StringBuilder sb = new StringBuilder("select u.USE_ID_C as c0, u.USE_USERNAME_C as c1, u.USE_EMAIL_C as c2, u.USE_CREATEDATE_D as c3, u.USE_STORAGECURRENT_N as c4, u.USE_STORAGEQUOTA_N as c5"); sb.append(" from T_USER u "); @@ -300,7 +283,7 @@ public class UserDao { List l = QueryUtil.getNativeQuery(queryParam).getResultList(); // Assemble results - List userDtoList = new ArrayList(); + List userDtoList = new ArrayList<>(); for (Object[] o : l) { int i = 0; UserDto userDto = new UserDto(); @@ -309,7 +292,7 @@ public class UserDao { userDto.setEmail((String) o[i++]); userDto.setCreateTimestamp(((Timestamp) o[i++]).getTime()); userDto.setStorageCurrent(((Number) o[i++]).longValue()); - userDto.setStorageQuota(((Number) o[i++]).longValue()); + userDto.setStorageQuota(((Number) o[i]).longValue()); userDtoList.add(userDto); } return userDtoList; diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestGroupResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestGroupResource.java index c37ad04c..1d5d37a9 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestGroupResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestGroupResource.java @@ -106,7 +106,7 @@ public class TestGroupResource extends BaseJerseyTest { Assert.assertEquals(1, users.size()); // Add group1 to g112 (again) - json = target().path("/group/g112").request() + target().path("/group/g112").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) .put(Entity.form(new Form() .param("username", "group1")), JsonObject.class); @@ -145,7 +145,7 @@ public class TestGroupResource extends BaseJerseyTest { Assert.assertEquals("group1", members.getString(0)); // Remove group1 from g12new - json = target().path("/group/g12new/group1").request() + target().path("/group/g12new/group1").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) .delete(JsonObject.class); @@ -164,7 +164,7 @@ public class TestGroupResource extends BaseJerseyTest { Assert.assertTrue(groupList.contains("g112")); // Delete group g1 - json = target().path("/group/g1").request() + target().path("/group/g1").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) .delete(JsonObject.class); From 27027ec412926dabbd66667cebfcd32b5f7cfc72 Mon Sep 17 00:00:00 2001 From: jendib Date: Thu, 5 May 2016 02:34:33 +0200 Subject: [PATCH 19/42] #83: Tag DAO refactoring --- .../docs/core/dao/jpa/DocumentDao.java | 2 +- .../com/sismics/docs/core/dao/jpa/TagDao.java | 184 +++++++---------- .../core/dao/jpa/criteria/TagCriteria.java | 78 ++++++++ .../sismics/docs/core/dao/jpa/dto/TagDto.java | 12 +- .../docs/rest/resource/AclResource.java | 20 +- .../docs/rest/resource/DocumentResource.java | 189 ++++++++++-------- .../docs/rest/resource/TagResource.java | 75 ++++--- 7 files changed, 333 insertions(+), 227 deletions(-) create mode 100644 docs-core/src/main/java/com/sismics/docs/core/dao/jpa/criteria/TagCriteria.java diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java index 7a28b2ab..403f2ee2 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java @@ -212,7 +212,7 @@ public class DocumentDao { sb.append(" (select count(f.FIL_ID_C) from T_FILE f where f.FIL_DELETEDATE_D is null and f.FIL_IDDOC_C = d.DOC_ID_C) c6 "); sb.append(" from T_DOCUMENT d "); - // Adds search criteria + // Add search criterias if (criteria.getTargetIdList() != null) { // Read permission is enough for searching sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null "); diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java index ac5a9b59..e6f37a2d 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java @@ -1,21 +1,24 @@ package com.sismics.docs.core.dao.jpa; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.util.*; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.Query; +import com.google.common.base.Joiner; import com.sismics.docs.core.constant.AuditLogType; +import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria; +import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; +import com.sismics.docs.core.dao.jpa.dto.GroupDto; import com.sismics.docs.core.dao.jpa.dto.TagDto; import com.sismics.docs.core.dao.jpa.dto.TagStatDto; import com.sismics.docs.core.model.jpa.DocumentTag; import com.sismics.docs.core.model.jpa.Tag; import com.sismics.docs.core.util.AuditLogUtil; +import com.sismics.docs.core.util.jpa.QueryParam; +import com.sismics.docs.core.util.jpa.QueryUtil; +import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.util.context.ThreadLocalContext; /** @@ -39,20 +42,6 @@ public class TagDao { } } - /** - * Returns the list of all tags. - * - * @return List of tags - */ - @SuppressWarnings("unchecked") - public List getByUserId(String userId) { - // TODO Use ACLs - EntityManager em = ThreadLocalContext.get().getEntityManager(); - Query q = em.createQuery("select t from Tag t where t.userId = :userId and t.deleteDate is null order by t.name"); - q.setParameter("userId", userId); - return q.getResultList(); - } - /** * Update tags on a document. * @@ -94,42 +83,6 @@ public class TagDao { } } } - - /** - * Returns tag list on a document. - * - * @param documentId Document ID - * @return List of tags - */ - @SuppressWarnings("unchecked") - public List getByDocumentId(String documentId, String userId) { - // TODO Use ACLs - EntityManager em = ThreadLocalContext.get().getEntityManager(); - StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C, t.TAG_IDPARENT_C from T_DOCUMENT_TAG dt "); - sb.append(" join T_TAG t on t.TAG_ID_C = dt.DOT_IDTAG_C "); - sb.append(" where dt.DOT_IDDOCUMENT_C = :documentId and t.TAG_DELETEDATE_D is null "); - sb.append(" and t.TAG_IDUSER_C = :userId and dt.DOT_DELETEDATE_D is null "); - sb.append(" order by t.TAG_NAME_C "); - - // Perform the query - Query q = em.createNativeQuery(sb.toString()); - q.setParameter("documentId", documentId); - q.setParameter("userId", userId); - List l = q.getResultList(); - - // Assemble results - List tagDtoList = new ArrayList<>(); - for (Object[] o : l) { - int i = 0; - TagDto tagDto = new TagDto(); - tagDto.setId((String) o[i++]); - tagDto.setName((String) o[i++]); - tagDto.setColor((String) o[i++]); - tagDto.setParentId((String) o[i]); - tagDtoList.add(tagDto); - } - return tagDtoList; - } /** * Returns stats on tags. @@ -189,46 +142,6 @@ public class TagDao { return tag.getId(); } - - /** - * Returns a tag by name. - * - * @param userId User ID - * @param name Name - * @return Tag - */ - public Tag getByName(String userId, String name) { - // TODO Use ACLs - EntityManager em = ThreadLocalContext.get().getEntityManager(); - Query q = em.createQuery("select t from Tag t where t.name = :name and t.userId = :userId and t.deleteDate is null"); - q.setParameter("userId", userId); - q.setParameter("name", name); - try { - return (Tag) q.getSingleResult(); - } catch (NoResultException e) { - return null; - } - } - - /** - * Returns a tag by ID. - * - * @param userId User ID - * @param tagId Tag ID - * @return Tag - */ - public Tag getByTagId(String userId, String tagId) { - // TODO Use ACLs - EntityManager em = ThreadLocalContext.get().getEntityManager(); - Query q = em.createQuery("select t from Tag t where t.id = :tagId and t.userId = :userId and t.deleteDate is null"); - q.setParameter("userId", userId); - q.setParameter("tagId", tagId); - try { - return (Tag) q.getSingleResult(); - } catch (NoResultException e) { - return null; - } - } /** * Deletes a tag. @@ -262,22 +175,6 @@ public class TagDao { // Create audit log AuditLogUtil.create(tagDb, AuditLogType.DELETE, userId); } - - /** - * Search tags by name. - * - * @param name Tag name - * @return List of found tags - */ - @SuppressWarnings("unchecked") - public List findByName(String userId, String name) { - // TODO Use ACLs - EntityManager em = ThreadLocalContext.get().getEntityManager(); - Query q = em.createQuery("select t from Tag t where t.name like :name and t.userId = :userId and t.deleteDate is null"); - q.setParameter("userId", userId); - q.setParameter("name", "%" + name + "%"); - return q.getResultList(); - } /** * Update a tag. @@ -304,5 +201,70 @@ public class TagDao { return tagFromDb; } + + /** + * Returns the list of all tags. + * + * @param criteria Search criteria + * @param sortCriteria Sort criteria + * @return List of groups + */ + public List findByCriteria(TagCriteria criteria, SortCriteria sortCriteria) { + Map parameterMap = new HashMap<>(); + List criteriaList = new ArrayList<>(); + + StringBuilder sb = new StringBuilder("select t.TAG_ID_C as c0, t.TAG_NAME_C as c1, t.TAG_COLOR_C as c2, t.TAG_IDPARENT_C as c3 "); + sb.append(" from T_TAG t "); + // TODO Use ACLs + + // Add search criterias + if (criteria.getId() != null) { + criteriaList.add("t.TAG_ID_C = :id"); + parameterMap.put("id", criteria.getId()); + } + if (criteria.getUserId() != null) { + criteriaList.add("t.TAG_IDUSER_C = :userId"); + parameterMap.put("userId", criteria.getUserId()); + } + if (criteria.getDocumentId() != null) { + sb.append(" join T_DOCUMENT_TAG dt on dt.DOT_IDTAG_C = t.TAG_ID_C and dt.DOT_DELETEDATE_D is null "); + criteriaList.add("dt.DOT_IDDOCUMENT_C = :documentId"); + parameterMap.put("documentId", criteria.getDocumentId()); + } + if (criteria.getName() != null) { + criteriaList.add("t.TAG_NAME_C = :name"); + parameterMap.put("name", criteria.getName()); + } + if (criteria.getNameLike() != null) { + criteriaList.add("t.TAG_NAME_C like :nameLike"); + parameterMap.put("nameLike", "%" + criteria.getNameLike() + "%"); + } + + criteriaList.add("t.TAG_DELETEDATE_D is null"); + + if (!criteriaList.isEmpty()) { + sb.append(" where "); + sb.append(Joiner.on(" and ").join(criteriaList)); + } + + // Perform the search + QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria); + @SuppressWarnings("unchecked") + List l = QueryUtil.getNativeQuery(queryParam).getResultList(); + + // Assemble results + List tagDtoList = new ArrayList<>(); + for (Object[] o : l) { + int i = 0; + TagDto tagDto = new TagDto() + .setId((String) o[i++]) + .setName((String) o[i++]) + .setColor((String) o[i++]) + .setParentId((String) o[i]); + tagDtoList.add(tagDto); + } + + return tagDtoList; + } } diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/criteria/TagCriteria.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/criteria/TagCriteria.java new file mode 100644 index 00000000..9bb7be9d --- /dev/null +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/criteria/TagCriteria.java @@ -0,0 +1,78 @@ +package com.sismics.docs.core.dao.jpa.criteria; + +/** + * Tag criteria. + * + * @author bgamard + */ +public class TagCriteria { + /** + * Tag ID. + */ + private String id; + + /** + * User ID. + */ + private String userId; + + /** + * Document ID. + */ + private String documentId; + + /** + * Tag name. + */ + private String name; + + /** + * Approximate tag name. + */ + private String nameLike; + + public String getId() { + return id; + } + + public TagCriteria setId(String id) { + this.id = id; + return this; + } + + public String getUserId() { + return userId; + } + + public TagCriteria setUserId(String userId) { + this.userId = userId; + return this; + } + + public String getDocumentId() { + return documentId; + } + + public TagCriteria setDocumentId(String documentId) { + this.documentId = documentId; + return this; + } + + public String getName() { + return name; + } + + public TagCriteria setName(String name) { + this.name = name; + return this; + } + + public String getNameLike() { + return nameLike; + } + + public TagCriteria setNameLike(String nameLike) { + this.nameLike = nameLike; + return this; + } +} diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java index 81bea5a1..60524d4a 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java @@ -30,31 +30,35 @@ public class TagDto { return id; } - public void setId(String id) { + public TagDto setId(String id) { this.id = id; + return this; } public String getName() { return name; } - public void setName(String name) { + public TagDto setName(String name) { this.name = name; + return this; } public String getColor() { return color; } - public void setColor(String color) { + public TagDto setColor(String color) { this.color = color; + return this; } public String getParentId() { return parentId; } - public void setParentId(String parentId) { + public TagDto setParentId(String parentId) { this.parentId = parentId; + return this; } } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java index 59be252e..d5a0a48b 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java @@ -18,18 +18,13 @@ import javax.ws.rs.core.Response; import com.google.common.collect.Lists; import com.sismics.docs.core.constant.AclTargetType; import com.sismics.docs.core.constant.PermType; -import com.sismics.docs.core.dao.jpa.AclDao; -import com.sismics.docs.core.dao.jpa.DocumentDao; -import com.sismics.docs.core.dao.jpa.GroupDao; -import com.sismics.docs.core.dao.jpa.UserDao; +import com.sismics.docs.core.dao.jpa.*; import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria; import com.sismics.docs.core.dao.jpa.criteria.UserCriteria; import com.sismics.docs.core.dao.jpa.dto.GroupDto; +import com.sismics.docs.core.dao.jpa.dto.TagDto; import com.sismics.docs.core.dao.jpa.dto.UserDto; -import com.sismics.docs.core.model.jpa.Acl; -import com.sismics.docs.core.model.jpa.Document; -import com.sismics.docs.core.model.jpa.Group; -import com.sismics.docs.core.model.jpa.User; +import com.sismics.docs.core.model.jpa.*; import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ForbiddenClientException; @@ -156,7 +151,14 @@ public class AclResource extends BaseResource { if (document != null && document.getUserId().equals(targetId)) { throw new ClientException("AclError", "Cannot delete base ACL on a document"); } - + + // Cannot delete R/W on a source tag if the target is the creator + TagDao tagDao = new TagDao(); + Tag tag = tagDao.getById(sourceId); + if (tag != null && tag.getUserId().equals(targetId)) { + throw new ClientException("AclError", "Cannot delete base ACL on a tag"); + } + // Delete the ACL aclDao.delete(sourceId, perm, targetId, principal.getId()); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index 0f9d21de..a3420324 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -27,6 +27,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.StreamingOutput; +import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; @@ -83,6 +84,7 @@ public class DocumentResource extends BaseResource { * Returns a document. * * @param documentId Document ID + * @param shareId Share ID * @return Response */ @GET @@ -114,7 +116,7 @@ public class DocumentResource extends BaseResource { } else { // Add tags added by the current user on this document TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.getByDocumentId(documentId, principal.getId()); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setDocumentId(documentId), new SortCriteria(1, true)); JsonArrayBuilder tags = Json.createArrayBuilder(); for (TagDto tagDto : tagDtoList) { tags.add(Json.createObjectBuilder() @@ -188,6 +190,11 @@ public class DocumentResource extends BaseResource { * Export a document to PDF. * * @param documentId Document ID + * @param shareId Share ID + * @param metadata Export metadata + * @param comments Export comments + * @param fitImageToPage Fit images to page + * @param marginStr Margins * @return Response */ @GET @@ -231,7 +238,11 @@ public class DocumentResource extends BaseResource { } catch (Exception e) { throw new IOException(e); } finally { - outputStream.close(); + try { + outputStream.close(); + } catch (IOException e) { + // Ignore + } } } }; @@ -247,6 +258,9 @@ public class DocumentResource extends BaseResource { * * @param limit Page limit * @param offset Page offset + * @param sortColumn Sort column + * @param asc Sorting + * @param search Search query * @return Response */ @GET @@ -278,7 +292,7 @@ public class DocumentResource extends BaseResource { for (DocumentDto documentDto : paginatedList.getResultList()) { // Get tags added by the current user on this document - List tagDtoList = tagDao.getByDocumentId(documentDto.getId(), principal.getId()); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setDocumentId(documentDto.getId()), new SortCriteria(1, true)); JsonArrayBuilder tags = Json.createArrayBuilder(); for (TagDto tagDto : tagDtoList) { tags.add(Json.createObjectBuilder() @@ -337,77 +351,87 @@ public class DocumentResource extends BaseResource { query.add(criteria); continue; } - - if (params[0].equals("tag")) { - // New tag criteria - List tagList = tagDao.findByName(principal.getId(), params[1]); - if (documentCriteria.getTagIdList() == null) { - documentCriteria.setTagIdList(new ArrayList()); - } - if (tagList.size() == 0) { - // No tag found, the request must returns nothing - documentCriteria.getTagIdList().add(UUID.randomUUID().toString()); - } - for (Tag tag : tagList) { - documentCriteria.getTagIdList().add(tag.getId()); - } - } else if (params[0].equals("after") || params[0].equals("before")) { - // New date span criteria - try { - DateTime date = formatter.parseDateTime(params[1]); - if (params[0].equals("before")) documentCriteria.setCreateDateMax(date.toDate()); - else documentCriteria.setCreateDateMin(date.toDate()); - } catch (IllegalArgumentException e) { - // Invalid date, returns no documents - if (params[0].equals("before")) documentCriteria.setCreateDateMax(new Date(0)); - else documentCriteria.setCreateDateMin(new Date(Long.MAX_VALUE / 2)); - } - } else if (params[0].equals("at")) { - // New specific date criteria - try { - if (params[1].length() == 10) { - DateTime date = dayFormatter.parseDateTime(params[1]); - documentCriteria.setCreateDateMin(date.toDate()); - documentCriteria.setCreateDateMax(date.plusDays(1).minusSeconds(1).toDate()); - } else if (params[1].length() == 7) { - DateTime date = monthFormatter.parseDateTime(params[1]); - documentCriteria.setCreateDateMin(date.toDate()); - documentCriteria.setCreateDateMax(date.plusMonths(1).minusSeconds(1).toDate()); - } else if (params[1].length() == 4) { - DateTime date = yearFormatter.parseDateTime(params[1]); - documentCriteria.setCreateDateMin(date.toDate()); - documentCriteria.setCreateDateMax(date.plusYears(1).minusSeconds(1).toDate()); + + switch (params[0]) { + case "tag": + // New tag criteria + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setNameLike(params[1]), null); + if (documentCriteria.getTagIdList() == null) { + documentCriteria.setTagIdList(new ArrayList()); } - } catch (IllegalArgumentException e) { - // Invalid date, returns no documents - documentCriteria.setCreateDateMin(new Date(0)); - documentCriteria.setCreateDateMax(new Date(0)); - } - } else if (params[0].equals("shared")) { - // New shared state criteria - if (params[1].equals("yes")) { - documentCriteria.setShared(true); - } - } else if (params[0].equals("lang")) { - // New language criteria - if (Constants.SUPPORTED_LANGUAGES.contains(params[1])) { - documentCriteria.setLanguage(params[1]); - } - } else if (params[0].equals("by")) { - // New creator criteria - User user = userDao.getActiveByUsername(params[1]); - if (user == null) { - // This user doesn't exists, return nothing - documentCriteria.setCreatorId(UUID.randomUUID().toString()); - } else { - // This user exists, search its documents - documentCriteria.setCreatorId(user.getId()); - } - } else if (params[0].equals("full")) { - // New full content search criteria - fullQuery.add(params[1]); - } else { - query.add(criteria); + if (tagDtoList.size() == 0) { + // No tag found, the request must returns nothing + documentCriteria.getTagIdList().add(UUID.randomUUID().toString()); + } + for (TagDto tagDto : tagDtoList) { + documentCriteria.getTagIdList().add(tagDto.getId()); + } + break; + case "after": + case "before": + // New date span criteria + try { + DateTime date = formatter.parseDateTime(params[1]); + if (params[0].equals("before")) documentCriteria.setCreateDateMax(date.toDate()); + else documentCriteria.setCreateDateMin(date.toDate()); + } catch (IllegalArgumentException e) { + // Invalid date, returns no documents + if (params[0].equals("before")) documentCriteria.setCreateDateMax(new Date(0)); + else documentCriteria.setCreateDateMin(new Date(Long.MAX_VALUE / 2)); + } + break; + case "at": + // New specific date criteria + try { + if (params[1].length() == 10) { + DateTime date = dayFormatter.parseDateTime(params[1]); + documentCriteria.setCreateDateMin(date.toDate()); + documentCriteria.setCreateDateMax(date.plusDays(1).minusSeconds(1).toDate()); + } else if (params[1].length() == 7) { + DateTime date = monthFormatter.parseDateTime(params[1]); + documentCriteria.setCreateDateMin(date.toDate()); + documentCriteria.setCreateDateMax(date.plusMonths(1).minusSeconds(1).toDate()); + } else if (params[1].length() == 4) { + DateTime date = yearFormatter.parseDateTime(params[1]); + documentCriteria.setCreateDateMin(date.toDate()); + documentCriteria.setCreateDateMax(date.plusYears(1).minusSeconds(1).toDate()); + } + } catch (IllegalArgumentException e) { + // Invalid date, returns no documents + documentCriteria.setCreateDateMin(new Date(0)); + documentCriteria.setCreateDateMax(new Date(0)); + } + break; + case "shared": + // New shared state criteria + if (params[1].equals("yes")) { + documentCriteria.setShared(true); + } + break; + case "lang": + // New language criteria + if (Constants.SUPPORTED_LANGUAGES.contains(params[1])) { + documentCriteria.setLanguage(params[1]); + } + break; + case "by": + // New creator criteria + User user = userDao.getActiveByUsername(params[1]); + if (user == null) { + // This user doesn't exists, return nothing + documentCriteria.setCreatorId(UUID.randomUUID().toString()); + } else { + // This user exists, search its documents + documentCriteria.setCreatorId(user.getId()); + } + break; + case "full": + // New full content search criteria + fullQuery.add(params[1]); + break; + default: + query.add(criteria); + break; } } @@ -421,7 +445,16 @@ public class DocumentResource extends BaseResource { * * @param title Title * @param description Description - * @param tags Tags + * @param subject Subject + * @param identifier Identifier + * @param publisher Publisher + * @param format Format + * @param source Source + * @param type Type + * @param coverage Coverage + * @param rights Rights + * @param tagList Tags + * @param relationList Relations * @param language Language * @param createDateStr Creation date * @return Response @@ -594,7 +627,7 @@ public class DocumentResource extends BaseResource { document.setCreateDate(createDate); } - document = documentDao.update(document, principal.getId()); + documentDao.update(document, principal.getId()); // Update tags updateTagList(id, tagList); @@ -624,9 +657,9 @@ public class DocumentResource extends BaseResource { TagDao tagDao = new TagDao(); Set tagSet = new HashSet<>(); Set tagIdSet = new HashSet<>(); - List tagDbList = tagDao.getByUserId(principal.getId()); - for (Tag tagDb : tagDbList) { - tagIdSet.add(tagDb.getId()); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()), null); + for (TagDto tagDto : tagDtoList) { + tagIdSet.add(tagDto.getId()); } for (String tagId : tagList) { if (!tagIdSet.contains(tagId)) { diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java index 97b8e4d4..ecf06573 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java @@ -15,6 +15,11 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.Response; +import com.sismics.docs.core.constant.PermType; +import com.sismics.docs.core.dao.jpa.AclDao; +import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; +import com.sismics.docs.core.dao.jpa.dto.TagDto; +import com.sismics.docs.core.model.jpa.Acl; import org.apache.commons.lang.StringUtils; import com.sismics.docs.core.dao.jpa.TagDao; @@ -33,7 +38,7 @@ import com.sismics.rest.util.ValidationUtil; @Path("/tag") public class TagResource extends BaseResource { /** - * Returns the list of all tags. + * Returns the list of all visible tags. * * @return Response */ @@ -45,14 +50,14 @@ public class TagResource extends BaseResource { } TagDao tagDao = new TagDao(); - List tagList = tagDao.getByUserId(principal.getId()); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()), null); JsonArrayBuilder items = Json.createArrayBuilder(); - for (Tag tag : tagList) { + for (TagDto tagDto : tagDtoList) { items.add(Json.createObjectBuilder() - .add("id", tag.getId()) - .add("name", tag.getName()) - .add("color", tag.getColor()) - .add("parent", JsonUtil.nullable(tag.getParentId()))); + .add("id", tagDto.getId()) + .add("name", tagDto.getName()) + .add("color", tagDto.getColor()) + .add("parent", JsonUtil.nullable(tagDto.getParentId()))); } JsonObjectBuilder response = Json.createObjectBuilder() @@ -93,6 +98,8 @@ public class TagResource extends BaseResource { * Creates a new tag. * * @param name Name + * @param color Color + * @param parentId Parent ID * @return Response */ @PUT @@ -115,8 +122,8 @@ public class TagResource extends BaseResource { // Get the tag TagDao tagDao = new TagDao(); - Tag tag = tagDao.getByName(principal.getId(), name); - if (tag != null) { + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setName(name), null); + if (tagDtoList.size() > 0) { throw new ClientException("AlreadyExistingTag", MessageFormat.format("Tag already exists: {0}", name)); } @@ -124,19 +131,35 @@ public class TagResource extends BaseResource { if (StringUtils.isEmpty(parentId)) { parentId = null; } else { - Tag parentTag = tagDao.getByTagId(principal.getId(), parentId); - if (parentTag == null) { + tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setId(parentId), null); + if (tagDtoList.size() == 0) { throw new ClientException("ParentNotFound", MessageFormat.format("Parent not found: {0}", parentId)); } + parentId = tagDtoList.get(0).getId(); } // Create the tag - tag = new Tag(); + Tag tag = new Tag(); tag.setName(name); tag.setColor(color); tag.setUserId(principal.getId()); tag.setParentId(parentId); String id = tagDao.create(tag, principal.getId()); + + // Create read ACL + AclDao aclDao = new AclDao(); + Acl acl = new Acl(); + acl.setPerm(PermType.READ); + acl.setSourceId(id); + acl.setTargetId(principal.getId()); + aclDao.create(acl, principal.getId()); + + // Create write ACL + acl = new Acl(); + acl.setPerm(PermType.WRITE); + acl.setSourceId(id); + acl.setTargetId(principal.getId()); + aclDao.create(acl, principal.getId()); JsonObjectBuilder response = Json.createObjectBuilder() .add("id", id); @@ -147,6 +170,8 @@ public class TagResource extends BaseResource { * Update a tag. * * @param name Name + * @param color Color + * @param parentId Parent ID * @return Response */ @POST @@ -171,8 +196,8 @@ public class TagResource extends BaseResource { // Get the tag TagDao tagDao = new TagDao(); - Tag tag = tagDao.getByTagId(principal.getId(), id); - if (tag == null) { + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setId(id), null); + if (tagDtoList.size() == 0) { throw new ClientException("TagNotFound", MessageFormat.format("Tag not found: {0}", id)); } @@ -180,19 +205,21 @@ public class TagResource extends BaseResource { if (StringUtils.isEmpty(parentId)) { parentId = null; } else { - Tag parentTag = tagDao.getByTagId(principal.getId(), parentId); - if (parentTag == null) { + tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setId(parentId), null); + if (tagDtoList.size() == 0) { throw new ClientException("ParentNotFound", MessageFormat.format("Parent not found: {0}", parentId)); } + parentId = tagDtoList.get(0).getId(); } // Check for name duplicate - Tag tagDuplicate = tagDao.getByName(principal.getId(), name); - if (tagDuplicate != null && !tagDuplicate.getId().equals(id)) { + tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setName(name), null); + if (tagDtoList.size() > 0 && !tagDtoList.get(0).getId().equals(id)) { throw new ClientException("AlreadyExistingTag", MessageFormat.format("Tag already exists: {0}", name)); } // Update the tag + Tag tag = tagDao.getById(id); if (!StringUtils.isEmpty(name)) { tag.setName(name); } @@ -212,26 +239,26 @@ public class TagResource extends BaseResource { /** * Delete a tag. * - * @param tagId Tag ID + * @param id Tag ID * @return Response */ @DELETE @Path("{id: [a-z0-9\\-]+}") public Response delete( - @PathParam("id") String tagId) { + @PathParam("id") String id) { if (!authenticate()) { throw new ForbiddenClientException(); } // Get the tag TagDao tagDao = new TagDao(); - Tag tag = tagDao.getByTagId(principal.getId(), tagId); - if (tag == null) { - throw new ClientException("TagNotFound", MessageFormat.format("Tag not found: {0}", tagId)); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setId(id), null); + if (tagDtoList.size() == 0) { + throw new ClientException("TagNotFound", MessageFormat.format("Tag not found: {0}", id)); } // Delete the tag - tagDao.delete(tagId, principal.getId()); + tagDao.delete(id, principal.getId()); // Always return OK JsonObjectBuilder response = Json.createObjectBuilder() From c398a3c4f5b152e751caf4bdd32e0c77c1a9be60 Mon Sep 17 00:00:00 2001 From: jendib Date: Thu, 5 May 2016 21:12:14 +0200 Subject: [PATCH 20/42] #83: Tag name duplicates now allowed --- .travis.yml | 9 +++++++++ .../docs/rest/resource/TagResource.java | 18 +++--------------- .../com/sismics/docs/rest/TestTagResource.java | 2 +- 3 files changed, 13 insertions(+), 16 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..8a2fd166 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: java +before_install: + - sudo apt-get -qq update + - sudo apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn +env: + - TESSDATA_PREFIX=/usr/share/tesseract-ocr + - LC_NUMERIC=C +before_script: + - cd docs-parent \ No newline at end of file diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java index ecf06573..63c3bf79 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java @@ -57,7 +57,7 @@ public class TagResource extends BaseResource { .add("id", tagDto.getId()) .add("name", tagDto.getName()) .add("color", tagDto.getColor()) - .add("parent", JsonUtil.nullable(tagDto.getParentId()))); + .add("parent", JsonUtil.nullable(tagDto.getParentId()))); // TODO Don't return the parent if it's not visible } JsonObjectBuilder response = Json.createObjectBuilder() @@ -120,18 +120,12 @@ public class TagResource extends BaseResource { throw new ClientException("SpacesNotAllowed", "Spaces are not allowed in tag name"); } - // Get the tag - TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setName(name), null); - if (tagDtoList.size() > 0) { - throw new ClientException("AlreadyExistingTag", MessageFormat.format("Tag already exists: {0}", name)); - } - // Check the parent + TagDao tagDao = new TagDao(); if (StringUtils.isEmpty(parentId)) { parentId = null; } else { - tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setId(parentId), null); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setId(parentId), null); if (tagDtoList.size() == 0) { throw new ClientException("ParentNotFound", MessageFormat.format("Parent not found: {0}", parentId)); } @@ -212,12 +206,6 @@ public class TagResource extends BaseResource { parentId = tagDtoList.get(0).getId(); } - // Check for name duplicate - tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setName(name), null); - if (tagDtoList.size() > 0 && !tagDtoList.get(0).getId().equals(id)) { - throw new ClientException("AlreadyExistingTag", MessageFormat.format("Tag already exists: {0}", name)); - } - // Update the tag Tag tag = tagDao.getById(id); if (!StringUtils.isEmpty(name)) { diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java index 597074da..f8b63729 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java @@ -55,7 +55,7 @@ public class TestTagResource extends BaseJerseyTest { Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus())); // Create a document - json = target().path("/document").request() + target().path("/document").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token) .put(Entity.form(new Form() .param("title", "My super document 1") From f2ae8999380e752b0bcd31b161d7512c43f37601 Mon Sep 17 00:00:00 2001 From: jendib Date: Thu, 5 May 2016 21:33:31 +0200 Subject: [PATCH 21/42] Don't dump entities in JUnit --- .travis.yml | 5 ++-- README.md | 2 +- .../com/sismics/util/jpa/DbOpenHelper.java | 25 ++++++------------- .../com/sismics/docs/rest/BaseJerseyTest.java | 2 +- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8a2fd166..f95ceb24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,8 @@ before_install: - sudo apt-get -qq update - sudo apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn env: - - TESSDATA_PREFIX=/usr/share/tesseract-ocr - - LC_NUMERIC=C + global: + - TESSDATA_PREFIX=/usr/share/tesseract-ocr + - LC_NUMERIC=C before_script: - cd docs-parent \ No newline at end of file diff --git a/README.md b/README.md index 5b8898a1..dc6e5535 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Sismics Docs +Sismics Docs [![Build Status](https://secure.travis-ci.org/sismics/docs.png)](http://travis-ci.org/sismics/docs) ============ _Web interface_ diff --git a/docs-core/src/main/java/com/sismics/util/jpa/DbOpenHelper.java b/docs-core/src/main/java/com/sismics/util/jpa/DbOpenHelper.java index d742f670..dac8b80c 100644 --- a/docs-core/src/main/java/com/sismics/util/jpa/DbOpenHelper.java +++ b/docs-core/src/main/java/com/sismics/util/jpa/DbOpenHelper.java @@ -37,27 +37,25 @@ import com.sismics.util.ResourceUtil; * * @author jtremeaux */ -public abstract class DbOpenHelper { +abstract class DbOpenHelper { /** * Logger. */ private static final Logger log = LoggerFactory.getLogger(DbOpenHelper.class); - private final SqlStatementLogger sqlStatementLogger; - private final JdbcConnectionAccess jdbcConnectionAccess; - private final List exceptions = new ArrayList(); + private final List exceptions = new ArrayList<>(); private Formatter formatter; private boolean haltOnError; - + private Statement stmt; - public DbOpenHelper(ServiceRegistry serviceRegistry) throws HibernateException { + DbOpenHelper(ServiceRegistry serviceRegistry) throws HibernateException { final JdbcServices jdbcServices = serviceRegistry.getService(JdbcServices.class); - sqlStatementLogger = jdbcServices.getSqlStatementLogger(); + SqlStatementLogger sqlStatementLogger = jdbcServices.getSqlStatementLogger(); jdbcConnectionAccess = jdbcServices.getBootstrapJdbcConnectionAccess(); formatter = (sqlStatementLogger.isFormat() ? FormatStyle.DDL : FormatStyle.NONE).getFormatter(); } @@ -66,7 +64,6 @@ public abstract class DbOpenHelper { log.info("Opening database and executing incremental updates"); Connection connection = null; - Writer outputFileWriter = null; exceptions.clear(); @@ -130,14 +127,6 @@ public abstract class DbOpenHelper { exceptions.add(e); log.error("Unable to close connection", e); } - try { - if (outputFileWriter != null) { - outputFileWriter.close(); - } - } catch (Exception e) { - exceptions.add(e); - log.error("Unable to close connection", e); - } } } @@ -147,7 +136,7 @@ public abstract class DbOpenHelper { * @param version Version number * @throws Exception */ - protected void executeAllScript(final int version) throws Exception { + void executeAllScript(final int version) throws Exception { List fileNameList = ResourceUtil.list(getClass(), "/db/update/", new FilenameFilter() { @Override public boolean accept(File dir, String name) { @@ -173,7 +162,7 @@ public abstract class DbOpenHelper { * @throws IOException * @throws SQLException */ - protected void executeScript(InputStream inputScript) throws IOException, SQLException { + void executeScript(InputStream inputScript) throws IOException, SQLException { List lines = CharStreams.readLines(new InputStreamReader(inputScript)); for (String sql : lines) { diff --git a/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java b/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java index 283a76a6..704d17ea 100644 --- a/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java +++ b/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java @@ -45,7 +45,7 @@ public abstract class BaseJerseyTest extends JerseyTest { @Override protected Application configure() { enable(TestProperties.LOG_TRAFFIC); - enable(TestProperties.DUMP_ENTITY); + // enable(TestProperties.DUMP_ENTITY); return new Application(); } From 6af7b6fce9bb88711eb60ab7e60ae39d8f82d955 Mon Sep 17 00:00:00 2001 From: jendib Date: Thu, 5 May 2016 21:59:50 +0200 Subject: [PATCH 22/42] Reduce tests verbosity --- .travis.yml | 2 ++ .../test/java/com/sismics/docs/core/util/TestFileUtil.java | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f95ceb24..1b199113 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +sudo: required +dist: trusty language: java before_install: - sudo apt-get -qq update diff --git a/docs-core/src/test/java/com/sismics/docs/core/util/TestFileUtil.java b/docs-core/src/test/java/com/sismics/docs/core/util/TestFileUtil.java index bf3bb1de..8ce18dc4 100644 --- a/docs-core/src/test/java/com/sismics/docs/core/util/TestFileUtil.java +++ b/docs-core/src/test/java/com/sismics/docs/core/util/TestFileUtil.java @@ -97,10 +97,9 @@ public class TestFileUtil { File file4 = new File(); file4.setId("document_odt"); file4.setMimeType(MimeType.OPEN_DOCUMENT_TEXT); - - try (InputStream pdfInputStream = PdfUtil.convertToPdf(documentDto, Lists.newArrayList(file0, file1, file2, file3, file4), true, true, 10)) { - ByteStreams.copy(pdfInputStream, System.out); - } + + InputStream is = PdfUtil.convertToPdf(documentDto, Lists.newArrayList(file0, file1, file2, file3, file4), true, true, 10); + is.close(); } } } From bc94466cf7568bc20a56c350b10a9aacc8f1a85e Mon Sep 17 00:00:00 2001 From: jendib Date: Thu, 5 May 2016 22:16:04 +0200 Subject: [PATCH 23/42] Entropy source for Travis --- .travis.yml | 1 + .../java/com/sismics/util/filter/RequestContextFilter.java | 2 +- .../java/com/sismics/docs/rest/resource/UserResource.java | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1b199113..80b3fd10 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ language: java before_install: - sudo apt-get -qq update - sudo apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn + - sudo apt-get install haveged && sudo service haveged start env: global: - TESSDATA_PREFIX=/usr/share/tesseract-ocr diff --git a/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java b/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java index f3eff148..6acdef32 100644 --- a/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java +++ b/docs-web-common/src/main/java/com/sismics/util/filter/RequestContextFilter.java @@ -80,7 +80,7 @@ public class RequestContextFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { - EntityManager em = null; + EntityManager em; try { em = EMF.get().createEntityManager(); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java index 1c12c419..433812ec 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java @@ -99,7 +99,7 @@ public class UserResource extends BaseResource { user.setPassword(password); user.setEmail(email); user.setStorageQuota(storageQuota); - user.setStorageCurrent(0l); + user.setStorageCurrent(0L); try { user.setPrivateKey(EncryptionUtil.generatePrivateKey()); } catch (NoSuchAlgorithmException e) { @@ -678,7 +678,7 @@ public class UserResource extends BaseResource { UserDao userDao = new UserDao(); User user = userDao.getActiveByUsername(principal.getName()); user.setTotpKey(key.getKey()); - user = userDao.update(user, principal.getId()); + userDao.update(user, principal.getId()); JsonObjectBuilder response = Json.createObjectBuilder() .add("secret", key.getKey()); From 37fc2d09bb38247973b8e056a20aafe4fbcaf81b Mon Sep 17 00:00:00 2001 From: jendib Date: Thu, 5 May 2016 22:18:07 +0200 Subject: [PATCH 24/42] Entropy source for Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 80b3fd10..5538f1de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ language: java before_install: - sudo apt-get -qq update - sudo apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn - - sudo apt-get install haveged && sudo service haveged start + - sudo apt-get -y -q install haveged && sudo service haveged start env: global: - TESSDATA_PREFIX=/usr/share/tesseract-ocr From 1b1d5e9b4c6dc4db7984ab017a171131943c9a95 Mon Sep 17 00:00:00 2001 From: jendib Date: Thu, 5 May 2016 22:36:53 +0200 Subject: [PATCH 25/42] #83: Use ACLs for tag operations --- .../java/com/sismics/docs/core/dao/jpa/TagDao.java | 7 +++---- .../docs/core/dao/jpa/criteria/TagCriteria.java | 14 ++++++++------ .../docs/rest/resource/DocumentResource.java | 8 ++++---- .../sismics/docs/rest/resource/TagResource.java | 10 +++++----- docs-parent/pom.xml => pom.xml | 14 ++++++++------ 5 files changed, 28 insertions(+), 25 deletions(-) rename docs-parent/pom.xml => pom.xml (98%) diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java index e6f37a2d..fe780e36 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java @@ -215,16 +215,15 @@ public class TagDao { StringBuilder sb = new StringBuilder("select t.TAG_ID_C as c0, t.TAG_NAME_C as c1, t.TAG_COLOR_C as c2, t.TAG_IDPARENT_C as c3 "); sb.append(" from T_TAG t "); - // TODO Use ACLs // Add search criterias if (criteria.getId() != null) { criteriaList.add("t.TAG_ID_C = :id"); parameterMap.put("id", criteria.getId()); } - if (criteria.getUserId() != null) { - criteriaList.add("t.TAG_IDUSER_C = :userId"); - parameterMap.put("userId", criteria.getUserId()); + if (criteria.getTargetIdList() != null) { + sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = t.TAG_ID_C and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null "); + parameterMap.put("targetIdList", criteria.getTargetIdList()); } if (criteria.getDocumentId() != null) { sb.append(" join T_DOCUMENT_TAG dt on dt.DOT_IDTAG_C = t.TAG_ID_C and dt.DOT_DELETEDATE_D is null "); diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/criteria/TagCriteria.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/criteria/TagCriteria.java index 9bb7be9d..2665a205 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/criteria/TagCriteria.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/criteria/TagCriteria.java @@ -1,5 +1,7 @@ package com.sismics.docs.core.dao.jpa.criteria; +import java.util.List; + /** * Tag criteria. * @@ -12,9 +14,9 @@ public class TagCriteria { private String id; /** - * User ID. + * ACL target ID list. */ - private String userId; + private List targetIdList; /** * Document ID. @@ -40,12 +42,12 @@ public class TagCriteria { return this; } - public String getUserId() { - return userId; + public List getTargetIdList() { + return targetIdList; } - public TagCriteria setUserId(String userId) { - this.userId = userId; + public TagCriteria setTargetIdList(List targetIdList) { + this.targetIdList = targetIdList; return this; } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index a3420324..acb156e7 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -116,7 +116,7 @@ public class DocumentResource extends BaseResource { } else { // Add tags added by the current user on this document TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setDocumentId(documentId), new SortCriteria(1, true)); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setDocumentId(documentId), new SortCriteria(1, true)); JsonArrayBuilder tags = Json.createArrayBuilder(); for (TagDto tagDto : tagDtoList) { tags.add(Json.createObjectBuilder() @@ -292,7 +292,7 @@ public class DocumentResource extends BaseResource { for (DocumentDto documentDto : paginatedList.getResultList()) { // Get tags added by the current user on this document - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setDocumentId(documentDto.getId()), new SortCriteria(1, true)); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setDocumentId(documentDto.getId()), new SortCriteria(1, true)); JsonArrayBuilder tags = Json.createArrayBuilder(); for (TagDto tagDto : tagDtoList) { tags.add(Json.createObjectBuilder() @@ -355,7 +355,7 @@ public class DocumentResource extends BaseResource { switch (params[0]) { case "tag": // New tag criteria - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setNameLike(params[1]), null); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setNameLike(params[1]), null); if (documentCriteria.getTagIdList() == null) { documentCriteria.setTagIdList(new ArrayList()); } @@ -657,7 +657,7 @@ public class DocumentResource extends BaseResource { TagDao tagDao = new TagDao(); Set tagSet = new HashSet<>(); Set tagIdSet = new HashSet<>(); - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()), null); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)), null); for (TagDto tagDto : tagDtoList) { tagIdSet.add(tagDto.getId()); } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java index 63c3bf79..9891b816 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java @@ -50,7 +50,7 @@ public class TagResource extends BaseResource { } TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()), null); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)), null); JsonArrayBuilder items = Json.createArrayBuilder(); for (TagDto tagDto : tagDtoList) { items.add(Json.createObjectBuilder() @@ -125,7 +125,7 @@ public class TagResource extends BaseResource { if (StringUtils.isEmpty(parentId)) { parentId = null; } else { - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setId(parentId), null); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(parentId), null); if (tagDtoList.size() == 0) { throw new ClientException("ParentNotFound", MessageFormat.format("Parent not found: {0}", parentId)); } @@ -190,7 +190,7 @@ public class TagResource extends BaseResource { // Get the tag TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setId(id), null); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(id), null); if (tagDtoList.size() == 0) { throw new ClientException("TagNotFound", MessageFormat.format("Tag not found: {0}", id)); } @@ -199,7 +199,7 @@ public class TagResource extends BaseResource { if (StringUtils.isEmpty(parentId)) { parentId = null; } else { - tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setId(parentId), null); + tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(parentId), null); if (tagDtoList.size() == 0) { throw new ClientException("ParentNotFound", MessageFormat.format("Parent not found: {0}", parentId)); } @@ -240,7 +240,7 @@ public class TagResource extends BaseResource { // Get the tag TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setUserId(principal.getId()).setId(id), null); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(id), null); if (tagDtoList.size() == 0) { throw new ClientException("TagNotFound", MessageFormat.format("Tag not found: {0}", id)); } diff --git a/docs-parent/pom.xml b/pom.xml similarity index 98% rename from docs-parent/pom.xml rename to pom.xml index 81362ea6..64c8b8e7 100644 --- a/docs-parent/pom.xml +++ b/pom.xml @@ -120,9 +120,9 @@ - ../docs-core - ../docs-web-common - ../docs-web + docs-core + docs-web-common + docs-web @@ -374,8 +374,9 @@ org.apache.poi.xwpf.converter.pdf ${fr.opensagres.xdocreport.version} - - + + + com.twelvemonkeys.servlet servlet ${com.twelvemonkeys.imageio.version} @@ -389,7 +390,8 @@ - + + com.twelvemonkeys.imageio imageio-jpeg ${com.twelvemonkeys.imageio.version} From a59c67d774e011f426c710cb28aba333ab09a652 Mon Sep 17 00:00:00 2001 From: jendib Date: Thu, 5 May 2016 22:37:27 +0200 Subject: [PATCH 26/42] docs-parent folder removed --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5538f1de..51c40faf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,4 @@ before_install: env: global: - TESSDATA_PREFIX=/usr/share/tesseract-ocr - - LC_NUMERIC=C -before_script: - - cd docs-parent \ No newline at end of file + - LC_NUMERIC=C \ No newline at end of file From 5226df53a2fc1fa042091820687cce91d749b884 Mon Sep 17 00:00:00 2001 From: jendib Date: Thu, 5 May 2016 22:43:18 +0200 Subject: [PATCH 27/42] Fix pom.xml after removing docs-parent --- README.md | 3 +-- docs-core/pom.xml | 2 +- docs-stress/pom.xml | 2 +- docs-web-common/pom.xml | 5 +++-- docs-web/pom.xml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index dc6e5535..d2f9eb4f 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,6 @@ Prerequisites: JDK 7 with JCE, Maven 3, Tesseract 3.02 Docs is organized in several Maven modules: - - docs-parent - docs-core - docs-web - docs-web-common @@ -60,7 +59,7 @@ or download the sources from GitHub. #### Launch the build -From the `docs-parent` directory: +From the root directory: mvn clean -DskipTests install diff --git a/docs-core/pom.xml b/docs-core/pom.xml index eeb23ba9..3c1730c1 100644 --- a/docs-core/pom.xml +++ b/docs-core/pom.xml @@ -6,7 +6,7 @@ com.sismics.docs docs-parent 1.0-SNAPSHOT - ../docs-parent + .. 4.0.0 diff --git a/docs-stress/pom.xml b/docs-stress/pom.xml index ec85c4d0..6c917af7 100644 --- a/docs-stress/pom.xml +++ b/docs-stress/pom.xml @@ -6,7 +6,7 @@ com.sismics.docs docs-parent 1.0-SNAPSHOT - ../docs-parent + .. 4.0.0 diff --git a/docs-web-common/pom.xml b/docs-web-common/pom.xml index 38df767c..427999c7 100644 --- a/docs-web-common/pom.xml +++ b/docs-web-common/pom.xml @@ -1,11 +1,12 @@ - + com.sismics.docs docs-parent 1.0-SNAPSHOT - ../docs-parent + .. 4.0.0 diff --git a/docs-web/pom.xml b/docs-web/pom.xml index 048cc060..e4f2420a 100644 --- a/docs-web/pom.xml +++ b/docs-web/pom.xml @@ -6,7 +6,7 @@ com.sismics.docs docs-parent 1.0-SNAPSHOT - ../docs-parent + .. 4.0.0 From f12e3ec663d1963bd5c51e6051737189be41e68b Mon Sep 17 00:00:00 2001 From: jendib Date: Fri, 6 May 2016 00:36:54 +0200 Subject: [PATCH 28/42] #83: Access documents by a shared tag --- .../com/sismics/docs/core/dao/jpa/TagDao.java | 3 +- .../com/sismics/docs/rest/BaseJerseyTest.java | 8 +- .../docs/rest/resource/AuditLogResource.java | 20 +-- .../docs/rest/resource/CommentResource.java | 30 ++-- .../docs/rest/resource/DocumentResource.java | 75 +++------ .../docs/rest/resource/FileResource.java | 38 ++--- .../docs/rest/resource/GroupResource.java | 41 ++--- .../docs/rest/resource/ShareResource.java | 22 +-- .../docs/rest/resource/TagResource.java | 40 ++--- .../rest/resource/VocabularyResource.java | 33 ++-- .../sismics/docs/rest/TestAclResource.java | 145 ++++++++++++++++++ 11 files changed, 267 insertions(+), 188 deletions(-) diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java index fe780e36..83c97365 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java @@ -213,7 +213,7 @@ public class TagDao { Map parameterMap = new HashMap<>(); List criteriaList = new ArrayList<>(); - StringBuilder sb = new StringBuilder("select t.TAG_ID_C as c0, t.TAG_NAME_C as c1, t.TAG_COLOR_C as c2, t.TAG_IDPARENT_C as c3 "); + StringBuilder sb = new StringBuilder("select distinct t.TAG_ID_C as c0, t.TAG_NAME_C as c1, t.TAG_COLOR_C as c2, t.TAG_IDPARENT_C as c3 "); sb.append(" from T_TAG t "); // Add search criterias @@ -223,6 +223,7 @@ public class TagDao { } if (criteria.getTargetIdList() != null) { sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = t.TAG_ID_C and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null "); + criteriaList.add("a.ACL_ID_C is not null"); parameterMap.put("targetIdList", criteria.getTargetIdList()); } if (criteria.getDocumentId() != null) { diff --git a/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java b/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java index 704d17ea..e4244d6f 100644 --- a/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java +++ b/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java @@ -30,7 +30,7 @@ public abstract class BaseJerseyTest extends JerseyTest { /** * Test HTTP server. */ - protected HttpServer httpServer; + private HttpServer httpServer; /** * Utility class for the REST client. @@ -45,7 +45,11 @@ public abstract class BaseJerseyTest extends JerseyTest { @Override protected Application configure() { enable(TestProperties.LOG_TRAFFIC); - // enable(TestProperties.DUMP_ENTITY); + String travisEnv = System.getenv("TRAVIS"); + if (travisEnv == null || !travisEnv.equals("true")) { + // Travis don't like entity dumped in the logs + enable(TestProperties.DUMP_ENTITY); + } return new Application(); } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AuditLogResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AuditLogResource.java index 2d42e01e..83f5b798 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AuditLogResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AuditLogResource.java @@ -1,14 +1,5 @@ package com.sismics.docs.rest.resource; -import javax.json.Json; -import javax.json.JsonArrayBuilder; -import javax.json.JsonObjectBuilder; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - import com.google.common.base.Strings; import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.AclDao; @@ -21,6 +12,15 @@ import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.util.JsonUtil; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + /** * Audit log REST resources. * @@ -50,7 +50,7 @@ public class AuditLogResource extends BaseResource { // Check ACL on the document AclDao aclDao = new AclDao(); if (!aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(null))) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } criteria.setDocumentId(documentId); } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/CommentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/CommentResource.java index 26a1881f..bd032dcd 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/CommentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/CommentResource.java @@ -1,20 +1,5 @@ package com.sismics.docs.rest.resource; -import java.util.List; - -import javax.json.Json; -import javax.json.JsonArrayBuilder; -import javax.json.JsonObjectBuilder; -import javax.ws.rs.DELETE; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.CommentDao; import com.sismics.docs.core.dao.jpa.DocumentDao; @@ -24,6 +9,13 @@ import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.util.ValidationUtil; import com.sismics.util.ImageUtil; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.util.List; + /** * Comment REST resource. * @@ -52,7 +44,7 @@ public class CommentResource extends BaseResource { // Read access on doc gives access to write comments DocumentDao documentDao = new DocumentDao(); if (documentDao.getDocument(documentId, PermType.READ, getTargetIdList(null)) == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Create the comment @@ -90,7 +82,7 @@ public class CommentResource extends BaseResource { CommentDao commentDao = new CommentDao(); Comment comment = commentDao.getActiveById(id); if (comment == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // If the current user owns the comment, skip ACL check @@ -98,7 +90,7 @@ public class CommentResource extends BaseResource { // Get the associated document DocumentDao documentDao = new DocumentDao(); if (documentDao.getDocument(comment.getDocumentId(), PermType.WRITE, getTargetIdList(null)) == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } } @@ -126,7 +118,7 @@ public class CommentResource extends BaseResource { // Read access on doc gives access to read comments DocumentDao documentDao = new DocumentDao(); if (documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId)) == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Assemble results diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index acb156e7..a7175486 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -1,57 +1,14 @@ package com.sismics.docs.rest.resource; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; - -import javax.json.Json; -import javax.json.JsonArrayBuilder; -import javax.json.JsonObjectBuilder; -import javax.ws.rs.DELETE; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.StreamingOutput; - -import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; -import org.joda.time.DateTime; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.DateTimeFormatterBuilder; -import org.joda.time.format.DateTimeParser; - import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.io.ByteStreams; import com.sismics.docs.core.constant.Constants; import com.sismics.docs.core.constant.PermType; -import com.sismics.docs.core.dao.jpa.AclDao; -import com.sismics.docs.core.dao.jpa.ContributorDao; -import com.sismics.docs.core.dao.jpa.DocumentDao; -import com.sismics.docs.core.dao.jpa.FileDao; -import com.sismics.docs.core.dao.jpa.RelationDao; -import com.sismics.docs.core.dao.jpa.TagDao; -import com.sismics.docs.core.dao.jpa.UserDao; +import com.sismics.docs.core.dao.jpa.*; import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria; -import com.sismics.docs.core.dao.jpa.dto.AclDto; -import com.sismics.docs.core.dao.jpa.dto.ContributorDto; -import com.sismics.docs.core.dao.jpa.dto.DocumentDto; -import com.sismics.docs.core.dao.jpa.dto.RelationDto; -import com.sismics.docs.core.dao.jpa.dto.TagDto; +import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; +import com.sismics.docs.core.dao.jpa.dto.*; import com.sismics.docs.core.event.DocumentCreatedAsyncEvent; import com.sismics.docs.core.event.DocumentDeletedAsyncEvent; import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent; @@ -60,7 +17,6 @@ import com.sismics.docs.core.model.context.AppContext; import com.sismics.docs.core.model.jpa.Acl; import com.sismics.docs.core.model.jpa.Document; import com.sismics.docs.core.model.jpa.File; -import com.sismics.docs.core.model.jpa.Tag; import com.sismics.docs.core.model.jpa.User; import com.sismics.docs.core.util.PdfUtil; import com.sismics.docs.core.util.jpa.PaginatedList; @@ -72,6 +28,23 @@ import com.sismics.rest.exception.ServerException; import com.sismics.rest.util.JsonUtil; import com.sismics.rest.util.ValidationUtil; import com.sismics.util.mime.MimeType; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.DateTimeFormatterBuilder; +import org.joda.time.format.DateTimeParser; + +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.MessageFormat; +import java.util.*; /** * Document REST resources. @@ -98,7 +71,7 @@ public class DocumentResource extends BaseResource { AclDao aclDao = new AclDao(); DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId)); if (documentDto == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } JsonObjectBuilder document = Json.createObjectBuilder() @@ -215,7 +188,7 @@ public class DocumentResource extends BaseResource { DocumentDao documentDao = new DocumentDao(); final DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId)); if (documentDto == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Get files @@ -606,7 +579,7 @@ public class DocumentResource extends BaseResource { DocumentDao documentDao = new DocumentDao(); Document document = documentDao.getById(id); if (document == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Update the document @@ -712,7 +685,7 @@ public class DocumentResource extends BaseResource { FileDao fileDao = new FileDao(); DocumentDto documentDto = documentDao.getDocument(id, PermType.WRITE, getTargetIdList(null)); if (documentDto == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } List fileList = fileDao.getByDocumentId(principal.getId(), id); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java index 084eb13b..31a3af3a 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java @@ -16,17 +16,7 @@ import java.util.zip.ZipOutputStream; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObjectBuilder; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; +import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; @@ -100,7 +90,7 @@ public class FileResource extends BaseResource { DocumentDao documentDao = new DocumentDao(); documentDto = documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null)); if (documentDto == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } } @@ -215,7 +205,7 @@ public class FileResource extends BaseResource { File file = fileDao.getFile(id, principal.getId()); DocumentDto documentDto = documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null)); if (file == null || documentDto == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Check that the file is orphan @@ -277,7 +267,7 @@ public class FileResource extends BaseResource { // Get the document DocumentDao documentDao = new DocumentDao(); if (documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null)) == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Reorder files @@ -313,7 +303,7 @@ public class FileResource extends BaseResource { if (documentId != null) { AclDao aclDao = new AclDao(); if (!aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(shareId))) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } } else if (!authenticated) { throw new ForbiddenClientException(); @@ -360,7 +350,7 @@ public class FileResource extends BaseResource { DocumentDao documentDao = new DocumentDao(); File file = fileDao.getFile(id); if (file == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } DocumentDto documentDto = null; @@ -371,7 +361,7 @@ public class FileResource extends BaseResource { throw new ForbiddenClientException(); } } else if ((documentDto = documentDao.getDocument(file.getDocumentId(), PermType.WRITE, getTargetIdList(null))) == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Delete the file @@ -433,7 +423,7 @@ public class FileResource extends BaseResource { UserDao userDao = new UserDao(); File file = fileDao.getFile(fileId); if (file == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } if (file.getDocumentId() == null) { @@ -454,7 +444,7 @@ public class FileResource extends BaseResource { // Get the stored file java.nio.file.Path storedFile; String mimeType; - boolean decrypt = false; + boolean decrypt; if (size != null) { storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId + "_" + size); mimeType = MimeType.IMAGE_JPEG; // Thumbnails are JPEG @@ -488,8 +478,12 @@ public class FileResource extends BaseResource { try { ByteStreams.copy(responseInputStream, outputStream); } finally { - responseInputStream.close(); - outputStream.close(); + try { + responseInputStream.close(); + outputStream.close(); + } catch (IOException e) { + // Ignore + } } } }; @@ -521,7 +515,7 @@ public class FileResource extends BaseResource { DocumentDao documentDao = new DocumentDao(); DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId)); if (documentDto == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Get files and user associated with this document diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java index 8cf8c07c..d345ee97 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java @@ -1,22 +1,5 @@ package com.sismics.docs.rest.resource; -import java.text.MessageFormat; -import java.util.List; - -import javax.json.Json; -import javax.json.JsonArrayBuilder; -import javax.json.JsonObjectBuilder; -import javax.ws.rs.DELETE; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - import com.google.common.base.Strings; import com.sismics.docs.core.dao.jpa.GroupDao; import com.sismics.docs.core.dao.jpa.UserDao; @@ -34,6 +17,14 @@ import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.util.JsonUtil; import com.sismics.rest.util.ValidationUtil; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.text.MessageFormat; +import java.util.List; + /** * Group REST resources. * @@ -109,12 +100,12 @@ public class GroupResource extends BaseResource { GroupDao groupDao = new GroupDao(); Group group = groupDao.getActiveByName(groupName); if (group == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Avoid duplicates Group existingGroup = groupDao.getActiveByName(name); - if (existingGroup != null && existingGroup.getId() != group.getId()) { + if (existingGroup != null && !existingGroup.getId().equals(group.getId())) { throw new ClientException("GroupAlreadyExists", MessageFormat.format("This group already exists: {0}", name)); } @@ -155,7 +146,7 @@ public class GroupResource extends BaseResource { GroupDao groupDao = new GroupDao(); Group group = groupDao.getActiveByName(groupName); if (group == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Delete the group @@ -191,14 +182,14 @@ public class GroupResource extends BaseResource { GroupDao groupDao = new GroupDao(); Group group = groupDao.getActiveByName(groupName); if (group == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Get the user UserDao userDao = new UserDao(); User user = userDao.getActiveByUsername(username); if (user == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Avoid duplicates @@ -248,14 +239,14 @@ public class GroupResource extends BaseResource { GroupDao groupDao = new GroupDao(); Group group = groupDao.getActiveByName(groupName); if (group == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Get the user UserDao userDao = new UserDao(); User user = userDao.getActiveByUsername(username); if (user == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Remove the membership @@ -315,7 +306,7 @@ public class GroupResource extends BaseResource { GroupDao groupDao = new GroupDao(); Group group = groupDao.getActiveByName(groupName); if (group == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Build the response diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java index 93957469..f51d2214 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java @@ -1,19 +1,6 @@ package com.sismics.docs.rest.resource; -import java.text.MessageFormat; -import java.util.List; - -import javax.json.Json; -import javax.json.JsonObjectBuilder; -import javax.ws.rs.DELETE; -import javax.ws.rs.FormParam; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - import com.sismics.docs.core.constant.AclTargetType; import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.AclDao; @@ -26,6 +13,13 @@ import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.util.JsonUtil; import com.sismics.rest.util.ValidationUtil; +import javax.json.Json; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.text.MessageFormat; +import java.util.List; + /** * Share REST resources. * @@ -55,7 +49,7 @@ public class ShareResource extends BaseResource { // Get the document DocumentDao documentDao = new DocumentDao(); if (documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null)) == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Create the share diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java index 9891b816..11eab035 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java @@ -6,13 +6,7 @@ import java.util.List; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObjectBuilder; -import javax.ws.rs.DELETE; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; +import javax.ws.rs.*; import javax.ws.rs.core.Response; import com.sismics.docs.core.constant.PermType; @@ -20,6 +14,7 @@ import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; import com.sismics.docs.core.dao.jpa.dto.TagDto; import com.sismics.docs.core.model.jpa.Acl; +import com.sismics.docs.core.util.jpa.SortCriteria; import org.apache.commons.lang.StringUtils; import com.sismics.docs.core.dao.jpa.TagDao; @@ -50,7 +45,7 @@ public class TagResource extends BaseResource { } TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)), null); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)), new SortCriteria(1, true)); JsonArrayBuilder items = Json.createArrayBuilder(); for (TagDto tagDto : tagDtoList) { items.add(Json.createObjectBuilder() @@ -121,18 +116,17 @@ public class TagResource extends BaseResource { } // Check the parent - TagDao tagDao = new TagDao(); if (StringUtils.isEmpty(parentId)) { parentId = null; } else { - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(parentId), null); - if (tagDtoList.size() == 0) { + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(parentId, PermType.READ, getTargetIdList(null))) { throw new ClientException("ParentNotFound", MessageFormat.format("Parent not found: {0}", parentId)); } - parentId = tagDtoList.get(0).getId(); } // Create the tag + TagDao tagDao = new TagDao(); Tag tag = new Tag(); tag.setName(name); tag.setColor(color); @@ -188,25 +182,23 @@ public class TagResource extends BaseResource { throw new ClientException("SpacesNotAllowed", "Spaces are not allowed in tag name"); } - // Get the tag - TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(id), null); - if (tagDtoList.size() == 0) { - throw new ClientException("TagNotFound", MessageFormat.format("Tag not found: {0}", id)); + // Check permission + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(id, PermType.WRITE, getTargetIdList(null))) { + throw new NotFoundException(); } // Check the parent if (StringUtils.isEmpty(parentId)) { parentId = null; } else { - tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(parentId), null); - if (tagDtoList.size() == 0) { + if (!aclDao.checkPermission(parentId, PermType.READ, getTargetIdList(null))) { throw new ClientException("ParentNotFound", MessageFormat.format("Parent not found: {0}", parentId)); } - parentId = tagDtoList.get(0).getId(); } // Update the tag + TagDao tagDao = new TagDao(); Tag tag = tagDao.getById(id); if (!StringUtils.isEmpty(name)) { tag.setName(name); @@ -239,13 +231,13 @@ public class TagResource extends BaseResource { } // Get the tag - TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(id), null); - if (tagDtoList.size() == 0) { - throw new ClientException("TagNotFound", MessageFormat.format("Tag not found: {0}", id)); + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(id, PermType.WRITE, getTargetIdList(null))) { + throw new NotFoundException(); } // Delete the tag + TagDao tagDao = new TagDao(); tagDao.delete(id, principal.getId()); // Always return OK diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/VocabularyResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/VocabularyResource.java index 479d86d2..06ba5ee8 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/VocabularyResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/VocabularyResource.java @@ -1,26 +1,18 @@ package com.sismics.docs.rest.resource; -import java.util.List; - -import javax.json.Json; -import javax.json.JsonArrayBuilder; -import javax.json.JsonObjectBuilder; -import javax.ws.rs.DELETE; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; - import com.sismics.docs.core.dao.jpa.VocabularyDao; import com.sismics.docs.core.model.jpa.Vocabulary; import com.sismics.docs.rest.constant.BaseFunction; import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.util.ValidationUtil; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.util.List; + /** * Vocabulary REST resources. * @@ -58,7 +50,7 @@ public class VocabularyResource extends BaseResource { * * @param name Name * @param value Value - * @param order Order + * @param orderStr Order * @return Response */ @PUT @@ -95,10 +87,11 @@ public class VocabularyResource extends BaseResource { /** * Update a vocabulary. - * + * + * @param id ID * @param name Name * @param value Value - * @param order Order + * @param orderStr Order * @return Response */ @POST @@ -127,7 +120,7 @@ public class VocabularyResource extends BaseResource { VocabularyDao vocabularyDao = new VocabularyDao(); Vocabulary vocabulary = vocabularyDao.getById(id); if (vocabulary == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Update the vocabulary @@ -169,7 +162,7 @@ public class VocabularyResource extends BaseResource { VocabularyDao vocabularyDao = new VocabularyDao(); Vocabulary vocabulary = vocabularyDao.getById(id); if (vocabulary == null) { - return Response.status(Status.NOT_FOUND).build(); + throw new NotFoundException(); } // Delete the vocabulary diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java index 35cf277f..8b2440e3 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java @@ -249,4 +249,149 @@ public class TestAclResource extends BaseJerseyTest { groups = json.getJsonArray("groups"); Assert.assertEquals(1, groups.size()); } + + @Test + public void testAclTags() { + // Login acltag1 + clientUtil.createUser("acltag1"); + String acltag1Token = clientUtil.login("acltag1"); + + // Login acltag2 + clientUtil.createUser("acltag2"); + String acltag2Token = clientUtil.login("acltag2"); + + // Create tag1 with acltag1 + JsonObject json = target().path("/tag").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag1Token) + .put(Entity.form(new Form() + .param("name", "AclTag1") + .param("color", "#ff0000")), JsonObject.class); + String tag1Id = json.getString("id"); + Assert.assertNotNull(tag1Id); + + // Create document1 with acltag1 tagged with tag1 + json = target().path("/document").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag1Token) + .put(Entity.form(new Form() + .param("title", "My super document 1") + .param("tags", tag1Id) + .param("language", "eng")), JsonObject.class); + String document1Id = json.getString("id"); + + // acltag2 cannot see document1 + Response response = target().path("/document/" + document1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .get(); + Assert.assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus()); + + // acltag2 cannot see any tag + json = target().path("/tag/list").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .get(JsonObject.class); + JsonArray tags = json.getJsonArray("tags"); + Assert.assertEquals(0, tags.size()); + + // acltag2 cannot see any document + json = target().path("/document/list") + .queryParam("sort_column", 3) + .queryParam("asc", true) + .request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .get(JsonObject.class); + JsonArray documents = json.getJsonArray("documents"); + Assert.assertEquals(0, documents.size()); + + // acltag2 cannot edit tag1 + response = target().path("/tag/" + tag1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .post(Entity.form(new Form() + .param("name", "AclTag1") + .param("color", "#ff0000"))); + Assert.assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus()); + + // acltag2 cannot edit document1 + response = target().path("/document/" + document1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .post(Entity.form(new Form() + .param("title", "My super document 1") + .param("tags", tag1Id) + .param("language", "eng"))); + Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus()); + + // Add an ACL READ for acltag2 with acltag1 on tag1 + target().path("/acl").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag1Token) + .put(Entity.form(new Form() + .param("source", tag1Id) + .param("perm", "READ") + .param("target", "acltag2") + .param("type", "USER")), JsonObject.class); + + // acltag2 still cannot edit tag1 + response = target().path("/tag/" + tag1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .post(Entity.form(new Form() + .param("name", "AclTag1") + .param("color", "#ff0000"))); + Assert.assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus()); + + // acltag2 still cannot edit document1 + response = target().path("/document/" + document1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .post(Entity.form(new Form() + .param("title", "My super document 1") + .param("tags", tag1Id) + .param("language", "eng"))); + Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus()); + + // acltag2 can see document1 with tag1 + json = target().path("/document/" + document1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .get(JsonObject.class); + tags = json.getJsonArray("tags"); + Assert.assertEquals(1, tags.size()); + Assert.assertEquals(tag1Id, tags.getJsonObject(0).getString("id")); + + // acltag2 can see tag1 + json = target().path("/tag/list").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .get(JsonObject.class); + tags = json.getJsonArray("tags"); + Assert.assertEquals(1, tags.size()); + Assert.assertEquals(tag1Id, tags.getJsonObject(0).getString("id")); + + // acltag2 can see exactly one document + json = target().path("/document/list") + .queryParam("sort_column", 3) + .queryParam("asc", true) + .request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .get(JsonObject.class); + documents = json.getJsonArray("documents"); + Assert.assertEquals(1, documents.size()); + + // Add an ACL WRITE for acltag2 with acltag1 on tag1 + target().path("/acl").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag1Token) + .put(Entity.form(new Form() + .param("source", tag1Id) + .param("perm", "WRITE") + .param("target", "acltag2") + .param("type", "USER")), JsonObject.class); + + // acltag2 can edit tag1 + target().path("/tag/" + tag1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .post(Entity.form(new Form() + .param("name", "AclTag1") + .param("color", "#ff0000")), JsonObject.class); + + // acltag2 can edit document1 + target().path("/document/" + document1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .post(Entity.form(new Form() + .param("title", "My super document 1") + .param("tags", tag1Id) + .param("language", "eng")), JsonObject.class); + } } \ No newline at end of file From 62020864ef8736609018c48542de9c5b51e2fbb5 Mon Sep 17 00:00:00 2001 From: jendib Date: Fri, 6 May 2016 00:49:41 +0200 Subject: [PATCH 29/42] #83: Fix ACL resource test --- .../src/test/java/com/sismics/docs/rest/TestAclResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java index 8b2440e3..0fd0746e 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java @@ -234,9 +234,9 @@ public class TestAclResource extends BaseJerseyTest { .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl1Token) .get(JsonObject.class); JsonArray users = json.getJsonArray("users"); - Assert.assertEquals(2, users.size()); + Assert.assertTrue(users.size() > 0); JsonArray groups = json.getJsonArray("groups"); - Assert.assertEquals(1, groups.size()); + Assert.assertTrue(groups.size() > 0); // Search target list (admin) json = target().path("/acl/target/search") From eaf2e816b484cc18ba0d6739c00ee2d0a16bb549 Mon Sep 17 00:00:00 2001 From: jendib Date: Fri, 6 May 2016 00:55:00 +0200 Subject: [PATCH 30/42] Imports --- .../docs/rest/resource/AclResource.java | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java index d5a0a48b..480b9a51 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java @@ -1,20 +1,5 @@ package com.sismics.docs.rest.resource; -import java.text.MessageFormat; -import java.util.List; - -import javax.json.Json; -import javax.json.JsonArrayBuilder; -import javax.json.JsonObjectBuilder; -import javax.ws.rs.DELETE; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Response; - import com.google.common.collect.Lists; import com.sismics.docs.core.constant.AclTargetType; import com.sismics.docs.core.constant.PermType; @@ -22,7 +7,6 @@ import com.sismics.docs.core.dao.jpa.*; import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria; import com.sismics.docs.core.dao.jpa.criteria.UserCriteria; import com.sismics.docs.core.dao.jpa.dto.GroupDto; -import com.sismics.docs.core.dao.jpa.dto.TagDto; import com.sismics.docs.core.dao.jpa.dto.UserDto; import com.sismics.docs.core.model.jpa.*; import com.sismics.docs.core.util.jpa.SortCriteria; @@ -30,6 +14,14 @@ import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.util.ValidationUtil; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import java.text.MessageFormat; +import java.util.List; + /** * ACL REST resources. * From 73133f5ba5a87eb29294c3dc569c55daaea1e28a Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 7 May 2016 15:41:19 +0200 Subject: [PATCH 31/42] #83: Remove GET /tag/stats --- .../com/sismics/docs/core/dao/jpa/TagDao.java | 37 ----------- .../docs/rest/resource/TagResource.java | 64 +++++-------------- .../sismics/docs/rest/TestTagResource.java | 9 --- 3 files changed, 16 insertions(+), 94 deletions(-) diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java index 83c97365..eb54b7e6 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java @@ -84,43 +84,6 @@ public class TagDao { } } - /** - * Returns stats on tags. - * - * @param userId User ID - * @return Stats by tag - */ - @SuppressWarnings("unchecked") - public List getStats(String userId) { - EntityManager em = ThreadLocalContext.get().getEntityManager(); - StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C, t.TAG_IDPARENT_C, count(d.DOC_ID_C) "); - sb.append(" from T_TAG t "); - sb.append(" left join T_DOCUMENT_TAG dt on t.TAG_ID_C = dt.DOT_IDTAG_C and dt.DOT_DELETEDATE_D is null "); - sb.append(" left join T_DOCUMENT d on d.DOC_ID_C = dt.DOT_IDDOCUMENT_C and d.DOC_DELETEDATE_D is null and d.DOC_IDUSER_C = :userId "); - sb.append(" where t.TAG_IDUSER_C = :userId and t.TAG_DELETEDATE_D is null "); - sb.append(" group by t.TAG_ID_C "); - sb.append(" order by t.TAG_NAME_C "); - - // Perform the query - Query q = em.createNativeQuery(sb.toString()); - q.setParameter("userId", userId); - List l = q.getResultList(); - - // Assemble results - List tagStatDtoList = new ArrayList<>(); - for (Object[] o : l) { - int i = 0; - TagStatDto tagDto = new TagStatDto(); - tagDto.setId((String) o[i++]); - tagDto.setName((String) o[i++]); - tagDto.setColor((String) o[i++]); - tagDto.setParentId((String) o[i++]); - tagDto.setCount(((Number) o[i]).intValue()); - tagStatDtoList.add(tagDto); - } - return tagStatDtoList; - } - /** * Creates a new tag. * diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java index 11eab035..8d508538 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java @@ -1,29 +1,26 @@ package com.sismics.docs.rest.resource; -import java.text.MessageFormat; -import java.util.List; +import com.sismics.docs.core.constant.PermType; +import com.sismics.docs.core.dao.jpa.AclDao; +import com.sismics.docs.core.dao.jpa.TagDao; +import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; +import com.sismics.docs.core.dao.jpa.dto.TagDto; +import com.sismics.docs.core.model.jpa.Acl; +import com.sismics.docs.core.model.jpa.Tag; +import com.sismics.docs.core.util.jpa.SortCriteria; +import com.sismics.rest.exception.ClientException; +import com.sismics.rest.exception.ForbiddenClientException; +import com.sismics.rest.util.JsonUtil; +import com.sismics.rest.util.ValidationUtil; +import org.apache.commons.lang.StringUtils; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObjectBuilder; import javax.ws.rs.*; import javax.ws.rs.core.Response; - -import com.sismics.docs.core.constant.PermType; -import com.sismics.docs.core.dao.jpa.AclDao; -import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; -import com.sismics.docs.core.dao.jpa.dto.TagDto; -import com.sismics.docs.core.model.jpa.Acl; -import com.sismics.docs.core.util.jpa.SortCriteria; -import org.apache.commons.lang.StringUtils; - -import com.sismics.docs.core.dao.jpa.TagDao; -import com.sismics.docs.core.dao.jpa.dto.TagStatDto; -import com.sismics.docs.core.model.jpa.Tag; -import com.sismics.rest.exception.ClientException; -import com.sismics.rest.exception.ForbiddenClientException; -import com.sismics.rest.util.JsonUtil; -import com.sismics.rest.util.ValidationUtil; +import java.text.MessageFormat; +import java.util.List; /** * Tag REST resources. @@ -59,36 +56,7 @@ public class TagResource extends BaseResource { .add("tags", items); return Response.ok().entity(response.build()).build(); } - - /** - * Returns stats on tags. - * - * @return Response - */ - @GET - @Path("/stats") - public Response stats() { - if (!authenticate()) { - throw new ForbiddenClientException(); - } - - TagDao tagDao = new TagDao(); - List tagStatDtoList = tagDao.getStats(principal.getId()); - JsonArrayBuilder items = Json.createArrayBuilder(); - for (TagStatDto tagStatDto : tagStatDtoList) { - items.add(Json.createObjectBuilder() - .add("id", tagStatDto.getId()) - .add("name", tagStatDto.getName()) - .add("color", tagStatDto.getColor()) - .add("parent", JsonUtil.nullable(tagStatDto.getParentId())) - .add("count", tagStatDto.getCount())); - } - - JsonObjectBuilder response = Json.createObjectBuilder() - .add("stats", items); - return Response.ok().entity(response.build()).build(); - } - + /** * Creates a new tag. * diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java index f8b63729..eceac45d 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java @@ -115,15 +115,6 @@ public class TestTagResource extends BaseJerseyTest { Assert.assertEquals(1, tags.size()); Assert.assertEquals(tag4Id, tags.getJsonObject(0).getString("id")); - // Get tag stats - json = target().path("/tag/stats").request() - .cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token) - .get(JsonObject.class); - JsonArray stats = json.getJsonArray("stats"); - Assert.assertTrue(stats.size() == 2); - Assert.assertEquals(1, stats.getJsonObject(0).getInt("count")); - Assert.assertEquals(1, stats.getJsonObject(1).getInt("count")); - // Get all tags json = target().path("/tag/list").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token) From c8f7fe15efd44956046da4ba004f87db268409c8 Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 7 May 2016 15:53:13 +0200 Subject: [PATCH 32/42] #83: Don't return non-visible tag parent --- .../docs/rest/resource/TagResource.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java index 8d508538..ee3990b3 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java @@ -1,5 +1,6 @@ package com.sismics.docs.rest.resource; +import com.google.common.collect.Sets; import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.TagDao; @@ -17,10 +18,12 @@ import org.apache.commons.lang.StringUtils; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObjectBuilder; +import javax.json.JsonValue; import javax.ws.rs.*; import javax.ws.rs.core.Response; import java.text.MessageFormat; import java.util.List; +import java.util.Set; /** * Tag REST resources. @@ -43,13 +46,26 @@ public class TagResource extends BaseResource { TagDao tagDao = new TagDao(); List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)), new SortCriteria(1, true)); + + // Extract tag IDs + Set tagIdSet = Sets.newHashSet(); + for (TagDto tagDto : tagDtoList) { + tagIdSet.add(tagDto.getId()); + } + + // Build the response JsonArrayBuilder items = Json.createArrayBuilder(); for (TagDto tagDto : tagDtoList) { - items.add(Json.createObjectBuilder() + JsonObjectBuilder item = Json.createObjectBuilder() .add("id", tagDto.getId()) .add("name", tagDto.getName()) - .add("color", tagDto.getColor()) - .add("parent", JsonUtil.nullable(tagDto.getParentId()))); // TODO Don't return the parent if it's not visible + .add("color", tagDto.getColor()); + if (tagIdSet.contains(tagDto.getParentId())) { + item.add("parent", tagDto.getParentId()); + } else { + item.add("parent", JsonValue.NULL); + } + items.add(item); } JsonObjectBuilder response = Json.createObjectBuilder() From b851fd0ecc5e725c80a166f02154e31cbc08a4a0 Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 7 May 2016 18:20:01 +0200 Subject: [PATCH 33/42] #83: GET /tag/id --- .../java/com/sismics/rest/util/AclUtil.java | 49 +++++++++++++++++++ .../java/com/sismics/rest/util/JsonUtil.java | 1 - .../docs/rest/resource/DocumentResource.java | 24 ++------- .../docs/rest/resource/TagResource.java | 34 +++++++++++++ .../sismics/docs/rest/TestAclResource.java | 20 ++++++++ .../sismics/docs/rest/TestTagResource.java | 10 ++++ 6 files changed, 116 insertions(+), 22 deletions(-) create mode 100644 docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java diff --git a/docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java b/docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java new file mode 100644 index 00000000..be467d8d --- /dev/null +++ b/docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java @@ -0,0 +1,49 @@ +package com.sismics.rest.util; + +import com.sismics.docs.core.constant.PermType; +import com.sismics.docs.core.dao.jpa.AclDao; +import com.sismics.docs.core.dao.jpa.dto.AclDto; +import com.sismics.security.IPrincipal; + +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import java.util.List; + +/** + * Acl utilities. + * + * @author bgamard + */ +public class AclUtil { + /** + * Add ACLs to a JSON response. + * + * @param json JSON + * @param sourceId Source ID + * @param principal Principal + */ + public static void addAcls(JsonObjectBuilder json, String sourceId, IPrincipal principal) { + AclDao aclDao = new AclDao(); + List aclDtoList = aclDao.getBySourceId(sourceId); + JsonArrayBuilder aclList = Json.createArrayBuilder(); + boolean writable = false; + for (AclDto aclDto : aclDtoList) { + aclList.add(Json.createObjectBuilder() + .add("perm", aclDto.getPerm().name()) + .add("id", aclDto.getTargetId()) + .add("name", JsonUtil.nullable(aclDto.getTargetName())) + .add("type", aclDto.getTargetType())); + + if (!principal.isAnonymous() + && (aclDto.getTargetId().equals(principal.getId()) + || principal.getGroupIdSet().contains(aclDto.getTargetId())) + && aclDto.getPerm() == PermType.WRITE) { + // The source is writable for the current user + writable = true; + } + } + json.add("acls", aclList) + .add("writable", writable); + } +} diff --git a/docs-web-common/src/main/java/com/sismics/rest/util/JsonUtil.java b/docs-web-common/src/main/java/com/sismics/rest/util/JsonUtil.java index a244ef1a..039cc063 100644 --- a/docs-web-common/src/main/java/com/sismics/rest/util/JsonUtil.java +++ b/docs-web-common/src/main/java/com/sismics/rest/util/JsonUtil.java @@ -9,7 +9,6 @@ import javax.json.JsonValue; * @author bgamard */ public class JsonUtil { - /** * Returns a JsonValue from a String. * diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index a7175486..b92197d7 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -25,6 +25,7 @@ import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.exception.ServerException; +import com.sismics.rest.util.AclUtil; import com.sismics.rest.util.JsonUtil; import com.sismics.rest.util.ValidationUtil; import com.sismics.util.mime.MimeType; @@ -110,28 +111,9 @@ public class DocumentResource extends BaseResource { document.add("coverage", JsonUtil.nullable(documentDto.getCoverage())); document.add("rights", JsonUtil.nullable(documentDto.getRights())); document.add("creator", documentDto.getCreator()); - + // Add ACL - List aclDtoList = aclDao.getBySourceId(documentId); - JsonArrayBuilder aclList = Json.createArrayBuilder(); - boolean writable = false; - for (AclDto aclDto : aclDtoList) { - aclList.add(Json.createObjectBuilder() - .add("perm", aclDto.getPerm().name()) - .add("id", aclDto.getTargetId()) - .add("name", JsonUtil.nullable(aclDto.getTargetName())) - .add("type", aclDto.getTargetType())); - - if (!principal.isAnonymous() - && (aclDto.getTargetId().equals(principal.getId()) - || principal.getGroupIdSet().contains(aclDto.getTargetId())) - && aclDto.getPerm() == PermType.WRITE) { - // The document is writable for the current user - writable = true; - } - } - document.add("acls", aclList) - .add("writable", writable); + AclUtil.addAcls(document, documentId, principal); // Add contributors ContributorDao contributorDao = new ContributorDao(); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java index ee3990b3..0654d60f 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java @@ -5,12 +5,14 @@ import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.TagDao; import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; +import com.sismics.docs.core.dao.jpa.dto.AclDto; import com.sismics.docs.core.dao.jpa.dto.TagDto; import com.sismics.docs.core.model.jpa.Acl; import com.sismics.docs.core.model.jpa.Tag; import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ForbiddenClientException; +import com.sismics.rest.util.AclUtil; import com.sismics.rest.util.JsonUtil; import com.sismics.rest.util.ValidationUtil; import org.apache.commons.lang.StringUtils; @@ -73,6 +75,38 @@ public class TagResource extends BaseResource { return Response.ok().entity(response.build()).build(); } + /** + * Returns a tag. + * + * @param id Tag ID + * @return Response + */ + @GET + @Path("{id: [a-z0-9\\-]+}") + public Response get(@PathParam("id") String id) { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + + TagDao tagDao = new TagDao(); + List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(id), null); + if (tagDtoList.size() == 0) { + throw new NotFoundException(); + } + + // Add tag informatiosn + TagDto tagDto = tagDtoList.get(0); + JsonObjectBuilder tag = Json.createObjectBuilder() + .add("id", tagDto.getId()) + .add("name", tagDto.getName()) + .add("color", tagDto.getColor()); + + // Add ACL + AclUtil.addAcls(tag, id, principal); + + return Response.ok().entity(tag.build()).build(); + } + /** * Creates a new tag. * diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java index 0fd0746e..d5b4690a 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java @@ -291,6 +291,12 @@ public class TestAclResource extends BaseJerseyTest { JsonArray tags = json.getJsonArray("tags"); Assert.assertEquals(0, tags.size()); + // acltag2 cannot see tag1 + response = target().path("/tag/" + tag1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .get(); + Assert.assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus()); + // acltag2 cannot see any document json = target().path("/document/list") .queryParam("sort_column", 3) @@ -327,6 +333,13 @@ public class TestAclResource extends BaseJerseyTest { .param("target", "acltag2") .param("type", "USER")), JsonObject.class); + // acltag2 can see tag1 + json = target().path("/tag/" + tag1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .get(JsonObject.class); + Assert.assertFalse(json.getBoolean("writable")); + Assert.assertEquals(3, json.getJsonArray("acls").size()); + // acltag2 still cannot edit tag1 response = target().path("/tag/" + tag1Id).request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) @@ -379,6 +392,13 @@ public class TestAclResource extends BaseJerseyTest { .param("target", "acltag2") .param("type", "USER")), JsonObject.class); + // acltag2 can see and edit tag1 + json = target().path("/tag/" + tag1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .get(JsonObject.class); + Assert.assertTrue(json.getBoolean("writable")); + Assert.assertEquals(4, json.getJsonArray("acls").size()); + // acltag2 can edit tag1 target().path("/tag/" + tag1Id).request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java index eceac45d..ddbcbf29 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java @@ -46,6 +46,16 @@ public class TestTagResource extends BaseJerseyTest { .param("parent", tag3Id)), JsonObject.class); String tag4Id = json.getString("id"); Assert.assertNotNull(tag4Id); + + // Get the tag + json = target().path("/tag/" + tag4Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token) + .get(JsonObject.class); + Assert.assertEquals("Tag4", json.getString("name")); + Assert.assertEquals("#00ff00", json.getString("color")); + Assert.assertTrue(json.getBoolean("writable")); + JsonArray acls = json.getJsonArray("acls"); + Assert.assertEquals(2, acls.size()); // Create a tag with space (not allowed) Response response = target().path("/tag").request() From a55c55bbdb700d9ea6df046a5792c79b733a19da Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 8 May 2016 00:46:32 +0200 Subject: [PATCH 34/42] Closes #83: Edit ACLs for tags in UI + batch for old DB --- .../com/sismics/docs/core/dao/jpa/TagDao.java | 6 +- .../sismics/docs/core/dao/jpa/dto/TagDto.java | 14 + .../java/com/sismics/rest/util/AclUtil.java | 15 +- .../docs/rest/resource/AppResource.java | 56 ++- .../docs/rest/resource/DocumentResource.java | 9 +- .../docs/rest/resource/TagResource.java | 8 +- docs-web/src/main/webapp/src/app/docs/app.js | 340 ++++++++++-------- .../document/DocumentViewPermissions.js | 92 +---- .../webapp/src/app/docs/controller/tag/Tag.js | 38 +- .../src/app/docs/controller/tag/TagEdit.js | 10 + .../webapp/src/app/docs/directive/AclEdit.js | 112 ++++++ .../src/app/docs/directive/SelectTag.js | 6 +- .../main/webapp/src/app/docs/service/Tag.js | 18 - docs-web/src/main/webapp/src/index.html | 3 +- .../src/partial/docs/directive.acledit.html | 61 ++++ .../docs/document.view.permissions.html | 63 +--- .../webapp/src/partial/docs/tag.default.html | 6 + .../webapp/src/partial/docs/tag.edit.html | 4 + .../src/main/webapp/src/partial/docs/tag.html | 18 +- .../src/partial/docs/usergroup.default.html | 2 + .../sismics/docs/rest/TestAclResource.java | 12 +- .../sismics/docs/rest/TestAppResource.java | 35 ++ .../sismics/docs/rest/TestTagResource.java | 1 + 23 files changed, 531 insertions(+), 398 deletions(-) create mode 100644 docs-web/src/main/webapp/src/app/docs/controller/tag/TagEdit.js create mode 100644 docs-web/src/main/webapp/src/app/docs/directive/AclEdit.js delete mode 100644 docs-web/src/main/webapp/src/app/docs/service/Tag.js create mode 100644 docs-web/src/main/webapp/src/partial/docs/directive.acledit.html create mode 100644 docs-web/src/main/webapp/src/partial/docs/tag.default.html create mode 100644 docs-web/src/main/webapp/src/partial/docs/tag.edit.html create mode 100644 docs-web/src/main/webapp/src/partial/docs/usergroup.default.html diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java index eb54b7e6..a00ee16a 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java @@ -176,8 +176,9 @@ public class TagDao { Map parameterMap = new HashMap<>(); List criteriaList = new ArrayList<>(); - StringBuilder sb = new StringBuilder("select distinct t.TAG_ID_C as c0, t.TAG_NAME_C as c1, t.TAG_COLOR_C as c2, t.TAG_IDPARENT_C as c3 "); + StringBuilder sb = new StringBuilder("select distinct t.TAG_ID_C as c0, t.TAG_NAME_C as c1, t.TAG_COLOR_C as c2, t.TAG_IDPARENT_C as c3, u.USE_USERNAME_C as c4 "); sb.append(" from T_TAG t "); + sb.append(" join T_USER u on t.TAG_IDUSER_C = u.USE_ID_C "); // Add search criterias if (criteria.getId() != null) { @@ -223,7 +224,8 @@ public class TagDao { .setId((String) o[i++]) .setName((String) o[i++]) .setColor((String) o[i++]) - .setParentId((String) o[i]); + .setParentId((String) o[i++]) + .setCreator((String) o[i]); tagDtoList.add(tagDto); } diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java index 60524d4a..de40d7bb 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java @@ -26,6 +26,11 @@ public class TagDto { */ private String parentId; + /** + * Creator. + */ + private String creator; + public String getId() { return id; } @@ -61,4 +66,13 @@ public class TagDto { this.parentId = parentId; return this; } + + public String getCreator() { + return creator; + } + + public TagDto setCreator(String creator) { + this.creator = creator; + return this; + } } diff --git a/docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java b/docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java index be467d8d..5e4f5865 100644 --- a/docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java +++ b/docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java @@ -21,29 +21,20 @@ public class AclUtil { * * @param json JSON * @param sourceId Source ID - * @param principal Principal + * @param targetIdList List of target ID */ - public static void addAcls(JsonObjectBuilder json, String sourceId, IPrincipal principal) { + public static void addAcls(JsonObjectBuilder json, String sourceId, List targetIdList) { AclDao aclDao = new AclDao(); List aclDtoList = aclDao.getBySourceId(sourceId); JsonArrayBuilder aclList = Json.createArrayBuilder(); - boolean writable = false; for (AclDto aclDto : aclDtoList) { aclList.add(Json.createObjectBuilder() .add("perm", aclDto.getPerm().name()) .add("id", aclDto.getTargetId()) .add("name", JsonUtil.nullable(aclDto.getTargetName())) .add("type", aclDto.getTargetType())); - - if (!principal.isAnonymous() - && (aclDto.getTargetId().equals(principal.getId()) - || principal.getGroupIdSet().contains(aclDto.getTargetId())) - && aclDto.getPerm() == PermType.WRITE) { - // The source is writable for the current user - writable = true; - } } json.add("acls", aclList) - .add("writable", writable); + .add("writable", aclDao.checkPermission(sourceId, PermType.WRITE, targetIdList)); } } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java index 6aa342ac..80f8c02e 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java @@ -20,6 +20,13 @@ import javax.ws.rs.Path; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; +import com.sismics.docs.core.constant.PermType; +import com.sismics.docs.core.dao.jpa.AclDao; +import com.sismics.docs.core.dao.jpa.TagDao; +import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; +import com.sismics.docs.core.dao.jpa.dto.AclDto; +import com.sismics.docs.core.dao.jpa.dto.TagDto; +import com.sismics.docs.core.model.jpa.Acl; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Appender; import org.apache.log4j.Level; @@ -284,12 +291,12 @@ public class AppResource extends BaseResource { Map userMap = new HashMap<>(); for (File file : fileList) { java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId()); - User user = null; + User user; if (userMap.containsKey(file.getUserId())) { user = userMap.get(file.getUserId()); } else { user = userDao.getById(file.getUserId()); - user.setStorageCurrent(0l); + user.setStorageCurrent(0L); userMap.put(user.getId(), user); } @@ -312,4 +319,49 @@ public class AppResource extends BaseResource { .add("status", "ok"); return Response.ok().entity(response.build()).build(); } + + /** + * Add base ACLs to tags. + * + * @return Response + */ + @POST + @Path("batch/tag_acls") + public Response batchTagAcls() { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + checkBaseFunction(BaseFunction.ADMIN); + + // Get all tags + TagDao tagDao = new TagDao(); + List tagDtoList = tagDao.findByCriteria(new TagCriteria(), null); + + // Add READ and WRITE ACLs + for (TagDto tagDto : tagDtoList) { + AclDao aclDao = new AclDao(); + List aclDtoList = aclDao.getBySourceId(tagDto.getId()); + + if (aclDtoList.size() == 0) { + // Create read ACL + Acl acl = new Acl(); + acl.setPerm(PermType.READ); + acl.setSourceId(tagDto.getId()); + acl.setTargetId(principal.getId()); + aclDao.create(acl, principal.getId()); + + // Create write ACL + acl = new Acl(); + acl.setPerm(PermType.WRITE); + acl.setSourceId(tagDto.getId()); + acl.setTargetId(principal.getId()); + aclDao.create(acl, principal.getId()); + } + } + + // Always return OK + JsonObjectBuilder response = Json.createObjectBuilder() + .add("status", "ok"); + return Response.ok().entity(response.build()).build(); + } } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index b92197d7..4633e34b 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -69,7 +69,6 @@ public class DocumentResource extends BaseResource { authenticate(); DocumentDao documentDao = new DocumentDao(); - AclDao aclDao = new AclDao(); DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId)); if (documentDto == null) { throw new NotFoundException(); @@ -90,7 +89,11 @@ public class DocumentResource extends BaseResource { } else { // Add tags added by the current user on this document TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setDocumentId(documentId), new SortCriteria(1, true)); + List tagDtoList = tagDao.findByCriteria( + new TagCriteria() + .setTargetIdList(getTargetIdList(shareId)) + .setDocumentId(documentId), + new SortCriteria(1, true)); JsonArrayBuilder tags = Json.createArrayBuilder(); for (TagDto tagDto : tagDtoList) { tags.add(Json.createObjectBuilder() @@ -113,7 +116,7 @@ public class DocumentResource extends BaseResource { document.add("creator", documentDto.getCreator()); // Add ACL - AclUtil.addAcls(document, documentId, principal); + AclUtil.addAcls(document, documentId, getTargetIdList(shareId)); // Add contributors ContributorDao contributorDao = new ContributorDao(); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java index 0654d60f..9448f8cf 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java @@ -5,7 +5,6 @@ import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.TagDao; import com.sismics.docs.core.dao.jpa.criteria.TagCriteria; -import com.sismics.docs.core.dao.jpa.dto.AclDto; import com.sismics.docs.core.dao.jpa.dto.TagDto; import com.sismics.docs.core.model.jpa.Acl; import com.sismics.docs.core.model.jpa.Tag; @@ -13,14 +12,12 @@ import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.util.AclUtil; -import com.sismics.rest.util.JsonUtil; import com.sismics.rest.util.ValidationUtil; import org.apache.commons.lang.StringUtils; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObjectBuilder; -import javax.json.JsonValue; import javax.ws.rs.*; import javax.ws.rs.core.Response; import java.text.MessageFormat; @@ -64,8 +61,6 @@ public class TagResource extends BaseResource { .add("color", tagDto.getColor()); if (tagIdSet.contains(tagDto.getParentId())) { item.add("parent", tagDto.getParentId()); - } else { - item.add("parent", JsonValue.NULL); } items.add(item); } @@ -98,11 +93,12 @@ public class TagResource extends BaseResource { TagDto tagDto = tagDtoList.get(0); JsonObjectBuilder tag = Json.createObjectBuilder() .add("id", tagDto.getId()) + .add("creator", tagDto.getCreator()) .add("name", tagDto.getName()) .add("color", tagDto.getColor()); // Add ACL - AclUtil.addAcls(tag, id, principal); + AclUtil.addAcls(tag, id, getTargetIdList(null)); return Response.ok().entity(tag.build()).build(); } diff --git a/docs-web/src/main/webapp/src/app/docs/app.js b/docs-web/src/main/webapp/src/app/docs/app.js index 62e6976a..8c9d4fca 100644 --- a/docs-web/src/main/webapp/src/app/docs/app.js +++ b/docs-web/src/main/webapp/src/app/docs/app.js @@ -13,36 +13,55 @@ angular.module('docs', * Configuring modules. */ .config(function($stateProvider, $httpProvider, RestangularProvider) { + // Configuring UI Router $stateProvider - .state('main', { - url: '', - views: { - 'page': { - templateUrl: 'partial/docs/main.html', - controller: 'Main' + .state('main', { + url: '', + views: { + 'page': { + templateUrl: 'partial/docs/main.html', + controller: 'Main' + } } - } - }) - .state('tag', { - url: '/tag', - views: { - 'page': { - templateUrl: 'partial/docs/tag.html', - controller: 'Tag' + }) + .state('tag', { + url: '/tag', + abstract: true, + views: { + 'page': { + templateUrl: 'partial/docs/tag.html', + controller: 'Tag' + } } - } - }) - .state('settings', { - url: '/settings', - abstract: true, - views: { - 'page': { - templateUrl: 'partial/docs/settings.html', - controller: 'Settings' + }) + .state('tag.default', { + url: '', + views: { + 'tag': { + templateUrl: 'partial/docs/tag.default.html' + } } - } - }) + }) + .state('tag.edit', { + url: '/:id', + views: { + 'tag': { + templateUrl: 'partial/docs/tag.edit.html', + controller: 'TagEdit' + } + } + }) + .state('settings', { + url: '/settings', + abstract: true, + views: { + 'page': { + templateUrl: 'partial/docs/settings.html', + controller: 'Settings' + } + } + }) .state('settings.default', { url: '', views: { @@ -106,70 +125,70 @@ angular.module('docs', } } }) - .state('settings.user.edit', { - url: '/edit/:username', - views: { - 'user': { - templateUrl: 'partial/docs/settings.user.edit.html', - controller: 'SettingsUserEdit' - } + .state('settings.user.edit', { + url: '/edit/:username', + views: { + 'user': { + templateUrl: 'partial/docs/settings.user.edit.html', + controller: 'SettingsUserEdit' } - }) - .state('settings.user.add', { - url: '/add', - views: { - 'user': { - templateUrl: 'partial/docs/settings.user.edit.html', - controller: 'SettingsUserEdit' - } + } + }) + .state('settings.user.add', { + url: '/add', + views: { + 'user': { + templateUrl: 'partial/docs/settings.user.edit.html', + controller: 'SettingsUserEdit' } - }) + } + }) .state('settings.group', { - url: '/group', - views: { - 'settings': { - templateUrl: 'partial/docs/settings.group.html', - controller: 'SettingsGroup' - } + url: '/group', + views: { + 'settings': { + templateUrl: 'partial/docs/settings.group.html', + controller: 'SettingsGroup' } - }) - .state('settings.group.edit', { - url: '/edit/:name', - views: { - 'group': { - templateUrl: 'partial/docs/settings.group.edit.html', - controller: 'SettingsGroupEdit' - } - } - }) - .state('settings.group.add', { - url: '/add', - views: { - 'group': { - templateUrl: 'partial/docs/settings.group.edit.html', - controller: 'SettingsGroupEdit' - } - } - }) - .state('settings.vocabulary', { - url: '/vocabulary', - views: { - 'settings': { - templateUrl: 'partial/docs/settings.vocabulary.html', - controller: 'SettingsVocabulary' } - } - }) - .state('document', { - url: '/document', - abstract: true, - views: { - 'page': { - templateUrl: 'partial/docs/document.html', - controller: 'Document' + }) + .state('settings.group.edit', { + url: '/edit/:name', + views: { + 'group': { + templateUrl: 'partial/docs/settings.group.edit.html', + controller: 'SettingsGroupEdit' + } } - } - }) + }) + .state('settings.group.add', { + url: '/add', + views: { + 'group': { + templateUrl: 'partial/docs/settings.group.edit.html', + controller: 'SettingsGroupEdit' + } + } + }) + .state('settings.vocabulary', { + url: '/vocabulary', + views: { + 'settings': { + templateUrl: 'partial/docs/settings.vocabulary.html', + controller: 'SettingsVocabulary' + } + } + }) + .state('document', { + url: '/document', + abstract: true, + views: { + 'page': { + templateUrl: 'partial/docs/document.html', + controller: 'Document' + } + } + }) .state('document.default', { url: '', views: { @@ -179,17 +198,17 @@ angular.module('docs', } } }) - .state('document.default.search', { - url: '/search/:search' - }) - .state('document.default.file', { - url: '/file/:fileId', - views: { - 'file': { - controller: 'FileView' - } + .state('document.default.search', { + url: '/search/:search' + }) + .state('document.default.file', { + url: '/file/:fileId', + views: { + 'file': { + controller: 'FileView' } - }) + } + }) .state('document.add', { url: '/add?files', views: { @@ -218,59 +237,68 @@ angular.module('docs', } } }) - .state('document.view.content', { - url: '/content', - views: { - 'tab': { - templateUrl: 'partial/docs/document.view.content.html', - controller: 'DocumentViewContent' - } + .state('document.view.content', { + url: '/content', + views: { + 'tab': { + templateUrl: 'partial/docs/document.view.content.html', + controller: 'DocumentViewContent' } - }) - .state('document.view.content.file', { - url: '/file/:fileId', - views: { - 'file': { - controller: 'FileView' - } - } - }) - .state('document.view.permissions', { - url: '/permissions', - views: { - 'tab': { - templateUrl: 'partial/docs/document.view.permissions.html', - controller: 'DocumentViewPermissions' - } - } - }) - .state('document.view.activity', { - url: '/activity', - views: { - 'tab': { - templateUrl: 'partial/docs/document.view.activity.html', - controller: 'DocumentViewActivity' - } - } - }) - .state('login', { - url: '/login', - views: { - 'page': { - templateUrl: 'partial/docs/login.html', - controller: 'Login' } - } - }) - .state('user', { - url: '/user', - views: { - 'page': { - templateUrl: 'partial/docs/usergroup.html', - controller: 'UserGroup' + }) + .state('document.view.content.file', { + url: '/file/:fileId', + views: { + 'file': { + controller: 'FileView' + } } - } - }) + }) + .state('document.view.permissions', { + url: '/permissions', + views: { + 'tab': { + templateUrl: 'partial/docs/document.view.permissions.html', + controller: 'DocumentViewPermissions' + } + } + }) + .state('document.view.activity', { + url: '/activity', + views: { + 'tab': { + templateUrl: 'partial/docs/document.view.activity.html', + controller: 'DocumentViewActivity' + } + } + }) + .state('login', { + url: '/login', + views: { + 'page': { + templateUrl: 'partial/docs/login.html', + controller: 'Login' + } + } + }) + .state('user', { + url: '/user', + abstract: true, + views: { + 'page': { + templateUrl: 'partial/docs/usergroup.html', + controller: 'UserGroup' + } + } + }) + .state('user.default', { + url: '', + views: { + 'sub': { + templateUrl: 'partial/docs/usergroup.default.html' + } + } + }) .state('user.profile', { url: '/:username', views: { @@ -280,15 +308,24 @@ angular.module('docs', } } }) - .state('group', { - url: '/group', - views: { - 'page': { - templateUrl: 'partial/docs/usergroup.html', - controller: 'UserGroup' + .state('group', { + url: '/group', + abstract: true, + views: { + 'page': { + templateUrl: 'partial/docs/usergroup.html', + controller: 'UserGroup' + } } - } - }) + }) + .state('group.default', { + url: '', + views: { + 'sub': { + templateUrl: 'partial/docs/usergroup.default.html' + } + } + }) .state('group.profile', { url: '/:name', views: { @@ -298,7 +335,6 @@ angular.module('docs', } } }); - // Configuring Restangular RestangularProvider.setBaseUrl('../api'); diff --git a/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewPermissions.js b/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewPermissions.js index 80231f2f..bfbc5493 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewPermissions.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewPermissions.js @@ -3,95 +3,5 @@ /** * Document view permissions controller. */ -angular.module('docs').controller('DocumentViewPermissions', function ($scope, $stateParams, Restangular, $q) { - // Watch for ACLs change and group them for easy displaying - $scope.$watch('document.acls', function(acls) { - $scope.acls = _.groupBy(acls, function(acl) { - return acl.id; - }); - }); - - // Initialize add ACL - $scope.acl = { perm: 'READ' }; - - /** - * Delete an ACL. - */ - $scope.deleteAcl = function(acl) { - Restangular.one('acl/' + $stateParams.id + '/' + acl.perm + '/' + acl.id, null).remove().then(function () { - $scope.document.acls = _.reject($scope.document.acls, function(s) { - return angular.equals(acl, s); - }); - }); - }; - - /** - * Add an ACL. - */ - $scope.addAcl = function() { - // Compute ACLs to add - $scope.acl.source = $stateParams.id; - var acls = []; - if ($scope.acl.perm == 'READWRITE') { - acls = [{ - source: $stateParams.id, - target: $scope.acl.target.name, - perm: 'READ', - type: $scope.acl.target.type - }, { - source: $stateParams.id, - target: $scope.acl.target.name, - perm: 'WRITE', - type: $scope.acl.target.type - }]; - } else { - acls = [{ - source: $stateParams.id, - target: $scope.acl.target.name, - perm: $scope.acl.perm, - type: $scope.acl.target.type - }]; - } - - // Add ACLs - _.each(acls, function(acl) { - Restangular.one('acl').put(acl).then(function(acl) { - if (_.isUndefined(acl.id)) { - return; - } - $scope.document.acls.push(acl); - $scope.document.acls = angular.copy($scope.document.acls); - }); - }); - - // Reset form - $scope.acl = { perm: 'READ' }; - }; - - /** - * Auto-complete on ACL target. - */ - $scope.getTargetAclTypeahead = function($viewValue) { - var deferred = $q.defer(); - Restangular.one('acl/target/search') - .get({ - search: $viewValue - }).then(function(data) { - var output = []; - - // Add the type to use later - output.push.apply(output, _.map(data.users, function(user) { - user.type = 'USER'; - return user; - })); - output.push.apply(output, _.map(data.groups, function(group) { - group.type = 'GROUP'; - return group; - })); - - // Send the data to the typeahead directive - deferred.resolve(output, true); - }); - return deferred.promise; - }; +angular.module('docs').controller('DocumentViewPermissions', function() { }); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/docs/controller/tag/Tag.js b/docs-web/src/main/webapp/src/app/docs/controller/tag/Tag.js index 53b44ea1..acf3b5b5 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/tag/Tag.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/tag/Tag.js @@ -3,37 +3,14 @@ /** * Tag controller. */ -angular.module('docs').controller('Tag', function($scope, $dialog, Tag, Restangular) { +angular.module('docs').controller('Tag', function($scope, $dialog, Restangular) { $scope.tag = { name: '', color: '#3a87ad' }; // Retrieve tags - Tag.tags().then(function(data) { + Restangular.one('tag/list').get().then(function(data) { $scope.tags = data.tags; }); - // Retrieve tag stats - Restangular.one('tag/stats').get().then(function(data) { - $scope.stats = data.stats; - }); - - /** - * Returns total number of document from tag stats. - */ - $scope.getStatCount = function() { - return _.reduce($scope.stats, function(memo, stat) { - return memo + stat.count - }, 0); - }; - - /** - * Validate a tag name for duplicate. - */ - $scope.validateDuplicate = function(name) { - return !_.find($scope.tags, function(tag) { - return tag.name == name; - }); - }; - /** * Add a tag. */ @@ -71,15 +48,6 @@ angular.module('docs').controller('Tag', function($scope, $dialog, Tag, Restangu */ $scope.updateTag = function(tag) { // Update the server - return Restangular.one('tag', tag.id).post('', tag).then(function () { - // Update the stat object - var stat = _.find($scope.stats, function (t) { - return tag.id == t.id; - }); - - if (stat) { - _.extend(stat, tag); - } - }); + return Restangular.one('tag', tag.id).post('', tag); }; }); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/docs/controller/tag/TagEdit.js b/docs-web/src/main/webapp/src/app/docs/controller/tag/TagEdit.js new file mode 100644 index 00000000..1910309d --- /dev/null +++ b/docs-web/src/main/webapp/src/app/docs/controller/tag/TagEdit.js @@ -0,0 +1,10 @@ +'use strict'; + +/** + * Tag edit controller. + */ +angular.module('docs').controller('TagEdit', function($scope, $stateParams, Restangular) { + Restangular.one('tag', $stateParams.id).get().then(function(data) { + $scope.tag = data; + }) +}); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/docs/directive/AclEdit.js b/docs-web/src/main/webapp/src/app/docs/directive/AclEdit.js new file mode 100644 index 00000000..0e53b207 --- /dev/null +++ b/docs-web/src/main/webapp/src/app/docs/directive/AclEdit.js @@ -0,0 +1,112 @@ +'use strict'; + +/** + * ACL edit directive. + */ +angular.module('docs').directive('aclEdit', function() { + return { + restrict: 'E', + templateUrl: 'partial/docs/directive.acledit.html', + replace: true, + scope: { + source: '=', + acls: '=', + writable: '=', + creator: '=' + }, + controller: function($scope, Restangular, $q) { + // Watch for ACLs change and group them for easy displaying + $scope.$watch('acls', function(acls) { + $scope.groupedAcls = _.groupBy(acls, function(acl) { + return acl.id; + }); + }); + + // Initialize add ACL + $scope.acl = { perm: 'READ' }; + + /** + * Delete an ACL. + */ + $scope.deleteAcl = function(acl) { + Restangular.one('acl/' + $scope.source + '/' + acl.perm + '/' + acl.id, null).remove().then(function () { + $scope.acls = _.reject($scope.acls, function(s) { + return angular.equals(acl, s); + }); + }); + }; + + /** + * Add an ACL. + */ + $scope.addAcl = function() { + // Compute ACLs to add + $scope.acl.source = $scope.source; + var acls = []; + if ($scope.acl.perm == 'READWRITE') { + acls = [{ + source: $scope.source, + target: $scope.acl.target.name, + perm: 'READ', + type: $scope.acl.target.type + }, { + source: $scope.source, + target: $scope.acl.target.name, + perm: 'WRITE', + type: $scope.acl.target.type + }]; + } else { + acls = [{ + source: $scope.source, + target: $scope.acl.target.name, + perm: $scope.acl.perm, + type: $scope.acl.target.type + }]; + } + + // Add ACLs + _.each(acls, function(acl) { + Restangular.one('acl').put(acl).then(function(acl) { + if (_.isUndefined(acl.id)) { + return; + } + $scope.acls.push(acl); + $scope.acls = angular.copy($scope.acls); + }); + }); + + // Reset form + $scope.acl = { perm: 'READ' }; + }; + + /** + * Auto-complete on ACL target. + */ + $scope.getTargetAclTypeahead = function($viewValue) { + var deferred = $q.defer(); + Restangular.one('acl/target/search') + .get({ + search: $viewValue + }).then(function(data) { + var output = []; + + // Add the type to use later + output.push.apply(output, _.map(data.users, function(user) { + user.type = 'USER'; + return user; + })); + output.push.apply(output, _.map(data.groups, function(group) { + group.type = 'GROUP'; + return group; + })); + + // Send the data to the typeahead directive + deferred.resolve(output, true); + }); + return deferred.promise; + }; + }, + link: function(scope, element, attr, ctrl) { + } + } +}); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/docs/directive/SelectTag.js b/docs-web/src/main/webapp/src/app/docs/directive/SelectTag.js index c0753325..d61ee60e 100644 --- a/docs-web/src/main/webapp/src/app/docs/directive/SelectTag.js +++ b/docs-web/src/main/webapp/src/app/docs/directive/SelectTag.js @@ -13,9 +13,9 @@ angular.module('docs').directive('selectTag', function() { ref: '@', ngDisabled: '=' }, - controller: function($scope, Tag) { + controller: function($scope, Restangular) { // Retrieve tags - Tag.tags().then(function(data) { + Restangular.one('tag/list').get().then(function(data) { $scope.allTags = data.tags; }); @@ -48,7 +48,7 @@ angular.module('docs').directive('selectTag', function() { if ($event) { $event.preventDefault(); } - } + }; /** * Remove a tag. diff --git a/docs-web/src/main/webapp/src/app/docs/service/Tag.js b/docs-web/src/main/webapp/src/app/docs/service/Tag.js deleted file mode 100644 index 04a94687..00000000 --- a/docs-web/src/main/webapp/src/app/docs/service/Tag.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -/** - * Tag service. - */ -angular.module('docs').factory('Tag', function(Restangular) { - var tags = null; - - return { - /** - * Returns tags. - * @param force If true, force reloading data - */ - tags: function(force) { - return Restangular.one('tag/list').get(); - } - } -}); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/index.html b/docs-web/src/main/webapp/src/index.html index 3924d6e7..5b92a0b1 100644 --- a/docs-web/src/main/webapp/src/index.html +++ b/docs-web/src/main/webapp/src/index.html @@ -55,6 +55,7 @@ + @@ -73,7 +74,6 @@ - @@ -84,6 +84,7 @@ + diff --git a/docs-web/src/main/webapp/src/partial/docs/directive.acledit.html b/docs-web/src/main/webapp/src/partial/docs/directive.acledit.html new file mode 100644 index 00000000..7e436e50 --- /dev/null +++ b/docs-web/src/main/webapp/src/partial/docs/directive.acledit.html @@ -0,0 +1,61 @@ +
+ + + + + + + + + + +
ForPermission
+ + {{ a.perm }} + + +
+ +
+

Add a permission

+ +
+
+ +
+ +
+
+ + + +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.permissions.html b/docs-web/src/main/webapp/src/partial/docs/document.view.permissions.html index 07160373..0c611570 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.view.permissions.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.view.permissions.html @@ -1,59 +1,4 @@ - - - - - - - - - - -
ForPermission
- - {{ a.perm }} - - -
- -
-

Add a permission

- -
-
- -
- -
-
- - - -
-
- -
- -
- -
-
- -
-
- -
-
-
-
\ No newline at end of file + \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/partial/docs/tag.default.html b/docs-web/src/main/webapp/src/partial/docs/tag.default.html new file mode 100644 index 00000000..dee44dec --- /dev/null +++ b/docs-web/src/main/webapp/src/partial/docs/tag.default.html @@ -0,0 +1,6 @@ +

Tags

+

Tags are labels associated to documents.

+

A document can be tagged by multiple tags, and a tag can be applied to multiple documents.

+

Using the button, you can edit permissions on a tag.

+

If a tag can be read by another user or group, associated documents can also be read by those people.

+

For example, tag your company documents with a tag MyCompany and add the permission Read to a group employees

\ No newline at end of file diff --git a/docs-web/src/main/webapp/src/partial/docs/tag.edit.html b/docs-web/src/main/webapp/src/partial/docs/tag.edit.html new file mode 100644 index 00000000..90f56a22 --- /dev/null +++ b/docs-web/src/main/webapp/src/partial/docs/tag.edit.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/partial/docs/tag.html b/docs-web/src/main/webapp/src/partial/docs/tag.html index 733ab8ee..576a8bfa 100644 --- a/docs-web/src/main/webapp/src/partial/docs/tag.html +++ b/docs-web/src/main/webapp/src/partial/docs/tag.html @@ -5,10 +5,9 @@

  + ng-maxlength="36" required ng-model="tag.name" ui-validate="{ space: '!$value || $value.indexOf(\' \') == -1' }"> Add

- This tag already exists Space are not allowed @@ -31,22 +30,15 @@   - + + -
-

{{ tags.length }} tag{{ tags.length > 1 ? 's' : '' }}

-
-
{{ stat.name }} {{ stat.count }}
-
-
-
- -
- +
+
\ No newline at end of file diff --git a/docs-web/src/main/webapp/src/partial/docs/usergroup.default.html b/docs-web/src/main/webapp/src/partial/docs/usergroup.default.html new file mode 100644 index 00000000..5899b461 --- /dev/null +++ b/docs-web/src/main/webapp/src/partial/docs/usergroup.default.html @@ -0,0 +1,2 @@ +

Users & Groups

+

Here you can view informations about users and groups.

\ No newline at end of file diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java index d5b4690a..e693ea05 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java @@ -357,12 +357,13 @@ public class TestAclResource extends BaseJerseyTest { .param("language", "eng"))); Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus()); - // acltag2 can see document1 with tag1 + // acltag2 can see document1 with tag1 (non-writable) json = target().path("/document/" + document1Id).request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) .get(JsonObject.class); tags = json.getJsonArray("tags"); Assert.assertEquals(1, tags.size()); + Assert.assertFalse(json.getBoolean("writable")); Assert.assertEquals(tag1Id, tags.getJsonObject(0).getString("id")); // acltag2 can see tag1 @@ -392,6 +393,15 @@ public class TestAclResource extends BaseJerseyTest { .param("target", "acltag2") .param("type", "USER")), JsonObject.class); + // acltag2 can see document1 with tag1 (writable) + json = target().path("/document/" + document1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) + .get(JsonObject.class); + tags = json.getJsonArray("tags"); + Assert.assertEquals(1, tags.size()); + Assert.assertTrue(json.getBoolean("writable")); + Assert.assertEquals(tag1Id, tags.getJsonObject(0).getString("id")); + // acltag2 can see and edit tag1 json = target().path("/tag/" + tag1Id).request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token) diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java index 4a8df5b0..e8fc889c 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestAppResource.java @@ -2,11 +2,17 @@ package com.sismics.docs.rest; import javax.json.JsonArray; import javax.json.JsonObject; +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Form; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import com.sismics.docs.core.constant.PermType; +import com.sismics.docs.core.dao.jpa.AclDao; +import com.sismics.util.context.ThreadLocalContext; +import com.sismics.util.jpa.EMF; import org.junit.Assert; import org.junit.Test; @@ -57,6 +63,35 @@ public class TestAppResource extends BaseJerseyTest { .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) .post(Entity.form(new Form())); Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + + // Create a tag + json = target().path("/tag").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .put(Entity.form(new Form() + .param("name", "Tag4") + .param("color", "#00ff00")), JsonObject.class); + String tagId = json.getString("id"); + + // Init transactional context + EntityManager em = EMF.get().createEntityManager(); + ThreadLocalContext context = ThreadLocalContext.get(); + context.setEntityManager(em); + EntityTransaction tx = em.getTransaction(); + tx.begin(); + + // Remove base ACLs + AclDao aclDao = new AclDao(); + aclDao.delete(tagId, PermType.READ, "admin", "admin"); + aclDao.delete(tagId, PermType.WRITE, "admin", "admin"); + Assert.assertEquals(0, aclDao.getBySourceId(tagId).size()); + tx.commit(); + + // Add base ACLs to tags + response = target().path("/app/batch/tag_acls").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .post(Entity.form(new Form())); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + Assert.assertEquals(2, aclDao.getBySourceId(tagId).size()); } /** diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java index ddbcbf29..410725a7 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java @@ -52,6 +52,7 @@ public class TestTagResource extends BaseJerseyTest { .cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token) .get(JsonObject.class); Assert.assertEquals("Tag4", json.getString("name")); + Assert.assertEquals("tag1", json.getString("creator")); Assert.assertEquals("#00ff00", json.getString("color")); Assert.assertTrue(json.getBoolean("writable")); JsonArray acls = json.getJsonArray("acls"); From 3dd8a52f7d3a2c9d16026b2aa8d8b5a732704aa3 Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 8 May 2016 01:03:15 +0200 Subject: [PATCH 35/42] #83: Fix test for tag parent --- .../src/test/java/com/sismics/docs/rest/TestTagResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java index 410725a7..3724d848 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java @@ -152,7 +152,7 @@ public class TestTagResource extends BaseJerseyTest { Assert.assertTrue(tags.size() > 0); Assert.assertEquals("UpdatedName", tags.getJsonObject(1).getString("name")); Assert.assertEquals("#0000ff", tags.getJsonObject(1).getString("color")); - Assert.assertEquals(JsonValue.NULL, tags.getJsonObject(1).get("parent")); + Assert.assertNull(tags.getJsonObject(1).get("parent")); // Deletes a tag target().path("/tag/" + tag4Id).request() From 642b9a63d314338cb7b729dfa7ff03376c2a988c Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 8 May 2016 12:14:06 +0200 Subject: [PATCH 36/42] Cleanup ACL checks --- .../docs/rest/resource/CommentResource.java | 14 +++++++------- .../docs/rest/resource/DocumentResource.java | 8 ++++---- .../sismics/docs/rest/resource/FileResource.java | 13 ++++++------- .../sismics/docs/rest/resource/ShareResource.java | 8 +++----- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/CommentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/CommentResource.java index bd032dcd..ce745054 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/CommentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/CommentResource.java @@ -1,8 +1,8 @@ package com.sismics.docs.rest.resource; import com.sismics.docs.core.constant.PermType; +import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.CommentDao; -import com.sismics.docs.core.dao.jpa.DocumentDao; import com.sismics.docs.core.dao.jpa.dto.CommentDto; import com.sismics.docs.core.model.jpa.Comment; import com.sismics.rest.exception.ForbiddenClientException; @@ -42,8 +42,8 @@ public class CommentResource extends BaseResource { content = ValidationUtil.validateLength(content, "content", 1, 4000, false); // Read access on doc gives access to write comments - DocumentDao documentDao = new DocumentDao(); - if (documentDao.getDocument(documentId, PermType.READ, getTargetIdList(null)) == null) { + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(null))) { throw new NotFoundException(); } @@ -88,8 +88,8 @@ public class CommentResource extends BaseResource { // If the current user owns the comment, skip ACL check if (!comment.getUserId().equals(principal.getId())) { // Get the associated document - DocumentDao documentDao = new DocumentDao(); - if (documentDao.getDocument(comment.getDocumentId(), PermType.WRITE, getTargetIdList(null)) == null) { + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(comment.getDocumentId(), PermType.WRITE, getTargetIdList(null))) { throw new NotFoundException(); } } @@ -116,8 +116,8 @@ public class CommentResource extends BaseResource { authenticate(); // Read access on doc gives access to read comments - DocumentDao documentDao = new DocumentDao(); - if (documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId)) == null) { + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(shareId))) { throw new NotFoundException(); } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index 4633e34b..ceb61274 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -668,14 +668,14 @@ public class DocumentResource extends BaseResource { // Get the document DocumentDao documentDao = new DocumentDao(); FileDao fileDao = new FileDao(); - DocumentDto documentDto = documentDao.getDocument(id, PermType.WRITE, getTargetIdList(null)); - if (documentDto == null) { + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(id, PermType.WRITE, getTargetIdList(null))) { throw new NotFoundException(); } List fileList = fileDao.getByDocumentId(principal.getId(), id); // Delete the document - documentDao.delete(documentDto.getId(), principal.getId()); + documentDao.delete(id, principal.getId()); // Raise file deleted events (don't bother sending document updated event) for (File file : fileList) { @@ -688,7 +688,7 @@ public class DocumentResource extends BaseResource { // Raise a document deleted event DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent(); documentDeletedAsyncEvent.setUserId(principal.getId()); - documentDeletedAsyncEvent.setDocumentId(documentDto.getId()); + documentDeletedAsyncEvent.setDocumentId(id); AppContext.getInstance().getAsyncEventBus().post(documentDeletedAsyncEvent); // Always return OK diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java index 31a3af3a..b692ed4e 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java @@ -265,8 +265,8 @@ public class FileResource extends BaseResource { ValidationUtil.validateRequired(idList, "order"); // Get the document - DocumentDao documentDao = new DocumentDao(); - if (documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null)) == null) { + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(documentId, PermType.WRITE, getTargetIdList(null))) { throw new NotFoundException(); } @@ -347,20 +347,19 @@ public class FileResource extends BaseResource { // Get the file FileDao fileDao = new FileDao(); - DocumentDao documentDao = new DocumentDao(); + AclDao aclDao = new AclDao(); File file = fileDao.getFile(id); if (file == null) { throw new NotFoundException(); } - DocumentDto documentDto = null; if (file.getDocumentId() == null) { // It's an orphan file if (!file.getUserId().equals(principal.getId())) { // But not ours throw new ForbiddenClientException(); } - } else if ((documentDto = documentDao.getDocument(file.getDocumentId(), PermType.WRITE, getTargetIdList(null))) == null) { + } else if (!aclDao.checkPermission(file.getDocumentId(), PermType.WRITE, getTargetIdList(null))) { throw new NotFoundException(); } @@ -384,11 +383,11 @@ public class FileResource extends BaseResource { fileDeletedAsyncEvent.setFile(file); AppContext.getInstance().getAsyncEventBus().post(fileDeletedAsyncEvent); - if (documentDto != null) { + if (file.getDocumentId() != null) { // Raise a new document updated DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent(); documentUpdatedAsyncEvent.setUserId(principal.getId()); - documentUpdatedAsyncEvent.setDocumentId(documentDto.getId()); + documentUpdatedAsyncEvent.setDocumentId(file.getDocumentId()); AppContext.getInstance().getAsyncEventBus().post(documentUpdatedAsyncEvent); } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java index f51d2214..9e4d49e5 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java @@ -4,7 +4,6 @@ package com.sismics.docs.rest.resource; import com.sismics.docs.core.constant.AclTargetType; import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.AclDao; -import com.sismics.docs.core.dao.jpa.DocumentDao; import com.sismics.docs.core.dao.jpa.ShareDao; import com.sismics.docs.core.model.jpa.Acl; import com.sismics.docs.core.model.jpa.Share; @@ -46,9 +45,9 @@ public class ShareResource extends BaseResource { ValidationUtil.validateRequired(documentId, "id"); name = ValidationUtil.validateLength(name, "name", 1, 36, true); - // Get the document - DocumentDao documentDao = new DocumentDao(); - if (documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null)) == null) { + // Check write permission on the document + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(documentId, PermType.WRITE, getTargetIdList(null))) { throw new NotFoundException(); } @@ -59,7 +58,6 @@ public class ShareResource extends BaseResource { shareDao.create(share); // Create the ACL - AclDao aclDao = new AclDao(); Acl acl = new Acl(); acl.setSourceId(documentId); acl.setPerm(PermType.READ); From bf4cb02de5c50151d0bc86303b4e9ea6e627bf45 Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 8 May 2016 13:45:46 +0200 Subject: [PATCH 37/42] Closes #91: Display ACL inherited from tags in document permissions --- .../java/com/sismics/rest/util/AclUtil.java | 1 - .../docs/rest/resource/DocumentResource.java | 26 ++++++++++-- .../document/DocumentViewPermissions.js | 8 +++- .../src/partial/docs/directive.acledit.html | 12 +++--- .../partial/docs/document.view.activity.html | 2 + .../docs/document.view.permissions.html | 41 +++++++++++++++++-- .../webapp/src/partial/docs/tag.edit.html | 4 ++ .../sismics/docs/rest/TestAclResource.java | 8 ++++ 8 files changed, 87 insertions(+), 15 deletions(-) diff --git a/docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java b/docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java index 5e4f5865..e9de8794 100644 --- a/docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java +++ b/docs-web-common/src/main/java/com/sismics/rest/util/AclUtil.java @@ -3,7 +3,6 @@ package com.sismics.rest.util; import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.dto.AclDto; -import com.sismics.security.IPrincipal; import javax.json.Json; import javax.json.JsonArrayBuilder; diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index ceb61274..555e4dc5 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -82,14 +82,15 @@ public class DocumentResource extends BaseResource { .add("language", documentDto.getLanguage()) .add("shared", documentDto.getShared()) .add("file_count", documentDto.getFileCount()); - + + List tagDtoList = null; if (principal.isAnonymous()) { // No tags in anonymous mode (sharing) document.add("tags", Json.createArrayBuilder()); } else { - // Add tags added by the current user on this document + // Add tags visible by the current user on this document TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.findByCriteria( + tagDtoList = tagDao.findByCriteria( new TagCriteria() .setTargetIdList(getTargetIdList(shareId)) .setDocumentId(documentId), @@ -117,6 +118,25 @@ public class DocumentResource extends BaseResource { // Add ACL AclUtil.addAcls(document, documentId, getTargetIdList(shareId)); + + // Add computed ACL + if (tagDtoList != null) { + JsonArrayBuilder aclList = Json.createArrayBuilder(); + for (TagDto tagDto : tagDtoList) { + AclDao aclDao = new AclDao(); + List aclDtoList = aclDao.getBySourceId(tagDto.getId()); + for (AclDto aclDto : aclDtoList) { + aclList.add(Json.createObjectBuilder() + .add("perm", aclDto.getPerm().name()) + .add("source_id", tagDto.getId()) + .add("source_name", tagDto.getName()) + .add("id", aclDto.getTargetId()) + .add("name", JsonUtil.nullable(aclDto.getTargetName())) + .add("type", aclDto.getTargetType())); + } + } + document.add("inherited_acls", aclList); + } // Add contributors ContributorDao contributorDao = new ContributorDao(); diff --git a/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewPermissions.js b/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewPermissions.js index bfbc5493..cb953fcf 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewPermissions.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/document/DocumentViewPermissions.js @@ -3,5 +3,11 @@ /** * Document view permissions controller. */ -angular.module('docs').controller('DocumentViewPermissions', function() { +angular.module('docs').controller('DocumentViewPermissions', function($scope) { + // Watch for ACLs change and group them for easy displaying + $scope.$watch('document.inherited_acls', function(acls) { + $scope.inheritedAcls = _.groupBy(acls, function(acl) { + return acl.id; + }); + }); }); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/partial/docs/directive.acledit.html b/docs-web/src/main/webapp/src/partial/docs/directive.acledit.html index 7e436e50..9cf0c792 100644 --- a/docs-web/src/main/webapp/src/partial/docs/directive.acledit.html +++ b/docs-web/src/main/webapp/src/partial/docs/directive.acledit.html @@ -8,12 +8,12 @@ - - {{ a.perm }} - - + + {{ a.perm }} + + diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.activity.html b/docs-web/src/main/webapp/src/partial/docs/document.view.activity.html index 6726c5ed..ff100f5e 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.view.activity.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.view.activity.html @@ -1 +1,3 @@ +

Every actions on this document are logged here.

+ \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.permissions.html b/docs-web/src/main/webapp/src/partial/docs/document.view.permissions.html index 0c611570..7c4401f5 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.view.permissions.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.view.permissions.html @@ -1,4 +1,37 @@ - \ No newline at end of file +

Permissions can be applied directly to this document, or can come from tags.

+ +
+

Permissions inherited by tags

+ + + + + + + + + + + + + +
FromForPermission
+ +   + {{ acl[0].source_name }} + + + + {{ a.perm }} + +
+
+ +
+

Permissions on this document

+ + +
\ No newline at end of file diff --git a/docs-web/src/main/webapp/src/partial/docs/tag.edit.html b/docs-web/src/main/webapp/src/partial/docs/tag.edit.html index 90f56a22..98332e9a 100644 --- a/docs-web/src/main/webapp/src/partial/docs/tag.edit.html +++ b/docs-web/src/main/webapp/src/partial/docs/tag.edit.html @@ -1,3 +1,7 @@ +

{{ tag.name }}

+ +

Permissions on this tag will also be applied to documents tagged {{ tag.name }}

+ Date: Sun, 8 May 2016 13:47:35 +0200 Subject: [PATCH 38/42] Tag color in #/tag/id --- docs-web/src/main/webapp/src/partial/docs/tag.edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-web/src/main/webapp/src/partial/docs/tag.edit.html b/docs-web/src/main/webapp/src/partial/docs/tag.edit.html index 98332e9a..10e48dbe 100644 --- a/docs-web/src/main/webapp/src/partial/docs/tag.edit.html +++ b/docs-web/src/main/webapp/src/partial/docs/tag.edit.html @@ -1,6 +1,6 @@

{{ tag.name }}

-

Permissions on this tag will also be applied to documents tagged {{ tag.name }}

+

Permissions on this tag will also be applied to documents tagged {{ tag.name }}

Date: Sun, 8 May 2016 15:38:47 +0200 Subject: [PATCH 39/42] #79: Change custom CSS and app name --- .../docs/rest/resource/ThemeResource.java | 2 ++ docs-web/src/main/webapp/src/app/docs/app.js | 10 +++++-- .../docs/controller/settings/SettingsTheme.js | 29 ++++++++++--------- docs-web/src/main/webapp/src/app/share/app.js | 8 ++++- docs-web/src/main/webapp/src/index.html | 4 +-- .../src/partial/docs/settings.security.html | 6 ++-- .../src/partial/docs/settings.theme.html | 22 ++++++++++++-- docs-web/src/main/webapp/src/share.html | 4 +-- .../sismics/docs/rest/TestThemeResource.java | 4 +++ 9 files changed, 62 insertions(+), 27 deletions(-) diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java index abf6ee13..6e3f96a1 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java @@ -56,6 +56,8 @@ public class ThemeResource extends BaseResource { JsonObject themeConfig = getThemeConfig(); JsonObjectBuilder json = Json.createObjectBuilder(); json.add("name", themeConfig.getString("name", "Sismics Docs")); + json.add("color", themeConfig.getString("color", "#263238")); + json.add("css", themeConfig.getString("css", "")); return Response.ok().entity(json.build()).build(); } diff --git a/docs-web/src/main/webapp/src/app/docs/app.js b/docs-web/src/main/webapp/src/app/docs/app.js index 8c9d4fca..f68b8b72 100644 --- a/docs-web/src/main/webapp/src/app/docs/app.js +++ b/docs-web/src/main/webapp/src/app/docs/app.js @@ -13,7 +13,6 @@ angular.module('docs', * Configuring modules. */ .config(function($stateProvider, $httpProvider, RestangularProvider) { - // Configuring UI Router $stateProvider .state('main', { @@ -381,10 +380,15 @@ angular.module('docs', /** * Application initialization. */ -.run(function($rootScope, $state, $stateParams) { +.run(function($rootScope, $state, $stateParams, Restangular) { $rootScope.$state = $state; $rootScope.$stateParams = $stateParams; - $rootScope.pageTitle = 'Sismics Docs'; + + // Fetch the current theme configuration + $rootScope.appName = ''; + Restangular.one('theme').get().then(function(data) { + $rootScope.appName = data.name; + }); }) /** * Redirection support for ui-router. diff --git a/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsTheme.js b/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsTheme.js index 405d0066..02a06f9d 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsTheme.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/settings/SettingsTheme.js @@ -3,19 +3,20 @@ /** * Settings theme page controller. */ -angular.module('docs').controller('SettingsTheme', function($scope, Restangular) { - // Fetch the current theme configuration - $scope.theme = { - color: $('.navbar').css('background-color') - }; +angular.module('docs').controller('SettingsTheme', function($scope, $rootScope, Restangular) { + // Fetch the current theme configuration + Restangular.one('theme').get().then(function(data) { + $scope.theme = data; + $rootScope.appName = $scope.theme.name; + }); - // Update the main color - $scope.updateColor = function(color) { - Restangular.one('theme').post('color', { - color: color - }).then(function() { - var stylesheet = $('#theme-stylesheet')[0]; - stylesheet.href = stylesheet.href.replace(/\?.*|$/, '?' + new Date().getTime()); - }); - } + // Update the theme + $scope.update = function() { + $scope.theme.name = $scope.theme.name.length == 0 ? 'Sismics Docs' : $scope.theme.name; + Restangular.one('theme').post('', $scope.theme).then(function() { + var stylesheet = $('#theme-stylesheet')[0]; + stylesheet.href = stylesheet.href.replace(/\?.*|$/, '?' + new Date().getTime()); + $rootScope.appName = $scope.theme.name; + }); + } }); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/share/app.js b/docs-web/src/main/webapp/src/app/share/app.js index 9fe7cdfb..e9c69b98 100644 --- a/docs-web/src/main/webapp/src/app/share/app.js +++ b/docs-web/src/main/webapp/src/app/share/app.js @@ -95,7 +95,13 @@ angular.module('share', /** * Application initialization. */ -.run(function($rootScope, $state, $stateParams) { +.run(function($rootScope, $state, $stateParams, Restangular) { $rootScope.$state = $state; $rootScope.$stateParams = $stateParams; + + // Fetch the current theme configuration + $rootScope.appName = ''; + Restangular.one('theme').get().then(function(data) { + $rootScope.appName = data.name; + }); }); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/index.html b/docs-web/src/main/webapp/src/index.html index 5b92a0b1..d4cce917 100644 --- a/docs-web/src/main/webapp/src/index.html +++ b/docs-web/src/main/webapp/src/index.html @@ -1,7 +1,7 @@ - Sismics Docs + Sismics Docs @@ -103,7 +103,7 @@ - Sismics Docs + {{ appName }} - Sismics Docs + {{ appName }} diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java index 61c06cef..79039e9d 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java @@ -31,6 +31,8 @@ public class TestThemeResource extends BaseJerseyTest { JsonObject json = target().path("/theme").request() .get(JsonObject.class); Assert.assertEquals("Sismics Docs", json.getString("name")); + Assert.assertEquals("#263238", json.getString("color")); + Assert.assertEquals("", json.getString("css")); // Update the main color as admin target().path("/theme").request() @@ -50,5 +52,7 @@ public class TestThemeResource extends BaseJerseyTest { json = target().path("/theme").request() .get(JsonObject.class); Assert.assertEquals("My App", json.getString("name")); + Assert.assertEquals("#ff0000", json.getString("color")); + Assert.assertEquals(".body { content: 'Custom CSS'; }", json.getString("css")); } } \ No newline at end of file From 4d79dd707676d80fe85c1672b47930191e08f7e6 Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 8 May 2016 17:25:21 +0200 Subject: [PATCH 40/42] #79: Change background and logo image --- .../docs/core/model/context/AppContext.java | 2 +- .../sismics/docs/core/util/DirectoryUtil.java | 9 +++ .../com/sismics/util/mime/MimeTypeUtil.java | 7 +- .../docs/rest/resource/FileResource.java | 2 +- .../docs/rest/resource/ThemeResource.java | 74 +++++++++++++++++- .../img => resources/image}/background.jpg | Bin .../favicon.png => resources/image/logo.png} | Bin docs-web/src/main/webapp/Gruntfile.js | 2 +- docs-web/src/main/webapp/src/index.html | 4 +- docs-web/src/main/webapp/src/share.html | 2 +- docs-web/src/main/webapp/src/style/main.less | 6 +- .../sismics/docs/rest/TestFileResource.java | 26 +++--- .../sismics/docs/rest/TestThemeResource.java | 51 +++++++++++- 13 files changed, 153 insertions(+), 32 deletions(-) rename docs-web/src/main/{webapp/src/img => resources/image}/background.jpg (100%) rename docs-web/src/main/{webapp/src/favicon.png => resources/image/logo.png} (100%) diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java b/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java index 2d2ab739..526371e7 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java +++ b/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java @@ -72,7 +72,7 @@ public class AppContext { eventBus = new EventBus(); eventBus.register(new DeadEventListener()); - asyncExecutorList = new ArrayList(); + asyncExecutorList = new ArrayList<>(); asyncEventBus = newAsyncEventBus(); asyncEventBus.register(new FileCreatedAsyncListener()); diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/DirectoryUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/DirectoryUtil.java index 7f947157..6f0973ba 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/DirectoryUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/DirectoryUtil.java @@ -86,6 +86,15 @@ public class DirectoryUtil { return getDataSubDirectory("log"); } + /** + * Returns the theme directory. + * + * @return Theme directory. + */ + public static Path getThemeDirectory() { + return getDataSubDirectory("theme"); + } + /** * Returns a subdirectory of the base data directory * diff --git a/docs-core/src/main/java/com/sismics/util/mime/MimeTypeUtil.java b/docs-core/src/main/java/com/sismics/util/mime/MimeTypeUtil.java index fcb3d2ec..2eb5f079 100644 --- a/docs-core/src/main/java/com/sismics/util/mime/MimeTypeUtil.java +++ b/docs-core/src/main/java/com/sismics/util/mime/MimeTypeUtil.java @@ -1,5 +1,6 @@ package com.sismics.util.mime; +import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -21,16 +22,16 @@ public class MimeTypeUtil { * * @param is Stream to inspect * @return MIME type - * @throws Exception + * @throws IOException */ - public static String guessMimeType(InputStream is) throws Exception { + public static String guessMimeType(InputStream is) throws IOException { byte[] headerBytes = new byte[64]; is.mark(headerBytes.length); int readCount = is.read(headerBytes); is.reset(); if (readCount <= 0) { - throw new Exception("Cannot read input file"); + throw new IOException("Cannot read input file"); } return guessMimeType(headerBytes); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java index b692ed4e..35520179 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java @@ -107,7 +107,7 @@ public class FileResource extends BaseResource { String mimeType; try { mimeType = MimeTypeUtil.guessMimeType(fileInputStream); - } catch (Exception e) { + } catch (IOException e) { throw new ServerException("ErrorGuessMime", "Error guessing mime type", e); } if (mimeType == null) { diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java index 6e3f96a1..672b8e0c 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/ThemeResource.java @@ -1,22 +1,31 @@ package com.sismics.docs.rest.resource; -import javax.json.*; -import javax.ws.rs.*; -import javax.ws.rs.core.Response; - import com.google.common.base.Strings; +import com.google.common.io.ByteStreams; import com.sismics.docs.core.constant.ConfigType; import com.sismics.docs.core.dao.jpa.ConfigDao; import com.sismics.docs.core.model.jpa.Config; +import com.sismics.docs.core.util.DirectoryUtil; import com.sismics.docs.rest.constant.BaseFunction; +import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ForbiddenClientException; +import com.sismics.rest.exception.ServerException; import com.sismics.rest.util.JsonUtil; import com.sismics.rest.util.ValidationUtil; import com.sismics.util.css.Selector; import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.glassfish.jersey.media.multipart.FormDataParam; +import javax.json.*; +import javax.ws.rs.*; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.StringReader; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.Map; /** @@ -26,6 +35,10 @@ import java.util.Map; */ @Path("/theme") public class ThemeResource extends BaseResource { + // Filenames for images in theme directory + private static final String LOGO_IMAGE = "logo"; + private static final String BACKGROUND_IMAGE = "background"; + /** * Returns custom CSS stylesheet. * @@ -116,10 +129,63 @@ public class ThemeResource extends BaseResource { throw new ForbiddenClientException(); } checkBaseFunction(BaseFunction.ADMIN); + if (logoBodyPart == null && backgrounBodyPart == null) { + throw new ClientException("NoImageProvided", "logo or background is required"); + } + + // Only a background or a logo is handled + FormDataBodyPart bodyPart = logoBodyPart == null ? backgrounBodyPart : logoBodyPart; + String type = logoBodyPart == null ? BACKGROUND_IMAGE : LOGO_IMAGE; + java.nio.file.Path filePath = DirectoryUtil.getThemeDirectory().resolve(type); + + // Copy the image to the theme directory + try (InputStream inputStream = bodyPart.getValueAs(InputStream.class)) { + Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + throw new ServerException("CopyError", "Error copying the image to the theme directory", e); + } return Response.ok().build(); } + @GET + @Produces("image/*") + @Path("image/{type: logo|background}") + public Response getImage(@PathParam("type") final String type) { + if (!LOGO_IMAGE.equals(type) && !BACKGROUND_IMAGE.equals(type)) { + throw new ClientException("InvalidType", "Type must be logo or background"); + } + + final java.nio.file.Path filePath = DirectoryUtil.getThemeDirectory().resolve(type); + + // Copy the image to the response output + return Response.ok(new StreamingOutput() { + @Override + public void write(OutputStream outputStream) throws IOException, WebApplicationException { + InputStream inputStream = null; + try { + if (Files.exists(filePath)) { + inputStream = Files.newInputStream(filePath); + } else { + inputStream = getClass().getResource("/image/" + (type.equals(LOGO_IMAGE) ? "logo.png" : "background.jpg")).openStream(); + } + ByteStreams.copy(inputStream, outputStream); + } finally { + try { + if (inputStream != null) { + inputStream.close(); + } + outputStream.close(); + } catch (IOException e) { + // Ignore + } + } + } + }) + .header("Content-Type", "image/*") + .build(); + } + /** * Returns the theme configuration object. * diff --git a/docs-web/src/main/webapp/src/img/background.jpg b/docs-web/src/main/resources/image/background.jpg similarity index 100% rename from docs-web/src/main/webapp/src/img/background.jpg rename to docs-web/src/main/resources/image/background.jpg diff --git a/docs-web/src/main/webapp/src/favicon.png b/docs-web/src/main/resources/image/logo.png similarity index 100% rename from docs-web/src/main/webapp/src/favicon.png rename to docs-web/src/main/resources/image/logo.png diff --git a/docs-web/src/main/webapp/Gruntfile.js b/docs-web/src/main/webapp/Gruntfile.js index cb4ad357..5dba6d2d 100644 --- a/docs-web/src/main/webapp/Gruntfile.js +++ b/docs-web/src/main/webapp/Gruntfile.js @@ -93,7 +93,7 @@ module.exports = function(grunt) { }, replace: { dist: { - src: ['dist/docs.min.js', 'dist/share.min.js', 'dist/**/*.html'], + src: ['dist/docs.min.js', 'dist/share.min.js', 'dist/**/*.html', 'dist/style/style.min.css'], overwrite: true, replacements: [{ from: '../api', diff --git a/docs-web/src/main/webapp/src/index.html b/docs-web/src/main/webapp/src/index.html index d4cce917..c7760315 100644 --- a/docs-web/src/main/webapp/src/index.html +++ b/docs-web/src/main/webapp/src/index.html @@ -4,7 +4,7 @@ Sismics Docs - + @@ -100,7 +100,7 @@ {{ appName }} diff --git a/docs-web/src/main/webapp/src/share.html b/docs-web/src/main/webapp/src/share.html index a9a7ddf8..ab67af4a 100644 --- a/docs-web/src/main/webapp/src/share.html +++ b/docs-web/src/main/webapp/src/share.html @@ -46,7 +46,7 @@ + +
+ +
+
+
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+

+ Uploading the image... +

+
+
\ No newline at end of file diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java index 1718e0f5..25e47a14 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestThemeResource.java @@ -72,11 +72,11 @@ public class TestThemeResource extends BaseJerseyTest { // Change the logo try (InputStream is = Resources.getResource("file/PIA00452.jpg").openStream()) { - StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("logo", is, "PIA00452.jpg"); + StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("image", is, "PIA00452.jpg"); try (FormDataMultiPart multiPart = new FormDataMultiPart()) { target() .register(MultiPartFeature.class) - .path("/theme/images").request() + .path("/theme/image/logo").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) .put(Entity.entity(multiPart.bodyPart(streamDataBodyPart), MediaType.MULTIPART_FORM_DATA_TYPE), JsonObject.class); @@ -85,11 +85,11 @@ public class TestThemeResource extends BaseJerseyTest { // Change the background try (InputStream is = Resources.getResource("file/Einstein-Roosevelt-letter.png").openStream()) { - StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("background", is, "Einstein-Roosevelt-letter.png"); + StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("image", is, "Einstein-Roosevelt-letter.png"); try (FormDataMultiPart multiPart = new FormDataMultiPart()) { target() .register(MultiPartFeature.class) - .path("/theme/images").request() + .path("/theme/image/background").request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) .put(Entity.entity(multiPart.bodyPart(streamDataBodyPart), MediaType.MULTIPART_FORM_DATA_TYPE), JsonObject.class); From e234440ce66ea14c521fcf263a0ed01b04dfd57f Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 8 May 2016 23:05:44 +0200 Subject: [PATCH 42/42] Closes #93: Edit tag color and title in #/tag/id --- .../docs/rest/resource/TagResource.java | 8 +++ .../webapp/src/app/docs/controller/tag/Tag.js | 17 +++-- .../src/app/docs/controller/tag/TagEdit.js | 18 +++++- .../src/app/docs/directive/InlineEdit.js | 62 ------------------- docs-web/src/main/webapp/src/index.html | 1 - .../webapp/src/partial/docs/tag.default.html | 2 +- .../webapp/src/partial/docs/tag.edit.html | 58 +++++++++++++++-- .../src/main/webapp/src/partial/docs/tag.html | 30 ++++----- docs-web/src/main/webapp/src/style/main.less | 11 ++++ .../sismics/docs/rest/TestTagResource.java | 1 + 10 files changed, 114 insertions(+), 94 deletions(-) delete mode 100644 docs-web/src/main/webapp/src/app/docs/directive/InlineEdit.js diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java index 9448f8cf..9e8f5871 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java @@ -97,6 +97,14 @@ public class TagResource extends BaseResource { .add("name", tagDto.getName()) .add("color", tagDto.getColor()); + // Add the parent if its visible + if (tagDto.getParentId() != null) { + AclDao aclDao = new AclDao(); + if (aclDao.checkPermission(tagDto.getParentId(), PermType.READ, getTargetIdList(null))) { + tag.add("parent", tagDto.getParentId()); + } + } + // Add ACL AclUtil.addAcls(tag, id, getTargetIdList(null)); diff --git a/docs-web/src/main/webapp/src/app/docs/controller/tag/Tag.js b/docs-web/src/main/webapp/src/app/docs/controller/tag/Tag.js index acf3b5b5..c92f0e1b 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/tag/Tag.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/tag/Tag.js @@ -3,13 +3,20 @@ /** * Tag controller. */ -angular.module('docs').controller('Tag', function($scope, $dialog, Restangular) { +angular.module('docs').controller('Tag', function($scope, $dialog, Restangular, $state) { $scope.tag = { name: '', color: '#3a87ad' }; // Retrieve tags Restangular.one('tag/list').get().then(function(data) { $scope.tags = data.tags; }); + + /** + * Display a tag. + */ + $scope.viewTag = function(id) { + $state.go('tag.edit', { id: id }); + }; /** * Add a tag. @@ -42,12 +49,4 @@ angular.module('docs').controller('Tag', function($scope, $dialog, Restangular) } }); }; - - /** - * Update a tag. - */ - $scope.updateTag = function(tag) { - // Update the server - return Restangular.one('tag', tag.id).post('', tag); - }; }); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/docs/controller/tag/TagEdit.js b/docs-web/src/main/webapp/src/app/docs/controller/tag/TagEdit.js index 1910309d..591de7b6 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/tag/TagEdit.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/tag/TagEdit.js @@ -4,7 +4,23 @@ * Tag edit controller. */ angular.module('docs').controller('TagEdit', function($scope, $stateParams, Restangular) { + // Retrieve the tag Restangular.one('tag', $stateParams.id).get().then(function(data) { $scope.tag = data; - }) + + // Replace the tag from the list with this reference + _.each($scope.tags, function(tag, i) { + if (tag.id == $scope.tag.id) { + $scope.tags[i] = $scope.tag; + } + }); + }); + + /** + * Update a tag. + */ + $scope.edit = function() { + // Update the server + Restangular.one('tag', $scope.tag.id).post('', $scope.tag); + }; }); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/docs/directive/InlineEdit.js b/docs-web/src/main/webapp/src/app/docs/directive/InlineEdit.js deleted file mode 100644 index 02ea72c8..00000000 --- a/docs-web/src/main/webapp/src/app/docs/directive/InlineEdit.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -/** - * Inline edition directive. - * Thanks to http://jsfiddle.net/joshdmiller/NDFHg/ - */ -angular.module('docs').directive('inlineEdit', function() { - return { - restrict: 'E', - scope: { - value: '=', - editCallback: '&onEdit' - }, - template: '', - link: function (scope, element, attrs) { - // Let's get a reference to the input element, as we'll want to reference it. - var inputElement = angular.element(element.children()[1]); - var el = inputElement[0]; - - // This directive should have a set class so we can style it. - element.addClass('inline-edit'); - - // Initially, we're not editing. - scope.editing = false; - - // ng-click handler to activate edit-in-place - scope.edit = function () { - scope.editing = true; - scope.oldValue = el.value; - - // We control display through a class on the directive itself. See the CSS. - element.addClass('active'); - - // And we must focus the element. - // `angular.element()` provides a chainable array, like jQuery so to access a native DOM function, - // we have to reference the first element in the array. - el.focus(); - el.selectionStart = 0; - el.selectionEnd = el.value.length; - }; - - // When we leave the input, we're done editing. - inputElement.on('blur', function() { - scope.editing = false; - element.removeClass('active'); - - // Invoke parent scope callback - if (scope.editCallback && scope.oldValue != el.value) { - scope.$apply(function() { - if (scope.value) { - scope.editCallback().then(null, function() { - scope.value = scope.oldValue; - }); - } else { - scope.value = scope.oldValue; - } - }); - } - }); - } - }; -}); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/index.html b/docs-web/src/main/webapp/src/index.html index c7760315..a5a6d8ce 100644 --- a/docs-web/src/main/webapp/src/index.html +++ b/docs-web/src/main/webapp/src/index.html @@ -81,7 +81,6 @@ - diff --git a/docs-web/src/main/webapp/src/partial/docs/tag.default.html b/docs-web/src/main/webapp/src/partial/docs/tag.default.html index dee44dec..f9af3e0f 100644 --- a/docs-web/src/main/webapp/src/partial/docs/tag.default.html +++ b/docs-web/src/main/webapp/src/partial/docs/tag.default.html @@ -1,6 +1,6 @@

Tags

Tags are labels associated to documents.

A document can be tagged by multiple tags, and a tag can be applied to multiple documents.

-

Using the button, you can edit permissions on a tag.

+

Using the button, you can edit permissions on a tag.

If a tag can be read by another user or group, associated documents can also be read by those people.

For example, tag your company documents with a tag MyCompany and add the permission Read to a group employees

\ No newline at end of file diff --git a/docs-web/src/main/webapp/src/partial/docs/tag.edit.html b/docs-web/src/main/webapp/src/partial/docs/tag.edit.html index 10e48dbe..843a80a1 100644 --- a/docs-web/src/main/webapp/src/partial/docs/tag.edit.html +++ b/docs-web/src/main/webapp/src/partial/docs/tag.edit.html @@ -1,8 +1,54 @@ -

{{ tag.name }}

+

{{ tag.name }} 

-

Permissions on this tag will also be applied to documents tagged {{ tag.name }}

+
+
+
+ - \ No newline at end of file +
+ +
+
+ +
+ + +
+   +
+
+ +
+ + +
+ +
+
+ +
+
+ +
+
+
+
+ +
+

Permissions on this tag will also be applied to documents tagged {{ tag.name }}

+ + +
\ No newline at end of file diff --git a/docs-web/src/main/webapp/src/partial/docs/tag.html b/docs-web/src/main/webapp/src/partial/docs/tag.html index 576a8bfa..280daed3 100644 --- a/docs-web/src/main/webapp/src/partial/docs/tag.html +++ b/docs-web/src/main/webapp/src/partial/docs/tag.html @@ -16,22 +16,24 @@

- +
- - - + + + - - -
- +
+   + {{ tag.name }} + + + + + +  
diff --git a/docs-web/src/main/webapp/src/style/main.less b/docs-web/src/main/webapp/src/style/main.less index a1f3e31f..dbf01f1c 100644 --- a/docs-web/src/main/webapp/src/style/main.less +++ b/docs-web/src/main/webapp/src/style/main.less @@ -21,6 +21,17 @@ } } +// Tags list +.table-tags { + td { + vertical-align: middle !important; + } + + .label { + font-size: 100%; + } +} + // Documents list .table-documents { thead th { diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java index 3724d848..c7d61e80 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java @@ -54,6 +54,7 @@ public class TestTagResource extends BaseJerseyTest { Assert.assertEquals("Tag4", json.getString("name")); Assert.assertEquals("tag1", json.getString("creator")); Assert.assertEquals("#00ff00", json.getString("color")); + Assert.assertEquals(tag3Id, json.getString("parent")); Assert.assertTrue(json.getBoolean("writable")); JsonArray acls = json.getJsonArray("acls"); Assert.assertEquals(2, acls.size());