Class: Signature::Request

Inherits:
Object
  • Object
show all
Includes:
QueryEncoder
Defined in:
lib/signature/request.rb

Constant Summary collapse

AUTH_HEADER_PREFIX =
"X-API-"
AUTH_HEADER_PREFIX_REGEX =
/X\-API\-(AUTH\-.+)$|X_API_(AUTH_.+)$/
ISO8601 =
"%Y-%m-%dT%H:%M:%SZ"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from QueryEncoder

encode_param, encode_param_without_escaping

Constructor Details

#initialize(method, path, query, headers = nil) ⇒ Request

Returns a new instance of Request.

Raises:

  • (ArgumentError)


16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/signature/request.rb', line 16

def initialize(method, path, query, headers=nil)
  raise ArgumentError, "Expected string" unless path.kind_of?(String)
  raise ArgumentError, "Expected hash" unless query.kind_of?(Hash)

  query_hash = {}

  auth_hash = self.class.parse_headers(headers) if headers
  auth_hash ||= {}

  query.each do |key, v|
    k = key.to_s.downcase
    k[0..4] == 'auth_' ? auth_hash[k] = v : query_hash[k] = v
  end

  @method = method.upcase
  @path, @query_hash, @auth_hash = path, query_hash, auth_hash
  @signed = false
end

Instance Attribute Details

#pathObject

Returns the value of attribute path.



9
10
11
# File 'lib/signature/request.rb', line 9

def path
  @path
end

#query_hashObject

Returns the value of attribute query_hash.



9
10
11
# File 'lib/signature/request.rb', line 9

def query_hash
  @query_hash
end

Class Method Details

.parse_headers(headers = {}) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/signature/request.rb', line 35

def self.parse_headers headers={}
  hh = {}
  headers.each do |k,v|
    if match = k.upcase.match(AUTH_HEADER_PREFIX_REGEX)
      mindex = match[1] if match[1]
      mindex ||= match[2]
      hh[mindex.downcase.gsub('-', '_')] = v if mindex
    end
  end
  hh
end

Instance Method Details

#auth_hashObject

Expose the authentication parameters for a signed request



161
162
163
164
# File 'lib/signature/request.rb', line 161

def auth_hash
  raise "Request not signed" unless @signed
  @auth_hash
end

#authenticate(timestamp_grace = 600) ⇒ Object

Authenticate a request

Takes a block which will be called with the auth_key from the request, and which should return a Signature::Token (or nil if no token can be found for the key)

Raises errors in the same way as authenticate_by_token!

Raises:

  • (ArgumentError)


108
109
110
111
112
113
114
115
116
117
118
# File 'lib/signature/request.rb', line 108

def authenticate(timestamp_grace = 600)
  raise ArgumentError, "Block required" unless block_given?
  key = @auth_hash['auth_key']
  raise AuthenticationError, "Missing parameter: auth_key" unless key
  token = yield key
  unless token
    raise AuthenticationError, "Unknown auth_key"
  end
  authenticate_by_token!(token, timestamp_grace)
  return token
end

#authenticate_async(timestamp_grace = 600) ⇒ Object

Authenticate a request asynchronously

This method is useful it you’re running a server inside eventmachine and need to lookup the token asynchronously.

The block is passed an auth key and a deferrable which should succeed with the token, or fail if the token cannot be found

This method returns a deferrable which succeeds with the valid token, or fails with an AuthenticationError which can be used to pass the error back to the user



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
# File 'lib/signature/request.rb', line 132

def authenticate_async(timestamp_grace = 600)
  raise ArgumentError, "Block required" unless block_given?
  df = EM::DefaultDeferrable.new

  key = @auth_hash['auth_key']

  unless key
    df.fail(AuthenticationError.new("Missing parameter: auth_key"))
    return
  end

  token_df = yield key
  token_df.callback { |token|
    begin
      authenticate_by_token!(token, timestamp_grace)
      df.succeed(token)
    rescue AuthenticationError => e
      df.fail(e)
    end
  }
  token_df.errback {
    df.fail(AuthenticationError.new("Unknown auth_key"))
  }
ensure
  return df
end

#authenticate_by_token(token, timestamp_grace = 600) ⇒ Object

Authenticate the request with a token, but rather than raising an exception if the request is invalid, simply returns false



94
95
96
97
98
# File 'lib/signature/request.rb', line 94

def authenticate_by_token(token, timestamp_grace = 600)
  authenticate_by_token!(token, timestamp_grace)
rescue AuthenticationError
  false
end

#authenticate_by_token!(token, timestamp_grace = 600) ⇒ Object

Authenticates the request with a token

Raises an AuthenticationError if the request is invalid. AuthenticationError exception messages are designed to be exposed to API consumers, and should help them correct errors generating signatures

Timestamp: Unless timestamp_grace is set to nil (which allows this check to be skipped), AuthenticationError will be raised if the timestamp is missing or further than timestamp_grace period away from the real time (defaults to 10 minutes)

Signature: Raises AuthenticationError if the signature does not match the computed HMAC. The error contains a hint for how to sign.



77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/signature/request.rb', line 77

def authenticate_by_token!(token, timestamp_grace = 600)
  # Validate that your code has provided a valid token. This does not
  # raise an AuthenticationError since passing tokens with empty secret is
  # a code error which should be fixed, not reported to the API's consumer
  if token.secret.nil? || token.secret.empty?
    raise AuthenticationError, "Provided token is missing secret"
  end

  validate_version!
  validate_timestamp!(timestamp_grace)
  validate_signature!(token)
  true
end

#sign(token) ⇒ Object

Sign the request with the given token, and return the computed authentication parameters



50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/signature/request.rb', line 50

def sign(token)
  @auth_hash = {
    :auth_version => "1.0",
    :auth_key => token.key,
    :auth_timestamp => Time.now.to_i.to_s
  }
  @auth_hash[:auth_signature] = signature(token)

  @signed = true

  return @auth_hash
end

#signed_paramsObject

Query parameters merged with the computed authentication parameters



168
169
170
# File 'lib/signature/request.rb', line 168

def signed_params
  @query_hash.merge(auth_hash)
end