Skip to content

Commit

Permalink
fix: enforce a user's "allowed organizations" when creating libraries
Browse files Browse the repository at this point in the history
Also adds the list of "allowed organizations for libraries" to the
Studio Home API, so it can be used by the Authoring MFE.
  • Loading branch information
pomegranited committed Jan 14, 2025
1 parent 1ac6e02 commit f1f40e0
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 0 deletions.
4 changes: 4 additions & 0 deletions cms/djangoapps/contentstore/rest_api/v1/serializers/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ class StudioHomeSerializer(serializers.Serializer):
child=serializers.CharField(),
allow_empty=True
)
allowed_organizations_for_libraries = serializers.ListSerializer(
child=serializers.CharField(),
allow_empty=True
)
archived_courses = CourseCommonSerializer(required=False, many=True)
can_access_advanced_settings = serializers.BooleanField()
can_create_organizations = serializers.BooleanField()
Expand Down
1 change: 1 addition & 0 deletions cms/djangoapps/contentstore/rest_api/v1/views/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def get(self, request: Request):
"allow_to_create_new_org": true,
"allow_unicode_course_id": false,
"allowed_organizations": [],
"allowed_organizations_for_libraries": [],
"archived_courses": [],
"can_access_advanced_settings": true,
"can_create_organizations": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def setUp(self):
"allow_to_create_new_org": True,
"allow_unicode_course_id": False,
"allowed_organizations": [],
"allowed_organizations_for_libraries": [],
"archived_courses": [],
"can_access_advanced_settings": True,
"can_create_organizations": True,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import ddt
from django.contrib.auth.models import Group
from django.test import override_settings
from django.test.client import Client
from freezegun import freeze_time
from opaque_keys.edx.locator import LibraryLocatorV2, LibraryUsageLocatorV2
Expand Down Expand Up @@ -139,6 +140,63 @@ def test_library_validation(self):
'slug': ['Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or hyphens.'],
}

def test_library_org_validation(self):
"""
Staff users can create libraries in any existing or auto-created organization.
"""
assert Organization.objects.filter(short_name='auto-created-org').count() == 0
self._create_library(slug="auto-created-org-1", title="Library in an auto-created org", org='auto-created-org')
assert Organization.objects.filter(short_name='auto-created-org').count() == 1
self._create_library(slug="existing-org-1", title="Library in an existing org", org="CL-TEST")

@patch(
"openedx.core.djangoapps.content_libraries.views.user_can_create_organizations",
)
@patch(
"openedx.core.djangoapps.content_libraries.views.get_allowed_organizations_for_libraries",
)
@override_settings(ORGANIZATIONS_AUTOCREATE=False)
def test_library_org_no_autocreate(self, mock_get_allowed_organizations, mock_can_create_organizations):
"""
When org auto-creation is disabled, user must use one of their allowed orgs.
"""
mock_can_create_organizations.return_value = False
mock_get_allowed_organizations.return_value = ["CL-TEST"]
assert Organization.objects.filter(short_name='auto-created-org').count() == 0
response = self._create_library(
slug="auto-created-org-2",
org="auto-created-org",
title="Library in an auto-created org",
expect_response=400,
)
assert response == {
'org': "No such organization 'auto-created-org' found.",
}

Organization.objects.get_or_create(
short_name="not-allowed-org",
defaults={"name": "Content Libraries Test Org Membership"},
)
response = self._create_library(
slug="not-allowed-org",
org="not-allowed-org",
title="Library in an not-allowed org",
expect_response=400,
)
assert response == {
'org': "User not allowed to create libraries in 'not-allowed-org'.",
}
assert mock_can_create_organizations.call_count == 1
assert mock_get_allowed_organizations.call_count == 1

self._create_library(
slug="allowed-org-2",
org="CL-TEST",
title="Library in an allowed org",
)
assert mock_can_create_organizations.call_count == 2
assert mock_get_allowed_organizations.call_count == 2

@skip("This endpoint shouldn't support num_blocks and has_unpublished_*.")
@patch("openedx.core.djangoapps.content_libraries.views.LibraryRootView.pagination_class.page_size", new=2)
def test_list_library(self):
Expand Down
12 changes: 12 additions & 0 deletions openedx/core/djangoapps/content_libraries/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@
from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet

from cms.djangoapps.contentstore.views.course import (
get_allowed_organizations_for_libraries,
user_can_create_organizations,
)
from openedx.core.djangoapps.content_libraries import api, permissions
from openedx.core.djangoapps.content_libraries.serializers import (
ContentLibraryBlockImportTaskCreateSerializer,
Expand Down Expand Up @@ -269,6 +273,14 @@ def post(self, request):
raise ValidationError( # lint-amnesty, pylint: disable=raise-missing-from
detail={"org": f"No such organization '{org_name}' found."}
)
# Ensure the user is allowed to create libraries under this org
if not (
user_can_create_organizations(request.user) or
org_name in get_allowed_organizations_for_libraries(request.user)
):
raise ValidationError( # lint-amnesty, pylint: disable=raise-missing-from
detail={"org": f"User not allowed to create libraries in '{org_name}'."}
)
org = Organization.objects.get(short_name=org_name)

try:
Expand Down

0 comments on commit f1f40e0

Please sign in to comment.