From a55c55bbdb700d9ea6df046a5792c79b733a19da Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 8 May 2016 00:46:32 +0200 Subject: [PATCH] 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");