ims-lti 1.2.2 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +5 -5
  2. data/Changelog.txt +0 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +25 -91
  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/errors/authentication_failed_error.rb +11 -0
  19. data/lib/ims/lti/errors/invalid_lti_config_error.rb +4 -0
  20. data/lib/ims/lti/errors/invalid_tool_consumer_profile.rb +4 -0
  21. data/lib/ims/lti/errors/tool_proxy_registration_error.rb +15 -0
  22. data/lib/ims/lti/errors.rb +8 -0
  23. data/lib/ims/lti/models/base_url_choice.rb +15 -0
  24. data/lib/ims/lti/models/base_url_selector.rb +5 -0
  25. data/lib/ims/lti/models/contact.rb +5 -0
  26. data/lib/ims/lti/models/content_item_container.rb +14 -0
  27. data/lib/ims/lti/models/content_item_placement.rb +20 -0
  28. data/lib/ims/lti/models/content_items/content_item.rb +32 -0
  29. data/lib/ims/lti/models/content_items/file_item.rb +15 -0
  30. data/lib/ims/lti/models/content_items/lti_link_item.rb +14 -0
  31. data/lib/ims/lti/models/content_items.rb +7 -0
  32. data/lib/ims/lti/models/icon_endpoint.rb +5 -0
  33. data/lib/ims/lti/models/icon_info.rb +6 -0
  34. data/lib/ims/lti/models/image.rb +7 -0
  35. data/lib/ims/lti/models/localized_name.rb +12 -0
  36. data/lib/ims/lti/models/localized_text.rb +12 -0
  37. data/lib/ims/lti/models/lti_model.rb +227 -0
  38. data/lib/ims/lti/models/membership_service/agent.rb +11 -0
  39. data/lib/ims/lti/models/membership_service/container.rb +11 -0
  40. data/lib/ims/lti/models/membership_service/context.rb +11 -0
  41. data/lib/ims/lti/models/membership_service/lis_membership_container.rb +13 -0
  42. data/lib/ims/lti/models/membership_service/lis_person.rb +14 -0
  43. data/lib/ims/lti/models/membership_service/membership.rb +14 -0
  44. data/lib/ims/lti/models/membership_service/organization.rb +14 -0
  45. data/lib/ims/lti/models/membership_service/page.rb +16 -0
  46. data/lib/ims/lti/models/membership_service/person.rb +13 -0
  47. data/lib/ims/lti/models/membership_service.rb +16 -0
  48. data/lib/ims/lti/models/message_handler.rb +14 -0
  49. data/lib/ims/lti/models/messages/basic_lti_launch_request.rb +24 -0
  50. data/lib/ims/lti/models/messages/content_item_selection.rb +32 -0
  51. data/lib/ims/lti/models/messages/content_item_selection_request.rb +26 -0
  52. data/lib/ims/lti/models/messages/message.rb +239 -0
  53. data/lib/ims/lti/models/messages/registration_request.rb +20 -0
  54. data/lib/ims/lti/models/messages/request_message.rb +12 -0
  55. data/lib/ims/lti/models/messages/tool_proxy_update_request.rb +15 -0
  56. data/lib/ims/lti/models/messages.rb +11 -0
  57. data/lib/ims/lti/models/parameter.rb +28 -0
  58. data/lib/ims/lti/models/product_family.rb +8 -0
  59. data/lib/ims/lti/models/product_info.rb +26 -0
  60. data/lib/ims/lti/models/product_instance.rb +10 -0
  61. data/lib/ims/lti/models/resource_handler.rb +22 -0
  62. data/lib/ims/lti/models/resource_type.rb +6 -0
  63. data/lib/ims/lti/models/rest_service.rb +30 -0
  64. data/lib/ims/lti/models/rest_service_profile.rb +15 -0
  65. data/lib/ims/lti/models/security_contract.rb +21 -0
  66. data/lib/ims/lti/models/security_profile.rb +10 -0
  67. data/lib/ims/lti/models/serializable.rb +12 -0
  68. data/lib/ims/lti/models/service_owner.rb +26 -0
  69. data/lib/ims/lti/models/service_provider.rb +11 -0
  70. data/lib/ims/lti/models/tool_consumer_profile.rb +45 -0
  71. data/lib/ims/lti/models/tool_profile.rb +35 -0
  72. data/lib/ims/lti/models/tool_proxy.rb +21 -0
  73. data/lib/ims/lti/models/tool_setting.rb +12 -0
  74. data/lib/ims/lti/models/tool_setting_container.rb +14 -0
  75. data/lib/ims/lti/models/vendor.rb +28 -0
  76. data/lib/ims/lti/models.rb +38 -0
  77. data/lib/ims/lti/serializers/base.rb +125 -0
  78. data/lib/ims/lti/serializers/membership_service/agent_serializer.rb +5 -0
  79. data/lib/ims/lti/serializers/membership_service/container_serializer.rb +6 -0
  80. data/lib/ims/lti/serializers/membership_service/context_serializer.rb +9 -0
  81. data/lib/ims/lti/serializers/membership_service/lis_membership_container_serializer.rb +9 -0
  82. data/lib/ims/lti/serializers/membership_service/lis_person_serializer.rb +12 -0
  83. data/lib/ims/lti/serializers/membership_service/membership_serializer.rb +7 -0
  84. data/lib/ims/lti/serializers/membership_service/organization_serializer.rb +8 -0
  85. data/lib/ims/lti/serializers/membership_service/page_serializer.rb +10 -0
  86. data/lib/ims/lti/serializers/membership_service/person_serializer.rb +8 -0
  87. data/lib/ims/lti/serializers/membership_service.rb +13 -0
  88. data/lib/ims/lti/serializers.rb +6 -0
  89. data/lib/ims/lti/services/authentication_service.rb +67 -0
  90. data/lib/ims/lti/services/message_authenticator.rb +80 -0
  91. data/lib/ims/lti/services/oauth2_client.rb +18 -0
  92. data/lib/ims/lti/{tool_config.rb → services/tool_config.rb} +26 -34
  93. data/lib/ims/lti/services/tool_consumer_profile_service.rb +16 -0
  94. data/lib/ims/lti/services/tool_proxy_registration_service.rb +84 -0
  95. data/lib/ims/lti/services/tool_proxy_validator.rb +182 -0
  96. data/lib/ims/lti/services.rb +11 -0
  97. data/lib/ims/lti/version.rb +5 -0
  98. data/lib/ims/lti.rb +13 -63
  99. data/lib/ims.rb +4 -1
  100. metadata +280 -36
  101. data/Changelog +0 -51
  102. data/LICENSE +0 -18
  103. data/lib/ims/lti/deprecated_role_checks.rb +0 -52
  104. data/lib/ims/lti/extensions/canvas.rb +0 -122
  105. data/lib/ims/lti/extensions/content.rb +0 -209
  106. data/lib/ims/lti/extensions/outcome_data.rb +0 -199
  107. data/lib/ims/lti/extensions.rb +0 -45
  108. data/lib/ims/lti/launch_params.rb +0 -166
  109. data/lib/ims/lti/outcome_request.rb +0 -210
  110. data/lib/ims/lti/outcome_response.rb +0 -166
  111. data/lib/ims/lti/request_validator.rb +0 -56
  112. data/lib/ims/lti/role_checks.rb +0 -101
  113. data/lib/ims/lti/tool_base.rb +0 -29
  114. data/lib/ims/lti/tool_consumer.rb +0 -86
  115. data/lib/ims/lti/tool_provider.rb +0 -143
