ims-lti 1.2.4 → 2.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Changelog.txt +0 -0
- data/LICENSE.txt +22 -0
- data/README.md +25 -91
- data/lib/ims/lis/context_type/handles.rb +10 -0
- data/lib/ims/lis/context_type/urns.rb +10 -0
- data/lib/ims/lis/roles/context/handles.rb +60 -0
- data/lib/ims/lis/roles/context/urns.rb +60 -0
- data/lib/ims/lis/roles/institution/handles.rb +22 -0
- data/lib/ims/lis/roles/institution/urns.rb +22 -0
- data/lib/ims/lis/roles/system/handles.rb +15 -0
- data/lib/ims/lis/roles/system/urns.rb +15 -0
- data/lib/ims/lis/statuses/simple_names.rb +9 -0
- data/lib/ims/lis/statuses/uris.rb +9 -0
- data/lib/ims/lis.rb +14 -0
- data/lib/ims/lti/converters/time_json_converter.rb +13 -0
- data/lib/ims/lti/converters.rb +5 -0
- data/lib/ims/lti/errors/authentication_failed_error.rb +11 -0
- data/lib/ims/lti/errors/invalid_lti_config_error.rb +4 -0
- data/lib/ims/lti/errors/invalid_tool_consumer_profile.rb +4 -0
- data/lib/ims/lti/errors/tool_proxy_registration_error.rb +15 -0
- data/lib/ims/lti/errors.rb +8 -0
- data/lib/ims/lti/models/base_url_choice.rb +15 -0
- data/lib/ims/lti/models/base_url_selector.rb +5 -0
- data/lib/ims/lti/models/contact.rb +5 -0
- data/lib/ims/lti/models/content_item_container.rb +14 -0
- data/lib/ims/lti/models/content_item_placement.rb +20 -0
- data/lib/ims/lti/models/content_items/content_item.rb +32 -0
- data/lib/ims/lti/models/content_items/file_item.rb +15 -0
- data/lib/ims/lti/models/content_items/lti_link_item.rb +14 -0
- data/lib/ims/lti/models/content_items.rb +7 -0
- data/lib/ims/lti/models/icon_endpoint.rb +5 -0
- data/lib/ims/lti/models/icon_info.rb +6 -0
- data/lib/ims/lti/models/image.rb +7 -0
- data/lib/ims/lti/models/localized_name.rb +12 -0
- data/lib/ims/lti/models/localized_text.rb +12 -0
- data/lib/ims/lti/models/lti_model.rb +227 -0
- data/lib/ims/lti/models/membership_service/agent.rb +11 -0
- data/lib/ims/lti/models/membership_service/container.rb +11 -0
- data/lib/ims/lti/models/membership_service/context.rb +11 -0
- data/lib/ims/lti/models/membership_service/lis_membership_container.rb +13 -0
- data/lib/ims/lti/models/membership_service/lis_person.rb +13 -0
- data/lib/ims/lti/models/membership_service/membership.rb +14 -0
- data/lib/ims/lti/models/membership_service/organization.rb +14 -0
- data/lib/ims/lti/models/membership_service/page.rb +16 -0
- data/lib/ims/lti/models/membership_service/person.rb +13 -0
- data/lib/ims/lti/models/membership_service.rb +16 -0
- data/lib/ims/lti/models/message_handler.rb +14 -0
- data/lib/ims/lti/models/messages/basic_lti_launch_request.rb +24 -0
- data/lib/ims/lti/models/messages/content_item_selection.rb +32 -0
- data/lib/ims/lti/models/messages/content_item_selection_request.rb +26 -0
- data/lib/ims/lti/models/messages/message.rb +222 -0
- data/lib/ims/lti/models/messages/registration_request.rb +20 -0
- data/lib/ims/lti/models/messages/request_message.rb +12 -0
- data/lib/ims/lti/models/messages/tool_proxy_update_request.rb +15 -0
- data/lib/ims/lti/models/messages.rb +11 -0
- data/lib/ims/lti/models/parameter.rb +28 -0
- data/lib/ims/lti/models/product_family.rb +8 -0
- data/lib/ims/lti/models/product_info.rb +26 -0
- data/lib/ims/lti/models/product_instance.rb +10 -0
- data/lib/ims/lti/models/resource_handler.rb +22 -0
- data/lib/ims/lti/models/resource_type.rb +6 -0
- data/lib/ims/lti/models/rest_service.rb +30 -0
- data/lib/ims/lti/models/rest_service_profile.rb +15 -0
- data/lib/ims/lti/models/security_contract.rb +21 -0
- data/lib/ims/lti/models/security_profile.rb +10 -0
- data/lib/ims/lti/models/serializable.rb +12 -0
- data/lib/ims/lti/models/service_owner.rb +26 -0
- data/lib/ims/lti/models/service_provider.rb +11 -0
- data/lib/ims/lti/models/tool_consumer_profile.rb +45 -0
- data/lib/ims/lti/models/tool_profile.rb +35 -0
- data/lib/ims/lti/models/tool_proxy.rb +21 -0
- data/lib/ims/lti/models/tool_setting.rb +12 -0
- data/lib/ims/lti/models/tool_setting_container.rb +14 -0
- data/lib/ims/lti/models/vendor.rb +28 -0
- data/lib/ims/lti/models.rb +38 -0
- data/lib/ims/lti/serializers/base.rb +125 -0
- data/lib/ims/lti/serializers/membership_service/agent_serializer.rb +5 -0
- data/lib/ims/lti/serializers/membership_service/container_serializer.rb +6 -0
- data/lib/ims/lti/serializers/membership_service/context_serializer.rb +9 -0
- data/lib/ims/lti/serializers/membership_service/lis_membership_container_serializer.rb +9 -0
- data/lib/ims/lti/serializers/membership_service/lis_person_serializer.rb +11 -0
- data/lib/ims/lti/serializers/membership_service/membership_serializer.rb +7 -0
- data/lib/ims/lti/serializers/membership_service/organization_serializer.rb +8 -0
- data/lib/ims/lti/serializers/membership_service/page_serializer.rb +10 -0
- data/lib/ims/lti/serializers/membership_service/person_serializer.rb +8 -0
- data/lib/ims/lti/serializers/membership_service.rb +13 -0
- data/lib/ims/lti/serializers.rb +6 -0
- data/lib/ims/lti/services/authentication_service.rb +67 -0
- data/lib/ims/lti/services/message_authenticator.rb +80 -0
- data/lib/ims/lti/services/oauth2_client.rb +18 -0
- data/lib/ims/lti/{tool_config.rb → services/tool_config.rb} +26 -34
- data/lib/ims/lti/services/tool_consumer_profile_service.rb +16 -0
- data/lib/ims/lti/services/tool_proxy_registration_service.rb +84 -0
- data/lib/ims/lti/services/tool_proxy_validator.rb +182 -0
- data/lib/ims/lti/services.rb +11 -0
- data/lib/ims/lti/version.rb +5 -0
- data/lib/ims/lti.rb +13 -63
- data/lib/ims.rb +4 -1
- metadata +266 -44
- data/Changelog +0 -54
- data/LICENSE +0 -18
- data/lib/ims/lti/deprecated_role_checks.rb +0 -52
- data/lib/ims/lti/extensions/canvas.rb +0 -122
- data/lib/ims/lti/extensions/content.rb +0 -209
- data/lib/ims/lti/extensions/outcome_data.rb +0 -216
- data/lib/ims/lti/extensions.rb +0 -45
- data/lib/ims/lti/launch_params.rb +0 -166
- data/lib/ims/lti/outcome_request.rb +0 -225
- data/lib/ims/lti/outcome_response.rb +0 -166
- data/lib/ims/lti/request_validator.rb +0 -56
- data/lib/ims/lti/role_checks.rb +0 -101
- data/lib/ims/lti/tool_base.rb +0 -29
- data/lib/ims/lti/tool_consumer.rb +0 -86
- data/lib/ims/lti/tool_provider.rb +0 -143
@@ -1,225 +0,0 @@
|
|
1
|
-
module IMS::LTI
|
2
|
-
# Class for consuming/generating LTI Outcome Requests
|
3
|
-
#
|
4
|
-
# Outcome Request documentation: http://www.imsglobal.org/lti/v1p1pd/ltiIMGv1p1pd.html#_Toc309649691
|
5
|
-
#
|
6
|
-
# This class can be used by both Tool Providers and Tool Consumers. Each will
|
7
|
-
# use it a bit differently. The Tool Provider will use it to POST an OAuth-signed
|
8
|
-
# request to a TC. A Tool Consumer will use it to parse such a request from a TP.
|
9
|
-
#
|
10
|
-
# === Tool Provider Usage
|
11
|
-
# An OutcomeRequest will generally be created through a configured ToolProvider
|
12
|
-
# object. See the ToolProvider documentation.
|
13
|
-
#
|
14
|
-
# === Tool Consumer Usage
|
15
|
-
# When an outcome request is sent from a TP the body of the request is XML.
|
16
|
-
# This class parses that XML and provides a simple interface for accessing the
|
17
|
-
# information in the request. Typical usage would be:
|
18
|
-
#
|
19
|
-
# # create an OutcomeRequest from the request object
|
20
|
-
# req = IMS::LTI::OutcomeRequest.from_post_request(request)
|
21
|
-
#
|
22
|
-
# # access the source id to identify the user who's grade you'd like to access
|
23
|
-
# req.lis_result_sourcedid
|
24
|
-
#
|
25
|
-
# # process the request
|
26
|
-
# if req.replace_request?
|
27
|
-
# # set a new score for the user
|
28
|
-
# elsif req.read_request?
|
29
|
-
# # return the score for the user
|
30
|
-
# elsif req.delete_request?
|
31
|
-
# # clear the score for the user
|
32
|
-
# else
|
33
|
-
# # return an unsupported OutcomeResponse
|
34
|
-
# end
|
35
|
-
class OutcomeRequest < ToolBase
|
36
|
-
include IMS::LTI::Extensions::Base
|
37
|
-
|
38
|
-
REPLACE_REQUEST = 'replaceResult'
|
39
|
-
DELETE_REQUEST = 'deleteResult'
|
40
|
-
READ_REQUEST = 'readResult'
|
41
|
-
|
42
|
-
attr_accessor :operation, :score, :outcome_response, :message_identifier,
|
43
|
-
:lis_outcome_service_url, :lis_result_sourcedid,
|
44
|
-
:consumer_key, :consumer_secret, :post_request
|
45
|
-
|
46
|
-
# Create a new OutcomeRequest
|
47
|
-
#
|
48
|
-
# @param opts [Hash] initialization hash
|
49
|
-
def initialize(opts={})
|
50
|
-
opts.each_pair do |key, val|
|
51
|
-
self.send("#{key}=", val) if self.respond_to?("#{key}=")
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Convenience method for creating a new OutcomeRequest from a request object
|
56
|
-
#
|
57
|
-
# req = IMS::LTI::OutcomeRequest.from_post_request(request)
|
58
|
-
def self.from_post_request(post_request)
|
59
|
-
request = OutcomeRequest.new
|
60
|
-
request.process_post_request(post_request)
|
61
|
-
end
|
62
|
-
|
63
|
-
def process_post_request(post_request)
|
64
|
-
self.post_request = post_request
|
65
|
-
if post_request.body.respond_to?(:read)
|
66
|
-
xml = post_request.body.read
|
67
|
-
post_request.body.rewind
|
68
|
-
else
|
69
|
-
xml = post_request.body
|
70
|
-
end
|
71
|
-
self.process_xml(xml)
|
72
|
-
self
|
73
|
-
end
|
74
|
-
|
75
|
-
# POSTs the given score to the Tool Consumer with a replaceResult
|
76
|
-
#
|
77
|
-
# @return [OutcomeResponse] The response from the Tool Consumer
|
78
|
-
def post_replace_result!(score)
|
79
|
-
@operation = REPLACE_REQUEST
|
80
|
-
@score = score
|
81
|
-
post_outcome_request
|
82
|
-
end
|
83
|
-
|
84
|
-
# POSTs a deleteResult to the Tool Consumer
|
85
|
-
#
|
86
|
-
# @return [OutcomeResponse] The response from the Tool Consumer
|
87
|
-
def post_delete_result!
|
88
|
-
@operation = DELETE_REQUEST
|
89
|
-
post_outcome_request
|
90
|
-
end
|
91
|
-
|
92
|
-
# POSTs a readResult to the Tool Consumer
|
93
|
-
#
|
94
|
-
# @return [OutcomeResponse] The response from the Tool Consumer
|
95
|
-
def post_read_result!
|
96
|
-
@operation = READ_REQUEST
|
97
|
-
post_outcome_request
|
98
|
-
end
|
99
|
-
|
100
|
-
# Check whether this request is a replaceResult request
|
101
|
-
def replace_request?
|
102
|
-
@operation == REPLACE_REQUEST
|
103
|
-
end
|
104
|
-
|
105
|
-
# Check whether this request is a deleteResult request
|
106
|
-
def delete_request?
|
107
|
-
@operation == DELETE_REQUEST
|
108
|
-
end
|
109
|
-
|
110
|
-
# Check whether this request is a readResult request
|
111
|
-
def read_request?
|
112
|
-
@operation == READ_REQUEST
|
113
|
-
end
|
114
|
-
|
115
|
-
# Check whether the last outcome POST was successful
|
116
|
-
def outcome_post_successful?
|
117
|
-
@outcome_response && @outcome_response.success?
|
118
|
-
end
|
119
|
-
|
120
|
-
# POST an OAuth signed request to the Tool Consumer
|
121
|
-
#
|
122
|
-
# @return [OutcomeResponse] The response from the Tool Consumer
|
123
|
-
def post_outcome_request
|
124
|
-
raise IMS::LTI::InvalidLTIConfigError, "" unless has_required_attributes?
|
125
|
-
|
126
|
-
res = post_service_request(@lis_outcome_service_url,
|
127
|
-
'application/xml',
|
128
|
-
generate_request_xml)
|
129
|
-
|
130
|
-
@outcome_response = extend_outcome_response(OutcomeResponse.new)
|
131
|
-
@outcome_response.process_post_response(res)
|
132
|
-
end
|
133
|
-
|
134
|
-
# Parse Outcome Request data from XML
|
135
|
-
def process_xml(xml)
|
136
|
-
doc = REXML::Document.new xml
|
137
|
-
@message_identifier = doc.text("//imsx_POXRequestHeaderInfo/imsx_messageIdentifier")
|
138
|
-
@lis_result_sourcedid = doc.text("//resultRecord/sourcedGUID/sourcedId")
|
139
|
-
|
140
|
-
if REXML::XPath.first(doc, "//deleteResultRequest")
|
141
|
-
@operation = DELETE_REQUEST
|
142
|
-
elsif REXML::XPath.first(doc, "//readResultRequest")
|
143
|
-
@operation = READ_REQUEST
|
144
|
-
elsif REXML::XPath.first(doc, "//replaceResultRequest")
|
145
|
-
@operation = REPLACE_REQUEST
|
146
|
-
@score = doc.get_text("//resultRecord/result/resultScore/textString")
|
147
|
-
end
|
148
|
-
extention_process_xml(doc)
|
149
|
-
end
|
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
|
-
|
177
|
-
private
|
178
|
-
|
179
|
-
def extention_process_xml(doc)
|
180
|
-
end
|
181
|
-
|
182
|
-
def has_result_data?
|
183
|
-
!!score
|
184
|
-
end
|
185
|
-
|
186
|
-
def has_details_data?
|
187
|
-
false
|
188
|
-
end
|
189
|
-
|
190
|
-
def results(node)
|
191
|
-
return unless has_result_data?
|
192
|
-
|
193
|
-
node.result do |res|
|
194
|
-
result_values(res)
|
195
|
-
end
|
196
|
-
end
|
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
|
-
|
208
|
-
def result_values(node)
|
209
|
-
if score
|
210
|
-
node.resultScore do |res_score|
|
211
|
-
res_score.language "en" # 'en' represents the format of the number
|
212
|
-
res_score.textString score.to_s
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
def has_required_attributes?
|
218
|
-
@consumer_key && @consumer_secret && @lis_outcome_service_url && @lis_result_sourcedid && @operation
|
219
|
-
end
|
220
|
-
|
221
|
-
def has_request_xml_attributes?
|
222
|
-
@operation && @lis_result_sourcedid
|
223
|
-
end
|
224
|
-
end
|
225
|
-
end
|
@@ -1,166 +0,0 @@
|
|
1
|
-
module IMS::LTI
|
2
|
-
# Class for consuming/generating LTI Outcome Responses
|
3
|
-
#
|
4
|
-
# Response documentation: http://www.imsglobal.org/lti/v1p1pd/ltiIMGv1p1pd.html#_Toc309649691
|
5
|
-
#
|
6
|
-
# Error code documentation: http://www.imsglobal.org/gws/gwsv1p0/imsgws_baseProfv1p0.html#1639667
|
7
|
-
#
|
8
|
-
# This class can be used by both Tool Providers and Tool Consumers. Each will
|
9
|
-
# use it a bit differently. The Tool Provider will use it parse the result of
|
10
|
-
# an OutcomeRequest to the Tool Consumer. A Tool Consumer will use it generate
|
11
|
-
# proper response XML to send back to a Tool Provider
|
12
|
-
#
|
13
|
-
# === Tool Provider Usage
|
14
|
-
# An OutcomeResponse will generally be created when POSTing an OutcomeRequest
|
15
|
-
# through a configured ToolProvider. See the ToolProvider documentation for
|
16
|
-
# typical usage.
|
17
|
-
#
|
18
|
-
# === Tool Consumer Usage
|
19
|
-
# When an outcome request is sent from a Tool Provider the body of the request
|
20
|
-
# is XML. This class parses that XML and provides a simple interface for
|
21
|
-
# accessing the information in the request. Typical usage would be:
|
22
|
-
#
|
23
|
-
# # create a new response and set the appropriate values
|
24
|
-
# res = IMS::LTI::OutcomeResponse.new
|
25
|
-
# res.message_ref_identifier = outcome_request.message_identifier
|
26
|
-
# res.operation = outcome_request.operation
|
27
|
-
# res.code_major = 'success'
|
28
|
-
# res.severity = 'status'
|
29
|
-
#
|
30
|
-
# # set a description (optional) and other information based on the type of response
|
31
|
-
# if outcome_request.replace_request?
|
32
|
-
# res.description = "Your old score of 0 has been replaced with #{outcome_request.score}"
|
33
|
-
# elsif outcome_request.read_request?
|
34
|
-
# res.description = "You score is 50"
|
35
|
-
# res.score = 50
|
36
|
-
# elsif outcome_request.delete_request?
|
37
|
-
# res.description = "You score has been cleared"
|
38
|
-
# else
|
39
|
-
# res.code_major = 'unsupported'
|
40
|
-
# res.severity = 'status'
|
41
|
-
# res.description = "#{outcome_request.operation} is not supported"
|
42
|
-
# end
|
43
|
-
#
|
44
|
-
# # the generated xml is returned to the Tool Provider
|
45
|
-
# res.generate_response_xml
|
46
|
-
#
|
47
|
-
class OutcomeResponse
|
48
|
-
include IMS::LTI::Extensions::Base
|
49
|
-
|
50
|
-
attr_accessor :request_type, :score, :message_identifier, :response_code,
|
51
|
-
:post_response, :code_major, :severity, :description, :operation,
|
52
|
-
:message_ref_identifier
|
53
|
-
|
54
|
-
CODE_MAJOR_CODES = %w{success processing failure unsupported}
|
55
|
-
SEVERITY_CODES = %w{status warning error}
|
56
|
-
|
57
|
-
# Create a new OutcomeResponse
|
58
|
-
#
|
59
|
-
# @param opts [Hash] initialization hash
|
60
|
-
def initialize(opts={})
|
61
|
-
opts.each_pair do |key, val|
|
62
|
-
self.send("#{key}=", val) if self.respond_to?("#{key}=")
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# Convenience method for creating a new OutcomeResponse from a response object
|
67
|
-
#
|
68
|
-
# req = IMS::LTI::OutcomeResponse.from_post_response(response)
|
69
|
-
def self.from_post_response(post_response)
|
70
|
-
response = OutcomeResponse.new
|
71
|
-
response.process_post_response(post_response)
|
72
|
-
end
|
73
|
-
|
74
|
-
def process_post_response(post_response)
|
75
|
-
self.post_response = post_response
|
76
|
-
self.response_code = post_response.code
|
77
|
-
xml = post_response.body
|
78
|
-
self.process_xml(xml)
|
79
|
-
self
|
80
|
-
end
|
81
|
-
|
82
|
-
def success?
|
83
|
-
@code_major == 'success'
|
84
|
-
end
|
85
|
-
|
86
|
-
def processing?
|
87
|
-
@code_major == 'processing'
|
88
|
-
end
|
89
|
-
|
90
|
-
def failure?
|
91
|
-
@code_major == 'failure'
|
92
|
-
end
|
93
|
-
|
94
|
-
def unsupported?
|
95
|
-
@code_major == 'unsupported'
|
96
|
-
end
|
97
|
-
|
98
|
-
def has_warning?
|
99
|
-
@severity == 'warning'
|
100
|
-
end
|
101
|
-
|
102
|
-
def has_error?
|
103
|
-
@severity == 'error'
|
104
|
-
end
|
105
|
-
|
106
|
-
# Parse Outcome Response data from XML
|
107
|
-
def process_xml(xml)
|
108
|
-
begin
|
109
|
-
doc = REXML::Document.new xml
|
110
|
-
rescue => e
|
111
|
-
raise IMS::LTI::XMLParseError, "#{e}\nOriginal xml: '#{xml}'"
|
112
|
-
end
|
113
|
-
@message_identifier = doc.text("//imsx_statusInfo/imsx_messageIdentifier").to_s
|
114
|
-
@code_major = doc.text("//imsx_statusInfo/imsx_codeMajor")
|
115
|
-
@code_major.downcase! if @code_major
|
116
|
-
@severity = doc.text("//imsx_statusInfo/imsx_severity")
|
117
|
-
@severity.downcase! if @severity
|
118
|
-
@description = doc.text("//imsx_statusInfo/imsx_description")
|
119
|
-
@description = @description.to_s if @description
|
120
|
-
@message_ref_identifier = doc.text("//imsx_statusInfo/imsx_messageRefIdentifier")
|
121
|
-
@operation = doc.text("//imsx_statusInfo/imsx_operationRefIdentifier")
|
122
|
-
@score = doc.text("//readResultResponse//resultScore/textString")
|
123
|
-
@score = @score.to_s if @score
|
124
|
-
end
|
125
|
-
|
126
|
-
# Generate XML based on the current configuration
|
127
|
-
# @return [String] The response xml
|
128
|
-
def generate_response_xml
|
129
|
-
builder = Builder::XmlMarkup.new
|
130
|
-
builder.instruct!
|
131
|
-
|
132
|
-
builder.imsx_POXEnvelopeResponse("xmlns" => "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0") do |env|
|
133
|
-
env.imsx_POXHeader do |header|
|
134
|
-
header.imsx_POXResponseHeaderInfo do |info|
|
135
|
-
info.imsx_version "V1.0"
|
136
|
-
info.imsx_messageIdentifier @message_identifier || IMS::LTI::generate_identifier
|
137
|
-
info.imsx_statusInfo do |status|
|
138
|
-
status.imsx_codeMajor @code_major
|
139
|
-
status.imsx_severity @severity
|
140
|
-
status.imsx_description @description
|
141
|
-
status.imsx_messageRefIdentifier @message_ref_identifier
|
142
|
-
status.imsx_operationRefIdentifier @operation
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end #/header
|
146
|
-
env.imsx_POXBody do |body|
|
147
|
-
unless unsupported?
|
148
|
-
if @operation == OutcomeRequest::READ_REQUEST
|
149
|
-
body.tag!(@operation + 'Response') do |request|
|
150
|
-
request.result do |res|
|
151
|
-
res.resultScore do |res_score|
|
152
|
-
res_score.language "en" # 'en' represents the format of the number
|
153
|
-
res_score.textString @score.to_s
|
154
|
-
end
|
155
|
-
end #/result
|
156
|
-
end
|
157
|
-
else
|
158
|
-
body.tag!(@operation + 'Response')
|
159
|
-
end #/operationResponse
|
160
|
-
end
|
161
|
-
end #/body
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
end
|
166
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
module IMS::LTI
|
2
|
-
# A mixin for OAuth request validation
|
3
|
-
module RequestValidator
|
4
|
-
|
5
|
-
attr_reader :oauth_signature_validator
|
6
|
-
|
7
|
-
# Validates and OAuth request using the OAuth Gem - https://github.com/oauth/oauth-ruby
|
8
|
-
#
|
9
|
-
# To validate the OAuth signatures you need to require the appropriate
|
10
|
-
# request proxy for your application. For example:
|
11
|
-
#
|
12
|
-
# # For a sinatra app:
|
13
|
-
# require 'oauth/request_proxy/rack_request'
|
14
|
-
#
|
15
|
-
# # For a rails app:
|
16
|
-
# require 'oauth/request_proxy/action_controller_request'
|
17
|
-
# @return [Bool] Whether the request was valid
|
18
|
-
def valid_request?(request, handle_error=true)
|
19
|
-
begin
|
20
|
-
@oauth_signature_validator = OAuth::Signature.build(request, :consumer_secret => @consumer_secret)
|
21
|
-
@oauth_signature_validator.verify() or raise OAuth::Unauthorized.new(request)
|
22
|
-
true
|
23
|
-
rescue OAuth::Signature::UnknownSignatureMethod
|
24
|
-
if handle_error
|
25
|
-
false
|
26
|
-
else
|
27
|
-
raise $!
|
28
|
-
end
|
29
|
-
rescue OAuth::Unauthorized
|
30
|
-
if handle_error
|
31
|
-
false
|
32
|
-
else
|
33
|
-
raise OAuth::Unauthorized.new(request)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Check whether the OAuth-signed request is valid and throw error if not
|
39
|
-
#
|
40
|
-
# @return [Bool] Whether the request was valid
|
41
|
-
def valid_request!(request)
|
42
|
-
valid_request?(request, false)
|
43
|
-
end
|
44
|
-
|
45
|
-
# convenience method for getting the oauth nonce from the request
|
46
|
-
def request_oauth_nonce
|
47
|
-
@oauth_signature_validator && @oauth_signature_validator.request.oauth_nonce
|
48
|
-
end
|
49
|
-
|
50
|
-
# convenience method for getting the oauth timestamp from the request
|
51
|
-
def request_oauth_timestamp
|
52
|
-
@oauth_signature_validator && @oauth_signature_validator.request.oauth_timestamp
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
end
|
data/lib/ims/lti/role_checks.rb
DELETED
@@ -1,101 +0,0 @@
|
|
1
|
-
module IMS::LTI
|
2
|
-
# Some convenience methods for the most used roles
|
3
|
-
# Take care when using context_ helpers, as the context of an LTI launch
|
4
|
-
# determines the meaning of that role. For example, if the context is an
|
5
|
-
# institution context instead of a course context, then the short role of
|
6
|
-
# "Instructor" means they are a teacher at the institution, but not necessarily
|
7
|
-
# of the course you're working in.
|
8
|
-
#
|
9
|
-
# Also note that these only check for the base roles. So, asking context_student?
|
10
|
-
# only matches `urn:lti:role:ims/lis/Learner`, not `urn:lti:role:ims/lis/Learner/NonCreditLearner`
|
11
|
-
# If you make use of the more specific roles you'll need to ask specifically for those:
|
12
|
-
# @tool_provider.has_exact_role?("urn:lti:role:ims/lis/Learner/NonCreditLearner")
|
13
|
-
# Or you can use `has_base_role?`
|
14
|
-
module RoleChecks
|
15
|
-
|
16
|
-
# Check whether the Launch Parameters have a given role
|
17
|
-
def has_exact_role?(role)
|
18
|
-
role = role.downcase
|
19
|
-
@roles && @roles.any? { |r| r.downcase == role }
|
20
|
-
end
|
21
|
-
|
22
|
-
# Check whether the Launch Parameters have a given role ignoring
|
23
|
-
# sub roles. So asking:
|
24
|
-
# @tool_provider.has_base_role?("urn:lti:role:ims/lis/Instructor/")
|
25
|
-
# will return true if the role is `urn:lti:role:ims/lis/Instructor/GuestInstructor`
|
26
|
-
def has_base_role?(role)
|
27
|
-
role = role.downcase
|
28
|
-
@roles && @roles.any? { |r| r.downcase.start_with?(role) }
|
29
|
-
end
|
30
|
-
|
31
|
-
# Convenience method for checking if the user is the system administrator of the TC
|
32
|
-
def system_administrator?
|
33
|
-
has_exact_role?('urn:lti:sysrole:ims/lis/SysAdmin') ||
|
34
|
-
has_exact_role?('SysAdmin') ||
|
35
|
-
has_exact_role?('urn:lti:sysrole:ims/lis/Administrator')
|
36
|
-
end
|
37
|
-
|
38
|
-
### Institution-level roles
|
39
|
-
# Note, these only check if the role is explicitely an institution level role
|
40
|
-
# if the context of the LTI launch is the institution, the short names
|
41
|
-
# will apply, and you should use the context_x? helpers.
|
42
|
-
|
43
|
-
# Convenience method for checking if the user has 'student' or 'learner' roles at the institution
|
44
|
-
def institution_student?
|
45
|
-
has_exact_role?('urn:lti:instrole:ims/lis/Student') || has_exact_role?('urn:lti:instrole:ims/lis/Learner')
|
46
|
-
end
|
47
|
-
|
48
|
-
# Convenience method for checking if the user has 'Instructor' role at the institution
|
49
|
-
def institution_instructor?
|
50
|
-
has_exact_role?('urn:lti:instrole:ims/lis/Instructor')
|
51
|
-
end
|
52
|
-
|
53
|
-
# Convenience method for checking if the user has 'Administrator' role at the institution
|
54
|
-
def institution_admin?
|
55
|
-
has_exact_role?('urn:lti:instrole:ims/lis/Administrator')
|
56
|
-
end
|
57
|
-
|
58
|
-
|
59
|
-
### Context-level roles
|
60
|
-
# Note, the most common LTI context is a course, but that is not always the
|
61
|
-
# case. You should be aware of the context when using these helpers.
|
62
|
-
# The difference for the context_ helpers is that they check for the
|
63
|
-
# short version of the roles. So `Learner` and `urn:lti:role:ims/lis/Learner`
|
64
|
-
# are both valid.
|
65
|
-
|
66
|
-
# Convenience method for checking if the user has 'learner' role in the current launch context
|
67
|
-
def context_student?
|
68
|
-
has_exact_role?('Learner') || has_exact_role?('urn:lti:role:ims/lis/Learner')
|
69
|
-
end
|
70
|
-
|
71
|
-
# Convenience method for checking if the user has 'instructor' role in the current launch context
|
72
|
-
def context_instructor?
|
73
|
-
has_exact_role?('instructor') || has_exact_role?('urn:lti:role:ims/lis/Instructor')
|
74
|
-
end
|
75
|
-
|
76
|
-
# Convenience method for checking if the user has 'contentdeveloper' role in the current launch context
|
77
|
-
def context_content_developer?
|
78
|
-
has_exact_role?('ContentDeveloper') || has_exact_role?('urn:lti:role:ims/lis/ContentDeveloper')
|
79
|
-
end
|
80
|
-
|
81
|
-
# Convenience method for checking if the user has 'Mentor' role in the current launch context
|
82
|
-
def context_mentor?
|
83
|
-
has_exact_role?('Mentor') || has_exact_role?('urn:lti:role:ims/lis/Mentor')
|
84
|
-
end
|
85
|
-
|
86
|
-
# Convenience method for checking if the user has 'administrator' role in the current launch context
|
87
|
-
def context_admin?
|
88
|
-
has_exact_role?('Administrator') || has_exact_role?('urn:lti:role:ims/lis/Administrator')
|
89
|
-
end
|
90
|
-
|
91
|
-
# Convenience method for checking if the user has 'TeachingAssistant' role in the current launch context
|
92
|
-
def context_ta?
|
93
|
-
has_exact_role?('TeachingAssistant') || has_exact_role?('urn:lti:role:ims/lis/TeachingAssistant')
|
94
|
-
end
|
95
|
-
|
96
|
-
# Convenience method for checking if the user has 'Observer' role in the current launch context
|
97
|
-
def context_observer?
|
98
|
-
has_exact_role?('Observer') || has_exact_role?('urn:lti:instrole:ims/lis/Observer')
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
data/lib/ims/lti/tool_base.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
module IMS::LTI
|
2
|
-
class ToolBase
|
3
|
-
include IMS::LTI::Extensions::Base
|
4
|
-
include IMS::LTI::LaunchParams
|
5
|
-
include IMS::LTI::RequestValidator
|
6
|
-
|
7
|
-
# OAuth credentials
|
8
|
-
attr_accessor :consumer_key, :consumer_secret
|
9
|
-
|
10
|
-
def initialize(consumer_key, consumer_secret, params={})
|
11
|
-
@consumer_key = consumer_key
|
12
|
-
@consumer_secret = consumer_secret
|
13
|
-
@custom_params = {}
|
14
|
-
@ext_params = {}
|
15
|
-
@non_spec_params = {}
|
16
|
-
process_params(params)
|
17
|
-
end
|
18
|
-
|
19
|
-
# Convenience method for doing oauth signed requests to services that
|
20
|
-
# aren't supported by this library
|
21
|
-
def post_service_request(url, content_type, body)
|
22
|
-
IMS::LTI::post_service_request(@consumer_key,
|
23
|
-
@consumer_secret,
|
24
|
-
url,
|
25
|
-
content_type,
|
26
|
-
body)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,86 +0,0 @@
|
|
1
|
-
module IMS::LTI
|
2
|
-
# Class for implementing an LTI Tool Consumer
|
3
|
-
class ToolConsumer < ToolBase
|
4
|
-
attr_accessor :launch_url, :timestamp, :nonce
|
5
|
-
|
6
|
-
# Create a new ToolConsumer
|
7
|
-
#
|
8
|
-
# @param consumer_key [String] The OAuth consumer key
|
9
|
-
# @param consumer_secret [String] The OAuth consumer secret
|
10
|
-
# @param params [Hash] Set the launch parameters as described in LaunchParams
|
11
|
-
def initialize(consumer_key, consumer_secret, params={})
|
12
|
-
super(consumer_key, consumer_secret, params)
|
13
|
-
@launch_url = params['launch_url']
|
14
|
-
end
|
15
|
-
|
16
|
-
def process_post_request(post_request)
|
17
|
-
request = extend_outcome_request(OutcomeRequest.new)
|
18
|
-
request.process_post_request(post_request)
|
19
|
-
end
|
20
|
-
|
21
|
-
# Set launch data from a ToolConfig
|
22
|
-
#
|
23
|
-
# @param config [ToolConfig]
|
24
|
-
def set_config(config)
|
25
|
-
@launch_url ||= config.secure_launch_url
|
26
|
-
@launch_url ||= config.launch_url
|
27
|
-
# any parameters already set will take priority
|
28
|
-
@custom_params = config.custom_params.merge(@custom_params)
|
29
|
-
end
|
30
|
-
|
31
|
-
# Check if the required parameters for a tool launch are set
|
32
|
-
def has_required_params?
|
33
|
-
@consumer_key && @consumer_secret && @resource_link_id && @launch_url
|
34
|
-
end
|
35
|
-
|
36
|
-
# Generate the launch data including the necessary OAuth information
|
37
|
-
#
|
38
|
-
#
|
39
|
-
def generate_launch_data
|
40
|
-
raise IMS::LTI::InvalidLTIConfigError, "Not all required params set for tool launch" unless has_required_params?
|
41
|
-
|
42
|
-
params = self.to_params
|
43
|
-
params['lti_version'] ||= 'LTI-1p0'
|
44
|
-
params['lti_message_type'] ||= 'basic-lti-launch-request'
|
45
|
-
uri = URI.parse(@launch_url)
|
46
|
-
|
47
|
-
if uri.port == uri.default_port
|
48
|
-
host = uri.host
|
49
|
-
else
|
50
|
-
host = "#{uri.host}:#{uri.port}"
|
51
|
-
end
|
52
|
-
|
53
|
-
consumer = OAuth::Consumer.new(@consumer_key, @consumer_secret, {
|
54
|
-
:site => "#{uri.scheme}://#{host}",
|
55
|
-
:signature_method => "HMAC-SHA1"
|
56
|
-
})
|
57
|
-
|
58
|
-
path = uri.path
|
59
|
-
path = '/' if path.empty?
|
60
|
-
if uri.query && uri.query != ''
|
61
|
-
CGI.parse(uri.query).each do |query_key, query_values|
|
62
|
-
unless params[query_key]
|
63
|
-
params[query_key] = query_values.first
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
options = {
|
68
|
-
:scheme => 'body',
|
69
|
-
:timestamp => @timestamp,
|
70
|
-
:nonce => @nonce
|
71
|
-
}
|
72
|
-
request = consumer.create_signed_request(:post, path, nil, options, params)
|
73
|
-
|
74
|
-
# the request is made by a html form in the user's browser, so we
|
75
|
-
# want to revert the escapage and return the hash of post parameters ready
|
76
|
-
# for embedding in a html view
|
77
|
-
hash = {}
|
78
|
-
request.body.split(/&/).each do |param|
|
79
|
-
key, val = param.split(/=/).map { |v| CGI.unescape(v) }
|
80
|
-
hash[key] = val
|
81
|
-
end
|
82
|
-
hash
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
86
|
-
end
|