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.
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 +13 -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 +222 -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 +11 -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 +266 -44
  101. data/Changelog +0 -54
  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 -216
  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 -225
  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,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,125 @@
1
+ module IMS::LTI::Serializers
2
+ class Base
3
+ class Filter
4
+ def initialize
5
+ @optionals = Set.new
6
+ @keys = {}
7
+ @serializables = Set.new
8
+ @serializable_lists = Set.new
9
+ end
10
+
11
+ def add_filter(name, opts={})
12
+ @optionals.add(name) if opts[:optional]
13
+ @keys[name] = opts[:key] if opts[:key]
14
+ @serializables.add(name) if opts[:serializable]
15
+ @serializable_lists.add(name) if opts[:list_of_serializables]
16
+ end
17
+
18
+ def options_for_attribute(attribute)
19
+ options = {}
20
+ options[:optional] = @optionals.include?(attribute) ? true : false
21
+ options[:key] = @keys[attribute]
22
+ options[:has_serializable] = @serializables.include?(attribute) ? true : false
23
+ options[:has_list_of_serializables] = @serializable_lists.include?(attribute) ? true : false
24
+ options
25
+ end
26
+
27
+ def filter(hash)
28
+ hash = optionals(hash)
29
+ hash = serializables(hash)
30
+ hash = serializable_lists(hash)
31
+ keys(hash) # Needs to happen last
32
+ end
33
+
34
+ private
35
+ def optionals(hash)
36
+ hash.reject { |k, v| @optionals.include?(k) && v.nil? }
37
+ end
38
+
39
+ def serializables(hash)
40
+ hash.reduce({}) do |memo, (k, v)|
41
+ memo[k] = @serializables.include?(k) ? serialize_serializable(v) : v
42
+ memo
43
+ end
44
+ end
45
+
46
+ def serialize_serializable(serializable)
47
+ return if serializable.nil?
48
+ serializable.as_json
49
+ end
50
+
51
+ def serializable_lists(hash)
52
+ hash.reduce({}) do |memo, (k, v)|
53
+ memo[k] = @serializable_lists.include?(k) ? serialize_serializable_list(v) : v
54
+ memo
55
+ end
56
+ end
57
+
58
+ def serialize_serializable_list(list)
59
+ return if list.nil?
60
+ list.map(&:as_json)
61
+ end
62
+
63
+ def keys(hash)
64
+ hash.reduce({}) do |memo, (k, v)|
65
+ key = @keys.include?(k) ? @keys[k] : k
66
+ memo[key] = v
67
+ memo
68
+ end
69
+ end
70
+ end
71
+
72
+ @filter = Filter.new
73
+ @attributes = Set.new
74
+
75
+ def self.inherited(subclass)
76
+ subclass.instance_variable_set(:@filter, Filter.new)
77
+ subclass.instance_variable_set(:@attributes, Set.new)
78
+ end
79
+
80
+ def self.set_attribute(name, opts={})
81
+ @attributes.add(name)
82
+ @filter.add_filter(name, opts)
83
+ end
84
+
85
+ def self.set_attributes(*names)
86
+ names.each { |name| set_attribute(name) }
87
+ end
88
+
89
+ def self.has_serializable(name, opts={})
90
+ opts[:serializable] = true
91
+ opts[:list_of_serializables] = false
92
+ set_attribute(name, opts)
93
+ end
94
+
95
+ def self.has_list_of_serializables(name, opts={})
96
+ opts[:list_of_serializables] = true
97
+ opts[:serializable] = false
98
+ set_attribute(name, opts)
99
+ end
100
+
101
+ def self.as_json(obj)
102
+ @filter.filter(base_hash(obj))
103
+ end
104
+
105
+ def self.to_json(obj)
106
+ as_json(obj).to_json
107
+ end
108
+
109
+ def self.options_for_attribute(attribute)
110
+ @filter.options_for_attribute(attribute)
111
+ end
112
+
113
+ def self.attributes
114
+ @attributes
115
+ end
116
+
117
+ private
118
+
119
+ def self.base_hash(obj)
120
+ h = {}
121
+ @attributes.each { |attribute| h[attribute] = obj.send(attribute) }
122
+ h
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,5 @@
1
+ module IMS::LTI::Serializers::MembershipService
2
+ class AgentSerializer < IMS::LTI::Serializers::Base
3
+ set_attribute :id, key: :@id
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ module IMS::LTI::Serializers::MembershipService
2
+ class ContainerSerializer < IMS::LTI::Serializers::Base
3
+ set_attribute :membership_predicate, key: :membershipPredicate
4
+ has_serializable :membership_subject, key: :membershipSubject
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module IMS::LTI::Serializers::MembershipService
2
+ class ContextSerializer < IMS::LTI::Serializers::Base
3
+ set_attribute :id, key: :@id
4
+ set_attribute :name
5
+ set_attribute :type, key: :@type
6
+ set_attribute :context_id, key: :contextId
7
+ has_list_of_serializables :membership
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module IMS::LTI::Serializers::MembershipService
2
+ class LISMembershipContainerSerializer < IMS::LTI::Serializers::Base
3
+ set_attribute :membership_predicate, key: :membershipPredicate
4
+ has_serializable :membership_subject, key: :membershipSubject
5
+ set_attribute :id, key: :@id
6
+ set_attribute :context, key: :@context
7
+ set_attribute :type, key: :@type
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module IMS::LTI::Serializers::MembershipService
2
+ class LISPersonSerializer < IMS::LTI::Serializers::Base
3
+ set_attribute :id, key: :@id
4
+ set_attributes :name, :img, :email
5
+ set_attribute :family_name, key: :familyName
6
+ set_attribute :given_name, key: :givenName
7
+ set_attribute :result_sourced_id, key: :resultSourcedId
8
+ set_attribute :sourced_id, key: :sourcedId
9
+ set_attribute :user_id, key: :userId
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module IMS::LTI::Serializers::MembershipService
2
+ class MembershipSerializer < IMS::LTI::Serializers::Base
3
+ set_attribute :id, key: :@id
4
+ set_attributes :status, :role
5
+ has_serializable :member
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module IMS::LTI::Serializers::MembershipService
2
+ class OrganizationSerializer < IMS::LTI::Serializers::Base
3
+ set_attribute :id, key: :@id
4
+ set_attribute :name
5
+ set_attribute :type, key: :@type
6
+ has_list_of_serializables :membership
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ module IMS::LTI::Serializers::MembershipService
2
+ class PageSerializer < IMS::LTI::Serializers::Base
3
+ set_attribute :id, key: :@id
4
+ set_attribute :type, key: :@type
5
+ set_attribute :context, key: :@context
6
+ set_attribute :differences
7
+ set_attribute :next_page, key: :nextPage
8
+ has_serializable :page_of, key: :pageOf
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module IMS::LTI::Serializers::MembershipService
2
+ class PersonSerializer < IMS::LTI::Serializers::Base
3
+ set_attribute :id, key: :@id
4
+ set_attributes :name, :img
5
+ set_attribute :family_name, key: :familyName
6
+ set_attribute :given_name, key: :givenName
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ module IMS::LTI::Serializers
2
+ module MembershipService
3
+ require_relative 'membership_service/agent_serializer'
4
+ require_relative 'membership_service/container_serializer'
5
+ require_relative 'membership_service/context_serializer'
6
+ require_relative 'membership_service/lis_membership_container_serializer'
7
+ require_relative 'membership_service/lis_person_serializer'
8
+ require_relative 'membership_service/membership_serializer'
9
+ require_relative 'membership_service/organization_serializer'
10
+ require_relative 'membership_service/page_serializer'
11
+ require_relative 'membership_service/person_serializer'
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module IMS::LTI
2
+ module Serializers
3
+ require_relative 'serializers/base'
4
+ require_relative 'serializers/membership_service'
5
+ end
6
+ end
@@ -0,0 +1,67 @@
1
+ module IMS::LTI::Services
2
+ class AuthenticationService
3
+
4
+ attr_accessor :connection, :iss, :aud, :sub, :secret, :grant_type,
5
+ :additional_claims, :additional_params
6
+ attr_writer :secret
7
+
8
+ def initialize(iss:, aud:, sub:, secret:)
9
+ @iss = iss
10
+ @aud = aud
11
+ @sub = sub
12
+ @secret = secret
13
+ @additional_claims = {}
14
+ @additional_params = {}
15
+ @grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
16
+ end
17
+
18
+ def connection
19
+ @connection ||= Faraday.new
20
+ end
21
+
22
+ def access_token
23
+ access_token_request['access_token']
24
+ end
25
+
26
+ def expiration
27
+ expires_in = access_token_request['expires_in'].to_i
28
+ @_response_time + expires_in
29
+ end
30
+
31
+ def expired?
32
+ expiration < Time.now
33
+ end
34
+
35
+ def invalidate!
36
+ @_access_token_request = nil
37
+ @_response_time = nil
38
+ end
39
+
40
+ private
41
+
42
+ def access_token_request
43
+ @_access_token_request ||= begin
44
+ assertion = JSON::JWT.new(
45
+ iss: iss,
46
+ sub: sub,
47
+ aud: aud.to_s,
48
+ iat: Time.now.to_i,
49
+ exp: 1.minute.from_now,
50
+ jti: SecureRandom.uuid
51
+ )
52
+ assertion.merge!(@additional_claims)
53
+ assertion = assertion.sign(@secret, :HS256).to_s
54
+ body = {
55
+ grant_type: grant_type,
56
+ assertion: assertion
57
+ }
58
+ body.merge!(@additional_params)
59
+ response = connection.post(aud, body)
60
+ raise IMS::LTI::Errors::AuthenticationFailedError.new(response: response) unless response.success?
61
+ @_response_time = Time.now
62
+ response.body
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,80 @@
1
+ module IMS::LTI::Services
2
+ class MessageAuthenticator
3
+
4
+ attr_reader :launch_url, :params, :message, :simple_oauth_header, :signature, :consumer_key
5
+
6
+ def initialize(launch_url, params, secret)
7
+ @launch_url = launch_url
8
+ @params = params
9
+ @options, @parsed_params = parse_params(params)
10
+ @consumer_key = @options[:consumer_key]
11
+ @signature = @parsed_params.delete(:oauth_signature)
12
+ @secret = secret
13
+ end
14
+
15
+
16
+ def valid_signature?
17
+ message.jwt ? valid_jwt? : simple_oauth_header.valid?(signature: signature)
18
+ end
19
+
20
+ def message
21
+ @message ||= begin
22
+ m = IMS::LTI::Models::Messages::Message.generate(params)
23
+ m.launch_url = launch_url
24
+ m
25
+ end
26
+ end
27
+
28
+ def simple_oauth_header
29
+ @simple_oauth_header ||= begin
30
+ @simple_oauth_header = SimpleOAuth::Header.new(
31
+ :post, launch_url,
32
+ @parsed_params,
33
+ @options.merge(
34
+ {
35
+ consumer_key: consumer_key,
36
+ consumer_secret: @secret
37
+ }
38
+ )
39
+ )
40
+ @simple_oauth_header
41
+ end
42
+ end
43
+
44
+ def base_string
45
+ simple_oauth_header.send(:signature_base)
46
+ end
47
+
48
+ def signed_params
49
+ simple_oauth_header.signed_attributes.merge(@parsed_params)
50
+ end
51
+
52
+
53
+ private
54
+
55
+ def valid_jwt?
56
+ begin
57
+ jwt = JSON::JWT.decode(message.jwt, @secret)
58
+ aud1 = Addressable::URI.parse(jwt['aud'])
59
+ aud2 = Addressable::URI.parse(launch_url)
60
+ [aud1, aud2].each{ |aud| aud.fragment = '' }
61
+ aud1.normalize == aud2.normalize
62
+ rescue JSON::JWS::VerificationFailed
63
+ false
64
+ end
65
+ end
66
+
67
+ def parse_params(params)
68
+ params.inject([{}, {}]) do |array, (k, v)|
69
+ attr = k.to_s.sub('oauth_', '').to_sym
70
+ if SimpleOAuth::Header::ATTRIBUTE_KEYS.include?(attr)
71
+ array[0][attr] = v
72
+ else
73
+ array[1][k.to_sym] = v
74
+ end
75
+ array
76
+ end
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,18 @@
1
+ module IMS::LTI::Services
2
+
3
+ class OAuth2Client
4
+ attr_accessor :token, :base_url
5
+ attr_writer :connection
6
+
7
+ def initialize(token:, base_url: nil)
8
+ @base_url = base_url
9
+ @token = token
10
+ end
11
+
12
+ def connection
13
+ @connection ||= Faraday.new base_url do |conn|
14
+ conn.authorization :Bearer, token
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,4 +1,4 @@
1
- module IMS::LTI
1
+ module IMS::LTI::Services
2
2
  # Class used to represent an LTI configuration
