From bd9cdff478d43c9e659d28ea3adbcbc38b65ce67 Mon Sep 17 00:00:00 2001 From: eagw <emi.gradywillis@kitware.com> Date: Tue, 16 Aug 2022 10:08:13 -0400 Subject: [PATCH 1/7] Create annotation creation access flag --- girder_annotation/girder_large_image_annotation/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/girder_annotation/girder_large_image_annotation/__init__.py b/girder_annotation/girder_large_image_annotation/__init__.py index 5d9f5eeb2..3b417c34b 100644 --- a/girder_annotation/girder_large_image_annotation/__init__.py +++ b/girder_annotation/girder_large_image_annotation/__init__.py @@ -15,6 +15,7 @@ ############################################################################# from girder import events +from girder.constants import registerAccessFlag from girder.exceptions import ValidationException from girder.plugin import GirderPlugin, getPlugin from girder.settings import SettingDefault @@ -58,6 +59,10 @@ def validateBoolean(doc): constants.PluginSettings.LARGE_IMAGE_ANNOTATION_HISTORY: True, }) +# Access flags + +registerAccessFlag('createAnnots', 'Create annotations', 'Allow user to create annotations') + class LargeImageAnnotationPlugin(GirderPlugin): DISPLAY_NAME = 'Large Image Annotation' From 6008d4d1de746358f40f0427edbbc97ccfcac945 Mon Sep 17 00:00:00 2001 From: eagw <emi.gradywillis@kitware.com> Date: Tue, 16 Aug 2022 11:46:16 -0400 Subject: [PATCH 2/7] Add new endpoint to check for creation access --- .../rest/annotation.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/girder_annotation/girder_large_image_annotation/rest/annotation.py b/girder_annotation/girder_large_image_annotation/rest/annotation.py index 1e61eeaa4..ab3e1c5dc 100644 --- a/girder_annotation/girder_large_image_annotation/rest/annotation.py +++ b/girder_annotation/girder_large_image_annotation/rest/annotation.py @@ -28,6 +28,7 @@ from girder.api.rest import Resource, filtermodel, loadmodel, setResponseHeader from girder.constants import AccessType, SortDir, TokenScope from girder.exceptions import AccessException, RestException, ValidationException +from girder.models.folder import Folder from girder.models.item import Item from girder.models.user import User from girder.utility import JsonEncoder @@ -61,6 +62,7 @@ def __init__(self): self.route('DELETE', ('item', ':id'), self.deleteItemAnnotations) self.route('GET', ('folder', ':id'), self.returnFolderAnnotations) self.route('GET', ('folder', ':id', 'present'), self.existFolderAnnotations) + self.route('GET', ('folder', ':id', 'create'), self.canCreateFolderAnnotations) self.route('PUT', ('folder', ':id', 'access'), self.setFolderAnnotationAccess) self.route('GET', ('old',), self.getOldAnnotations) self.route('DELETE', ('old',), self.deleteOldAnnotations) @@ -671,6 +673,18 @@ def count(): annotations.count = count return annotations + @autoDescribeRoute( + Description('Check if the user can create annotations in a folder') + .param('id', 'The ID of the folder', required=True, paramType='path') + .errorResponse('ID was invalid.') + ) + @access.user + @loadmodel(model='folder', level=AccessType.READ) + def canCreateFolderAnnotations(self, folder): + user = self.getCurrentUser() + return Folder().hasAccess(folder, user, AccessType.WRITE) or Folder().hasAccessFlags( + folder, user, 'createAnnots') + @autoDescribeRoute( Description('Set the access for all the user-owned annotations from the items in a folder') .param('id', 'The ID of the folder', required=True, paramType='path') From f065e6a2fe193e48a9f76d72685f30891102a81f Mon Sep 17 00:00:00 2001 From: eagw <emi.gradywillis@kitware.com> Date: Tue, 16 Aug 2022 12:37:12 -0400 Subject: [PATCH 3/7] Update annot creation endpoint with access flag --- .../rest/annotation.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/girder_annotation/girder_large_image_annotation/rest/annotation.py b/girder_annotation/girder_large_image_annotation/rest/annotation.py index ab3e1c5dc..c7d8c84a3 100644 --- a/girder_annotation/girder_large_image_annotation/rest/annotation.py +++ b/girder_annotation/girder_large_image_annotation/rest/annotation.py @@ -258,22 +258,29 @@ def generateResult(): .param('body', 'A JSON object containing the annotation.', paramType='body') .errorResponse('ID was invalid.') - .errorResponse('Write access was denied for the item.', 403) + .errorResponse('Read access was denied for the item.', 403) .errorResponse('Invalid JSON passed in request body.') .errorResponse("Validation Error: JSON doesn't follow schema.") ) @access.user - @loadmodel(map={'itemId': 'item'}, model='item', level=AccessType.WRITE) + @loadmodel(map={'itemId': 'item'}, model='item', level=AccessType.READ) @filtermodel(model='annotation', plugin='large_image') def createAnnotation(self, item, params): - try: - return Annotation().createAnnotation( - item, self.getCurrentUser(), self.getBodyJson()) - except ValidationException as exc: - logger.exception('Failed to validate annotation') - raise RestException( - "Validation Error: JSON doesn't follow schema (%r)." % ( - exc.args, )) + user = self.getCurrentUser() + folder = Folder().load(id=item['folderId'], user=user, level=AccessType.READ) + if Folder().hasAccess(folder, user, AccessType.WRITE) or Folder( + ).hasAccessFlags(folder, user, 'createAnnots'): + try: + return Annotation().createAnnotation( + item, self.getCurrentUser(), self.getBodyJson()) + except ValidationException as exc: + logger.exception('Failed to validate annotation') + raise RestException( + "Validation Error: JSON doesn't follow schema (%r)." % ( + exc.args, )) + else: + raise RestException('Write access and annotation creation access ' + 'were denied for the item.', code=403) @describeRoute( Description('Copy an annotation from one item to an other.') From 8daa83e13e37115905a982728499e409676b160a Mon Sep 17 00:00:00 2001 From: eagw <emi.gradywillis@kitware.com> Date: Tue, 16 Aug 2022 13:08:50 -0400 Subject: [PATCH 4/7] Show annotation upload button w/access flag --- .../templates/annotationListWidget.pug | 2 +- .../web_client/views/annotationListWidget.js | 29 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/girder_annotation/girder_large_image_annotation/web_client/templates/annotationListWidget.pug b/girder_annotation/girder_large_image_annotation/web_client/templates/annotationListWidget.pug index 2abc55364..9d23bef99 100644 --- a/girder_annotation/girder_large_image_annotation/web_client/templates/annotationListWidget.pug +++ b/girder_annotation/girder_large_image_annotation/web_client/templates/annotationListWidget.pug @@ -5,7 +5,7 @@ if annotations.length a.g-annotation-download(href=`${apiRoot}/annotation/item/${item.id}`, title='Download annotations', download=`${item.get('name')}_annotations.json`) i.icon-download - if accessLevel >= AccessType.WRITE + if creationAccess a.g-annotation-upload(title='Upload annotation') i.icon-upload if accessLevel >= AccessType.ADMIN && annotations.length diff --git a/girder_annotation/girder_large_image_annotation/web_client/views/annotationListWidget.js b/girder_annotation/girder_large_image_annotation/web_client/views/annotationListWidget.js index 4fc31bac8..81f3797e7 100644 --- a/girder_annotation/girder_large_image_annotation/web_client/views/annotationListWidget.js +++ b/girder_annotation/girder_large_image_annotation/web_client/views/annotationListWidget.js @@ -59,16 +59,22 @@ const AnnotationListWidget = View.extend({ }, render() { - this.$el.html(annotationList({ - item: this.model, - accessLevel: this.model.getAccessLevel(), - annotations: this.collection, - users: this.users, - canDraw: this._viewer && this._viewer.annotationAPI(), - drawn: this._drawn, - apiRoot: getApiRoot(), - AccessType - })); + restRequest({ + type: 'GET', + url: 'annotation/folder/' + this.model.get('folderId') + '/create' + }).done((createResp) => { + this.$el.html(annotationList({ + item: this.model, + accessLevel: this.model.getAccessLevel(), + creationAccess: createResp, + annotations: this.collection, + users: this.users, + canDraw: this._viewer && this._viewer.annotationAPI(), + drawn: this._drawn, + apiRoot: getApiRoot(), + AccessType + })); + }) return this; }, @@ -185,7 +191,8 @@ const AnnotationListWidget = View.extend({ type: 'annotation', hideRecurseOption: true, parentView: this, - model + model, + noAccessFlag: true }).on('g:accessListSaved', () => { this.collection.fetch(null, true); }); From 3f2fed4c929a1dfe65347c6427b3df9f34c7c9b2 Mon Sep 17 00:00:00 2001 From: eagw <emi.gradywillis@kitware.com> Date: Tue, 16 Aug 2022 13:10:09 -0400 Subject: [PATCH 5/7] Hide access flag in annotation access control --- .../web_client/views/hierarchyWidget.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/girder_annotation/girder_large_image_annotation/web_client/views/hierarchyWidget.js b/girder_annotation/girder_large_image_annotation/web_client/views/hierarchyWidget.js index e28f8cb56..22f7906f5 100644 --- a/girder_annotation/girder_large_image_annotation/web_client/views/hierarchyWidget.js +++ b/girder_annotation/girder_large_image_annotation/web_client/views/hierarchyWidget.js @@ -87,7 +87,8 @@ function editAnnotAccess() { modelType: 'annotation', model, hideRecurseOption: false, - parentView: this + parentView: this, + noAccessFlag: true }); }); }); From a2bbb8d6b8c963aa4e42038af07050139a0899a3 Mon Sep 17 00:00:00 2001 From: eagw <emi.gradywillis@kitware.com> Date: Tue, 16 Aug 2022 13:31:08 -0400 Subject: [PATCH 6/7] Fix lint error --- .../web_client/views/annotationListWidget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/girder_annotation/girder_large_image_annotation/web_client/views/annotationListWidget.js b/girder_annotation/girder_large_image_annotation/web_client/views/annotationListWidget.js index 81f3797e7..6ba5317f7 100644 --- a/girder_annotation/girder_large_image_annotation/web_client/views/annotationListWidget.js +++ b/girder_annotation/girder_large_image_annotation/web_client/views/annotationListWidget.js @@ -74,7 +74,7 @@ const AnnotationListWidget = View.extend({ apiRoot: getApiRoot(), AccessType })); - }) + }); return this; }, From 99ada00d8298187d7975d9eb7a2d33be1ba697b0 Mon Sep 17 00:00:00 2001 From: eagw <emi.gradywillis@kitware.com> Date: Wed, 17 Aug 2022 18:19:15 -0400 Subject: [PATCH 7/7] Replace flag name string with constant --- girder_annotation/girder_large_image_annotation/__init__.py | 3 ++- girder_annotation/girder_large_image_annotation/constants.py | 2 ++ .../girder_large_image_annotation/rest/annotation.py | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/girder_annotation/girder_large_image_annotation/__init__.py b/girder_annotation/girder_large_image_annotation/__init__.py index 3b417c34b..cbbe39398 100644 --- a/girder_annotation/girder_large_image_annotation/__init__.py +++ b/girder_annotation/girder_large_image_annotation/__init__.py @@ -61,7 +61,8 @@ def validateBoolean(doc): # Access flags -registerAccessFlag('createAnnots', 'Create annotations', 'Allow user to create annotations') +registerAccessFlag(constants.ANNOTATION_ACCESS_FLAG, 'Create annotations', + 'Allow user to create annotations') class LargeImageAnnotationPlugin(GirderPlugin): diff --git a/girder_annotation/girder_large_image_annotation/constants.py b/girder_annotation/girder_large_image_annotation/constants.py index 933db8274..a45879550 100644 --- a/girder_annotation/girder_large_image_annotation/constants.py +++ b/girder_annotation/girder_large_image_annotation/constants.py @@ -18,3 +18,5 @@ from girder_large_image.constants import PluginSettings PluginSettings.LARGE_IMAGE_ANNOTATION_HISTORY = 'large_image.annotation_history' + +ANNOTATION_ACCESS_FLAG = 'large_image_create_annotations' diff --git a/girder_annotation/girder_large_image_annotation/rest/annotation.py b/girder_annotation/girder_large_image_annotation/rest/annotation.py index c7d8c84a3..4ef86edee 100644 --- a/girder_annotation/girder_large_image_annotation/rest/annotation.py +++ b/girder_annotation/girder_large_image_annotation/rest/annotation.py @@ -34,6 +34,7 @@ from girder.utility import JsonEncoder from girder.utility.progress import setResponseTimeLimit +from .. import constants from ..models.annotation import Annotation, AnnotationSchema from ..models.annotationelement import Annotationelement @@ -269,7 +270,7 @@ def createAnnotation(self, item, params): user = self.getCurrentUser() folder = Folder().load(id=item['folderId'], user=user, level=AccessType.READ) if Folder().hasAccess(folder, user, AccessType.WRITE) or Folder( - ).hasAccessFlags(folder, user, 'createAnnots'): + ).hasAccessFlags(folder, user, constants.ANNOTATION_ACCESS_FLAG): try: return Annotation().createAnnotation( item, self.getCurrentUser(), self.getBodyJson()) @@ -690,7 +691,7 @@ def count(): def canCreateFolderAnnotations(self, folder): user = self.getCurrentUser() return Folder().hasAccess(folder, user, AccessType.WRITE) or Folder().hasAccessFlags( - folder, user, 'createAnnots') + folder, user, constants.ANNOTATION_ACCESS_FLAG) @autoDescribeRoute( Description('Set the access for all the user-owned annotations from the items in a folder')