1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00

Merge pull request #10 from SDK-CLI-Docs/feature/93_cross_tenant_acls

Feature/93 cross tenant acls
This commit is contained in:
Rupak Ganguly 2012-10-23 19:02:54 +00:00
commit 24d5dac17e
16 changed files with 731 additions and 81 deletions

View file

@ -20,8 +20,13 @@ module Fog
data = nil
message = nil
else
begin
data = MultiJson.decode(error.response.body)
message = data['message']
rescue MultiJson::DecodeError
data = error.response.body #### the body is not in JSON format so just return it as it is
message = data
end
end
new_error = super(error, message)
@ -33,6 +38,7 @@ module Fog
class InternalServerError < ServiceError; end
class Conflict < ServiceError; end
class NotFound < ServiceError; end
class Forbidden < ServiceError; end
class ServiceUnavailable < ServiceError; end
class BadRequest < ServiceError

View file

@ -15,24 +15,8 @@ module Fog
end
def head(key, options = {})
read_header = nil
write_header = nil
data = connection.head_container(key)
directory = new(:key => key)
for key, value in data.headers
if ['X-Container-Bytes-Used', 'X-Container-Object-Count'].include?(key)
directory.merge_attributes(key => value)
end
if key == 'X-Container-Read'
read_header = value
elsif key == 'X-Container-Write'
write_header = value
end
end
# set the acl on the directory based on the headers
if !(read_header.nil? && write_header.nil?)
directory.acl = connection.header_to_acl(read_header, write_header)
end
directory = create_directory(key, data)
# set the cdn state for the directory
directory.cdn_enabled?
@ -42,24 +26,9 @@ module Fog
end
def get(key, options = {})
read_header = nil
write_header = nil
data = connection.get_container(key, options)
directory = new(:key => key)
for key, value in data.headers
if ['X-Container-Bytes-Used', 'X-Container-Object-Count'].include?(key)
directory.merge_attributes(key => value)
end
if key == 'X-Container-Read'
read_header = value
elsif key == 'X-Container-Write'
write_header = value
end
end
# set the acl on the directory based on the headers
if !(read_header.nil? && write_header.nil?)
directory.acl = connection.header_to_acl(read_header, write_header)
end
directory = create_directory(key, data)
# set the files for the directory
directory.files.merge_attributes(options)
directory.files.instance_variable_set(:@loaded, true)
data.body.each do |file|
@ -73,6 +42,32 @@ module Fog
nil
end
private
def create_directory(key, data)
read_header = nil
write_header = nil
directory = new(:key => key)
for key, value in data.headers
if ['X-Container-Bytes-Used', 'X-Container-Object-Count'].include?(key)
directory.merge_attributes(key => value)
end
if key == 'X-Container-Read'
read_header = value
elsif key == 'X-Container-Write'
write_header = value
end
end
# set the acl on the directory based on the headers
if !(read_header.nil? && write_header.nil?)
read_acl, write_acl = connection.header_to_perm_acl(read_header, write_header)
# do not want to expose the read_acl and write_acl as writable attributes
directory.instance_variable_set(:@read_acl, read_acl)
directory.instance_variable_set(:@write_acl, write_acl)
end
directory
end
end
end

View file

