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
 =========================  ================================
-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.
+\                          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.
@@ -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-Allowed-To         Allowed actions on object (optional)
 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-Allowed-To         Allowed actions on object (optional)
 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.
 
-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):
 
@@ -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``.
-* 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``.
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')
-                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':
@@ -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)
-    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.
@@ -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)
-    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.
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)
-        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:
@@ -189,10 +191,10 @@ def update_manifest_meta(request, v_account, meta):
         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
-    perm_path, perms = permissions
+    allowed, perm_path, perms = permissions
     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
+    if request.user != v_account:
+        meta['X-Object-Allowed-To'] = allowed
 
 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 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.
         
-        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
index 54fc340..631b8bd 100644 (file)
@@ -414,13 +414,22 @@ class ModularBackend(BaseBackend):
     
     @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)
-        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]
-        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):
index 76d8251..1e2f8a1 100644 (file)
@@ -449,13 +449,21 @@ class SimpleBackend(BaseBackend):
     
     @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)
-        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]
-        return self._get_permissions(path)
+        return (allowed,) + self._get_permissions(path)
     
     @backend_method
     def update_object_permissions(self, user, account, container, name, permissions):