From 1f092f7d93a25ee52b978d634bdbeb8795105cc3 Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 2 May 2015 19:37:56 +0200 Subject: [PATCH 1/7] Android: signing --- docs-android/app/app.iml | 6 +++--- docs-android/app/build.gradle | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docs-android/app/app.iml b/docs-android/app/app.iml index 999cb6ee..57f3cfd8 100644 --- a/docs-android/app/app.iml +++ b/docs-android/app/app.iml @@ -100,16 +100,16 @@ + - - + + - \ No newline at end of file diff --git a/docs-android/app/build.gradle b/docs-android/app/build.gradle index e1f591d4..108bd8e8 100644 --- a/docs-android/app/build.gradle +++ b/docs-android/app/build.gradle @@ -28,6 +28,21 @@ android { targetCompatibility JavaVersion.VERSION_1_7 } + signingConfigs { + release { + storeFile file(System.getenv("TRACKINO_STORE_PATH")) + storePassword System.getenv("TRACKINO_STORE_PASS") + keyAlias System.getenv("TRACKINO_STORE_ALIAS") + keyPassword System.getenv("TRACKINO_STORE_KEYPASS") + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + } + } + lintOptions { abortOnError false } @@ -35,7 +50,7 @@ android { dependencies { compile fileTree(dir: 'libs', include: '*.jar') - compile 'com.android.support:appcompat-v7:22.1.0' + compile 'com.android.support:appcompat-v7:22.1.1' compile 'com.android.support:recyclerview-v7:22.0.0' compile 'com.loopj.android:android-async-http:1.4.6' compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5' From f1eb3795d934148c62b196abb62fb2c5c26d61e4 Mon Sep 17 00:00:00 2001 From: jendib Date: Mon, 4 May 2015 20:51:32 +0200 Subject: [PATCH 2/7] Android: login activity design --- docs-android/app/app.iml | 6 + docs-android/app/build.gradle | 2 +- docs-android/app/src/main/AndroidManifest.xml | 3 +- .../src/main/res/layout/login_activity.xml | 148 +++++++----------- .../app/src/main/res/values/colors.xml | 6 + .../app/src/main/res/values/styles.xml | 13 +- 6 files changed, 83 insertions(+), 95 deletions(-) create mode 100644 docs-android/app/src/main/res/values/colors.xml diff --git a/docs-android/app/app.iml b/docs-android/app/app.iml index 57f3cfd8..d5ce36f5 100644 --- a/docs-android/app/app.iml +++ b/docs-android/app/app.iml @@ -75,6 +75,12 @@ + + + + + + diff --git a/docs-android/app/build.gradle b/docs-android/app/build.gradle index 108bd8e8..8fc77c04 100644 --- a/docs-android/app/build.gradle +++ b/docs-android/app/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:1.1.0' + classpath 'com.android.tools.build:gradle:1.2.2' } } apply plugin: 'com.android.application' diff --git a/docs-android/app/src/main/AndroidManifest.xml b/docs-android/app/src/main/AndroidManifest.xml index b38e3f46..0f669078 100644 --- a/docs-android/app/src/main/AndroidManifest.xml +++ b/docs-android/app/src/main/AndroidManifest.xml @@ -16,7 +16,8 @@ android:theme="@style/AppTheme" > + android:label="@string/app_name" + android:theme="@style/AppThemeDark"> diff --git a/docs-android/app/src/main/res/layout/login_activity.xml b/docs-android/app/src/main/res/layout/login_activity.xml index 6c04d955..eae776b3 100644 --- a/docs-android/app/src/main/res/layout/login_activity.xml +++ b/docs-android/app/src/main/res/layout/login_activity.xml @@ -2,110 +2,80 @@ + android:orientation="vertical" + android:background="@color/colorPrimaryDark"> - + android:visibility="visible" + android:padding="40dp" + android:orientation="vertical"> - + + + + + + + + + + + + + + + Edit @@ -16,11 +16,11 @@ -

+

-

@@ -30,44 +30,112 @@ - -

-
-
-
-
- - - -
-
-
+ + + + Content + + +

+ +
+
+
+
+ + + +
+
+
+
+
+ +
+
+
-
- + +
+

+ {{ file.status }} +

+
+ +
-
-
-
-

- {{ file.status }} +

+ + Drag & drop files here to upload