@ -12,15 +12,70 @@ module Fog
attribute :bytes, :aliases => 'X-Container-Bytes-Used'
attribute :count, :aliases => 'X-Container-Object-Count'
def acl=(new_acl)
if new_acl.nil?
new_acl = "private"
def initialize(attributes = {})
@read_acl = []
@write_acl = []
super
end
valid_acls = ['private', 'public-read', 'public-write', 'public-read-write']
unless valid_acls.include?(new_acl)
raise ArgumentError.new("acl must be one of [#{valid_acls.join(', ')}]")
def read_acl
@read_acl
end
@acl = new_acl
def write_acl
@write_acl
end
def can_read?(user)
return false if @read_acl.nil?
list_users_with_read.include?(user)
end
def can_write?(user)
return false if @write_acl.nil?
list_users_with_write.include?(user)
end
def can_read_write?(user)
can_read?(user) && can_write?(user)
end
def list_users_with_read
users = []
users = @read_acl.map {|acl| acl.split(':')[1]} unless @read_acl.nil?
return users
end
def list_users_with_write
users = []
users = @write_acl.map {|acl| acl.split(':')[1]} unless @write_acl.nil?
return users
end
def grant(perm, users=[])
r_acl, w_acl = connection.perm_to_acl(perm, users)
unless r_acl.nil?
@read_acl = @read_acl + r_acl
@read_acl.uniq!
end
unless w_acl.nil?
@write_acl = @write_acl + w_acl
@write_acl.uniq!
end
true
end
def revoke(perm, users=[])
r_acl, w_acl = connection.perm_to_acl(perm, users)
unless r_acl.nil?
@read_acl = @read_acl - r_acl
@read_acl.uniq!
end
unless w_acl.nil?
@write_acl = @write_acl - w_acl
@write_acl.uniq!
end
true
end
def destroy
@ -50,18 +105,20 @@ module Fog
def public=(new_public)
if new_public
@acl = 'public-read'
self.grant("pr")
else
@acl = 'private'
self.revoke("pr")
end
@public = new_public
end
def public?
if @acl.nil?
if @read_acl.empty?
false
elsif @read_acl.include?(".r:*")
true
else
@acl == 'public-read'
false
end
end
@ -70,7 +127,7 @@ module Fog
@public_url ||= begin
begin response = connection.head_container(key)
# escape the key to cover for special char. in container names
url = "#{connection.url}/#{Fog::HP.escape(key)}"
url = connection.public_url(key)
rescue Fog::Storage::HP::NotFound => err
nil
end
@ -135,9 +192,8 @@ module Fog
def save
requires :key
options = {}
if @acl
options.merge!(connection.acl_to_header(@acl))
end
# write out the acls into the headers before save
options.merge!(connection.perm_acl_to_header(@read_acl, @write_acl))
connection.put_container(key, options)
# Added an extra check to see if CDN is enabled for the container
if (!connection.cdn.nil? && connection.cdn.enabled?)

View file

@ -0,0 +1,49 @@
require 'fog/core/collection'
require 'fog/hp/models/storage/shared_directory'
module Fog
module Storage
class HP
class SharedDirectories < Fog::Collection
model Fog::Storage::HP::SharedDirectory
def head(url)
data = connection.head_shared_container(url)
shared_directory = new(:url => url)
for key, value in data.headers
if ['X-Container-Bytes-Used', 'X-Container-Object-Count'].include?(key)
shared_directory.merge_attributes(key => value)
end
end
shared_directory
rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden
nil
end
def get(url)
data = connection.get_shared_container(url)
shared_directory = new(:url => url)
for key, value in data.headers
if ['X-Container-Bytes-Used', 'X-Container-Object-Count'].include?(key)
shared_directory.merge_attributes(key => value)
end
end
# set the files for the directory
shared_directory.files.instance_variable_set(:@loaded, true)
data.body.each do |file|
shared_directory.files << shared_directory.files.new(file)
end
shared_directory
rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden
nil
end
end
end
end
end

View file

@ -0,0 +1,28 @@
require 'fog/core/model'
require 'fog/hp/models/storage/shared_files'
module Fog
module Storage
class HP
class SharedDirectory < Fog::Model
identity :url
attribute :bytes, :aliases => 'X-Container-Bytes-Used'
attribute :count, :aliases => 'X-Container-Object-Count'
def files
@files ||= begin
Fog::Storage::HP::SharedFiles.new(
:shared_directory => self,
:connection => connection
)
end
end
end
end
end
end

View file

