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

#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.



53
54
55
56
# File 'lib/waz/storage/core_service.rb', line 53

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.



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

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.account_name}/#{uri_component}"
  return canonicalized_message
end

#canonicalize_message20090919(url) ⇒ Object



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

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.account_name}/#{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.



115
116
117
118
119
# File 'lib/waz/storage/core_service.rb', line 115

def execute(verb, path, query = {}, headers = {}, payload = nil)
  url = generate_request_uri(path, query)
  request = generate_request(verb, url, headers, payload)
  request.execute()
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.



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

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 #{account_name}:#{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.



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

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?(account_name) ? "" : account_name }#{((path or "").start_with?("/") or path.start_with?(account_name)) ? "" : "/"}#{(path or "")}" if !self.use_devenv.nil? and self.use_devenv
  uri ||= "#{protocol}://#{account_name}.#{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.



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

def generate_signature(options = {})
  return generate_signature20090919(options) if options[:headers]["x-ms-version"] == "2009-09-19"

  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



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

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).



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

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") 
end