Report allowed actions in cross-user object requests, with the 'X-Object-Allowed...
authorAntony Chazapis <chazapis@gmail.com>
Tue, 13 Sep 2011 06:30:45 +0000 (09:30 +0300)
committerAntony Chazapis <chazapis@gmail.com>
Tue, 13 Sep 2011 06:30:45 +0000 (09:30 +0300)
docs/source/devguide.rst
pithos/api/functions.py
pithos/api/util.py
pithos/backends/base.py
pithos/backends/modular.py
pithos/backends/simple.py

index b99b645..c191915 100644 (file)
@@ -25,11 +25,12 @@ Document Revisions
 =========================  ================================
 Revision                   Description
 =========================  ================================
 =========================  ================================
 Revision                   Description
 =========================  ================================
-0.6 (Sept 12, 2011)        Reply with Merkle hash as the ETag when updating objects.
+0.6 (Sept 13, 2011)        Reply with Merkle hash as the ETag when updating objects.
 \                          Include version id in object replace/change replies.
 \                          Change conflict (409) replies format to text.
 \                          Tags should be migrated to a meta value.
 \                          Container ``PUT`` updates metadata/policy.
 \                          Include version id in object replace/change replies.
 \                          Change conflict (409) replies format to text.
 \                          Tags should be migrated to a meta value.
 \                          Container ``PUT`` updates metadata/policy.
+\                          Report allowed actions in shared object replies.
 0.5 (July 22, 2011)        Object update from another object's data.
 \                          Support object truncate.
 \                          Create object using a standard HTML form.
 0.5 (July 22, 2011)        Object update from another object's data.
 \                          Support object truncate.
 \                          Create object using a standard HTML form.
@@ -566,6 +567,7 @@ X-Object-Modified-By        The user that comitted the object's version
 X-Object-Manifest           Object parts prefix in ``<container>/<object>`` form (optional)
 X-Object-Sharing            Object permissions (optional)
 X-Object-Shared-By          Object inheriting permissions (optional)
 X-Object-Manifest           Object parts prefix in ``<container>/<object>`` form (optional)
 X-Object-Sharing            Object permissions (optional)
 X-Object-Shared-By          Object inheriting permissions (optional)
+X-Object-Allowed-To         Allowed actions on object (optional)
 X-Object-Public             Object's publicly accessible URI (optional)
 X-Object-Meta-*             Optional user defined metadata
 ==========================  ===============================
 X-Object-Public             Object's publicly accessible URI (optional)
 X-Object-Meta-*             Optional user defined metadata
 ==========================  ===============================
@@ -659,6 +661,7 @@ X-Object-Modified-By        The user that comitted the object's version
 X-Object-Manifest           Object parts prefix in ``<container>/<object>`` form (optional)
 X-Object-Sharing            Object permissions (optional)
 X-Object-Shared-By          Object inheriting permissions (optional)
 X-Object-Manifest           Object parts prefix in ``<container>/<object>`` form (optional)
 X-Object-Sharing            Object permissions (optional)
 X-Object-Shared-By          Object inheriting permissions (optional)
+X-Object-Allowed-To         Allowed actions on object (optional)
 X-Object-Public             Object's publicly accessible URI (optional)
 X-Object-Meta-*             Optional user defined metadata
 ==========================  ===============================
 X-Object-Public             Object's publicly accessible URI (optional)
 X-Object-Meta-*             Optional user defined metadata
 ==========================  ===============================
