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+ ([^:]+):(.+)$/.freeze
DEFAULT_CANONICALIZE_WITH =
%i[method content_type content_digest date path].freeze

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

  • :ttl (Integer) — default: nil

    duration during which HMAC is valid after signed date

  • :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

  • :include_query_string (Symbol) — default: false

    canonicalize with the request query string.

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

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



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/ey-hmac/adapter.rb', line 29

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

  @ttl                  = options[:ttl]
  @authorization_header = options[:authorization_header] || 'Authorization'
  @service              = options[:service] || 'EyHmac'
  @sign_with            = options[:sign_with] || :sha256
  @include_query_string = options.fetch(:include_query_string, false)
  @accept_digests = Array(options[:accept_digests] || :sha256)

  @canonicalize_with = DEFAULT_CANONICALIZE_WITH
  @canonicalize_with += :query_string if include_query_string
end

Instance Attribute Details

#accept_digestsObject (readonly)

Returns the value of attribute accept_digests.



12
13
14
# File 'lib/ey-hmac/adapter.rb', line 12

def accept_digests
  @accept_digests
end

#authorization_headerObject (readonly)

Returns the value of attribute authorization_header.



12
13
14
# File 'lib/ey-hmac/adapter.rb', line 12

def authorization_header
  @authorization_header
end

#canonicalize_withObject (readonly)

Returns the value of attribute canonicalize_with.



12
13
14
# File 'lib/ey-hmac/adapter.rb', line 12

def canonicalize_with
  @canonicalize_with
end

#include_query_stringObject (readonly)

Returns the value of attribute include_query_string.



12
13
14
# File 'lib/ey-hmac/adapter.rb', line 12

def include_query_string
  @include_query_string
end

#optionsObject (readonly)

Returns the value of attribute options.



12
13
14
# File 'lib/ey-hmac/adapter.rb', line 12

def options
  @options
end

#requestObject (readonly)

Returns the value of attribute request.



12
13
14
# File 'lib/ey-hmac/adapter.rb', line 12

def request
  @request
end

#serviceObject (readonly)

Returns the value of attribute service.



12
13
14
# File 'lib/ey-hmac/adapter.rb', line 12

def service
  @service
end

#sign_withObject (readonly)

Returns the value of attribute sign_with.



12
13
14
# File 'lib/ey-hmac/adapter.rb', line 12

def sign_with
  @sign_with
end

Instance Method Details

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

Raises:

See Also:

  • Ey::Hmac#authenticate!


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/ey-hmac/adapter.rb', line 134

def authenticated!(&block)
  key_id, signature_value = check_signature!
  key_secret = block.call(key_id)

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

  check_ttl!

  matching_signature =
    accept_digests
    .lazy
    .map { |ad| signature(key_secret, ad) }
    .any? { |cs| secure_compare(signature_value, cs) }

  raise Ey::Hmac::SignatureMismatch unless matching_signature

  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:



127
128
129
130
131
# File 'lib/ey-hmac/adapter.rb', line 127

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



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

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)


111
112
113
# File 'lib/ey-hmac/adapter.rb', line 111

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)


92
93
94
# File 'lib/ey-hmac/adapter.rb', line 92

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



48
49
50
# File 'lib/ey-hmac/adapter.rb', line 48

def canonicalize
  canonicalize_with.map { |message| public_send(message) }.join("\n")
end

#content_digestString

This method is abstract.

Digest of body. Default is MD5.

Returns:

  • (String)

    digest of body

Raises:

  • (NotImplementedError)


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

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)


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

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


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

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)


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

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)


78
79
80
# File 'lib/ey-hmac/adapter.rb', line 78

def path
  raise NotImplementedError
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!


119
120
121
# File 'lib/ey-hmac/adapter.rb', line 119

def sign!(key_id, key_secret)
  raise NotImplementedError
end

#signature(key_secret, digest = 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



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

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