Skip to content

Commit cd0851b

Browse files
committed
feat(lti): add signal handlers for LTI configuration deletion
* Add signal handlers to delete LTI configurations when xblocks or library blocks are deleted * Ensure LTI configurations are properly cleaned up when associated blocks are removed from the system * Update documentation for LTI 1.3 configuration changes to inform users about potential regeneration of client IDs and URLs when public keys are changed
1 parent 433ac0d commit cd0851b

3 files changed

Lines changed: 95 additions & 3 deletions

File tree

lti_consumer/lti_xblock.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,9 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
374374
" requests received have the signature from the tool."
375375
"<br /><b>This is not required when doing LTI 1.3 Launches"
376376
" without LTI Advantage nor Basic Outcomes requests.</b>"
377+
"<br /><br /><b>Changing the public key or keyset url will cause the client ID, block keyset url "
378+
"and access token url to be regenerated if they are shared between blocks. "
379+
"Please check and update them in the LTI tool settings if necessary.</b>"
377380
),
378381
)
379382
lti_1p3_tool_public_key = String(
@@ -388,6 +391,9 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
388391
"from the tool."
389392
"<br /><b>This is not required when doing LTI 1.3 Launches without LTI Advantage nor "
390393
"Basic Outcomes requests.</b>"
394+
"<br /><br /><b>Changing the public key or keyset url will cause the client ID, block keyset url "
395+
"and access token url to be regenerated if they are shared between blocks. "
396+
"Please check and update them in the LTI tool settings if necessary.</b>"
391397
),
392398
)
393399

lti_consumer/plugin/compat.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,27 @@ def nrps_pii_disallowed():
353353
"""
354354
return (hasattr(settings, 'LTI_NRPS_DISALLOW_PII') and
355355
settings.LTI_NRPS_DISALLOW_PII is True)
356+
357+
358+
def get_signal_handler():
359+
"""
360+
Import the signal handler from LMS
361+
"""
362+
try:
363+
# pylint: disable=import-outside-toplevel
364+
from xmodule.modulestore.django import SignalHandler
365+
return SignalHandler
366+
except ImportError:
367+
return None
368+
369+
370+
def yield_dynamic_block_descendants(block, user_id):
371+
"""
372+
Import and run `yield_dynamic_block_descendants` from LMS
373+
"""
374+
try:
375+
# pylint: disable=import-outside-toplevel,redefined-outer-name
376+
from common.djangoapps.util.block_utils import yield_dynamic_block_descendants
377+
return yield_dynamic_block_descendants(block, user_id)
378+
except ImportError:
379+
return None

lti_consumer/signals/signals.py

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
import logging
55

66
from django.db.models.signals import post_save
7-
from django.dispatch import receiver, Signal
7+
from django.dispatch import Signal, receiver
8+
from openedx_events.content_authoring.data import LibraryBlockData, XBlockData
9+
from openedx_events.content_authoring.signals import LIBRARY_BLOCK_DELETED, XBLOCK_DELETED
810

9-
from lti_consumer.models import LtiAgsScore, Lti1p3Passport, LtiConfiguration
11+
from lti_consumer.models import Lti1p3Passport, LtiAgsScore, LtiConfiguration
1012
from lti_consumer.plugin import compat
1113

12-
1314
log = logging.getLogger(__name__)
15+
SignalHandler = compat.get_signal_handler()
1416

1517

1618
@receiver(post_save, sender=LtiAgsScore, dispatch_uid='publish_grade_on_score_update')
@@ -87,4 +89,64 @@ def create_lti_1p3_passport(sender, instance: LtiConfiguration, **kwargs): # py
8789
instance.create_lti_1p3_passport()
8890

8991

92+
@receiver(SignalHandler.pre_item_delete if SignalHandler else [])
93+
def delete_child_lti_configurations(**kwargs):
94+
"""
95+
Delete lti configurtion from database for this block children.
96+
"""
97+
usage_key = kwargs.get('usage_key')
98+
if usage_key:
99+
# Strip branch info
100+
usage_key = usage_key.for_branch(None)
101+
try:
102+
deleted_block = compat.load_enough_xblock(usage_key)
103+
except Exception as e: # pylint: disable=broad-exception-caught
104+
log.warning(f"Cannot find xblock for key {usage_key}. Reason: {str(e)}. ")
105+
return
106+
id_list = {str(deleted_block.location)}
107+
for block in compat.yield_dynamic_block_descendants(deleted_block, kwargs.get('user_id')):
108+
id_list.add(str(block.location))
109+
110+
LtiConfiguration.objects.filter(
111+
location__in=id_list
112+
).delete()
113+
log.info(f"Deleted {len(id_list)} lti configurations in modulestore")
114+
result = Lti1p3Passport.objects.filter(lticonfiguration__isnull=True).delete()
115+
log.info(f"Deleted {result} lti 1.3 passport objects in library")
116+
117+
118+
@receiver(XBLOCK_DELETED)
119+
def delete_lti_configuration(**kwargs):
120+
"""
121+
Delete lti configurtion from database for this block.
122+
"""
123+
xblock_info = kwargs.get("xblock_info", None)
124+
if not xblock_info or not isinstance(xblock_info, XBlockData):
125+
log.error("Received null or incorrect data for event")
126+
return
127+
128+
LtiConfiguration.objects.filter(
129+
location=str(xblock_info.usage_key)
130+
).delete()
131+
result = Lti1p3Passport.objects.filter(lticonfiguration__isnull=True).delete()
132+
log.info(f"Deleted {result} lti 1.3 passport objects in library")
133+
134+
135+
@receiver(LIBRARY_BLOCK_DELETED)
136+
def delete_lib_lti_configuration(**kwargs):
137+
"""
138+
Delete lti configurtion from database for this library block.
139+
"""
140+
library_block = kwargs.get("library_block", None)
141+
if not library_block or not isinstance(library_block, LibraryBlockData):
142+
log.error("Received null or incorrect data for event")
143+
return
144+
145+
LtiConfiguration.objects.filter(
146+
location=str(library_block.usage_key)
147+
).delete()
148+
result = Lti1p3Passport.objects.filter(lticonfiguration__isnull=True).delete()
149+
log.info(f"Deleted {result} lti 1.3 passport objects in library")
150+
151+
90152
LTI_1P3_PROCTORING_ASSESSMENT_STARTED = Signal()

0 commit comments

Comments
 (0)