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

  • :sign_with (Symbol) — default: :sha_256

    outgoing signature digest algorithm. See OpenSSL::Digest#new

  • :accepted_digests (Array) — default: [:sha_256]

    accepted incoming signature digest algorithm. See OpenSSL::Digest#new



17
18
19
20
21
22
23
24
# File 'lib/ey-hmac/adapter.rb', line 17

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

  @authorization_header = options[:authorization_header] || 'Authorization'
  @service              = options[:service] || 'EyHmac'
  @sign_with            = options[:sign_with] || :sha256
  @accept_digests       = Array(options[:accept_digests] || :sha256)
end

Instance Attribute Details

#accept_digestsObject (readonly)

Returns the value of attribute accept_digests.



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

def accept_digests
  @accept_digests
end

#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

#sign_withObject (readonly)

Returns the value of attribute sign_with.



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

def sign_with
  @sign_with
end

Instance Method Details

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

See Also:

  • Ey::Hmac#authenticate!


112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/ey-hmac/adapter.rb', line 112

def authenticated!(&block)
  unless authorization_match = AUTHORIZATION_REGEXP.match(authorization_signature)
    raise(Ey::Hmac::MissingAuthorization, "Failed to parse authorization_signature #{authorization_signature}")
  end

  key_id          = authorization_match[1]
  signature_value = authorization_match[2]

  unless key_secret = block.call(key_id)
    raise(Ey::Hmac::MissingSecret, "Failed to find secret matching #{key_id.inspect}")
  end

  calculated_signatures = self.accept_digests.map { |ad| signature(key_secret, ad) }

  unless calculated_signatures.any? { |cs| secure_compare(signature_value, cs) }
    raise(Ey::Hmac::SignatureMismatch, "Calculated signature #{signature_value} does not match #{calculated_signatures.inspect} using #{canonicalize.inspect}")
  end
  true
end

#authenticated?(options = {}) {|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:



105
106
107
108
109
# File 'lib/ey-hmac/adapter.rb', line 105

def authenticated?(options={}, &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



44
45
46
# File 'lib/ey-hmac/adapter.rb', line 44

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

#authorization_signatureString

This method is abstract.

used when verifying a signed request

Returns value of the #authorization_header.

Returns:

Raises:

  • (NotImplementedError)


89
90
91
# File 'lib/ey-hmac/adapter.rb', line 89

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)


70
71
72
# File 'lib/ey-hmac/adapter.rb', line 70

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



30
31
32
# File 'lib/ey-hmac/adapter.rb', line 30

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

#content_digestString

This method is abstract.

Digest of body. Default is MD5.

Returns:

  • (String)

    digest of body

Raises:

  • (NotImplementedError)


63
64
65
# File 'lib/ey-hmac/adapter.rb', line 63

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)


76
77
78
# File 'lib/ey-hmac/adapter.rb', line 76

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


83
84
85
# File 'lib/ey-hmac/adapter.rb', line 83

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)


50
51
52
# File 'lib/ey-hmac/adapter.rb', line 50

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)


56
57
58
# File 'lib/ey-hmac/adapter.rb', line 56

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



135
136
137
138
139
140
141
142
143
# File 'lib/ey-hmac/adapter.rb', line 135

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!


97
98
99
# File 'lib/ey-hmac/adapter.rb', line 97

def sign!(key_id, key_secret)
  raise NotImplementedError
end

#signature(key_secret, digest = self.sign_with) ⇒ String

Returns HMAC signature of #request.

Parameters:

  • key_secret (String)

    private HMAC key

  • signature (String)

    digest hash function. Defaults to #sign_with

Returns:

  • (String)

    HMAC signature of #request



37
38
39
# File 'lib/ey-hmac/adapter.rb', line 37

def signature(key_secret, digest = self.sign_with)
  Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new(digest.to_s), key_secret, canonicalize)).strip
end