3
3
  #
4
4
  # It can create and read the Common Cartridge XML representation of LTI links
@@ -8,7 +8,7 @@ module IMS::LTI
8
8
  # To generate an XML configuration:
9
9
  #
10
10
  # # Create a config object and set some options
11
- # tc = IMS::LTI::ToolConfig.new(:title => "Example Sinatra Tool Provider", :launch_url => url)
11
+ # tc = IMS::LTI::Services::ToolConfig.new(:title => "Example Sinatra Tool Provider", :launch_url => url)
12
12
  # tc.description = "This example LTI Tool Provider supports LIS Outcome pass-back."
13
13
  #
14
14
  # # generate the XML
@@ -16,7 +16,7 @@ module IMS::LTI
16
16
  #
17
17
  # Or to create a config object from an XML String:
18
18
  #
19
- # tc = IMS::LTI::ToolConfig.create_from_xml(xml)
19
+ # tc = IMS::LTI::Services::ToolConfig.create_from_xml(xml)
20
20
  class ToolConfig
21
21
  attr_reader :custom_params, :extensions
22
22
 
@@ -79,10 +79,10 @@ module IMS::LTI
79
79
 
80
80
  # Namespaces used for parsing configuration XML
81
81
  LTI_NAMESPACES = {
82
- "xmlns" => 'http://www.imsglobal.org/xsd/imslticc_v1p0',
83
- "blti" => 'http://www.imsglobal.org/xsd/imsbasiclti_v1p0',
84
- "lticm" => 'http://www.imsglobal.org/xsd/imslticm_v1p0',
85
- "lticp" => 'http://www.imsglobal.org/xsd/imslticp_v1p0',
82
+ "xmlns" => 'http://www.imsglobal.org/xsd/imslticc_v1p0',
83
+ "blti" => 'http://www.imsglobal.org/xsd/imsbasiclti_v1p0',
84
+ "lticm" => 'http://www.imsglobal.org/xsd/imslticm_v1p0',
85
+ "lticp" => 'http://www.imsglobal.org/xsd/imslticp_v1p0',
86
86
  }
