Skip to content
Merged
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
4 changes: 1 addition & 3 deletions cms/djangoapps/contentstore/views/preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,10 @@ def _preview_module_system(request, descriptor, field_data):
render_template=render_from_lms,
debug=True,
replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_id=course_id),
user=request.user,
can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)),
get_python_lib_zip=(lambda: get_python_lib_zip(contentstore, course_id)),
mixins=settings.XBLOCK_MIXINS,
course_id=course_id,
anonymous_student_id='student',

# Set up functions to modify the fragment produced by student_view
wrappers=wrappers,
Expand All @@ -216,7 +214,7 @@ def _preview_module_system(request, descriptor, field_data):
"field-data": field_data,
"i18n": ModuleI18nService,
"settings": SettingsService(),
"user": DjangoXBlockUserService(request.user),
"user": DjangoXBlockUserService(request.user, anonymous_user_id='student'),
"partitions": StudioPartitionService(course_id=course_id),
"teams_configuration": TeamsConfigurationService(),
},
Expand Down
11 changes: 11 additions & 0 deletions common/djangoapps/xblock_django/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Constants used by DjangoXBlockUserService
"""

# Optional attributes stored on the XBlockUser
ATTR_KEY_ANONYMOUS_USER_ID = 'edx-platform.anonymous_user_id'
ATTR_KEY_IS_AUTHENTICATED = 'edx-platform.is_authenticated'
ATTR_KEY_USER_ID = 'edx-platform.user_id'
ATTR_KEY_USERNAME = 'edx-platform.username'
ATTR_KEY_USER_IS_STAFF = 'edx-platform.user_is_staff'
ATTR_KEY_USER_PREFERENCES = 'edx-platform.user_preferences'
25 changes: 20 additions & 5 deletions common/djangoapps/xblock_django/tests/test_user_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Tests for the DjangoXBlockUserService.
"""

import ddt
import pytest
from django.test import TestCase
from opaque_keys.edx.keys import CourseKey
Expand All @@ -12,6 +13,7 @@
from common.djangoapps.student.tests.factories import AnonymousUserFactory, UserFactory
from common.djangoapps.xblock_django.user_service import (
ATTR_KEY_IS_AUTHENTICATED,
ATTR_KEY_ANONYMOUS_USER_ID,
ATTR_KEY_USER_ID,
ATTR_KEY_USER_IS_STAFF,
ATTR_KEY_USER_PREFERENCES,
Expand All @@ -21,6 +23,7 @@
)


@ddt.ddt
class UserServiceTestCase(TestCase):
"""
Tests for the DjangoXBlockUserService.
Expand All @@ -42,7 +45,7 @@ def assert_is_anon_xb_user(self, xb_user):
assert xb_user.full_name is None
self.assertListEqual(xb_user.emails, [])

def assert_xblock_user_matches_django(self, xb_user, dj_user):
def assert_xblock_user_matches_django(self, xb_user, dj_user, user_is_staff=False, anonymous_user_id=None):
"""
A set of assertions for comparing a XBlockUser to a django User
"""
Expand All @@ -51,7 +54,8 @@ def assert_xblock_user_matches_django(self, xb_user, dj_user):
assert xb_user.full_name == dj_user.profile.name
assert xb_user.opt_attrs[ATTR_KEY_USERNAME] == dj_user.username
assert xb_user.opt_attrs[ATTR_KEY_USER_ID] == dj_user.id
assert not xb_user.opt_attrs[ATTR_KEY_USER_IS_STAFF]
assert xb_user.opt_attrs[ATTR_KEY_USER_IS_STAFF] == user_is_staff
assert xb_user.opt_attrs[ATTR_KEY_ANONYMOUS_USER_ID] == anonymous_user_id
assert all((pref in USER_PREFERENCES_WHITE_LIST) for pref in xb_user.opt_attrs[ATTR_KEY_USER_PREFERENCES])

def test_convert_anon_user(self):
Expand All @@ -63,14 +67,25 @@ def test_convert_anon_user(self):
assert xb_user.is_current_user
self.assert_is_anon_xb_user(xb_user)

def test_convert_authenticate_user(self):
@ddt.data(
(False, None),
(True, None),
(False, 'abcdef0123'),
(True, 'abcdef0123'),
)
@ddt.unpack
def test_convert_authenticate_user(self, user_is_staff, anonymous_user_id):
"""
Tests for convert_django_user_to_xblock_user behavior when django user is User.
"""
django_user_service = DjangoXBlockUserService(self.user)
django_user_service = DjangoXBlockUserService(
self.user,
user_is_staff=user_is_staff,
anonymous_user_id=anonymous_user_id,
)
xb_user = django_user_service.get_current_user()
assert xb_user.is_current_user
self.assert_xblock_user_matches_django(xb_user, self.user)
self.assert_xblock_user_matches_django(xb_user, self.user, user_is_staff, anonymous_user_id)

def test_get_anonymous_user_id_returns_none_for_non_staff_users(self):
"""
Expand Down
29 changes: 21 additions & 8 deletions common/djangoapps/xblock_django/user_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@
from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences
from common.djangoapps.student.models import anonymous_id_for_user, get_user_by_username_or_email