@ -0,0 +1,57 @@
require 'fog/core/model'
module Fog
module Storage
class HP
class SharedFile < Fog::Model
identity :key, :aliases => 'name'
attribute :url
attribute :content_length, :aliases => ['bytes', 'Content-Length'], :type => :integer
attribute :content_type, :aliases => ['content_type', 'Content-Type']
attribute :etag, :aliases => ['hash', 'Etag']
attribute :last_modified, :aliases => ['last_modified', 'Last-Modified'], :type => :time
def url
"#{self.collection.shared_directory.url}/#{key}"
end
def body
attributes[:body] ||= if last_modified
collection.get(identity).body
else
''
end
end
def body=(new_body)
attributes[:body] = new_body
end
def shared_directory
@shared_directory
end
def save(options = {})
requires :shared_directory, :key
options['Content-Type'] = content_type if content_type
data = connection.put_shared_object(shared_directory.url, key, body, options)
merge_attributes(data.headers)
self.content_length = Fog::Storage.get_body_size(body)
true
rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden
false
end
private
def shared_directory=(new_shared_directory)
@shared_directory = new_shared_directory
end
end
end
end
end

View file

@ -0,0 +1,60 @@
require 'fog/core/collection'
require 'fog/hp/models/storage/shared_file'
module Fog
module Storage
class HP
class SharedFiles < Fog::Collection
attribute :shared_directory
model Fog::Storage::HP::SharedFile
def all
requires :shared_directory
parent = shared_directory.collection.get(shared_directory.url)
if parent
load(parent.files.map {|file| file.attributes})
else
nil
end
rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden
nil
end
def get(key, &block)
requires :shared_directory
shared_object_url = "#{shared_directory.url}/#{key}"
data = connection.get_shared_object(shared_object_url, &block)
file_data = data.headers.merge({
:body => data.body,
:key => key
})
new(file_data)
rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden
nil
end
def head(key)
requires :shared_directory
shared_object_url = "#{shared_directory.url}/#{key}"
data = connection.head_shared_object(shared_object_url)
file_data = data.headers.merge({
:key => key
})
new(file_data)
rescue Fog::Storage::HP::NotFound, Fog::HP::Errors::Forbidden
nil
end
def new(attributes = {})
requires :shared_directory
super({ :shared_directory => shared_directory }.merge!(attributes))
end
end
end
end
end

View file

@ -0,0 +1,75 @@
module Fog
module Storage
class HP
class Real
# Get details for a shared container
#
# ==== Parameters
# * shared_container_url<~String> - Url of the shared container
# * options<~String>:
# * 'limit'<~String> - Maximum number of objects to return
# * 'marker'<~String> - Only return objects whose name is greater than marker
# * 'prefix'<~String> - Limits results to those starting with prefix
# * 'path'<~String> - Return objects nested in the pseudo path
#
# ==== Returns
# * response<~Excon::Response>:
# * headers<~Hash>:
# * 'X-Container-Object-Count'<~String> - Count of objects in container
# * 'X-Container-Bytes-Used'<~String> - Bytes used
# * 'X-Trans-Id'<~String> - Trans Id
# * body<~Array>:
# * item<~Hash>:
# * 'bytes'<~String> - Size of object
# * 'content_type'<~String> Content-Type of object
# * 'hash'<~String> - Hash of object (etag?)
# * 'last_modified'<~String> - Last modified timestamp
# * 'name'<~String> - Name of object
def get_shared_container(shared_container_url, options = {})
options = options.reject {|key, value| value.nil?}
# split up the shared container url
uri = URI.parse(shared_container_url)
path = uri.path
response = shared_request(
:expects => 200,
:method => 'GET',
:path => path,
:query => {'format' => 'json'}.merge!(options)
)
response
end
end
class Mock # :nodoc:all
def get_shared_container(shared_container_url, options = {})
response = Excon::Response.new
data = {
'name' => Fog::Mock.random_letters(10),
'hash' => Fog::HP::Mock.etag,
'bytes' => 11,
'content_type' => "text/plain",
'last_modified' => Time.now
}
response.status = 200
response.body = [data]
response.headers = {
'X-Container-Object-Count' => 1,
'X-Container-Bytes-Used' => 11,
'Accept-Ranges' => 'bytes',
'Content-Type' => "application/json",
'Content-Length' => 11,
'X-Trans-Id' => "tx#{Fog::Mock.random_hex(32)}"
}
response
end
end
end
end
end