@@ -0,0 +1,84 @@
1
+ module IMS::LTI::Services
2
+ class ToolProxyRegistrationService
3
+ def initialize(registration_request)
4
+ @registration_request = registration_request
5
+ end
6
+
7
+ def tool_consumer_profile
8
+ return @tool_consumer_profile if @tool_consumer_profile
9
+
10
+ connection = Faraday.new
11
+ response = connection.get(@registration_request.tc_profile_url)
12
+ @tool_consumer_profile = IMS::LTI::Models::ToolConsumerProfile.new.from_json(response.body)
13
+ end
14
+
15
+ def service_profiles
16
+ tool_consumer_profile.services_offered.map(&:profile)
17
+ end
18
+
19
+ def register_tool_proxy(tool_proxy, reregistration_confirm_url = nil, shared_secret = nil)
20
+ service = tool_consumer_profile.services_offered.find { |s| s.formats.include?('application/vnd.ims.lti.v2.toolproxy+json') && s.actions.include?('POST') }
21
+
22
+ SimpleOAuth::Header::ATTRIBUTE_KEYS << :body_hash unless SimpleOAuth::Header::ATTRIBUTE_KEYS.include? :body_hash
23
+ tool_proxy_json = tool_proxy.to_json
24
+ body_hash = Digest::SHA1.base64digest tool_proxy_json
25
+
26
+ if reregistration?
27
+ consumer_key = tool_proxy.tool_proxy_guid
28
+ consumer_secret = shared_secret
29
+ else
30
+ consumer_key = @registration_request.reg_key
31
+ consumer_secret = @registration_request.reg_password
32
+ end
33
+
34
+ conn = Faraday.new do |conn|
35
+ conn.request :oauth, {:consumer_key => consumer_key, :consumer_secret => consumer_secret, :body_hash => body_hash}
36
+ conn.adapter :net_http
37
+ end
38
+
39
+ response = conn.post do |req|
40
+ req.url service.endpoint
41
+ req.headers['Content-Type'] = 'application/vnd.ims.lti.v2.toolproxy+json'
42
+ req.headers['VND-IMS-CONFIRM-URL'] = reregistration_confirm_url if reregistration_confirm_url
43
+ req.body = tool_proxy_json
44
+ end
45
+
46
+ if response.status == 201 || (response.status == 200 && reregistration?)
47
+ IMS::LTI::Models::ToolProxy.new.from_json(tool_proxy.to_json).from_json(response.body)
48
+ else
49
+ raise IMS::LTI::Errors::ToolProxyRegistrationError.new(response.status, response.body)
50
+ end
51
+ end
52
+
53
+
54
+ def remove_invalid_capabilities!(message_handler)
55
+ {
56
+ invalid_capabilities: remove_capabilites!(message_handler),
57
+ invalid_parameters: remove_params!(message_handler)
58
+ }
59
+ end
60
+
61
+ private
62
+
63
+ def remove_params!(message_handler)
64
+ orig_parameters = message_handler.parameter || []
65
+ parameters = orig_parameters.select {|p| p.fixed? || tool_consumer_profile.capability_offered.include?(p.variable)}
66
+ message_handler.parameter = parameters
67
+ orig_parameters - parameters
68
+ end
69
+
70
+ def remove_capabilites!(message_handler)
71
+ orig_capabilities = message_handler.enabled_capability || []
72
+ capabilites = orig_capabilities & tool_consumer_profile.capability_offered
73
+ capabilites.reject! { |cap| IMS::LTI::Models::ToolConsumerProfile::MESSAGING_CAPABILITIES.include? cap }
74
+ message_handler.enabled_capability = capabilites
75
+ orig_capabilities - capabilites
76
+ end
77
+
78
+ def reregistration?
79
+ @registration_request.is_a?(IMS::LTI::Models::Messages::ToolProxyUpdateRequest)
80
+ end
81
+
82
+
83
+ end
84
+ end
@@ -0,0 +1,182 @@
1
+ module IMS::LTI::Services
2
+ class ToolProxyValidator
3
+
4
+ attr_reader :tool_proxy
5
+
6
+ def initialize(tool_proxy)
7
+ @tool_proxy = tool_proxy
8
+ end
9
+
10
+ def tool_consumer_profile
11
+ return @tool_consumer_profile if @tool_consumer_profile
12
+
13
+ connection = Faraday.new
14
+ response = connection.get(tool_proxy.tool_consumer_profile)
15
+ @tool_consumer_profile = IMS::LTI::Models::ToolConsumerProfile.new.from_json(response.body)
16
+ @tool_consumer_profile
17
+ end
18
+
19
+ def tool_consumer_profile=(tcp)
20
+ tcp = IMS::LTI::Models::ToolConsumerProfile.from_json(tcp) unless tcp.is_a?(IMS::LTI::Models::ToolConsumerProfile)
21
+ if tool_proxy.tool_consumer_profile != tcp.id
22
+ raise IMS::LTI::Errors::InvalidToolConsumerProfile, "Tool Consumer Profile @id doesn't match the Tool Proxy"
23
+ end
24
+ @tool_consumer_profile = tcp
25
+ end
26
+
27
+ def capabilities_offered
28
+ tool_consumer_profile.capabilities_offered
29
+ end
30
+
31
+ def invalid_services
32
+ services = tool_proxy.security_contract.services
33
+ services_used = services.each_with_object({}) do |s, hash|
34
+ hash[s.service.split('#').last.strip] = s.actions
35
+ hash
36
+ end
37
+ services_offered = tool_consumer_profile.services_offered.each_with_object({}) do |s, hash|
38
+ hash[s.id.split(':').last.split('#').last.strip] = s.actions
39
+ hash
40
+ end
41
+ invalid_services = services_used.each_with_object({}) do |(id, actions), hash|
42
+ if services_offered.keys.include?(id)
43
+ actions_used = normalize_strings(*services_offered[id])
44
+ actions_offered = normalize_strings(*actions)
45
+ invalid_actions = actions_offered - actions_used
46
+ hash[id] = invalid_actions unless invalid_actions.empty?
47
+ else
48
+ hash[id] = actions
49
+ end
50
+ hash
51
+ end
52
+ invalid_services
53
+ end
54
+
55
+ def invalid_message_handlers
56
+ ret_val = {}
57
+ tool_profile = tool_proxy.tool_profile
58
+ #singleton_message_handlers = tool_profile.messages
59
+ invalid_rhs = validate_resource_handlers(tool_profile.resource_handlers)
60
+ ret_val[:resource_handlers] = invalid_rhs unless invalid_rhs.empty?
61
+ invalid_singleton_message_handlers = validate_singleton_message_handlers(tool_profile.messages)
62
+ ret_val[:singleton_message_handlers] = invalid_singleton_message_handlers unless invalid_singleton_message_handlers.empty?
63
+ ret_val
64
+ end
65
+
66
+ def invalid_capabilities
67
+ tool_proxy.enabled_capabilities - capabilities_offered
68
+ end
69
+
70
+ def invalid_security_contract
71
+ ret_val = {}
72
+
73
+ is_split_secret_capable = tool_proxy.enabled_capabilities.include?('Security.splitSecret')
74
+ has_shared_secret = tool_proxy.security_contract.shared_secret != nil && !tool_proxy.security_contract.shared_secret.empty?
75
+ has_split_secret = tool_proxy.security_contract.tp_half_shared_secret != nil && !tool_proxy.security_contract.tp_half_shared_secret.empty?
76
+
77
+ if is_split_secret_capable
78
+ ret_val[:missing_secret] = :tp_half_shared_secret unless has_split_secret
79
+ ret_val[:invalid_secret_type] = :shared_secret if has_shared_secret
80
+ else
81
+ ret_val[:missing_secret] = :shared_secret unless has_shared_secret
82
+ ret_val[:invalid_secret_type] = :tp_half_shared_secret if has_split_secret
83
+ end
84
+
85
+ ret_val
86
+ end
87
+
88
+ def invalid_security_profiles
89
+ security_profiles = tool_proxy.tool_profile.security_profiles
90
+ array = security_profiles.each_with_object([]) do |sp, array|
91
+ tcp_sp = tool_consumer_profile.security_profile_by_name(security_profile_name: sp.security_profile_name)
92
+ if tcp_sp
93
+ supported_algorithms = sp.digest_algorithms & tcp_sp.digest_algorithms
94
+ unsupported_algorithms = sp.digest_algorithms - supported_algorithms
95
+ unless unsupported_algorithms.empty?
96
+ array << { name: sp.security_profile_name, algorithms: unsupported_algorithms }
97
+ end
98
+ else
99
+ array << { name: sp.security_profile_name }
100
+ end
101
+ end
102
+ array
103
+ end
104
+
105
+ def errors
106
+ ret_val = {}
107
+ ret_val[:invalid_security_contract] = invalid_security_contract unless invalid_security_contract.empty?
108
+ ret_val[:invalid_capabilities] = invalid_capabilities unless invalid_capabilities.empty?
109
+ ret_val[:invalid_message_handlers] = invalid_message_handlers unless invalid_message_handlers.empty?
110
+ ret_val[:invalid_services] = invalid_services unless invalid_services.empty?
111
+ ret_val[:invalid_security_profiles] = invalid_security_profiles unless invalid_security_profiles.empty?
112
+ ret_val
113
+ end
114
+
115
+ def valid?
116
+ errors.keys.empty?
117
+ end
118
+
119
+ private
120
+
121
+ def normalize_strings(string, *strings)
122
+ strings.push(string)
123
+ normalized = strings.map {|s| s.upcase.strip}
124
+ normalized
125
+ end
126
+
127
+ def validate_message_handlers(message_handlers)
128
+ message_handlers.each_with_object([]) do |mh, array|
129
+ invalid_capabilities = mh.enabled_capabilities - capabilities_offered
130
+ invalid_parameters = validate_parameters(mh.parameters)
131
+ if !invalid_parameters.empty? || !invalid_capabilities.empty?
132
+ hash = { message_type: mh.message_type, }
133
+ hash[:invalid_capabilities] = invalid_capabilities unless invalid_capabilities.empty?
134
+ hash[:invalid_parameters] = invalid_parameters unless invalid_parameters.empty?
135
+ array << hash
136
+ end
137
+ array
138
+ end
139
+ end
140
+
141
+ def validate_parameters(parameters)
142
+ parameters.each_with_object([]) do |p, array|
143
+ if !p.fixed? && !capabilities_offered.include?(p.variable)
144
+ array << { name: p.name, variable: p.variable }
145
+ end
146
+ end
147
+ end
148
+
149
+ def validate_message_types(message_handlers)
150
+ message_handlers.each_with_object([]) do |mh, array|
151
+ array << mh.message_type unless capabilities_offered.include?(mh.message_type)
152
+ array
153
+ end
154
+ end
155
+
156
+ def validate_resource_handlers(resource_handlers)
157
+ resource_handlers.each_with_object([]) do |rh, array|
158
+ invalid_message_types = validate_message_types(rh.messages)
159
+ invalid_mhs = validate_message_handlers(rh.messages)
160
+ if !invalid_mhs.empty? || !invalid_message_types.empty?
161
+ hash = {
162
+ code: rh.resource_type.code,
163
+ }
164
+ hash[:messages] = invalid_mhs unless invalid_mhs.empty?
165
+ hash[:invalid_message_types] = invalid_message_types unless invalid_message_types.empty?
166
+ array << hash
167
+ end
168
+ array
169
+ end
170
+ end
171
+
172
+ def validate_singleton_message_handlers(message_handlers)
173
+ hash = {}
174
+ invalid_messages = validate_message_handlers(message_handlers)
175
+ invalid_message_types = validate_message_types(message_handlers)
176
+ hash[:messages] = invalid_messages unless invalid_messages.empty?
177
+ hash[:invalid_message_types] = invalid_message_types unless invalid_message_types.empty?
178
+ hash
179
+ end
180
+
181
+ end
182
+ end
@@ -0,0 +1,11 @@
1
+ module IMS::LTI
2
+ module Services
3
+ require_relative 'services/oauth2_client'
4
+ require_relative 'services/tool_proxy_registration_service'
5
+ require_relative 'services/tool_config'
6
+ require_relative 'services/tool_proxy_validator'
7
+ require_relative 'services/message_authenticator'
8
+ require_relative 'services/tool_consumer_profile_service'
9
+ require_relative 'services/authentication_service'
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module IMS
2
+ module LTI
3
+ VERSION = "2.3.2"
4
+ end
5
+ end
data/lib/ims/lti.rb CHANGED
@@ -1,69 +1,19 @@
1
- require 'oauth'
1
+ require 'addressable/uri'
2
2
  require 'builder'
