Skip to content

Commit 4e52975

Browse files
[FC-0099] feat: assign library roles after successful library creation (openedx#37532)
1 parent 27ccc61 commit 4e52975

8 files changed

Lines changed: 95 additions & 0 deletions

File tree

openedx/core/djangoapps/content_libraries/api/libraries.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
from openedx_learning.api.authoring_models import Component
6767
from organizations.models import Organization
6868
from xblock.core import XBlock
69+
from openedx_authz.api import assign_role_to_user_in_scope
6970

7071
from openedx.core.types import User as UserType
7172

@@ -159,6 +160,12 @@ class AccessLevel:
159160
NO_ACCESS = None
160161

161162

163+
ACCESS_LEVEL_TO_LIBRARY_ROLE = {
164+
AccessLevel.ADMIN_LEVEL: "library_admin",
165+
AccessLevel.AUTHOR_LEVEL: "library_author",
166+
}
167+
168+
162169
@dataclass(frozen=True)
163170
class ContentLibraryPermissionEntry:
164171
"""
@@ -534,6 +541,30 @@ def set_library_user_permissions(library_key: LibraryLocatorV2, user: UserType,
534541
)
535542

536543

544+
def assign_library_role_to_user(library_key: LibraryLocatorV2, user: UserType, access_level: str):
545+
"""Grant a role to the specified user for this library.
546+
547+
Args:
548+
library_key (LibraryLocatorV2): The key of the content library.
549+
user (UserType): The user to whom the role will be granted.
550+
access_level (str | None): The access level to be granted. This access level maps to a specific role.
551+
552+
Raises:
553+
TypeError: If the user is an instance of AnonymousUser.
554+
"""
555+
if isinstance(user, AnonymousUser):
556+
raise TypeError("Invalid user type")
557+
558+
role = ACCESS_LEVEL_TO_LIBRARY_ROLE.get(access_level)
559+
if role is None:
560+
raise ValueError(f"Invalid access level: {access_level}")
561+
562+
if assign_role_to_user_in_scope(user.username, role, str(library_key)):
563+
log.info(f"Assigned role '{role}' to user '{user.username}' for library '{library_key}'")
564+
else:
565+
log.warning(f"Failed to assign role '{role}' to user '{user.username}' for library '{library_key}'")
566+
567+
537568
def set_library_group_permissions(library_key: LibraryLocatorV2, group, access_level: str):
538569
"""
539570
Change the specified group's level of access to this library.

openedx/core/djangoapps/content_libraries/rest_api/libraries.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,12 @@ def post(self, request):
244244
result = api.create_library(org=org, **data)
245245
# Grant the current user admin permissions on the library:
246246
api.set_library_user_permissions(result.key, request.user, api.AccessLevel.ADMIN_LEVEL)
247+
248+
# Grant the current user the library admin role for this library.
249+
# Other role assignments are handled by openedx-authz and the Console MFE.
250+
# This ensures the creator has access to new libraries. From the library views,
251+
# users can then manage roles for others.
252+
api.assign_library_role_to_user(result.key, request.user, api.AccessLevel.ADMIN_LEVEL)
247253
except api.LibraryAlreadyExists:
248254
raise ValidationError(detail={"slug": "A library with that ID already exists."}) # lint-amnesty, pylint: disable=raise-missing-from
249255

openedx/core/djangoapps/content_libraries/tests/test_api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
LIBRARY_COLLECTION_UPDATED,
2626
LIBRARY_CONTAINER_UPDATED,
2727
)
28+
from openedx_authz.api.users import get_user_role_assignments_in_scope
2829
from openedx_learning.api import authoring as authoring_api
2930

31+
from common.djangoapps.student.tests.factories import UserFactory
3032
from .. import api
3133
from ..models import ContentLibrary
3234
from .base import ContentLibrariesRestApiTest