View file

@ -0,0 +1,67 @@
module Fog
module Storage
class HP
class Real
# Get details for a shared object
#
# ==== Parameters
# * shared_object_url<~String> - Url of the shared object
#
def get_shared_object(shared_object_url, &block)
# split up the shared object url
uri = URI.parse(shared_object_url)
path = uri.path
if block_given?
response = shared_request(
:response_block => block,
:expects => 200,
:method => 'GET',
:path => path
)
else
response = shared_request({
:block => block,
:expects => 200,
:method => 'GET',
:path => path
}, false, &block)
end
response
end
end
class Mock # :nodoc:all
def get_shared_object(shared_object_url, &block)
response = Excon::Response.new
response.status = 200
response.headers = {
'Last-Modified' => Date.today.rfc822,
'Etag' => Fog::HP::Mock.etag,
'Accept-Ranges' => 'bytes',
'Content-Type' => "text/plain",
'Content-Length' => 11,
'X-Trans-Id' => "tx#{Fog::Mock.random_hex(32)}"
}
unless block_given?
response.body = "This is a sample text.\n"
else
data = StringIO.new("This is a sample text.\n")
remaining = data.length
while remaining > 0
chunk = data.read([remaining, Excon::CHUNK_SIZE].min)
block.call(chunk)
remaining -= Excon::CHUNK_SIZE
end
end
response
end
end
end
end
end

View file

@ -30,6 +30,7 @@ module Fog
def head_container(container_name)
response = get_container(container_name)
response.body = nil
response.status = 204
response
end

View file

@ -11,7 +11,7 @@ module Fog
#
def head_object(container, object)
response = request({
:expects => 200,
:expects => 200, # should be 204
:method => 'HEAD',
:path => "#{Fog::HP.escape(container)}/#{Fog::HP.escape(object)}"
}, false)
@ -25,6 +25,7 @@ module Fog
def head_object(container_name, object_name, options = {})
response = get_object(container_name, object_name, options)
response.body = nil
response.status = 200 # should be 204
response
end

View file

@ -0,0 +1,45 @@
module Fog
module Storage
class HP
class Real
# List number of objects and total bytes stored for a shared container
#
# ==== Parameters
# * shared_container_url<~String> - Url of the shared container
#
# ==== Returns
# * response<~Excon::Response>:
# * headers<~Hash>:
# * 'X-Container-Object-Count'<~String> - Count of containers
# * 'X-Container-Bytes-Used'<~String> - Bytes used
def head_shared_container(shared_container_url)
# split up the shared container url
uri = URI.parse(shared_container_url)
path = uri.path
response = shared_request(
:expects => 204,
:method => 'HEAD',
:path => path,
:query => {'format' => 'json'}
)
response
end
end
class Mock # :nodoc:all
def head_shared_container(shared_container_url)
response = get_shared_container(shared_container_url)
response.body = nil
response.status = 204
response
end
end
end
end
end

View file

@ -0,0 +1,39 @@
module Fog
module Storage
class HP
class Real
# Get headers for shared object
#
# ==== Parameters
# * * shared_object_url<~String> - Url of the shared object
#
def head_shared_object(shared_object_url)
# split up the shared object url
uri = URI.parse(shared_object_url)
path = uri.path
response = shared_request({
:expects => 200, # should be 204
:method => 'HEAD',
:path => path
}, false)
response
end
end
class Mock # :nodoc:all
def head_shared_object(shared_object_url)
response = get_shared_object(shared_object_url)
response.body = nil
response.status = 200 # should be 204
response
end
end
end
end
end

View file

