def test_creation(self):
        """
        The user that creates a library should have instructor (admin) and staff permissions
        """
        # self.library has been auto-created by the staff user.
        self.assertTrue(has_studio_write_access(self.user, self.lib_key))
        self.assertTrue(has_studio_read_access(self.user, self.lib_key))
        # Make sure the user was actually assigned the instructor role and not just using is_staff superpowers:
        self.assertTrue(CourseInstructorRole(self.lib_key).has_user(self.user))

        # Now log out and ensure we are forbidden from creating a library:
        self.client.logout()
        self._assert_cannot_create_library(
            expected_code=302)  # 302 redirect to login expected

        # Now check that logged-in users without CourseCreator role cannot create libraries
        self._login_as_non_staff_user(logout_first=False)
        with patch.dict('django.conf.settings.FEATURES',
                        {'ENABLE_CREATOR_GROUP': True}):
            self._assert_cannot_create_library(
                expected_code=403)  # 403 user is not CourseCreator

        # Now check that logged-in users with CourseCreator role can create libraries
        add_user_with_status_granted(self.user, self.non_staff_user)
        with patch.dict('django.conf.settings.FEATURES',
                        {'ENABLE_CREATOR_GROUP': True}):
            lib_key2 = self._create_library(library="lib2",
                                            display_name="Test Library 2")
            library2 = modulestore().get_library(lib_key2)
            self.assertIsNotNone(library2)
Exemplo n.º 2
0
 def has_permission(self, request, view):
     """
     Check if the user has write access to studio.
     """
     user = request.user
     course_key_string = view.kwargs.get("course_id")
     course_key = validate_course_key(course_key_string)
     return has_studio_write_access(user, course_key)
Exemplo n.º 3
0
def _get_course_and_check_access(course_key, user, depth=0):
    """
    Internal method used to calculate and return the locator and
    course module for the view functions in this file.
    """
    if not has_studio_write_access(user, course_key):
        raise PermissionDenied()
    course_module = modulestore().get_course(course_key, depth=depth)
    return course_module
Exemplo n.º 4
0
def user_has_studio_write_access(*args, **kwargs):
    """
    Import and run `has_studio_write_access` from common modules.

    Used to check if someone saving deep linking content has the
    correct write permissions for a given.
    """
    # pylint: disable=import-error,import-outside-toplevel
    from common.djangoapps.student.auth import has_studio_write_access
    return has_studio_write_access(*args, **kwargs)
Exemplo n.º 5
0
    def patch(self, request: Request, course_id: str):
        """
        Update a course's advanced settings.

        **Example Request**

            PATCH /api/contentstore/v0/advanced_settings/{course_id} {
                "{setting_name}": {
                    "value": {setting_value}
                }
            }

        **PATCH Parameters**

        The data sent for a patch request should follow a similar format as
        is returned by a ``GET`` request. Multiple settings can be updated in
        a single request, however only the ``value`` field can be updated
        any other fields, if included, will be ignored.

        Here is an example request that updates the ``advanced_modules``
        available for the course, and enables the calculator tool:

        ```json
        {
            "advanced_modules": {
                "value": [
                    "poll",
                    "survey",
                    "drag-and-drop-v2",
                    "lti_consumer"
                ]
            },
            "show_calculator": {
                "value": true
            }
        }
        ```

        **Response Values**

        If the request is successful, an HTTP 200 "OK" response is returned,
        along with all the course's settings similar to a ``GET`` request.
        """
        course_key = CourseKey.from_string(course_id)
        if not has_studio_write_access(request.user, course_key):
            self.permission_denied(request)
        course_module = modulestore().get_course(course_key)
        updated_data = update_course_advanced_settings(course_module,
                                                       request.data,
                                                       request.user)
        return Response(updated_data)
Exemplo n.º 6
0
    def import_from_blockstore(self, dest_block, blockstore_block_ids):
        """
        Imports a block from a blockstore-based learning context (usually a
        content library) into modulestore, as a new child of dest_block.
        Any existing children of dest_block are replaced.

        This is only used by LibrarySourcedBlock. It should verify first that
        the number of block IDs is reasonable.
        """
        dest_key = dest_block.scope_ids.usage_id
        if not isinstance(dest_key, BlockUsageLocator):
            raise TypeError(
                "Destination {} should be a modulestore course.".format(
                    dest_key))
        if self.user_id is None:
            raise ValueError(
                "Cannot check user permissions - LibraryTools user_id is None")

        if len(set(blockstore_block_ids)) != len(blockstore_block_ids):
            # We don't support importing the exact same block twice because it would break the way we generate new IDs
            # for each block and then overwrite existing copies of blocks when re-importing the same blocks.
            raise ValueError(
                "One or more library component IDs is a duplicate.")

        dest_course_key = dest_key.context_key
        user = User.objects.get(id=self.user_id)
        if not has_studio_write_access(user, dest_course_key):
            raise PermissionDenied()

        # Read the source block; this will also confirm that user has permission to read it.
        # (This could be slow and use lots of memory, except for the fact that LibrarySourcedBlock which calls this
        # should be limiting the number of blocks to a reasonable limit. We load them all now instead of one at a
        # time in order to raise any errors before we start actually copying blocks over.)
        orig_blocks = [
            load_block(UsageKey.from_string(key), user)
            for key in blockstore_block_ids
        ]

        with self.store.bulk_operations(dest_course_key):
            child_ids_updated = set()

            for block in orig_blocks:
                new_block_id = self._import_block(block, dest_key)
                child_ids_updated.add(new_block_id)

            # Remove any existing children that are no longer used
            for old_child_id in set(dest_block.children) - child_ids_updated:
                self.store.delete_item(old_child_id, self.user_id)
            # If this was called from a handler, it will save dest_block at the end, so we must update
            # dest_block.children to avoid it saving the old value of children and deleting the new ones.
            dest_block.children = self.store.get_item(dest_key).children
