Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
7303966c
Commit
7303966c
authored
Dec 31, 2014
by
Braden MacDonald
Committed by
E. Kolpakov
Jan 12, 2015
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Check permissions when updating LibraryContent blocks
parent
95c81256
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
80 additions
and
6 deletions
+80
-6
cms/djangoapps/contentstore/tests/test_libraries.py
+36
-2
cms/djangoapps/contentstore/views/preview.py
+24
-0
common/lib/xmodule/xmodule/library_content_module.py
+3
-1
common/lib/xmodule/xmodule/library_tools.py
+6
-1
common/test/acceptance/tests/studio/test_studio_library_container.py
+11
-2
No files found.
cms/djangoapps/contentstore/tests/test_libraries.py
View file @
7303966c
...
@@ -67,7 +67,7 @@ class LibraryTestCase(ModuleStoreTestCase):
...
@@ -67,7 +67,7 @@ class LibraryTestCase(ModuleStoreTestCase):
**
(
other_settings
or
{})
**
(
other_settings
or
{})
)
)
def
_refresh_children
(
self
,
lib_content_block
):
def
_refresh_children
(
self
,
lib_content_block
,
status_code_expected
=
200
):
"""
"""
Helper method: Uses the REST API to call the 'refresh_children' handler
Helper method: Uses the REST API to call the 'refresh_children' handler
of a LibraryContent block
of a LibraryContent block
...
@@ -76,7 +76,7 @@ class LibraryTestCase(ModuleStoreTestCase):
...
@@ -76,7 +76,7 @@ class LibraryTestCase(ModuleStoreTestCase):
lib_content_block
.
runtime
.
_services
[
'user'
]
=
Mock
(
user_id
=
self
.
user
.
id
)
# pylint: disable=protected-access
lib_content_block
.
runtime
.
_services
[
'user'
]
=
Mock
(
user_id
=
self
.
user
.
id
)
# pylint: disable=protected-access
handler_url
=
reverse_usage_url
(
'component_handler'
,
lib_content_block
.
location
,
kwargs
=
{
'handler'
:
'refresh_children'
})
handler_url
=
reverse_usage_url
(
'component_handler'
,
lib_content_block
.
location
,
kwargs
=
{
'handler'
:
'refresh_children'
})
response
=
self
.
client
.
ajax_post
(
handler_url
)
response
=
self
.
client
.
ajax_post
(
handler_url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
status_code_expected
)
return
modulestore
()
.
get_item
(
lib_content_block
.
location
)
return
modulestore
()
.
get_item
(
lib_content_block
.
location
)
def
_bind_module
(
self
,
descriptor
,
user
=
None
):
def
_bind_module
(
self
,
descriptor
,
user
=
None
):
...
@@ -556,3 +556,37 @@ class TestLibraryAccess(LibraryTestCase):
...
@@ -556,3 +556,37 @@ class TestLibraryAccess(LibraryTestCase):
self
.
assertIn
(
response
.
status_code
,
(
200
,
403
))
# 400 would be ambiguous
self
.
assertIn
(
response
.
status_code
,
(
200
,
403
))
# 400 would be ambiguous
duplicate_action_allowed
=
(
response
.
status_code
==
200
)
duplicate_action_allowed
=
(
response
.
status_code
==
200
)
self
.
assertEqual
(
duplicate_action_allowed
,
expected_result
)
self
.
assertEqual
(
duplicate_action_allowed
,
expected_result
)
@ddt.data
(
(
LibraryUserRole
,
CourseStaffRole
,
True
),
(
CourseStaffRole
,
CourseStaffRole
,
True
),
(
None
,
CourseStaffRole
,
False
),
(
LibraryUserRole
,
None
,
False
),
)
@ddt.unpack
def
test_refresh_library_content_permissions
(
self
,
library_role
,
course_role
,
expected_result
):
"""
Test that the LibraryContent block's 'refresh_children' handler will correctly
handle permissions and allow/refuse when updating its content with the latest
version of a library. We try updating from a library with (write, read, or no)
access to a course with (write or no) access.
"""
# As staff user, add a block to self.library:
ItemFactory
.
create
(
category
=
"html"
,
parent_location
=
self
.
library
.
location
,
user_id
=
self
.
user
.
id
,
publish_item
=
False
)
# And create a course:
with
modulestore
()
.
default_store
(
ModuleStoreEnum
.
Type
.
split
):
course
=
CourseFactory
.
create
()
self
.
_login_as_non_staff_user
()
# Assign roles:
if
library_role
:
library_role
(
self
.
lib_key
)
.
add_users
(
self
.
non_staff_user
)
if
course_role
:
course_role
(
course
.
location
.
course_key
)
.
add_users
(
self
.
non_staff_user
)
# Try updating our library content block:
lc_block
=
self
.
_add_library_content_block
(
course
,
self
.
lib_key
)
self
.
_bind_module
(
lc_block
,
user
=
self
.
non_staff_user
)
# We must use the CMS's module system in order to get permissions checks.
lc_block
=
self
.
_refresh_children
(
lc_block
,
status_code_expected
=
200
if
expected_result
else
403
)
self
.
assertEqual
(
len
(
lc_block
.
children
),
1
if
expected_result
else
0
)
cms/djangoapps/contentstore/views/preview.py
View file @
7303966c
...
@@ -22,6 +22,7 @@ from xblock.runtime import KvsFieldData
...
@@ -22,6 +22,7 @@ from xblock.runtime import KvsFieldData
from
xblock.django.request
import
webob_to_django_response
,
django_to_webob_request
from
xblock.django.request
import
webob_to_django_response
,
django_to_webob_request
from
xblock.exceptions
import
NoSuchHandlerError
from
xblock.exceptions
import
NoSuchHandlerError
from
xblock.fragment
import
Fragment
from
xblock.fragment
import
Fragment
from
student.auth
import
has_studio_read_access
,
has_studio_write_access
from
lms.djangoapps.lms_xblock.field_data
import
LmsFieldData
from
lms.djangoapps.lms_xblock.field_data
import
LmsFieldData
from
cms.lib.xblock.field_data
import
CmsFieldData
from
cms.lib.xblock.field_data
import
CmsFieldData
...
@@ -124,6 +125,28 @@ class StudioUserService(object):
...
@@ -124,6 +125,28 @@ class StudioUserService(object):
return
self
.
_request
.
user
.
id
return
self
.
_request
.
user
.
id
class
StudioPermissionsService
(
object
):
"""
Service that can provide information about a user's permissions.
Deprecated. To be replaced by a more general authorization service.
Only used by LibraryContentDescriptor (and library_tools.py).
"""
def
__init__
(
self
,
request
):
super
(
StudioPermissionsService
,
self
)
.
__init__
()
self
.
_request
=
request
def
can_read
(
self
,
course_key
):
""" Does the user have read access to the given course/library? """
return
has_studio_read_access
(
self
.
_request
.
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
.
_request
.
user
,
course_key
)
def
_preview_module_system
(
request
,
descriptor
,
field_data
):
def
_preview_module_system
(
request
,
descriptor
,
field_data
):
"""
"""
Returns a ModuleSystem for the specified descriptor that is specialized for
Returns a ModuleSystem for the specified descriptor that is specialized for
...
@@ -153,6 +176,7 @@ def _preview_module_system(request, descriptor, field_data):
...
@@ -153,6 +176,7 @@ def _preview_module_system(request, descriptor, field_data):
]
]
descriptor
.
runtime
.
_services
[
'user'
]
=
StudioUserService
(
request
)
# pylint: disable=protected-access
descriptor
.
runtime
.
_services
[
'user'
]
=
StudioUserService
(
request
)
# pylint: disable=protected-access
descriptor
.
runtime
.
_services
[
'studio_user_permissions'
]
=
StudioPermissionsService
(
request
)
# pylint: disable=protected-access
return
PreviewModuleSystem
(
return
PreviewModuleSystem
(
static_url
=
settings
.
STATIC_URL
,
static_url
=
settings
.
STATIC_URL
,
...
...
common/lib/xmodule/xmodule/library_content_module.py
View file @
7303966c
...
@@ -279,6 +279,7 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
...
@@ -279,6 +279,7 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
@XBlock.wants
(
'user'
)
@XBlock.wants
(
'user'
)
@XBlock.wants
(
'library_tools'
)
# Only needed in studio
@XBlock.wants
(
'library_tools'
)
# Only needed in studio
@XBlock.wants
(
'studio_user_permissions'
)
# Only available in studio
class
LibraryContentDescriptor
(
LibraryContentFields
,
MakoModuleDescriptor
,
XmlDescriptor
,
StudioEditableDescriptor
):
class
LibraryContentDescriptor
(
LibraryContentFields
,
MakoModuleDescriptor
,
XmlDescriptor
,
StudioEditableDescriptor
):
"""
"""
Descriptor class for LibraryContentModule XBlock.
Descriptor class for LibraryContentModule XBlock.
...
@@ -307,8 +308,9 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe
...
@@ -307,8 +308,9 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe
"""
"""
lib_tools
=
self
.
runtime
.
service
(
self
,
'library_tools'
)
lib_tools
=
self
.
runtime
.
service
(
self
,
'library_tools'
)
user_service
=
self
.
runtime
.
service
(
self
,
'user'
)
user_service
=
self
.
runtime
.
service
(
self
,
'user'
)
user_perms
=
self
.
runtime
.
service
(
self
,
'studio_user_permissions'
)
user_id
=
user_service
.
user_id
if
user_service
else
None
# May be None when creating bok choy test fixtures
user_id
=
user_service
.
user_id
if
user_service
else
None
# May be None when creating bok choy test fixtures
lib_tools
.
update_children
(
self
,
user_id
,
update_db
)
lib_tools
.
update_children
(
self
,
user_id
,
u
ser_perms
,
u
pdate_db
)
return
Response
()
return
Response
()
def
validate
(
self
):
def
validate
(
self
):
...
...
common/lib/xmodule/xmodule/library_tools.py
View file @
7303966c
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
XBlock runtime services for LibraryContentModule
XBlock runtime services for LibraryContentModule
"""
"""
import
hashlib
import
hashlib
from
django.core.exceptions
import
PermissionDenied
from
opaque_keys.edx.locator
import
LibraryLocator
from
opaque_keys.edx.locator
import
LibraryLocator
from
xblock.fields
import
Scope
from
xblock.fields
import
Scope
from
xmodule.library_content_module
import
LibraryVersionReference
from
xmodule.library_content_module
import
LibraryVersionReference
...
@@ -44,7 +45,7 @@ class LibraryToolsService(object):
...
@@ -44,7 +45,7 @@ class LibraryToolsService(object):
return
library
.
location
.
library_key
.
version_guid
return
library
.
location
.
library_key
.
version_guid
return
None
return
None
def
update_children
(
self
,
dest_block
,
user_id
,
update_db
=
True
):
def
update_children
(
self
,
dest_block
,
user_id
,
u
ser_perms
=
None
,
u
pdate_db
=
True
):
"""
"""
This method is to be used when any of the libraries that a LibraryContentModule
This method is to be used when any of the libraries that a LibraryContentModule
references have been updated. It will re-fetch all matching blocks from
references have been updated. It will re-fetch all matching blocks from
...
@@ -62,6 +63,8 @@ class LibraryToolsService(object):
...
@@ -62,6 +63,8 @@ class LibraryToolsService(object):
anyways. Otherwise, orphaned blocks may be created.
anyways. Otherwise, orphaned blocks may be created.
"""
"""
root_children
=
[]
root_children
=
[]
if
user_perms
and
not
user_perms
.
can_write
(
dest_block
.
location
.
course_key
):
raise
PermissionDenied
()
with
self
.
store
.
bulk_operations
(
dest_block
.
location
.
course_key
):
with
self
.
store
.
bulk_operations
(
dest_block
.
location
.
course_key
):
# Currently, ALL children are essentially deleted and then re-added
# Currently, ALL children are essentially deleted and then re-added
...
@@ -76,6 +79,8 @@ class LibraryToolsService(object):
...
@@ -76,6 +79,8 @@ class LibraryToolsService(object):
library
=
self
.
_get_library
(
library_key
)
library
=
self
.
_get_library
(
library_key
)
if
library
is
None
:
if
library
is
None
:
raise
ValueError
(
"Required library not found."
)
raise
ValueError
(
"Required library not found."
)
if
user_perms
and
not
user_perms
.
can_read
(
library_key
):
raise
PermissionDenied
()
libraries
.
append
((
library_key
,
library
))
libraries
.
append
((
library_key
,
library
))
# Next, delete all our existing children to avoid block_id conflicts when we add them:
# Next, delete all our existing children to avoid block_id conflicts when we add them:
...
...
common/test/acceptance/tests/studio/test_studio_library_container.py
View file @
7303966c
...
@@ -2,8 +2,11 @@
...
@@ -2,8 +2,11 @@
Acceptance tests for Library Content in LMS
Acceptance tests for Library Content in LMS
"""
"""
import
ddt
import
ddt
from
.base_studio_test
import
StudioLibraryTest
,
ContainerBase
from
.base_studio_test
import
StudioLibraryTest
from
...fixtures.course
import
CourseFixture
from
..helpers
import
UniqueCourseTest
from
...pages.studio.library
import
StudioLibraryContentXBlockEditModal
,
StudioLibraryContainerXBlockWrapper
from
...pages.studio.library
import
StudioLibraryContentXBlockEditModal
,
StudioLibraryContainerXBlockWrapper
from
...pages.studio.overview
import
CourseOutlinePage
from
...fixtures.course
import
XBlockFixtureDesc
from
...fixtures.course
import
XBlockFixtureDesc
SECTION_NAME
=
'Test Section'
SECTION_NAME
=
'Test Section'
...
@@ -12,7 +15,7 @@ UNIT_NAME = 'Test Unit'
...
@@ -12,7 +15,7 @@ UNIT_NAME = 'Test Unit'
@ddt.ddt
@ddt.ddt
class
StudioLibraryContainerTest
(
ContainerBase
,
StudioLibrary
Test
):
class
StudioLibraryContainerTest
(
StudioLibraryTest
,
UniqueCourse
Test
):
"""
"""
Test Library Content block in LMS
Test Library Content block in LMS
"""
"""
...
@@ -21,6 +24,12 @@ class StudioLibraryContainerTest(ContainerBase, StudioLibraryTest):
...
@@ -21,6 +24,12 @@ class StudioLibraryContainerTest(ContainerBase, StudioLibraryTest):
Install library with some content and a course using fixtures
Install library with some content and a course using fixtures
"""
"""
super
(
StudioLibraryContainerTest
,
self
)
.
setUp
()
super
(
StudioLibraryContainerTest
,
self
)
.
setUp
()
# Also create a course:
self
.
course_fixture
=
CourseFixture
(
self
.
course_info
[
'org'
],
self
.
course_info
[
'number'
],
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
])
self
.
populate_course_fixture
(
self
.
course_fixture
)
self
.
course_fixture
.
install
()
self
.
outline
=
CourseOutlinePage
(
self
.
browser
,
self
.
course_info
[
'org'
],
self
.
course_info
[
'number'
],
self
.
course_info
[
'run'
])
self
.
outline
.
visit
()
self
.
outline
.
visit
()
subsection
=
self
.
outline
.
section
(
SECTION_NAME
)
.
subsection
(
SUBSECTION_NAME
)
subsection
=
self
.
outline
.
section
(
SECTION_NAME
)
.
subsection
(
SUBSECTION_NAME
)
self
.
unit_page
=
subsection
.
toggle_expand
()
.
unit
(
UNIT_NAME
)
.
go_to
()
self
.
unit_page
=
subsection
.
toggle_expand
()
.
unit
(
UNIT_NAME
)
.
go_to
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment