#------------------------------------------------------------------------- # # Copyright (c) Microsoft and contributors. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #-------------------------------------------------------------------------- require 'cgi' require 'azure/core/auth/signer' module Azure module Core module Auth class SharedKey < Signer # The Azure account's name. attr :account_name # Initialize the Signer. # # @param account_name [String] The account name. Defaults to the one in the # global configuration. # @param access_key [String] The access_key encoded in Base64. Defaults to the # one in the global configuration. def initialize(account_name=Azure.storage_account_name, access_key=Azure.storage_access_key) @account_name = account_name super(access_key) end # The name of the strategy. # # @return [String] def name 'SharedKey' end # Create the signature for the request parameters # # @param method [Symbol] HTTP request method. # @param uri [URI] URI of the request we're signing. # @param headers [Hash] HTTP request headers. # # @return [String] base64 encoded signature def sign(method, uri, headers) "#{account_name}:#{super(signable_string(method, uri, headers))}" end # Sign the request # # @param req [Azure::Core::Http::HttpRequest] HTTP request to sign # # @return [Azure::Core::Http::HttpRequest] def sign_request(req) # Need to make sure Content-Length is correctly set. if ((!req.body.nil?)) then if (req.body.respond_to? :bytesize) then req.headers['Content-Length'] = req.body.bytesize.to_s elsif (req.body.respond_to? :size) req.headers['Content-Length'] = req.body.size.to_s end end req.headers['Authorization'] = "#{name} #{sign(req.method, req.uri, req.headers)}" req end # Generate the string to sign. # # @param method [Symbol] HTTP request method. # @param uri [URI] URI of the request we're signing. # @param headers [Hash] HTTP request headers. # # @return [String] def signable_string(method, uri, headers) [ method.to_s.upcase, headers.fetch('Content-Encoding', ''), headers.fetch('Content-Language', ''), headers.fetch('Content-Length', ''), headers.fetch('Content-MD5', ''), headers.fetch('Content-Type', ''), headers.fetch('Date', ''), headers.fetch('If-Modified-Since', ''), headers.fetch('If-Match', ''), headers.fetch('If-None-Match', ''), headers.fetch('If-Unmodified-Since', ''), headers.fetch('Range', ''), canonicalized_headers(headers), canonicalized_resource(uri) ].join("\n") end # Calculate the Canonicalized Headers string for a request. # # @param headers [Hash] HTTP request headers. # # @return [String] a string with the canonicalized headers. def canonicalized_headers(headers) headers = headers.map { |k,v| [k.to_s.downcase, v] } headers.select! { |k,_| k =~ /^x-ms-/ } headers.sort_by! { |(k,_)| k } headers.map! { |k,v| '%s:%s' % [k, v] }.join("\n") end # Calculate the Canonicalized Resource string for a request. # # @param uri [URI] URI of the request we're signing. # # @return [String] a string with the canonicalized resource. def canonicalized_resource(uri) resource = '/' + account_name + (uri.path.empty? ? '/' : uri.path) params = CGI.parse(uri.query.to_s).map { |k,v| [k.downcase, v] } params.sort_by! { |k,_| k } params.map! { |k,v| '%s:%s' % [k, v.map(&:strip).sort.join(',')] } [resource, *params].join("\n") end end end end end