Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Course roles service #33734

Merged
merged 74 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
8f7f612
docs: course_roles readme file
julianpalmerio Sep 12, 2023
a793577
feat: course_roles Model Setup (#33229)
julianpalmerio Sep 13, 2023
3883210
docs: adr for data storage in course_roles djangoapp
hsinkoff Sep 13, 2023
7752009
docs: adr for course_level roles
lucascalvino Sep 13, 2023
54cec18
chore: update new table diagram
lucascalvino Sep 14, 2023
9c180e0
feat: course_roles permission check helper function (#33201)
julianpalmerio Sep 15, 2023
d8ffa7b
feat: course_roles Create permissions in db table (#33394)
julianpalmerio Oct 4, 2023
668510e
feat: course_roles mfe-course authoring helper function (#33599)
julianpalmerio Oct 31, 2023
29f1f76
feat: Waffle Flag for course_roles helper functions
hsinkoff Oct 16, 2023
5051704
feat: remove caching from course_roles
hsinkoff Oct 31, 2023
343656b
feat: expose course_roles.use_permission_checks waffle flag for mfe use
hsinkoff Nov 2, 2023
9d5e690
docs: update documenation related to course_roles
hsinkoff Nov 2, 2023
f596a53
feat: rename course_roles helper functions
hsinkoff Nov 8, 2023
7f937d9
feat: remove course roles namespace for models
julianpalmerio Nov 13, 2023
2cffa51
feat: remove course roles namespace for models
julianpalmerio Nov 13, 2023
ab2cce3
feat: update migrations
julianpalmerio Nov 13, 2023
4cd51d0
feat: add unique constraints in many to many relationships
julianpalmerio Nov 13, 2023
7249516
feat: update migrations
julianpalmerio Nov 13, 2023
1198f99
test: Add CourseOverview loading to test_helpers.py
julianpalmerio Nov 14, 2023
1fac6ec
test: Add CourseOverview loading int test_views.py
julianpalmerio Nov 14, 2023
7b4a7dc
chore: fixup rebase misses
hsinkoff Nov 16, 2023
5cb6194
feat: change RolePermissions table name to RolePermission
julianpalmerio Nov 17, 2023
c6eeb28
feat: update course_roles migrations
julianpalmerio Nov 17, 2023
c11118e
feat: improve CourseRolesPermission enum
julianpalmerio Nov 17, 2023
d09690e
feat: improve course_roles helper functions types
julianpalmerio Nov 17, 2023
1385fb0
feat: add str type in permission lists helper functions
julianpalmerio Nov 21, 2023
95d4ebe
feat: change course_id parameter to course_key in helper functions
julianpalmerio Nov 21, 2023
b56fdb1
feat: change error messages
julianpalmerio Nov 21, 2023
4545279
feat: remove translation of error messages
julianpalmerio Nov 21, 2023
2aa8be4
feat: improve get_all_user_permissions_for_a_course query
julianpalmerio Nov 21, 2023
828863d
fix: remove unused import
julianpalmerio Nov 21, 2023
19d4e96
feat: remove org parameter from course_org helper functions
julianpalmerio Nov 21, 2023
bd89bbc
feat: replace modulestore call to CourseOverview call
julianpalmerio Nov 22, 2023
8e6b144
feat: improve permissions query set in get_all_user_permissions_for_a…
julianpalmerio Nov 22, 2023
c67f544
feat: add request_cahed decorator tho course_roles helper functions
julianpalmerio Nov 22, 2023
bd3fb11
test: fix course_roles tests to handle cache issues
julianpalmerio Nov 22, 2023
1f9d3af
feat: improve typing and align with OEP-49
julianpalmerio Nov 29, 2023
bd157c5
feat: update import paths
julianpalmerio Nov 29, 2023
73c6f58
test: update tests
julianpalmerio Nov 29, 2023
f7cd140
feat: Remove old helper functions and update the unique function to g…
julianpalmerio Nov 30, 2023
9a25c60
test: update new api tests
julianpalmerio Nov 30, 2023
b675a6e
feat: update views with the new api function
julianpalmerio Nov 30, 2023
da941de
test: update view tests
julianpalmerio Nov 30, 2023
85e3a4e
feat: add bridgekeeper rules
julianpalmerio Nov 30, 2023
bbe7130
feat: add bridgekeeper permissions
julianpalmerio Nov 30, 2023
e445147
feat: update models
julianpalmerio Nov 30, 2023
030adfe
fix: parameter as *arg in HasForumsRolesRule
julianpalmerio Nov 30, 2023
ae37a84
feat: update migrations
julianpalmerio Dec 1, 2023
e7ea5c4
docs: add migration readme
julianpalmerio Dec 5, 2023
d03ac33
feat: move waffle flag from api.py to toggles.py
julianpalmerio Dec 5, 2023
22a05d7
feat: add waffle flag to bridgekeeper rules
julianpalmerio Dec 5, 2023
ab2de2b
docs: fix rst syntax
julianpalmerio Dec 5, 2023
e688cba
docs: Add data.py docstring for course roles app
julianpalmerio Dec 5, 2023
e9702ad
test: update api tests
julianpalmerio Dec 6, 2023
0ed7b41
feat: update api
julianpalmerio Dec 6, 2023
f44fe1f
chore: Delete duplicate readme.rst
julianpalmerio Dec 6, 2023
ef74b7f
fix: pylint error
julianpalmerio Dec 6, 2023
af387ac
fix: pylint error
julianpalmerio Dec 7, 2023
dcdc955
fix: pylint error
julianpalmerio Dec 7, 2023
9a270e2
fix: pylint error
julianpalmerio Dec 7, 2023
37b8e5c
feat: make error messages more descriptive
julianpalmerio Dec 13, 2023
dbb3d30
feat: improve data file docstring
julianpalmerio Dec 13, 2023
c0e5f63
feat: make error messages more descriptive
julianpalmerio Dec 13, 2023
4edd1c5
feat: update bridgekeeper rules
julianpalmerio Dec 13, 2023
90a9872
test: add test for bridgekeeper rules
julianpalmerio Dec 13, 2023
3cb6a01
feat: change permission name
julianpalmerio Dec 14, 2023
97eab69
feat: fix waffle flag toggle name in docstring
julianpalmerio Dec 14, 2023
301ddca
feat: update permission name in bridgekeeper permissions
julianpalmerio Dec 14, 2023
aa289b5
feat: improve error handling
julianpalmerio Dec 14, 2023
46641af
feat: add course_id and course_key to the response on UserPermissions…
julianpalmerio Dec 14, 2023
3f6c734
feat: user validation in UserPermissionsView
julianpalmerio Dec 14, 2023
44961e7
style: fix line over-indented
julianpalmerio Dec 15, 2023
4da0366
feat: sort permissions in UserPermissionsView response
julianpalmerio Dec 18, 2023
d8b680f
test: update view test
julianpalmerio Dec 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pylint-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- module-name: lms-2
path: "--django-settings-module=lms.envs.test lms/djangoapps/gating/ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_analytics/ lms/djangoapps/discussion/ lms/djangoapps/edxnotes/ lms/djangoapps/email_marketing/ lms/djangoapps/experiments/ lms/djangoapps/instructor_task/ lms/djangoapps/learner_dashboard/ lms/djangoapps/learner_recommendations/ lms/djangoapps/learner_home/ lms/djangoapps/lms_initialization/ lms/djangoapps/lms_xblock/ lms/djangoapps/lti_provider/ lms/djangoapps/mailing/ lms/djangoapps/mobile_api/ lms/djangoapps/monitoring/ lms/djangoapps/ora_staff_grader/ lms/djangoapps/program_enrollments/ lms/djangoapps/rss_proxy lms/djangoapps/static_template_view/ lms/djangoapps/staticbook/ lms/djangoapps/support/ lms/djangoapps/survey/ lms/djangoapps/teams/ lms/djangoapps/tests/ lms/djangoapps/user_tours/ lms/djangoapps/verify_student/ lms/djangoapps/mfe_config_api/ lms/envs/ lms/lib/ lms/tests.py"
- module-name: openedx-1
path: "--django-settings-module=lms.envs.test openedx/core/types/ openedx/core/djangoapps/ace_common/ openedx/core/djangoapps/agreements/ openedx/core/djangoapps/api_admin/ openedx/core/djangoapps/auth_exchange/ openedx/core/djangoapps/bookmarks/ openedx/core/djangoapps/cache_toolbox/ openedx/core/djangoapps/catalog/ openedx/core/djangoapps/ccxcon/ openedx/core/djangoapps/commerce/ openedx/core/djangoapps/common_initialization/ openedx/core/djangoapps/common_views/ openedx/core/djangoapps/config_model_utils/ openedx/core/djangoapps/content/ openedx/core/djangoapps/content_libraries/ openedx/core/djangoapps/content_staging/ openedx/core/djangoapps/contentserver/ openedx/core/djangoapps/cookie_metadata/ openedx/core/djangoapps/cors_csrf/ openedx/core/djangoapps/course_apps/ openedx/core/djangoapps/course_date_signals/ openedx/core/djangoapps/course_groups/ openedx/core/djangoapps/courseware_api/ openedx/core/djangoapps/crawlers/ openedx/core/djangoapps/credentials/ openedx/core/djangoapps/credit/ openedx/core/djangoapps/dark_lang/ openedx/core/djangoapps/debug/ openedx/core/djangoapps/demographics/ openedx/core/djangoapps/discussions/ openedx/core/djangoapps/django_comment_common/ openedx/core/djangoapps/embargo/ openedx/core/djangoapps/enrollments/ openedx/core/djangoapps/external_user_ids/ openedx/core/djangoapps/zendesk_proxy/ openedx/core/djangolib/ openedx/core/lib/ openedx/core/tests/ openedx/core/djangoapps/course_live/"
path: "--django-settings-module=lms.envs.test openedx/core/types/ openedx/core/djangoapps/ace_common/ openedx/core/djangoapps/agreements/ openedx/core/djangoapps/api_admin/ openedx/core/djangoapps/auth_exchange/ openedx/core/djangoapps/bookmarks/ openedx/core/djangoapps/cache_toolbox/ openedx/core/djangoapps/catalog/ openedx/core/djangoapps/ccxcon/ openedx/core/djangoapps/commerce/ openedx/core/djangoapps/common_initialization/ openedx/core/djangoapps/common_views/ openedx/core/djangoapps/config_model_utils/ openedx/core/djangoapps/content/ openedx/core/djangoapps/content_libraries/ openedx/core/djangoapps/content_staging/ openedx/core/djangoapps/contentserver/ openedx/core/djangoapps/cookie_metadata/ openedx/core/djangoapps/cors_csrf/ openedx/core/djangoapps/course_apps/ openedx/core/djangoapps/course_date_signals/ openedx/core/djangoapps/course_groups/ openedx/core/djangoapps/course_roles/ openedx/core/djangoapps/courseware_api/ openedx/core/djangoapps/crawlers/ openedx/core/djangoapps/credentials/ openedx/core/djangoapps/credit/ openedx/core/djangoapps/dark_lang/ openedx/core/djangoapps/debug/ openedx/core/djangoapps/demographics/ openedx/core/djangoapps/discussions/ openedx/core/djangoapps/django_comment_common/ openedx/core/djangoapps/embargo/ openedx/core/djangoapps/enrollments/ openedx/core/djangoapps/external_user_ids/ openedx/core/djangoapps/zendesk_proxy/ openedx/core/djangolib/ openedx/core/lib/ openedx/core/tests/ openedx/core/djangoapps/course_live/"
- module-name: openedx-2
path: "--django-settings-module=lms.envs.test openedx/core/djangoapps/geoinfo/ openedx/core/djangoapps/header_control/ openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/lang_pref/ openedx/core/djangoapps/models/ openedx/core/djangoapps/monkey_patch/ openedx/core/djangoapps/oauth_dispatch/ openedx/core/djangoapps/olx_rest_api/ openedx/core/djangoapps/password_policy/ openedx/core/djangoapps/plugin_api/ openedx/core/djangoapps/plugins/ openedx/core/djangoapps/profile_images/ openedx/core/djangoapps/programs/ openedx/core/djangoapps/safe_sessions/ openedx/core/djangoapps/schedules/ openedx/core/djangoapps/service_status/ openedx/core/djangoapps/session_inactivity_timeout/ openedx/core/djangoapps/signals/ openedx/core/djangoapps/site_configuration/ openedx/core/djangoapps/system_wide_roles/ openedx/core/djangoapps/theming/ openedx/core/djangoapps/user_api/ openedx/core/djangoapps/user_authn/ openedx/core/djangoapps/util/ openedx/core/djangoapps/verified_track_content/ openedx/core/djangoapps/video_config/ openedx/core/djangoapps/video_pipeline/ openedx/core/djangoapps/waffle_utils/ openedx/core/djangoapps/xblock/ openedx/core/djangoapps/xmodule_django/ openedx/core/tests/ openedx/features/ openedx/testing/ openedx/tests/ openedx/core/djangoapps/learner_pathway/ openedx/core/djangoapps/notifications/ openedx/core/djangoapps/staticfiles/ openedx/core/djangoapps/content_tagging/"
- module-name: common
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/unit-test-shards.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"openedx/core/djangoapps/course_apps/",
"openedx/core/djangoapps/course_date_signals/",
"openedx/core/djangoapps/course_groups/",
"openedx/core/djangoapps/course_roles/",
"openedx/core/djangoapps/courseware_api/",
"openedx/core/djangoapps/crawlers/",
"openedx/core/djangoapps/credentials/",
Expand Down Expand Up @@ -182,6 +183,7 @@
"openedx/core/djangoapps/course_apps/",
"openedx/core/djangoapps/course_date_signals/",
"openedx/core/djangoapps/course_groups/",
"openedx/core/djangoapps/course_roles/",
"openedx/core/djangoapps/courseware_api/",
"openedx/core/djangoapps/crawlers/",
"openedx/core/djangoapps/credentials/",
Expand Down
3 changes: 3 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1809,6 +1809,9 @@
# alternative swagger generator for CMS API
'drf_spectacular',
'openedx_events',

# Course Roles
'openedx.core.djangoapps.course_roles',
]


Expand Down
5 changes: 5 additions & 0 deletions cms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,8 @@
re_path('^cms-api/ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
re_path('^cms-api/schema/', SpectacularAPIView.as_view(), name='schema'),
]

# Course Roles API
urlpatterns += [
path('api/course_roles/', include('openedx.core.djangoapps.course_roles.urls', namespace='course_roles_api')),
]
3 changes: 3 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3316,6 +3316,9 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring
# Notifications
'openedx.core.djangoapps.notifications',
'openedx_events',

# Course Roles
'openedx.core.djangoapps.course_roles',
]

######################### CSRF #########################################
Expand Down
5 changes: 5 additions & 0 deletions lms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,3 +1054,8 @@
urlpatterns += [
path('api/notifications/', include('openedx.core.djangoapps.notifications.urls')),
]

# Course Roles API
urlpatterns += [
path('api/course_roles/', include('openedx.core.djangoapps.course_roles.urls', namespace='course_roles_api')),
]
15 changes: 15 additions & 0 deletions openedx/core/djangoapps/course_roles/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Course Roles
#############################

Status: Development

Purpose
*******

The intention of this app is to to make course level roles for users within LMS/CMS more flexible.

Responsibilities
***************

Add models to manage the course roles and their permissions.
Provide a helper to check if a user has a specific permission in a course.
Empty file.
55 changes: 55 additions & 0 deletions openedx/core/djangoapps/course_roles/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Helpers for the course roles app.
"""
from __future__ import annotations

from django.contrib.auth.models import AnonymousUser, User # lint-amnesty, pylint: disable=imported-auth-user
from django.db.models import Q
from opaque_keys.edx.keys import CourseKey

from edx_django_utils.cache import RequestCache
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.course_roles.models import UserRole
from openedx.core.djangoapps.course_roles.data import CourseRolesPermission


def get_all_user_permissions_for_a_course(
user: User | AnonymousUser, course_key: CourseKey
) -> set[CourseRolesPermission]:
"""
Get all of a user's permissions for a course,
including, if applicable, organization-wide permissions
and instance-wide permissions.
"""
if isinstance(user, AnonymousUser):
return set()
if not isinstance(course_key, CourseKey):
raise TypeError('course_key must be a CourseKey')
julianpalmerio marked this conversation as resolved.
Show resolved Hide resolved
if not isinstance(user, User):
raise TypeError('user must be a User')
julianpalmerio marked this conversation as resolved.
Show resolved Hide resolved
cache = RequestCache("course_roles")
cache_key = f"all_user_permissions_for_course:{user.id}:{course_key}"
cached_response = cache.get_cached_response(cache_key)
if cached_response.is_found:
return cached_response.value
if not CourseOverview.course_exists(course_key):
raise ValueError(f'course does not exist: {course_key}')
permissions_qset = UserRole.objects.filter(
Q(user=user),
(
# Course-specific roles
Q(course=course_key) |
# Org-wide roles that apply to this course
(Q(course__isnull=True) & Q(org__name=course_key.org)) |
# Instance-wide roles
(Q(course__isnull=True) & Q(org__isnull=True))
)
)
permissions = set(
CourseRolesPermission[permission_name.upper()]
julianpalmerio marked this conversation as resolved.
Show resolved Hide resolved
for permission_name
in permissions_qset.values_list('role__permissions__name', flat=True)
)
cache.set(cache_key, permissions)

return permissions
160 changes: 160 additions & 0 deletions openedx/core/djangoapps/course_roles/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
"""
Permissions for course roles app.

These are the permissions that can be assigned to a CourseRole which grants access for a course,
but can also be assigned for org wide course access or instance wide course access.
They are defined in the database in the course_roles_permission table.
julianpalmerio marked this conversation as resolved.
Show resolved Hide resolved

To add a new permission, add a new entry to the CourseRolesPermission enum,
then and add a new row to the course_roles_permission table in database,
with a migration.
julianpalmerio marked this conversation as resolved.
Show resolved Hide resolved

To remove a permission, remove the entry from the CourseRolesPermission enum,
and remove the row from the course_roles_permission table in database,
with a migration.

To change the readable_name or description of a permission, change the
corresponding entry in CourseRolesPermission enum.

To change the name of a permission, change the corresponding entry on the
CourseRolesPermission enum, and change the name field of the corresponding
row in the course_roles_permission table in database, with a migration.
"""
from attrs import frozen, field, validators
from enum import Enum, unique

from django.utils.translation import gettext as _


@frozen
class PermissionData:
"""
Data class for a permission.
"""
name: str = field(validator=validators.instance_of(str))
readable_name: str = field(validator=validators.instance_of(str))
description: str = field(validator=validators.instance_of(str))
julianpalmerio marked this conversation as resolved.
Show resolved Hide resolved


@unique
class CourseRolesPermission(Enum):
"""
Enum of all user permissions, the values are the permissions names
in the course_roles_permission table in database.

The readable_name and description are used in the UI.
"""
Copy link
Contributor

@ormsbee ormsbee Dec 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long term note: One architectural failure mode I'm worried about here is if people find it difficult to add new permissions and instead start using other permissions that imply a role.

For instance, say someone wants to add a permission for the Staff Debug information under Components in the LMS rendering. But adding a permission and doing the data migration sounds hard. Then they browse through this class and think to themselves, "Well, if they can VIEW_ALL_CONTENT AND MANAGE_STUDENTS, then they're course staff and they should be able to see the Staff Debug info..." and we get into the mode where people are re-deriving roles implicitly from permissions and extrapolating those onto other implied permissions.

I'm not saying we're doomed to this. Just that it's a risk and that we'll need docs and outreach, presentations, etc. in order to make sure that developers working on this will have the right mindset. This gets more likely if there's any real friction to adding permissions–long approval process, unclear docs, any perception of operational risk in the event of a rollback, etc.

Come to think on it, this whole thing would make for a great Open edX Conference talk or a Meetup presentation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We agree this presents a risk and have been thinking through strategies to mitigate for this risk. We do not think this presents an immediate risk, but plan to add documentation around how and when to add a permission in OEP-66 and within the djangoapps/course_roles/docs folder.


MANAGE_CONTENT = PermissionData(
"manage_content",
_("Manage Content"),
_("Can view, create, edit, delete and publish (not publisher tool) any course content."),
)
MANAGE_COURSE_SETTINGS = PermissionData(
"manage_course_settings", _("Manage Course Settings"), _("Can view and edit settings pages.")
)
MANAGE_ADVANCED_SETTINGS = PermissionData(
"manage_advanced_settings", _("Manage Advanced Settings"), _("Can view and edit advanced settings.")
)
VIEW_COURSE_SETTINGS = PermissionData(
"view_course_settings", _("View Course Settings"), _("Can view all settings pages.")
)
VIEW_ALL_CONTENT = PermissionData(
"view_all_content",
_("View All Content"),
_(
"Can view course content in LMS in all statuses: "
"Published and live, Draft, Staff-only, Published-not-yet-released."
),
)
VIEW_ONLY_LIVE_PUBLISHED_CONTENT = PermissionData(
"view_only_live_published_content",
_("View Only Live Published Content"),
_(
"Can only view published-and-live content in a course. "
"Cannot view published-not-yet-released content. Cannot view Staff-only content."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It seems odd to have a permission that tells you what you can't do. It looks like this is supposed to be exclusive with VIEW_ALL_CONTENT so that you only have one or the other, but I would have expected this permission to be VIEW_LIVE_PUBLISHED_CONTENT and for staff to have both.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A role can have both permissions, but in that case VIEW_ONLY_LIVE_PUBLISHED_CONTENT will not actually add any access. All content includes all live published content so having both would be a redundancy, but it won't cause any issues in the code.

This permission (VIEW_ONLY_LIVE_PUBLISHED_CONTENT) exists so that it can be given to users who should only see live published content and not published content before it is live or unpublished content.

@cablaa77 Please let us know if you agree the permission name should be changed to VIEW_LIVE_PUBLISHED_CONTENT from VIEW_ONLY_LIVE_PUBLISHED_CONTENT.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the general pattern I am leery of is that people will start to use a permission check as a proxy for the absence of other permissions, and that it will introduce unnecessary coupling when we want to change things later. But I don't have a concrete case off the top of my head, and I won't block approving this PR over this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The permission name is being changed to VIEW_LIVE_PUBLISHED_CONTENT

),
)
VIEW_ALL_PUBLISHED_CONTENT = PermissionData(
"view_all_published_content",
_("View All Published Content"),
_("Can view all Published content, including published-not-yet-released."),
)
ACCESS_INSTRUCTOR_DASHBOARD = PermissionData(
"access_instructor_dashboard",
_("Access Instructor Dashboard"),
_("Can view Instructor Dashboard with Basic Course Information."),
)
ACCESS_DATA_DOWNLOADS = PermissionData(
"access_data_downloads",
_("Access Data Downloads"),
_("Can view Data Downloads tab and generate all report types."),
)
MANAGE_GRADES = PermissionData(
"manage_grades",
_("Manage Grades"),
_("Can view and edit grades in Student Admin tab, Gradebook, and Open Responses."),
)
VIEW_GRADEBOOK = PermissionData(
"view_gradebook", _("View Gradebook"), _("Can view gradebook but cannot download or edit anything.")
)
MANAGE_ALL_USERS = PermissionData(
"manage_all_users",
_("Manage All Users"),
_("Can add, remove, and change role for all members of the course team."),
)
MANAGE_USERS_EXCEPT_ADMIN_AND_STAFF = PermissionData(
"manage_users_except_admin_and_staff",
_("Manage Users Except Admin and Staff"),
_("Can add, remove, change role for members of the course team, EXCEPT Admins and Staff."),
)
MANAGE_DISCUSSION_MODERATORS = PermissionData(
"manage_discussion_moderators", _("Manage Discussion Moderators"), _("Can add and remove all moderator roles.")
)
MANAGE_COHORTS = PermissionData(
"manage_cohorts", _("Manage Cohorts"), _("Can add and remove learners in cohorts; Can add new cohorts.")
)
MANAGE_STUDENTS = PermissionData(
"manage_students",
_("Manage Students"),
_(
"Can manage batch enrollments of audit learners and beta testers; Can manage cohorts, "
"extensions, student admin, Insights, bulk email, special exams, and open responses."
),
)
MODERATE_DISCUSSION_FORUMS = PermissionData(
"moderate_discussion_forums",
_("Moderate Discussion Forums"),
_(
"Can view, edit, or delete all posts; Can pin, close, or reopen posts; "
"Can endorse responses, reports/flags."
),
)
MODERATE_DISCUSSION_FORUMS_FOR_A_COHORT = PermissionData(
"moderate_discussion_forums_for_a_cohort",
_("Moderate Discussion Forums for a Cohort"),
_(
"Can view, edit, or delete all posts; Can pin, close, or reopen posts; Can endorse responses, "
"reports/flags. (Limited to user's own cohort)."
),
)
MANAGE_CERTIFICATES = PermissionData(
"manage_certificates",
_("Manage Certificates"),
_("Can generate, regenerate, make exceptions, and invalidate certificates."),
)
MANAGE_LIBRARIES = PermissionData(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is managing Libraries a Course Role/Permission?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This permission is for the v1 content libraries. It will not need to exist if v2 content libraries is live, migrations are completed, and v1 code is removed before this code goes live, but a permission was included in case v1 content libraries is still live when a new role built off these permissions is created. This permission will be removed once v1 content libraries is fully sunset.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. So v1 libraries will definitely still be around when this rolls out (for the community, even if edx.org decides to do the upgrade before then).

I'd like to understand a little more what it means when someone has this permission, because I'm not clear on what it would mean to have permission to MANAGE_LIBRARIES for a specific course, when the library exists outside of that course.

  1. When we're deciding whether someone is allowed to create an entirely new library, do we check this permission at the org level?
  2. When we're editing an individual v1 content library, does this use the v1 library key as the course key (because through hackiness, the v1 library keys are a subclass of CourseKey)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking through the existing code, we found only one location (so far) where the code checks if a user has the LibraryUser role.
This permission will be added as a permission check where the role is currently checked and used to create the LibraryUser role in course_roles, but at this time we haven't identified any additional locations where we would add a check for the new permission.

  1. In course_roles permissions checks are not dependent on the level on which the permission was granted, but just that the user has that permission in the context in question. This means if the user has the LibraryUser role at the org-wide level, they have that permission for all courses within the org. It also means if we check if the user has the permission, we are checking for course, org, and instance wide assignment of the permission.
  2. We will likely have to make a determination on that when we update the code referenced above. course_roles has a fk dependency so we cannot assign a course_role on the library itself. It is also possible that the migration order of roles from student_courseaccessrole to course_roles_role can be altered so that the LibraryUser role stays on the student_courseaccessrole until after the point in time when the named release is using v2 content libraries and this permission (and role) are no longer relevant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I'd suggest leaving this behind in student_courseaccessrole, but I'm okay either way in the short term.

"manage_libraries",
_("Manage Libraries"),
_("Can create and edit content libraries; Can pull in content libraries via Content Outline."),
)
GENERAL_MASQUERADING = PermissionData(
"general_masquerading",
_("General Masquerading"),
_("Can view the course as an Audit or Verified learner only."),
)
SPECIFIC_MASQUERADING = PermissionData(
"specific_masquerading",
_("Specific Masquerading"),
_("Can view the course as an Audit, Verified, Beta Tester, Masters track, username/email."),
julianpalmerio marked this conversation as resolved.
Show resolved Hide resolved
)
Loading
Loading