Skip to content

Commit 6262506

Browse files
committed
[DelegationSas]support directory sas & add feature for delegation sas
1 parent a7907db commit 6262506

28 files changed

Lines changed: 636 additions & 167 deletions

sdk/storage/azure-storage-blob/azure/storage/blob/_download.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ def readall(self):
457457
"""Download the contents of this blob.
458458
459459
This operation is blocking until all data is downloaded.
460+
460461
:rtype: bytes or str
461462
"""
462463
stream = BytesIO()

sdk/storage/azure-storage-blob/azure/storage/blob/_shared/shared_access_signature.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ class QueryStringConstants(object):
3939
SIGNED_KEY_SERVICE = 'sks'
4040
SIGNED_KEY_VERSION = 'skv'
4141

42+
# for ADLS
43+
SIGNED_AUTHORIZED_OID = 'saoid'
44+
SIGNED_UNAUTHORIZED_OID = 'suoid'
45+
SIGNED_CORRELATION_ID = 'scid'
46+
SIGNED_DIRECTORY_DEPTH = 'sdd'
47+
4248
@staticmethod
4349
def to_list():
4450
return [
@@ -68,6 +74,11 @@ def to_list():
6874
QueryStringConstants.SIGNED_KEY_EXPIRY,
6975
QueryStringConstants.SIGNED_KEY_SERVICE,
7076
QueryStringConstants.SIGNED_KEY_VERSION,
77+
# for ADLS
78+
QueryStringConstants.SIGNED_AUTHORIZED_OID,
79+
QueryStringConstants.SIGNED_UNAUTHORIZED_OID,
80+
QueryStringConstants.SIGNED_CORRELATION_ID,
81+
QueryStringConstants.SIGNED_DIRECTORY_DEPTH,
7182
]
7283

7384

sdk/storage/azure-storage-blob/azure/storage/blob/_shared_access_signature.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def generate_blob(self, container_name, blob_name, snapshot=None, version_id=Non
5555
expiry=None, start=None, policy_id=None, ip=None, protocol=None,
5656
cache_control=None, content_disposition=None,
5757
content_encoding=None, content_language=None,
58-
content_type=None):
58+
content_type=None, **kwargs):
5959
'''
6060
Generates a shared access signature for the blob or one of its snapshots.
6161
Use the returned signature with the sas_token parameter of any BlobService.
@@ -126,12 +126,14 @@ def generate_blob(self, container_name, blob_name, snapshot=None, version_id=Non
126126

127127
resource = 'bs' if snapshot else 'b'
128128
resource = 'bv' if version_id else resource
129+
resource = 'd' if kwargs.pop("is_directory", None) else resource
129130
sas.add_resource(resource)
130131

131132
sas.add_timestamp(snapshot or version_id)
132133
sas.add_override_response_headers(cache_control, content_disposition,
133134
content_encoding, content_language,
134135
content_type)
136+
sas.add_info_for_hns_account(**kwargs)
135137
sas.add_resource_signature(self.account_name, self.account_key, resource_path,
136138
user_delegation_key=self.user_delegation_key)
137139

@@ -141,7 +143,7 @@ def generate_container(self, container_name, permission=None, expiry=None,
141143
start=None, policy_id=None, ip=None, protocol=None,
142144
cache_control=None, content_disposition=None,
143145
content_encoding=None, content_language=None,
144-
content_type=None):
146+
content_type=None, **kwargs):
145147
'''
146148
Generates a shared access signature for the container.
147149
Use the returned signature with the sas_token parameter of any BlobService.
@@ -206,6 +208,7 @@ def generate_container(self, container_name, permission=None, expiry=None,
206208
sas.add_override_response_headers(cache_control, content_disposition,
207209
content_encoding, content_language,
208210
content_type)
211+
sas.add_info_for_hns_account(**kwargs)
209212
sas.add_resource_signature(self.account_name, self.account_key, container_name,
210213
user_delegation_key=self.user_delegation_key)
211214
return sas.get_token()
@@ -216,6 +219,12 @@ class _BlobSharedAccessHelper(_SharedAccessHelper):
216219
def add_timestamp(self, timestamp):
217220
self._add_query(BlobQueryStringConstants.SIGNED_TIMESTAMP, timestamp)
218221

222+
def add_info_for_hns_account(self, **kwargs):
223+
self._add_query(QueryStringConstants.SIGNED_DIRECTORY_DEPTH, kwargs.pop('sdd', None))
224+
self._add_query(QueryStringConstants.SIGNED_AUTHORIZED_OID, kwargs.pop('preauthorized_agent_object_id', None))
225+
self._add_query(QueryStringConstants.SIGNED_UNAUTHORIZED_OID, kwargs.pop('agent_object_id', None))
226+
self._add_query(QueryStringConstants.SIGNED_CORRELATION_ID, kwargs.pop('correlation_id', None))
227+
219228
def get_value_to_append(self, query):
220229
return_value = self.query_dict.get(query) or ''
221230
return return_value + '\n'
@@ -249,7 +258,10 @@ def add_resource_signature(self, account_name, account_key, path, user_delegatio
249258
self.get_value_to_append(QueryStringConstants.SIGNED_KEY_START) +
250259
self.get_value_to_append(QueryStringConstants.SIGNED_KEY_EXPIRY) +
251260
self.get_value_to_append(QueryStringConstants.SIGNED_KEY_SERVICE) +
252-
self.get_value_to_append(QueryStringConstants.SIGNED_KEY_VERSION))
261+
self.get_value_to_append(QueryStringConstants.SIGNED_KEY_VERSION) +
262+
self.get_value_to_append(QueryStringConstants.SIGNED_AUTHORIZED_OID) +
263+
self.get_value_to_append(QueryStringConstants.SIGNED_UNAUTHORIZED_OID) +
264+
self.get_value_to_append(QueryStringConstants.SIGNED_CORRELATION_ID))
253265
else:
254266
string_to_sign += self.get_value_to_append(QueryStringConstants.SIGNED_IDENTIFIER)
255267