Exemplo n.º 7
0
    def post(self, request: Request, course_id: str) -> Response:
        """
        Reorder tabs in a course.

        **Example Requests**

        Move course tabs:

            POST /api/contentstore/v0/tabs/{course_id}/reorder [
                {
                    "tab_id": "info"
                },
                {
                    "tab_id": "courseware"
                },
                {
                    "tab_locator": "block-v1:TstX+DemoX+Demo+type@static_tab+block@d26fcb0e93824fbfa5c9e5f100e2511a"
                },
                {
                    "tab_id": "wiki"
                },
                {
                    "tab_id": "discussion"
                },
                {
                    "tab_id": "progress"
                }
            ]


        **Response Values**

        If the request is successful, an HTTP 204 response is returned
        without any content.
        """
        course_key = CourseKey.from_string(course_id)
        if not has_studio_write_access(request.user, course_key):
            self.permission_denied(request)

        course_module = modulestore().get_course(course_key)
        tab_id_locators = TabIDLocatorSerializer(data=request.data, many=True)
        tab_id_locators.is_valid(raise_exception=True)
        reorder_tabs_handler(
            course_module,
            {"tabs": tab_id_locators.validated_data},
            request.user,
        )
        return Response(status=status.HTTP_204_NO_CONTENT)
Exemplo n.º 8
0
def library_blocks_view(library, user, response_format):
    """
    The main view of a course's content library.
    Shows all the XBlocks in the library, and allows adding/editing/deleting
    them.
    Can be called with response_format="json" to get a JSON-formatted list of
    the XBlocks in the library along with library metadata.

    Assumes that read permissions have been checked before calling this.
    """
    assert isinstance(library.location.library_key, LibraryLocator)
    assert isinstance(library.location, LibraryUsageLocator)

    children = library.children
    if response_format == "json":
        # The JSON response for this request is short and sweet:
        prev_version = library.runtime.course_entry.structure[
            'previous_version']
        return JsonResponse({
            "display_name":
            library.display_name,
            "library_id":
            str(library.location.library_key),
            "version":
            str(library.runtime.course_entry.course_key.version_guid),
            "previous_version":
            str(prev_version) if prev_version else None,
            "blocks": [str(x) for x in children],
        })

    can_edit = has_studio_write_access(user, library.location.library_key)

    xblock_info = create_xblock_info(library,
                                     include_ancestor_info=False,
                                     graders=[])
    component_templates = get_component_templates(
        library, library=True) if can_edit else []

    return render_to_response(
        'library.html', {
            'can_edit': can_edit,
            'context_library': library,
            'component_templates': component_templates,
            'xblock_info': xblock_info,
            'templates': CONTAINER_TEMPLATES,
        })
Exemplo n.º 9
0
    def post(self, request: Request, course_id: str) -> Response:
        """
        Change visibility of tabs in a course.

        **Example Requests**

        You can provide either a tab_id or a tab_location.

        Hide a course tab using ``tab_id``:

            POST /api/contentstore/v0/tabs/{course_id}/settings/?tab_id={tab_id} {
                "is_hidden": true
            }

        Hide a course tab using ``tab_location``

            POST /api/contentstore/v0/tabs/{course_id}/settings/?tab_location={tab_location} {
                "is_hidden": true
            }

        **Response Values**

        If the request is successful, an HTTP 204 response is returned
        without any content.
        """
        course_key = CourseKey.from_string(course_id)
        if not has_studio_write_access(request.user, course_key):
            self.permission_denied(request)

        tab_id_locator = TabIDLocatorSerializer(data=request.query_params)
        tab_id_locator.is_valid(raise_exception=True)

        course_module = modulestore().get_course(course_key)
        serializer = CourseTabUpdateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        edit_tab_handler(
            course_module,
            {
                "tab_id_locator": tab_id_locator.data,
                **serializer.data,
            },
            request.user,
        )
        return Response(status=status.HTTP_204_NO_CONTENT)
Exemplo n.º 10
0
def transcript_delete_handler(request, course_key_string, edx_video_id,
                              language_code):
    """
    View to delete a transcript file.

    Arguments:
        request: A WSGI request object
        course_key_string: Course key identifying a course.
        edx_video_id: edX video identifier whose transcript need to be deleted.
        language_code: transcript's language code.

    Returns
        - A 404 if the user does not have required permisions
        - A 200 if transcript is deleted without any error(s)
    """
    # Check whether the feature is available for this course.
    course_key = CourseKey.from_string(course_key_string)
    # User needs to have studio write access for this course.
    if not has_studio_write_access(request.user, course_key):
        return HttpResponseNotFound()

    delete_video_transcript(video_id=edx_video_id, language_code=language_code)

    return JsonResponse(status=200)
Exemplo n.º 11
0
 def test_no_staff_write_access(self):
     """
     Test that course staff have no write access
     """
     assert not has_studio_write_access(self.staff, self.ccx_course_key)
Exemplo n.º 12
0
 def test_no_global_admin_write_access(self):
     """
     Test that global admins have no write access
     """
     assert not has_studio_write_access(self.global_admin,
                                        self.ccx_course_key)