← Back to team overview

txaws-dev team mailing list archive

[Merge] lp:~djfroofy/txaws/641620-acls into lp:txaws

 

Drew Smathers has proposed merging lp:~djfroofy/txaws/641620-acls into lp:txaws.

Requested reviews:
  txAWS Developers (txaws-dev)
Related bugs:
  #641620 s3 client does not provide ACLs api
  https://bugs.launchpad.net/bugs/641620

-- 
https://code.launchpad.net/~djfroofy/txaws/641620-acls/+merge/35959
Your team txAWS Developers is requested to review the proposed merge of lp:~djfroofy/txaws/641620-acls into lp:txaws.
=== added file 'txaws/s3/acls.py'
--- txaws/s3/acls.py	1970-01-01 00:00:00 +0000
+++ txaws/s3/acls.py	2010-09-19 18:31:04 +0000
@@ -0,0 +1,117 @@
+from txaws.util import XML
+
+PERMISSIONS = (
+    'FULL_CONTROL',
+    'WRITE',
+    'WRITE_ACP',
+    'READ',
+    'READ_ACP' )
+
+
+class XMLMixin(object):
+    
+    def to_xml(self):
+        return ''.join(self._to_xml())
+
+
+class AccessControlPolicy(XMLMixin):
+
+    def __init__(self, owner=None, access_control_list=()):
+        self.owner = owner
+        self.access_control_list = access_control_list
+
+    def _to_xml(self, buffer=None):
+        if buffer is None:
+            buffer = []
+        buffer.append('<AccessControlPolicy>\n')
+        if self.owner:
+            self.owner._to_xml(buffer=buffer, indent=1)
+        buffer.append('  <AccessControlList>\n')
+        for grant in self.access_control_list:
+            grant._to_xml(buffer=buffer, indent=2)
+        buffer.append('  </AccessControlList>\n'
+                   '</AccessControlPolicy>')
+        return buffer
+
+    @classmethod
+    def from_xml(cls, xml_bytes):
+        root = XML(xml_bytes)
+        owner_node = root.find('Owner')
+        owner = Owner(owner_node.findtext('ID'),
+                      owner_node.findtext('DisplayName'))
+        acl_node = root.find('AccessControlList')
+        acl = []
+        for grant_node in acl_node.findall('Grant'):
+            grantee_node = grant_node.find('Grantee')
+            grantee = Grantee(grantee_node.findtext('ID'),
+                              grantee_node.findtext('DisplayName'))
+            permission = grant_node.findtext('Permission')
+            acl.append(Grant(grantee, permission))
+        return cls(owner, acl)
+
+
+class Grant(XMLMixin):
+
+    def __init__(self, grantee, permission=None):
+        self.grantee = grantee
+        self.permission = permission
+
+    def _set_permission(self, perm):
+        if perm not in PERMISSIONS:
+            raise ValueError('Invalid permission "%s". Must be one of %s' %
+                             (perm, ','.join(PERMISSIONS)))
+        self._permission = perm
+
+    def _get_permission(self):
+        return self._permission
+
+    permission = property(_get_permission, _set_permission)
+
+    def _to_xml(self, buffer=None, indent=0):
+        if buffer is None:
+            buffer = []
+        ws = ' ' * (indent * 2)
+        buffer.append(ws + '<Grant>\n')
+        if self.grantee:
+            self.grantee._to_xml(buffer, indent+1)
+        if self.permission:
+            buffer.append('%s  <Permission>%s</Permission>\n' % (
+                          ws, self.permission))
+        buffer.append(ws + '</Grant>\n')
+        return buffer 
+
+
+class Owner(XMLMixin):
+    
+    def __init__(self, id, display_name):
+        self.id = id
+        self.display_name = display_name
+
+    def _to_xml(self, buffer=None, indent=0):
+        if buffer is None:
+            buffer = []
+        ws = ' ' * (indent * 2)
+        buffer.append('%s<Owner>\n'
+                      '%s  <ID>%s</ID>\n'
+                      '%s  <DisplayName>%s</DisplayName>\n'
+                      '%s</Owner>\n' % (
+                          ws, ws, self.id, ws,
+                          self.display_name, ws))
+        return buffer
+
+class Grantee(Owner):
+
+    def _to_xml(self, buffer=None, indent=0):
+        if buffer is None:
+            buffer = []
+        ws = ' ' * (indent * 2)
+        buffer.append('%s<Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";'
+                   ' xsi:type="CanonicalUser">\n'
+                   '%s  <ID>%s</ID>\n'
+                   '%s  <DisplayName>%s</DisplayName>\n'
+                   '%s</Grantee>\n' % (
+                        ws, ws, self.id, ws,
+                        self.display_name, ws))
+        return buffer
+
+

