qalam_ims_lti 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/Changelog.txt +0 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +39 -0
  5. data/lib/ims/lis/context_type/handles.rb +10 -0
  6. data/lib/ims/lis/context_type/urns.rb +10 -0
  7. data/lib/ims/lis/roles/context/handles.rb +60 -0
  8. data/lib/ims/lis/roles/context/urns.rb +60 -0
  9. data/lib/ims/lis/roles/institution/handles.rb +22 -0
  10. data/lib/ims/lis/roles/institution/urns.rb +22 -0
  11. data/lib/ims/lis/roles/system/handles.rb +15 -0
  12. data/lib/ims/lis/roles/system/urns.rb +15 -0
  13. data/lib/ims/lis/statuses/simple_names.rb +9 -0
  14. data/lib/ims/lis/statuses/uris.rb +9 -0
  15. data/lib/ims/lis.rb +14 -0
  16. data/lib/ims/lti/converters/time_json_converter.rb +13 -0
  17. data/lib/ims/lti/converters.rb +5 -0
  18. data/lib/ims/lti/deprecated_role_checks.rb +52 -0
  19. data/lib/ims/lti/errors/authentication_failed_error.rb +11 -0
  20. data/lib/ims/lti/errors/invalid_lti_config_error.rb +4 -0
  21. data/lib/ims/lti/errors/invalid_tool_consumer_profile.rb +4 -0
  22. data/lib/ims/lti/errors/tool_proxy_registration_error.rb +15 -0
  23. data/lib/ims/lti/errors.rb +8 -0
  24. data/lib/ims/lti/extensions/canvas.rb +125 -0
  25. data/lib/ims/lti/extensions/content.rb +209 -0
  26. data/lib/ims/lti/extensions/outcome_data.rb +216 -0
  27. data/lib/ims/lti/extensions.rb +45 -0
  28. data/lib/ims/lti/launch_params.rb +166 -0
  29. data/lib/ims/lti/models/base_url_choice.rb +15 -0
  30. data/lib/ims/lti/models/base_url_selector.rb +5 -0
  31. data/lib/ims/lti/models/contact.rb +5 -0
  32. data/lib/ims/lti/models/content_item_container.rb +14 -0
  33. data/lib/ims/lti/models/content_item_placement.rb +20 -0
  34. data/lib/ims/lti/models/content_items/content_item.rb +32 -0
  35. data/lib/ims/lti/models/content_items/file_item.rb +15 -0
  36. data/lib/ims/lti/models/content_items/lti_link_item.rb +14 -0
  37. data/lib/ims/lti/models/content_items.rb +7 -0
  38. data/lib/ims/lti/models/icon_endpoint.rb +5 -0
  39. data/lib/ims/lti/models/icon_info.rb +6 -0
  40. data/lib/ims/lti/models/image.rb +7 -0
  41. data/lib/ims/lti/models/localized_name.rb +12 -0
  42. data/lib/ims/lti/models/localized_text.rb +12 -0
  43. data/lib/ims/lti/models/lti_model.rb +227 -0
  44. data/lib/ims/lti/models/membership_service/agent.rb +11 -0
  45. data/lib/ims/lti/models/membership_service/container.rb +11 -0
  46. data/lib/ims/lti/models/membership_service/context.rb +11 -0
  47. data/lib/ims/lti/models/membership_service/lis_membership_container.rb +13 -0
  48. data/lib/ims/lti/models/membership_service/lis_person.rb +14 -0
  49. data/lib/ims/lti/models/membership_service/membership.rb +14 -0
  50. data/lib/ims/lti/models/membership_service/organization.rb +14 -0
  51. data/lib/ims/lti/models/membership_service/page.rb +16 -0
  52. data/lib/ims/lti/models/membership_service/person.rb +13 -0
  53. data/lib/ims/lti/models/membership_service.rb +16 -0
  54. data/lib/ims/lti/models/message_handler.rb +14 -0
  55. data/lib/ims/lti/models/messages/basic_lti_launch_request.rb +24 -0
  56. data/lib/ims/lti/models/messages/content_item_selection.rb +32 -0
  57. data/lib/ims/lti/models/messages/content_item_selection_request.rb +26 -0
  58. data/lib/ims/lti/models/messages/message.rb +222 -0
  59. data/lib/ims/lti/models/messages/registration_request.rb +20 -0
  60. data/lib/ims/lti/models/messages/request_message.rb +12 -0
  61. data/lib/ims/lti/models/messages/tool_proxy_update_request.rb +15 -0
  62. data/lib/ims/lti/models/messages.rb +11 -0
  63. data/lib/ims/lti/models/parameter.rb +28 -0
  64. data/lib/ims/lti/models/product_family.rb +8 -0
  65. data/lib/ims/lti/models/product_info.rb +26 -0
  66. data/lib/ims/lti/models/product_instance.rb +10 -0
  67. data/lib/ims/lti/models/resource_handler.rb +22 -0
  68. data/lib/ims/lti/models/resource_type.rb +6 -0
  69. data/lib/ims/lti/models/rest_service.rb +30 -0
  70. data/lib/ims/lti/models/rest_service_profile.rb +15 -0
  71. data/lib/ims/lti/models/security_contract.rb +21 -0
  72. data/lib/ims/lti/models/security_profile.rb +10 -0
  73. data/lib/ims/lti/models/serializable.rb +12 -0
  74. data/lib/ims/lti/models/service_owner.rb +26 -0
  75. data/lib/ims/lti/models/service_provider.rb +11 -0
  76. data/lib/ims/lti/models/tool_consumer_profile.rb +45 -0
  77. data/lib/ims/lti/models/tool_profile.rb +35 -0
  78. data/lib/ims/lti/models/tool_proxy.rb +21 -0
  79. data/lib/ims/lti/models/tool_setting.rb +12 -0
  80. data/lib/ims/lti/models/tool_setting_container.rb +14 -0
  81. data/lib/ims/lti/models/vendor.rb +28 -0
  82. data/lib/ims/lti/models.rb +38 -0
  83. data/lib/ims/lti/outcome_request.rb +225 -0
  84. data/lib/ims/lti/outcome_response.rb +166 -0
  85. data/lib/ims/lti/request_validator.rb +56 -0
  86. data/lib/ims/lti/role_checks.rb +101 -0
  87. data/lib/ims/lti/serializers/base.rb +125 -0
  88. data/lib/ims/lti/serializers/membership_service/agent_serializer.rb +5 -0
  89. data/lib/ims/lti/serializers/membership_service/container_serializer.rb +6 -0
  90. data/lib/ims/lti/serializers/membership_service/context_serializer.rb +9 -0
  91. data/lib/ims/lti/serializers/membership_service/lis_membership_container_serializer.rb +9 -0
  92. data/lib/ims/lti/serializers/membership_service/lis_person_serializer.rb +12 -0
  93. data/lib/ims/lti/serializers/membership_service/membership_serializer.rb +7 -0
  94. data/lib/ims/lti/serializers/membership_service/organization_serializer.rb +8 -0
  95. data/lib/ims/lti/serializers/membership_service/page_serializer.rb +10 -0
  96. data/lib/ims/lti/serializers/membership_service/person_serializer.rb +8 -0
  97. data/lib/ims/lti/serializers/membership_service.rb +13 -0
  98. data/lib/ims/lti/serializers.rb +6 -0
  99. data/lib/ims/lti/services/authentication_service.rb +67 -0
  100. data/lib/ims/lti/services/message_authenticator.rb +80 -0
  101. data/lib/ims/lti/services/oauth2_client.rb +18 -0
  102. data/lib/ims/lti/services/tool_config.rb +223 -0
  103. data/lib/ims/lti/services/tool_consumer_profile_service.rb +16 -0
  104. data/lib/ims/lti/services/tool_proxy_registration_service.rb +84 -0
  105. data/lib/ims/lti/services/tool_proxy_validator.rb +182 -0
  106. data/lib/ims/lti/services.rb +11 -0
  107. data/lib/ims/lti/tool_base.rb +29 -0
  108. data/lib/ims/lti/tool_config.rb +231 -0
  109. data/lib/ims/lti/tool_consumer.rb +86 -0
  110. data/lib/ims/lti/tool_provider.rb +143 -0
  111. data/lib/ims/lti/version.rb +5 -0
  112. data/lib/ims/lti.rb +61 -0
  113. data/lib/ims.rb +4 -0
  114. 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