class OAuthenticator::SignedRequest
this class represents an OAuth signed request. its primary user-facing method is {#errors}, which returns nil if the request is valid and authentic, or a helpful object of error messages describing what was invalid if not.
this class is not useful on its own, as various methods must be implemented on a module to be included before the implementation is complete enough to use. see the README and the documentation for the module {OAuthenticator::ConfigMethods} for details. to pass such a module to {OAuthenticator::SignedRequest}, use {.including_config}, like ‘OAuthenticator::SignedRequest.including_config(config_module)`.
Constants
- ATTRIBUTE_KEYS
attributes of a
SignedRequest
- OAUTH_ATTRIBUTE_KEYS
oauth attributes parsed from the request authorization
Public Class Methods
instantiates a ‘OAuthenticator::SignedRequest` (subclass thereof, more precisely) representing a request given as a Rack::Request.
like {#initialize}, this should be called on a subclass of SignedRequest
created with {.including_config}
@param request [Rack::Request] @return [subclass of OAuthenticator::SignedRequest
]
# File lib/oauthenticator/signed_request.rb, line 63 def from_rack_request(request) new({ :request_method => request.request_method, :uri => request.url, :body => request.body, :media_type => request.media_type, :authorization => request.env['HTTP_AUTHORIZATION'], }) end
returns a subclass of OAuthenticator::SignedRequest
which includes the given config module
@param config_methods_module [Module] a module which implements the methods described in the documentation for {OAuthenticator::ConfigMethods} and the README
@return [Class] subclass of SignedRequest
with the given module included
# File lib/oauthenticator/signed_request.rb, line 26 def including_config(config_methods_module) @extended_classes ||= Hash.new do |h, confmodule| h[confmodule] = Class.new(::OAuthenticator::SignedRequest).send(:include, confmodule) end @extended_classes[config_methods_module] end
initialize a {SignedRequest}. this should not be called on OAuthenticator::SignedRequest
directly, but a subclass made with {.including_config} - see {SignedRequest}‘s documentation.
# File lib/oauthenticator/signed_request.rb, line 76 def initialize(attributes) @attributes = attributes.inject({}){|acc, (k,v)| acc.update((k.is_a?(Symbol) ? k.to_s : k) => v) } extra_attributes = @attributes.keys - ATTRIBUTE_KEYS if extra_attributes.any? raise ArgumentError, "received unrecognized attribute keys: #{extra_attributes.inspect}" end end
Public Instance Methods
inspects the request represented by this instance of SignedRequest
. if the request is authentically signed with OAuth, returns nil to indicate that there are no errors. if the request is inauthentic or invalid for any reason, this returns a hash containing the reason(s) why the request is invalid.
The error object’s structure is a hash with string keys indicating attributes with errors, and values being arrays of strings indicating error messages on the attribute key. this structure takes after structured rails / ActiveResource, and looks like:
{'attribute1': ['messageA', 'messageB'], 'attribute2': ['messageC']}
@return [nil, Hash<String, Array<String>>] either nil or a hash of errors
# File lib/oauthenticator/signed_request.rb, line 95 def errors return @errors if instance_variable_defined?('@errors') @errors = catch(:errors) do if authorization.nil? throw(:errors, {'Authorization' => ["Authorization header is missing"]}) elsif authorization !~ /\S/ throw(:errors, {'Authorization' => ["Authorization header is blank"]}) end begin oauth_header_params rescue OAuthenticator::Error => parse_exception throw(:errors, parse_exception.errors) end errors = Hash.new { |h,k| h[k] = [] } # timestamp if !timestamp? unless signature_method == 'PLAINTEXT' errors['Authorization oauth_timestamp'] << "Authorization oauth_timestamp is missing" end elsif timestamp !~ /\A\s*\d+\s*\z/ errors['Authorization oauth_timestamp'] << "Authorization oauth_timestamp is not an integer - got: #{timestamp}" else timestamp_i = timestamp.to_i if timestamp_i < Time.now.to_i - timestamp_valid_past errors['Authorization oauth_timestamp'] << "Authorization oauth_timestamp is too old: #{timestamp}" elsif timestamp_i > Time.now.to_i + timestamp_valid_future errors['Authorization oauth_timestamp'] << "Authorization oauth_timestamp is too far in the future: #{timestamp}" end end # oauth version if version? && version != '1.0' errors['Authorization oauth_version'] << "Authorization oauth_version must be 1.0; got: #{version}" end # she's filled with secrets secrets = {} # consumer / client application if !consumer_key? errors['Authorization oauth_consumer_key'] << "Authorization oauth_consumer_key is missing" else secrets[:consumer_secret] = consumer_secret if !secrets[:consumer_secret] errors['Authorization oauth_consumer_key'] << 'Authorization oauth_consumer_key is invalid' end end # token if token? secrets[:token_secret] = token_secret if !secrets[:token_secret] errors['Authorization oauth_token'] << 'Authorization oauth_token is invalid' elsif !token_belongs_to_consumer? errors['Authorization oauth_token'] << 'Authorization oauth_token does not belong to the specified consumer' end end # nonce if !nonce? unless signature_method == 'PLAINTEXT' errors['Authorization oauth_nonce'] << "Authorization oauth_nonce is missing" end elsif nonce_used? errors['Authorization oauth_nonce'] << "Authorization oauth_nonce has already been used" end # signature method if !signature_method? errors['Authorization oauth_signature_method'] << "Authorization oauth_signature_method is missing" elsif !allowed_signature_methods.any? { |sm| signature_method.downcase == sm.downcase } errors['Authorization oauth_signature_method'] << "Authorization oauth_signature_method must be one of " + "#{allowed_signature_methods.join(', ')}; got: #{signature_method}" end # signature if !signature? errors['Authorization oauth_signature'] << "Authorization oauth_signature is missing" end signable_request = SignableRequest.new(@attributes.merge(secrets).merge('authorization' => oauth_header_params)) # body hash # present? if body_hash? # allowed? if !signable_request.form_encoded? # applicable? if SignableRequest::BODY_HASH_METHODS.key?(signature_method) # correct? if body_hash == signable_request.body_hash # all good else errors['Authorization oauth_body_hash'] << "Authorization oauth_body_hash is invalid" end else # received a body hash with plaintext. weird situation - we will ignore it; signature will not # be verified but it will be a part of the signature. end else errors['Authorization oauth_body_hash'] << "Authorization oauth_body_hash must not be included with form-encoded requests" end else # allowed? if !signable_request.form_encoded? # required? if body_hash_required? errors['Authorization oauth_body_hash'] << "Authorization oauth_body_hash is required (on non-form-encoded requests)" else # okay - not supported by client, but allowed end else # all good end end throw(:errors, errors) if errors.any? # proceed to check signature unless self.signature == signable_request.signature throw(:errors, {'Authorization oauth_signature' => ['Authorization oauth_signature is invalid']}) end if nonce? begin use_nonce! rescue NonceUsedError throw(:errors, {'Authorization oauth_nonce' => ['Authorization oauth_nonce has already been used']}) end end nil end end
hash of header params. keys should be a subset of OAUTH_ATTRIBUTE_KEYS
.
# File lib/oauthenticator/signed_request.rb, line 238 def oauth_header_params @oauth_header_params ||= OAuthenticator.parse_authorization(authorization) end
Private Instance Methods
raise a nice error message for a method that needs to be implemented on a module of config methods
# File lib/oauthenticator/signed_request.rb, line 245 def config_method_not_implemented caller_name = caller[0].match(%r(in `(.*?)'))[1] using_middleware = caller.any? { |l| l =~ %r(oauthenticator/rack_authenticator.rb:.*`call') } message = "method \##{caller_name} must be implemented on a module of oauth config methods, which is " + begin if using_middleware "passed to OAuthenticator::RackAuthenticator using the option :config_methods." else "included in a subclass of OAuthenticator::SignedRequest, typically by passing it to OAuthenticator::SignedRequest.including_config(your_module)." end end + " Please consult the documentation." raise NotImplementedError, message end