jupyter / nbgrader

A system for assigning and grading notebooks
https://nbgrader.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
1.28k stars 316 forks source link

course_list server extension does not work on JupyterHub when formgrader service is accessed by non-admin user #1831

Open lahwaacz opened 12 months ago

lahwaacz commented 12 months ago

Operating system

Arch Linux

nbgrader --version

nbgrader version 0.9.0

jupyterhub --version (if used with JupyterHub)

4.0.2

jupyter notebook --version

7.0.3

Expected behavior

The formgrader service should be accessible by admin and non-admin users alike.

Actual behavior

Traceback:

  File "/usr/lib/python3.11/site-packages/nbgrader/server_extensions/course_list/handlers.py", line 173, in get
    jhub_courses = yield self.check_for_jupyterhub_formgraders(config)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/tornado/gen.py", line 769, in run
    value = future.result()
            ^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/tornado/gen.py", line 782, in run
    yielded = self.gen.send(value)
              ^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/nbgrader/server_extensions/course_list/handlers.py", line 159, in check_for_jupyterhub_formgraders
    'url': self.get_base_url() + service['prefix'].rstrip('/') + "/lab",
                                 ~~~~~~~^^^^^^^^^^
KeyError: 'prefix'

service object for a non-admin user:

{'kind': 'service', 'name': 'testcourse', 'admin': False}

service object for an admin user:

{'display': True, 'info': {}, 'pid': 0, 'name': 'testcourse', 'command': [], 'roles': [], 'url': 'http://127.0.0.1:10000', 'kind': 'service', 'admin': False, 'prefix': '/jupyter/services/testcourse/'}

Steps to reproduce the behavior

I have the following jupyterhub config based on the one class, multiple graders use case:

c.JupyterHub.load_groups.update({
    "instructors": {
        "users": [
            "instructor1",
            "instructor2",
        ],
        "properties": {},
    },
    "formgrade-testcourse": {
        "users": [
            "instructor1",
            "instructor2",
            "grader-testcourse",
        ],
        "properties": {},
    },
    "nbgrader-testcourse": {
        "users": [
            "student1",
        ],
        "properties": {},
    },
})

c.JupyterHub.load_roles += [
    {
        'name': 'instructor',
        'groups': ['instructors'],
        'scopes': [
            # these are the scopes required for the admin UI
            'admin:users',
            'admin:servers',
        ],
    },
    # The class_list extension needs permission to access services
    {
        'name': 'server',
        'scopes': [
            'inherit',
        ],
    },
]

c.JupyterHub.load_roles += [
    # access to formgrader for instructors
    {
        "name": "formgrade-testcourse",
        "groups": ["formgrade-testcourse"],
        "scopes": [
            "list:services",
            "access:services!service=testcourse",
        ],
    },
    # access to course materials for students
    {
        "name": "nbgrader-testcourse",
        "groups": ["nbgrader-testcourse"],
        "scopes": [
            # access to the services API to discover the service(s)
            "list:services",
            "read:services!service=testcourse",
        ],
    },
]

# Start the notebook server as a service. The port can be whatever you want
# and the group has to match the name of the group defined above. The name
# of the service MUST match the name of your course.
c.JupyterHub.services.append({
    "name": "testcourse",
    "url": "http://127.0.0.1:10000",
    "api_token": "redacted",
})

The "instructor1" is an admin in JupyterHub, but "instructor2" is not. Formgrader is running as an externally-managed JupyterHub service.

jeflem commented 12 months ago

Hi @lahwaacz,

had the same problem some time ago. I think your formgrade-testcourse role in addition needs the read:services!service=testcourse scope to see all service properties. But not sure. I it does not work, I'll have a look at my setup where this (non-admin instructors) works.

Best regards,

Jens

lahwaacz commented 12 months ago

Hi @jeflem, thanks for the hint! It fixed the problem, but I will leave this issue open for the developers to decide if this is worth adding to the documentation.

brichet commented 11 months ago

Thanks @jeflem for the answer. Is this fix also necessary for the docker example ?

jeflem commented 11 months ago

I would say: yes, fix is necessary in demo_multiple_classes. But did not test this.

The formgrade-course101 role in demo_one_class_multiple_graders has the read:services... scope, but the formgrade-... roles in demo_multiple_classes do not.

lahwaacz commented 11 months ago

@brichet Please also update the documentation on Using nbgrader with JupyterHub – it uses an obsolete format for load_groups and does not mention scopes at all.

szazs89 commented 6 months ago

@jeflem I confirm the need of scope read:services!service=testcourse I had similar problem: non-admin users could not list courses. The log misses scope list:services, however, adding it was not enough.

I have updated the wiki pages on my multi-grader / multiple-class show-case with the new configuration (jupyterhub 4.0.2, nbgrader 0.9.1).