From ff5e49b5e896ec6c528b5855925e425cfae9d7e2 Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Fri, 24 Jan 2020 13:08:22 -0500 Subject: [PATCH 1/5] Drop end-of-life Ruby versions from CI --- .travis.yml | 9 ++++----- README.md | 2 +- blob/BreakingChanges.md | 4 ++++ blob/ChangeLog.md | 3 +++ blob/README.md | 4 ++-- blob/azure-storage-blob.gemspec | 2 +- common/BreakingChanges.md | 4 ++++ common/ChangeLog.md | 3 +++ common/README.md | 4 ++-- common/azure-storage-common.gemspec | 2 +- file/BreakingChanges.md | 4 ++++ file/ChangeLog.md | 3 +++ file/README.md | 4 ++-- file/azure-storage-file.gemspec | 2 +- queue/BreakingChanges.md | 4 ++++ queue/ChangeLog.md | 3 +++ queue/README.md | 4 ++-- queue/azure-storage-queue.gemspec | 2 +- table/BreakingChanges.md | 4 ++++ table/ChangeLog.md | 3 +++ table/README.md | 4 ++-- table/azure-storage-table.gemspec | 2 +- 22 files changed, 55 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index c53d3a3e..28227663 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,10 @@ language: ruby rvm: -- 2.0.0 -- 2.1.6 -- 2.2.2 -- 2.4.1 -- 2.5.0 +- 2.4 +- 2.5 +- 2.6 +- 2.7 before_install: - gem install bundler diff --git a/README.md b/README.md index 45942b1e..b36f9a20 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This project provides Ruby packages that makes it easy to access and manage Micr Note: * x64 Ruby for Windows is known to have some compatibility issues. -* Each service gems depends on gem nokogiri. For Ruby version lower than 2.2, please install the compatible nokogiri before trying to install azure-storage. +* Each service gems depends on gem nokogiri. # Getting Started for Contributors diff --git a/blob/BreakingChanges.md b/blob/BreakingChanges.md index f14368ed..6cab0205 100644 --- a/blob/BreakingChanges.md +++ b/blob/BreakingChanges.md @@ -1,3 +1,7 @@ +Tracking Breaking Changes in 1.2.0 + +* This module now supports Ruby versions to 2.4 through 2.7 + Tracking Breaking Changes in 1.0.0 * This module now only consists of functionalities to access Azure Storage Blob Service. diff --git a/blob/ChangeLog.md b/blob/ChangeLog.md index abb8f4f9..b60fb322 100644 --- a/blob/ChangeLog.md +++ b/blob/ChangeLog.md @@ -1,3 +1,6 @@ +2020.2 - version 1.2.0 +* This module now supports Ruby versions to 2.4 through 2.7 + 2018.11 - version 1.1.0 * Added the support for sending a request with a bearer token. diff --git a/blob/README.md b/blob/README.md index 44fc42b8..8b056184 100644 --- a/blob/README.md +++ b/blob/README.md @@ -9,12 +9,12 @@ This project provides a Ruby package that makes it easy to access and manage Mic # Supported Ruby Versions -* Ruby 1.9.3 to 2.5 +* Ruby 2.4 to 2.7 Note: * x64 Ruby for Windows is known to have some compatibility issues. -* azure-storage-blob depends on gem nokogiri. For Ruby version lower than 2.2, please install the compatible nokogiri before trying to install azure-storage-blob. +* azure-storage-blob depends on gem nokogiri. # Getting Started diff --git a/blob/azure-storage-blob.gemspec b/blob/azure-storage-blob.gemspec index 528c72ec..233a5dca 100644 --- a/blob/azure-storage-blob.gemspec +++ b/blob/azure-storage-blob.gemspec @@ -38,7 +38,7 @@ Gem::Specification.new do |s| s.license = "MIT" s.files = `git ls-files ./lib/azure/storage/blob/`.split("\n") << "./lib/azure/storage/blob.rb" - s.required_ruby_version = ">= 1.9.3" + s.required_ruby_version = ">= 2.4.0" s.add_runtime_dependency("azure-core", "~> 0.1.13") s.add_runtime_dependency("azure-storage-common", "~> 1.0") diff --git a/common/BreakingChanges.md b/common/BreakingChanges.md index 98b81484..4b229ea6 100644 --- a/common/BreakingChanges.md +++ b/common/BreakingChanges.md @@ -1,3 +1,7 @@ +Tracking Breaking Changes in 1.2.0 + +* This module now supports Ruby versions to 2.4 through 2.7 + Tracking Breaking Changes in 1.0.0 * This module now consists of functionalities to support service client library modules. diff --git a/common/ChangeLog.md b/common/ChangeLog.md index 5228ca14..f1e119f3 100644 --- a/common/ChangeLog.md +++ b/common/ChangeLog.md @@ -1,3 +1,6 @@ +2020.2 - version 1.2.0 +* This module now supports Ruby versions to 2.4 through 2.7 + 2018.11 - version 1.1.0 * Added the support for sending a request with a bearer token. * Added the configuration for SSL versions. diff --git a/common/README.md b/common/README.md index f8e0fa62..f2e22d2b 100644 --- a/common/README.md +++ b/common/README.md @@ -8,12 +8,12 @@ This project provides a Ruby package that supports service client libraries. # Supported Ruby Versions -* Ruby 1.9.3 to 2.5 +* Ruby 2.4 to 2.7 Note: * x64 Ruby for Windows is known to have some compatibility issues. -* azure-storage-common depends on gem nokogiri. For Ruby version lower than 2.2, please install the compatible nokogiri before trying to install azure-storage. +* azure-storage-common depends on gem nokogiri. # Getting Started diff --git a/common/azure-storage-common.gemspec b/common/azure-storage-common.gemspec index c1c8782d..f6e76216 100644 --- a/common/azure-storage-common.gemspec +++ b/common/azure-storage-common.gemspec @@ -38,7 +38,7 @@ Gem::Specification.new do |s| s.license = "MIT" s.files = `git ls-files ./lib/azure/storage/common/`.split("\n") << "./lib/azure/storage/common.rb" - s.required_ruby_version = ">= 1.9.3" + s.required_ruby_version = ">= 2.4.0" s.add_runtime_dependency("azure-core", "~> 0.1.13") s.add_runtime_dependency("nokogiri", "~> 1.6", ">= 1.6.8") diff --git a/file/BreakingChanges.md b/file/BreakingChanges.md index 8f62e326..a1f5d177 100644 --- a/file/BreakingChanges.md +++ b/file/BreakingChanges.md @@ -1,3 +1,7 @@ +Tracking Breaking Changes in 1.2.0 + +* This module now supports Ruby versions to 2.4 through 2.7 + Tracking Breaking Changes in 1.0.0 * This module now only consists of functionalities to access Azure Storage File Service. diff --git a/file/ChangeLog.md b/file/ChangeLog.md index f22dbb38..39a67ba9 100644 --- a/file/ChangeLog.md +++ b/file/ChangeLog.md @@ -1,3 +1,6 @@ +2020.2 - version 1.2.0 +* This module now supports Ruby versions to 2.4 through 2.7 + 2018.1 - version 1.0.1 * Resolved an issue where user cannot use Gem package using `gem install`. diff --git a/file/README.md b/file/README.md index 8010beb4..93f39464 100644 --- a/file/README.md +++ b/file/README.md @@ -8,12 +8,12 @@ This project provides a Ruby package that makes it easy to access and manage Mic # Supported Ruby Versions -* Ruby 1.9.3 to 2.5 +* Ruby 2.4 to 2.7 Note: * x64 Ruby for Windows is known to have some compatibility issues. -* azure-storage-file depends on gem nokogiri. For Ruby version lower than 2.2, please install the compatible nokogiri before trying to install azure-storage-file. +* azure-storage-file depends on gem nokogiri. # Getting Started diff --git a/file/azure-storage-file.gemspec b/file/azure-storage-file.gemspec index b066627c..ea5e6e5e 100644 --- a/file/azure-storage-file.gemspec +++ b/file/azure-storage-file.gemspec @@ -38,7 +38,7 @@ Gem::Specification.new do |s| s.license = "MIT" s.files = `git ls-files ./lib/azure/storage/file`.split("\n") << "./lib/azure/storage/file.rb" - s.required_ruby_version = ">= 1.9.3" + s.required_ruby_version = ">= 2.4.0" s.add_runtime_dependency("azure-core", "~> 0.1.13") s.add_runtime_dependency("azure-storage-common", "~> 1.0") diff --git a/queue/BreakingChanges.md b/queue/BreakingChanges.md index b0783a5d..36f33aa7 100644 --- a/queue/BreakingChanges.md +++ b/queue/BreakingChanges.md @@ -1,3 +1,7 @@ +Tracking Breaking Changes in 1.2.0 + +* This module now supports Ruby versions to 2.4 through 2.7 + Tracking Breaking Changes in 1.0.0 * This module now only consists of functionalities to access Azure Storage Queue Service. diff --git a/queue/ChangeLog.md b/queue/ChangeLog.md index 85139f24..9cd86f77 100644 --- a/queue/ChangeLog.md +++ b/queue/ChangeLog.md @@ -1,3 +1,6 @@ +2020.2 - version 1.2.0 +* This module now supports Ruby versions to 2.4 through 2.7 + 2018.11 - version 1.1.0 * Added the support for sending a request with a bearer token. diff --git a/queue/README.md b/queue/README.md index 6e99bb70..cb7fa0bf 100644 --- a/queue/README.md +++ b/queue/README.md @@ -8,12 +8,12 @@ This project provides a Ruby package that makes it easy to access and manage Mic # Supported Ruby Versions -* Ruby 1.9.3 to 2.5 +* Ruby 2.4 to 2.7 Note: * x64 Ruby for Windows is known to have some compatibility issues. -* azure-storage-queue depends on gem nokogiri. For Ruby version lower than 2.2, please install the compatible nokogiri before trying to install azure-storage-queue. +* azure-storage-queue depends on gem nokogiri. # Getting Started diff --git a/queue/azure-storage-queue.gemspec b/queue/azure-storage-queue.gemspec index b176c707..300a0fac 100644 --- a/queue/azure-storage-queue.gemspec +++ b/queue/azure-storage-queue.gemspec @@ -38,7 +38,7 @@ Gem::Specification.new do |s| s.license = "MIT" s.files = `git ls-files ./lib/azure/storage/queue/`.split("\n") << "./lib/azure/storage/queue.rb" - s.required_ruby_version = ">= 1.9.3" + s.required_ruby_version = ">= 2.4.0" s.add_runtime_dependency("azure-core", "~> 0.1.13") s.add_runtime_dependency("azure-storage-common", "~> 1.0") diff --git a/table/BreakingChanges.md b/table/BreakingChanges.md index 850f41f4..6536be84 100644 --- a/table/BreakingChanges.md +++ b/table/BreakingChanges.md @@ -1,3 +1,7 @@ +Tracking Breaking Changes in 1.2.0 + +* This module now supports Ruby versions to 2.4 through 2.7 + Tracking Breaking Changes in 1.0.0 * This module now only consists of functionalities to access Azure Storage Table Service. diff --git a/table/ChangeLog.md b/table/ChangeLog.md index e08005dc..6a7f68bf 100644 --- a/table/ChangeLog.md +++ b/table/ChangeLog.md @@ -1,3 +1,6 @@ +2020.2 - version 1.2.0 +* This module now supports Ruby versions to 2.4 through 2.7 + 2018.1 - version 1.0.1 * Resolved an issue where user cannot use Gem package using `gem install`. diff --git a/table/README.md b/table/README.md index dc91a69b..af7bcf3b 100644 --- a/table/README.md +++ b/table/README.md @@ -8,12 +8,12 @@ This project provides a Ruby package that makes it easy to access and manage Mic # Supported Ruby Versions -* Ruby 1.9.3 to 2.5 +* Ruby 2.4 to 2.7 Note: * x64 Ruby for Windows is known to have some compatibility issues. -* azure-storage-table depends on gem nokogiri. For Ruby version lower than 2.2, please install the compatible nokogiri before trying to install azure-storage-table. +* azure-storage-table depends on gem nokogiri. # Getting Started diff --git a/table/azure-storage-table.gemspec b/table/azure-storage-table.gemspec index af06cbe1..b3e6fe10 100644 --- a/table/azure-storage-table.gemspec +++ b/table/azure-storage-table.gemspec @@ -38,7 +38,7 @@ Gem::Specification.new do |s| s.license = "MIT" s.files = `git ls-files ./lib/azure/storage/table/`.split("\n") << "./lib/azure/storage/table.rb" - s.required_ruby_version = ">= 1.9.3" + s.required_ruby_version = ">= 2.4.0" s.add_runtime_dependency("azure-core", "~> 0.1.13") s.add_runtime_dependency("azure-storage-common", "~> 1.0") From aadf4f0098690a28d3217927d0f045cc9c96f2e4 Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Thu, 23 Jan 2020 18:46:31 -0500 Subject: [PATCH 2/5] Fix minitest import --- test/test_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index 60b70aa6..fa0b3311 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -30,7 +30,7 @@ ENV["AZURE_STORAGE_CONNECTION_STRING"] = "DefaultEndpointsProtocol=https;AccountName=mockaccount;AccountKey=bW9ja2tleQ==" unless ENV["AZURE_STORAGE_CONNECTION_STRING"] require "minitest/autorun" -require "mocha/mini_test" +require "mocha/minitest" require "minitest/reporters" Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new require "timecop" From 65850bbfa62331eaf2a6d926d919ef147c7e4cca Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Thu, 23 Jan 2020 14:38:11 -0500 Subject: [PATCH 3/5] Implement fetching of user delegation key --- blob/ChangeLog.md | 1 + blob/lib/azure/storage/blob/blob_service.rb | 41 +++++++++++++++ blob/lib/azure/storage/blob/default.rb | 3 ++ blob/lib/azure/storage/blob/serialization.rb | 25 ++++++++++ common/lib/azure/storage/common/autoload.rb | 1 + .../common/service/user_delegation_key.rb | 50 +++++++++++++++++++ test/unit/blob/blob_service_test.rb | 39 +++++++++++++++ 7 files changed, 160 insertions(+) create mode 100644 common/lib/azure/storage/common/service/user_delegation_key.rb diff --git a/blob/ChangeLog.md b/blob/ChangeLog.md index b60fb322..c03ac975 100644 --- a/blob/ChangeLog.md +++ b/blob/ChangeLog.md @@ -1,5 +1,6 @@ 2020.2 - version 1.2.0 * This module now supports Ruby versions to 2.4 through 2.7 +* Add support for generating user delegation shared access signatures. 2018.11 - version 1.1.0 * Added the support for sending a request with a bearer token. diff --git a/blob/lib/azure/storage/blob/blob_service.rb b/blob/lib/azure/storage/blob/blob_service.rb index 097f4eb3..c9f13966 100755 --- a/blob/lib/azure/storage/blob/blob_service.rb +++ b/blob/lib/azure/storage/blob/blob_service.rb @@ -254,6 +254,33 @@ def list_containers(options = {}) Serialization.container_enumeration_results_from_xml(response.body) end + # Public: Obtain a user delegation key for the purpose of signing SAS tokens. + # + # ==== Attributes + # + # * +start+ - Time. The start time for the user delegation SAS. + # * +expiry+ - Time. The expiry time of user delegation SAS. + # + # See: https://docs.microsoft.com/en-us/rest/api/storageservices/get-user-delegation-key + # + # NOTE: A token credential must be present on the service object for this request to succeed. + # The start and expiry times must be within 7 days of the current time. + # + # Returns an Azure::Storage::Common::UserDelegationKey + # + def get_user_delegation_key(start, expiry) + max_delegation_time = Time.now + BlobConstants::MAX_USER_DELEGATION_KEY_SECONDS + raise ArgumentError, "Start time must be before #{max_delegation_time}" if start > max_delegation_time + raise ArgumentError, "Expiry time must be before #{max_delegation_time}" if expiry > max_delegation_time + raise ArgumentError, "Start time must be before expiry time" if start >= expiry + + body = Serialization.key_info_to_xml(start, expiry) + + response = call(:post, user_delegation_key_uri, body) + + Serialization.user_delegation_key_from_xml(response.body) + end + # Protected: Establishes an exclusive write lock on a container or a blob. The lock duration can be 15 to 60 seconds, or can be infinite. # To write to a locked container or blob, a client must provide a lease ID. # @@ -577,6 +604,20 @@ def containers_uri(query = {}, options = {}) generate_uri("", query, options) end + # Protected: Generate the URI for the user delegation key. + # + # ==== Attributes + # + # * +query+ - A Hash of key => value query parameters. + # + # Returns a URI. + # + protected + def user_delegation_key_uri(query = {}, options = {}) + query = { :restype => "service", :comp => "userdelegationkey" }.merge(query) + generate_uri("", query, options) + end + # Protected: Generate the URI for a specific container. # # ==== Attributes diff --git a/blob/lib/azure/storage/blob/default.rb b/blob/lib/azure/storage/blob/default.rb index 3fa20ee6..726f4eee 100644 --- a/blob/lib/azure/storage/blob/default.rb +++ b/blob/lib/azure/storage/blob/default.rb @@ -119,6 +119,9 @@ module BlobConstants # The size of a page, in bytes, in a page blob. PAGE_SIZE = 512 + # The maximum validity of user delegation SAS (7 days from the current time). + MAX_USER_DELEGATION_KEY_SECONDS = 60 * 60 * 24 * 7 + # Resource types. module ResourceTypes CONTAINER = "c" diff --git a/blob/lib/azure/storage/blob/serialization.rb b/blob/lib/azure/storage/blob/serialization.rb index fe8ac6a8..04d5a04b 100644 --- a/blob/lib/azure/storage/blob/serialization.rb +++ b/blob/lib/azure/storage/blob/serialization.rb @@ -49,6 +49,31 @@ def self.container_enumeration_results_from_xml(xml) results end + def self.key_info_to_xml(start, expiry) + builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml| + xml.KeyInfo { + xml.Start start.utc.iso8601 + xml.Expiry expiry.utc.iso8601 + } + end + builder.to_xml + end + + def self.user_delegation_key_from_xml(xml) + xml = slopify(xml) + expect_node("UserDelegationKey", xml) + + Azure::Storage::Common::Service::UserDelegationKey.new do |user_delegation_key| + user_delegation_key.signed_oid = xml.SignedOid.text if (xml > "SignedOid").any? + user_delegation_key.signed_tid = xml.SignedTid.text if (xml > "SignedTid").any? + user_delegation_key.signed_start = xml.SignedStart.text if (xml > "SignedStart").any? + user_delegation_key.signed_expiry = xml.SignedExpiry.text if (xml > "SignedExpiry").any? + user_delegation_key.signed_service = xml.SignedService.text if (xml > "SignedService").any? + user_delegation_key.signed_version = xml.SignedVersion.text if (xml > "SignedVersion").any? + user_delegation_key.value = xml.Value.text if (xml > "Value").any? + end + end + def self.container_from_xml(xml) xml = slopify(xml) expect_node("Container", xml) diff --git a/common/lib/azure/storage/common/autoload.rb b/common/lib/azure/storage/common/autoload.rb index a937c16b..57b1db17 100644 --- a/common/lib/azure/storage/common/autoload.rb +++ b/common/lib/azure/storage/common/autoload.rb @@ -55,6 +55,7 @@ module Service autoload :StorageService, "azure/storage/common/service/storage_service" autoload :CorsRule, "azure/storage/common/service/cors_rule" autoload :EnumerationResults, "azure/storage/common/service/enumeration_results" + autoload :UserDelegationKey, "azure/storage/common/service/user_delegation_key" end end end diff --git a/common/lib/azure/storage/common/service/user_delegation_key.rb b/common/lib/azure/storage/common/service/user_delegation_key.rb new file mode 100644 index 00000000..6d3771c1 --- /dev/null +++ b/common/lib/azure/storage/common/service/user_delegation_key.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +#------------------------------------------------------------------------- +# # Copyright (c) Microsoft and contributors. All rights reserved. +# +# The MIT License(MIT) + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files(the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions : + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +#-------------------------------------------------------------------------- + +module Azure::Storage::Common + module Service + class UserDelegationKey + def initialize + @signed_oid = nil + @signed_tid = nil + @signed_start = nil + @signed_expiry = nil + @signed_service = nil + @signed_version = nil + @value = nil + yield self if block_given? + end + + attr_accessor :signed_oid + attr_accessor :signed_tid + attr_accessor :signed_start + attr_accessor :signed_expiry + attr_accessor :signed_service + attr_accessor :signed_version + attr_accessor :value + end + end +end diff --git a/test/unit/blob/blob_service_test.rb b/test/unit/blob/blob_service_test.rb index d3c39f6a..a9b7c98f 100644 --- a/test/unit/blob/blob_service_test.rb +++ b/test/unit/blob/blob_service_test.rb @@ -47,6 +47,45 @@ subject.stubs(:call).returns(response) } + describe "#get_user_delegation_key" do + let(:response_body) { + ' + + aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa + bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb + 2020-01-24T13:24:33Z + 2020-01-24T14:24:33Z + b + 2018-11-09 + abcdefgh1234567890=?.,:- +' + } + + it "validates that start is before expiry" do + now = Time.now + assert_raises(ArgumentError) { + subject.get_user_delegation_key(now, now - 1) + } + end + + it "validates that start and expiry are within 7 days of current time" do + now = Time.now + days = 60 * 60 * 24 + assert_raises(ArgumentError) { + subject.get_user_delegation_key(now, now + 8 * days) + } + assert_raises(ArgumentError) { + subject.get_user_delegation_key(now + 8 * days, now) + } + end + + it "returns a user delegation key for the account" do + now = Time.now + result = subject.get_user_delegation_key(now, now + 100) + _(result).must_be_kind_of Azure::Storage::Common::Service::UserDelegationKey + end + end + describe "#list_containers" do let(:verb) { :get } let(:options) { { request_location_mode: Azure::Storage::Common::RequestLocationMode::PRIMARY_OR_SECONDARY} } From 0824b9f2c3babecc750cf9cb83cfb0c41d8b77ce Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Thu, 23 Jan 2020 16:13:18 -0500 Subject: [PATCH 4/5] Implement SAS with user delegation key --- Gemfile | 1 + common/ChangeLog.md | 2 + .../auth/shared_access_signature_generator.rb | 55 +++++++++++++- common/lib/azure/storage/common/default.rb | 2 +- ...gation_key_shared_access_signature_test.rb | 76 +++++++++++++++++++ .../core/auth/shared_access_signature_test.rb | 1 + 6 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 test/integration/auth/user_delegation_key_shared_access_signature_test.rb diff --git a/Gemfile b/Gemfile index 0d052dda..5dd04ff7 100644 --- a/Gemfile +++ b/Gemfile @@ -27,6 +27,7 @@ source "https://rubygems.org" do gem "azure-core", "~> 0.1.13", :require => false gem "nokogiri", "~> 1.6", ">= 1.6.8", :require => false + gem "adal", "~> 1.0", :require => false gem "dotenv", "~> 2.0", :require => false gem "minitest", "~> 5", :require => false gem "minitest-reporters", "~> 1", :require => false diff --git a/common/ChangeLog.md b/common/ChangeLog.md index f1e119f3..610b0d1e 100644 --- a/common/ChangeLog.md +++ b/common/ChangeLog.md @@ -1,5 +1,7 @@ 2020.2 - version 1.2.0 * This module now supports Ruby versions to 2.4 through 2.7 +* Add support for generating user delegation shared access signatures. +* Update the storage API version used to generate shared access signatures to 2018-11-09. 2018.11 - version 1.1.0 * Added the support for sending a request with a bearer token. diff --git a/common/lib/azure/storage/common/core/auth/shared_access_signature_generator.rb b/common/lib/azure/storage/common/core/auth/shared_access_signature_generator.rb index 03815747..adbd00ed 100644 --- a/common/lib/azure/storage/common/core/auth/shared_access_signature_generator.rb +++ b/common/lib/azure/storage/common/core/auth/shared_access_signature_generator.rb @@ -67,8 +67,18 @@ class SharedAccessSignature ip_range: :sip } + USER_DELEGATION_KEY_MAPPINGS = { + signed_oid: :skoid, + signed_tid: :sktid, + signed_start: :skt, + signed_expiry: :ske, + signed_service: :sks, + signed_version: :skv + } + BLOB_KEY_MAPPINGS = { resource: :sr, + timestamp: :snapshot, cache_control: :rscc, content_disposition: :rscd, content_encoding: :rsce, @@ -103,13 +113,20 @@ class SharedAccessSignature # # @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 = "", access_key = "") + # @param user_delegation_key [Azure::Storage::Common::UserDelegationKey] The user delegation key obtained from + # calling get_user_delegation_key after authenticating with an Azure Active Directory entity. If present, the + # SAS is signed with the user delegation key instead of the access key. + def initialize(account_name = "", access_key = "", user_delegation_key = nil) + if access_key.empty? && !user_delegation_key.nil? + access_key = user_delegation_key.value + end if account_name.empty? || access_key.empty? client = Azure::Storage::Common::Client.create_from_env account_name = client.storage_account_name if account_name.empty? access_key = client.storage_access_key if access_key.empty? end @account_name = account_name + @user_delegation_key = user_delegation_key @signer = Azure::Core::Auth::Signer.new(access_key) end @@ -131,10 +148,12 @@ def initialize(account_name = "", access_key = "") # * +:start+ - String. Optional. UTC Date/Time in ISO8601 format. # * +:expiry+ - String. Optional. UTC Date/Time in ISO8601 format. Default now + 30 minutes. # * +:identifier+ - String. Optional. Identifier for stored access policy. + # This option must be omitted if a user delegation key has been provided. # * +:protocol+ - String. Optional. Permitted protocols. # * +:ip_range+ - String. Optional. An IP address or a range of IP addresses from which to accept requests. # # Below options for blob serivce only + # * +:snapshot+ - String. Optional. UTC Date/Time in ISO8601 format. The blob snapshot to grant permission. # * +:cache_control+ - String. Optional. Response header override. # * +:content_disposition+ - String. Optional. Response header override. # * +:content_encoding+ - String. Optional. Response header override. @@ -175,12 +194,20 @@ def generate_service_sas_token(path, options = {}) valid_mappings.merge!(FILE_KEY_MAPPINGS) end + service_key_mappings = SERVICE_KEY_MAPPINGS + unless @user_delegation_key.nil? + valid_mappings.delete(:identifier) + USER_DELEGATION_KEY_MAPPINGS.each { |k, _| options[k] = @user_delegation_key.send(k) } + valid_mappings.merge!(USER_DELEGATION_KEY_MAPPINGS) + service_key_mappings = service_key_mappings.merge(USER_DELEGATION_KEY_MAPPINGS) + end + invalid_options = options.reject { |k, _| valid_mappings.key?(k) } raise Azure::Storage::Common::InvalidOptionsError, "invalid options #{invalid_options} provided for SAS token generate" if invalid_options.length > 0 canonicalize_time(options) - query_hash = Hash[options.map { |k, v| [SERVICE_KEY_MAPPINGS[k], v] }] + query_hash = Hash[options.map { |k, v| [service_key_mappings[k], v] }] .reject { |k, v| SERVICE_OPTIONAL_QUERY_PARAMS.include?(k) && v.to_s == "" } .merge(sig: @signer.sign(signable_string_for_service(service_type, path, options))) @@ -197,13 +224,33 @@ def signable_string_for_service(service_type, path, options) options[:permissions], options[:start], options[:expiry], - canonicalized_resource(service_type, path), - options[:identifier], + canonicalized_resource(service_type, path) + ] + + if @user_delegation_key.nil? + signable_fields.push(options[:identifier]) + else + signable_fields.concat [ + @user_delegation_key.signed_oid, + @user_delegation_key.signed_tid, + @user_delegation_key.signed_start, + @user_delegation_key.signed_expiry, + @user_delegation_key.signed_service, + @user_delegation_key.signed_version + ] + end + + signable_fields.concat [ options[:ip_range], options[:protocol], Azure::Storage::Common::Default::STG_VERSION ] + signable_fields.concat [ + options[:resource], + options[:timestamp] + ] if service_type == Azure::Storage::Common::ServiceType::BLOB + signable_fields.concat [ options[:cache_control], options[:content_disposition], diff --git a/common/lib/azure/storage/common/default.rb b/common/lib/azure/storage/common/default.rb index 4e0d3e16..197d3c4c 100644 --- a/common/lib/azure/storage/common/default.rb +++ b/common/lib/azure/storage/common/default.rb @@ -30,7 +30,7 @@ module Azure::Storage::Common module Default # Default REST service (STG) version number. This is used only for SAS generator. - STG_VERSION = "2017-11-09" + STG_VERSION = "2018-11-09" # The number of default concurrent requests for parallel operation. DEFAULT_PARALLEL_OPERATION_THREAD_COUNT = 1 diff --git a/test/integration/auth/user_delegation_key_shared_access_signature_test.rb b/test/integration/auth/user_delegation_key_shared_access_signature_test.rb new file mode 100644 index 00000000..d0fefa14 --- /dev/null +++ b/test/integration/auth/user_delegation_key_shared_access_signature_test.rb @@ -0,0 +1,76 @@ +#------------------------------------------------------------------------- +# # Copyright (c) Microsoft and contributors. All rights reserved. +# +# The MIT License(MIT) + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files(the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions : + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +#-------------------------------------------------------------------------- +require "adal" +require "azure/storage/common" +require "azure/storage/common/core/auth/shared_access_signature" +require "integration/test_helper" +require "net/http" + +describe Azure::Storage::Common::Core::Auth::SharedAccessSignature do + subject { + tenant_id = ENV.fetch("AZURE_TENANT_ID", nil) + client_id = ENV.fetch("AZURE_CLIENT_ID", nil) + client_secret = ENV.fetch("AZURE_CLIENT_SECRET", nil) + storage_account_name = SERVICE_CREATE_OPTIONS()[:storage_account_name] + + if tenant_id.nil? || client_id.nil? || client_secret.nil? + skip "AAD credentials not provided" + end + + auth_ctx = ADAL::AuthenticationContext.new("login.microsoftonline.com", tenant_id) + client_cred = ADAL::ClientCredential.new(client_id, client_secret) + token = auth_ctx.acquire_token_for_client("https://storage.azure.com/", client_cred) + access_token = token.access_token + + token_credential = Azure::Storage::Common::Core::TokenCredential.new access_token + token_signer = Azure::Storage::Common::Core::Auth::TokenSigner.new token_credential + client = Azure::Storage::Common::Client::create(storage_account_name: storage_account_name, signer: token_signer) + Azure::Storage::Blob::BlobService.new(api_version: "2018-11-09", client: client) + } + + let(:generator) { + user_delegation_key = subject.get_user_delegation_key(Time.now - 60 * 5, Time.now + 60 * 15) + storage_account_name = SERVICE_CREATE_OPTIONS()[:storage_account_name] + + Azure::Storage::Common::Core::Auth::SharedAccessSignature.new(storage_account_name, "", user_delegation_key) + } + + describe "#blob_service_sas_for_container" do + let(:container_name) { ContainerNameHelper.name } + let(:block_blob_name) { BlobNameHelper.name } + let(:content) { content = ""; 512.times.each { |i| content << "@" }; content } + + before { + subject.create_container container_name + subject.create_block_blob container_name, block_blob_name, content + } + + after { ContainerNameHelper.clean } + + it "fetches blob from sas uri" do + uri = generator.signed_uri(subject.generate_uri("#{container_name}/#{block_blob_name}"), false, service: "b", permissions: "r", expiry: (Time.now.utc + 60 * 10).iso8601) + _(Net::HTTP.get(uri)).must_equal content + end + end +end diff --git a/test/unit/core/auth/shared_access_signature_test.rb b/test/unit/core/auth/shared_access_signature_test.rb index a4518cb3..7693cec8 100644 --- a/test/unit/core/auth/shared_access_signature_test.rb +++ b/test/unit/core/auth/shared_access_signature_test.rb @@ -66,6 +66,7 @@ subject.signable_string_for_service(service_type, path, service_options).must_equal( "rwd\n#{Time.parse('2020-12-10T00:00:00Z').utc.iso8601}\n#{Time.parse('2020-12-11T00:00:00Z').utc.iso8601}\n" + "/blob/account-name/example/path\n\n168.1.5.60-168.1.5.70\nhttps,http\n#{Azure::Storage::Common::Default::STG_VERSION}\n" + + "b\n\n" + "public\ninline, filename=nyan.cat\ngzip\nEnglish\nbinary" ) end From 3e29adee4ca8aa28c56a5c3de9ed53b4bf747230 Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Tue, 28 Jan 2020 11:07:41 -0500 Subject: [PATCH 5/5] Bump versions --- blob/azure-storage-blob.gemspec | 2 +- blob/lib/azure/storage/blob/version.rb | 2 +- common/lib/azure/storage/common/version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/blob/azure-storage-blob.gemspec b/blob/azure-storage-blob.gemspec index 233a5dca..fba9f607 100644 --- a/blob/azure-storage-blob.gemspec +++ b/blob/azure-storage-blob.gemspec @@ -41,7 +41,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 2.4.0" s.add_runtime_dependency("azure-core", "~> 0.1.13") - s.add_runtime_dependency("azure-storage-common", "~> 1.0") + s.add_runtime_dependency("azure-storage-common", "~> 1.2") s.add_runtime_dependency("nokogiri", "~> 1.6", ">= 1.6.8") s.add_development_dependency("dotenv", "~> 2.0") diff --git a/blob/lib/azure/storage/blob/version.rb b/blob/lib/azure/storage/blob/version.rb index 090399fb..ff0b23c7 100644 --- a/blob/lib/azure/storage/blob/version.rb +++ b/blob/lib/azure/storage/blob/version.rb @@ -30,7 +30,7 @@ module Blob class Version # Fields represent the parts defined in http://semver.org/ MAJOR = 1 unless defined? MAJOR - MINOR = 1 unless defined? MINOR + MINOR = 2 unless defined? MINOR UPDATE = 0 unless defined? UPDATE class << self diff --git a/common/lib/azure/storage/common/version.rb b/common/lib/azure/storage/common/version.rb index 7a00a8a3..8b2a1ed1 100644 --- a/common/lib/azure/storage/common/version.rb +++ b/common/lib/azure/storage/common/version.rb @@ -30,7 +30,7 @@ module Common class Version # Fields represent the parts defined in http://semver.org/ MAJOR = 1 unless defined? MAJOR - MINOR = 1 unless defined? MINOR + MINOR = 2 unless defined? MINOR UPDATE = 0 unless defined? UPDATE class << self