From 13509fced449e1a93c35a1521d6708f10af7624e Mon Sep 17 00:00:00 2001 From: Rupak Ganguly Date: Tue, 16 Oct 2012 21:48:50 -0400 Subject: [PATCH] Add grant, revoke and list methods for cross tenant object acls implementation. --- lib/fog/hp/models/storage/directories.rb | 5 +- lib/fog/hp/models/storage/directory.rb | 86 ++++++++++++++++++---- lib/fog/hp/storage.rb | 92 ++++++++++++++++++------ 3 files changed, 144 insertions(+), 39 deletions(-) diff --git a/lib/fog/hp/models/storage/directories.rb b/lib/fog/hp/models/storage/directories.rb index f3f70235c..29b462069 100644 --- a/lib/fog/hp/models/storage/directories.rb +++ b/lib/fog/hp/models/storage/directories.rb @@ -58,7 +58,10 @@ module Fog 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) + 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.files.merge_attributes(options) directory.files.instance_variable_set(:@loaded, true) diff --git a/lib/fog/hp/models/storage/directory.rb b/lib/fog/hp/models/storage/directory.rb index 972acc2a4..4336a1efe 100644 --- a/lib/fog/hp/models/storage/directory.rb +++ b/lib/fog/hp/models/storage/directory.rb @@ -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 + + def read_acl + @read_acl + end + + 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 - 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(', ')}]") + unless w_acl.nil? + @write_acl = @write_acl + w_acl + @write_acl.uniq! end - @acl = new_acl + 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?) diff --git a/lib/fog/hp/storage.rb b/lib/fog/hp/storage.rb index 8ba015aad..3c3617f00 100644 --- a/lib/fog/hp/storage.rb +++ b/lib/fog/hp/storage.rb @@ -53,34 +53,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)