@@ -909,7 +912,7 @@ Sharing and Public Objects
 
 Read and write control in Pithos is managed by setting appropriate permissions with the ``X-Object-Sharing`` header. The permissions are applied using prefix-based inheritance. Thus, each set of authorization directives is applied to all objects sharing the same prefix with the object where the corresponding ``X-Object-Sharing`` header is defined. For simplicity, nested/overlapping permissions are not allowed. Setting ``X-Object-Sharing`` will fail, if the object is already "covered", or another object with a longer common-prefix name already has permissions. When retrieving an object, the ``X-Object-Shared-By`` header reports where it gets its permissions from. If not present, the object is the actual source of authorization directives.
 
 
 Read and write control in Pithos is managed by setting appropriate permissions with the ``X-Object-Sharing`` header. The permissions are applied using prefix-based inheritance. Thus, each set of authorization directives is applied to all objects sharing the same prefix with the object where the corresponding ``X-Object-Sharing`` header is defined. For simplicity, nested/overlapping permissions are not allowed. Setting ``X-Object-Sharing`` will fail, if the object is already "covered", or another object with a longer common-prefix name already has permissions. When retrieving an object, the ``X-Object-Shared-By`` header reports where it gets its permissions from. If not present, the object is the actual source of authorization directives.
 
-A user may ``GET`` another account or container. The result will include a limited reply, containing only the allowed containers or objects respectively. A top-level request with an authentication token, will return a list of allowed accounts, so the user can easily find out which other users share objects.
+A user may ``GET`` another account or container. The result will include a limited reply, containing only the allowed containers or objects respectively. A top-level request with an authentication token, will return a list of allowed accounts, so the user can easily find out which other users share objects. The ``X-Object-Allowed-To`` header lists the actions allowed on an object, if it does not belong to the requesting user.
 
 Objects that are marked as public, via the ``X-Object-Public`` meta, are also available at the corresponding URI returned for ``HEAD`` or ``GET``. Requests for public objects do not need to include an ``X-Auth-Token``. Pithos will ignore request parameters and only include the following headers in the reply (all ``X-Object-*`` meta is hidden):
 
 
 Objects that are marked as public, via the ``X-Object-Public`` meta, are also available at the corresponding URI returned for ``HEAD`` or ``GET``. Requests for public objects do not need to include an ``X-Auth-Token``. Pithos will ignore request parameters and only include the following headers in the reply (all ``X-Object-*`` meta is hidden):
 
@@ -952,7 +955,7 @@ List of differences from the OOS API:
 * Conditional object create/update operations, using ``If-Match`` and ``If-None-Match`` headers.
 * Time-variant account/container listings via the ``until`` parameter.
 * Object versions - parameter ``version`` in ``HEAD``/``GET`` (list versions with ``GET``), ``X-Object-Version-*`` meta in replies, ``X-Source-Version`` in ``PUT``/``COPY``.
 * Conditional object create/update operations, using ``If-Match`` and ``If-None-Match`` headers.
 * Time-variant account/container listings via the ``until`` parameter.
 * Object versions - parameter ``version`` in ``HEAD``/``GET`` (list versions with ``GET``), ``X-Object-Version-*`` meta in replies, ``X-Source-Version`` in ``PUT``/``COPY``.
-* Sharing/publishing with ``X-Object-Sharing``, ``X-Object-Public`` at the object level. Cross-user operations are allowed - controlled by sharing directives. Permissions may include groups defined with ``X-Account-Group-*`` at the account level. These apply to the object - not its versions.
+* Sharing/publishing with ``X-Object-Sharing``, ``X-Object-Public`` at the object level. Cross-user operations are allowed - controlled by sharing directives. Available actions in cross-user requests are reported with ``X-Object-Allowed-To``. Permissions may include groups defined with ``X-Account-Group-*`` at the account level. These apply to the object - not its versions.
 * Support for prefix-based inheritance when enforcing permissions. Parent object carrying the authorization directives is reported in ``X-Object-Shared-By``.
 * Large object support with ``X-Object-Manifest``.
 * Trace the user that created/modified an object with ``X-Object-Modified-By``.
 * Support for prefix-based inheritance when enforcing permissions. Parent object carrying the authorization directives is reported in ``X-Object-Shared-By``.
 * Large object support with ``X-Object-Manifest``.
 * Trace the user that created/modified an object with ``X-Object-Modified-By``.
