Module: WAZ::Storage::SharedKeyCoreService

Included in:
Blobs::Service, Queues::Service, Tables::Service
Defined in:
lib/waz/storage/core_service.rb

Overview

This module is imported by the specific services that use Shared Key authentication profile. On the current implementation this module is imported from WAZ::Queues::Service and WAZ::Blobs::Service.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#access_keyObject

Returns the value of attribute access_key.



6
7
8
# File 'lib/waz/storage/core_service.rb', line 6

def access_key
  @access_key
end

#account_nameObject

Returns the value of attribute account_name.



6
7
8
# File 'lib/waz/storage/core_service.rb', line 6

def 
  @account_name
end

#base_urlObject

Returns the value of attribute base_url.



6
7
8
# File 'lib/waz/storage/core_service.rb', line 6

def base_url
  @base_url
end

#retry_countObject

Returns the value of attribute retry_count.



7
8
9
# File 'lib/waz/storage/core_service.rb', line 7

def retry_count
  @retry_count
end

#sharedaccesssignatureObject

Returns the value of attribute sharedaccesssignature.



6
7
8
# File 'lib/waz/storage/core_service.rb', line 6

def sharedaccesssignature
  @sharedaccesssignature
end

#type_of_serviceObject

Returns the value of attribute type_of_service.



6
7
8
# File 'lib/waz/storage/core_service.rb', line 6

def type_of_service
  @type_of_service
end

#use_devenvObject

Returns the value of attribute use_devenv.



6
7
8
# File 'lib/waz/storage/core_service.rb', line 6

def use_devenv
  @use_devenv
end

#use_sas_auth_onlyObject

Returns the value of attribute use_sas_auth_only.



6
7
8
# File 'lib/waz/storage/core_service.rb', line 6

def use_sas_auth_only
  @use_sas_auth_only
end

#use_sslObject

Returns the value of attribute use_ssl.



6
7
8
# File 'lib/waz/storage/core_service.rb', line 6

def use_ssl
  @use_ssl
end

Instance Method Details

#canonicalize_headers(headers) ⇒ Object

Canonicalizes the request headers by following Microsoft’s specification on how those headers have to be sorted and which of the given headers apply to be canonicalized.



55
56
57
58
# File 'lib/waz/storage/core_service.rb', line 55

def canonicalize_headers(headers)
  cannonicalized_headers = headers.keys.select {|h| h.to_s.start_with? 'x-ms'}.map{ |h| "#{h.downcase.strip}:#{headers[h].strip}" }.sort{ |a, b| a <=> b }.join("\x0A")
  return cannonicalized_headers
end

#canonicalize_message(url) ⇒ Object

Creates a canonical representation of the message by combining account_name/resource_path.



61
62
63
64
65
66
67
# File 'lib/waz/storage/core_service.rb', line 61

