Class: IMS::LTI::Models::Messages::Message

Inherits:
Object
  • Object
show all
Defined in:
lib/ims/lti/models/messages/message.rb

Direct Known Subclasses

ContentItemSelection, RequestMessage

Constant Summary collapse

MESSAGE_TYPE =
"".freeze
LAUNCH_TARGET_IFRAME =
'iframe'
LAUNCH_TARGET_WINDOW =
'window'
EXTENSION_PREFIX =
'ext_'
CUSTOM_PREFIX =
'custom_'
OAUTH_KEYS =
:oauth_callback, :oauth_consumer_key, :oauth_nonce, :oauth_signature, :oauth_signature_method,
:oauth_timestamp, :oauth_token, :oauth_verifier, :oauth_version

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attrs = {}, custom_params = {}, ext_params = {}) ⇒ Message

Returns a new instance of Message.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/ims/lti/models/messages/message.rb', line 121

def initialize(attrs = {}, custom_params = {}, ext_params = {})

  @custom_params = custom_params
  @ext_params = ext_params
  @unknown_params = {}

  attrs.each do |k, v|
    str_key = k.to_s
    if str_key.start_with?(EXTENSION_PREFIX)
      @ext_params[str_key] = v
    elsif str_key.start_with?(CUSTOM_PREFIX)
      @custom_params[str_key] = v
    elsif !v.nil? && self.respond_to?(k.to_sym)
      send(("#{k}=").to_sym, v)

    else
      warn "Unknown parameter #{k}"
      @unknown_params[str_key] = v
    end
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &block) ⇒ Object



197
198
199
200
201
202
203
204
205
# File 'lib/ims/lti/models/messages/message.rb', line 197

def method_missing(meth, *args, &block)
  if match = /^(custom|ext)_([^=$]*)/.match(meth)
    param_type, key = match.captures
    param_hash = instance_variable_get("@#{param_type}_params".to_sym)
    meth =~ /=$/ ? param_hash[match.to_s] = args[0] : param_hash[match.to_s]
  else
    super
  end
end

Instance Attribute Details

#custom_paramsObject (readonly)

Returns the value of attribute custom_params.



98
99
100
# File 'lib/ims/lti/models/messages/message.rb', line 98

def custom_params
  @custom_params
end

#ext_paramsObject (readonly)

Returns the value of attribute ext_params.



98
99
100
# File 'lib/ims/lti/models/messages/message.rb', line 98

def ext_params
  @ext_params
end

#message_authenticatorObject (readonly)

Returns the value of attribute message_authenticator.



4
5
6
# File 'lib/ims/lti/models/messages/message.rb', line 4

def message_authenticator
  @message_authenticator
end

#unknown_paramsObject (readonly)

Returns the value of attribute unknown_params.



98
99
100
# File 'lib/ims/lti/models/messages/message.rb', line 98

def unknown_params
  @unknown_params
end

Class Method Details

.add_deprecated_params(param, *params) ⇒ Object



36
37
38
# File 'lib/ims/lti/models/messages/message.rb', line 36

def add_deprecated_params(param, *params)
  add_params('@deprecated_params', param, *params)
end

.add_optional_params(param, *params) ⇒ Object



28
29
30
# File 'lib/ims/lti/models/messages/message.rb', line 28

def add_optional_params(param, *params)
  add_params('@optional_params', param, *params)
end


20
21
22
# File 'lib/ims/lti/models/messages/message.rb', line 20

def add_recommended_params(param, *params)
  add_params('@recommended_params', param, *params)
end

.add_required_params(param, *params) ⇒ Object



12
13
14
# File 'lib/ims/lti/models/messages/message.rb', line 12

def add_required_params(param, *params)
  add_params('@required_params', param, *params)
end

.convert_param_values_to_crlf_endings(hash) ⇒ Object

For signature generation – see usage in signed_post_params



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/ims/lti/models/messages/message.rb', line 51

def convert_param_values_to_crlf_endings(hash)
  hash.transform_values do |val|
    if val.is_a?(String)
      # Convert to all newlines first, for consistency, just in case there
      # is some weird mix of newlines & carriage returns in input
      val.encode(universal_newline: true).encode(crlf_newline: true)
    else
      val
    end
  end
end

.deprecated_paramsObject



32
33
34
# File 'lib/ims/lti/models/messages/message.rb', line 32

def deprecated_params
  supers_params('@deprecated_params') | (@deprecated_params || [])
end

.descendantsObject



46
47
48
# File 'lib/ims/lti/models/messages/message.rb', line 46

def descendants
  @descendants || Set.new
end

.generate(launch_params) ⇒ Object



102
103
104
105
106
107
108
# File 'lib/ims/lti/models/messages/message.rb', line 102

def self.generate(launch_params)
  params = launch_params.key?('jwt') ? parse_jwt(jwt: launch_params['jwt']) : launch_params
  klass = self.descendants.select {|d| d::MESSAGE_TYPE == params['lti_message_type']}.first
  message = klass ? klass.new(params) : Message.new(params)
  message.jwt = launch_params['jwt'] if launch_params.key?('jwt')
  message
end

.inherited(klass) ⇒ Object



40
41
42
43
44
# File 'lib/ims/lti/models/messages/message.rb', line 40

