Class: Cerner::OAuth1a::AccessToken

Inherits:
Object
  • Object
show all
Defined in:
lib/cerner/oauth1a/access_token.rb

Overview

Public: A Cerner OAuth 1.0a Access Token and related request parameters for use in Consumer or Service Provider use cases.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(accessor_secret: nil, consumer_key:, expires_at: nil, nonce: nil, signature: nil, signature_method: 'PLAINTEXT', timestamp: nil, token:, token_secret: nil, realm: nil) ⇒ AccessToken

Public: Constructs an instance.

arguments - The keyword arguments of the method:

:accessor_secret  - The optional String representing the accessor secret.
:consumer_key     - The required String representing the consumer key.
:expires_at       - An optional Time representing the expiration moment or any
                    object responding to to_i that represents the expiration
                    moment as the number of seconds since the epoch.
:nonce            - The optional String representing the nonce.
:timestamp        - A optional Time representing the creation moment or any
                    object responding to to_i that represents the creation
                    moment as the number of seconds since the epoch.
:token            - The required String representing the token.
:token_secret     - The optional String representing the token secret.
:signature_method - The optional String representing the signature method.
                    Defaults to PLAINTEXT.
:signature        - The optional String representing the signature.
:realm            - The optional String representing the protection realm.

Raises ArgumentError if consumer_key or token is nil.

Raises:

  • (ArgumentError)


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/cerner/oauth1a/access_token.rb', line 102

def initialize(
  accessor_secret: nil,
  consumer_key:,
  expires_at: nil,
  nonce: nil,
  signature: nil,
  signature_method: 'PLAINTEXT',
  timestamp: nil,
  token:,
  token_secret: nil,
  realm: nil
)
  raise ArgumentError, 'consumer_key is nil' unless consumer_key
  raise ArgumentError, 'token is nil' unless token

  @accessor_secret = accessor_secret || nil
  @consumer_key = consumer_key
  @consumer_principal = nil
  @expires_at = expires_at ? Internal.convert_to_time(time: expires_at, name: 'expires_at') : nil
  @nonce = nonce
  @signature = signature
  @signature_method = signature_method || 'PLAINTEXT'
  @timestamp = timestamp ? Internal.convert_to_time(time: timestamp, name: 'timestamp') : nil
  @token = token
  @token_secret = token_secret || nil
  @realm = realm || nil
end

Instance Attribute Details

#accessor_secretObject (readonly)

Returns a String, but may be nil, with the Accessor Secret (oauth_accessor_secret) related to this token. Note: nil and empty are considered equivalent.



56
57
58
# File 'lib/cerner/oauth1a/access_token.rb', line 56

def accessor_secret
  @accessor_secret
end

#consumer_keyObject (readonly)

Returns a String with the Consumer Key (oauth_consumer_key) related to this token.



58
59
60
# File 'lib/cerner/oauth1a/access_token.rb', line 58

def consumer_key
  @consumer_key
end

#consumer_principalObject (readonly)

Returns a String with the Consumer Principal (Consumer.Principal param encoded within oauth_token). This value is only populated after a successful #authenticate and only if the #token (oauth_token) contains a ‘Consumer.Principal’ parameter.



78
79
80
# File 'lib/cerner/oauth1a/access_token.rb', line 78

def consumer_principal
  @consumer_principal
end

#expires_atObject (readonly)

Returns a Time, but may be nil, which represents the moment when this token expires.



60
61
62
# File 'lib/cerner/oauth1a/access_token.rb', line 60

def expires_at
  @expires_at
end

#nonceObject (readonly)

Returns a String, but may be nil, with the Nonce (oauth_nonce) related to this token. This is generally only populated when parsing a token for authentication.



63
64
65
# File 'lib/cerner/oauth1a/access_token.rb', line 63

def nonce
  @nonce
end

#realmObject (readonly)

Returns a String, but may be nil, with the Protection Realm related to this token.



80
81
82
# File 'lib/cerner/oauth1a/access_token.rb', line 80

def realm
  @realm
end

#signatureObject (readonly)

Returns a String, but may be nil, with the Signature (oauth_signature) related to this token.



74
75
76
# File 'lib/cerner/oauth1a/access_token.rb', line 74

