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')