index 89067e4..34939b7 100644 (file)
@@ -498,7 +498,7 @@ def object_list(request, v_account, v_container):
                 rename_meta_key(meta, 'modified_by', 'x_object_modified_by')
                 rename_meta_key(meta, 'version', 'x_object_version')
                 rename_meta_key(meta, 'version_timestamp', 'x_object_version_timestamp')
                 rename_meta_key(meta, 'modified_by', 'x_object_modified_by')
                 rename_meta_key(meta, 'version', 'x_object_version')
                 rename_meta_key(meta, 'version_timestamp', 'x_object_version_timestamp')
-                update_sharing_meta(permissions, v_account, v_container, x[0], meta)
+                update_sharing_meta(request, permissions, v_account, v_container, x[0], meta)
                 update_public_meta(public, meta)
                 object_meta.append(printable_header_dict(meta))
     if request.serialization == 'xml':
                 update_public_meta(public, meta)
                 object_meta.append(printable_header_dict(meta))
     if request.serialization == 'xml':
@@ -534,7 +534,7 @@ def object_meta(request, v_account, v_container, v_object):
         raise ItemNotFound('Version does not exist')
     
     update_manifest_meta(request, v_account, meta)
         raise ItemNotFound('Version does not exist')
     
     update_manifest_meta(request, v_account, meta)
-    update_sharing_meta(permissions, v_account, v_container, v_object, meta)
+    update_sharing_meta(request, permissions, v_account, v_container, v_object, meta)
     update_public_meta(public, meta)
     
     # Evaluate conditions.
     update_public_meta(public, meta)
     
     # Evaluate conditions.
@@ -599,7 +599,7 @@ def object_read(request, v_account, v_container, v_object):
         raise ItemNotFound('Version does not exist')
     
     update_manifest_meta(request, v_account, meta)
         raise ItemNotFound('Version does not exist')
     
     update_manifest_meta(request, v_account, meta)
-    update_sharing_meta(permissions, v_account, v_container, v_object, meta)
+    update_sharing_meta(request, permissions, v_account, v_container, v_object, meta)
     update_public_meta(public, meta)
     
     # Evaluate conditions.
     update_public_meta(public, meta)
     
     # Evaluate conditions.
index 8104e5d..12c8818 100644 (file)
@@ -160,7 +160,9 @@ def put_object_headers(response, meta, restricted=False):
         response['X-Object-Version-Timestamp'] = http_date(int(meta['version_timestamp']))
         for k in [x for x in meta.keys() if x.startswith('X-Object-Meta-')]:
             response[smart_str(k, strings_only=True)] = smart_str(meta[k], strings_only=True)
         response['X-Object-Version-Timestamp'] = http_date(int(meta['version_timestamp']))
         for k in [x for x in meta.keys() if x.startswith('X-Object-Meta-')]:
             response[smart_str(k, strings_only=True)] = smart_str(meta[k], strings_only=True)
-        for k in ('Content-Encoding', 'Content-Disposition', 'X-Object-Manifest', 'X-Object-Sharing', 'X-Object-Shared-By', 'X-Object-Public'):
+        for k in ('Content-Encoding', 'Content-Disposition', 'X-Object-Manifest',
+                  'X-Object-Sharing', 'X-Object-Shared-By', 'X-Object-Allowed-To',
+                  'X-Object-Public'):
             if k in meta:
                 response[k] = smart_str(meta[k], strings_only=True)
     else:
             if k in meta:
                 response[k] = smart_str(meta[k], strings_only=True)
     else:
@@ -189,10 +191,10 @@ def update_manifest_meta(request, v_account, meta):
         md5.update(hash)
         meta['hash'] = md5.hexdigest().lower()
 
         md5.update(hash)
         meta['hash'] = md5.hexdigest().lower()
 
-def update_sharing_meta(permissions, v_account, v_container, v_object, meta):
+def update_sharing_meta(request, permissions, v_account, v_container, v_object, meta):
     if permissions is None:
         return
     if permissions is None:
         return
-    perm_path, perms = permissions
+    allowed, perm_path, perms = permissions
     if len(perms) == 0:
         return
     ret = []
     if len(perms) == 0:
         return
     ret = []
