ims-lti 1.1.12 → 1.2.6
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 +5 -5
- data/Changelog +10 -0
- data/README.md +4 -4
- data/lib/ims/lti/deprecated_role_checks.rb +1 -1
- data/lib/ims/lti/extensions/outcome_data.rb +47 -6
- data/lib/ims/lti/launch_params.rb +1 -1
- data/lib/ims/lti/outcome_request.rb +46 -27
- data/lib/ims/lti/outcome_response.rb +7 -3
- data/lib/ims/lti/request_validator.rb +8 -2
- data/lib/ims/lti/role_checks.rb +2 -2
- data/lib/ims/lti/tool_provider.rb +2 -2
- data/lib/ims/lti.rb +6 -2
- metadata +28 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a7fb650dcdd9fad408934d47de72f8f2e11a55605d10f23ee04944976c900cd4
|
4
|
+
data.tar.gz: 2835e4925ea33ebd4f4aac6ea8c0b47d3696a67ae548addd6f1aad5adf4f12ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98469e50f9bb313fc90facdc65c309ec286e3b71b410226d390ffa0d25b6b78ed4c1d3ca7bfec31baf27c8f949a7c83f8525dc66c313968eb8af333dd514637d
|
7
|
+
data.tar.gz: 8ab0abecf8f0f7038f91f006a1a82ddf7a3717f5532a7bfa52ba9ae2a0379fd260e6f07cdb5074cfd283ed33a4a8b692b79c99fd487aad40394586052b32733b
|
data/Changelog
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
2020-02-03 Version 1.2.4
|
2
|
+
* Add support for submittedAt date
|
3
|
+
|
4
|
+
2017-03-08 Version 1.2.0
|
5
|
+
* Don't downcase roles
|
6
|
+
|
7
|
+
2016-10-05 Version 1.1.13
|
8
|
+
* Fix Oauth::Unauthorized initialization bug
|
9
|
+
* Relax oauth dependency
|
10
|
+
|
1
11
|
2016-06-16 Version 1.1.12
|
2
12
|
* Add support for detecting observer role
|
3
13
|
|
data/README.md
CHANGED
@@ -16,14 +16,14 @@ To require the library in your project:
|
|
16
16
|
To validate the OAuth signatures you need to require the appropriate request
|
17
17
|
proxy for your application. For example:
|
18
18
|
|
19
|
+
# For a Rails 5 (and 2.3) app:
|
20
|
+
require 'oauth/request_proxy/action_controller_request'
|
21
|
+
|
19
22
|
# For a Sinatra or a Rails 3 or 4 app:
|
20
23
|
require 'oauth/request_proxy/rack_request'
|
21
24
|
# You also need to explicitly enable OAuth 1 support in the environment.rb or an initializer:
|
22
25
|
OAUTH_10_SUPPORT = true
|
23
26
|
|
24
|
-
# For a Rails 2.3 app:
|
25
|
-
require 'oauth/request_proxy/action_controller_request'
|
26
|
-
|
27
27
|
For further information see the [oauth-ruby](https://github.com/oauth-xx/oauth-ruby) project.
|
28
28
|
|
29
29
|
As a quick debugging note, if you forget that step, you'll get an error like:
|
@@ -52,7 +52,7 @@ and it will be signed with OAuth using a key/secret that both the TP and TC shar
|
|
52
52
|
This is covered in the [LTI security model](http://www.imsglobal.org/LTI/v1p1/ltiIMGv1p1.html#_Toc319560466)
|
53
53
|
|
54
54
|
Here is an example of a simple TP Sinatra app using this gem:
|
55
|
-
[LTI Tool Provider](https://github.com/instructure/
|
55
|
+
[LTI Tool Provider](https://github.com/instructure/lti1_tool_provider_example)
|
56
56
|
|
57
57
|
Once you find the `oauth_consumer_secret` based on the `oauth_consumer_key` in
|
58
58
|
the request, you can initialize a `ToolProvider` object with them and the post parameters:
|
@@ -6,7 +6,7 @@ module DeprecatedRoleChecks
|
|
6
6
|
# Check whether the Launch Parameters have a role
|
7
7
|
def has_role?(role)
|
8
8
|
role = role.downcase
|
9
|
-
@roles && @roles.any?{|r| r.index(role)}
|
9
|
+
@roles && @roles.any?{|r| r.downcase.index(role)}
|
10
10
|
end
|
11
11
|
|
12
12
|
# Convenience method for checking if the user has 'learner' or 'student' role
|
@@ -67,6 +67,12 @@ module IMS::LTI
|
|
67
67
|
accepted_outcome_types.member?("url")
|
68
68
|
end
|
69
69
|
|
70
|
+
# check if the consumer accepts a submitted at date as outcome data
|
71
|
+
def accepts_submitted_at?
|
72
|
+
accepted_outcome_types.member?("submitted_at") ||
|
73
|
+
@ext_params["ext_outcome_submission_submitted_at_accepted"] == "true"
|
74
|
+
end
|
75
|
+
|
70
76
|
def accepts_outcome_lti_launch_url?
|
71
77
|
accepted_outcome_types.member?("lti_launch_url")
|
72
78
|
end
|
@@ -75,8 +81,19 @@ module IMS::LTI
|
|
75
81
|
!!@ext_params["outcome_result_total_score_accepted"]
|
76
82
|
end
|
77
83
|
|
84
|
+
def accepts_needs_additional_review?
|
85
|
+
@ext_params["ext_outcome_submission_needs_additional_review_accepted"] == "true"
|
86
|
+
end
|
87
|
+
|
88
|
+
def accepts_prioritize_non_tool_grade?
|
89
|
+
@ext_params["ext_outcome_submission_prioritize_non_tool_grade_accepted"] == "true"
|
90
|
+
end
|
91
|
+
|
78
92
|
# POSTs the given score to the Tool Consumer with a replaceResult and
|
79
|
-
# adds the specified data.
|
93
|
+
# adds the specified data.
|
94
|
+
#
|
95
|
+
# The data hash can have the keys "text", "cdata_text", "url", "submitted_at"
|
96
|
+
# "needs_additional_review", "prioritize_non_tool_grade", or "lti_launch_url"
|
80
97
|
#
|
81
98
|
# If both cdata_text and text are sent, cdata_text will be used
|
82
99
|
#
|
@@ -93,7 +110,8 @@ module IMS::LTI
|
|
93
110
|
|
94
111
|
# POSTs the given score to the Tool Consumer with a replaceResult and
|
95
112
|
# adds the specified data. The options hash can have the keys
|
96
|
-
# :text, :cdata_text, :url, :lti_launch_url, :score,
|
113
|
+
# :text, :cdata_text, :url, :submitted_at, :lti_launch_url, :score,
|
114
|
+
# :needs_additional_review, or :total_score
|
97
115
|
#
|
98
116
|
# If both cdata_text and text are sent, cdata_text will be used
|
99
117
|
# If both total_score and score are sent, total_score will be used
|
@@ -110,6 +128,9 @@ module IMS::LTI
|
|
110
128
|
req.outcome_cdata_text = opts[:cdata_text]
|
111
129
|
req.outcome_text = opts[:text]
|
112
130
|
req.outcome_url = opts[:url]
|
131
|
+
req.submitted_at = opts[:submitted_at]
|
132
|
+
req.needs_additional_review = opts[:needs_additional_review]
|
133
|
+
req.prioritize_non_tool_grade = opts[:prioritize_non_tool_grade]
|
113
134
|
req.outcome_lti_launch_url = opts[:lti_launch_url]
|
114
135
|
req.total_score = opts[:total_score]
|
115
136
|
req.post_replace_result!(opts[:score])
|
@@ -120,9 +141,9 @@ module IMS::LTI
|
|
120
141
|
include IMS::LTI::Extensions::ExtensionBase
|
121
142
|
include Base
|
122
143
|
|
123
|
-
OUTCOME_DATA_TYPES = %w{text url lti_launch_url}
|
144
|
+
OUTCOME_DATA_TYPES = %w{text url lti_launch_url submitted_at}
|
124
145
|
|
125
|
-
# a list of the outcome data types accepted, currently only 'url' and
|
146
|
+
# a list of the outcome data types accepted, currently only 'url', 'submitted_at' and
|
126
147
|
# 'text' are valid
|
127
148
|
#
|
128
149
|
# tc.outcome_data_values_accepted(['url', 'text'])
|
@@ -150,7 +171,14 @@ module IMS::LTI
|
|
150
171
|
include IMS::LTI::Extensions::ExtensionBase
|
151
172
|
include Base
|
152
173
|
|
153
|
-
attr_accessor :outcome_text,
|
174
|
+
attr_accessor :outcome_text,
|
175
|
+
:outcome_url,
|
176
|
+
:submitted_at,
|
177
|
+
:outcome_lti_launch_url,
|
178
|
+
:outcome_cdata_text,
|
179
|
+
:total_score,
|
180
|
+
:needs_additional_review,
|
181
|
+
:prioritize_non_tool_grade
|
154
182
|
|
155
183
|
def result_values(node)
|
156
184
|
super
|
@@ -178,6 +206,15 @@ module IMS::LTI
|
|
178
206
|
end
|
179
207
|
end
|
180
208
|
|
209
|
+
def details(node)
|
210
|
+
super
|
211
|
+
return unless has_details_data?
|
212
|
+
|
213
|
+
node.submittedAt submitted_at if submitted_at
|
214
|
+
node.needsAdditionalReview if needs_additional_review
|
215
|
+
node.prioritizeNonToolGrade if prioritize_non_tool_grade
|
216
|
+
end
|
217
|
+
|
181
218
|
def score
|
182
219
|
total_score ? nil : @score
|
183
220
|
end
|
@@ -186,6 +223,10 @@ module IMS::LTI
|
|
186
223
|
!!outcome_text || !!outcome_url || !!outcome_lti_launch_url || !!outcome_cdata_text || !!total_score || super
|
187
224
|
end
|
188
225
|
|
226
|
+
def has_details_data?
|
227
|
+
!!submitted_at || !!needs_additional_review || !!prioritize_non_tool_grade
|
228
|
+
end
|
229
|
+
|
189
230
|
def extention_process_xml(doc)
|
190
231
|
super
|
191
232
|
@outcome_text = doc.get_text("//resultRecord/result/resultData/text")
|
@@ -196,4 +237,4 @@ module IMS::LTI
|
|
196
237
|
|
197
238
|
end
|
198
239
|
end
|
199
|
-
end
|
240
|
+
end
|
@@ -148,23 +148,63 @@ module IMS::LTI
|
|
148
148
|
extention_process_xml(doc)
|
149
149
|
end
|
150
150
|
|
151
|
+
def generate_request_xml
|
152
|
+
raise IMS::LTI::InvalidLTIConfigError, "`@operation` and `@lis_result_sourcedid` are required" unless has_request_xml_attributes?
|
153
|
+
builder = Builder::XmlMarkup.new #(:indent=>2)
|
154
|
+
builder.instruct!
|
155
|
+
|
156
|
+
builder.imsx_POXEnvelopeRequest("xmlns" => "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0") do |env|
|
157
|
+
env.imsx_POXHeader do |header|
|
158
|
+
header.imsx_POXRequestHeaderInfo do |info|
|
159
|
+
info.imsx_version "V1.0"
|
160
|
+
info.imsx_messageIdentifier @message_identifier || IMS::LTI::generate_identifier
|
161
|
+
end
|
162
|
+
end
|
163
|
+
env.imsx_POXBody do |body|
|
164
|
+
body.tag!(@operation + 'Request') do |request|
|
165
|
+
request.resultRecord do |record|
|
166
|
+
record.sourcedGUID do |guid|
|
167
|
+
guid.sourcedId @lis_result_sourcedid
|
168
|
+
end
|
169
|
+
results(record)
|
170
|
+
end
|
171
|
+
submission_details(request)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
151
177
|
private
|
152
|
-
|
178
|
+
|
153
179
|
def extention_process_xml(doc)
|
154
180
|
end
|
155
|
-
|
181
|
+
|
156
182
|
def has_result_data?
|
157
183
|
!!score
|
158
184
|
end
|
159
|
-
|
185
|
+
|
186
|
+
def has_details_data?
|
187
|
+
false
|
188
|
+
end
|
189
|
+
|
160
190
|
def results(node)
|
161
191
|
return unless has_result_data?
|
162
|
-
|
192
|
+
|
163
193
|
node.result do |res|
|
164
194
|
result_values(res)
|
165
195
|
end
|
166
196
|
end
|
167
197
|
|
198
|
+
def submission_details(request)
|
199
|
+
return unless has_details_data?
|
200
|
+
request.submissionDetails do |record|
|
201
|
+
details(record)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def details(record)
|
206
|
+
end
|
207
|
+
|
168
208
|
def result_values(node)
|
169
209
|
if score
|
170
210
|
node.resultScore do |res_score|
|
@@ -178,29 +218,8 @@ module IMS::LTI
|
|
178
218
|
@consumer_key && @consumer_secret && @lis_outcome_service_url && @lis_result_sourcedid && @operation
|
179
219
|
end
|
180
220
|
|
181
|
-
def
|
182
|
-
|
183
|
-
builder.instruct!
|
184
|
-
|
185
|
-
builder.imsx_POXEnvelopeRequest("xmlns" => "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0") do |env|
|
186
|
-
env.imsx_POXHeader do |header|
|
187
|
-
header.imsx_POXRequestHeaderInfo do |info|
|
188
|
-
info.imsx_version "V1.0"
|
189
|
-
info.imsx_messageIdentifier @message_identifier || IMS::LTI::generate_identifier
|
190
|
-
end
|
191
|
-
end
|
192
|
-
env.imsx_POXBody do |body|
|
193
|
-
body.tag!(@operation + 'Request') do |request|
|
194
|
-
request.resultRecord do |record|
|
195
|
-
record.sourcedGUID do |guid|
|
196
|
-
guid.sourcedId @lis_result_sourcedid
|
197
|
-
end
|
198
|
-
results(record)
|
199
|
-
end
|
200
|
-
end
|
201
|
-
end
|
202
|
-
end
|
221
|
+
def has_request_xml_attributes?
|
222
|
+
@operation && @lis_result_sourcedid
|
203
223
|
end
|
204
|
-
|
205
224
|
end
|
206
225
|
end
|
@@ -46,7 +46,7 @@ module IMS::LTI
|
|
46
46
|
#
|
47
47
|
class OutcomeResponse
|
48
48
|
include IMS::LTI::Extensions::Base
|
49
|
-
|
49
|
+
|
50
50
|
attr_accessor :request_type, :score, :message_identifier, :response_code,
|
51
51
|
:post_response, :code_major, :severity, :description, :operation,
|
52
52
|
:message_ref_identifier
|
@@ -70,7 +70,7 @@ module IMS::LTI
|
|
70
70
|
response = OutcomeResponse.new
|
71
71
|
response.process_post_response(post_response)
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
def process_post_response(post_response)
|
75
75
|
self.post_response = post_response
|
76
76
|
self.response_code = post_response.code
|
@@ -105,7 +105,11 @@ module IMS::LTI
|
|
105
105
|
|
106
106
|
# Parse Outcome Response data from XML
|
107
107
|
def process_xml(xml)
|
108
|
-
|
108
|
+
begin
|
109
|
+
doc = REXML::Document.new xml
|
110
|
+
rescue => e
|
111
|
+
raise IMS::LTI::XMLParseError, "#{e}\nOriginal xml: '#{xml}'"
|
112
|
+
end
|
109
113
|
@message_identifier = doc.text("//imsx_statusInfo/imsx_messageIdentifier").to_s
|
110
114
|
@code_major = doc.text("//imsx_statusInfo/imsx_codeMajor")
|
111
115
|
@code_major.downcase! if @code_major
|
@@ -18,14 +18,20 @@ module IMS::LTI
|
|
18
18
|
def valid_request?(request, handle_error=true)
|
19
19
|
begin
|
20
20
|
@oauth_signature_validator = OAuth::Signature.build(request, :consumer_secret => @consumer_secret)
|
21
|
-
@oauth_signature_validator.verify() or raise OAuth::Unauthorized
|
21
|
+
@oauth_signature_validator.verify() or raise OAuth::Unauthorized.new(request)
|
22
22
|
true
|
23
|
-
rescue OAuth::Signature::UnknownSignatureMethod
|
23
|
+
rescue OAuth::Signature::UnknownSignatureMethod
|
24
24
|
if handle_error
|
25
25
|
false
|
26
26
|
else
|
27
27
|
raise $!
|
28
28
|
end
|
29
|
+
rescue OAuth::Unauthorized
|
30
|
+
if handle_error
|
31
|
+
false
|
32
|
+
else
|
33
|
+
raise OAuth::Unauthorized.new(request)
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
|
data/lib/ims/lti/role_checks.rb
CHANGED
@@ -16,7 +16,7 @@ module IMS::LTI
|
|
16
16
|
# Check whether the Launch Parameters have a given role
|
17
17
|
def has_exact_role?(role)
|
18
18
|
role = role.downcase
|
19
|
-
@roles && @roles.any? { |r| r == role }
|
19
|
+
@roles && @roles.any? { |r| r.downcase == role }
|
20
20
|
end
|
21
21
|
|
22
22
|
# Check whether the Launch Parameters have a given role ignoring
|
@@ -25,7 +25,7 @@ module IMS::LTI
|
|
25
25
|
# will return true if the role is `urn:lti:role:ims/lis/Instructor/GuestInstructor`
|
26
26
|
def has_base_role?(role)
|
27
27
|
role = role.downcase
|
28
|
-
@roles && @roles.any? { |r| r.start_with?(role) }
|
28
|
+
@roles && @roles.any? { |r| r.downcase.start_with?(role) }
|
29
29
|
end
|
30
30
|
|
31
31
|
# Convenience method for checking if the user is the system administrator of the TC
|
@@ -135,9 +135,9 @@ module IMS::LTI
|
|
135
135
|
:consumer_secret => @consumer_secret,
|
136
136
|
:lis_outcome_service_url => lis_outcome_service_url,
|
137
137
|
:lis_result_sourcedid =>lis_result_sourcedid)
|
138
|
-
|
138
|
+
|
139
139
|
extend_outcome_request(@outcome_requests.last)
|
140
140
|
end
|
141
|
-
|
141
|
+
|
142
142
|
end
|
143
143
|
end
|
data/lib/ims/lti.rb
CHANGED
@@ -2,6 +2,7 @@ require 'oauth'
|
|
2
2
|
require 'builder'
|
3
3
|
require "rexml/document"
|
4
4
|
require 'cgi'
|
5
|
+
require 'securerandom'
|
5
6
|
|
6
7
|
module IMS # :nodoc:
|
7
8
|
|
@@ -25,13 +26,16 @@ module IMS # :nodoc:
|
|
25
26
|
#
|
26
27
|
# require 'ims/lti'
|
27
28
|
module LTI
|
28
|
-
|
29
|
+
|
29
30
|
# The versions of LTI this library supports
|
30
31
|
VERSIONS = %w{1.0 1.1}
|
31
|
-
|
32
|
+
|
32
33
|
class InvalidLTIConfigError < StandardError
|
33
34
|
end
|
34
35
|
|
36
|
+
class XMLParseError < StandardError
|
37
|
+
end
|
38
|
+
|
35
39
|
# POST a signed oauth request with the given key/secret/data
|
36
40
|
def self.post_service_request(key, secret, url, content_type, body)
|
37
41
|
raise IMS::LTI::InvalidLTIConfigError, "" unless key && secret
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ims-lti
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Instructure
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: builder
|
@@ -16,56 +16,60 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
19
|
+
version: '1.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '4.0'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
29
|
+
version: '1.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '4.0'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: oauth
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
|
-
- - "
|
37
|
+
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
33
39
|
version: 0.4.5
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0.6'
|
34
43
|
type: :runtime
|
35
44
|
prerelease: false
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
37
46
|
requirements:
|
38
|
-
- - "
|
47
|
+
- - ">="
|
39
48
|
- !ruby/object:Gem::Version
|
40
49
|
version: 0.4.5
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0.6'
|
41
53
|
- !ruby/object:Gem::Dependency
|
42
54
|
name: rspec
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
44
56
|
requirements:
|
45
|
-
- - "
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
57
|
+
- - "~>"
|
53
58
|
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
|
-
-
|
56
|
-
name: ruby-debug
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
59
|
+
version: '3.0'
|
60
|
+
- - ">"
|
60
61
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
+
version: '3.0'
|
62
63
|
type: :development
|
63
64
|
prerelease: false
|
64
65
|
version_requirements: !ruby/object:Gem::Requirement
|
65
66
|
requirements:
|
66
|
-
- - "
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.0'
|
70
|
+
- - ">"
|
67
71
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
72
|
+
version: '3.0'
|
69
73
|
description:
|
70
74
|
email:
|
71
75
|
executables: []
|
@@ -111,10 +115,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
115
|
- !ruby/object:Gem::Version
|
112
116
|
version: '0'
|
113
117
|
requirements: []
|
114
|
-
|
115
|
-
rubygems_version: 2.5.1
|
118
|
+
rubygems_version: 3.1.6
|
116
119
|
signing_key:
|
117
120
|
specification_version: 4
|
118
121
|
summary: Ruby library for creating IMS LTI tool providers and consumers
|
119
122
|
test_files: []
|
120
|
-
has_rdoc:
|