qalam_ims_lti 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Changelog.txt +0 -0
- data/LICENSE.txt +22 -0
- data/README.md +39 -0
- 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/deprecated_role_checks.rb +52 -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/extensions/canvas.rb +125 -0
- data/lib/ims/lti/extensions/content.rb +209 -0
- data/lib/ims/lti/extensions/outcome_data.rb +216 -0
- data/lib/ims/lti/extensions.rb +45 -0
- data/lib/ims/lti/launch_params.rb +166 -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 +14 -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/outcome_request.rb +225 -0
- data/lib/ims/lti/outcome_response.rb +166 -0
- data/lib/ims/lti/request_validator.rb +56 -0
- data/lib/ims/lti/role_checks.rb +101 -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 +12 -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/services/tool_config.rb +223 -0
- 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/tool_base.rb +29 -0
- data/lib/ims/lti/tool_config.rb +231 -0
- data/lib/ims/lti/tool_consumer.rb +86 -0
- data/lib/ims/lti/tool_provider.rb +143 -0
- data/lib/ims/lti/version.rb +5 -0
- data/lib/ims/lti.rb +61 -0
- data/lib/ims.rb +4 -0
- metadata +379 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module IMS::LTI::Models
|
2
|
+
class Vendor < LTIModel
|
3
|
+
|
4
|
+
add_attributes :code, :website
|
5
|
+
add_attribute :id, json_key:'@id'
|
6
|
+
add_attribute :contact, relation:'IMS::LTI::Models::Contact'
|
7
|
+
add_attribute :vendor_name, relation:'IMS::LTI::Models::LocalizedName'
|
8
|
+
add_attribute :description, relation:'IMS::LTI::Models::LocalizedText'
|
9
|
+
add_attribute :timestamp, json_converter: 'IMS::LTI::Converters::TimeJSONConverter'
|
10
|
+
|
11
|
+
def create_vendor_name(name, key = 'vendor.name')
|
12
|
+
@vendor_name = LocalizedName.new(name, key)
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_description(name, key = 'vendor.description')
|
16
|
+
@description = LocalizedText.new(name, key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_name
|
20
|
+
vendor_name && vendor_name.default_value
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_description
|
24
|
+
description && description.default_value
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module IMS::LTI
|
2
|
+
module Models
|
3
|
+
require_relative 'models/lti_model'
|
4
|
+
require_relative 'models/serializable'
|
5
|
+
require_relative 'models/contact'
|
6
|
+
require_relative 'models/localized_name'
|
7
|
+
require_relative 'models/localized_text'
|
8
|
+
require_relative 'models/product_family'
|
9
|
+
require_relative 'models/product_info'
|
10
|
+
require_relative 'models/product_instance'
|
11
|
+
require_relative 'models/rest_service'
|
12
|
+
require_relative 'models/service_owner'
|
13
|
+
require_relative 'models/service_provider'
|
14
|
+
require_relative 'models/tool_consumer_profile'
|
15
|
+
require_relative 'models/vendor'
|
16
|
+
require_relative 'models/messages'
|
17
|
+
require_relative 'models/tool_proxy'
|
18
|
+
require_relative 'models/tool_profile'
|
19
|
+
require_relative 'models/resource_handler'
|
20
|
+
require_relative 'models/resource_type'
|
21
|
+
require_relative 'models/message_handler'
|
22
|
+
require_relative 'models/parameter'
|
23
|
+
require_relative 'models/icon_info'
|
24
|
+
require_relative 'models/icon_endpoint'
|
25
|
+
require_relative 'models/security_contract'
|
26
|
+
require_relative 'models/rest_service_profile'
|
27
|
+
require_relative 'models/base_url_choice'
|
28
|
+
require_relative 'models/base_url_selector'
|
29
|
+
require_relative 'models/tool_setting'
|
30
|
+
require_relative 'models/tool_setting_container'
|
31
|
+
require_relative 'models/content_items'
|
32
|
+
require_relative 'models/content_item_placement'
|
33
|
+
require_relative 'models/content_item_container'
|
34
|
+
require_relative 'models/image'
|
35
|
+
require_relative 'models/membership_service'
|
36
|
+
require_relative 'models/security_profile'
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,225 @@
|
|
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
|
@@ -0,0 +1,166 @@
|
|
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
|
@@ -0,0 +1,56 @@
|
|
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
|
@@ -0,0 +1,101 @@
|
|
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
|