def inherited(klass)
  @descendants ||= Set.new
  @descendants << klass
  superclass.inherited(klass) unless (self == Message)
end

.optional_paramsObject



24
25
26
# File 'lib/ims/lti/models/messages/message.rb', line 24

def optional_params
  supers_params('@optional_params') | (@optional_params || [])
end

.parse_jwt(jwt:) ⇒ Object



110
111
112
113
114
115
116
117
118
119
# File 'lib/ims/lti/models/messages/message.rb', line 110

def self.parse_jwt(jwt:)
  decoded_jwt = JSON::JWT.decode(jwt, :skip_verification)
  params = decoded_jwt['org.imsglobal.lti.message'] || {}
  custom = params.delete(:custom)
  custom.each {|k,v| params["custom_#{k}"] = v }
  params['consumer_key'] = decoded_jwt[:sub]
  ext = params.delete(:ext)
  ext.each {|k,v| params["ext_#{k}"] = v }
  params
end


16
17
18
# File 'lib/ims/lti/models/messages/message.rb', line 16

def recommended_params
  supers_params('@recommended_params') | (@recommended_params || [])
end

.required_paramsObject



8
9
10
# File 'lib/ims/lti/models/messages/message.rb', line 8

def required_params
  supers_params('@required_params') | (@required_params || [])
end

Instance Method Details

#add_custom_params(params) ⇒ Object



143
144
145
# File 'lib/ims/lti/models/messages/message.rb', line 143

def add_custom_params(params)
  params.each {|k, v| k.to_s.start_with?('custom_') ? @custom_params[k.to_s] = v : @custom_params["custom_#{k.to_s}"] = v}
end

#deprecated_paramsObject



189
190
191
# File 'lib/ims/lti/models/messages/message.rb', line 189

def deprecated_params
  collect_attributes(self.class.deprecated_params)
end

#get_custom_paramsObject



147
148
149
# File 'lib/ims/lti/models/messages/message.rb', line 147

def get_custom_params
  @custom_params.inject({}) {|hash, (k, v)| hash[k.gsub(/\Acustom_/, '')] = v; hash}
end

#get_ext_paramsObject



151
152
153
# File 'lib/ims/lti/models/messages/message.rb', line 151

def get_ext_params
  @ext_params.inject({}) {|hash, (k, v)| hash[k.gsub(/\Aext_/, '')] = v; hash}
end

#jwt_params(private_key:, originating_domain:, algorithm: :HS256) ⇒ Object



159
160
161
# File 'lib/ims/lti/models/messages/message.rb', line 159

def jwt_params(private_key:, originating_domain:, algorithm: :HS256)
  { 'jwt' => to_jwt(private_key: private_key, originating_domain: originating_domain, algorithm: algorithm) }
end

#oauth_paramsObject



193
194
195
# File 'lib/ims/lti/models/messages/message.rb', line 193

def oauth_params
  collect_attributes(OAUTH_KEYS)
end

#optional_paramsObject



185
186
187
# File 'lib/ims/lti/models/messages/message.rb', line 185

def optional_params
  collect_attributes(self.class.optional_params)
end

#parametersObject



173
174
175
# File 'lib/ims/lti/models/messages/message.rb', line 173

def parameters
  collect_attributes(self.class.send("parameters"))
end

#post_paramsObject



155
156
157
# File 'lib/ims/lti/models/messages/message.rb', line 155

def post_params
  unknown_params.merge(@custom_params).merge(@ext_params).merge(parameters)
end


181
182
183
# File 'lib/ims/lti/models/messages/message.rb', line 181

def recommended_params
  collect_attributes(self.class.recommended_params)
end

#required_paramsObject



177
178
179
# File 'lib/ims/lti/models/messages/message.rb', line 177

def required_params
  collect_attributes(self.class.required_params)
end

#signed_post_params(secret) ⇒ Object



163
164
165
166
167
168
169
170
171
# File 'lib/ims/lti/models/messages/message.rb', line 163

def signed_post_params(secret)
  # The params will be used in an HTML form, and browsers will always use
  # newlines+carriage return line endings for submitted form data. The
  # signature needs to match, and signature generation does not add carriage
  # returns, so we ensure CRLF endings here.
  message_params = self.class.convert_param_values_to_crlf_endings(oauth_params.merge(post_params))
  @message_authenticator = IMS::LTI::Services::MessageAuthenticator.new(launch_url, message_params, secret)
  @message_authenticator.signed_params
end

#to_jwt(private_key:, originating_domain:, algorithm: :HS256) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/ims/lti/models/messages/message.rb', line 207

def to_jwt(private_key:, originating_domain:, algorithm: :HS256)
  now = Time.now
  exp = now + 60 * 5
  ims = unknown_params.merge(parameters)
  ims[:custom] = get_custom_params
  ims[:ext] = get_ext_params
  claim = {
    iss: originating_domain,
    sub: consumer_key,
    aud: launch_url,
    iat: now,
    exp: exp,
    jti: SecureRandom.uuid,
    "org.imsglobal.lti.message" => ims
  }
  jwt = JSON::JWT.new(claim).sign(private_key, algorithm)
  jwt.to_s
end