def signature
  @signature
end

#signature_methodObject (readonly)

Returns a String with the Signature Method (oauth_signature_method) related to this token.



72
73
74
# File 'lib/cerner/oauth1a/access_token.rb', line 72

def signature_method
  @signature_method
end

#timestampObject (readonly)

Returns a Time, but may be nil, with the Timestamp (oauth_timestamp) related to this token. This is generally only populated when parsing a token for authentication.



66
67
68
# File 'lib/cerner/oauth1a/access_token.rb', line 66

def timestamp
  @timestamp
end

#tokenObject (readonly)

Returns a String with the Token (oauth_token).



68
69
70
# File 'lib/cerner/oauth1a/access_token.rb', line 68

def token
  @token
end

#token_secretObject (readonly)

Returns a String, but may be nil, with the Token Secret related to this token.



70
71
72
# File 'lib/cerner/oauth1a/access_token.rb', line 70

def token_secret
  @token_secret
end

Class Method Details

.from_authorization_header(value) ⇒ Object

Public: Constructs an AccessToken using the value of an HTTP Authorization Header based on the OAuth HTTP Authorization Scheme (oauth.net/core/1.0a/#auth_header).

value - A String containing the HTTP Authorization Header value.

Returns an AccessToken.

Raises a Cerner::OAuth1a::OAuthError with a populated oauth_problem if any of the parameters in the value are invalid.

Raises:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/cerner/oauth1a/access_token.rb', line 23

def self.from_authorization_header(value)
  params = Protocol.parse_authorization_header(value)

  if params[:oauth_version] && !params[:oauth_version].eql?('1.0')
    raise OAuthError.new('', nil, 'version_rejected')
  end

  missing_params = []
  consumer_key = params[:oauth_consumer_key]
  missing_params << :oauth_consumer_key if consumer_key.nil? || consumer_key.empty?
  token = params[:oauth_token]
  missing_params << :oauth_token if token.nil? || token.empty?
  signature_method = params[:oauth_signature_method]
  missing_params << :oauth_signature_method if signature_method.nil? || signature_method.empty?
  signature = params[:oauth_signature]
  missing_params << :oauth_signature if signature.nil? || signature.empty?

  raise OAuthError.new('', nil, 'parameter_absent', missing_params) unless missing_params.empty?

  AccessToken.new(
    accessor_secret: params[:oauth_accessor_secret],
    consumer_key: consumer_key,
    nonce: params[:oauth_nonce],
    timestamp: params[:oauth_timestamp],
    token: token,
    signature_method: signature_method,
    signature: signature,
    realm: params[:realm]
  )
end

Instance Method Details

#==(other) ⇒ Object

Public: Compare this to other based on attributes.

other - The AccessToken to compare this to.

Return true if equal; false otherwise



325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/cerner/oauth1a/access_token.rb', line 325

def ==(other)
  accessor_secret == other.accessor_secret &&
    consumer_key == other.consumer_key &&
    expires_at == other.expires_at &&
    nonce == other.nonce &&
    timestamp == other.timestamp &&
    token == other.token &&
    token_secret == other.token_secret &&
    signature_method == other.signature_method &&
    signature == other.signature &&
    realm == other.realm
end

#authenticate(access_token_agent, http_method: 'GET', fully_qualified_url: nil, request_params: nil) ⇒ Object

Public: Authenticates the #token against the #consumer_key, #signature and side-channel secrets exchange via AccessTokenAgent#retrieve_keys. If this instance has a #realm set, then it will compare it to the AccessTokenAgent#realm using the AccessTokenAgent#realm_eql? method.

access_token_agent - An instance of Cerner::OAuth1a::AccessTokenAgent configured with

appropriate credentials to retrieve secrets via
Cerner::OAuth1a::AccessTokenAgent#retrieve_keys.

keywords - The keyword arguments:

:http_method         - An optional String or Symbol containing an HTTP
                       method name. (default: 'GET')
:fully_qualified_url - An optional String or URI that contains the
                       scheme, host, port (optional) and path of a URL.
:request_params      - An optional Hash of name/value pairs
                       representing the request parameters. The keys
                       and values  of the Hash will be assumed to be
                       represented by the value returned from #to_s.

Returns a Hash (symbolized keys) of any extra parameters within #token (oauth_token), if authentication succeeds. In most scenarios, the Hash will be empty.

Raises ArgumentError if access_token_agent is nil Raises Cerner::OAuth1a::OAuthError with an oauth_problem if authentication fails.

Raises:

  • (ArgumentError)


259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/cerner/oauth1a/access_token.rb', line 259

def authenticate(
  access_token_agent,
  http_method: 'GET',
  fully_qualified_url: nil,
  request_params: nil
)
  raise ArgumentError, 'access_token_agent is nil' unless access_token_agent

  if @realm && !access_token_agent.realm_eql?(@realm)
    raise OAuthError.new('realm does not match provider', nil, 'token_rejected', nil, access_token_agent.realm)
  end

  # Set realm to the provider's realm if it's not already set
  @realm ||= access_token_agent.realm

  tuples = Protocol.parse_url_query_string(@token)

  unless @consumer_key == tuples.delete(:ConsumerKey)
    raise OAuthError.new('consumer keys do not match', nil, 'consumer_key_rejected', nil, @realm)
  end

  verify_expiration(tuples.delete(:ExpiresOn))

  keys = load_keys(access_token_agent, tuples.delete(:KeysVersion))

  verify_token(keys)
  # RSASHA1 param gets consumed in #verify_token, so remove it too
  tuples.delete(:RSASHA1)

  verify_signature(
    keys: keys,
    hmac_secrets: tuples.delete(:HMACSecrets),
    http_method: http_method,
    fully_qualified_url: fully_qualified_url,
    request_params: request_params
  )

  @consumer_principal = tuples.delete(:"Consumer.Principal")

  tuples
end

#authorization_header(nonce: nil, timestamp: nil, http_method: 'GET', fully_qualified_url: nil, request_params: nil) ⇒ Object

Public: Generates a value suitable for use as an HTTP Authorization header. If #signature is nil, then a signature will be generated based on the #signature_method.

PLAINTEXT Signature (preferred)

When using PLAINTEXT signatures, no additional arguments are necessary. If an oauth_nonce or oauth_timestamp are desired, then the values can be passed via the :nonce and :timestamp keyword arguments. The actual signature will be constructed from the Accessor Secret (#accessor_secret) and the Token Secret (#token_secret).

HMAC-SHA1 Signature

When using HMAC-SHA1 signatures, access to the HTTP request information is necessary. This requies that additional information is passed via the keyword arguments. The required information includes the HTTP method (see :http_method), the host authority & path (see :fully_qualified_url) and the request parameters (see :fully_qualified_url and :request_params).

keywords - The keyword arguments:

:nonce               - The optional String containing a Nonce to generate the
                       header with HMAC-SHA1 signatures. When nil, a Nonce will
                       be generated.
:timestamp           - The optional Time or #to_i compliant object containing a
                       Timestamp to generate the header with HMAC-SHA1
                       signatures. When nil, a Timestamp will be generated.
:http_method         - The optional String or Symbol containing a HTTP Method for
                       constructing the HMAC-SHA1 signature. When nil, the value
                       defualts to 'GET'.
:fully_qualified_url - The optional String or URI containing the fully qualified
                       URL of the HTTP API being invoked for constructing the
                       HMAC-SHA1 signature. If the URL contains a query string,
                       the parameters will be extracted and used in addition to
                       the :request_params keyword argument.
:request_params      - The optional Hash of name/value pairs containing the
                       request parameters of the HTTP API being invoked for
                       constructing the HMAC-SHA1 signature. Parameters passed
                       here will override and augment those passed in the
                       :fully_qualified_url parameter. The parameter names and
                       values MUST be unencoded. See
                       Protocol#parse_url_query_string for help with decoding an
                       encoded query string.

Returns a String representation of the access token.

Raises Cerner::OAuth1a::OAuthError if #signature_method is not PLAINTEXT or if a signature can’t be determined.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/cerner/oauth1a/access_token.rb', line 176

def authorization_header(
  nonce: nil, timestamp: nil, http_method: 'GET', fully_qualified_url: nil, request_params: nil
)
  oauth_params = {}
  oauth_params[:oauth_version] = '1.0'
  oauth_params[:oauth_signature_method] = @signature_method
  oauth_params[:oauth_consumer_key] = @consumer_key
  oauth_params[:oauth_nonce] = nonce if nonce
  oauth_params[:oauth_timestamp] = Internal.convert_to_time(time: timestamp, name: 'timestamp').to_i if timestamp
  oauth_params[:oauth_token] = @token

  if @signature
    sig = @signature
  else
    # NOTE: @accessor_secret is always used, but an empty value is allowed and project assumes
    # that nil implies an empty value

    raise OAuthError.new('token_secret is nil', nil, 'parameter_absent', nil, @realm) unless @token_secret

    if @signature_method == 'PLAINTEXT'
      sig =
        Signature.sign_via_plaintext(client_shared_secret: @accessor_secret, token_shared_secret: @token_secret)
    elsif @signature_method == 'HMAC-SHA1'
      http_method ||= 'GET' # default to HTTP GET
      request_params ||= {} # default to no request params
      oauth_params[:oauth_nonce] = Internal.generate_nonce unless oauth_params[:oauth_nonce]
      oauth_params[:oauth_timestamp] = Internal.generate_timestamp unless oauth_params[:oauth_timestamp]

      begin
        fully_qualified_url = Internal.convert_to_http_uri(url: fully_qualified_url, name: 'fully_qualified_url')
      rescue ArgumentError => ae
        raise OAuthError.new(ae.message, nil, 'parameter_absent', nil, @realm)
      end

      query_params = fully_qualified_url.query ? Protocol.parse_url_query_string(fully_qualified_url.query) : {}
      request_params = query_params.merge(request_params)

      params = request_params.merge(oauth_params)
      signature_base_string =
        Signature.build_signature_base_string(
          http_method: http_method, fully_qualified_url: fully_qualified_url, params: params
        )

      sig =
        Signature.sign_via_hmacsha1(
          client_shared_secret: @accessor_secret,
          token_shared_secret: @token_secret,
          signature_base_string: signature_base_string
        )
    else
      raise OAuthError.new('signature_method is invalid', nil, 'signature_method_rejected', nil, @realm)
    end
  end

  oauth_params[:realm] = @realm if @realm
  oauth_params[:oauth_signature] = sig

  Protocol.generate_authorization_header(oauth_params)
end

#eql?(other) ⇒ Boolean

Public: Compare this to other based on the attributes. Equivalent to calling #==.

other - The AccessToken to compare this to.

Return true if equal; false otherwise

Returns:

  • (Boolean)


343
344
345
# File 'lib/cerner/oauth1a/access_token.rb', line 343

def eql?(other)
  self == other
end

#expired?(now: Time.now, fudge_sec: 300) ⇒ Boolean

Public: Check whether the access token has expired, if #expires_at is not nil. By default (with no arguments), the method checks whether the token has expired based on the current time and a fudge factor of 300 seconds (5 minutes). Non-default argument values can be used to see whether the access token has expired at a different time and with a different fudge factor.

now - A Time instance to check the expiration information against. Defaults to

Time.now.

fudge_sec - The number of seconds to remove from #expires_at to adjust the comparison.

Returns true if the access token is expired or #expires_at is nil; false otherwise

Returns:

  • (Boolean)


312
313
314
315
316
317
318
# File 'lib/cerner/oauth1a/access_token.rb', line 312

def expired?(now: Time.now, fudge_sec: 300)
  # if @expires_at is nil, return true now
  return true unless @expires_at

  now = Internal.convert_to_time(time: now, name: 'now')
  now.tv_sec >= @expires_at.tv_sec - fudge_sec
end

#to_hObject

Public: Generates a Hash of the attributes.

Returns a Hash with keys for each attribute.



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/cerner/oauth1a/access_token.rb', line 350

def to_h
  {
    accessor_secret: @accessor_secret,
    consumer_key: @consumer_key,
    expires_at: @expires_at,
    nonce: @nonce,
    timestamp: @timestamp,
    token: @token,
    token_secret: @token_secret,
    signature_method: @signature_method,
    signature: @signature,
    consumer_principal: @consumer_principal,
    realm: @realm
  }
end