def canonicalize_message(url)
  uri_component = url.gsub(/https?:\/\/[^\/]+\//i, '').gsub(/\?.*/i, '')
  comp_component = url.scan(/comp=[^&]+/i).first()
  uri_component << "?#{comp_component}" if comp_component
  canonicalized_message = "/#{self.}/#{uri_component}"
  return canonicalized_message
end

#canonicalize_message20090919(url) ⇒ Object



106
107
108
109
110
111
112
113
# File 'lib/waz/storage/core_service.rb', line 106

def canonicalize_message20090919(url)
  uri_component = url.gsub(/https?:\/\/[^\/]+\//i, '').gsub(/\?.*/i, '')
  query_component = (url.scan(/\?(.*)/i).first() or []).first()
  query_component = query_component.split('&').sort{|a, b| a <=> b}.map{ |p| CGI::unescape(p.split('=').join(':')) }.join("\n") if query_component
  canonicalized_message = "/#{self.}/#{uri_component}"
  canonicalized_message << "\n#{query_component}" if query_component
  return canonicalized_message
end

#execute(verb, path, query = {}, headers = {}, payload = nil) ⇒ Object

Generates a Windows Azure Storage call, it internally calls url generation method and the request generation message.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/waz/storage/core_service.rb', line 117

def execute(verb, path, query = {}, headers = {}, payload = nil)
  escaped_path = path.split("/").map{ | part | CGI.escape(part) }.join("/")
  url = generate_request_uri(escaped_path, query)
  request = generate_request(verb, url, headers, payload)

  errors = 0
  begin
    request.execute()
  rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET => e
    errors += 1
    if errors > self.retry_count
      raise e.class, e.message, e.backtrace
    end
    retry
  end
end

#generate_request(verb, url, headers = {}, payload = nil) ⇒ Object

Generates a request based on Adam Wiggings’ rest-client, including all the required headers for interacting with Windows Azure Storage API (except for Tables). This methods embeds the authorization key signature on the request based on the given access_key.



28
29
30
31
32
33
34
35
36
# File 'lib/waz/storage/core_service.rb', line 28

def generate_request(verb, url, headers = {}, payload = nil)
  http_headers = {}
  headers.each{ |k, v| http_headers[k.to_s.gsub(/_/, '-')] = v} unless headers.nil?
  http_headers.merge!("x-ms-Date" => Time.new.httpdate)
  http_headers.merge!("Content-Length" => (payload or "").size)
  request = {:headers => http_headers, :method => verb.to_s.downcase.to_sym, :url => url, :payload => payload}
  request[:headers].merge!("Authorization" => "SharedKey #{}:#{generate_signature(request)}") unless self.use_sas_auth_only 
  return RestClient::Request.new(request)
end

#generate_request_uri(path = nil, options = {}) ⇒ Object

Generates the request uri based on the resource path, the protocol, the account name and the parameters passed on the options hash.



40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/waz/storage/core_service.rb', line 40

def generate_request_uri(path = nil, options = {})
  protocol = use_ssl ? "https" : "http"
  query_params = options.keys.sort{ |a, b| a.to_s <=> b.to_s}.map{ |k| "#{k.to_s.gsub(/_/, '')}=#{CGI.escape(options[k].to_s)}"}.join("&") unless options.nil? or options.empty?
  uri = "#{protocol}://#{base_url}/#{path.start_with?() ? "" :  }#{((path or "").start_with?("/") or path.start_with?()) ? "" : "/"}#{(path or "")}" if !self.use_devenv.nil? and self.use_devenv
  uri ||= "#{protocol}://#{}.#{base_url}#{(path or "").start_with?("/") ? "" : "/"}#{(path or "")}" 
  if self.use_sas_auth_only 
    uri << "?#{self.sharedaccesssignature.gsub(/\?/,'')}" 
	else
    uri << "?#{query_params}" if query_params
  end        
  return uri
end

#generate_signature(options = {}) ⇒ Object

Generates the signature based on Micosoft specs for the REST API. It includes some special headers, the canonicalized header line and the canonical form of the message, all of the joined by n character. Encoded with Base64 and encrypted with SHA256 using the access_key as the seed.



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/waz/storage/core_service.rb', line 72

def generate_signature(options = {})
  return generate_signature20090919(options) if options[:headers]["x-ms-version"] == "2011-08-18"

  signature = options[:method].to_s.upcase + "\x0A" +
               (options[:headers]["Content-MD5"] or "") + "\x0A" +
               (options[:headers]["Content-Type"] or "") + "\x0A" +
               (options[:headers]["Date"] or "")+ "\x0A"

  signature += canonicalize_headers(options[:headers]) + "\x0A" unless self.type_of_service == 'table'
  signature += canonicalize_message(options[:url])
  signature = signature.toutf8 if(signature.respond_to? :toutf8)
  Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature).digest)
end

#generate_signature20090919(options = {}) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/waz/storage/core_service.rb', line 86

def generate_signature20090919(options = {})
  signature = options[:method].to_s.upcase + "\x0A" +
              (options[:headers]["Content-Encoding"] or "") + "\x0A" +
              (options[:headers]["Content-Language"] or "") + "\x0A" +
              (options[:headers]["Content-Length"] or "").to_s + "\x0A" +                    
              (options[:headers]["Content-MD5"] or "") + "\x0A" +
              (options[:headers]["Content-Type"] or "") + "\x0A" +
              (options[:headers]["Date"] or "")+ "\x0A" +
              (options[:headers]["If-Modified-Since"] or "")+ "\x0A" +
              (options[:headers]["If-Match"] or "")+ "\x0A" +
              (options[:headers]["If-None-Match"] or "")+ "\x0A" +                    
              (options[:headers]["If-Unmodified-Since"] or "")+ "\x0A" +
              (options[:headers]["Range"] or "")+ "\x0A" +                    
              canonicalize_headers(options[:headers]) + "\x0A" +
              canonicalize_message20090919(options[:url])

  signature = signature.toutf8 if(signature.respond_to? :toutf8)
  Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.access_key)).update(signature).digest)
end

#initialize(options = {}) ⇒ Object

Creates an instance of the implementor service (internally used by the API).



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/waz/storage/core_service.rb', line 10

def initialize(options = {})
  # Flag to define the use of shared access signature only
  self.use_sas_auth_only = options[:use_sas_auth_only] or false
  self.sharedaccesssignature = options[:sharedaccesssignature] 

  self. = options[:account_name]
  self.access_key = options[:access_key]
  self.type_of_service = options[:type_of_service]        
  self.use_ssl = options[:use_ssl] or false
  self.use_devenv = !!options[:use_devenv]
  self.base_url = "#{options[:type_of_service] or "blobs"}.#{options[:base_url] or "core.windows.net"}" unless self.use_devenv
  self.base_url ||= (options[:base_url] or "core.windows.net")
  self.retry_count = (options[:retry_count] or 5)
end