Class: Ey::Hmac::Adapter Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/ey-hmac/adapter.rb

Overview

This class is abstract.

override methods #method, #path, #body, #content_type and #content_digest

This class is responsible for forming the canonical string to used to sign requests

Direct Known Subclasses

Faraday, Rack

Defined Under Namespace

Classes: Faraday, Rack

Constant Summary collapse

AUTHORIZATION_REGEXP =
/\w+ ([^:]+):(.+)$/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(request, options = {}) ⇒ Adapter

Returns a new instance of Adapter.

Parameters:

  • request (Object)

    signer-specific request implementation

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :version (Integer)

    signature version

  • :authorization_header (String) — default: 'Authorization'

    Authorization header key.

  • :server (String) — default: 'EyHmac'

    service name prefixed to #authorization. set to #service



15
16
17
18
19
20
# File 'lib/ey-hmac/adapter.rb', line 15

def initialize(request, options={})
  @request, @options = request, options

  @authorization_header = options[:authorization_header] || 'Authorization'
  @service = options[:service] || 'EyHmac'
end

Instance Attribute Details

#authorization_headerObject (readonly)

Returns the value of attribute authorization_header.



9
10
11
# File 'lib/ey-hmac/adapter.rb', line 9

def authorization_header
  @authorization_header
end

#optionsObject (readonly)

Returns the value of attribute options.



9
10
11
# File 'lib/ey-hmac/adapter.rb', line 9

def options
  @options
end

#requestObject (readonly)

Returns the value of attribute request.



9
10
11
# File 'lib/ey-hmac/adapter.rb', line 9

def request
  @request
end

#serviceObject (readonly)

Returns the value of attribute service.



9
10
11
# File 'lib/ey-hmac/adapter.rb', line 9

def service
  @service
end

Instance Method Details

#authenticated!(&block) ⇒ Object Also known as: authenticate!

See Also:

  • Ey::Hmac#authenticate!


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/ey-hmac/adapter.rb', line 109

def authenticated!(&block)
  if authorization_match = AUTHORIZATION_REGEXP.match(authorization_signature)
    key_id          = authorization_match[1]
    signature_value = authorization_match[2]

    if key_secret = block.call(key_id)
      calculated_signature = signature(key_secret)
      if secure_compare(signature_value, calculated_signature)
      else raise(Ey::Hmac::SignatureMismatch, "Calculated siganature #{signature_value} does not match #{calculated_signature} using #{canonicalize.inspect}")
      end
    else raise(Ey::Hmac::MissingSecret, "Failed to find secret matching #{key_id.inspect}")
    end
  else
    raise(Ey::Hmac::MissingAuthorization, "Failed to parse authorization_signature #{authorization_signature}")
  end
  true
end

#authenticated? {|key_id| ... } ⇒ Boolean

Check #authorization_signature against calculated #signature

Yield Parameters:

  • key_id (String)

    public HMAC key

Returns:

  • (Boolean)

    true if block yields matching private key and signature matches, else false

See Also:



102
103
104
105
106
# File 'lib/ey-hmac/adapter.rb', line 102

def authenticated?(&block)
  authenticated!(&block)
rescue Ey::Hmac::Error
  false
end

#authorization(key_id, key_secret) ⇒ String

Returns HMAC header value of #request.

Parameters:

  • key_id (String)

    public HMAC key

  • key_secret (String)

    private HMAC key

Returns:

  • (String)

    HMAC header value of #request



40
41
42
# File 'lib/ey-hmac/adapter.rb', line 40

def authorization(key_id, key_secret)
  "#{service} #{key_id}:#{signature(key_secret)}"
end

#authorization_signatureString

This method is abstract.

used when verifying a signed request

Returns value of the #authorization_header.

Returns:

Raises:

  • (NotImplementedError)


86
87
88
# File 'lib/ey-hmac/adapter.rb', line 86

def authorization_signature
  raise NotImplementedError
end

#bodyString, NilClass

This method is abstract.

Returns:

  • (String)

    request body.

  • (NilClass)

    if there is no body or the body is empty

Raises:

  • (NotImplementedError)


67
68
69
# File 'lib/ey-hmac/adapter.rb', line 67

def body
  raise NotImplementedError
end

#canonicalizeString

In order for the server to correctly authorize the request, the client and server MUST AGREE on this format

default canonical string formation is ‘#method\n#content_type\n#content_digest\n#date\n#path

Returns:

  • (String)

    canonical string used to form the #signature



26
27
28
# File 'lib/ey-hmac/adapter.rb', line 26

def canonicalize
  [method, content_type, content_digest, date, path].join("\n")
end

#content_digestString

This method is abstract.
TODO:

support explicit digest methods

Digest of body. Default is MD5.

Returns:

  • (String)

    digest of body

Raises:

  • (NotImplementedError)


60
61
62
# File 'lib/ey-hmac/adapter.rb', line 60

def content_digest
  raise NotImplementedError
end

#content_typeString

This method is abstract.

Returns value of the Content-Type header in #request.

Returns:

  • (String)

    value of the Content-Type header in #request

Raises:

  • (NotImplementedError)


73
74
75
# File 'lib/ey-hmac/adapter.rb', line 73

def content_type
  raise NotImplementedError
end

#dateString

This method is abstract.

Returns value of the Date header in #request.

Returns:

  • (String)

    value of the Date header in #request.

Raises:

  • (NotImplementedError)

See Also:

  • Time#http_date


80
81
82
# File 'lib/ey-hmac/adapter.rb', line 80

def date
  raise NotImplementedError
end

#methodString

This method is abstract.

Returns upcased request verb. i.e. ‘GET’.

Returns:

  • (String)

    upcased request verb. i.e. ‘GET’

Raises:

  • (NotImplementedError)


46
47
48
# File 'lib/ey-hmac/adapter.rb', line 46

def method
  raise NotImplementedError
end

#pathString

This method is abstract.

Returns request path. i.e. ‘/blogs/1’.

Returns:

  • (String)

    request path. i.e. ‘/blogs/1’

Raises:

  • (NotImplementedError)


52
53
54
# File 'lib/ey-hmac/adapter.rb', line 52

def path
  raise NotImplementedError
end

#secure_compare(a, b) ⇒ Object

Constant time string comparison. pulled from github.com/rack/rack/blob/master/lib/rack/utils.rb#L399



130
131
132
133
134
135
136
137
138
# File 'lib/ey-hmac/adapter.rb', line 130

def secure_compare(a, b)
  return false unless a.bytesize == b.bytesize

  l = a.unpack("C*")

  r, i = 0, -1
  b.each_byte { |v| r |= v ^ l[i+=1] }
  r == 0
end

#sign!(key_id, key_secret) ⇒ String

This method is abstract.

Add #signature header to request. Typically this is ‘Authorization’ or ‘WWW-Authorization’

Returns:

Raises:

  • (NotImplementedError)

See Also:

  • Ey::Hmac#sign!


94
95
96
# File 'lib/ey-hmac/adapter.rb', line 94

def sign!(key_id, key_secret)
  raise NotImplementedError
end

#signature(key_secret) ⇒ String

TODO:

handle multiple hash functions

Returns HMAC signature of #request.

Parameters:

  • key_secret (String)

    private HMAC key

Returns:

  • (String)

    HMAC signature of #request



33
34
35
# File 'lib/ey-hmac/adapter.rb', line 33

def signature(key_secret)
  Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), key_secret, canonicalize)).strip
end