@ -22,16 +22,16 @@ module Fog
class Mock # :nodoc:all
def put_container(container_name, options = {})
acl = options['X-Container-Read'] || 'private'
if !['private', 'public-read'].include?(acl)
#raise Excon::Errors::BadRequest.new('invalid X-Container-Read')
else
self.data[:acls][:container][container_name] = self.class.acls(acl)
read_h = options['X-Container-Read'] || ''
write_h = options['X-Container-Write'] || ''
unless options
read_acl, write_acl = self.class.header_to_perm_acl(read_h, write_h)
self.data[:acls][:container][container_name] = {:read_acl => read_acl, :write_acl => write_acl}
end
response = Excon::Response.new
container = {
:objects => {},
:objects => {}
}
if self.data[:containers][container_name]
response.status = 202

View file

@ -0,0 +1,85 @@
module Fog
module Storage
class HP
class Real
# Create a new object in a shared container
#
# ==== Parameters
# * shared_container_url<~String> - Shared url for the container
# * object<~String> - Name of the object
# * options<~Hash> - header options
#
def put_shared_object(shared_container_url, object_name, data, options = {}, &block)
# split up the shared object url
uri = URI.parse(shared_container_url)
path = uri.path
data = Fog::Storage.parse_data(data)
headers = data[:headers].merge!(options)
if block_given?
headers['Transfer-Encoding'] = 'chunked'
headers.delete('Content-Length')
return shared_request(
:request_block => block,
:expects => 201,
:headers => headers,
:method => 'PUT',
:path => "#{path}/#{Fog::HP.escape(object_name)}"
)
end
if headers.has_key?('Transfer-Encoding')
headers.delete('Content-Length')
end
response = shared_request(
:body => data[:body],
:expects => 201,
:headers => headers,
:method => 'PUT',
:path => "#{path}/#{Fog::HP.escape(object_name)}"
)
response
end
end
class Mock # :nodoc:all
def put_shared_object(shared_container_url, object_name, data, options = {}, &block)
response = Excon::Response.new
data = Fog::Storage.parse_data(data)
unless data[:body].is_a?(String)
data[:body] = data[:body].read
end
response.status = 201
object = {
:body => data[:body],
'Content-Type' => options['Content-Type'] || data[:headers]['Content-Type'],
'ETag' => Fog::HP::Mock.etag,
'Key' => object_name,
'Date' => Fog::Time.now.to_date_header,
'Content-Length' => options['Content-Length'] || data[:headers]['Content-Length'],
}
for key, value in options
case key
when 'Cache-Control', 'Content-Disposition', 'Content-Encoding', 'Content-MD5', 'Expires', /^X-Object-Meta-/
object[key] = value
end
end
response.headers = {
'Content-Length' => object['Content-Length'],
'Content-Type' => object['Content-Type'],
'ETag' => object['ETag'],
'Date' => object['Date']
}
response
end
end
end
end
end

View file