87
87
 
88
88
  # Parse tool configuration data out of the Common Cartridge LTI link XML
@@ -115,7 +115,13 @@ module IMS::LTI
115
115
  platform = vendor_ext_node.attributes['platform']
116
116
  properties = {}
117
117
  set_properties(properties, vendor_ext_node)
118
- set_options(properties, vendor_ext_node)
118
+ REXML::XPath.each(vendor_ext_node, 'lticm:options', LTI_NAMESPACES) do |options_node|
119
+ opt_name = options_node.attributes['name']
120
+ options = {}
121
+ set_properties(options, options_node)
122
+ properties[opt_name] = options
123
+ end
124
+
119
125
  self.set_ext_params(platform, properties)
120
126
  end
121
127
 
@@ -124,8 +130,6 @@ module IMS::LTI
124
130
 
125
131
  # Generate XML from the current settings
126
132
  def to_xml(opts = {})
127
- raise IMS::LTI::InvalidLTIConfigError, "A launch url is required for an LTI configuration." unless self.launch_url || self.secure_launch_url
128
-
129
133
  builder = Builder::XmlMarkup.new(:indent => opts[:indent] || 0)
130
134
  builder.instruct!
131
135
  builder.cartridge_basiclti_link("xmlns" => "http://www.imsglobal.org/xsd/imslticc_v1p0",
@@ -141,7 +145,7 @@ module IMS::LTI
141
145
  end
142
146
 
143
147
  vendor_keys = %w{name code description url}
144
- if vendor_keys.any?{|k|self.send("vendor_#{k}")} || vendor_contact_email
148
+ if vendor_keys.any? { |k| self.send("vendor_#{k}") } || vendor_contact_email
145
149
  blti_node.blti :vendor do |v_node|
146
150
  vendor_keys.each do |key|
147
151
  v_node.lticp key.to_sym, self.send("vendor_#{key}") if self.send("vendor_#{key}")
@@ -169,7 +173,17 @@ module IMS::LTI
169
173
  ext_params = @extensions[ext_platform]
170
174
  blti_node.blti(:extensions, :platform => ext_platform) do |ext_node|
171
175
  ext_params.keys.sort.each do |key|
172
- nest_xml(ext_node, key, ext_params[key])
176
+ val = ext_params[key]
177
+ if val.is_a?(Hash)
178
+ ext_node.lticm(:options, :name => key) do |type_node|
179
+ val.keys.sort.each do |p_key|
180
+ p_val = val[p_key]
181
+ type_node.lticm :property, p_val, 'name' => p_key
182
+ end
183
+ end
184
+ else
185
+ ext_node.lticm :property, val, 'name' => key
186
+ end
173
187
  end
174
188
  end
175
189
  end
@@ -183,18 +197,6 @@ module IMS::LTI
183
197
 
184
198
  private
185
199
 
186
- def nest_xml(ext_node, key, value)
187
- if value.is_a?(Hash)
188
- ext_node.lticm(:options, :name => key) do |type_node|
189
- value.keys.sort.each do |sub_key|
190
- nest_xml(type_node, sub_key, value[sub_key])
191
- end
192
- end
193
- else
194
- ext_node.lticm :property, value, 'name' => key
195
- end
196
- end
197
-
198
200
  def get_node_text(node, path)
199
201
  if val = REXML::XPath.first(node, path, LTI_NAMESPACES)
200
202
  val.text
@@ -217,15 +219,5 @@ module IMS::LTI
217
219
  end
218
220
  end
219
221
 
220
- def set_options(hash, node)
221
- REXML::XPath.each(node, 'lticm:options', LTI_NAMESPACES) do |options_node|
222
- opt_name = options_node.attributes['name']
223
- options = {}
224
- set_properties(options, options_node)
225
- set_options(options, options_node)
226
- hash[opt_name] = options
227
- end
228
- end
229
-
230
222
  end
231
223
  end
@@ -0,0 +1,16 @@
1
+ module IMS::LTI::Services
2
+ class ToolConsumerProfileService
3
+
4
+ attr_accessor :tcp
5
+
6
+ def initialize(tool_consumer_profile)
7
+ @tcp = tool_consumer_profile
8
+ end
9
+
10
+ def supports_capabilities?(capability, *capabilities)
11
+ capabilities.unshift(capability)
12
+ (capabilities - tcp.capabilities_offered).empty?
13
+ end
14
+
15
+ end
16
+ end