Skip to content

Commit b96a3bf

Browse files
authored
fix: allow_to_create_new_org checks org autocreate [FC-0076] (#36094)
Updates the StudioHome API's allow_to_create_new_org to require both organization-creation permissions and ORGANIZATION_AUTOCREATE to be enabled. It also adds the list of "allowed organizations for libraries" to the Studio Home API so that the Authoring MFE can use it.
1 parent fa0ead4 commit b96a3bf

File tree

5 files changed

+95
-2
lines changed

5 files changed

+95
-2
lines changed

cms/djangoapps/contentstore/rest_api/v1/serializers/home.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ class StudioHomeSerializer(serializers.Serializer):
5050
child=serializers.CharField(),
5151
allow_empty=True
5252
)
53+
allowed_organizations_for_libraries = serializers.ListSerializer(
54+
child=serializers.CharField(),
55+
allow_empty=True
56+
)
5357
archived_courses = CourseCommonSerializer(required=False, many=True)
5458
can_access_advanced_settings = serializers.BooleanField()
5559
can_create_organizations = serializers.BooleanField()

cms/djangoapps/contentstore/rest_api/v1/views/home.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from rest_framework.request import Request
66
from rest_framework.response import Response
77
from rest_framework.views import APIView
8+
from organizations import api as org_api
89
from openedx.core.lib.api.view_utils import view_auth_classes
910

1011
from ....utils import get_home_context, get_course_context, get_library_context
@@ -51,6 +52,7 @@ def get(self, request: Request):
5152
"allow_to_create_new_org": true,
5253
"allow_unicode_course_id": false,
5354
"allowed_organizations": [],
55+
"allowed_organizations_for_libraries": [],
5456
"archived_courses": [],
5557
"can_access_advanced_settings": true,
5658
"can_create_organizations": true,
@@ -79,7 +81,12 @@ def get(self, request: Request):
7981

