Skip to content

Commit eb5f71a

Browse files
authored
Merge pull request #249 from open-craft/tecoholic/BB-6145-deprecate-user-rebinding-function
refactor: replace deprecated `rebind_noauth_module_to_user`, `get_real_user`, `runtime.hostname`, `runtime.course_id` [BD-13]
2 parents 224e65e + f647c5f commit eb5f71a

19 files changed

Lines changed: 116 additions & 97 deletions

CHANGELOG.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ Please See the [releases tab](https://github.com/openedx/xblock-lti-consumer/rel
1515

1616
Unreleased
1717
~~~~~~~~~~
18+
19+
7.0.0 - 2022-11-29
20+
------------------
21+
* Refactor anonymous user to real user rebinding function to use `rebind_user` service.
22+
* Refactor accessing hostname from runtime attribute to using `settings.LMS_BASE`.
23+
* Refactor usage of `get_real_user` with `UserService`.
24+
* Refactor deprecated usage of `runtime.course_id` and replace with `runtime.scope_ids.usage_id.context_key`.
25+
* Refactor deprecated usage of `block.location` with `block.scope_ids.usage_id`.
26+
1827
6.4.0 - 2022-11-18
1928
------------------
2029
Adds support for sending an external_user_id in LTI 1.1 XBlock launches. When the

lti_consumer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
from .apps import LTIConsumerApp
55
from .lti_xblock import LtiConsumerXBlock
66

7-
__version__ = '6.4.0'
7+
__version__ = '7.0.0'

lti_consumer/api.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,24 +65,24 @@ def _get_lti_config_for_block(block):
6565
if block.config_type == 'database':
6666
lti_config = _get_or_create_local_lti_config(
6767
block.lti_version,
68-
block.location,
68+
block.scope_ids.usage_id,
6969
LtiConfiguration.CONFIG_ON_DB,
7070
)
7171
elif block.config_type == 'external':
7272
config = get_external_config_from_filter(
73-
{"course_key": block.location.course_key},
73+
{"course_key": block.scope_ids.usage_id.context_key},
7474
block.external_config
7575
)
7676
lti_config = _get_or_create_local_lti_config(
7777
config.get("version"),
78-
block.location,
78+
block.scope_ids.usage_id,
7979
LtiConfiguration.CONFIG_EXTERNAL,
8080
external_id=block.external_config,
8181
)
8282
else:
8383
lti_config = _get_or_create_local_lti_config(
8484
block.lti_version,
85-
block.location,
85+
block.scope_ids.usage_id,
8686
LtiConfiguration.CONFIG_ON_XBLOCK,
8787
)
8888
return lti_config

lti_consumer/lti_1p1/oauth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def log_authorization_header(request, client_key, client_secret):
147147
oauth_body_hash = str(base64.b64encode(sha1.digest()))
148148
log.debug("[LTI] oauth_body_hash = %s", oauth_body_hash)
149149
client = oauth1.Client(client_key, client_secret)
150-
params = client.get_oauth_params(request)
150+
params = oauth1.rfc5849.signature.collect_parameters(headers=request.headers, exclude_oauth_signature=False)
151151
params.append(('oauth_body_hash', oauth_body_hash))
152152
mock_request = SignedRequest(
153153
uri=str(urllib.parse.unquote(request.url)),

lti_consumer/lti_xblock.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,10 @@ def valid_config_type_values(block):
139139
{"display_name": _("Configuration on block"), "value": "new"}
140140
]
141141

142-
if database_config_enabled(block.location.course_key):
142+
if database_config_enabled(block.scope_ids.usage_id.context_key):
143143
values.append({"display_name": _("Database Configuration"), "value": "database"})
144144

145-
if external_config_filter_enabled(block.location.course_key):
145+
if external_config_filter_enabled(block.scope_ids.usage_id.context_key):
146146
values.append({"display_name": _("Reusable Configuration"), "value": "external"})
147147

148148
return values
@@ -161,6 +161,7 @@ class LaunchTarget:
161161

162162

163163
@XBlock.needs('i18n')
164+
@XBlock.needs('rebind_user')
164165
@XBlock.wants('user')
165166
@XBlock.wants('settings')
166167
@XBlock.wants('lti-configuration')
@@ -695,8 +696,8 @@ def editable_fields(self):
695696
editable_fields = self.editable_field_names
696697
noneditable_fields = []
697698

698-
is_database_config_enabled = database_config_enabled(self.location.course_key) # pylint: disable=no-member
699-
is_external_config_filter_enabled = external_config_filter_enabled(self.location.course_key) # pylint: disable=no-member
699+
is_database_config_enabled = database_config_enabled(self.scope_ids.usage_id.context_key)
700+
is_external_config_filter_enabled = external_config_filter_enabled(self.scope_ids.usage_id.context_key)
700701

701702
# If neither additional config_types are enabled, do not display the "config_type" field to users, as "new" is
702703
# the only option and does not make sense without other options.
@@ -713,7 +714,7 @@ def editable_fields(self):
713714
if config_service:
714715
is_already_sharing_learner_info = self.ask_to_send_email or self.ask_to_send_username
715716
if not config_service.configuration.lti_access_to_learners_editable(
716-
self.course_id,
717+
self.scope_ids.usage_id.context_key,
717718
is_already_sharing_learner_info,
718719
):
719720
noneditable_fields.extend(['ask_to_send_username', 'ask_to_send_email'])
@@ -745,7 +746,7 @@ def context_id(self):
745746
context_id is an opaque identifier that uniquely identifies the context (e.g., a course)
746747
that contains the link being launched.
747748
"""
748-
return str(self.course_id)
749+
return str(self.scope_ids.usage_id.context_key)
749750

750751
@property
751752
def role(self):
@@ -763,7 +764,7 @@ def course(self):
763764
"""
764765
Return course by course id.
765766
"""
766-
return self.runtime.modulestore.get_course(self.runtime.course_id)
767+
return self.runtime.modulestore.get_course(self.scope_ids.usage_id.context_key)
767768

768769
@property
769770
def lti_provider_key_secret(self):
@@ -843,7 +844,7 @@ def get_lti_1p1_user_id(self):
843844
toggling this flag in a running course carries the risk of breaking the LTI integrations in the course. This
844845
flag should also only be enabled for new courses in which no LTI attempts have been made.
845846
"""
846-
if external_user_id_1p1_launches_enabled(self.location.course_key): # pylint: disable=no-member
847+
if external_user_id_1p1_launches_enabled(self.scope_ids.usage_id.context_key):
847848
return self.external_user_id
848849

849850
return self.anonymous_user_id
@@ -880,9 +881,7 @@ def resource_link_id(self):
880881
i4x-2-3-lti-31de800015cf4afb973356dbe81496df this part of resource_link_id:
881882
makes resource_link_id to be unique among courses inside same system.
882883
"""
883-
return str(urllib.parse.quote(
884-
f"{self.runtime.hostname}-{self.location.html_id()}" # pylint: disable=no-member
885-
))
884+
return str(urllib.parse.quote(f"{settings.LMS_BASE}-{self.scope_ids.usage_id.html_id()}"))
886885

887886
@property
888887
def lis_result_sourcedid(self):
@@ -1223,7 +1222,7 @@ def lti_1p3_access_token(self, request, suffix=''): # pylint: disable=unused-ar
12231222
# Runtime import because this can only be run in the LMS/Studio Django
12241223
# environments. Importing the views on the top level will cause RuntimeErorr
12251224
from lti_consumer.plugin.views import access_token_endpoint # pylint: disable=import-outside-toplevel
1226-
return access_token_endpoint(request, usage_id=str(self.location)) # pylint: disable=no-member
1225+
return access_token_endpoint(request, usage_id=str(self.scope_ids.usage_id))
12271226

12281227
@XBlock.handler
12291228
def outcome_service_handler(self, request, suffix=''): # pylint: disable=unused-argument
@@ -1294,7 +1293,7 @@ def result_service_handler(self, request, suffix=''):
12941293
except LtiError:
12951294
return Response(status=401) # Unauthorized in this case. 401 is right
12961295

1297-
user = self.runtime.get_real_user(anon_id)
1296+
user = self.runtime.service(self, 'user').get_user_by_anonymous_id(anon_id)
12981297
if not user: # that means we can't save to database, as we do not have real user id.
12991298
msg = _("[LTI]: Real user not found against anon_id: {}").format(anon_id)
13001299
log.info(msg)
@@ -1332,7 +1331,7 @@ def _result_service_get(self, lti_consumer, user):
13321331
Returns:
13331332
dict: response to this request as dictated by the LtiConsumer
13341333
"""
1335-
self.runtime.rebind_noauth_module_to_user(self, user)
1334+
self.runtime.service(self, 'rebind_user').rebind_noauth_module_to_user(self, user)
13361335
args = []
13371336
if self.module_score:
13381337
args.extend([self.module_score, self.score_comment])
@@ -1420,7 +1419,7 @@ def set_user_module_score(self, user, score, max_score, comment=''):
14201419
else:
14211420
scaled_score = None
14221421

1423-
self.runtime.rebind_noauth_module_to_user(self, user)
1422+
self.runtime.service(self, 'rebind_user').rebind_noauth_module_to_user(self, user)
14241423

14251424
# have to publish for the progress page...
14261425
self.runtime.publish(
@@ -1459,8 +1458,8 @@ def get_lti_1p3_launch_data(self):
14591458
from lti_consumer.api import config_id_for_block
14601459
config_id = config_id_for_block(self)
14611460

1462-
location = self.location # pylint: disable=no-member
1463-
course_key = str(location.course_key)
1461+
location = self.scope_ids.usage_id
1462+
course_key = str(location.context_key)
14641463

14651464
launch_data = Lti1p3LaunchData(
14661465
user_id=self.lms_user_id,
@@ -1482,7 +1481,7 @@ def get_context_title(self):
14821481
Return the title attribute of the context_claim for LTI 1.3 launches. This information is included in the
14831482
launch_data query or form parameter of the LTI 1.3 third-party login initiation request.
14841483
"""
1485-
course_key = self.location.course_key # pylint: disable=no-member
1484+
course_key = self.scope_ids.usage_id.context_key
14861485
course = compat.get_course_by_id(course_key)
14871486

14881487
return " - ".join([
@@ -1555,7 +1554,7 @@ def _get_context_for_template(self):
15551554
return {
15561555
'launch_url': launch_url.strip(),
15571556
'lti_1p3_launch_url': lti_1p3_launch_url,
1558-
'element_id': self.location.html_id(), # pylint: disable=no-member
1557+
'element_id': self.scope_ids.usage_id.html_id(),
15591558
'element_class': self.category,
15601559
'launch_target': self.launch_target,
15611560
'display_name': self.display_name,

lti_consumer/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ def clean(self):
241241
})
242242
if self.version == self.LTI_1P3 and self.config_store == self.CONFIG_ON_DB:
243243
block = compat.load_enough_xblock(self.location)
244-
if not database_config_enabled(block.location.course_key):
244+
if not database_config_enabled(block.scope_ids.usage_id.context_key):
245245
raise ValidationError({
246246
"config_store": _("LTI Configuration stores on database is not enabled."),
247247
})

lti_consumer/outcomes.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"""
77

88
import logging
9-
import urllib.parse
109
from xml.sax.saxutils import escape
10+
from urllib.parse import unquote
1111

1212
from lxml import etree
1313
from xblockutils.resources import ResourceLoader
@@ -183,7 +183,8 @@ def handle_request(self, request):
183183
log.debug("[LTI]: %s", error_message)
184184
return response_xml_template.format(**failure_values)
185185

186-
real_user = self.xblock.runtime.get_real_user(urllib.parse.unquote(sourced_id.split(':')[-1]))
186+
anon_id = unquote(sourced_id.split(':')[-1])
187+
real_user = self.xblock.runtime.service(self, 'user').get_user_by_anonymous_id(anon_id)
187188
if not real_user: # that means we can't save to database, as we do not have real user id.
188189
failure_values['imsx_messageIdentifier'] = escape(imsx_message_identifier)
189190
failure_values['imsx_description'] = "User not found."

lti_consumer/signals/signals.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def publish_grade_on_score_update(sender, instance, **kwargs): # pylint: disabl
4646
# the LMS database.
4747
log.info(
4848
"Publishing LTI grade from block %s to LMS. User: %s (score: %s)",
49-
block.location,
49+
block.scope_ids.usage_id,
5050
user,
5151
score,
5252
)

lti_consumer/tests/test_utils.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from unittest.mock import Mock
66
import urllib
77

8-
from opaque_keys.edx.keys import UsageKey
8+
from opaque_keys.edx.keys import CourseKey
9+
from opaque_keys.edx.locator import LocalId
910
from webob import Request
1011
from workbench.runtime import WorkbenchRuntime
1112
from xblock.fields import ScopeIds
@@ -22,30 +23,27 @@ def make_xblock(xblock_name, xblock_cls, attributes):
2223
runtime = WorkbenchRuntime()
2324
key_store = DictKeyValueStore()
2425
db_model = KvsFieldData(key_store)
25-
ids = generate_scope_ids(runtime, xblock_name)
26+
course_id = 'course-v1:edX+DemoX+Demo_Course'
27+
course_key = CourseKey.from_string(course_id)
28+
ids = generate_scope_ids(course_key, xblock_name)
29+
2630
xblock = xblock_cls(runtime, db_model, scope_ids=ids)
2731
xblock.category = Mock()
2832

29-
xblock.location = UsageKey.from_string(
30-
'block-v1:edX+DemoX+Demo_Course+type@problem+block@466f474fa4d045a8b7bde1b911e095ca'
31-
)
32-
3333
xblock.runtime = Mock(
3434
hostname='localhost',
3535
)
36-
xblock.course_id = 'course-v1:edX+DemoX+Demo_Course'
3736
for key, value in attributes.items():
3837
setattr(xblock, key, value)
3938
return xblock
4039

4140

42-
def generate_scope_ids(runtime, block_type):
41+
def generate_scope_ids(course_key, block_type):
4342
"""
4443
Helper to generate scope IDs for an XBlock
4544
"""
46-
def_id = runtime.id_generator.create_definition(block_type)
47-
usage_id = runtime.id_generator.create_usage(def_id)
48-
return ScopeIds('user', block_type, def_id, usage_id)
45+
usage_key = course_key.make_usage_key(block_type, str(LocalId()))
46+
return ScopeIds('user', block_type, usage_key, usage_key)
4947

5048

5149
def make_request(body, method='POST'):

lti_consumer/tests/unit/plugin/test_views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ def test_launch_callback_endpoint_deep_linking_database_config(self, dl_enabled)
333333
self.xblock.config_type = 'database'
334334

335335
LtiConfiguration.objects.filter(id=self.config.id).update(
336-
location=self.xblock.location, # pylint: disable=no-member
336+
location=self.xblock.scope_ids.usage_id,
337337
version=LtiConfiguration.LTI_1P3,
338338
config_store=LtiConfiguration.CONFIG_ON_DB,
339339
lti_advantage_deep_linking_enabled=dl_enabled,

0 commit comments

Comments
 (0)