Repository: internetarchive/openlibrary

Issue:
Modifying Librarian Permissions
<!-- What problem are we solving? What does the experience look like today? What are the symptoms? -->

@jimchamp was investigating what librarian privileges should be, I think the following permissions may make sense, for @seabelis to verify, whereas `super-librarians` should likely have many of these permissions.

:x: Viewing /admin pages (super-librarians also shouldn't have this)
:x: Recaptcha exemption when adding books
:question: Deleting a book via the edit form
:x: Modification of OCAIDs
:white_check_mark: Access to work merge UI (MR only)
:question: Revert records to previous versions
:white_check_mark: Bypass spam checks
:confused:  Render "Next" link on (probably :white_check_mark: ?) MARC record pages (what is this?)
:x: Add "Internet Archive" ID when adding a new book
:x: Render "Delete Record" button on book edit pages
:x: Delete OCAIDs
:x: Move editions to another work
:white_check_mark: Access to ILE blue bar
:white_check_mark: Display OCAID and ISBNs in book details omnibar
:x: Render "Revert" button in viewpage.html
:x: Add `link`s to context in viewpage.html
And not on that list...
:white_check_mark: Editing documentation pages & collections

We likely also need to update our docs + invite librarians from open-librarians-g

### Evidence / Screenshot (if possible)

### Relevant url?
<!-- `https://openlibrary.org/...` -->

### Steps to Reproduce
<!-- What steps caused you to find the bug? -->
1. Go to ...
2. Do ...

<!-- What actually happened after these steps? What did you expect to happen? -->
* Actual:
* Expected:

### Details

- **Logged in (Y/N)?**
- **Browser type/version?**
- **Operating system?**
- **Environment (prod/dev/local)?** prod
<!-- If not sure, put prod -->

### Proposal & Constraints
<!-- What is the proposed solution / implementation? Is there a precedent of this approach succeeding elsewhere? -->

### Related files
<!-- Files related to this issue; this is super useful for new contributors who might want to help! If you're not sure, leave this blank; a maintainer will add them. -->

### Stakeholders
<!-- @ tag stakeholders of this bug -->



Discussion:

Resolving patch:
diff --git a/openlibrary/plugins/admin/code.py b/openlibrary/plugins/admin/code.py
--- a/openlibrary/plugins/admin/code.py
+++ b/openlibrary/plugins/admin/code.py
@@ -83,7 +83,9 @@ def handle(self, cls, args=(), librarians=False):
             raise web.nomethod(cls=cls)
         else:
             if self.is_admin() or (
-                librarians and context.user and context.user.is_librarian()
+                librarians
+                and context.user
+                and context.user.is_usergroup_member('/usergroup/super-librarians')
             ):
                 return m(*args)
             else:
diff --git a/openlibrary/plugins/upstream/addbook.py b/openlibrary/plugins/upstream/addbook.py
--- a/openlibrary/plugins/upstream/addbook.py
+++ b/openlibrary/plugins/upstream/addbook.py
@@ -536,7 +536,10 @@ def save(self, formdata):
         user = accounts.get_current_user()
         delete = (
             user
-            and (user.is_admin() or user.is_librarian())
+            and (
+                user.is_admin()
+                or user.is_usergroup_member('/usergroup/super-librarians')
+            )
             and formdata.pop('_delete', '')
         )
 
@@ -756,7 +759,9 @@ def read_subject(subjects):
     def _prevent_ocaid_deletion(self, edition):
         # Allow admins to modify ocaid
         user = accounts.get_current_user()
-        if user and (user.is_admin() or user.is_librarian()):
+        if user and (
+            user.is_admin() or user.is_usergroup_member('/usergroup/super-librarians')
+        ):
             return
 
         # read ocaid from form data
diff --git a/openlibrary/plugins/upstream/code.py b/openlibrary/plugins/upstream/code.py
--- a/openlibrary/plugins/upstream/code.py
+++ b/openlibrary/plugins/upstream/code.py
@@ -256,9 +256,11 @@ def setup_jquery_urls():
     web.template.Template.globals['use_google_cdn'] = config.get('use_google_cdn', True)
 
 
-def user_is_admin_or_librarian():
+def user_can_revert_records():
     user = web.ctx.site.get_user()
-    return user and (user.is_admin() or user.is_librarian())
+    return user and (
+        user.is_admin() or user.is_usergroup_member('/usergroup/super-librarians')
+    )
 
 
 @public
@@ -286,7 +288,7 @@ def POST(self, key):
         if v is None:
             raise web.seeother(web.changequery({}))
 
-        if not web.ctx.site.can_write(key) or not user_is_admin_or_librarian():
+        if not web.ctx.site.can_write(key) or not user_can_revert_records():
             return render.permission_denied(
                 web.ctx.fullpath, "Permission denied to edit " + key + "."
             )


Test patch:
diff --git a/openlibrary/plugins/upstream/tests/test_addbook.py b/openlibrary/plugins/upstream/tests/test_addbook.py
--- a/openlibrary/plugins/upstream/tests/test_addbook.py
+++ b/openlibrary/plugins/upstream/tests/test_addbook.py
@@ -16,6 +16,7 @@ def mock_user():
         {
             'is_admin': lambda slf: False,
             'is_librarian': lambda slf: False,
+            'is_usergroup_member': lambda slf, grp: False,
         },
     )()