@@ -205,6 +207,8 @@ def update_sharing_meta(permissions, v_account, v_container, v_object, meta):
     meta['X-Object-Sharing'] = '; '.join(ret)
     if '/'.join((v_account, v_container, v_object)) != perm_path:
         meta['X-Object-Shared-By'] = perm_path
     meta['X-Object-Sharing'] = '; '.join(ret)
     if '/'.join((v_account, v_container, v_object)) != perm_path:
         meta['X-Object-Shared-By'] = perm_path
+    if request.user != v_account:
+        meta['X-Object-Allowed-To'] = allowed
 
 def update_public_meta(public, meta):
     if not public:
 
 def update_public_meta(public, meta):
     if not public:
index 005762f..9fa962c 100644 (file)
@@ -311,10 +311,11 @@ class BaseBackend(object):
         return ''
     
     def get_object_permissions(self, user, account, container, name):
         return ''
     
     def get_object_permissions(self, user, account, container, name):
-        """Return the path from which this object gets its permissions from,
+        """Return the action allowed on the object, the path
+        from which the object gets its permissions from,
         along with a dictionary containing the permissions.
         
         along with a dictionary containing the permissions.
         
-        The keys are:
+        The dictionary keys are (also used for defining the action):
             'read': The object is readable by the users/groups in the list
             
             'write': The object is writable by the users/groups in the list
             'read': The object is readable by the users/groups in the list
             
             'write': The object is writable by the users/groups in the list
index 54fc340..631b8bd 100644 (file)
@@ -414,13 +414,22 @@ class ModularBackend(BaseBackend):
     
     @backend_method
     def get_object_permissions(self, user, account, container, name):
     
     @backend_method
     def get_object_permissions(self, user, account, container, name):
-        """Return the path from which this object gets its permissions from,\
+        """Return the action allowed on the object, the path
+        from which the object gets its permissions from,
         along with a dictionary containing the permissions."""
         
         logger.debug("get_object_permissions: %s %s %s", account, container, name)
         along with a dictionary containing the permissions."""
         
         logger.debug("get_object_permissions: %s %s %s", account, container, name)
-        self._can_read(user, account, container, name)
+        allowed = 'write'
+        if user != account:
+            path = '/'.join((account, container, name))
+            if self.permissions.access_check(path, self.WRITE, user):
+                allowed = 'write'
+            elif self.permissions.access_check(path, self.READ, user):
+                allowed = 'read'
+            else:
+                raise NotAllowedError
         path = self._lookup_object(account, container, name)[0]
         path = self._lookup_object(account, container, name)[0]
-        return self.permissions.access_inherit(path)
+        return (allowed,) + self.permissions.access_inherit(path)
     
     @backend_method
     def update_object_permissions(self, user, account, container, name, permissions):
     
     @backend_method
     def update_object_permissions(self, user, account, container, name, permissions):
index 76d8251..1e2f8a1 100644 (file)
@@ -449,13 +449,21 @@ class SimpleBackend(BaseBackend):
     
     @backend_method
     def get_object_permissions(self, user, account, container, name):
     
     @backend_method
     def get_object_permissions(self, user, account, container, name):
-        """Return the path from which this object gets its permissions from,\
+        """Return the action allowed on the object, the path
+        from which the object gets its permissions from,
         along with a dictionary containing the permissions."""
         
         logger.debug("get_object_permissions: %s %s %s", account, container, name)
         along with a dictionary containing the permissions."""
         
         logger.debug("get_object_permissions: %s %s %s", account, container, name)
-        self._can_read(user, account, container, name)
+        allowed = 'write'
+        if user != account:
+            if self._is_allowed(user, account, container, name, 'write'):
+                allowed = 'write'
+            elif self._is_allowed(user, account, container, name, 'read'):
+                allowed = 'read'
+            else:
+                raise NotAllowedError
         path = self._get_objectinfo(account, container, name)[0]
         path = self._get_objectinfo(account, container, name)[0]
-        return self._get_permissions(path)
+        return (allowed,) + self._get_permissions(path)
     
     @backend_method
     def update_object_permissions(self, user, account, container, name, permissions):
     
     @backend_method
     def update_object_permissions(self, user, account, container, name, permissions):