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



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



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


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
# 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
      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