@@ -44,25 +44,47 @@ class Handler < Seahorse::Client::Handler
4444 list_objects_v2 : 'READ' ,
4545 list_object_versions : 'READ' ,
4646 list_parts : 'READ' ,
47+ head_bucket : 'READ' ,
48+ get_object_attributes : 'READ' ,
4749 put_object : 'WRITE' ,
4850 put_object_acl : 'WRITE' ,
4951 delete_object : 'WRITE' ,
5052 abort_multipart_upload : 'WRITE' ,
5153 create_multipart_upload : 'WRITE' ,
5254 upload_part : 'WRITE' ,
53- complete_multipart_upload : 'WRITE'
55+ complete_multipart_upload : 'WRITE' ,
56+ delete_objects : 'WRITE' ,
57+ copy_object : 'READWRITE'
5458 } . freeze
5559
5660 def call ( context )
61+ provider = context . config . access_grants_credentials_provider
62+
5763 if access_grants_operation? ( context ) &&
58- !s3_express_endpoint? ( context )
64+ !s3_express_endpoint? ( context ) &&
65+ !credentials_head_bucket_call? ( provider )
5966 params = context [ :endpoint_params ]
6067 permission = PERMISSION_MAP [ context . operation_name ]
6168
62- provider = context . config . access_grants_credentials_provider
69+ key =
70+ case context . operation_name
71+ when :delete_objects
72+ delete_params = context . params [ :delete ]
73+ common_prefixes ( delete_params [ :objects ] . map { |o | o [ :key ] } )
74+ when :copy_object
75+ source_bucket , source_key = params [ :copy_source ] . split ( '/' , 2 )
76+ if params [ :bucket ] != source_bucket
77+ raise ArgumentError ,
78+ 'source and destination bucket must be the same'
79+ end
80+ common_prefixes ( [ params [ :key ] , source_key ] )
81+ else
82+ params [ :key ]
83+ end
84+
6385 credentials = provider . access_grants_credentials_for (
6486 bucket : params [ :bucket ] ,
65- key : params [ : key] ,
87+ key : key ,
6688 prefix : params [ :prefix ] ,
6789 permission : permission
6890 )
@@ -80,6 +102,12 @@ def with_metric(credentials, &block)
80102 Aws ::Plugins ::UserAgent . metric ( 'S3_ACCESS_GRANTS' , &block )
81103 end
82104
105+ # HeadBucket is a supported call. When fetching credentials,
106+ # this plugin is executed again, and becomes recursive.
107+ def credentials_head_bucket_call? ( provider )
108+ provider . instance_variable_get ( :@head_bucket_call )
109+ end
110+
83111 def access_grants_operation? ( context )
84112 params = context [ :endpoint_params ]
85113 params [ :bucket ] && PERMISSION_MAP [ context . operation_name ]
@@ -88,6 +116,42 @@ def access_grants_operation?(context)
88116 def s3_express_endpoint? ( context )
89117 context [ :endpoint_properties ] [ 'backend' ] == 'S3Express'
90118 end
119+
120+ # Return the common prefix of the keys, regardless of the delimiter.
121+ # For example, given keys ['foo/bar', 'foo/baz'], the common prefix
122+ # is 'foo/ba'.
123+ def common_prefixes ( keys )
124+ return '' if keys . empty?
125+
126+ first_key = keys [ 0 ]
127+ common_ancestor = first_key
128+ last_prefix = ''
129+ keys . each do |k |
130+ until common_ancestor . empty?
131+ break if k . start_with? ( common_ancestor )
132+
133+ last_index = common_ancestor . rindex ( '/' )
134+ return '' if last_index . nil?
135+
136+ last_prefix = common_ancestor [ ( last_index + 1 ) ..-1 ]
137+ common_ancestor = common_ancestor [ 0 ...last_index ]
138+ end
139+ end
140+ new_common_ancestor = "#{ common_ancestor } /#{ last_prefix } "
141+ keys . each do |k |
142+ until last_prefix . empty?
143+ break if k . start_with? ( new_common_ancestor )
144+
145+ last_prefix = last_prefix [ 0 ...-1 ]
146+ new_common_ancestor = "#{ common_ancestor } /#{ last_prefix } "
147+ end
148+ end
149+ if new_common_ancestor == "#{ first_key } /"
150+ first_key
151+ else
152+ new_common_ancestor
153+ end
154+ end
91155 end
92156
93157 def add_handlers ( handlers , config )
0 commit comments