qti 2.13.1 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/qti/sanitizer.rb +2 -2
- data/lib/qti/v1/models/assessment_item.rb +7 -3
- data/lib/qti/v1/models/base.rb +16 -0
- data/lib/qti/v1/models/interactions/base_fill_blank_interaction.rb +2 -3
- data/lib/qti/version.rb +1 -1
- data/spec/fixtures/items_1.2/true_false.xml +41 -0
- data/spec/lib/qti/sanitizer_spec.rb +9 -0
- data/spec/lib/qti/v1/models/assessment_item_spec.rb +31 -0
- data/spec/lib/qti/v1/models/base_spec.rb +22 -0
- data/spec/lib/qti/v1/models/interactions/choice_interaction_spec.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fcafa61127adf8dafdd3084cd6c22d56630473c0d01e42a7e6cddf6ee7b4ba96
|
4
|
+
data.tar.gz: 68fdf75e1b11fae2ca2ac55aca9daf936c2754ee1a0a244ccc94a51953c3957f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 06543ad3a11e643f4c9731a641da3495afa53d94defa9ac8b40a34015d152ca24e5943fb1b33d9fe0db0714915f9a2e86dd2511821197f1b29395b071adf1ce0
|
7
|
+
data.tar.gz: 7b3ad6b2fa593ca9f2be62f524777fef0d41d9ddc8833af38ec445a89a3ffe5c855dd80312000978a0bd55bada68b8d711220a54b46d1b231223b0e41727b0b3
|
data/lib/qti/sanitizer.rb
CHANGED
@@ -40,7 +40,7 @@ module Qti
|
|
40
40
|
'object' => MEDIA_ATTR,
|
41
41
|
'embed' => %w[name src type allowfullscreen pluginspage wmode
|
42
42
|
allowscriptaccess width height],
|
43
|
-
'iframe' => %w[src width height name align frameborder scrolling sandbox
|
43
|
+
'iframe' => %w[src style width height name align frameborder scrolling sandbox
|
44
44
|
allowfullscreen webkitallowfullscreen mozallowfullscreen
|
45
45
|
allow] + ALL_DATA_ATTR, # TODO: remove explicit allow with domain whitelist account setting
|
46
46
|
'a' => relaxed_config('a', ['target'] + ALL_DATA_ATTR),
|
@@ -92,7 +92,7 @@ module Qti
|
|
92
92
|
lambda do |env|
|
93
93
|
return unless FILTER_TAGS.include?(env[:node_name])
|
94
94
|
return if env[:is_whitelisted] || !env[:node].element?
|
95
|
-
Sanitize.node!(env[:node], CONFIG)
|
95
|
+
Sanitize.node!(env[:node], Sanitize::Config.merge(Sanitize::Config::RELAXED, CONFIG))
|
96
96
|
{ node_whitelist: [env[:node]] }
|
97
97
|
end
|
98
98
|
end
|
@@ -18,7 +18,7 @@ module Qti
|
|
18
18
|
node = @doc.dup
|
19
19
|
presentation = node.at_xpath('.//xmlns:presentation')
|
20
20
|
mattext = presentation.at_xpath('.//xmlns:mattext')
|
21
|
-
prompt = return_inner_content!(mattext)
|
21
|
+
prompt = sanitize_attributes(return_inner_content!(mattext))
|
22
22
|
sanitize_content!(prompt)
|
23
23
|
end
|
24
24
|
end
|
@@ -83,11 +83,15 @@ module Qti
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def feedback
|
86
|
-
@feedback ||= interaction_model.canvas_item_feedback
|
86
|
+
@feedback ||= interaction_model.canvas_item_feedback&.transform_values { |v| sanitize_content!(v) }
|
87
87
|
end
|
88
88
|
|
89
89
|
def answer_feedback
|
90
|
-
@answer_feedback ||= interaction_model.answer_feedback
|
90
|
+
@answer_feedback ||= interaction_model.answer_feedback&.map do |answer|
|
91
|
+
duped = answer.dup
|
92
|
+
duped[:feedback] = sanitize_content!(duped[:feedback])
|
93
|
+
duped
|
94
|
+
end
|
91
95
|
end
|
92
96
|
end
|
93
97
|
end
|
data/lib/qti/v1/models/base.rb
CHANGED
@@ -2,6 +2,8 @@ module Qti
|
|
2
2
|
module V1
|
3
3
|
module Models
|
4
4
|
class Base < Qti::Models::Base
|
5
|
+
CANVAS_BLANK_REGEX ||= /(\[[A-Za-z0-9_\-.]+\])/.freeze
|
6
|
+
|
5
7
|
def qti_version
|
6
8
|
1
|
7
9
|
end
|
@@ -12,6 +14,20 @@ module Qti
|
|
12
14
|
node.inner_html
|
13
15
|
end
|
14
16
|
|
17
|
+
def sanitize_attributes(html)
|
18
|
+
node = Nokogiri::HTML.fragment(html)
|
19
|
+
sanitize_attributes_by_node(node)
|
20
|
+
node.to_html
|
21
|
+
end
|
22
|
+
|
23
|
+
def sanitize_attributes_by_node(node)
|
24
|
+
node.attribute_nodes.each do |a|
|
25
|
+
matches = a.value.match(CANVAS_BLANK_REGEX) || []
|
26
|
+
a.value = a.value.gsub!('[', '[').gsub!(']', ']') if matches.length.positive?
|
27
|
+
end
|
28
|
+
node.children.each { |c| sanitize_attributes_by_node(c) }
|
29
|
+
end
|
30
|
+
|
15
31
|
private
|
16
32
|
|
17
33
|
def text_node?(node)
|
@@ -3,10 +3,9 @@ module Qti
|
|
3
3
|
module Models
|
4
4
|
module Interactions
|
5
5
|
class BaseFillBlankInteraction < BaseInteraction
|
6
|
-
CANVAS_REGEX ||= /(\[[A-Za-z0-9_\-.]+\])/.freeze
|
7
|
-
|
8
6
|
def canvas_stem_items(item_prompt)
|
9
|
-
item_prompt
|
7
|
+
item_prompt = sanitize_attributes(item_prompt)
|
8
|
+
item_prompt.split(CANVAS_BLANK_REGEX).map.with_index do |stem_item, index|
|
10
9
|
if canvas_fib_response_ids.include?(stem_item)
|
11
10
|
# Strip the brackets before searching
|
12
11
|
value = stem_item[1..-2]
|
data/lib/qti/version.rb
CHANGED
@@ -3,6 +3,18 @@
|
|
3
3
|
<assessment title="1.2 Import Quiz" ident="A1001">
|
4
4
|
<section title="Main" ident="S1002">
|
5
5
|
<item title="Grading - specific - 3 pt score" ident="QUE_1003">
|
6
|
+
<itemmetadata>
|
7
|
+
<qtimetadata>
|
8
|
+
<qtimetadatafield>
|
9
|
+
<fieldlabel>question_type</fieldlabel>
|
10
|
+
<fieldentry>true_false_question</fieldentry>
|
11
|
+
</qtimetadatafield>
|
12
|
+
<qtimetadatafield>
|
13
|
+
<fieldlabel>assessment_question_identifierref</fieldlabel>
|
14
|
+
<fieldentry>iff205c7405d3b36bdca9b75b51198932</fieldentry>
|
15
|
+
</qtimetadatafield>
|
16
|
+
</qtimetadata>
|
17
|
+
</itemmetadata>
|
6
18
|
<presentation>
|
7
19
|
<material>
|
8
20
|
<mattext texttype="text/html"><![CDATA[If I get a 3, I must have done something wrong. <img data-equation-content="sample equation" script="alert('bad')" align="bottom" alt="image.png" src="org0/images/image.png" border="0"/> <img script="alert('bad')" align="bottom" alt="image.png" src="org0/images/image.png" border="0"/>]]></mattext>
|
@@ -37,8 +49,37 @@
|
|
37
49
|
<varequal respident="QUE_1004_RL">QUE_1006_A2</varequal>
|
38
50
|
</conditionvar>
|
39
51
|
<setvar varname="que_score" action="Set">10.00</setvar>
|
52
|
+
<displayfeedback feedbacktype="Response" linkrefid="fb_QUE_1006_A2"/>
|
40
53
|
</respcondition>
|
41
54
|
</resprocessing>
|
55
|
+
<itemfeedback ident="general_fb">
|
56
|
+
<flow_mat>
|
57
|
+
<material>
|
58
|
+
<mattext texttype="text/html"><p>Neutral</p><img script="alert('bad')" alt="image.png" src="org0/images/image.png"/></mattext>
|
59
|
+
</material>
|
60
|
+
</flow_mat>
|
61
|
+
</itemfeedback>
|
62
|
+
<itemfeedback ident="correct_fb">
|
63
|
+
<flow_mat>
|
64
|
+
<material>
|
65
|
+
<mattext texttype="text/html"><p>Correct</p><img script="alert('bad')" alt="image.png" src="org0/images/image.png"/></mattext>
|
66
|
+
</material>
|
67
|
+
</flow_mat>
|
68
|
+
</itemfeedback>
|
69
|
+
<itemfeedback ident="general_incorrect_fb">
|
70
|
+
<flow_mat>
|
71
|
+
<material>
|
72
|
+
<mattext texttype="text/html"><p>Incorrect</p><img script="alert('bad')" alt="image.png" src="org0/images/image.png"/></mattext>
|
73
|
+
</material>
|
74
|
+
</flow_mat>
|
75
|
+
</itemfeedback>
|
76
|
+
<itemfeedback ident="fb_QUE_1006_A2">
|
77
|
+
<flow_mat>
|
78
|
+
<material>
|
79
|
+
<mattext texttype="text/html"><p>Answer was Correct</p><img script="alert('bad')" alt="image.png" src="org0/images/image.png"/></mattext>
|
80
|
+
</material>
|
81
|
+
</flow_mat>
|
82
|
+
</itemfeedback>
|
42
83
|
</item>
|
43
84
|
</section>
|
44
85
|
</assessment>
|
@@ -54,5 +54,14 @@ describe Qti::Sanitizer do
|
|
54
54
|
|
55
55
|
expect(sanitizer.clean(html)).to include 'target="_blank"'
|
56
56
|
end
|
57
|
+
|
58
|
+
it 'allows style attributes on iframe' do
|
59
|
+
html = '<iframe style="width: 523px; height: 294px; display: inline-block;"></iframe>'
|
60
|
+
|
61
|
+
expect(sanitizer.clean(html)).to include 'style'
|
62
|
+
expect(sanitizer.clean(html)).to include 'width: 523px;'
|
63
|
+
expect(sanitizer.clean(html)).to include 'height: 294px;'
|
64
|
+
expect(sanitizer.clean(html)).to include 'display: inline-block;'
|
65
|
+
end
|
57
66
|
end
|
58
67
|
end
|
@@ -10,6 +10,37 @@ describe Qti::V1::Models::AssessmentItem do
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
context 'feedback', focus: true do
|
14
|
+
let(:file_path) { File.join('spec', 'fixtures', 'items_1.2', 'true_false.xml') }
|
15
|
+
let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
|
16
|
+
let(:assessment_item_refs) { test_object.assessment_items }
|
17
|
+
let(:loaded_class) { described_class.new(assessment_item_refs) }
|
18
|
+
|
19
|
+
it 'sanitizes general neutral feedback' do
|
20
|
+
expect(loaded_class.feedback[:neutral]).to include '<p>Neutral'
|
21
|
+
expect(loaded_class.feedback[:neutral]).to include '<img'
|
22
|
+
expect(loaded_class.feedback[:neutral]).not_to include 'script="alert(\'bad\')"'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'sanitizes general correct feedback' do
|
26
|
+
expect(loaded_class.feedback[:correct]).to include '<p>Correct'
|
27
|
+
expect(loaded_class.feedback[:correct]).to include '<img'
|
28
|
+
expect(loaded_class.feedback[:correct]).not_to include 'script="alert(\'bad\')"'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'sanitizes general incorrect feedback' do
|
32
|
+
expect(loaded_class.feedback[:incorrect]).to include '<p>Incorrect'
|
33
|
+
expect(loaded_class.feedback[:incorrect]).to include '<img'
|
34
|
+
expect(loaded_class.feedback[:incorrect]).not_to include 'script="alert(\'bad\')"'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'sanitizes answer feedback' do
|
38
|
+
expect(loaded_class.answer_feedback.first[:feedback]).to include '<p>Answer was Correc'
|
39
|
+
expect(loaded_class.answer_feedback.first[:feedback]).to include '<img'
|
40
|
+
expect(loaded_class.answer_feedback.first[:feedback]).not_to include 'script="alert(\'bad\')"'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
13
44
|
context 'quiz.xml' do
|
14
45
|
let(:file_path) { File.join('spec', 'fixtures', 'items_1.2', 'true_false.xml') }
|
15
46
|
let(:test_object) { Qti::V1::Models::Assessment.from_path!(file_path) }
|
@@ -0,0 +1,22 @@
|
|
1
|
+
describe Qti::V1::Models::Base do
|
2
|
+
context 'specified as single content node matching helpers' do
|
3
|
+
let(:loaded_class) do
|
4
|
+
fixtures_path = File.join('spec', 'fixtures')
|
5
|
+
path = File.join(fixtures_path, 'test_qti_1.2', 'quiz.xml')
|
6
|
+
described_class.from_path!(path)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '.sanitize_attributes' do
|
10
|
+
it 'respects valid content' do
|
11
|
+
source = 'fill in the [blank]'
|
12
|
+
expect(loaded_class.sanitize_attributes(source)).to eq source
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'translates invalid content' do
|
16
|
+
source = '<span title="[x]">fill in the [blank]</span>'
|
17
|
+
expected = '<span title="&#91;x&#93;">fill in the [blank]</span>'
|
18
|
+
expect(loaded_class.sanitize_attributes(source)).to eq expected
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -39,7 +39,7 @@ describe Qti::V1::Models::Interactions::ChoiceInteraction do
|
|
39
39
|
let(:file_path) { File.join(fixtures_path, 'true_false.xml') }
|
40
40
|
let(:shuffle_value) { true }
|
41
41
|
let(:answer_choices_count) { 2 }
|
42
|
-
let(:meta_type) {
|
42
|
+
let(:meta_type) { 'true_false_question' }
|
43
43
|
|
44
44
|
include_examples 'shuffled?'
|
45
45
|
include_examples 'answers'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qti
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adrian Diaz
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2023-
|
15
|
+
date: 2023-05-16 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: actionview
|
@@ -665,6 +665,7 @@ files:
|
|
665
665
|
- spec/lib/qti/sanitizer_spec.rb
|
666
666
|
- spec/lib/qti/v1/models/assessment_item_spec.rb
|
667
667
|
- spec/lib/qti/v1/models/assessment_spec.rb
|
668
|
+
- spec/lib/qti/v1/models/base_spec.rb
|
668
669
|
- spec/lib/qti/v1/models/choices/fill_blank_choice_spec.rb
|
669
670
|
- spec/lib/qti/v1/models/choices/logical_identifier_choice_spec.rb
|
670
671
|
- spec/lib/qti/v1/models/interactions/base_fill_blank_interaction_spec.rb
|
@@ -1022,6 +1023,7 @@ test_files:
|
|
1022
1023
|
- spec/lib/qti/sanitizer_spec.rb
|
1023
1024
|
- spec/lib/qti/v1/models/assessment_item_spec.rb
|
1024
1025
|
- spec/lib/qti/v1/models/assessment_spec.rb
|
1026
|
+
- spec/lib/qti/v1/models/base_spec.rb
|
1025
1027
|
- spec/lib/qti/v1/models/choices/fill_blank_choice_spec.rb
|
1026
1028
|
- spec/lib/qti/v1/models/choices/logical_identifier_choice_spec.rb
|
1027
1029
|
- spec/lib/qti/v1/models/interactions/base_fill_blank_interaction_spec.rb
|