requirements/edx/base.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ attrs==25.3.0
4040
# edx-ace
4141
# jsonschema
4242
# lti-consumer-xblock
43+
# openedx-authz
4344
# openedx-events
4445
# openedx-learning
4546
# referencing
@@ -81,6 +82,8 @@ botocore==1.37.38
8182
# boto3
8283
# s3transfer
8384
# snowflake-connector-python
85+
bracex==2.6
86+
# via wcmatch
8487
bridgekeeper==0.9
8588
# via -r requirements/edx/kernel.in
8689
cachecontrol==0.14.2
@@ -170,6 +173,7 @@ django==4.2.20
170173
# -c requirements/edx/../common_constraints.txt
171174
# -c requirements/edx/../constraints.txt
172175
# -r requirements/edx/kernel.in
176+
# casbin-django-orm-adapter
173177
# django-appconf
174178
# django-celery-results
175179
# django-classy-tags
@@ -226,6 +230,7 @@ django==4.2.20
226230
# help-tokens
227231
# jsonfield
228232
# lti-consumer-xblock
233+
# openedx-authz
229234
# openedx-django-pyfs
230235
# openedx-django-wiki
231236
# openedx-events
@@ -380,6 +385,7 @@ djangorestframework==3.14.0
380385
# edx-organizations
381386
# edx-proctoring
382387
# edx-submissions
388+
# openedx-authz
383389
# openedx-forum
384390
# openedx-learning
385391
# ora2
@@ -491,6 +497,7 @@ edx-opaque-keys[django]==3.0.0
491497
# edx-proctoring
492498
# edx-when
493499
# lti-consumer-xblock
500+
# openedx-authz
494501
# openedx-events
495502
# openedx-filters
496503
# ora2
@@ -891,6 +898,10 @@ pyasn1==0.6.1
891898
# rsa
892899
pyasn1-modules==0.4.2
893900
# via google-auth
901+
pycasbin==2.4.0
902+
# via
903+
# casbin-django-orm-adapter
904+
# openedx-authz
894905
pycountry==24.6.1
895906
# via -r requirements/edx/kernel.in
896907
pycparser==2.22

requirements/edx/development.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ attrs==25.3.0
9393
# edx-ace
9494
# jsonschema
9595
# lti-consumer-xblock
96+
# openedx-authz
9697
# openedx-events
9798
# openedx-learning
9899
# referencing
@@ -155,6 +156,11 @@ botocore==1.37.38
155156
# boto3
156157
# s3transfer
157158
# snowflake-connector-python
159+
bracex==2.6
160+
# via
161+
# -r requirements/edx/doc.txt
162+
# -r requirements/edx/testing.txt
163+
# wcmatch
158164
bridgekeeper==0.9
159165
# via
160166
# -r requirements/edx/doc.txt
@@ -340,6 +346,7 @@ django==4.2.20
340346
# -c requirements/edx/../constraints.txt
341347
# -r requirements/edx/doc.txt
342348
# -r requirements/edx/testing.txt
349+
# casbin-django-orm-adapter
343350
# django-appconf
344351
# django-celery-results
345352
# django-classy-tags
@@ -399,6 +406,7 @@ django==4.2.20
399406
# help-tokens
400407
# jsonfield
401408
# lti-consumer-xblock
409+
# openedx-authz
402410
# openedx-django-pyfs
403411
# openedx-django-wiki
404412
# openedx-events
@@ -617,6 +625,7 @@ djangorestframework==3.14.0
617625
# edx-organizations
618626
# edx-proctoring
619627
# edx-submissions
628+
# openedx-authz
620629
# openedx-forum
621630
# openedx-learning
622631
# ora2
@@ -781,6 +790,7 @@ edx-opaque-keys[django]==3.0.0
781790
# edx-proctoring
782791
# edx-when
783792
# lti-consumer-xblock
793+
# openedx-authz
784794
# openedx-events
785795
# openedx-filters
786796
# ora2
@@ -1527,6 +1537,12 @@ pyasn1-modules==0.4.2
15271537
# -r requirements/edx/doc.txt
15281538
# -r requirements/edx/testing.txt
15291539
# google-auth
1540+
pycasbin==2.4.0
1541+
# via
1542+
# -r requirements/edx/doc.txt
1543+
# -r requirements/edx/testing.txt
1544+
# casbin-django-orm-adapter
1545+
# openedx-authz
15301546
pycodestyle==2.8.0
15311547
# via
15321548
# -c requirements/edx/../constraints.txt

