Class: OAuthenticator::SignedRequest

Inherits:
Object
  • Object
show all
Includes:
ConfigMethods
Defined in:
lib/oauthenticator/signed_request.rb

Overview

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 ConfigMethods for details. to pass such a module to SignedRequest, use SignedRequest.including_config, like OAuthenticator::SignedRequest.including_config(config_module).

Constant Summary collapse

ATTRIBUTE_KEYS =
%w(request_method url body media_type authorization).map(&:freeze).freeze
OAUTH_ATTRIBUTE_KEYS =
%w(consumer_key token timestamp nonce version signature_method signature).map(&:to_sym).freeze
VALID_SIGNATURE_METHODS =
%w(HMAC-SHA1 RSA-SHA1 PLAINTEXT).map(&:freeze).freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ConfigMethods

#access_token_belongs_to_consumer?, #access_token_secret, #allowed_signature_methods, #consumer_secret, #nonce_used?, #timestamp_valid_future, #timestamp_valid_past, #timestamp_valid_period, #use_nonce!

Constructor Details

#initialize(attributes) ⇒ SignedRequest

initialize a OAuthenticator::SignedRequest. this should not be called on OAuthenticator::SignedRequest directly, but a subclass made with including_config - see OAuthenticator::SignedRequest's documentation.



64
65
66
67
68
69
70
# File 'lib/oauthenticator/signed_request.rb', line 64

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

Class Method Details

.from_rack_request(request) ⇒ subclass of OAuthenticator::SignedRequest

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

Parameters:

  • request (Rack::Request)

Returns:



51
52
53
54
55
56
57
58
59
# File 'lib/oauthenticator/signed_request.rb', line 51

def from_rack_request(request)
  new({
    :request_method => request.request_method,
    :url => request.url,
    :body => request.body,
    :media_type => request.media_type,
    :authorization => request.env['HTTP_AUTHORIZATION'],
  })
end

.including_config(config_methods_module) ⇒ Class

returns a subclass of OAuthenticator::SignedRequest which includes the given config module

documentation for ConfigMethods and the README

Parameters:

  • config_methods_module (Module)

    a module which implements the methods described in the

Returns:

  • (Class)

    subclass of SignedRequest with the given module included



21
22
23
24
25
26
# File 'lib/oauthenticator/signed_request.rb', line 21

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

Instance Method Details

#errorsnil, Hash<String, Array<String>>

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']}

Returns:

  • (nil, Hash<String, Array<String>>)

    either nil or a hash of errors



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/oauthenticator/signed_request.rb', line 83

def errors
  @errors ||= begin
    if authorization.nil?
      {'Authorization' => ["Authorization header is missing"]}
    elsif authorization !~ /\S/
      {'Authorization' => ["Authorization header is blank"]}
    elsif authorization !~ /\Aoauth\s/i
      {'Authorization' => ["Authorization scheme is not OAuth; received Authorization: #{authorization}"]}
    else
      to_rescue = SimpleOAuth.const_defined?(:ParseError) ? SimpleOAuth::ParseError : StandardError
      begin
        oauth_header_params
      rescue to_rescue
        parse_exception = $!
      end
      if parse_exception
        if parse_exception.class.name == 'SimpleOAuth::ParseError'
          message = parse_exception.message
        else
          message = "Authorization header is not a properly-formed OAuth 1.0 header."
        end
        {'Authorization' => [message]}
      else
        errors = Hash.new { |h,k| h[k] = [] }

        # timestamp
        if !timestamp?
          errors['Authorization oauth_timestamp'] << "is missing"
        elsif timestamp !~ /\A\s*\d+\s*\z/
          errors['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'] << "is too old: #{timestamp}"
          elsif timestamp_i > Time.now.to_i + timestamp_valid_future
            errors['Authorization oauth_timestamp'] << "is too far in the future: #{timestamp}"
          end
        end

        # oauth version
        if version? && version != '1.0'
          errors['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'] << "is missing"
        else
          secrets[:consumer_secret] = consumer_secret
          if !secrets[:consumer_secret]
            errors['Authorization oauth_consumer_key'] << 'is invalid'
          end
        end

        # access token
        if token?
          secrets[:token_secret] = access_token_secret
          if !secrets[:token_secret]
            errors['Authorization oauth_token'] << 'is invalid'
          elsif !access_token_belongs_to_consumer?
            errors['Authorization oauth_token'] << 'does not belong to the specified consumer'
          end
        end

        # nonce
        if !nonce?
          errors['Authorization oauth_nonce'] << "is missing"
        elsif nonce_used?
          errors['Authorization oauth_nonce'] << "has already been used"
        end

        # signature method
        if !signature_method?
          errors['Authorization oauth_signature_method'] << "is missing"
        elsif !allowed_signature_methods.any? { |sm| signature_method.downcase == sm.downcase }
          errors['Authorization oauth_signature_method'] << "must be one of " +
            "#{allowed_signature_methods.join(', ')}; got: #{signature_method}"
        end

        # signature
        if !signature?
          errors['Authorization oauth_signature'] << "is missing"
        end

        if errors.any?
          errors
        else
          # proceed to check signature
          if !simple_oauth_header.valid?(secrets)
            {'Authorization oauth_signature' => ['is invalid']}
          else
            use_nonce!
            nil
          end
        end
      end
    end
  end
end