class Cerner::OAuth1a::AccessToken
Public: A Cerner
OAuth 1.0a Access Token and related request parameters for use in Consumer or Service Provider use cases.
Attributes
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.
Returns a String with the Consumer Key (oauth_consumer_key) related to this token.
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.
Returns a Time, but may be nil, which represents the moment when this token expires.
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.
Returns a String, but may be nil, with the Protection Realm related to this token.
Returns a String, but may be nil, with the Signature
(oauth_signature) related to this token.
Returns a String with the Signature
Method (oauth_signature_method) related to this token.
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.
Returns a String with the Token (oauth_token).
Returns a String, but may be nil, with the Token Secret related to this token.
Public Class Methods
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.
# 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
Public Instance Methods
Public: Compare this to other based on attributes.
other - The AccessToken
to compare this to.
Return true if equal; false otherwise
# 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
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.
# 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
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
# File lib/cerner/oauth1a/access_token.rb, line 343 def eql?(other) self == other end
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
# 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
Public: Generates a Hash of the attributes.
Returns a Hash with keys for each attribute.
# 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
Private Instance Methods
Internal: Used by authenticate
to load the keys
# File lib/cerner/oauth1a/access_token.rb, line 381 def load_keys(access_token_agent, keys_version) unless keys_version raise OAuthError.new('token missing KeysVersion', nil, 'parameter_rejected', 'oauth_token', @realm) end begin access_token_agent.retrieve_keys(keys_version) rescue OAuthError raise OAuthError.new( 'token references invalid keys version', nil, 'parameter_rejected', 'oauth_token', @realm ) end end
Internal: Used by authenticate
to verify the expiration time.
# File lib/cerner/oauth1a/access_token.rb, line 369 def verify_expiration(expires_on) unless expires_on raise OAuthError.new('token missing ExpiresOn', nil, 'parameter_rejected', 'oauth_token', @realm) end expires_on = Internal.convert_to_time(time: expires_on, name: 'expires_on') now = Internal.convert_to_time(time: Time.now) raise OAuthError.new('token has expired', nil, 'token_expired', nil, @realm) if now.tv_sec >= expires_on.tv_sec end
Internal: Used by authenticate
to verify the request signature.
# File lib/cerner/oauth1a/access_token.rb, line 407 def verify_signature(keys:, hmac_secrets:, http_method:, fully_qualified_url:, request_params:) unless @signature raise OAuthError.new('missing signature', nil, 'parameter_absent', 'oauth_signature', @realm) end unless hmac_secrets raise OAuthError.new('missing HMACSecrets', nil, 'parameter_rejected', 'oauth_token', @realm) end begin secrets = keys.decrypt_hmac_secrets(hmac_secrets) rescue ArgumentError, OpenSSL::PKey::RSAError => e raise OAuthError.new( "unable to decrypt HMACSecrets: #{e.message}", nil, 'parameter_rejected', 'oauth_token', @realm ) end secrets_parts = Protocol.parse_url_query_string(secrets) if @signature_method == 'PLAINTEXT' expected_signature = Signature.sign_via_plaintext( client_shared_secret: secrets_parts[:ConsumerSecret], token_shared_secret: secrets_parts[:TokenSecret] ) elsif @signature_method == 'HMAC-SHA1' http_method ||= 'GET' # default to HTTP GET request_params ||= {} # default to no request params oauth_params = { oauth_version: '1.0', # assumes version is present oauth_signature_method: 'HMAC-SHA1', oauth_consumer_key: @consumer_key, oauth_nonce: @nonce, oauth_timestamp: @timestamp.to_i, oauth_token: @token } 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 ) expected_signature = Signature.sign_via_hmacsha1( client_shared_secret: secrets_parts[:ConsumerSecret], token_shared_secret: secrets_parts[:TokenSecret], signature_base_string: signature_base_string ) else raise OAuthError.new( 'signature_method must be PLAINTEXT or HMAC-SHA1', nil, 'signature_method_rejected', nil, @realm ) end return if Internal.constant_time_compare(@signature, expected_signature) raise OAuthError.new('signature is not valid', nil, 'signature_invalid', nil, @realm) end
Internal: Used by authenticate
to verify the oauth_token value.
# File lib/cerner/oauth1a/access_token.rb, line 400 def verify_token(keys) return if keys.verify_rsasha1_signature(@token) raise OAuthError.new('token is not authentic', nil, 'parameter_rejected', 'oauth_token', @realm) end