Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 2 additions & 22 deletions cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.edxmako.services import MakoService
from common.djangoapps.student import auth
from common.djangoapps.student.auth import STUDIO_EDIT_ROLES, has_studio_read_access, has_studio_write_access
from common.djangoapps.student.auth import STUDIO_EDIT_ROLES, has_studio_write_access
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole, GlobalStaff
from common.djangoapps.track import contexts
Expand All @@ -71,6 +71,7 @@
from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService
from openedx.core import toggles as core_toggles
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.content.services import StudioPermissionsService
from openedx.core.djangoapps.content_libraries.api import get_container
from openedx.core.djangoapps.content_tagging.toggles import is_tagging_feature_disabled
from openedx.core.djangoapps.credit.api import get_credit_requirements, is_credit_course
Expand Down Expand Up @@ -2243,27 +2244,6 @@ def get_group_configurations_context(course, store):
return context


class StudioPermissionsService:
"""
Service that can provide information about a user's permissions.

Deprecated. To be replaced by a more general authorization service.

Only used by LegacyLibraryContentBlock (and library_tools.py).
"""

def __init__(self, user):
self._user = user

def can_read(self, course_key):
""" Does the user have read access to the given course/library? """
return has_studio_read_access(self._user, course_key)

def can_write(self, course_key):
""" Does the user have read access to the given course/library? """
return has_studio_write_access(self._user, course_key)


def track_course_update_event(course_key, user, course_update_content=None):
"""
Track course update event
Expand Down
3 changes: 2 additions & 1 deletion cms/djangoapps/contentstore/views/preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from common.djangoapps.student.models import anonymous_id_for_user
from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
from openedx.core.djangoapps.content.services import StudioPermissionsService
from openedx.core.djangoapps.discussions.services import DiscussionConfigService
from openedx.core.djangoapps.video_config.services import VideoConfigService
from openedx.core.lib.cache_utils import CacheService
Expand All @@ -44,7 +45,7 @@
from xmodule.util.sandboxing import SandboxService
from xmodule.x_module import AUTHOR_VIEW, PREVIEW_VIEWS, STUDENT_VIEW, XModuleMixin

from ..utils import StudioPermissionsService, get_visibility_partition_info
from ..utils import get_visibility_partition_info
from .access import get_user_role
from .session_kv_store import SessionKeyValueStore

Expand Down
39 changes: 39 additions & 0 deletions openedx/core/djangoapps/content/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""
Services for learning content
"""
from __future__ import annotations

from opaque_keys.edx.locator import LibraryLocatorV2
from openedx_authz import api as authz_api
from openedx_authz.constants.permissions import EDIT_LIBRARY_CONTENT, VIEW_LIBRARY

from common.djangoapps.student.auth import has_studio_read_access, has_studio_write_access


class StudioPermissionsService:
"""
Service that can provide information about a user's permissions.
"""

def __init__(self, user):
self._user = user

def can_read(self, context_key):
""" Does the user have read access to the given course/library? """
if isinstance(context_key, LibraryLocatorV2):
return authz_api.is_user_allowed(
self._user,
VIEW_LIBRARY.identifier,
str(context_key),
)
return has_studio_read_access(self._user, context_key)

def can_write(self, context_key):
""" Does the user have write access to the given course/library? """
if isinstance(context_key, LibraryLocatorV2):
return authz_api.is_user_allowed(
self._user,
EDIT_LIBRARY_CONTENT.identifier,
str(context_key),
)
return has_studio_write_access(self._user, context_key)
Empty file.
99 changes: 99 additions & 0 deletions openedx/core/djangoapps/content/tests/test_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""
Tests for content XBlock Services
"""
from django.contrib.auth import get_user_model
from django.test import TestCase
from opaque_keys.edx.locator import LibraryLocatorV2
from organizations.models import Organization

from common.djangoapps.student.auth import update_org_role
from common.djangoapps.student.roles import OrgStaffRole
from common.djangoapps.student.tests.factories import UserFactory
from openedx.core.djangoapps.content.services import StudioPermissionsService
from openedx.core.djangoapps.content_libraries.api import (
AccessLevel,
ContentLibraryMetadata,
assign_library_role_to_user,
create_library,
)
from xmodule.modulestore.tests.factories import CourseFactory

User = get_user_model()


class StudioPermissionsServiceTestCase(TestCase):
"""
Test the studio permissions service.
"""

def setUp(self) -> None:
self.org = Organization.objects.create(name="Organization A", short_name="orgA")
self.staff = UserFactory.create(
is_staff=True,
)

def _create_privileged_org_user(self) -> User:
user = UserFactory.create()
update_org_role(self.staff, OrgStaffRole, user, [self.org.short_name])
return user

def test_user_can_read_course(self) -> None:
course = CourseFactory.create(org=self.org)
user = self._create_privileged_org_user()
service = StudioPermissionsService(user=user)
assert service.can_read(course.key)

def test_user_can_write_course(self) -> None:
course = CourseFactory.create(org=self.org)
user = self._create_privileged_org_user()
service = StudioPermissionsService(user=user)
assert service.can_write(course.key)

def test_user_cannot_read_course(self) -> None:
course = CourseFactory.create(org=self.org)
user = UserFactory.create()
service = StudioPermissionsService(user=user)
assert not service.can_read(course.key)

def test_user_cannot_write_course(self) -> None:
course = CourseFactory.create(org=self.org)
user = UserFactory.create()
service = StudioPermissionsService(user=user)
assert not service.can_write(course.key)

def _create_library(self) -> ContentLibraryMetadata:
return create_library(
org=self.org,
slug="lib",
title="Library Org",
description="This is a library from Org",
)

def _create_privileged_library_user(self, library_key: LibraryLocatorV2) -> User:
user = UserFactory.create()
assign_library_role_to_user(library_key, user, AccessLevel.AUTHOR_LEVEL)
return user

def test_user_can_read_library(self) -> None:
library = self._create_library()
user = self._create_privileged_library_user(library.key)
service = StudioPermissionsService(user=user)
assert service.can_read(library.key)

def test_user_can_write_library(self) -> None:
library = self._create_library()
user = self._create_privileged_library_user(library.key)
service = StudioPermissionsService(user=user)
assert service.can_write(library.key)

def test_user_cannot_read_library(self) -> None:
library = self._create_library()
user = UserFactory.create()
service = StudioPermissionsService(user=user)
assert not service.can_read(library.key)

def test_user_cannot_write_library(self) -> None:
library = self._create_library()
user = UserFactory.create()
service = StudioPermissionsService(user=user)
assert not service.can_write(library.key)
3 changes: 3 additions & 0 deletions openedx/core/djangoapps/xblock/runtime/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ def service(self, block: XBlock, service_name: str):
return DiscussionConfigService()
elif service_name == 'xqueue':
return XQueueService(block)
elif service_name == 'studio_user_permissions':
from openedx.core.djangoapps.content.services import StudioPermissionsService
return StudioPermissionsService(self.user)

# Otherwise, fall back to the base implementation which loads services
# defined in the constructor:
Expand Down
Loading