class OCI::BaseSigner

The base class for other classes which are meant to generate a signature

Constants

BODY_HEADERS
GENERIC_HEADERS
SIGNATURE_VERSION

The Oracle Cloud Infrastructure API signature version

SIGNING_STRATEGY_ENUM

enum to define the signing strategy

Public Class Methods

new( api_key, private_key_content, pass_phrase: nil, signing_strategy: STANDARD, headers_to_sign_in_all_requests: GENERIC_HEADERS, body_headers_to_sign: BODY_HEADERS ) click to toggle source

Creates a BaseSigner

@param [String] api_key The API key needed when making calls. For token-based signing this should be ST$<token> but for calling as a user it will be tenancy/user/fingerprint @param [String] private_key_content The private key as a PEM-formatted string @param [String] pass_phrase Optional the pass phrase for the private key (if any) @param [SIGNING_STRATEGY_ENUM] signing_strategy Optional signing for standard service or object storage service @param [Array<String>] headers_to_sign_in_all_requests Optional headers which should be signed on each request @param [Array<String>] body_headers_to_sign Optional headers which should be signed on requests with bodies

# File lib/oci/base_signer.rb, line 31
def initialize(
  api_key,
  private_key_content,
  pass_phrase: nil,
  signing_strategy: STANDARD,
  headers_to_sign_in_all_requests: GENERIC_HEADERS,
  body_headers_to_sign: BODY_HEADERS
)
  raise 'Missing required parameter api_key.' unless api_key
  raise 'Missing required parameter private_key_content.' unless private_key_content

  @key_id = api_key
  @private_key_content = private_key_content
  @pass_phrase = pass_phrase
  @signing_strategy = signing_strategy

  @headers_to_sign_all_requests = headers_to_sign_in_all_requests
  @body_headers_to_sign = body_headers_to_sign
  @operation_header_mapping = {
    options: [],
    get: headers_to_sign_in_all_requests,
    head: headers_to_sign_in_all_requests,
    delete: headers_to_sign_in_all_requests,
    put: headers_to_sign_in_all_requests + body_headers_to_sign,
    post: headers_to_sign_in_all_requests + body_headers_to_sign,
    patch: headers_to_sign_in_all_requests + body_headers_to_sign
  }
end

Public Instance Methods

sign(method, uri, headers, body, operation_signing_strategy = :standard) click to toggle source

Generates the correct signature and adds it to the headers that are passed in. Also injects any required headers that might be missing.

@param [Symbol] method The HTTP method, such as :get or :post. @param [String] uri The URI, such as 'iaas.us-phoenix-1.oraclecloud.com/20160918/volumeAttachments/' @param [Hash] headers A hash of headers @param [String] body The request body @param [String] operation_signing_strategy the signing strategy for the operation. Default is :standard

# File lib/oci/base_signer.rb, line 69
def sign(method, uri, headers, body, operation_signing_strategy = :standard)
  method = method.to_sym.downcase
  uri = URI(uri)
  path = uri.query.nil? ? uri.path : "#{uri.path}?#{uri.query}"
  inject_missing_headers(method, headers, body, uri, operation_signing_strategy)
  signature = compute_signature(headers, method, path, operation_signing_strategy)
  inject_authorization_header(headers, method, signature, operation_signing_strategy) unless signature.nil?
end

Private Instance Methods

compute_signature(headers, method, path, operation_signing_strategy) click to toggle source
# File lib/oci/base_signer.rb, line 121
def compute_signature(headers, method, path, operation_signing_strategy)
  header_mapping = fetch_header_mapping(method, operation_signing_strategy)
  return if header_mapping.empty?

  signing_string = header_mapping.map do |header|
    if header == :'(request-target)'
      "#{header}: #{method.downcase} #{path}"
    else
      "#{header}: #{headers[header]}"
    end
  end.join("\n")

  signature = private_key.sign(OpenSSL::Digest::SHA256.new, signing_string.encode('ascii'))
  Base64.strict_encode64(signature)
end
fetch_header_mapping(method, operation_signing_strategy) click to toggle source
# File lib/oci/base_signer.rb, line 102
def fetch_header_mapping(method, operation_signing_strategy)
  return @headers_to_sign_all_requests if operation_signing_strategy == :exclude_body

  @operation_header_mapping[method]
end
inject_authorization_header(headers, method, signature, operation_signing_strategy) click to toggle source
# File lib/oci/base_signer.rb, line 108
def inject_authorization_header(headers, method, signature, operation_signing_strategy)
  header_mapping = fetch_header_mapping(method, operation_signing_strategy)

  signed_headers = header_mapping.map(&:to_s).join(' ')
  headers[:authorization] = [
    %(Signature headers="#{signed_headers}"),
    %(keyId="#{@key_id}"),
    %(algorithm="rsa-sha256"),
    %(signature="#{signature}"),
    %(version="#{SIGNATURE_VERSION}")
  ].join(',')
end
inject_missing_headers(method, headers, body, uri, operation_signing_strategy) click to toggle source
# File lib/oci/base_signer.rb, line 80
def inject_missing_headers(method, headers, body, uri, operation_signing_strategy)
  headers[:date] ||= Time.now.utc.httpdate
  headers[:accept] ||= '*/*'
  headers[:host] ||= uri.host if @headers_to_sign_all_requests.include?(:host)

  return unless %i[put post patch].include?(method)

  body ||= ''

  # For object storage service's put method, we don't need to set content length and x-content sha256
  if operation_signing_strategy == :exclude_body
    headers[:'content-length'] ||= if body.respond_to?(:read) && body.respond_to?(:write)
                                     body.respond_to?('size') ? body.size : body.stat.size
                                   else
                                     body.bytes.length.to_s
                                   end
  else
    headers[:'content-length'] ||= body.bytes.length.to_s
    headers[:'x-content-sha256'] ||= OpenSSL::Digest::SHA256.new.update(body).base64digest
  end
end
private_key() click to toggle source
# File lib/oci/base_signer.rb, line 137
def private_key
  # If a pass_phase was not provided and the key is in fact encrypted, then passing in
  # nil for the passphrase here will show a user prompt and block until there is a response.
  # Passing in an empty string will work for some versions of Ruby's openssl wrapper, but
  # other versions will enforce the 4 character password minimum at this point. Passing in
  # a dummy password that's greater than 4 characters avoids both problems, and will
  # always succeed if the file is not encrypted.
  @private_key ||= OpenSSL::PKey::RSA.new(
    @private_key_content,
    @pass_phrase || SecureRandom.uuid
  )
end