=== modified file 'txaws/s3/client.py'
--- txaws/s3/client.py	2009-11-23 00:55:44 +0000
+++ txaws/s3/client.py	2010-09-19 18:31:04 +0000
@@ -19,6 +19,7 @@
 
 from txaws.client.base import BaseClient, BaseQuery, error_wrapper
 from txaws.s3 import model
+from txaws.s3 import acls
 from txaws.s3.exception import S3Error
 from txaws.service import AWSServiceEndpoint, S3_ENDPOINT
 from txaws.util import XML, calculate_md5
@@ -177,6 +178,31 @@
             name, prefix, marker, max_keys, is_truncated, contents,
             common_prefixes)
 
+
+    def get_bucket_acl(self, bucket):
+        """
+        Get the access control policy for a bucket.
+        """
+        query = self.query_factory(
+            action='GET', creds=self.creds, endpoint=self.endpoint,
+            bucket=bucket, object_name='?acl')
+        return query.submit().addCallback(self._parse_acl)
+
+    def put_bucket_acl(self, bucket, access_control_policy):
+        """
+        Set access control policy on a bucket.
+        """
+        data = access_control_policy.to_xml()
+        query = self.query_factory(
+            action='PUT', creds=self.creds, endpoint=self.endpoint,
+            bucket=bucket, object_name='?acl', data=data)
+        return query.submit().addCallback(self._parse_acl)
+
+
+    def _parse_acl(self, xml_bytes):
+        return acls.AccessControlPolicy.from_xml(xml_bytes)        
+
+
     def put_object(self, bucket, object_name, data, content_type=None,
                    metadata={}):
         """
@@ -220,6 +246,14 @@
             bucket=bucket, object_name=object_name)
         return query.submit()
 
+    def get_object_acl(self, bucket, object_name):
+        """
+        Get the access control policy for an object.
+        """
+        query = self.query_factory(
+            action='GET', creds=self.creds, endpoint=self.endpoint,
+            bucket=bucket, object_name='%s?acl' % object_name)
+        return query.submit().addCallback(self._parse_acl)
 
 class Query(BaseQuery):
     """A query for submission to the S3 service."""

=== added file 'txaws/s3/tests/test_acls.py'
--- txaws/s3/tests/test_acls.py	1970-01-01 00:00:00 +0000
+++ txaws/s3/tests/test_acls.py	2010-09-19 18:31:04 +0000
@@ -0,0 +1,86 @@
+from twisted.trial.unittest import TestCase
+
+from txaws.testing import payload
+from txaws.s3 import acls
+
+
+class ACLTests(TestCase):
+
+    def test_owner_to_xml(self):
+        owner = acls.Owner(id='8a6925ce4adf588a4f21c32aa379004fef',
+                           display_name='BucketOwnersEmail@xxxxxxxxxx')
+        xml_bytes = owner.to_xml()
+        self.assertEquals(xml_bytes, """\
+<Owner>
+  <ID>8a6925ce4adf588a4f21c32aa379004fef</ID>
+  <DisplayName>BucketOwnersEmail@xxxxxxxxxx</DisplayName>
+</Owner>
+""")
+
+
+    def test_grantee_to_xml(self):
+        grantee = acls.Grantee(id='8a6925ce4adf588a4f21c32aa379004fef',
+                               display_name='BucketOwnersEmail@xxxxxxxxxx')
+        xml_bytes = grantee.to_xml()
+        self.assertEquals(xml_bytes, """\
+<Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; xsi:type="CanonicalUser">
+  <ID>8a6925ce4adf588a4f21c32aa379004fef</ID>
+  <DisplayName>BucketOwnersEmail@xxxxxxxxxx</DisplayName>
+</Grantee>
+""")
+
+
+    def test_grant_to_xml(self):
+        grantee = acls.Grantee(id='8a6925ce4adf588a4f21c32aa379004fef',
+                               display_name='BucketOwnersEmail@xxxxxxxxxx')
+        grant = acls.Grant(grantee, 'FULL_CONTROL')
+        xml_bytes = grant.to_xml()
+        self.assertEquals(xml_bytes, """\
+<Grant>
+  <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; xsi:type="CanonicalUser">
+    <ID>8a6925ce4adf588a4f21c32aa379004fef</ID>
+    <DisplayName>BucketOwnersEmail@xxxxxxxxxx</DisplayName>
+  </Grantee>
+  <Permission>FULL_CONTROL</Permission>
+</Grant>
+""")
+
+    def test_access_control_policy_to_xml(self):
+        
+        grantee = acls.Grantee(id='8a6925ce4adf588a4f21c32aa379004fef',
+                               display_name='foo@xxxxxxxxxxx')
+        grant1 = acls.Grant(grantee, 'FULL_CONTROL')
+        grantee = acls.Grantee(id='8a6925ce4adf588a4f21c32aa37900feed',
+                               display_name='bar@xxxxxxxxxxx')
+        grant2 = acls.Grant(grantee, 'READ')
+        owner = acls.Owner(id='8a6925ce4adf588a4f21c32aa37900beef',
+                           display_name='baz@xxxxxxxxxxx')
+        acp = acls.AccessControlPolicy(owner=owner, access_control_list=[grant1, grant2])
+        xml_bytes = acp.to_xml()
+        self.assertEquals(xml_bytes, payload.sample_access_control_policy_result)
+
+
+    def test_permission_enum(self):
+        grantee = acls.Grantee(id='8a6925ce4adf588a4f21c32aa379004fef',
+                               display_name='BucketOwnersEmail@xxxxxxxxxx')
+        acls.Grant(grantee, 'FULL_CONTROL')
+        acls.Grant(grantee, 'WRITE')
+        acls.Grant(grantee, 'WRITE_ACP')
+        acls.Grant(grantee, 'READ')
+        acls.Grant(grantee, 'READ_ACP')
+        self.assertRaises(ValueError, acls.Grant, grantee, 'GO_HOG_WILD')
+
+    def test_from_xml(self):
+        policy = acls.AccessControlPolicy.from_xml(payload.sample_access_control_policy_result)
+        self.assertEquals(policy.owner.id, '8a6925ce4adf588a4f21c32aa37900beef')
+        self.assertEquals(policy.owner.display_name, 'baz@xxxxxxxxxxx')
+        self.assertEquals(len(policy.access_control_list), 2)
+        grant1 = policy.access_control_list[0]
+        self.assertEquals(grant1.grantee.id, '8a6925ce4adf588a4f21c32aa379004fef')
+        self.assertEquals(grant1.grantee.display_name, 'foo@xxxxxxxxxxx')
+        self.assertEquals(grant1.permission, 'FULL_CONTROL')
+        grant2 = policy.access_control_list[1]
+        self.assertEquals(grant2.grantee.id, '8a6925ce4adf588a4f21c32aa37900feed')
+        self.assertEquals(grant2.grantee.display_name, 'bar@xxxxxxxxxxx')
+        self.assertEquals(grant2.permission, 'READ')
+

=== modified file 'txaws/s3/tests/test_client.py'
--- txaws/s3/tests/test_client.py	2009-11-23 00:55:44 +0000
+++ txaws/s3/tests/test_client.py	2010-09-19 18:31:04 +0000
@@ -2,6 +2,7 @@
 
 from txaws.credentials import AWSCredentials
 from txaws.s3 import client
+from txaws.s3 import acls
 from txaws.service import AWSServiceEndpoint
 from txaws.testing import payload
 from txaws.testing.base import TXAWSTestCase
@@ -195,6 +196,62 @@
         creds = AWSCredentials("foo", "bar")
         s3 = client.S3Client(creds, query_factory=StubQuery)
         return s3.delete_bucket("mybucket")
+    
+    def test_put_bucket_acl(self):
+
+        class StubQuery(client.Query):
+
+            def __init__(query, action, creds, endpoint, bucket=None, object_name=None,
+                         data=''):
+                super(StubQuery, query).__init__(
+                    action=action, creds=creds, bucket=bucket, object_name=object_name,
+                    data=data)
+                self.assertEquals(action, "PUT")
+                self.assertEqual(creds.access_key, "foo")
+                self.assertEqual(creds.secret_key, "bar")
+                self.assertEqual(query.bucket, "mybucket")
+                self.assertEqual(query.object_name, '?acl')
+                self.assertEqual(query.data, payload.sample_access_control_policy_result)
+                self.assertEqual(query.metadata, {})
+
+            def submit(query, url_context=None):
+                return succeed(payload.sample_access_control_policy_result)
+
+        def check_result(result):
+            self.assert_(isinstance(result, acls.AccessControlPolicy))
+
+        creds = AWSCredentials("foo", "bar")
+        s3 = client.S3Client(creds, query_factory=StubQuery)
+        policy = acls.AccessControlPolicy.from_xml(payload.sample_access_control_policy_result)
+        return s3.put_bucket_acl("mybucket", policy).addCallback(check_result)
+    
+    def test_get_bucket_acl(self):
+
+        class StubQuery(client.Query):
+
+            def __init__(query, action, creds, endpoint, bucket=None, object_name=None,
+                         data=''):
+                super(StubQuery, query).__init__(
+                    action=action, creds=creds, bucket=bucket, object_name=object_name,
+                    data=data)
+                self.assertEquals(action, "GET")
+                self.assertEqual(creds.access_key, "foo")
+                self.assertEqual(creds.secret_key, "bar")
+                self.assertEqual(query.bucket, "mybucket")
+                self.assertEqual(query.object_name, '?acl')
+                self.assertEqual(query.data, '')
+                self.assertEqual(query.metadata, {})
+
+            def submit(query, url_context=None):
+                return succeed(payload.sample_access_control_policy_result)
+
+        def check_result(result):
+            self.assert_(isinstance(result, acls.AccessControlPolicy))
+
+        creds = AWSCredentials("foo", "bar")
+        s3 = client.S3Client(creds, query_factory=StubQuery)
+        policy = acls.AccessControlPolicy.from_xml(payload.sample_access_control_policy_result)
+        return s3.get_bucket_acl("mybucket").addCallback(check_result)
 
     def test_put_object(self):
 
@@ -297,6 +354,33 @@
         s3 = client.S3Client(creds, query_factory=StubQuery)
         return s3.delete_object("mybucket", "objectname")
 
+    def test_get_object_acl(self):
+
+        class StubQuery(client.Query):
+
+            def __init__(query, action, creds, endpoint, bucket=None, object_name=None,
+                         data=''):
+                super(StubQuery, query).__init__(
+                    action=action, creds=creds, bucket=bucket, object_name=object_name,
+                    data=data)
+                self.assertEquals(action, "GET")
+                self.assertEqual(creds.access_key, "foo")
+                self.assertEqual(creds.secret_key, "bar")
+                self.assertEqual(query.bucket, "mybucket")
+                self.assertEqual(query.object_name, 'myobject?acl')
+                self.assertEqual(query.data, '')
+                self.assertEqual(query.metadata, {})
+
+            def submit(query, url_context=None):
+                return succeed(payload.sample_access_control_policy_result)
+
+        def check_result(result):
+            self.assert_(isinstance(result, acls.AccessControlPolicy))
+
+        creds = AWSCredentials("foo", "bar")
+        s3 = client.S3Client(creds, query_factory=StubQuery)
+        policy = acls.AccessControlPolicy.from_xml(payload.sample_access_control_policy_result)
+        return s3.get_object_acl("mybucket", "myobject").addCallback(check_result)
 
 class QueryTestCase(TXAWSTestCase):
 

=== modified file 'txaws/testing/payload.py'
--- txaws/testing/payload.py	2010-07-20 10:15:48 +0000
+++ txaws/testing/payload.py	2010-09-19 18:31:04 +0000
@@ -903,3 +903,28 @@
   <AWSAccessKeyId>SOMEKEYID</AWSAccessKeyId>
 </Error>
 """
+
+sample_access_control_policy_result = """\
+<AccessControlPolicy>
+  <Owner>
+    <ID>8a6925ce4adf588a4f21c32aa37900beef</ID>
+    <DisplayName>baz@xxxxxxxxxxx</DisplayName>
+  </Owner>
+  <AccessControlList>
+    <Grant>
+      <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; xsi:type="CanonicalUser">
+        <ID>8a6925ce4adf588a4f21c32aa379004fef</ID>
+        <DisplayName>foo@xxxxxxxxxxx</DisplayName>
+      </Grantee>
+      <Permission>FULL_CONTROL</Permission>
+    </Grant>
+    <Grant>
+      <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; xsi:type="CanonicalUser">
+        <ID>8a6925ce4adf588a4f21c32aa37900feed</ID>
+        <DisplayName>bar@xxxxxxxxxxx</DisplayName>
+      </Grantee>
+      <Permission>READ</Permission>
+    </Grant>
+  </AccessControlList>
+</AccessControlPolicy>"""
+