-
- -
+ + + + + Permissions + + + + + + + + + + + + +
ForPermission
{{ acl[0].type == 'SHARE' ? 'Shared' : 'User' }} {{ acl[0].name }} + + {{ a.perm }} + + +
+ +
+

Add a permission

+ +
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+
+
+ -

- - Drag & drop files here to upload -

-
-
-
\ No newline at end of file diff --git a/docs-web/src/main/webapp/src/style/main.less b/docs-web/src/main/webapp/src/style/main.less index 39551d4f..1ba0630a 100644 --- a/docs-web/src/main/webapp/src/style/main.less +++ b/docs-web/src/main/webapp/src/style/main.less @@ -179,4 +179,8 @@ input[readonly].share-link { .pointer { cursor: pointer; +} + +.tab-pane { + margin-top: 20px; } \ No newline at end of file diff --git a/docs-web/src/prod/resources/config.properties b/docs-web/src/prod/resources/config.properties index 1bfb8fb0..44ddb414 100644 --- a/docs-web/src/prod/resources/config.properties +++ b/docs-web/src/prod/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=7 \ No newline at end of file +db.version=8 \ 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 new file mode 100644 index 00000000..38618790 --- /dev/null +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java @@ -0,0 +1,177 @@ +package com.sismics.docs.rest; + +import java.util.Date; + +import junit.framework.Assert; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.junit.Test; + +import com.sismics.docs.rest.filter.CookieAuthenticationFilter; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.ClientResponse.Status; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.core.util.MultivaluedMapImpl; + +/** + * Test the ACL resource. + * + * @author bgamard + */ +public class TestAclResource extends BaseJerseyTest { + /** + * Test the ACL resource. + * + * @throws JSONException + */ + @Test + public void testAclResource() throws JSONException { + // Login acl1 + clientUtil.createUser("acl1"); + String acl1Token = clientUtil.login("acl1"); + + // Login acl2 + clientUtil.createUser("acl2"); + String acl2Token = clientUtil.login("acl2"); + + // Create a document + WebResource documentResource = resource().path("/document"); + documentResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + MultivaluedMapImpl postParams = new MultivaluedMapImpl(); + postParams.add("title", "My super title document 1"); + postParams.add("language", "eng"); + postParams.add("create_date", new Date().getTime()); + ClientResponse response = documentResource.put(ClientResponse.class, postParams); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + JSONObject json = response.getEntity(JSONObject.class); + String document1Id = json.optString("id"); + + // Get the document as acl1 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + Assert.assertEquals(document1Id, json.getString("id")); + JSONArray acls = json.getJSONArray("acls"); + Assert.assertEquals(2, acls.length()); + + // Get the document as acl2 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl2Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus())); + + // Add an ACL READ for acl2 with acl1 + WebResource aclResource = resource().path("/acl"); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + postParams = new MultivaluedMapImpl(); + postParams.add("source", document1Id); + postParams.add("perm", "READ"); + postParams.add("username", "acl2"); + response = aclResource.put(ClientResponse.class, postParams); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + json = response.getEntity(JSONObject.class); + String acl2Id = json.getString("id"); + + // Add an ACL WRITE for acl2 with acl1 + aclResource = resource().path("/acl"); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + postParams = new MultivaluedMapImpl(); + postParams.add("source", document1Id); + postParams.add("perm", "WRITE"); + postParams.add("username", "acl2"); + response = aclResource.put(ClientResponse.class, postParams); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + + // Add an ACL WRITE for acl2 with acl1 (again) + aclResource = resource().path("/acl"); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + postParams = new MultivaluedMapImpl(); + postParams.add("source", document1Id); + postParams.add("perm", "WRITE"); + postParams.add("username", "acl2"); + response = aclResource.put(ClientResponse.class, postParams); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + + // Get the document as acl1 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + Assert.assertEquals(document1Id, json.getString("id")); + acls = json.getJSONArray("acls"); + Assert.assertEquals(4, acls.length()); + + // Get the document as acl2 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl2Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + Assert.assertEquals(document1Id, json.getString("id")); + acls = json.getJSONArray("acls"); + Assert.assertEquals(4, acls.length()); + + // Delete the ACL WRITE for acl2 with acl2 + aclResource = resource().path("/acl/" + document1Id + "/WRITE/" + acl2Id); + aclResource.addFilter(new CookieAuthenticationFilter(acl2Token)); + response = aclResource.delete(ClientResponse.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + + // Delete the ACL READ for acl2 with acl2 + aclResource = resource().path("/acl/" + document1Id + "/READ/" + acl2Id); + aclResource.addFilter(new CookieAuthenticationFilter(acl2Token)); + response = aclResource.delete(ClientResponse.class); + Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus())); + + // Delete the ACL READ for acl2 with acl1 + aclResource = resource().path("/acl/" + document1Id + "/READ/" + acl2Id); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = aclResource.delete(ClientResponse.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + + // Get the document as acl1 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + Assert.assertEquals(document1Id, json.getString("id")); + acls = json.getJSONArray("acls"); + Assert.assertEquals(2, acls.length()); + String acl1Id = acls.getJSONObject(0).getString("id"); + + // Get the document as acl2 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl2Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus())); + + // Delete the ACL READ for acl1 with acl1 + aclResource = resource().path("/acl/" + document1Id + "/READ/" + acl1Id); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = aclResource.delete(ClientResponse.class); + Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus())); + + // Delete the ACL WRITE for acl1 with acl1 + aclResource = resource().path("/acl/" + document1Id + "/WRITE/" + acl1Id); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = aclResource.delete(ClientResponse.class); + Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus())); + + // Search target list + aclResource = resource().path("/acl/target/search"); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = aclResource.queryParam("search", "acl").get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + JSONArray users = json.getJSONArray("users"); + Assert.assertEquals(2, users.length()); + } +} \ No newline at end of file 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 78ba8535..24168ec9 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 @@ -43,6 +43,10 @@ public class TestDocumentResource extends BaseJerseyTest { clientUtil.createUser("document1"); String document1Token = clientUtil.login("document1"); + // Login document3 + clientUtil.createUser("document3"); + String document3Token = clientUtil.login("document3"); + // Create a tag WebResource tagResource = resource().path("/tag"); tagResource.addFilter(new CookieAuthenticationFilter(document1Token)); @@ -115,6 +119,61 @@ public class TestDocumentResource extends BaseJerseyTest { Assert.assertEquals("SuperTag", tags.getJSONObject(0).getString("name")); Assert.assertEquals("#ffff00", tags.getJSONObject(0).getString("color")); + // List all documents from document3 + documentResource = resource().path("/document/list"); + documentResource.addFilter(new CookieAuthenticationFilter(document3Token)); + getParams = new MultivaluedMapImpl(); + getParams.putSingle("sort_column", 3); + getParams.putSingle("asc", false); + response = documentResource.queryParams(getParams).get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + documents = json.getJSONArray("documents"); + Assert.assertTrue(documents.length() == 0); + + // Create a document with document3 + documentResource = resource().path("/document"); + documentResource.addFilter(new CookieAuthenticationFilter(document3Token)); + postParams = new MultivaluedMapImpl(); + postParams.add("title", "My super title document 1"); + postParams.add("description", "My super description for document 1"); + postParams.add("language", "eng"); + create1Date = new Date().getTime(); + postParams.add("create_date", create1Date); + response = documentResource.put(ClientResponse.class, postParams); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + json = response.getEntity(JSONObject.class); + String document3Id = json.optString("id"); + Assert.assertNotNull(document3Id); + + // Add a file + fileResource = resource().path("/file"); + fileResource.addFilter(new CookieAuthenticationFilter(document1Token)); + form = new FormDataMultiPart(); + file = this.getClass().getResourceAsStream("/file/Einstein-Roosevelt-letter.png"); + fdp = new FormDataBodyPart("file", + new BufferedInputStream(file), + MediaType.APPLICATION_OCTET_STREAM_TYPE); + form.bodyPart(fdp); + form.field("id", document1Id); + response = fileResource.type(MediaType.MULTIPART_FORM_DATA).put(ClientResponse.class, form); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + json = response.getEntity(JSONObject.class); + String file3Id = json.getString("id"); + Assert.assertNotNull(file3Id); + + // List all documents from document3 + documentResource = resource().path("/document/list"); + documentResource.addFilter(new CookieAuthenticationFilter(document3Token)); + getParams = new MultivaluedMapImpl(); + getParams.putSingle("sort_column", 3); + getParams.putSingle("asc", false); + response = documentResource.queryParams(getParams).get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + documents = json.getJSONArray("documents"); + Assert.assertTrue(documents.length() == 1); + // Search documents Assert.assertEquals(1, searchDocuments("full:uranium full:einstein", document1Token)); Assert.assertEquals(1, searchDocuments("full:title", document1Token)); diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestLocaleResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestLocaleResource.java index 6b2923d9..cbc24a5b 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestLocaleResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestLocaleResource.java @@ -24,7 +24,6 @@ public class TestLocaleResource extends BaseJerseyTest { public void testLocaleResource() throws JSONException { WebResource localeResource = resource().path("/locale"); ClientResponse response = localeResource.get(ClientResponse.class); - response = localeResource.get(ClientResponse.class); Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); JSONObject json = response.getEntity(JSONObject.class); JSONArray locale = json.getJSONArray("locales"); diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestShareResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestShareResource.java index e69ca0f6..2333d4e1 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestShareResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestShareResource.java @@ -83,9 +83,7 @@ public class TestShareResource extends BaseJerseyTest { json = response.getEntity(JSONObject.class); Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); Assert.assertEquals(document1Id, json.getString("id")); - Assert.assertEquals(1, json.getJSONArray("shares").length()); - Assert.assertEquals(share1Id, json.getJSONArray("shares").getJSONObject(0).getString("id")); - Assert.assertEquals("4 All", json.getJSONArray("shares").getJSONObject(0).getString("name")); + Assert.assertEquals(3, json.getJSONArray("acls").length()); // 2 for the creator, 1 for the share // Get all files from this document anonymously fileResource = resource().path("/file/list"); From 82ba0b57615e6287e6f9990e0ec7336c94d91f0d Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 9 May 2015 16:21:59 +0200 Subject: [PATCH 7/7] #13: Display the document's creator --- README.md | 5 +++-- .../com/sismics/docs/rest/resource/DocumentResource.java | 4 +++- .../src/main/webapp/src/app/docs/controller/DocumentView.js | 1 + docs-web/src/main/webapp/src/app/docs/controller/Login.js | 4 +++- docs-web/src/main/webapp/src/partial/docs/document.view.html | 4 ++-- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ff520de6..c9970c9b 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,10 @@ Features - Full text search in image and PDF - SHA-256 encryption - Tag system -- Multi-users -- Document sharing +- Multi-users ACL system +- Document sharing by URL - RESTful Web API +- Modern Android client Download -------- 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 57cbc72d..61c3cc06 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 @@ -38,6 +38,7 @@ import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.DocumentDao; import com.sismics.docs.core.dao.jpa.FileDao; import com.sismics.docs.core.dao.jpa.TagDao; +import com.sismics.docs.core.dao.jpa.UserDao; 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.DocumentDto; @@ -83,6 +84,7 @@ public class DocumentResource extends BaseResource { DocumentDao documentDao = new DocumentDao(); AclDao aclDao = new AclDao(); + UserDao userDao = new UserDao(); Document documentDb; try { documentDb = documentDao.getDocument(documentId); @@ -101,7 +103,7 @@ public class DocumentResource extends BaseResource { document.put("description", documentDb.getDescription()); document.put("create_date", documentDb.getCreateDate().getTime()); document.put("language", documentDb.getLanguage()); - document.put("creator", documentDb.getUserId()); + document.put("creator", userDao.getById(documentDb.getUserId()).getUsername()); // Add tags TagDao tagDao = new TagDao(); diff --git a/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js b/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js index 4e217334..18d50e78 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js @@ -117,6 +117,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta // Display the new share ACL and add it to the local ACLs $scope.showShare(acl); $scope.document.acls.push(acl); + $scope.document.acls = angular.copy($scope.document.acls); }) }); }; diff --git a/docs-web/src/main/webapp/src/app/docs/controller/Login.js b/docs-web/src/main/webapp/src/app/docs/controller/Login.js index c6d7a947..14340858 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/Login.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/Login.js @@ -6,7 +6,9 @@ angular.module('docs').controller('Login', function($scope, $rootScope, $state, $dialog, User) { $scope.login = function() { User.login($scope.user).then(function() { - $rootScope.userInfo = User.userInfo(true); + User.userInfo(true).then(function(data) { + $rootScope.userInfo = data; + }); $state.transitionTo('document.default'); }, function() { var title = 'Login failed'; diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.html b/docs-web/src/main/webapp/src/partial/docs/document.view.html index 3ff3d681..691e909a 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.view.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.view.html @@ -10,7 +10,7 @@