From 2a4f0a2d7ee4e668481f3e79c606b0a8e87ade81 Mon Sep 17 00:00:00 2001 From: Tianlong Wu Date: Mon, 7 Dec 2015 18:09:46 +0800 Subject: [PATCH 1/6] refactor aliyun/oss to include aliyun/sts --- aliyun-sdk.gemspec | 4 +- examples/aliyun/oss/bucket.rb | 2 +- examples/aliyun/oss/object.rb | 2 +- examples/aliyun/oss/resumable_download.rb | 2 +- examples/aliyun/oss/resumable_upload.rb | 2 +- examples/aliyun/oss/streaming.rb | 2 +- lib/aliyun/common.rb | 4 ++ lib/aliyun/{oss => common}/logging.rb | 6 +-- lib/aliyun/common/struct.rb | 55 +++++++++++++++++++++ lib/aliyun/oss.rb | 3 +- lib/aliyun/oss/bucket.rb | 2 +- lib/aliyun/oss/client.rb | 2 - lib/aliyun/oss/config.rb | 2 +- lib/aliyun/oss/download.rb | 3 ++ lib/aliyun/oss/exception.rb | 1 - lib/aliyun/oss/http.rb | 2 +- lib/aliyun/oss/multipart.rb | 6 +-- lib/aliyun/oss/object.rb | 2 +- lib/aliyun/oss/protocol.rb | 2 +- lib/aliyun/oss/struct.rb | 59 ++--------------------- lib/aliyun/oss/upload.rb | 3 ++ lib/aliyun/oss/util.rb | 2 +- lib/aliyun/oss/version.rb | 9 ---- spec/spec_helper.rb | 3 +- 24 files changed, 91 insertions(+), 89 deletions(-) create mode 100644 lib/aliyun/common.rb rename lib/aliyun/{oss => common}/logging.rb (91%) create mode 100644 lib/aliyun/common/struct.rb delete mode 100644 lib/aliyun/oss/version.rb diff --git a/aliyun-sdk.gemspec b/aliyun-sdk.gemspec index 1d1d98c..1ce9313 100644 --- a/aliyun-sdk.gemspec +++ b/aliyun-sdk.gemspec @@ -2,11 +2,11 @@ lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'aliyun/oss/version' +require 'aliyun/version' Gem::Specification.new do |spec| spec.name = 'aliyun-sdk' - spec.version = Aliyun::OSS::VERSION + spec.version = Aliyun::VERSION spec.authors = ['Tianlong Wu'] spec.email = ['rockuw.@gmail.com'] diff --git a/examples/aliyun/oss/bucket.rb b/examples/aliyun/oss/bucket.rb index 40def79..5d17923 100644 --- a/examples/aliyun/oss/bucket.rb +++ b/examples/aliyun/oss/bucket.rb @@ -5,7 +5,7 @@ require 'aliyun/oss' # 初始化OSS client -Aliyun::OSS::Logging.set_log_level(Logger::DEBUG) +Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.oss.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) client = Aliyun::OSS::Client.new( diff --git a/examples/aliyun/oss/object.rb b/examples/aliyun/oss/object.rb index 57c8923..a299ff3 100644 --- a/examples/aliyun/oss/object.rb +++ b/examples/aliyun/oss/object.rb @@ -5,7 +5,7 @@ require 'aliyun/oss' # 初始化OSS client -Aliyun::OSS::Logging.set_log_level(Logger::DEBUG) +Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.oss.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) bucket = Aliyun::OSS::Client.new( diff --git a/examples/aliyun/oss/resumable_download.rb b/examples/aliyun/oss/resumable_download.rb index 0746218..758a545 100644 --- a/examples/aliyun/oss/resumable_download.rb +++ b/examples/aliyun/oss/resumable_download.rb @@ -5,7 +5,7 @@ require 'aliyun/oss' # 初始化OSS Bucket -Aliyun::OSS::Logging.set_log_level(Logger::DEBUG) +Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.oss.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) bucket = Aliyun::OSS::Client.new( diff --git a/examples/aliyun/oss/resumable_upload.rb b/examples/aliyun/oss/resumable_upload.rb index 92d3707..514ac3c 100644 --- a/examples/aliyun/oss/resumable_upload.rb +++ b/examples/aliyun/oss/resumable_upload.rb @@ -5,7 +5,7 @@ require 'aliyun/oss' # 初始化OSS Bucket -Aliyun::OSS::Logging.set_log_level(Logger::DEBUG) +Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.oss.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) bucket = Aliyun::OSS::Client.new( diff --git a/examples/aliyun/oss/streaming.rb b/examples/aliyun/oss/streaming.rb index 03e1537..a7450ac 100644 --- a/examples/aliyun/oss/streaming.rb +++ b/examples/aliyun/oss/streaming.rb @@ -23,7 +23,7 @@ # 和下载。 # 初始化OSS client -Aliyun::OSS::Logging.set_log_level(Logger::DEBUG) +Aliyun::Common::Logging.set_log_level(Logger::DEBUG) conf_file = '~/.oss.yml' conf = YAML.load(File.read(File.expand_path(conf_file))) bucket = Aliyun::OSS::Client.new( diff --git a/lib/aliyun/common.rb b/lib/aliyun/common.rb new file mode 100644 index 0000000..b80cb35 --- /dev/null +++ b/lib/aliyun/common.rb @@ -0,0 +1,4 @@ +# -*- encoding: utf-8 -*- + +require_relative 'common/struct' +require_relative 'common/logging' diff --git a/lib/aliyun/oss/logging.rb b/lib/aliyun/common/logging.rb similarity index 91% rename from lib/aliyun/oss/logging.rb rename to lib/aliyun/common/logging.rb index c2a7d5b..46e339c 100644 --- a/lib/aliyun/oss/logging.rb +++ b/lib/aliyun/common/logging.rb @@ -3,7 +3,7 @@ require 'logger' module Aliyun - module OSS + module Common ## # Logging support # @example @@ -11,7 +11,7 @@ module OSS # logger.info(xxx) module Logging - DEFAULT_LOG_FILE = "./oss_sdk.log" + DEFAULT_LOG_FILE = "./aliyun_sdk.log" MAX_NUM_LOG = 100 ROTATE_SIZE = 10 * 1024 * 1024 @@ -42,5 +42,5 @@ def self.logger end end # logging - end # OSS + end # Common end # Aliyun diff --git a/lib/aliyun/common/struct.rb b/lib/aliyun/common/struct.rb new file mode 100644 index 0000000..f95f33d --- /dev/null +++ b/lib/aliyun/common/struct.rb @@ -0,0 +1,55 @@ +# -*- encoding: utf-8 -*- + +module Aliyun + module Common + + # Common structs used. It provides a 'attrs' helper method for + # subclass to define its attributes. 'attrs' is based on + # attr_reader and provide additional functionalities for classes + # that inherits Struct::Base : + # * the constuctor is provided to accept options and set the + # corresponding attibute automatically + # * the #to_s method is rewrite to concatenate the defined + # attributes keys and values + # @example + # class X < Struct::Base + # attrs :foo, :bar + # end + # + # x.new(:foo => 'hello', :bar => 'world') + # x.foo # == "hello" + # x.bar # == "world" + # x.to_s # == "foo: hello, bar: world" + module Struct + class Base + module AttrHelper + def attrs(*s) + define_method(:attrs) {s} + attr_reader(*s) + end + end + + extend AttrHelper + + def initialize(opts = {}) + extra_keys = opts.keys - attrs + unless extra_keys.empty? + fail ClientError, "Unexpected extra keys: #{extra_keys.join(', ')}" + end + + attrs.each do |attr| + instance_variable_set("@#{attr}", opts[attr]) + end + end + + def to_s + attrs.map do |attr| + v = instance_variable_get("@#{attr}") + "#{attr.to_s}: #{v}" + end.join(", ") + end + end # Base + end # Struct + + end # Common +end # Aliyun diff --git a/lib/aliyun/oss.rb b/lib/aliyun/oss.rb index cf9e0d8..72eccfe 100644 --- a/lib/aliyun/oss.rb +++ b/lib/aliyun/oss.rb @@ -1,7 +1,6 @@ # -*- encoding: utf-8 -*- -require_relative 'oss/version' -require_relative 'oss/logging' +require_relative 'common' require_relative 'oss/util' require_relative 'oss/exception' require_relative 'oss/struct' diff --git a/lib/aliyun/oss/bucket.rb b/lib/aliyun/oss/bucket.rb index 90aed8f..ae80bd7 100644 --- a/lib/aliyun/oss/bucket.rb +++ b/lib/aliyun/oss/bucket.rb @@ -8,7 +8,7 @@ module OSS # website, lifecycle, cors) # 2. object相关:上传、下载、追加、拷贝object等 # 3. multipart相关:断点续传、断点续载 - class Bucket < Struct::Base + class Bucket < Common::Struct::Base attrs :name, :location, :creation_time diff --git a/lib/aliyun/oss/client.rb b/lib/aliyun/oss/client.rb index 501657c..d03ff01 100644 --- a/lib/aliyun/oss/client.rb +++ b/lib/aliyun/oss/client.rb @@ -18,8 +18,6 @@ module OSS # bucket = client.get_bucket('my-bucket') class Client - include Logging - # 构造OSS client,用于操作buckets。 # @param opts [Hash] 构造Client时的参数选项 # @option opts [String] :endpoint [必填]OSS服务的地址,可以是以 diff --git a/lib/aliyun/oss/config.rb b/lib/aliyun/oss/config.rb index 2828635..cf040ba 100644 --- a/lib/aliyun/oss/config.rb +++ b/lib/aliyun/oss/config.rb @@ -7,7 +7,7 @@ module OSS # A place to store various configurations: credentials, api # timeout, retry mechanism, etc # - class Config < Struct::Base + class Config < Common::Struct::Base attrs :endpoint, :cname, :access_key_id, :access_key_secret, :open_timeout, :read_timeout diff --git a/lib/aliyun/oss/download.rb b/lib/aliyun/oss/download.rb index 8c2f2ec..cc00d69 100644 --- a/lib/aliyun/oss/download.rb +++ b/lib/aliyun/oss/download.rb @@ -7,6 +7,9 @@ module Multipart # A multipart download transaction # class Download < Transaction + + include Common::Logging + PART_SIZE = 10 * 1024 * 1024 READ_SIZE = 16 * 1024 NUM_THREAD = 10 diff --git a/lib/aliyun/oss/exception.rb b/lib/aliyun/oss/exception.rb index 00ac783..5122112 100644 --- a/lib/aliyun/oss/exception.rb +++ b/lib/aliyun/oss/exception.rb @@ -18,7 +18,6 @@ class Exception < RuntimeError # detailed information probably including the OSS request id. # class ServerError < Exception - include Logging attr_reader :http_code, :error_code, :message, :request_id diff --git a/lib/aliyun/oss/http.rb b/lib/aliyun/oss/http.rb index 1614687..44e925c 100644 --- a/lib/aliyun/oss/http.rb +++ b/lib/aliyun/oss/http.rb @@ -124,7 +124,7 @@ def closed? end - include Logging + include Common::Logging def initialize(config) @config = config diff --git a/lib/aliyun/oss/multipart.rb b/lib/aliyun/oss/multipart.rb index 9ae45fa..b7e0034 100644 --- a/lib/aliyun/oss/multipart.rb +++ b/lib/aliyun/oss/multipart.rb @@ -14,9 +14,7 @@ module Multipart ## # A multipart transaction. Provide the basic checkpoint methods. # - class Transaction < Struct::Base - - include Logging + class Transaction < Common::Struct::Base attrs :id, :object, :bucket, :creation_time, :options @@ -65,7 +63,7 @@ def get_file_md5(file) ## # A part in a multipart uploading transaction # - class Part < Struct::Base + class Part < Common::Struct::Base attrs :number, :etag, :size, :last_modified diff --git a/lib/aliyun/oss/object.rb b/lib/aliyun/oss/object.rb index 0a31c50..0b6ec0f 100644 --- a/lib/aliyun/oss/object.rb +++ b/lib/aliyun/oss/object.rb @@ -6,7 +6,7 @@ module OSS ## # Object表示OSS存储的一个对象 # - class Object < Struct::Base + class Object < Common::Struct::Base attrs :key, :type, :size, :etag, :metas, :last_modified, :content_type diff --git a/lib/aliyun/oss/protocol.rb b/lib/aliyun/oss/protocol.rb index 76f3b71..c1fb6ed 100644 --- a/lib/aliyun/oss/protocol.rb +++ b/lib/aliyun/oss/protocol.rb @@ -15,7 +15,7 @@ class Protocol STREAM_CHUNK_SIZE = 16 * 1024 - include Logging + include Common::Logging def initialize(config) @config = config diff --git a/lib/aliyun/oss/struct.rb b/lib/aliyun/oss/struct.rb index 1487583..6f1ac5c 100644 --- a/lib/aliyun/oss/struct.rb +++ b/lib/aliyun/oss/struct.rb @@ -51,55 +51,6 @@ def self.all end end # KeyEncoding - ## - # Common structs used. It provides a 'attrs' helper method for - # subclass to define its attributes. 'attrs' is based on - # attr_reader and provide additional functionalities for classes - # that inherits Struct::Base : - # * the constuctor is provided to accept options and set the - # corresponding attibute automatically - # * the #to_s method is rewrite to concatenate the defined - # attributes keys and values - # @example - # class X < Struct::Base - # attrs :foo, :bar - # end - # - # x.new(:foo => 'hello', :bar => 'world') - # x.foo # == "hello" - # x.bar # == "world" - # x.to_s # == "foo: hello, bar: world" - module Struct - class Base - module AttrHelper - def attrs(*s) - define_method(:attrs) {s} - attr_reader(*s) - end - end - - extend AttrHelper - - def initialize(opts = {}) - extra_keys = opts.keys - attrs - unless extra_keys.empty? - fail ClientError, "Unexpected extra keys: #{extra_keys.join(', ')}" - end - - attrs.each do |attr| - instance_variable_set("@#{attr}", opts[attr]) - end - end - - def to_s - attrs.map do |attr| - v = instance_variable_get("@#{attr}") - "#{attr.to_s}: #{v}" - end.join(", ") - end - end # Base - end # Struct - ## # Bucket Logging setting. See: {http://help.aliyun.com/document_detail/oss/product-documentation/function/logging.html OSS Bucket logging} # Attributes: @@ -111,7 +62,7 @@ def to_s # :enable => true, :target_bucket => 'log_bucket', :target_prefix => 'my-log') # @example Disable bucket logging # bucket.logging = BucketLogging.new(:enable => false) - class BucketLogging < Struct::Base + class BucketLogging < Common::Struct::Base attrs :enable, :target_bucket, :target_prefix def enabled? @@ -125,7 +76,7 @@ def enabled? # * enable [Boolean] whether to enable website hosting for the bucket # * index [String] the index object as the index page for the website # * error [String] the error object as the error page for the website - class BucketWebsite < Struct::Base + class BucketWebsite < Common::Struct::Base attrs :enable, :index, :error def enabled? @@ -138,7 +89,7 @@ def enabled? # Attributes: # * allow_empty [Boolean] whether to allow requests with empty "Referer" # * whitelist [Array] the allowed origins for requests - class BucketReferer < Struct::Base + class BucketReferer < Common::Struct::Base attrs :allow_empty, :whitelist def allow_empty? @@ -171,7 +122,7 @@ def allow_empty? # :prefix => 'foo/', # :expiry => 15) # @note the expiry date is treated as UTC time - class LifeCycleRule < Struct::Base + class LifeCycleRule < Common::Struct::Base attrs :id, :enable, :prefix, :expiry @@ -188,7 +139,7 @@ def enabled? # * allowed_headers [Array] the allowed headers # * expose_headers [Array] the expose headers # * max_age_seconds [Integer] the max age seconds - class CORSRule < Struct::Base + class CORSRule < Common::Struct::Base attrs :allowed_origins, :allowed_methods, :allowed_headers, :expose_headers, :max_age_seconds diff --git a/lib/aliyun/oss/upload.rb b/lib/aliyun/oss/upload.rb index 66e783e..7cee56c 100644 --- a/lib/aliyun/oss/upload.rb +++ b/lib/aliyun/oss/upload.rb @@ -7,6 +7,9 @@ module Multipart # A multipart upload transaction # class Upload < Transaction + + include Common::Logging + PART_SIZE = 10 * 1024 * 1024 READ_SIZE = 16 * 1024 NUM_THREAD = 10 diff --git a/lib/aliyun/oss/util.rb b/lib/aliyun/oss/util.rb index 12e6d1a..c42c57f 100644 --- a/lib/aliyun/oss/util.rb +++ b/lib/aliyun/oss/util.rb @@ -18,7 +18,7 @@ module Util class << self - include Logging + include Common::Logging # Calculate request signatures def get_signature(key, verb, headers, resources) diff --git a/lib/aliyun/oss/version.rb b/lib/aliyun/oss/version.rb deleted file mode 100644 index 8f3bfac..0000000 --- a/lib/aliyun/oss/version.rb +++ /dev/null @@ -1,9 +0,0 @@ -# -*- encoding: utf-8 -*- - -module Aliyun - module OSS - - VERSION = "0.1.8" - - end # OSS -end # Aliyun diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8481976..90e315c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,7 @@ require 'webmock/rspec' require 'aliyun/oss' +require 'aliyun/sts' RSpec.configure do |config| config.expect_with :rspec do |expectations| @@ -17,4 +18,4 @@ end -Aliyun::OSS::Logging::set_log_level(Logger::DEBUG) +Aliyun::Common::Logging::set_log_level(Logger::DEBUG) From a5416049f9a60ff771f6f80b6188b61494d59d1a Mon Sep 17 00:00:00 2001 From: Tianlong Wu Date: Mon, 7 Dec 2015 18:11:21 +0800 Subject: [PATCH 2/6] add aliyun/sts --- examples/aliyun/sts/assume_role.rb | 59 ++++++++++++ lib/aliyun/sts.rb | 9 ++ lib/aliyun/sts/client.rb | 38 ++++++++ lib/aliyun/sts/config.rb | 21 ++++ lib/aliyun/sts/exception.rb | 62 ++++++++++++ lib/aliyun/sts/protocol.rb | 130 +++++++++++++++++++++++++ lib/aliyun/sts/struct.rb | 64 ++++++++++++ lib/aliyun/sts/util.rb | 46 +++++++++ lib/aliyun/version.rb | 7 ++ spec/aliyun/sts/client_spec.rb | 150 +++++++++++++++++++++++++++++ spec/aliyun/sts/util_spec.rb | 39 ++++++++ 11 files changed, 625 insertions(+) create mode 100644 examples/aliyun/sts/assume_role.rb create mode 100644 lib/aliyun/sts.rb create mode 100644 lib/aliyun/sts/client.rb create mode 100644 lib/aliyun/sts/config.rb create mode 100644 lib/aliyun/sts/exception.rb create mode 100644 lib/aliyun/sts/protocol.rb create mode 100644 lib/aliyun/sts/struct.rb create mode 100644 lib/aliyun/sts/util.rb create mode 100644 lib/aliyun/version.rb create mode 100644 spec/aliyun/sts/client_spec.rb create mode 100644 spec/aliyun/sts/util_spec.rb diff --git a/examples/aliyun/sts/assume_role.rb b/examples/aliyun/sts/assume_role.rb new file mode 100644 index 0000000..3728995 --- /dev/null +++ b/examples/aliyun/sts/assume_role.rb @@ -0,0 +1,59 @@ +# -*- encoding: utf-8 -*- + +$LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__)) +require 'yaml' +require 'aliyun/sts' + +Aliyun::Common::Logging.set_log_level(Logger::DEBUG) +conf_file = '~/.sts.yml' +conf = YAML.load(File.read(File.expand_path(conf_file))) +client = Aliyun::STS::Client.new( + :access_key_id => conf['access_key_id'], + :access_key_secret => conf['access_key_secret']) + +# 辅助打印函数 +def demo(msg) + puts "######### #{msg} ########" + puts + yield + puts "-------------------------" + puts +end + +token = client.assume_role( + 'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-1') + +demo "Assume role" do + begin + token = client.assume_role( + 'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-1') + + puts "Credentials for session: #{token.session_name}" + puts "access key id: #{token.access_key_id}" + puts "access key secret: #{token.access_key_secret}" + puts "security token: #{token.security_token}" + puts "expiration at: #{token.expiration}" + rescue => e + puts "AssumeRole failed: #{e.message}" + end +end + +demo "Assume role with policy" do + begin + policy = Aliyun::STS::Policy.new + policy.allow( + ['oss:Get*', 'oss:PutObject'], + ['acs:oss:*:*:my-bucket', 'acs:oss:*:*:my-bucket/*']) + + token = client.assume_role( + 'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-2', policy, 900) + + puts "Credentials for session: #{token.session_name}" + puts "access key id: #{token.access_key_id}" + puts "access key secret: #{token.access_key_secret}" + puts "security token: #{token.security_token}" + puts "expiration at: #{token.expiration}" + rescue => e + puts "AssumeRole failed: #{e.message}" + end +end diff --git a/lib/aliyun/sts.rb b/lib/aliyun/sts.rb new file mode 100644 index 0000000..0fd7b7f --- /dev/null +++ b/lib/aliyun/sts.rb @@ -0,0 +1,9 @@ +# -*- encoding: utf-8 -*- + +require_relative 'common' +require_relative 'sts/util' +require_relative 'sts/exception' +require_relative 'sts/struct' +require_relative 'sts/config' +require_relative 'sts/protocol' +require_relative 'sts/client' diff --git a/lib/aliyun/sts/client.rb b/lib/aliyun/sts/client.rb new file mode 100644 index 0000000..f263e94 --- /dev/null +++ b/lib/aliyun/sts/client.rb @@ -0,0 +1,38 @@ +# -*- encoding: utf-8 -*- + +module Aliyun + module STS + + # STS服务的客户端,用于向STS申请临时token。 + # @example 创建Client + # client = Client.new( + # :access_key_id => 'access_key_id', + # :access_key_secret => 'access_key_secret') + # token = client.assume_role('role:arn', 'app') + # + # policy = Policy.new + # policy.allow(['oss:Get*'], ['acs:oss:*:*:my-bucket/*']) + # token = client.assume_role('role:arn', 'app', policy, 60) + # puts token.to_s + class Client + + def initialize(opts) + @config = Config.new(opts) + @protocol = Protocol.new(@config) + end + + # Assume a role + # @param role [String] the role arn + # @param session [String] the session name + # @param policy [STS::Policy] the policy + # @param duration [Fixnum] the duration seconds for the + # requested token + # @return [STS::Token] the sts token + def assume_role(role, session, policy = nil, duration = 3600) + @protocol.assume_role(role, session, policy, duration) + end + + end # Client + + end # STS +end # Aliyun diff --git a/lib/aliyun/sts/config.rb b/lib/aliyun/sts/config.rb new file mode 100644 index 0000000..41aa5a1 --- /dev/null +++ b/lib/aliyun/sts/config.rb @@ -0,0 +1,21 @@ +# -*- encoding: utf-8 -*- + +module Aliyun + module STS + + # A place to store various configurations: credentials, api + # timeout, retry mechanism, etc + class Config < Common::Struct::Base + + attrs :access_key_id, :access_key_secret + + def initialize(opts = {}) + super(opts) + + @access_key_id.strip! if @access_key_id + @access_key_secret.strip! if @access_key_secret + end + end # Config + + end # STS +end # Aliyun diff --git a/lib/aliyun/sts/exception.rb b/lib/aliyun/sts/exception.rb new file mode 100644 index 0000000..2e71286 --- /dev/null +++ b/lib/aliyun/sts/exception.rb @@ -0,0 +1,62 @@ +# -*- encoding: utf-8 -*- + +require 'nokogiri' + +module Aliyun + module STS + + # Base exception class + class Exception < RuntimeError + end + + # ServerError represents exceptions from the STS + # service. i.e. Client receives a HTTP response whose status is + # NOT OK. #message provides the error message and #to_s gives + # detailed information probably including the STS request id. + class ServerError < Exception + + attr_reader :http_code, :error_code, :message, :request_id + + def initialize(response) + @http_code = response.code + @attrs = {} + + doc = Nokogiri::XML(response.body) do |config| + config.options |= Nokogiri::XML::ParseOptions::NOBLANKS + end rescue nil + + if doc and doc.root + doc.root.children.each do |n| + @attrs[n.name] = n.text + end + end + + @error_code = @attrs['Code'] + @message = @attrs['Message'] + @request_id = @attrs['RequestId'] + end + + def message + msg = @attrs['Message'] || "UnknownError[#{http_code}]." + "#{msg} RequestId: #{request_id}" + end + + def to_s + @attrs.merge({'HTTPCode' => @http_code}).map do |k, v| + [k, v].join(": ") + end.join(", ") + end + end # ServerError + + # ClientError represents client exceptions caused mostly by + # invalid parameters. + class ClientError < Exception + attr_reader :message + + def initialize(message) + @message = message + end + end # ClientError + + end # STS +end # Aliyun diff --git a/lib/aliyun/sts/protocol.rb b/lib/aliyun/sts/protocol.rb new file mode 100644 index 0000000..a0f82da --- /dev/null +++ b/lib/aliyun/sts/protocol.rb @@ -0,0 +1,130 @@ +# -*- encoding: utf-8 -*- + +require 'rest-client' +require 'nokogiri' +require 'time' + +module Aliyun + module STS + + # Protocol implements the STS Open API which is low-level. User + # should refer to {STS::Client} for normal use. + class Protocol + + ENDPOINT = 'https://sts.aliyuncs.com' + FORMAT = 'XML' + API_VERSION = '2015-04-01' + SIGNATURE_METHOD = 'HMAC-SHA1' + SIGNATURE_VERSION = '1.0' + + include Common::Logging + + def initialize(config) + @config = config + end + + # Assume a role + # @param role [String] the role arn + # @param session [String] the session name + # @param policy [STS::Policy] the policy + # @param duration [Fixnum] the duration seconds for the + # requested token + # @return [STS::Token] the sts token + def assume_role(role, session, policy = nil, duration = 3600) + logger.info("Begin assume role, role: #{role}, session: #{session}, "\ + "policy: #{policy}, duration: #{duration}") + + params = { + 'Action' => 'AssumeRole', + 'RoleArn' => role, + 'RoleSessionName' => session, + 'DurationSeconds' => duration.to_s + } + params.merge!({'Policy' => policy.serialize}) if policy + + body = do_request(params) + doc = parse_xml(body) + + creds_node = doc.at_css("Credentials") + creds = { + session_name: session, + access_key_id: get_node_text(creds_node, 'AccessKeyId'), + access_key_secret: get_node_text(creds_node, 'AccessKeySecret'), + security_token: get_node_text(creds_node, 'SecurityToken'), + expiration: get_node_text( + creds_node, 'Expiration') { |x| Time.parse(x) }, + } + + logger.info("Done assume role, creds: #{creds}") + + Token.new(creds) + end + + private + # Generate a random signature nonce + # @return [String] a random string + def signature_nonce + (rand * 1_000_000_000).to_s + end + + # Do HTTP POST request with specified params + # @param params [Hash] the parameters to STS + # @return [String] the response body + # @raise [ServerError] raise errors if the server responds with errors + def do_request(params) + query = params.merge( + {'Format' => FORMAT, + 'Version' => API_VERSION, + 'AccessKeyId' => @config.access_key_id, + 'SignatureMethod' => SIGNATURE_METHOD, + 'SignatureVersion' => SIGNATURE_VERSION, + 'SignatureNonce' => signature_nonce, + 'Timestamp' => Time.now.utc.iso8601}) + + signature = Util.get_signature('POST', query, @config.access_key_secret) + query.merge!({'Signature' => signature}) + + r = RestClient::Request.execute( + :method => 'POST', + :url => ENDPOINT, + :payload => query + ) do |response, request, result, &blk| + + if response.code >= 300 + e = ServerError.new(response) + logger.error(e.to_s) + raise e + else + response.return!(request, result, &blk) + end + end + + logger.debug("Received HTTP response, code: #{r.code}, headers: "\ + "#{r.headers}, body: #{r.body}") + r.body + end + + # Parse body content to xml document + # @param content [String] the xml content + # @return [Nokogiri::XML::Document] the parsed document + def parse_xml(content) + doc = Nokogiri::XML(content) do |config| + config.options |= Nokogiri::XML::ParseOptions::NOBLANKS + end + + doc + end + + # Get the text of a xml node + # @param node [Nokogiri::XML::Node] the xml node + # @param tag [String] the node tag + # @yield [String] the node text is given to the block + def get_node_text(node, tag, &block) + n = node.at_css(tag) if node + value = n.text if n + block && value ? yield(value) : value + end + + end # Protocol + end # STS +end # Aliyun diff --git a/lib/aliyun/sts/struct.rb b/lib/aliyun/sts/struct.rb new file mode 100644 index 0000000..0525d67 --- /dev/null +++ b/lib/aliyun/sts/struct.rb @@ -0,0 +1,64 @@ +# -*- encoding: utf-8 -*- + +require 'json' +require 'cgi' + +module Aliyun + module STS + + # STS Policy. Referer to + # https://help.aliyun.com/document_detail/ram/ram-user-guide/policy_reference/struct_def.html for details. + class Policy < Common::Struct::Base + VERSION = '1' + + attrs :rules + + # Add an 'Allow' rule + # @param actions [Array] actions of the rule. e.g.: + # oss:GetObject, oss:Get*, oss:* + # @param resources [Array] resources of the rule. e.g.: + # acs:oss:*:*:my-bucket, acs:oss:*:*:my-bucket/*, acs:oss:*:*:* + def allow(actions, resources) + add_rule(true, actions, resources) + end + + # Add an 'Deny' rule + # @param actions [Array] actions of the rule. e.g.: + # oss:GetObject, oss:Get*, oss:* + # @param resources [Array] resources of the rule. e.g.: + # acs:oss:*:*:my-bucket, acs:oss:*:*:my-bucket/*, acs:oss:*:*:* + def deny(actions, resources) + add_rule(false, actions, resources) + end + + # Serialize to rule to string + def serialize + {'Version' => VERSION, 'Statement' => @rules}.to_json + end + + private + def add_rule(allow, actions, resources) + @rules ||= [] + @rules << { + 'Effect' => allow ? 'Allow' : 'Deny', + 'Action' => actions, + 'Resource' => resources + } + end + end + + # STS token. User may use the credentials included to access + # Alicloud resources(OSS, OTS, etc). + # Attributes: + # * access_key_id [String] the AccessKeyId + # * access_key_secret [String] the AccessKeySecret + # * security_token [String] the SecurityToken + # * expiration [Time] the time when the token will be expired + # * session_name [String] the session name for this token + class Token < Common::Struct::Base + attrs :access_key_id, :access_key_secret, + :security_token, :expiration, :session_name + end + + end # STS +end # Aliyun diff --git a/lib/aliyun/sts/util.rb b/lib/aliyun/sts/util.rb new file mode 100644 index 0000000..e80ddc1 --- /dev/null +++ b/lib/aliyun/sts/util.rb @@ -0,0 +1,46 @@ +require 'time' +require 'cgi' +require 'base64' +require 'openssl' +require 'digest/md5' + +module Aliyun + module STS + ## + # Util functions to help generate formatted Date, signatures, + # etc. + # + module Util + + class << self + + include Common::Logging + + # Calculate request signatures + def get_signature(verb, params, key) + logger.debug("Sign, verb: #{verb}, params: #{params}") + + cano_query = params.sort.map { + |k, v| [CGI.escape(k), CGI.escape(v)].join('=') }.join('&') + + string_to_sign = + verb + '&' + CGI.escape('/') + '&' + CGI.escape(cano_query) + + logger.debug("String to sign: #{string_to_sign}") + + Util.sign(key + '&', string_to_sign) + end + + # Sign a string using HMAC and BASE64 + # @param [String] key the secret key + # @param [String] string_to_sign the string to sign + # @return [String] the signature + def sign(key, string_to_sign) + Base64.strict_encode64( + OpenSSL::HMAC.digest('sha1', key, string_to_sign)) + end + + end # self + end # Util + end # STS +end # Aliyun diff --git a/lib/aliyun/version.rb b/lib/aliyun/version.rb new file mode 100644 index 0000000..d546af5 --- /dev/null +++ b/lib/aliyun/version.rb @@ -0,0 +1,7 @@ +# -*- encoding: utf-8 -*- + +module Aliyun + + VERSION = "0.2.0" + +end # Aliyun diff --git a/spec/aliyun/sts/client_spec.rb b/spec/aliyun/sts/client_spec.rb new file mode 100644 index 0000000..69f7184 --- /dev/null +++ b/spec/aliyun/sts/client_spec.rb @@ -0,0 +1,150 @@ +require 'spec_helper' +require 'yaml' +require 'nokogiri' + +module Aliyun + module STS + + describe Client do + + context "construct" do + it "should setup a/k" do + client = Client.new( + :access_key_id => 'xxx', :access_key_secret => 'yyy') + + config = client.instance_variable_get('@config') + expect(config.access_key_id).to eq('xxx') + expect(config.access_key_secret).to eq('yyy') + end + end + + def mock_sts(id, key, token, expiration) + Nokogiri::XML::Builder.new do |xml| + xml.AssumeRoleResponse { + xml.RequestId '0000' + xml.AssumedRoleUser { + xml.arn 'arn-001' + xml.AssumedRoleUserId 'id-001' + } + xml.Credentials { + xml.AccessKeyId id + xml.AccessKeySecret key + xml.SecurityToken token + xml.Expiration expiration.utc.iso8601 + } + } + end.to_xml + end + + def mock_error(code, message) + Nokogiri::XML::Builder.new do |xml| + xml.Error { + xml.Code code + xml.Message message + xml.RequestId '0000' + } + end.to_xml + end + + def err(msg, reqid = '0000') + "#{msg} RequestId: #{reqid}" + end + + before :all do + @url = 'https://sts.aliyuncs.com' + @client = Client.new(access_key_id: 'xxx', access_key_secret: 'yyy') + end + + context "assume role" do + it "should assume role" do + expiration = Time.parse(Time.now.utc.iso8601) + + stub_request(:post, @url) + .to_return(:body => mock_sts( + 'sts_id', 'sts_key', 'sts_token', expiration)) + + token = @client.assume_role('role-1', 'app-1') + + rbody = nil + expect(WebMock).to have_requested(:post, @url) + .with { |req| rbody = req.body } + params = rbody.split('&').reduce({}) { |h, i| + v = i.split('=') + h.merge({v[0] => v[1]}) + } + expect(params['Action']).to eq('AssumeRole') + expect(params['RoleArn']).to eq('role-1') + expect(params['RoleSessionName']).to eq('app-1') + expect(params['DurationSeconds']).to eq('3600') + expect(params['Format']).to eq('XML') + expect(params['Version']).to eq('2015-04-01') + expect(params['AccessKeyId']).to eq('xxx') + expect(params.key?('Signature')).to be true + expect(params.key?('SignatureNonce')).to be true + expect(params['SignatureMethod']).to eq('HMAC-SHA1') + + expect(token.access_key_id).to eq('sts_id') + expect(token.access_key_secret).to eq('sts_key') + expect(token.security_token).to eq('sts_token') + expect(token.expiration).to eq(expiration) + end + + it "should raise error" do + code = "InvalidParameter" + message = "Bla bla bla." + + stub_request(:post, @url) + .to_return(:status => 400, + :body => mock_error(code, message)) + + + expect { + @client.assume_role('role-1', 'app-1') + }.to raise_error(ServerError, err(message)) + + end + + it "should set policy and duration" do + expiration = Time.parse(Time.now.utc.iso8601) + + stub_request(:post, @url) + .to_return(:body => mock_sts( + 'sts_id', 'sts_key', 'sts_token', expiration)) + + policy = Policy.new + policy.allow( + ['oss:Get*', 'oss:PutObject'], + ['acs:oss:*:*:bucket', 'acs::oss:*:*:bucket/*']) + duration = 300 + token = @client.assume_role('role-1', 'app-1', policy, duration) + + rbody = nil + expect(WebMock).to have_requested(:post, @url) + .with { |req| rbody = req.body } + params = rbody.split('&').reduce({}) { |h, i| + v = i.split('=') + h.merge({v[0] => CGI.unescape(v[1])}) + } + expect(params['Action']).to eq('AssumeRole') + expect(params['RoleArn']).to eq('role-1') + expect(params['RoleSessionName']).to eq('app-1') + expect(params['DurationSeconds']).to eq('300') + expect(params['Format']).to eq('XML') + expect(params['Version']).to eq('2015-04-01') + expect(params['AccessKeyId']).to eq('xxx') + expect(params.key?('Signature')).to be true + expect(params.key?('SignatureNonce')).to be true + expect(params['SignatureMethod']).to eq('HMAC-SHA1') + expect(params['Policy']).to eq(policy.serialize) + + expect(token.access_key_id).to eq('sts_id') + expect(token.access_key_secret).to eq('sts_key') + expect(token.security_token).to eq('sts_token') + expect(token.expiration).to eq(expiration) + end + end + + end # Client + + end # OSS +end # Aliyun diff --git a/spec/aliyun/sts/util_spec.rb b/spec/aliyun/sts/util_spec.rb new file mode 100644 index 0000000..aa54299 --- /dev/null +++ b/spec/aliyun/sts/util_spec.rb @@ -0,0 +1,39 @@ +# -*- encoding: utf-8 -*- + +require 'spec_helper' +require 'yaml' +require 'nokogiri' + +module Aliyun + module STS + + describe Util do + + it "should get correct signature" do + key = 'helloworld' + ts = '2015-12-07T07:18:41Z' + + params = { + 'Action' => 'AssumeRole', + 'RoleArn' => 'role-1', + 'RoleSessionName' => 'app-1', + 'DurationSeconds' => '300', + 'Format' => 'XML', + 'Version' => '2015-04-01', + 'AccessKeyId' => 'xxx', + 'SignatureMethod' => 'HMAC-SHA1', + 'SignatureVersion' => '1.0', + 'SignatureNonce' => '3.14159', + 'Timestamp' => ts + } + + signature = Util.get_signature('POST', params, key) + expect(signature).to eq("92ta30QopCT4YTbRCaWtS31kyeg=") + + signature = Util.get_signature('GET', params, key) + expect(signature).to eq("nvMmnOSxGrfK+1zf0oFR5RB2M7k=") + end + + end # Util + end # OSS +end # Aliyun From dddc5fa382c2ef526ef165d91490c4164e630854 Mon Sep 17 00:00:00 2001 From: Tianlong Wu Date: Mon, 7 Dec 2015 18:35:44 +0800 Subject: [PATCH 3/6] OSS::Client support STS token --- examples/aliyun/oss/using_sts.rb | 48 ++++++++++++++++++++++++++++++++ lib/aliyun/common.rb | 2 ++ lib/aliyun/common/exception.rb | 18 ++++++++++++ lib/aliyun/common/struct.rb | 3 +- lib/aliyun/oss/client.rb | 2 ++ lib/aliyun/oss/config.rb | 3 +- lib/aliyun/oss/exception.rb | 15 ++-------- lib/aliyun/oss/http.rb | 2 ++ lib/aliyun/sts/exception.rb | 13 ++------- lib/aliyun/sts/util.rb | 2 ++ 10 files changed, 82 insertions(+), 26 deletions(-) create mode 100644 examples/aliyun/oss/using_sts.rb create mode 100644 lib/aliyun/common/exception.rb diff --git a/examples/aliyun/oss/using_sts.rb b/examples/aliyun/oss/using_sts.rb new file mode 100644 index 0000000..29ede9e --- /dev/null +++ b/examples/aliyun/oss/using_sts.rb @@ -0,0 +1,48 @@ +# -*- encoding: utf-8 -*- + +$LOAD_PATH.unshift(File.expand_path("../../../../lib", __FILE__)) +require 'yaml' +require 'aliyun/sts' +require 'aliyun/oss' + +# 初始化OSS client +Aliyun::Common::Logging.set_log_level(Logger::DEBUG) +conf_file = '~/.sts.yml' +conf = YAML.load(File.read(File.expand_path(conf_file))) + +# 辅助打印函数 +def demo(msg) + puts "######### #{msg} ########" + puts + yield + puts "-------------------------" + puts +end + +demo "Using STS" do + sts = Aliyun::STS::Client.new( + :access_key_id => conf['access_key_id'], + :access_key_secret => conf['access_key_secret']) + + token = sts.assume_role( + 'acs:ram::52352:role/aliyunosstokengeneratorrole', 'app-1') + + client = Aliyun::OSS::Client.new( + :endpoint => 'http://oss-cn-hangzhou.aliyuncs.com', + :sts_token => token.security_token, + :access_key_id => token.access_key_id, + :access_key_secret => token.access_key_secret) + + unless client.bucket_exists?('bucket-for-sts-test') + client.create_bucket('bucket-for-sts-test') + end + + bucket = client.get_bucket('bucket-for-sts-test') + + bucket.put_object('hello') { |s| s << 'hello' } + bucket.put_object('world') { |s| s << 'world' } + + bucket.list_objects.take(10).each do |obj| + puts "Object: #{obj.key}, size: #{obj.size}" + end +end diff --git a/lib/aliyun/common.rb b/lib/aliyun/common.rb index b80cb35..75bd541 100644 --- a/lib/aliyun/common.rb +++ b/lib/aliyun/common.rb @@ -1,4 +1,6 @@ # -*- encoding: utf-8 -*- +require_relative 'version' require_relative 'common/struct' require_relative 'common/logging' +require_relative 'common/exception' diff --git a/lib/aliyun/common/exception.rb b/lib/aliyun/common/exception.rb new file mode 100644 index 0000000..d47e7a1 --- /dev/null +++ b/lib/aliyun/common/exception.rb @@ -0,0 +1,18 @@ +# -*- encoding: utf-8 -*- + +module Aliyun + module Common + + ## + # Base exception class + # + class Exception < RuntimeError + attr_reader :message + + def initialize(message) + @message = message + end + end + + end # Common +end # Aliyun diff --git a/lib/aliyun/common/struct.rb b/lib/aliyun/common/struct.rb index f95f33d..b0d7cba 100644 --- a/lib/aliyun/common/struct.rb +++ b/lib/aliyun/common/struct.rb @@ -34,7 +34,8 @@ def attrs(*s) def initialize(opts = {}) extra_keys = opts.keys - attrs unless extra_keys.empty? - fail ClientError, "Unexpected extra keys: #{extra_keys.join(', ')}" + fail Common::Exception, + "Unexpected extra keys: #{extra_keys.join(', ')}" end attrs.each do |attr| diff --git a/lib/aliyun/oss/client.rb b/lib/aliyun/oss/client.rb index d03ff01..6f8ea8a 100644 --- a/lib/aliyun/oss/client.rb +++ b/lib/aliyun/oss/client.rb @@ -28,6 +28,8 @@ class Client # KEY SECRET,如果不填则会尝试匿名访问 # @option opts [Boolean] :cname [可选] 指定endpoint是否是用户绑 # 定的域名 + # @option opts [Boolean] :sts_token [可选] 指定STS的 + # SecurityToken,如果指定,则使用STS授权访问 # @option opts [Fixnum] :open_timeout [可选] 指定建立连接的超时 # 时间,默认为10秒 # @option opts [Fixnum] :read_timeout [可选] 指定等待响应的超时 diff --git a/lib/aliyun/oss/config.rb b/lib/aliyun/oss/config.rb index cf040ba..8d9677f 100644 --- a/lib/aliyun/oss/config.rb +++ b/lib/aliyun/oss/config.rb @@ -9,7 +9,8 @@ module OSS # class Config < Common::Struct::Base - attrs :endpoint, :cname, :access_key_id, :access_key_secret, + attrs :endpoint, :cname, :sts_token, + :access_key_id, :access_key_secret, :open_timeout, :read_timeout def initialize(opts = {}) diff --git a/lib/aliyun/oss/exception.rb b/lib/aliyun/oss/exception.rb index 5122112..fafba41 100644 --- a/lib/aliyun/oss/exception.rb +++ b/lib/aliyun/oss/exception.rb @@ -5,19 +5,13 @@ module Aliyun module OSS - ## - # Base exception class - # - class Exception < RuntimeError - end - ## # ServerError represents exceptions from the OSS # service. i.e. Client receives a HTTP response whose status is # NOT OK. #message provides the error message and #to_s gives # detailed information probably including the OSS request id. # - class ServerError < Exception + class ServerError < Common::Exception attr_reader :http_code, :error_code, :message, :request_id @@ -64,12 +58,7 @@ def get_request_id(response) # ClientError represents client exceptions caused mostly by # invalid parameters. # - class ClientError < Exception - attr_reader :message - - def initialize(message) - @message = message - end + class ClientError < Common::Exception end # ClientError ## diff --git a/lib/aliyun/oss/http.rb b/lib/aliyun/oss/http.rb index 44e925c..1e419c0 100644 --- a/lib/aliyun/oss/http.rb +++ b/lib/aliyun/oss/http.rb @@ -32,6 +32,7 @@ module OSS class HTTP DEFAULT_CONTENT_TYPE = 'application/octet-stream' + STS_HEADER = 'x-oss-security-token' OPEN_TIMEOUT = 10 READ_TIMEOUT = 120 @@ -209,6 +210,7 @@ def do_request(verb, resources = {}, http_options = {}, &block) headers['User-Agent'] = get_user_agent headers['Date'] = Time.now.httpdate headers['Content-Type'] ||= DEFAULT_CONTENT_TYPE + headers[STS_HEADER] = @config.sts_token if @config.sts_token if body = http_options[:body] if body.respond_to?(:read) diff --git a/lib/aliyun/sts/exception.rb b/lib/aliyun/sts/exception.rb index 2e71286..1cdc004 100644 --- a/lib/aliyun/sts/exception.rb +++ b/lib/aliyun/sts/exception.rb @@ -5,15 +5,11 @@ module Aliyun module STS - # Base exception class - class Exception < RuntimeError - end - # ServerError represents exceptions from the STS # service. i.e. Client receives a HTTP response whose status is # NOT OK. #message provides the error message and #to_s gives # detailed information probably including the STS request id. - class ServerError < Exception + class ServerError < Common::Exception attr_reader :http_code, :error_code, :message, :request_id @@ -50,12 +46,7 @@ def to_s # ClientError represents client exceptions caused mostly by # invalid parameters. - class ClientError < Exception - attr_reader :message - - def initialize(message) - @message = message - end + class ClientError < Common::Exception end # ClientError end # STS diff --git a/lib/aliyun/sts/util.rb b/lib/aliyun/sts/util.rb index e80ddc1..82449f8 100644 --- a/lib/aliyun/sts/util.rb +++ b/lib/aliyun/sts/util.rb @@ -1,3 +1,5 @@ +# -*- encoding: utf-8 -*- + require 'time' require 'cgi' require 'base64' From 9740e3df6dd0b76103334d746df49c23ab4b61a8 Mon Sep 17 00:00:00 2001 From: Tianlong Wu Date: Mon, 7 Dec 2015 18:43:01 +0800 Subject: [PATCH 4/6] add STS test for OSS::Client --- spec/aliyun/oss/client/client_spec.rb | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/spec/aliyun/oss/client/client_spec.rb b/spec/aliyun/oss/client/client_spec.rb index e1c5bad..ea786bb 100644 --- a/spec/aliyun/oss/client/client_spec.rb +++ b/spec/aliyun/oss/client/client_spec.rb @@ -14,12 +14,14 @@ module OSS endpoint = 'oss-cn-hangzhou.aliyuncs.com' client = Client.new( :endpoint => endpoint, - :access_key_id => 'xxx ', :access_key_secret => ' yyy ') + :access_key_id => 'xxx ', :access_key_secret => ' yyy ', + :sts_token => 'sts-token') config = client.instance_variable_get('@config') expect(config.endpoint.to_s).to eq("http://#{endpoint}") expect(config.access_key_id).to eq('xxx') expect(config.access_key_secret).to eq('yyy') + expect(config.sts_token).to eq('sts-token') end it "should not set Authorization with anonymous client" do @@ -37,6 +39,24 @@ module OSS .with{ |req| not req.headers.has_key?('Authorization') } end + it "should set STS header" do + endpoint = 'oss-cn-hangzhou.aliyuncs.com' + bucket = 'rubysdk-bucket' + object = 'rubysdk-object' + client = Client.new( + :endpoint => endpoint, + :access_key_id => 'xxx', :access_key_secret => 'yyy', + :sts_token => 'sts-token') + + stub_request(:get, "#{bucket}.#{endpoint}/#{object}") + + client.get_bucket(bucket).get_object(object) {} + + expect(WebMock) + .to have_requested(:get, "#{bucket}.#{endpoint}/#{object}") + .with{ |req| not req.headers.has_key?('x-oss-security-token') } + end + it "should construct different client" do bucket = 'rubysdk-bucket' object = 'rubysdk-object' From a06e778016ca07fa8b2addb9bf829335098c0f82 Mon Sep 17 00:00:00 2001 From: Tianlong Wu Date: Mon, 7 Dec 2015 18:52:40 +0800 Subject: [PATCH 5/6] add STS to README --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 78bb30e..fb456cd 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,28 @@ OSS支持自定义域名绑定,允许用户将自己的域名指向阿里云OS 3. 在{Aliyun::OSS::Client#get_bucket}时仍需要指定bucket名字,并且要与 域名所绑定的bucket名字相同 +#### 使用STS创建Client + +OSS支持用户使用STS进行访问,更多有关STS的内容,请参考 +[阿里云STS][aliyun-sts]。在使用STS之前需要先向STS申请一个临时token, +aliyun-sdk中包含了STS的SDK,使用时只需要`require 'aliyun/sts'`即可: + + require 'aliyun/sts' + sts = Aliyun::STS::Client.new( + access_key_id: 'access_key_id', + access_key_secret: 'access_key_secret') + + token = sts.assume_role('role-arn', 'my-app') + + client = Aliyun::OSS::Client.new( + :endpoint => 'http://img.my-domain.com', + :access_key_id => token.access_key_id, + :access_key_secret => token.access_key_secret, + :sts_token => token.sts_token) + +注意使用STS时必须指定`:sts_token`参数。用户还可以通过`STS::Client`申请 +带Policy的token,细节请参考[API文档][sdk-api]。 + ### 列出当前所有的Bucket buckets = client.list_buckets @@ -362,3 +384,5 @@ SDK采用rspec进行测试,如果要对SDK进行修改,请确保没有break [1]: http://help.aliyun.com/document_detail/oss/user_guide/endpoint_region.html [2]: http://help.aliyun.com/document_detail/oss/user_guide/oss_concept/oss_cname.html +[aliyun-sts]: https://help.aliyun.com/document_detail/ram/intro/concepts.html +[sdk-api]: http://www.rubydoc.info/gems/aliyun-sdk/ From e9abbfee58f3c4e362737ad40fa81513f76ffdf3 Mon Sep 17 00:00:00 2001 From: Tianlong Wu Date: Mon, 7 Dec 2015 19:01:30 +0800 Subject: [PATCH 6/6] Version => 0.2.0 --- CHANGELOG.md | 5 +++++ README.md | 12 ++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f7c387..8e75402 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## Change Log +### v0.2.0 + +- Add aliyun/sts +- OSS::Client support STS + ### v0.1.8 - Fix StreamWriter string encoding problem diff --git a/README.md b/README.md index fb456cd..b5644e3 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ AccessKeySecret,在使用Aliyun OSS SDK时需要提供您的这两个信息。 其中`endpoint`是OSS服务的地址,根据节点区域不同,这个地址可能不一样,例如 杭州节点的地址是:`http://oss-cn-hangzhou.aliyuncs.com`,其他节点的地址见: -[节点列表][1] +[节点列表][region-list] `access_key_id`和`access_key_secret`是您的服务凭证,在官网的“管理控制 台”上面可以查看。**请妥善保管您的AccessKeySecret,泄露之后可能影响您的 @@ -65,7 +65,7 @@ AccessKeySecret,在使用Aliyun OSS SDK时需要提供您的这两个信息。 OSS支持自定义域名绑定,允许用户将自己的域名指向阿里云OSS的服务地址 (CNAME),这样用户迁移到OSS上时应用内资源的路径可以不用修改。绑定的域 名指向OSS的一个bucket。绑定域名的操作只能在OSS控制台进行。更多关于自定 -义域名绑定的内容请到官网了解:[OSS自定义域名绑定][2] +义域名绑定的内容请到官网了解:[OSS自定义域名绑定][custom-domain] 用户绑定了域名后,使用SDK时指定的endpoint可以使用标准的OSS服务地址,也 可以使用用户绑定的域名: @@ -86,8 +86,8 @@ OSS支持自定义域名绑定,允许用户将自己的域名指向阿里云OS #### 使用STS创建Client -OSS支持用户使用STS进行访问,更多有关STS的内容,请参考 -[阿里云STS][aliyun-sts]。在使用STS之前需要先向STS申请一个临时token, +OSS支持用户使用STS进行访问,更多有关STS的内容,请参考 [阿里云STS][aliyun-sts]。 +在使用STS之前需要先向STS申请一个临时token, aliyun-sdk中包含了STS的SDK,使用时只需要`require 'aliyun/sts'`即可: require 'aliyun/sts' @@ -382,7 +382,7 @@ SDK采用rspec进行测试,如果要对SDK进行修改,请确保没有break - 阿里云官网文档:http://help.aliyun.com/product/8314910_oss.html -[1]: http://help.aliyun.com/document_detail/oss/user_guide/endpoint_region.html -[2]: http://help.aliyun.com/document_detail/oss/user_guide/oss_concept/oss_cname.html +[region-list]: https://help.aliyun.com/document_detail/oss/user_guide/endpoint_region.html +[custom-domain]: https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/oss_cname.html [aliyun-sts]: https://help.aliyun.com/document_detail/ram/intro/concepts.html [sdk-api]: http://www.rubydoc.info/gems/aliyun-sdk/