sdk/storage/azure-storage-blob/tests/test_blob_access_conditions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ def test_set_blob_properties_with_if_unmodified_fail(self, resource_group, locat
621621
# Assert
622622
self.assertEqual(StorageErrorCode.condition_not_met, e.exception.error_code)
623623

624+
@pytest.mark.playback_test_only
624625
@GlobalStorageAccountPreparer()
625626
def test_get_properties_last_access_time(self, resource_group, location, storage_account, storage_account_key):
626627
bsc = BlobServiceClient(self.account_url(storage_account, "blob"), storage_account_key,

sdk/storage/azure-storage-blob/tests/test_container.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,7 @@ def test_list_names(self, resource_group, location, storage_account, storage_acc
827827

828828
self.assertEqual(blobs, ['blob1', 'blob2'])
829829

830+
@pytest.mark.playback_test_only
830831
@GlobalStorageAccountPreparer()
831832
def test_list_blobs_contains_last_access_time(self, resource_group, location, storage_account, storage_account_key):
832833
bsc = BlobServiceClient(self.account_url(storage_account, "blob"), storage_account_key)

sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_data_lake_directory_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def rename_directory(self, new_name, # type: str
304304
"""
305305
new_name = new_name.strip('/')
306306
new_file_system = new_name.split('/')[0]
307-
new_path_and_token = new_name[len(new_file_system):].split('?')
307+
new_path_and_token = new_name[len(new_file_system):].strip('/').split('?')
308308
new_path = new_path_and_token[0]
309309
try:
310310
new_dir_sas = new_path_and_token[1] or self._query_str.strip('?')

sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_data_lake_file_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ def rename_file(self, new_name, # type: str
661661
"""
662662
new_name = new_name.strip('/')
663663
new_file_system = new_name.split('/')[0]
664-
new_path_and_token = new_name[len(new_file_system):].split('?')
664+
new_path_and_token = new_name[len(new_file_system):].strip('/').split('?')
665665
new_path = new_path_and_token[0]
666666
try:
667667
new_file_sas = new_path_and_token[1] or self._query_str.strip('?')

sdk/storage/azure-storage-file-datalake/azure/storage/filedatalake/_models.py

Lines changed: 180 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from azure.storage.blob import ResourceTypes as BlobResourceTypes
1313
from azure.storage.blob import UserDelegationKey as BlobUserDelegationKey
1414
from azure.storage.blob import ContentSettings as BlobContentSettings
15-
from azure.storage.blob import ContainerSasPermissions, BlobSasPermissions
1615
from azure.storage.blob import AccessPolicy as BlobAccessPolicy
1716
from azure.storage.blob import DelimitedTextDialect as BlobDelimitedTextDialect
1817
from azure.storage.blob import DelimitedJsonDialect as BlobDelimitedJSON
@@ -294,7 +293,7 @@ def __init__(self, read=False, write=False, delete=False, list=False, # pylint:
294293
)
295294

296295

297-
class FileSystemSasPermissions(ContainerSasPermissions):
296+
class FileSystemSasPermissions(object):
298297
"""FileSystemSasPermissions class to be used with the
299298
:func:`~azure.storage.filedatalake.generate_file_system_sas` function.
300299
@@ -308,14 +307,57 @@ class FileSystemSasPermissions(ContainerSasPermissions):
308307
List paths in the file system.
309308
"""
310309

311-
def __init__(self, read=False, write=False, delete=False, list=False # pylint: disable=redefined-builtin
312-
):
313-
super(FileSystemSasPermissions, self).__init__(
314-
read=read, write=write, delete=delete, list=list
315-
)
316-
310+
def __init__(self, read=False, write=False, delete=False, list=False, # pylint: disable=redefined-builtin
311+
**kwargs):
312+
self.read = read
313+
self.write = write
314+
self.delete = delete
315+
self.list = list
316+
self.move = kwargs.pop('move', None)
317+
self.execute = kwargs.pop('execute', None)
318+
self.manage_ownership = kwargs.pop('manage_ownership', None)
319+
self.manage_access_control = kwargs.pop('manage_access_control', None)
320+
self._str = (('r' if self.read else '') +
321+
('w' if self.write else '') +
322+
('d' if self.delete else '') +
323+
('l' if self.list else '') +
324+
('m' if self.move else '') +
325+
('e' if self.execute else '') +
326+
('o' if self.manage_ownership else '') +
327+
('p' if self.manage_access_control else ''))
328+
329+
def __str__(self):
330+
return self._str
317331

318-
class DirectorySasPermissions(BlobSasPermissions):
332+
@classmethod
333+
def from_string(cls, permission):
334+
"""Create a FileSystemSasPermissions from a string.
335+
336+
To specify read, write, or delete permissions you need only to
337+
include the first letter of the word in the string. E.g. For read and
338+
write permissions, you would provide a string "rw".
339+
340+
:param str permission: The string which dictates the read, add, create,
341+
write, or delete permissions.
342+
:return: A FileSystemSasPermissions object
343+
:rtype: ~azure.storage.fildatalake.FileSystemSasPermissions
344+
"""
345+
p_read = 'r' in permission
346+
p_write = 'w' in permission
347+
p_delete = 'd' in permission
348+
p_list = 'l' in permission
349+
p_move = 'm' in permission
350+
p_execute = 'e' in permission
351+
p_manage_ownership = 'o' in permission
352+
p_manage_access_control = 'p' in permission
353+
354+
parsed = cls(read=p_read, write=p_write, delete=p_delete,
355+
list=p_list, move=p_move, execute=p_execute, manage_ownership=p_manage_ownership,
356+
manage_access_control=p_manage_access_control)
357+
return parsed
358+
359+
360+
class DirectorySasPermissions(object):
319361
"""DirectorySasPermissions class to be used with the
320362
:func:`~azure.storage.filedatalake.generate_directory_sas` function.
321363
@@ -327,17 +369,77 @@ class DirectorySasPermissions(BlobSasPermissions):
327369
Create or write content, properties, metadata. Lease the directory.
328370
:param bool delete:
329371
Delete the directory.
372+
:keyword bool list:
373+
List any files in the directory. Implies Execute.
374+
:keyword bool move:
375+
Move any file in the directory to a new location.
376+
Note the move operation can optionally be restricted to the child file or directory owner or
377+
the parent directory owner if the saoid parameter is included in the token and the sticky bit is set
378+
on the parent directory.
379+
:keyword bool execute:
380+
Get the status (system defined properties) and ACL of any file in the directory.
381+
If the caller is the owner, set access control on any file in the directory.
382+
:keyword bool manage_ownership:
383+
Allows the user to set owner, owning group, or act as the owner when renaming or deleting a file or directory
384+
within a folder that has the sticky bit set.
385+
:keyword bool manage_access_control:
386+
Allows the user to set permissions and POSIX ACLs on files and directories.
330387
"""
331388

332389
def __init__(self, read=False, create=False, write=False,
333-
delete=False):
334-
super(DirectorySasPermissions, self).__init__(
335-
read=read, create=create, write=write,
336-
delete=delete
337-
)
390+
delete=False, **kwargs):
391+
self.read = read
392+
self.create = create
393+
self.write = write
394+
self.delete = delete
395+
self.list = kwargs.pop('list', None)
396+
self.move = kwargs.pop('move', None)
397+
self.execute = kwargs.pop('execute', None)
398+
self.manage_ownership = kwargs.pop('manage_ownership', None)
399+
self.manage_access_control = kwargs.pop('manage_access_control', None)
400+
self._str = (('r' if self.read else '') +
401+
('c' if self.create else '') +
402+
('w' if self.write else '') +
403+
('d' if self.delete else '') +
404+
('l' if self.list else '') +
405+
('m' if self.move else '') +
406+
('e' if self.execute else '') +
407+
('o' if self.manage_ownership else '') +
408+
('p' if self.manage_access_control else ''))
409+
410+
def __str__(self):
411+
return self._str
338412

339-
340-
class FileSasPermissions(BlobSasPermissions):
413+
@classmethod
414+
def from_string(cls, permission):
415+
"""Create a DirectorySasPermissions from a string.
416+
417+
To specify read, create, write, or delete permissions you need only to
418+
include the first letter of the word in the string. E.g. For read and
419+
write permissions, you would provide a string "rw".
420+
421+
:param str permission: The string which dictates the read, add, create,
422+
write, or delete permissions.
423+
:return: A DirectorySasPermissions object
424+
:rtype: ~azure.storage.filedatalake.DirectorySasPermissions
425+
"""
426+
p_read = 'r' in permission
427+
p_create = 'c' in permission
428+
p_write = 'w' in permission
429+
p_delete = 'd' in permission
430+
p_list = 'l' in permission
431+
p_move = 'm' in permission
432+
p_execute = 'e' in permission
433+
p_manage_ownership = 'o' in permission
434+
p_manage_access_control = 'p' in permission
435+
436+
parsed = cls(read=p_read, create=p_create, write=p_write, delete=p_delete,
437+
list=p_list, move=p_move, execute=p_execute, manage_ownership=p_manage_ownership,
438+
manage_access_control=p_manage_access_control)
439+
return parsed
440+
441+
442+
class FileSasPermissions(object):
341443
"""FileSasPermissions class to be used with the
342444
:func:`~azure.storage.filedatalake.generate_file_sas` function.
343445
@@ -350,14 +452,69 @@ class FileSasPermissions(BlobSasPermissions):
350452
Create or write content, properties, metadata. Lease the file.
351453
:param bool delete:
352454
Delete the file.
353-
"""
455+
:keyword bool move:
456+
Move any file in the directory to a new location.
457+
Note the move operation can optionally be restricted to the child file or directory owner or
458+
the parent directory owner if the saoid parameter is included in the token and the sticky bit is set
459+
on the parent directory.
460+
:keyword bool execute:
461+
Get the status (system defined properties) and ACL of any file in the directory.
462+
If the caller is the owner, set access control on any file in the directory.
463+
:keyword bool manage_ownership:
464+
Allows the user to set owner, owning group, or act as the owner when renaming or deleting a file or directory
465+
within a folder that has the sticky bit set.
466+
:keyword bool manage_access_control:
467+
Allows the user to set permissions and POSIX ACLs on files and directories.
468+
"""
469+
470+
def __init__(self, read=False, create=False, write=False, delete=False, **kwargs):
471+
self.read = read
472+
self.create = create
473+
self.write = write
474+
self.delete = delete
475+
self.list = list
476+
self.move = kwargs.pop('move', None)
477+
self.execute = kwargs.pop('execute', None)
478+
self.manage_ownership = kwargs.pop('manage_ownership', None)
479+
self.manage_access_control = kwargs.pop('manage_access_control', None)
480+
self._str = (('r' if self.read else '') +
481+
('c' if self.create else '') +
482+
('w' if self.write else '') +
483+
('d' if self.delete else '') +
484+
('m' if self.move else '') +
485+
('e' if self.execute else '') +
486+
('o' if self.manage_ownership else '') +
487+
('p' if self.manage_access_control else ''))
488+
489+
def __str__(self):
490+
return self._str
354491

355-
def __init__(self, read=False, create=False, write=False,
356-
delete=False):
357-
super(FileSasPermissions, self).__init__(
358-
read=read, create=create, write=write,
359-
delete=delete
360-
)
492+
@classmethod
493+
def from_string(cls, permission):
494+
"""Create a FileSasPermissions from a string.
495+
496+
To specify read, write, or delete permissions you need only to
497+
include the first letter of the word in the string. E.g. For read and
498+
write permissions, you would provide a string "rw".
499+
500+
:param str permission: The string which dictates the read, add, create,
501+
write, or delete permissions.
502+
:return: A FileSasPermissions object
503+
:rtype: ~azure.storage.fildatalake.FileSasPermissions
504+
"""
505+
p_read = 'r' in permission
506+
p_create = 'c' in permission
507+
p_write = 'w' in permission
508+
p_delete = 'd' in permission
509+
p_move = 'm' in permission
510+
p_execute = 'e' in permission
511+
p_manage_ownership = 'o' in permission
512+
p_manage_access_control = 'p' in permission
513+
514+
parsed = cls(read=p_read, create=p_create, write=p_write, delete=p_delete,
515+
move=p_move, execute=p_execute, manage_ownership=p_manage_ownership,
516+
manage_access_control=p_manage_access_control)
517+
return parsed
361518

362519

363520
class AccessPolicy(BlobAccessPolicy):

0 commit comments

Comments
 (0)