Merge pull request #84 from BluntBlade/master

重写与授权相关的函数和类,并归入Qiniu::Auth空间。
This commit is contained in:
LI Daobing
2014-04-02 12:41:08 +08:00
11 changed files with 383 additions and 36 deletions
-1
View File
@@ -7,7 +7,6 @@ rvm:
- 2.1.1
- jruby-18mode
- jruby-19mode
- jruby-head
- ree
before_script:
- export QINIU_ACCESS_KEY=LGRqJsjX7LAvtdtX6hhyxs861lS57HwnOQmJsOuX
+6
View File
@@ -1,5 +1,11 @@
## CHANGE LOG
### v6.2.0
- 重写与授权相关的函数并归入Qiniu::Auth空间,原授权凭证生成类维持不变。
- 添加Qiniu::Storage::PutPolicy类和Qiniu::Storage#upload_with_put_policy方法,并推荐使用两者组合实现单文件上传。
### v6.1.0
- Qiniu::Storage所有上传接口返回第三个值raw_headers,类型为Hash,包含已解析的HTTP响应报文中的所有Header信息。
+1 -1
View File
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
qiniu (6.1.0)
qiniu (6.2.0)
json (~> 1.7)
mime-types (~> 1.19)
rest-client (~> 1.6)
+215 -2
View File
@@ -1,17 +1,146 @@
# -*- encoding: utf-8 -*-
# vim: sw=2 ts=2
require 'hmac-sha1'
require 'uri'
require 'qiniu/exceptions'
module Qiniu
module Auth
DEFAULT_AUTH_SECONDS = 3600
class << self
def calculate_deadline(expires_in, deadline = nil)
### 授权期计算
if expires_in.is_a?(Integer) && expires_in > 0 then
# 指定相对时间,单位:秒
return Time.now.to_i + expires_in
elsif deadline.is_a?(Integer) then
# 指定绝对时间,常用于调试和单元测试
return deadline
end
# 默认授权期1小时
return Time.now.to_i + DEFAULT_AUTH_SECONDS
end # calculate_deadline
end # class << self
class PutPolicy
private
def initialize(bucket,
key = nil,
expires_in = DEFAULT_AUTH_SECONDS,
deadline = nil)
### 设定scope参数(必填项目)
self.scope!(bucket, key)
### 设定deadline参数(必填项目)
@expires_in = expires_in
@deadline = Auth.calculate_deadline(expires_in, deadline)
end # initialize
PARAMS = {
# 字符串类型参数
:scope => "scope" ,
:save_key => "saveKey" ,
:end_user => "endUser" ,
:return_url => "returnUrl" ,
:return_body => "returnBody" ,
:callback_url => "callbackUrl" ,
:callback_body => "callbackBody" ,
:persistent_ops => "persistentOps" ,
:persistent_notify_url => "persistentNotifyUrl" ,
:transform => "transform" ,
# 数值类型参数
:deadline => "deadline" ,
:insert_only => "insertOnly" ,
:fsize_limit => "fsizeLimit" ,
:detect_mime => "detectMime" ,
:mime_limit => "mimeLimit" ,
:fop_timeout => "fopTimeout"
} # PARAMS
public
attr_reader :bucket, :key
def scope!(bucket, key = nil)
@bucket = bucket
@key = key
if key.nil? then
# 新增语义,文件已存在则失败
@scope = bucket
else
# 覆盖语义,文件已存在则直接覆盖
@scope = "#{bucket}:#{key}"
end
end # scope!
def expires_in!(seconds)
if !seconds.nil? then
return @expires_in
end
@epires_in = seconds
@deadline = Auth.calculate_deadline(seconds)
return @expires_in
end # expires_in!
def expires_in=(seconds)
return expires_in!(seconds)
end # expires_in=
def expires_in
return @expires_in
end # expires_in
def allow_mime_list! (list)
@mime_limit = list
end # allow_mime_list!
def deny_mime_list! (list)
@mime_limit = "!#{list}"
end # deny_mime_list!
def insert_only!
@insert_only = 1
end # insert_only!
def detect_mime!
@detect_mime = 1
end # detect_mime!
def to_json
args = {}
PARAMS.each_pair do |key, fld|
val = self.__send__(key)
if !val.nil? then
args[fld] = val
end
end
return args.to_json
end # to_json
PARAMS.each_pair do |key, fld|
attr_accessor key
end
end # class PutPolicy
class << self
include Utils
def call_with_signature(url, data, retry_times = 0, options = {})
code, data, raw_headers = http_request url, data, options.merge({:qbox_signature_token => generate_qbox_signature(url, data, options[:mime])})
[code, data, raw_headers]
return Utils.http_request(
url,
data,
options.merge({:qbox_signature_token => generate_acctoken(url, data)})
)
end # call_with_signature
def request(url, data = nil, options = {})
@@ -19,6 +148,90 @@ module Qiniu
[code, data, raw_headers]
end # request
EMPTY_ARGS = {}
### 生成下载授权URL
def authorize_download_url(url, args = EMPTY_ARGS)
### 提取AK/SK信息
access_key = Config.settings[:access_key]
secret_key = Config.settings[:secret_key]
### 授权期计算
e = Auth.calculate_deadline(args[:expires_in], args[:deadline])
### URL变换:追加授权期参数
if url.index('?').is_a?(Fixnum) then
# 已有参数
download_url = "#{url}&e=#{e}"
else
# 尚无参数
download_url = "#{url}?e=#{e}"
end
### 生成数字签名
sign = HMAC::SHA1.new(secret_key).update(download_url).digest
encoded_sign = Utils.urlsafe_base64_encode(sign)
### 生成下载授权凭证
dntoken = "#{access_key}:#{encoded_sign}"
### 返回下载授权URL
return "#{download_url}&token=#{dntoken}"
end # authorize_download_url
def generate_acctoken(url, body = '')
### 提取AK/SK信息
access_key = Config.settings[:access_key]
secret_key = Config.settings[:secret_key]
### 解析URL,生成待签名字符串
uri = URI.parse(url)
signing_str = uri.path
# 如有QueryString部分,则需要加上
query_string = uri.query
if query_string.is_a?(String) && !query_string.empty?
signing_str += '?' + query_string
end
# 追加换行符
signing_str += "\n"
# 如果有Body,则也加上
# (仅限于mime == "application/x-www-form-urlencoded"的情况)
if body.is_a?(String) && !body.empty?
signing_str += body
end
### 生成数字签名
sign = HMAC::SHA1.new(secret_key).update(signing_str).digest
encoded_sign = Utils.urlsafe_base64_encode(sign)
### 生成管理授权凭证
acctoken = "#{access_key}:#{encoded_sign}"
### 返回管理授权凭证
return acctoken
end # generate_acctoken
def generate_uptoken(put_policy)
### 提取AK/SK信息
access_key = Config.settings[:access_key]
secret_key = Config.settings[:secret_key]
### 生成待签名字符串
encoded_put_policy = Utils.urlsafe_base64_encode(put_policy.to_json)
### 生成数字签名
sign = HMAC::SHA1.new(secret_key).update(encoded_put_policy).digest
encoded_sign = Utils.urlsafe_base64_encode(sign)
### 生成上传授权凭证
uptoken = "#{access_key}:#{encoded_sign}:#{encoded_put_policy}"
### 返回上传授权凭证
return uptoken
end # generate_uptoken
end # class << self
end # module Auth
+13 -2
View File
@@ -9,10 +9,21 @@ module Qiniu
Auth.request Config.settings[:rs_host] + '/buckets'
end # buckets
def mkbucket(bucket_name)
Auth.request Config.settings[:rs_host] + '/mkbucket/' + bucket_name
PRIVATE_BUCKET = 0
PUBLIC_BUCKET = 1
def mkbucket(bucket_name, is_public = PUBLIC_BUCKET)
Auth.request Config.settings[:rs_host] + '/mkbucket2/' + bucket_name + '/public/' + is_public.to_s
end # mkbucket
def make_a_private_bucket(bucket_name)
return mkbucket(bucket_name, PRIVATE_BUCKET)
end # make_a_private_bucket
def make_a_public_bucket(bucket_name)
return mkbucket(bucket_name, PUBLIC_BUCKET)
end # make_a_public_bucket
def stat(bucket, key)
Auth.request Config.settings[:rs_host] + '/stat/' + encode_entry_uri(bucket, key)
end # stat
+22
View File
@@ -1,4 +1,5 @@
# -*- encoding: utf-8 -*-
# vim: sw=2 ts=2
module Qiniu
module Storage
@@ -61,6 +62,27 @@ module Qiniu
Utils.http_request url, post_data
end # upload_with_token_2
### 授权举例
# put_policy.bucket | put_policy.key | key | 语义 | 授权
# :---------------- | :------------- | :------ | :--- | :---
# trivial_bucket | <nil> | <nil> | 新增 | 允许,最终key为1)使用put_policy.save_key生成的值或2)资源内容的Hash值
# trivial_bucket | <nil> | foo.txt | 新增 | 允许
# trivial_bucket | <nil> | bar.jpg | 新增 | 允许
# trivial_bucket | foo.txt | <nil> | 覆盖 | 允许,由SDK将put_policy.key赋值给key实现
# trivial_bucket | foo.txt | foo.txt | 覆盖 | 允许
# trivial_bucket | foo.txt | bar.jpg | 覆盖 | 禁止,put_policy.key与key不一致
def upload_with_put_policy(put_policy,
local_file,
key = nil,
x_vars = nil)
uptoken = Auth.generate_uptoken(put_policy)
if key.nil? then
key = put_policy.key
end
return upload_with_token_2(uptoken, local_file, key, x_vars)
end # upload_with_put_policy
private
def _generate_action_params(local_file,
bucket,
-21
View File
@@ -128,26 +128,5 @@ module Qiniu
File.open(filepath, "rb") { |f| Zlib.crc32 f.read }
end
def generate_qbox_signature(url, params, mime = nil)
access_key = Config.settings[:access_key]
secret_key = Config.settings[:secret_key]
uri = URI.parse(url)
signature = uri.path
query_string = uri.query
signature += '?' + query_string if !query_string.nil? && !query_string.empty?
signature += "\n"
if params.is_a?(Hash)
params_string = generate_query_string(params)
signature += params_string
end
if mime.is_a?(String) && mime == "application/x-www-form-urlencoded" && params.is_a?(String)
signature += params
end
hmac = HMAC::SHA1.new(secret_key)
hmac.update(signature)
encoded_digest = urlsafe_base64_encode(hmac.digest)
%Q(#{access_key}:#{encoded_digest})
end
end # module Utils
end # module Qiniu
+1 -1
View File
@@ -3,7 +3,7 @@
module Qiniu
module Version
MAJOR = 6
MINOR = 1
MINOR = 2
PATCH = 0
# Returns a version string by joining <tt>MAJOR</tt>, <tt>MINOR</tt>, and <tt>PATCH</tt> with <tt>'.'</tt>
#
+75
View File
@@ -0,0 +1,75 @@
# -*- encoding: utf-8 -*-
require 'spec_helper'
require 'qiniu/auth'
require 'qiniu/storage'
require 'digest/sha1'
module Qiniu
module Auth
describe Auth do
before :all do
@bucket = 'RubySDK-Test-Private'
@bucket = make_unique_bucket(@bucket)
### 尝试创建Bucket
result = Qiniu::Storage.make_a_private_bucket(@bucket)
puts result.inspect
end
after :all do
### 不删除Bucket以备下次使用
end
### 测试私有资源下载
context ".download_private_file" do
it "should works" do
### 生成Key
key = 'a_private_file'
key = make_unique_key_in_bucket(key)
puts "key=#{key}"
### 上传测试文件
pp = Auth::PutPolicy.new(@bucket, key)
code, data, raw_headers = Qiniu::Storage.upload_with_put_policy(
pp,
__FILE__
)
code.should == 200
puts data.inspect
puts raw_headers.inspect
### 获取下载地址
code, data = Qiniu::Storage.get(@bucket, key)
code.should == 200
puts data.inspect
url = data['url']
### 授权下载地址(不带参数)
download_url = Qiniu::Auth.authorize_download_url(url)
puts "download_url=#{download_url}"
result = RestClient.get(download_url)
result.code.should == 200
result.body.should_not be_empty
### 授权下载地址(带参数)
download_url = Qiniu::Auth.authorize_download_url(url + '?download/a.m3u8')
puts "download_url=#{download_url}"
result = RestClient.get(download_url)
result.code.should == 200
result.body.should_not be_empty
### 删除文件
code, data = Qiniu::Storage.delete(@bucket, key)
code.should == 200
puts data.inspect
end
end
end
end # module Storage
end # module Qiniu
+6 -8
View File
@@ -1,4 +1,5 @@
# -*- encoding: utf-8 -*-
# vim: sw=2 ts=2
require 'digest/sha1'
require 'spec_helper'
@@ -29,15 +30,12 @@ module Qiniu
end
### 准备数据
context ".upload_with_token_2" do
context ".prepare_file" do
it "should works" do
upopts = {:scope => @bucket, :expires_in => 3600, :endUser => "why404@gmail.com"}
uptoken = Qiniu.generate_upload_token(upopts)
code, data, raw_headers = Qiniu::Storage.upload_with_token_2(
uptoken,
__FILE__,
@key
pp = Auth::PutPolicy.new(@bucket, @key)
code, data, raw_headers = Qiniu::Storage.upload_with_put_policy(
pp,
__FILE__
)
code.should == 200
puts data.inspect
+44
View File
@@ -1,4 +1,5 @@
# -*- encoding: utf-8 -*-
# vim: sw=2 ts=2
require 'spec_helper'
require 'qiniu/auth'
@@ -126,6 +127,49 @@ module Qiniu
end
end
context ".upload_with_put_policy" do
it "should works" do
pp = Qiniu::Auth::PutPolicy.new(@bucket, @key)
pp.end_user = "why404@gmail.com"
puts 'put_policy=' + pp.to_json
code, data, raw_headers = Qiniu::Storage.upload_with_put_policy(
pp,
__FILE__,
@key + '-not-equal'
)
code.should_not == 200
puts data.inspect
puts raw_headers.inspect
code, data, raw_headers = Qiniu::Storage.upload_with_put_policy(
pp,
__FILE__,
@key
)
code.should == 200
puts data.inspect
puts raw_headers.inspect
end
end # .upload_with_put_policy
context ".stat" do
it "should exists" do
code, data = Qiniu::Storage.stat(@bucket, @key)
puts data.inspect
code.should == 200
end
end
context ".delete" do
it "should works" do
code, data = Qiniu::Storage.delete(@bucket, @key)
puts data.inspect
code.should == 200
end
end
### 测试断点续上传
context ".resumable_upload_with_token" do
it "should works" do