ATTR_KEY_IS_AUTHENTICATED = 'edx-platform.is_authenticated'
ATTR_KEY_USER_ID = 'edx-platform.user_id'
ATTR_KEY_USERNAME = 'edx-platform.username'
ATTR_KEY_USER_IS_STAFF = 'edx-platform.user_is_staff'
ATTR_KEY_USER_PREFERENCES = 'edx-platform.user_preferences'
from .constants import (
ATTR_KEY_ANONYMOUS_USER_ID,
ATTR_KEY_IS_AUTHENTICATED,
ATTR_KEY_USER_ID,
ATTR_KEY_USERNAME,
ATTR_KEY_USER_IS_STAFF,
ATTR_KEY_USER_PREFERENCES,
)


USER_PREFERENCES_WHITE_LIST = ['pref-lang', 'time_zone']


Expand All @@ -24,10 +29,17 @@ class DjangoXBlockUserService(UserService):
A user service that converts Django users to XBlockUser
"""
def __init__(self, django_user, **kwargs):
"""
Constructs a DjangoXBlockUserService object.

Args:
user_is_staff(bool): optional - whether the user is staff in the course
anonymous_user_id(str): optional - anonymous_user_id for the user in the course
"""
super().__init__(**kwargs)
self._django_user = django_user
if self._django_user:
self._django_user.user_is_staff = kwargs.get('user_is_staff', False)
self._user_is_staff = kwargs.get('user_is_staff', False)
self._anonymous_user_id = kwargs.get('anonymous_user_id', None)

def get_current_user(self):
"""
Expand Down Expand Up @@ -82,10 +94,11 @@ def _convert_django_user_to_xblock_user(self, django_user):
full_name = None
xblock_user.full_name = full_name
xblock_user.emails = [django_user.email]
xblock_user.opt_attrs[ATTR_KEY_ANONYMOUS_USER_ID] = self._anonymous_user_id
xblock_user.opt_attrs[ATTR_KEY_IS_AUTHENTICATED] = True
xblock_user.opt_attrs[ATTR_KEY_USER_ID] = django_user.id
xblock_user.opt_attrs[ATTR_KEY_USERNAME] = django_user.username
xblock_user.opt_attrs[ATTR_KEY_USER_IS_STAFF] = django_user.user_is_staff
xblock_user.opt_attrs[ATTR_KEY_USER_IS_STAFF] = self._user_is_staff
user_preferences = get_user_preferences(django_user)
xblock_user.opt_attrs[ATTR_KEY_USER_PREFERENCES] = {
pref: user_preferences.get(pref)
Expand Down
29 changes: 21 additions & 8 deletions common/lib/xmodule/xmodule/capa_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
from capa.inputtypes import Status
from capa.responsetypes import LoncapaProblemError, ResponseError, StudentInputError
from capa.util import convert_files_to_filenames, get_inner_html_from_xpath
from common.djangoapps.xblock_django.constants import (
ATTR_KEY_ANONYMOUS_USER_ID,
ATTR_KEY_USER_IS_STAFF,
ATTR_KEY_USER_ID,
)
from openedx.core.djangolib.markup import HTML, Text
from xmodule.contentstore.django import contentstore
from xmodule.editing_module import EditingMixin
Expand Down Expand Up @@ -113,7 +118,7 @@ def from_json(self, value):
to_json = from_json


@XBlock.wants('user')
@XBlock.needs('user')
@XBlock.needs('i18n')
@XBlock.wants('call_to_action')
class ProblemBlock(
Expand Down Expand Up @@ -784,9 +789,10 @@ def choose_new_seed(self):
"""
if self.rerandomize == RANDOMIZATION.NEVER:
self.seed = 1
elif self.rerandomize == RANDOMIZATION.PER_STUDENT and hasattr(self.runtime, 'seed'):
elif self.rerandomize == RANDOMIZATION.PER_STUDENT:
user_id = self.runtime.service(self, 'user').get_current_user().opt_attrs.get(ATTR_KEY_USER_ID) or 0
# see comment on randomization_bin
self.seed = randomization_bin(self.runtime.seed, str(self.location).encode('utf-8'))
self.seed = randomization_bin(user_id, str(self.location).encode('utf-8'))
else:
self.seed = struct.unpack('i', os.urandom(4))[0]

Expand All @@ -801,9 +807,13 @@ def new_lcp(self, state, text=None):
if text is None:
text = self.data

user_service = self.runtime.service(self, 'user')
anonymous_student_id = user_service.get_current_user().opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID)
seed = user_service.get_current_user().opt_attrs.get(ATTR_KEY_USER_ID) or 0

capa_system = LoncapaSystem(
ajax_url=self.ajax_url,
anonymous_student_id=self.runtime.anonymous_student_id,
anonymous_student_id=anonymous_student_id,
cache=self.runtime.cache,
can_execute_unsafe_code=self.runtime.can_execute_unsafe_code,
get_python_lib_zip=self.runtime.get_python_lib_zip,
Expand All @@ -812,7 +822,7 @@ def new_lcp(self, state, text=None):
i18n=self.runtime.service(self, "i18n"),
node_path=self.runtime.node_path,
render_template=self.runtime.render_template,
seed=self.runtime.seed, # Why do we do this if we have self.seed?
seed=seed, # Why do we do this if we have self.seed?
STATIC_URL=self.runtime.STATIC_URL,
xqueue=self.runtime.xqueue,
matlab_api_key=self.matlab_api_key
Expand Down Expand Up @@ -1412,14 +1422,15 @@ def answer_available(self):
"""
Is the user allowed to see an answer?
"""
user_is_staff = self.runtime.service(self, 'user').get_current_user().opt_attrs.get(ATTR_KEY_USER_IS_STAFF)
if not self.correctness_available():
# If correctness is being withheld, then don't show answers either.
return False
elif self.showanswer == '':
return False
elif self.showanswer == SHOWANSWER.NEVER:
return False
elif self.runtime.user_is_staff:
elif user_is_staff:
# This is after the 'never' check because admins can see the answer
# unless the problem explicitly prevents it
return True
Expand Down Expand Up @@ -1459,10 +1470,11 @@ def correctness_available(self):

Limits access to the correct/incorrect flags, messages, and problem score.
"""
user_is_staff = self.runtime.service(self, 'user').get_current_user().opt_attrs.get(ATTR_KEY_USER_IS_STAFF)
return ShowCorrectness.correctness_available(
show_correctness=self.show_correctness,
due_date=self.close_date,
has_staff_access=self.runtime.user_is_staff,
has_staff_access=user_is_staff,
)

def update_score(self, data):
Expand Down Expand Up @@ -1777,7 +1789,8 @@ def submit_problem(self, data, override_time=False):
# If the user is a staff member, include
# the full exception, including traceback,
# in the response
if self.runtime.user_is_staff:
user_is_staff = self.runtime.service(self, 'user').get_current_user().opt_attrs.get(ATTR_KEY_USER_IS_STAFF)
if user_is_staff:
msg = f"Staff debug info: {traceback.format_exc()}"

# Otherwise, display just an error message,
Expand Down
7 changes: 5 additions & 2 deletions common/lib/xmodule/xmodule/html_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from web_fragments.fragment import Fragment
from xblock.core import XBlock
from xblock.fields import Boolean, List, Scope, String
from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID
from xmodule.contentstore.content import StaticContent
from xmodule.editing_module import EditingMixin
from xmodule.edxnotes_utils import edxnotes
Expand All @@ -42,6 +43,7 @@


@XBlock.needs("i18n")
@XBlock.needs("user")
class HtmlBlockMixin( # lint-amnesty, pylint: disable=abstract-method
XmlMixin, EditingMixin,
XModuleDescriptorToXBlockMixin, XModuleToXBlockMixin, HTMLSnippet, ResourceTemplates, XModuleMixin,
Expand Down Expand Up @@ -117,8 +119,9 @@ def get_html(self):
""" Returns html required for rendering the block. """
if self.data:
data = self.data
if getattr(self.runtime, 'anonymous_student_id', None):
data = data.replace("%%USER_ID%%", self.runtime.anonymous_student_id)
user_id = self.runtime.service(self, 'user').get_current_user().opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID)
if user_id:
data = data.replace("%%USER_ID%%", user_id)
data = data.replace("%%COURSE_ID%%", str(self.scope_ids.usage_id.context_key))
return data
return self.data
Expand Down
10 changes: 8 additions & 2 deletions common/lib/xmodule/xmodule/lti_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
from openedx.core.djangolib.markup import HTML, Text
from xmodule.editing_module import EditingMixin

from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID
from xmodule.lti_2_util import LTI20BlockMixin, LTIError
from xmodule.raw_module import EmptyDataRawMixin
from xmodule.util.xmodule_django import add_webpack_to_fragment
Expand Down Expand Up @@ -269,6 +270,7 @@ class LTIFields:


@XBlock.needs("i18n")
@XBlock.needs("user")
class LTIBlock(
LTIFields,
LTI20BlockMixin,
Expand Down Expand Up @@ -529,7 +531,10 @@ def preview_handler(self, _, __):
return Response(template, content_type='text/html')

def get_user_id(self):
user_id = self.runtime.anonymous_student_id
"""
Returns the current user ID, URL-escaped so it is safe to use as a URL component.
"""
user_id = self.runtime.service(self, 'user').get_current_user().opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID)
assert user_id is not None
return str(parse.quote(user_id))

Expand Down Expand Up @@ -671,7 +676,8 @@ def oauth_params(self, custom_parameters, client_key, client_secret):
# To test functionality test in LMS

if callable(self.runtime.get_real_user):
real_user_object = self.runtime.get_real_user(self.runtime.anonymous_student_id)
user_id = self.runtime.service(self, 'user').get_current_user().opt_attrs.get(ATTR_KEY_ANONYMOUS_USER_ID)
real_user_object = self.runtime.get_real_user(user_id)
try:
self.user_email = real_user_object.email # lint-amnesty, pylint: disable=attribute-defined-outside-init
except AttributeError:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from xmodule.modulestore.tests.factories import XMODULE_FACTORY_LOCK
from xmodule.modulestore.tests.mongo_connection import MONGO_HOST, MONGO_PORT_NUM


class CourseUserType(Enum):
"""
Types of users to be used when testing a course.
Expand Down
Loading