@ -11,8 +11,12 @@ module Fog
model_path 'fog/hp/models/storage'
model :directory
collection :directories
model :shared_directory
collection :shared_directories
model :file
collection :files
model :shared_file
collection :shared_files
request_path 'fog/hp/requests/storage'
request :delete_container
@ -21,11 +25,16 @@ module Fog
request :get_containers
request :get_object
request :get_object_temp_url
request :get_shared_container
request :get_shared_object
request :head_container
request :head_containers
request :head_object
request :head_shared_container
request :head_shared_object
request :put_container
request :put_object
request :put_shared_object
module Utils
@ -53,34 +62,80 @@ module Fog
"#{@scheme}://#{@host}:#{@port}#{@path}"
end
def acl_to_header(acl)
def public_url(container=nil, object=nil)
public_url = nil
unless container.nil?
if object.nil?
# return container public url
public_url = "#{url}/#{Fog::HP.escape(container)}"
else
# return object public url
public_url = "#{url}/#{Fog::HP.escape(container)}/#{Fog::HP.escape(object)}"
end
end
public_url
end
def perm_to_acl(perm, users=[])
read_perm_acl = []
write_perm_acl = []
valid_public_perms = ['pr', 'pw', 'prw']
valid_account_perms = ['r', 'w', 'rw']
valid_perms = valid_public_perms + valid_account_perms
unless valid_perms.include?(perm)
raise ArgumentError.new("permission must be one of [#{valid_perms.join(', ')}]")
end
# tackle the public access differently
if valid_public_perms.include?(perm)
case perm
when "pr"
read_perm_acl = [".r:*",".rlistings"]
when "pw"
write_perm_acl = ["*"]
when "prw"
read_perm_acl = [".r:*",".rlistings"]
write_perm_acl = ["*"]
end
elsif valid_account_perms.include?(perm)
# tackle the user access differently
unless (users.nil? || users.empty?)
# return the correct acls
tenant_id = "*" # this might change later
acl_array = users.map { |u| "#{tenant_id}:#{u}" }
#acl_string = acl_array.join(',')
case perm
when "r"
read_perm_acl = acl_array
when "w"
write_perm_acl = acl_array
when "rw"
read_perm_acl = acl_array
write_perm_acl = acl_array
end
end
end
return read_perm_acl, write_perm_acl
end
def perm_acl_to_header(read_perm_acl, write_perm_acl)
header = {}
case acl
when "private"
header['X-Container-Read'] = ""
header['X-Container-Write'] = ""
when "public-read"
header['X-Container-Read'] = ".r:*,.rlistings"
when "public-write"
header['X-Container-Write'] = "*"
when "public-read-write"
header['X-Container-Read'] = ".r:*,.rlistings"
header['X-Container-Write'] = "*"
if read_perm_acl.nil? && write_perm_acl.nil?
header = {'X-Container-Read' => "", 'X-Container-Write' => ""}
elsif !read_perm_acl.nil? && write_perm_acl.nil?
header = {'X-Container-Read' => "#{read_perm_acl.join(',')}", 'X-Container-Write' => ""}
elsif read_perm_acl.nil? && !write_perm_acl.nil?
header = {'X-Container-Read' => "", 'X-Container-Write' => "#{write_perm_acl.join(',')}"}
elsif !read_perm_acl.nil? && !write_perm_acl.nil?
header = {'X-Container-Read' => "#{read_perm_acl.join(',')}", 'X-Container-Write' => "#{write_perm_acl.join(',')}"}
end
header
end
def header_to_acl(read_header=nil, write_header=nil)
acl = nil
if read_header.nil? && write_header.nil?
acl = nil
elsif !read_header.nil? && read_header.include?(".r:*") && write_header.nil?
acl = "public-read"
elsif !write_header.nil? && write_header.include?("*") && read_header.nil?
acl = "public-write"
elsif !read_header.nil? && read_header.include?(".r:*") && !write_header.nil? && write_header.include?("*")
acl = "public-read-write"
end
def header_to_perm_acl(read_header=nil, write_header=nil)
read_h, write_h = nil
read_h = read_header.split(',') unless read_header.nil?
write_h = write_header.split(',') unless write_header.nil?
return read_h, write_h
end
def generate_object_temp_url(container, object, expires_secs, method)
@ -230,6 +285,37 @@ module Fog
response
end
# this request is used only for get_shared_container and get_shared_object calls
def shared_request(params, parse_json = true, &block)
begin
response = @connection.request(params.merge!({
:headers => {
'Content-Type' => 'application/json',
'X-Auth-Token' => @auth_token
}.merge!(params[:headers] || {}),
:host => @host,
:path => "#{params[:path]}",
}), &block)
rescue Excon::Errors::HTTPStatusError => error
raise case error
when Excon::Errors::NotFound
Fog::Storage::HP::NotFound.slurp(error)
when Excon::Errors::Forbidden
Fog::HP::Errors::Forbidden.slurp(error)
else
error
end
end
if !response.body.empty? && parse_json && response.headers['Content-Type'] =~ %r{application/json}
begin
response.body = MultiJson.decode(response.body)
rescue MultiJson::DecodeError => error
response.body #### the body is not in JSON format so just return it as it is
end
end
response
end
end
end
end