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
205ded99
Commit
205ded99
authored
Nov 30, 2016
by
Toby Lawrence
Committed by
GitHub
Nov 30, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #14068 from edx/release
Merge release back into master
parents
989d01b5
68714c58
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
126 additions
and
59 deletions
+126
-59
common/djangoapps/student/auth.py
+14
-1
common/djangoapps/student/tests/test_authz.py
+41
-1
common/lib/capa/capa/capa_problem.py
+38
-35
common/lib/capa/capa/responsetypes.py
+23
-22
openedx/core/lib/celery/routers.py
+10
-0
No files found.
common/djangoapps/student/auth.py
View file @
205ded99
...
...
@@ -7,6 +7,7 @@ to decide whether to check course creator role, and other such functions.
from
django.core.exceptions
import
PermissionDenied
from
django.conf
import
settings
from
opaque_keys.edx.locator
import
LibraryLocator
from
ccx_keys.locator
import
CCXLocator
,
CCXBlockUsageLocator
from
student.roles
import
GlobalStaff
,
CourseCreatorRole
,
CourseStaffRole
,
CourseInstructorRole
,
CourseRole
,
\
CourseBetaTesterRole
,
OrgInstructorRole
,
OrgStaffRole
,
LibraryUserRole
,
OrgLibraryUserRole
...
...
@@ -17,9 +18,18 @@ STUDIO_EDIT_ROLES = 8
STUDIO_VIEW_USERS
=
4
STUDIO_EDIT_CONTENT
=
2
STUDIO_VIEW_CONTENT
=
1
STUDIO_NO_PERMISSIONS
=
0
# In addition to the above, one is always allowed to "demote" oneself to a lower role within a course, or remove oneself
def
is_ccx_course
(
course_key
):
"""
Check whether the course locator maps to a CCX course; this is important
because we don't allow access to CCX courses in Studio.
"""
return
isinstance
(
course_key
,
CCXLocator
)
or
isinstance
(
course_key
,
CCXBlockUsageLocator
)
def
user_has_role
(
user
,
role
):
"""
Check whether this user has access to this role (either direct or implied)
...
...
@@ -60,6 +70,9 @@ def get_user_permissions(user, course_key, org=None):
course_key
=
course_key
.
for_branch
(
None
)
else
:
assert
course_key
is
None
# No one has studio permissions for CCX courses
if
is_ccx_course
(
course_key
):
return
STUDIO_NO_PERMISSIONS
all_perms
=
STUDIO_EDIT_ROLES
|
STUDIO_VIEW_USERS
|
STUDIO_EDIT_CONTENT
|
STUDIO_VIEW_CONTENT
# global staff, org instructors, and course instructors have all permissions:
if
GlobalStaff
()
.
has_user
(
user
)
or
OrgInstructorRole
(
org
=
org
)
.
has_user
(
user
):
...
...
@@ -73,7 +86,7 @@ def get_user_permissions(user, course_key, org=None):
if
course_key
and
isinstance
(
course_key
,
LibraryLocator
):
if
OrgLibraryUserRole
(
org
=
org
)
.
has_user
(
user
)
or
user_has_role
(
user
,
LibraryUserRole
(
course_key
)):
return
STUDIO_VIEW_USERS
|
STUDIO_VIEW_CONTENT
return
0
return
STUDIO_NO_PERMISSIONS
def
has_studio_write_access
(
user
,
course_key
):
...
...
common/djangoapps/student/tests/test_authz.py
View file @
205ded99
...
...
@@ -9,8 +9,9 @@ from django.core.exceptions import PermissionDenied
from
student.roles
import
CourseInstructorRole
,
CourseStaffRole
,
CourseCreatorRole
from
student.tests.factories
import
AdminFactory
from
student.auth
import
user_has_role
,
add_users
,
remove_users
from
student.auth
import
user_has_role
,
add_users
,
remove_users
,
has_studio_write_access
,
has_studio_read_access
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
ccx_keys.locator
import
CCXLocator
class
CreatorGroupTest
(
TestCase
):
...
...
@@ -132,6 +133,45 @@ class CreatorGroupTest(TestCase):
remove_users
(
self
.
admin
,
CourseCreatorRole
(),
self
.
user
)
class
CCXCourseGroupTest
(
TestCase
):
"""
Test that access to a CCX course in Studio is disallowed
"""
def
setUp
(
self
):
"""
Set up test variables
"""
super
(
CCXCourseGroupTest
,
self
)
.
setUp
()
self
.
global_admin
=
AdminFactory
()
self
.
staff
=
User
.
objects
.
create_user
(
'teststaff'
,
'teststaff+courses@edx.org'
,
'foo'
)
self
.
ccx_course_key
=
CCXLocator
.
from_string
(
'ccx-v1:edX+DemoX+Demo_Course+ccx@1'
)
add_users
(
self
.
global_admin
,
CourseStaffRole
(
self
.
ccx_course_key
),
self
.
staff
)
def
test_no_global_admin_write_access
(
self
):
"""
Test that global admins have no write access
"""
self
.
assertFalse
(
has_studio_write_access
(
self
.
global_admin
,
self
.
ccx_course_key
))
def
test_no_staff_write_access
(
self
):
"""
Test that course staff have no write access
"""
self
.
assertFalse
(
has_studio_write_access
(
self
.
staff
,
self
.
ccx_course_key
))
def
test_no_global_admin_read_access
(
self
):
"""
Test that global admins have no read access
"""
self
.
assertFalse
(
has_studio_read_access
(
self
.
global_admin
,
self
.
ccx_course_key
))
def
test_no_staff_read_access
(
self
):
"""
Test that course staff have no read access
"""
self
.
assertFalse
(
has_studio_read_access
(
self
.
staff
,
self
.
ccx_course_key
))
class
CourseGroupTest
(
TestCase
):
"""
Tests for instructor and staff groups for a particular course.
...
...
common/lib/capa/capa/capa_problem.py
View file @
205ded99
...
...
@@ -187,7 +187,7 @@ class LoncapaProblem(object):
# construct script processor context (eg for customresponse problems)
if
minimal_init
:
self
.
context
=
{
'script_code'
:
""
}
self
.
context
=
{}
else
:
self
.
context
=
self
.
_extract_context
(
self
.
tree
)
...
...
@@ -195,24 +195,24 @@ class LoncapaProblem(object):
# transformations. This also creates the dict (self.responders) of Response
# instances for each question in the problem. The dict has keys = xml subtree of
# Response, values = Response instance
self
.
problem_data
=
self
.
_preprocess_problem
(
self
.
tree
)
self
.
problem_data
=
self
.
_preprocess_problem
(
self
.
tree
,
minimal_init
)
if
not
self
.
student_answers
:
# True when student_answers is an empty dict
self
.
set_initial_display
()
if
not
minimal_init
:
if
not
self
.
student_answers
:
# True when student_answers is an empty dict
self
.
set_initial_display
()
# dictionary of InputType objects associated with this problem
# input_id string -> InputType object
self
.
inputs
=
{}
# dictionary of InputType objects associated with this problem
# input_id string -> InputType object
self
.
inputs
=
{}
# Run response late_transforms last (see MultipleChoiceResponse)
# Sort the responses to be in *_1 *_2 ... order.
responses
=
self
.
responders
.
values
()
responses
=
sorted
(
responses
,
key
=
lambda
resp
:
int
(
resp
.
id
[
resp
.
id
.
rindex
(
'_'
)
+
1
:]))
for
response
in
responses
:
if
hasattr
(
response
,
'late_transforms'
):
response
.
late_transforms
(
self
)
# Run response late_transforms last (see MultipleChoiceResponse)
# Sort the responses to be in *_1 *_2 ... order.
responses
=
self
.
responders
.
values
()
responses
=
sorted
(
responses
,
key
=
lambda
resp
:
int
(
resp
.
id
[
resp
.
id
.
rindex
(
'_'
)
+
1
:]))
for
response
in
responses
:
if
hasattr
(
response
,
'late_transforms'
):
response
.
late_transforms
(
self
)
if
not
minimal_init
:
self
.
extracted_tree
=
self
.
_extract_html
(
self
.
tree
)
def
make_xml_compatible
(
self
,
tree
):
...
...
@@ -869,7 +869,7 @@ class LoncapaProblem(object):
return
tree
def
_preprocess_problem
(
self
,
tree
):
# private
def
_preprocess_problem
(
self
,
tree
,
minimal_init
):
# private
"""
Assign IDs to all the responses
Assign sub-IDs to all entries (textline, schematic, etc.)
...
...
@@ -907,28 +907,31 @@ class LoncapaProblem(object):
# instantiate capa Response
responsetype_cls
=
responsetypes
.
registry
.
get_class_for_tag
(
response
.
tag
)
responder
=
responsetype_cls
(
response
,
inputfields
,
self
.
context
,
self
.
capa_system
,
self
.
capa_module
)
responder
=
responsetype_cls
(
response
,
inputfields
,
self
.
context
,
self
.
capa_system
,
self
.
capa_module
,
minimal_init
)
# save in list in self
self
.
responders
[
response
]
=
responder
# get responder answers (do this only once, since there may be a performance cost,
# eg with externalresponse)
self
.
responder_answers
=
{}
for
response
in
self
.
responders
.
keys
():
try
:
self
.
responder_answers
[
response
]
=
self
.
responders
[
response
]
.
get_answers
()
except
:
log
.
debug
(
'responder
%
s failed to properly return get_answers()'
,
self
.
responders
[
response
])
# FIXME
raise
# <solution>...</solution> may not be associated with any specific response; give
# IDs for those separately
# TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i).
solution_id
=
1
for
solution
in
tree
.
findall
(
'.//solution'
):
solution
.
attrib
[
'id'
]
=
"
%
s_solution_
%
i"
%
(
self
.
problem_id
,
solution_id
)
solution_id
+=
1
if
not
minimal_init
:
# get responder answers (do this only once, since there may be a performance cost,
# eg with externalresponse)
self
.
responder_answers
=
{}
for
response
in
self
.
responders
.
keys
():
try
:
self
.
responder_answers
[
response
]
=
self
.
responders
[
response
]
.
get_answers
()
except
:
log
.
debug
(
'responder
%
s failed to properly return get_answers()'
,
self
.
responders
[
response
])
# FIXME
raise
# <solution>...</solution> may not be associated with any specific response; give
# IDs for those separately
# TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i).
solution_id
=
1
for
solution
in
tree
.
findall
(
'.//solution'
):
solution
.
attrib
[
'id'
]
=
"
%
s_solution_
%
i"
%
(
self
.
problem_id
,
solution_id
)
solution_id
+=
1
return
problem_data
...
...
common/lib/capa/capa/responsetypes.py
View file @
205ded99
...
...
@@ -154,7 +154,7 @@ class LoncapaResponse(object):
# By default, we set this to False, allowing subclasses to override as appropriate.
multi_device_support
=
False
def
__init__
(
self
,
xml
,
inputfields
,
context
,
system
,
capa_module
):
def
__init__
(
self
,
xml
,
inputfields
,
context
,
system
,
capa_module
,
minimal_init
):
"""
Init is passed the following arguments:
...
...
@@ -213,28 +213,29 @@ class LoncapaResponse(object):
maxpoints
=
inputfield
.
get
(
'points'
,
'1'
)
self
.
maxpoints
.
update
({
inputfield
.
get
(
'id'
):
int
(
maxpoints
)})
# dict for default answer map (provided in input elements)
self
.
default_answer_map
=
{}
for
entry
in
self
.
inputfields
:
answer
=
entry
.
get
(
'correct_answer'
)
if
answer
:
self
.
default_answer_map
[
entry
.
get
(
'id'
)]
=
contextualize_text
(
answer
,
self
.
context
)
# Does this problem have partial credit?
# If so, what kind? Get it as a list of strings.
partial_credit
=
xml
.
xpath
(
'.'
)[
0
]
.
get
(
'partial_credit'
,
default
=
False
)
if
str
(
partial_credit
)
.
lower
()
.
strip
()
==
'false'
:
self
.
has_partial_credit
=
False
self
.
credit_type
=
[]
else
:
self
.
has_partial_credit
=
True
self
.
credit_type
=
partial_credit
.
split
(
','
)
self
.
credit_type
=
[
word
.
strip
()
.
lower
()
for
word
in
self
.
credit_type
]
if
not
minimal_init
:
# dict for default answer map (provided in input elements)
self
.
default_answer_map
=
{}
for
entry
in
self
.
inputfields
:
answer
=
entry
.
get
(
'correct_answer'
)
if
answer
:
self
.
default_answer_map
[
entry
.
get
(
'id'
)]
=
contextualize_text
(
answer
,
self
.
context
)
# Does this problem have partial credit?
# If so, what kind? Get it as a list of strings.
partial_credit
=
xml
.
xpath
(
'.'
)[
0
]
.
get
(
'partial_credit'
,
default
=
False
)
if
str
(
partial_credit
)
.
lower
()
.
strip
()
==
'false'
:
self
.
has_partial_credit
=
False
self
.
credit_type
=
[]
else
:
self
.
has_partial_credit
=
True
self
.
credit_type
=
partial_credit
.
split
(
','
)
self
.
credit_type
=
[
word
.
strip
()
.
lower
()
for
word
in
self
.
credit_type
]
if
hasattr
(
self
,
'setup_response'
):
self
.
setup_response
()
if
hasattr
(
self
,
'setup_response'
):
self
.
setup_response
()
def
get_max_score
(
self
):
"""
...
...
openedx/core/lib/celery/routers.py
View file @
205ded99
...
...
@@ -5,6 +5,9 @@ For more, see http://celery.readthedocs.io/en/latest/userguide/routing.html#rout
"""
from
abc
import
ABCMeta
,
abstractproperty
from
django.conf
import
settings
import
logging
log
=
logging
.
getLogger
(
__name__
)
class
AlternateEnvironmentRouter
(
object
):
...
...
@@ -30,6 +33,13 @@ class AlternateEnvironmentRouter(object):
If None is returned from this method, default routing logic is used.
"""
alternate_env
=
self
.
alternate_env_tasks
.
get
(
task
,
None
)
if
'update_course_in_cache'
in
task
:
log
.
info
(
"TNL-5408: task={task}, args={args}, alternate_env={alt_env}, queues={queues}"
.
format
(
task
=
task
,
args
=
args
,
alt_env
=
alternate_env
,
queues
=
getattr
(
settings
,
'CELERY_QUEUES'
,
[])
.
keys
()
))
if
alternate_env
:
return
self
.
ensure_queue_env
(
alternate_env
)
return
None
...
...
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