3
- require "rexml/document"
4
- require 'cgi'
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+ require 'json'
6
+ require 'json/jwt'
7
+ require 'rexml/document'
5
8
  require 'securerandom'
9
+ require 'simple_oauth'
6
10
 
7
- module IMS # :nodoc:
8
-
9
- # :main:IMS::LTI
10
- # LTI is a standard defined by IMS for creating eduction Tool Consumers/Providers.
11
- # LTI documentation: http://www.imsglobal.org/lti/index.html
12
- #
13
- # When creating these tools you will work primarily with the ToolProvider and
14
- # ToolConsumer classes.
15
- #
16
- # For validating OAuth request be sure to require the necessary proxy request
17
- # object. See IMS::LTI::RequestValidator#valid_request? for more documentation.
18
- #
19
- # == Installation
20
- # This is packaged as the `ims-lti` rubygem, so you can just add the dependency to
21
- # your Gemfile or install the gem on your system:
22
- #
23
- # gem install ims-lti
24
- #
25
- # To require the library in your project:
26
- #
27
- # require 'ims/lti'
11
+ module IMS
28
12
  module LTI
29
-
30
- # The versions of LTI this library supports
31
- VERSIONS = %w{1.0 1.1}
32
-
33
- class InvalidLTIConfigError < StandardError
34
- end
35
-
36
- class XMLParseError < StandardError
37
- end
38
-
39
- # POST a signed oauth request with the given key/secret/data
40
- def self.post_service_request(key, secret, url, content_type, body)
41
- raise IMS::LTI::InvalidLTIConfigError, "" unless key && secret
42
-
43
- consumer = OAuth::Consumer.new(key, secret)
44
- token = OAuth::AccessToken.new(consumer)
45
- token.post(
46
- url,
47
- body,
48
- 'Content-Type' => content_type
49
- )
50
- end
51
-
52
- # Generates a unique identifier
53
- def self.generate_identifier
54
- SecureRandom.uuid
55
- end
13
+ require_relative 'lti/converters'
14
+ require_relative 'lti/errors'
15
+ require_relative 'lti/models'
16
+ require_relative 'lti/serializers'
17
+ require_relative 'lti/services'
56
18
  end
57
19
  end
58
-
59
- require 'ims/lti/extensions'
60
- require 'ims/lti/launch_params'
61
- require 'ims/lti/request_validator'
62
- require 'ims/lti/tool_base'
63
- require 'ims/lti/deprecated_role_checks'
64
- require 'ims/lti/role_checks'
65
- require 'ims/lti/tool_provider'
66
- require 'ims/lti/tool_consumer'
67
- require 'ims/lti/outcome_request'
68
- require 'ims/lti/outcome_response'
69
- require 'ims/lti/tool_config'
data/lib/ims.rb CHANGED
@@ -1 +1,4 @@
1
- require 'ims/lti'
1
+ module IMS
2
+ require 'ims/lti'
3
+ require 'ims/lis'
4
+ end