8082
home_context = get_home_context(request, True)
8183
home_context.update({
82-
'allow_to_create_new_org': settings.FEATURES.get('ENABLE_CREATOR_GROUP', True) and request.user.is_staff,
84+
# 'allow_to_create_new_org' is actually about auto-creating organizations
85+
# (e.g. when creating a course or library), so we add an additional test.
86+
'allow_to_create_new_org': (
87+
home_context['can_create_organizations'] and
88+
org_api.is_autocreate_enabled()
89+
),
8390
'studio_name': settings.STUDIO_NAME,
8491
'studio_short_name': settings.STUDIO_SHORT_NAME,
8592
'studio_request_email': settings.FEATURES.get('STUDIO_REQUEST_EMAIL', ''),

cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ def setUp(self):
3232
self.url = reverse("cms.djangoapps.contentstore:v1:home")
3333
self.expected_response = {
3434
"allow_course_reruns": True,
35-
"allow_to_create_new_org": False,
35+
"allow_to_create_new_org": True,
3636
"allow_unicode_course_id": False,
3737
"allowed_organizations": [],
38+
"allowed_organizations_for_libraries": [],
3839
"archived_courses": [],
3940
"can_access_advanced_settings": True,
4041
"can_create_organizations": True,
@@ -78,6 +79,17 @@ def test_home_page_studio_with_meilisearch_enabled(self):
7879
self.assertEqual(response.status_code, status.HTTP_200_OK)
7980
self.assertDictEqual(expected_response, response.data)
8081

82+
@override_settings(ORGANIZATIONS_AUTOCREATE=False)
83+
def test_home_page_studio_with_org_autocreate_disabled(self):
84+
"""Check response content when Organization autocreate is disabled"""
85+
response = self.client.get(self.url)
86+
87+
expected_response = self.expected_response
88+
expected_response["allow_to_create_new_org"] = False
89+
90+
self.assertEqual(response.status_code, status.HTTP_200_OK)
91+
self.assertDictEqual(expected_response, response.data)
92+
8193
def test_taxonomy_list_link(self):
8294
response = self.client.get(self.url)
8395
self.assertTrue(response.data['taxonomies_enabled'])

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

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

143+
def test_library_org_validation(self):
144+
"""
145+
Staff users can create libraries in any existing or auto-created organization.
146+
"""
147+
assert Organization.objects.filter(short_name='auto-created-org').count() == 0
148+
self._create_library(slug="auto-created-org-1", title="Library in an auto-created org", org='auto-created-org')
149+
assert Organization.objects.filter(short_name='auto-created-org').count() == 1
150+
self._create_library(slug="existing-org-1", title="Library in an existing org", org="CL-TEST")
151+
152+
@patch(
153+
"openedx.core.djangoapps.content_libraries.views.user_can_create_organizations",
154+
)
155+
@patch(
156+
"openedx.core.djangoapps.content_libraries.views.get_allowed_organizations_for_libraries",
157+
)
158+
@override_settings(ORGANIZATIONS_AUTOCREATE=False)
159+
def test_library_org_no_autocreate(self, mock_get_allowed_organizations, mock_can_create_organizations):
160+
"""
161+
When org auto-creation is disabled, user must use one of their allowed orgs.
162+
"""
163+
mock_can_create_organizations.return_value = False
164+
mock_get_allowed_organizations.return_value = ["CL-TEST"]
165+
assert Organization.objects.filter(short_name='auto-created-org').count() == 0
166+
response = self._create_library(
167+
slug="auto-created-org-2",
168+
org="auto-created-org",
169+
title="Library in an auto-created org",
170+
expect_response=400,
171+
)
172+
assert response == {
173+
'org': "No such organization 'auto-created-org' found.",
174+
}
175+
176+
Organization.objects.get_or_create(
177+
short_name="not-allowed-org",
178+
defaults={"name": "Content Libraries Test Org Membership"},
179+
)
180+
response = self._create_library(
181+
slug="not-allowed-org",
182+
org="not-allowed-org",
183+
title="Library in an not-allowed org",
184+
expect_response=400,
185+
)
186+
assert response == {
187+
'org': "User not allowed to create libraries in 'not-allowed-org'.",
188+
}
189+
assert mock_can_create_organizations.call_count == 1
190+
assert mock_get_allowed_organizations.call_count == 1
191+
192+
self._create_library(
193+
slug="allowed-org-2",
194+
org="CL-TEST",
195+
title="Library in an allowed org",
196+
)
197+
assert mock_can_create_organizations.call_count == 2
198+
assert mock_get_allowed_organizations.call_count == 2
199+
142200
@skip("This endpoint shouldn't support num_blocks and has_unpublished_*.")
143201
@patch("openedx.core.djangoapps.content_libraries.views.LibraryRootView.pagination_class.page_size", new=2)
144202
def test_list_library(self):

openedx/core/djangoapps/content_libraries/views.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@
9999
from rest_framework.views import APIView
100100
from rest_framework.viewsets import GenericViewSet
101101

102+
from cms.djangoapps.contentstore.views.course import (
103+
get_allowed_organizations_for_libraries,
104+
user_can_create_organizations,
105+
)
102106
from openedx.core.djangoapps.content_libraries import api, permissions
103107
from openedx.core.djangoapps.content_libraries.serializers import (
104108
ContentLibraryBlockImportTaskCreateSerializer,
@@ -269,6 +273,14 @@ def post(self, request):
269273
raise ValidationError( # lint-amnesty, pylint: disable=raise-missing-from
270274
detail={"org": f"No such organization '{org_name}' found."}
271275
)
276+
# Ensure the user is allowed to create libraries under this org
277+
if not (
278+
user_can_create_organizations(request.user) or
279+
org_name in get_allowed_organizations_for_libraries(request.user)
280+
):
281+
raise ValidationError( # lint-amnesty, pylint: disable=raise-missing-from
282+
detail={"org": f"User not allowed to create libraries in '{org_name}'."}
283+
)
272284
org = Organization.objects.get(short_name=org_name)
273285

274286
try:

0 commit comments

Comments
 (0)