requirements/edx/doc.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ attrs==25.3.0
6262
# edx-ace
6363
# jsonschema
6464
# lti-consumer-xblock
65+
# openedx-authz
6566
# openedx-events
6667
# openedx-learning
6768
# referencing
@@ -114,6 +115,10 @@ botocore==1.37.38
114115
# boto3
115116
# s3transfer
116117
# snowflake-connector-python
118+
bracex==2.6
119+
# via
120+
# -r requirements/edx/base.txt
121+
# wcmatch
117122
bridgekeeper==0.9
118123
# via -r requirements/edx/base.txt
119124
cachecontrol==0.14.2
@@ -226,6 +231,7 @@ django==4.2.20
226231
# -c requirements/edx/../common_constraints.txt
227232
# -c requirements/edx/../constraints.txt
228233
# -r requirements/edx/base.txt
234+
# casbin-django-orm-adapter
229235
# django-appconf
230236
# django-celery-results
231237
# django-classy-tags
@@ -282,6 +288,7 @@ django==4.2.20
282288
# help-tokens
283289
# jsonfield
284290
# lti-consumer-xblock
291+
# openedx-authz
285292
# openedx-django-pyfs
286293
# openedx-django-wiki
287294
# openedx-events
@@ -452,6 +459,7 @@ djangorestframework==3.14.0
452459
# edx-organizations
453460
# edx-proctoring
454461
# edx-submissions
462+
# openedx-authz
455463
# openedx-forum
456464
# openedx-learning
457465
# ora2
@@ -575,6 +583,7 @@ edx-opaque-keys[django]==3.0.0
575583
# edx-proctoring
576584
# edx-when
577585
# lti-consumer-xblock
586+
# openedx-authz
578587
# openedx-events
579588
# openedx-filters
580589
# ora2
@@ -1086,6 +1095,11 @@ pyasn1-modules==0.4.2
10861095
# via
10871096
# -r requirements/edx/base.txt
10881097
# google-auth
1098+
pycasbin==2.4.0
1099+
# via
1100+
# -r requirements/edx/base.txt
1101+
# casbin-django-orm-adapter
1102+
# openedx-authz
10891103
pycountry==24.6.1
10901104
# via -r requirements/edx/base.txt
10911105
pycparser==2.22

requirements/edx/kernel.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,4 @@ wrapt # Better functools.wrapped. TODO: functools
163163
XBlock[django] # Courseware component architecture
164164
xss-utils # https://github.com/openedx/edx-platform/pull/20633 Fix XSS via Translations
165165
unicodeit # Converts mathjax equation to plain text by using unicode symbols
166+
openedx-authz # Authorization Framework for the Open edX Ecosystem

requirements/edx/testing.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ attrs==25.3.0
6464
# edx-ace
6565
# jsonschema
6666
# lti-consumer-xblock
67+
# openedx-authz
6768
# openedx-events
6869
# openedx-learning
6970
# referencing
@@ -114,6 +115,10 @@ botocore==1.37.38
114115
# boto3
115116
# s3transfer
116117
# snowflake-connector-python
118+
bracex==2.6
119+
# via
120+
# -r requirements/edx/base.txt
121+
# wcmatch
117122
bridgekeeper==0.9
118123
# via -r requirements/edx/base.txt
119124
cachecontrol==0.14.2
@@ -256,6 +261,7 @@ django==4.2.20
256261
# -c requirements/edx/../common_constraints.txt
257262
# -c requirements/edx/../constraints.txt
258263
# -r requirements/edx/base.txt
264+
# casbin-django-orm-adapter
259265
# django-appconf
260266
# django-celery-results
261267
# django-classy-tags
@@ -312,6 +318,7 @@ django==4.2.20
312318
# help-tokens
313319
# jsonfield
314320
# lti-consumer-xblock
321+
# openedx-authz
315322
# openedx-django-pyfs
316323
# openedx-django-wiki
317324
# openedx-events
@@ -482,6 +489,7 @@ djangorestframework==3.14.0
482489
# edx-organizations
483490
# edx-proctoring
484491
# edx-submissions
492+
# openedx-authz
485493
# openedx-forum
486494
# openedx-learning
487495
# ora2
@@ -602,6 +610,7 @@ edx-opaque-keys[django]==3.0.0
602610
# edx-proctoring
603611
# edx-when
604612
# lti-consumer-xblock
613+
# openedx-authz
605614
# openedx-events
606615
# openedx-filters
607616
# ora2
@@ -1159,6 +1168,11 @@ pyasn1-modules==0.4.2
11591168
# via
11601169
# -r requirements/edx/base.txt
11611170
# google-auth
1171+
pycasbin==2.4.0
1172+
# via
1173+
# -r requirements/edx/base.txt
1174+
# casbin-django-orm-adapter
1175+
# openedx-authz
11621176
pycodestyle==2.8.0
11631177
# via
11641178
# -c requirements/edx/../constraints.txt

0 commit comments

Comments
 (0)