commit 469d0997fe0781914856ae1ec00e40fa8fcd3c10
Author: Georgios D. Tsoukalas <gtsouk@grnet.gr>
Date:   Mon Feb 4 17:59:29 2013 +0200

    ui compatibility: translate uuids to display names

diff --git a/snf-astakos-app/astakos/im/cookie.py b/snf-astakos-app/astakos/im/cookie.py
index 59ce656..1dc1b16 100644
--- a/snf-astakos-app/astakos/im/cookie.py
+++ b/snf-astakos-app/astakos/im/cookie.py
@@ -40,7 +40,7 @@ from django.http import HttpRequest
 from django.utils.translation import ugettext as _
 
 from astakos.im.settings import (
-    COOKIE_NAME, COOKIE_DOMAIN, COOKIE_SECURE, LOGGING_LEVEL)
+    COOKIE_NAME, COOKIE_DOMAIN, COOKIE_SECURE, LOGGING_LEVEL, TRANSLATE_UUIDS)
 
 import astakos.im.messages as astakos_messages
 
@@ -69,8 +69,9 @@ class Cookie():
 
     @property
     def is_valid(self):
-        return self.uuid == getattr(self.user, 'uuid', '') and \
-            self.auth_token == getattr(self.user, 'auth_token', '')
+        cookie_attribute = 'uuid' if not TRANSLATE_UUIDS else 'username'
+        return (self.uuid == getattr(self.user, cookie_attribute, '') and
+                self.auth_token == getattr(self.user, 'auth_token', ''))
 
     @property
     def user(self):
@@ -81,7 +82,10 @@ class Cookie():
             raise ValueError(_(astakos_messages.NO_RESPONSE))
         user = self.user
         expire_fmt = user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
-        cookie_value = quote(user.uuid + '|' + user.auth_token)
+        if TRANSLATE_UUIDS:
+            cookie_value = quote(user.username + '|' + user.auth_token)
+        else:
+            cookie_value = quote(user.uuid + '|' + user.auth_token)
         self.response.set_cookie(
             COOKIE_NAME, value=cookie_value, expires=expire_fmt, path='/',
             domain=COOKIE_DOMAIN, secure=COOKIE_SECURE
diff --git a/snf-astakos-app/astakos/im/settings.py b/snf-astakos-app/astakos/im/settings.py
index 40d3ee2..4e7d404 100644
--- a/snf-astakos-app/astakos/im/settings.py
+++ b/snf-astakos-app/astakos/im/settings.py
@@ -341,5 +341,9 @@ ACTIVATION_REDIRECT_URL = getattr(settings,
                                   'ASTAKOS_ACTIVATION_REDIRECT_URL',
                                   "/im/landing")
 
+# If true, this enables a ui compatibility layer for the introduction of UUIDs
+# in identity management. WARNING: Setting to True will break your installation.
+TRANSLATE_UUIDS = getattr(settings, 'ASTAKOS_TRANSLATE_UUIDS', False)
+
 # Users that can approve or deny project applications from the web.
 PROJECT_ADMINS = getattr(settings, 'ASTAKOS_PROJECT_ADMINS', set())
diff --git a/snf-astakos-app/conf/20-snf-astakos-app-settings.conf b/snf-astakos-app/conf/20-snf-astakos-app-settings.conf
index dfc17b4..a1baa6c 100644
--- a/snf-astakos-app/conf/20-snf-astakos-app-settings.conf
+++ b/snf-astakos-app/conf/20-snf-astakos-app-settings.conf
@@ -143,3 +143,8 @@
 
 # UUIDs of users that can approve or deny project applications from the web.
 # ASTAKOS_PROJECT_ADMINS = set() # e.g. set(['01234567-89ab-cdef-0123-456789abcdef'])
+#
+# If true, this enables a ui compatibility layer for the introduction
+# of UUIDs in identity management.
+# WARNING: Setting to True will break your installation.
+# ASTAKOS_TRANSLATE_UUIDS = False
diff --git a/snf-cyclades-app/synnefo/app_settings/default/plankton.py b/snf-cyclades-app/synnefo/app_settings/default/plankton.py
index adb75bc..34360b5 100644
--- a/snf-cyclades-app/synnefo/app_settings/default/plankton.py
+++ b/snf-cyclades-app/synnefo/app_settings/default/plankton.py
@@ -18,3 +18,7 @@ DEFAULT_CONTAINER_FORMAT = 'bare'
 
 # The owner of the images that will be marked as "system images" by the UI
 SYSTEM_IMAGES_OWNER = 'okeanos'
+
+# If true, this enables a ui compatibility layer for the introduction of UUIDs
+# in identity management. WARNING: Setting to True will break your installation.
+TRANSLATE_UUIDS = False
diff --git a/snf-cyclades-app/synnefo/plankton/backend.py b/snf-cyclades-app/synnefo/plankton/backend.py
index 94950ab..4312894 100644
--- a/snf-cyclades-app/synnefo/plankton/backend.py
+++ b/snf-cyclades-app/synnefo/plankton/backend.py
@@ -55,11 +55,15 @@ import warnings
 
 from operator import itemgetter
 from time import gmtime, strftime
-from functools import wraps
+from functools import wraps, partial
 
 from django.conf import settings
 
 from pithos.backends.base import NotAllowedError as PithosNotAllowedError
+import synnefo.lib.astakos as lib_astakos
+import logging
+
+logger = logging.getLogger(__name__)
 
 
 PLANKTON_DOMAIN = 'plankton'
@@ -69,6 +73,20 @@ PROPERTY_PREFIX = 'property:'
 PLANKTON_META = ('container_format', 'disk_format', 'name', 'properties',
                  'status')
 
+TRANSLATE_UUIDS = getattr(settings, 'TRANSLATE_UUIDS', False)
+
+def get_displaynames(names):
+    try:
+        auth_url = settings.ASTAKOS_URL
+        url = auth_url.replace('im/authenticate', 'service/api/user_catalogs')
+        token = settings.CYCLADES_ASTAKOS_SERVICE_TOKEN
+        uuids = lib_astakos.get_displaynames(token, names, url=url)
+    except Exception, e:
+        logger.exception(e)
+        return {}
+
+    return uuids
+
 
 def get_location(account, container, object):
     assert '/' not in account, "Invalid account"
@@ -158,7 +176,15 @@ class ImageBackend(object):
         image['id'] = meta['uuid']
         image['is_public'] = '*' in permissions.get('read', [])
         image['location'] = location
-        image['owner'] = account
+        if TRANSLATE_UUIDS:
+            displaynames = get_displaynames([account])
+            if account in displaynames:
+                display_account = displaynames[account]
+            else:
+                display_account = 'unknown'
+            image['owner'] = display_account
+        else:
+            image['owner'] = account
         image['size'] = meta['bytes']
         image['store'] = 'pithos'
         image['updated_at'] = format_timestamp(meta['modified'])
@@ -305,7 +331,6 @@ class ImageBackend(object):
             # To get shared images, we connect as shared_from member and
             # get the list shared by us
             user = shared_from
-            accounts = [self.user]
         else:
             user = None if public else self.user
             accounts = backend.list_accounts(user)
diff --git a/snf-pithos-app/conf/20-snf-pithos-app-settings.conf b/snf-pithos-app/conf/20-snf-pithos-app-settings.conf
index 1753228..e2c3253 100644
--- a/snf-pithos-app/conf/20-snf-pithos-app-settings.conf
+++ b/snf-pithos-app/conf/20-snf-pithos-app-settings.conf
@@ -45,3 +45,7 @@ PITHOS_BACKEND_BLOCK_PATH = '/usr/share/synnefo/pithos/data'
 
 # Service Token acquired by identity provider.
 #PITHOS_SERVICE_TOKEN = ''
+
+# This enables a ui compatibility layer for the introduction of UUIDs in
+# identity management.  WARNING: Setting to True will break your installation.
+# PITHOS_TRANSLATE_UUIDS = False
diff --git a/snf-pithos-app/pithos/api/functions.py b/snf-pithos-app/pithos/api/functions.py
index 3186dbc..e6103b3 100644
--- a/snf-pithos-app/pithos/api/functions.py
+++ b/snf-pithos-app/pithos/api/functions.py
@@ -42,7 +42,7 @@ from django.utils.http import parse_etags
 from django.utils.encoding import smart_str
 from django.views.decorators.csrf import csrf_exempt
 
-from synnefo.lib.astakos import get_user
+from synnefo.lib.astakos import get_user, get_uuids as _get_uuids
 
 from pithos.api.faults import (
     Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound,
@@ -58,11 +58,12 @@ from pithos.api.util import (
     copy_or_move_object, get_int_parameter, get_content_length,
     get_content_range, socket_read_iterator, SaveToBackendHandler,
     object_data_response, put_object_block, hashmap_md5, simple_list_response,
-    api_method,
-#    retrieve_uuid
+    api_method, is_uuid,
+    retrieve_uuid, retrieve_displayname, retrieve_uuids, retrieve_displaynames
 )
 
-from pithos.api.settings import UPDATE_MD5
+from pithos.api.settings import (UPDATE_MD5, TRANSLATE_UUIDS,
+                                 SERVICE_TOKEN, AUTHENTICATION_URL)
 
 from pithos.backends.base import (
     NotAllowedError, QuotaError, ContainerNotEmpty, ItemNotExists,
@@ -73,9 +74,20 @@ from pithos.backends.filter import parse_filters
 import logging
 import hashlib
 
-
 logger = logging.getLogger(__name__)
 
+def get_uuids(names):
+    try:
+        uuids = _get_uuids(SERVICE_TOKEN, names,
+                           url=AUTHENTICATION_URL.replace(
+                                            'im/authenticate',
+                                            'service/api/user_catalogs'))
+    except Exception, e:
+        logger.exception(e)
+        return {}
+
+    return uuids
+
 
 @csrf_exempt
 def top_demux(request):
@@ -94,6 +106,13 @@ def top_demux(request):
 
 @csrf_exempt
 def account_demux(request, v_account):
+    if TRANSLATE_UUIDS:
+        if not is_uuid(v_account):
+            uuids = get_uuids([v_account])
+            if not uuids or not v_account in uuids:
+                return HttpResponse(status=404)
+            v_account = uuids[v_account]
+
     if request.method == 'HEAD':
         return account_meta(request, v_account)
     elif request.method == 'POST':
@@ -106,6 +125,13 @@ def account_demux(request, v_account):
 
 @csrf_exempt
 def container_demux(request, v_account, v_container):
+    if TRANSLATE_UUIDS:
+        if not is_uuid(v_account):
+            uuids = get_uuids([v_account])
+            if not uuids or not v_account in uuids:
+                return HttpResponse(status=404)
+            v_account = uuids[v_account]
+
     if request.method == 'HEAD':
         return container_meta(request, v_account, v_container)
     elif request.method == 'PUT':
@@ -123,6 +149,13 @@ def container_demux(request, v_account, v_container):
 @csrf_exempt
 def object_demux(request, v_account, v_container, v_object):
     # Helper to avoid placing the token in the URL when loading objects from a browser.
+    if TRANSLATE_UUIDS:
+        if not is_uuid(v_account):
+            uuids = get_uuids([v_account])
+            if not uuids or not v_account in uuids:
+                return HttpResponse(status=404)
+            v_account = uuids[v_account]
+
     if request.method == 'HEAD':
         return object_meta(request, v_account, v_container, v_object)
     elif request.method == 'GET':
@@ -181,6 +214,9 @@ def account_list(request):
     accounts = request.backend.list_accounts(request.user_uniq, marker, limit)
 
     if request.serialization == 'text':
+        if TRANSLATE_UUIDS:
+            accounts = retrieve_displaynames(
+                    getattr(request, 'token', None), accounts)
         if len(accounts) == 0:
             # The cloudfiles python bindings expect 200 if json/xml.
             response.status_code = 204
@@ -208,6 +244,14 @@ def account_list(request):
                 meta['X-Account-Group'] = printable_header_dict(
                     dict([(k, ','.join(v)) for k, v in groups.iteritems()]))
             account_meta.append(printable_header_dict(meta))
+
+    if TRANSLATE_UUIDS:
+        uuids = list(d['name'] for d in account_meta)
+        catalog = retrieve_displaynames(
+                getattr(request, 'token', None), uuids, return_dict=True)
+        for meta in account_meta:
+            meta['name'] = catalog.get(meta.get('name'))
+
     if request.serialization == 'xml':
         data = render_to_string('accounts.xml', {'accounts': account_meta})
     elif request.serialization == 'json':
@@ -231,6 +275,11 @@ def account_meta(request, v_account):
             external_quota=request.user_usage)
         groups = request.backend.get_account_groups(
             request.user_uniq, v_account)
+
+        if TRANSLATE_UUIDS:
+            for k in groups:
+                groups[k] = retrieve_displaynames(
+                        getattr(request, 'token', None), groups[k])
         policy = request.backend.get_account_policy(
             request.user_uniq, v_account, external_quota=request.user_usage)
     except NotAllowedError:
@@ -251,12 +300,25 @@ def account_update(request, v_account):
     #                       badRequest (400)
 
     meta, groups = get_account_headers(request)
-#    for k in groups:
-#        try:
-#            groups[k] = [retrieve_uuid(request.token, x) for x in groups[k]]
-#        except ItemNotExists, e:
-#            raise BadRequest(
-#                'Bad X-Account-Group header value: unknown account: %s' % e)
+    for k in groups:
+        if TRANSLATE_UUIDS:
+            try:
+                groups[k] = retrieve_uuids(
+                        getattr(request, 'token', None),
+                        groups[k],
+                        fail_silently=False)
+            except ItemNotExists, e:
+                raise BadRequest(
+                        'Bad X-Account-Group header value: %s' % e)
+        else:
+            try:
+                retrieve_displaynames(
+                    getattr(request, 'token', None),
+                    groups[k],
+                    fail_silently=False)
+            except ItemNotExists, e:
+                raise BadRequest(
+                        'Bad X-Account-Group header value: %s'  % e)
     replace = True
     if 'update' in request.GET:
         replace = False
@@ -636,6 +698,14 @@ def object_list(request, v_account, v_container):
 
     object_meta = []
     for meta in objects:
+        if TRANSLATE_UUIDS:
+            modified_by = meta.get('modified_by')
+            if modified_by:
+                l = retrieve_displaynames(
+                        getattr(request, 'token', None), [meta['modified_by']])
+                if l is not None and len(l) == 1:
+                    meta['modified_by'] = l[0]
+
         if len(meta) == 1:
             # Virtual objects/directories.
             object_meta.append(meta)
@@ -661,6 +731,7 @@ def object_list(request, v_account, v_container):
             if public:
                 update_public_meta(public, meta)
             object_meta.append(printable_header_dict(meta))
+
     if request.serialization == 'xml':
         data = render_to_string(
             'objects.xml', {'container': v_container, 'objects': object_meta})
@@ -715,7 +786,7 @@ def object_meta(request, v_account, v_container, v_object):
         return response
 
     response = HttpResponse(status=200)
-    put_object_headers(response, meta)
+    put_object_headers(response, meta, token=getattr(request, 'token', None))
     return response
 
 
@@ -851,7 +922,8 @@ def object_read(request, v_account, v_container, v_object):
             data = json.dumps(d)
 
         response = HttpResponse(data, status=200)
-        put_object_headers(response, meta)
+        put_object_headers(
+                response, meta, token=getattr(request, 'token', None))
         response['Content-Length'] = len(data)
         return response
 
diff --git a/snf-pithos-app/pithos/api/settings.py b/snf-pithos-app/pithos/api/settings.py
index d900b85..78435db 100644
--- a/snf-pithos-app/pithos/api/settings.py
+++ b/snf-pithos-app/pithos/api/settings.py
@@ -28,6 +28,8 @@ AUTHENTICATION_URL = getattr(settings, 'PITHOS_AUTHENTICATION_URL',
                              'http://localhost:8000/im/authenticate/')
 AUTHENTICATION_USERS = getattr(settings, 'PITHOS_AUTHENTICATION_USERS', {})
 
+TRANSLATE_UUIDS = getattr(settings, 'PITHOS_TRANSLATE_UUIDS', False)
+
 COOKIE_NAME = getattr(settings, 'ASTAKOS_COOKIE_NAME', '_pithos2_a')
 
 # SQLAlchemy (choose SQLite/MySQL/PostgreSQL).
diff --git a/snf-pithos-app/pithos/api/util.py b/snf-pithos-app/pithos/api/util.py
index 5d56fd1..a423530 100644
--- a/snf-pithos-app/pithos/api/util.py
+++ b/snf-pithos-app/pithos/api/util.py
@@ -67,7 +67,7 @@ from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION,
                                  AUTHENTICATION_URL, AUTHENTICATION_USERS,
                                  COOKIE_NAME, USER_CATALOG_URL,
                                  RADOS_STORAGE, RADOS_POOL_BLOCKS,
-                                 RADOS_POOL_MAPS)
+                                 RADOS_POOL_MAPS, TRANSLATE_UUIDS)
 from pithos.backends import connect_backend
 from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists,
                                   VersionNotExists)
@@ -80,7 +80,6 @@ import hashlib
 import uuid
 import decimal
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -226,7 +225,7 @@ def get_object_headers(request):
     return content_type, meta, get_sharing(request), get_public(request)
 
 
-def put_object_headers(response, meta, restricted=False):
+def put_object_headers(response, meta, restricted=False, token=None):
     response['ETag'] = meta['checksum']
     response['Content-Length'] = meta['bytes']
     response['Content-Type'] = meta.get('type', 'application/octet-stream')
@@ -234,8 +233,10 @@ def put_object_headers(response, meta, restricted=False):
     if not restricted:
         response['X-Object-Hash'] = meta['hash']
         response['X-Object-UUID'] = meta['uuid']
-        response['X-Object-Modified-By'] = smart_str(
-            meta['modified_by'], strings_only=True)
+        modified_by = retrieve_displayname(token, meta['modified_by'])
+        if TRANSLATE_UUIDS:
+            response['X-Object-Modified-By'] = smart_str(
+                    modified_by, strings_only=True)
         response['X-Object-Version'] = meta['version']
         response['X-Object-Version-Timestamp'] = http_date(
             int(meta['version_timestamp']))
@@ -291,17 +292,23 @@ def is_uuid(str):
 # USER CATALOG utilities #
 ##########################
 
-def retrieve_displayname(token, uuid):
-    try:
-        return get_displayname(
+def retrieve_displayname(token, uuid, fail_silently=True):
+    displayname = get_displayname(
             token, uuid, USER_CATALOG_URL, AUTHENTICATION_USERS)
-    except:
-        # if it fails just leave the input intact
+    if not displayname and not fail_silently:
+        raise ItemNotExists(uuid)
+    elif not displayname:
+        # just return the uuid
         return uuid
+    return displayname
 
-def retrieve_displaynames(token, uuids):
-    return get_displaynames(
-        token, uuids, USER_CATALOG_URL, AUTHENTICATION_USERS)
+def retrieve_displaynames(token, uuids, return_dict=False, fail_silently=True):
+    catalog =  get_displaynames(
+            token, uuids, USER_CATALOG_URL, AUTHENTICATION_USERS) or {}
+    missing = list(set(uuids) - set(catalog))
+    if missing and not fail_silently:
+        raise ItemNotExists('Unknown displaynames: %s' % ', '.join(missing))
+    return catalog if return_dict else [catalog.get(i) for i in uuids]
 
 def retrieve_uuid(token, displayname):
     if is_uuid(displayname):
@@ -313,27 +320,35 @@ def retrieve_uuid(token, displayname):
         raise ItemNotExists(displayname)
     return uuid
 
-def retrieve_uuids(token, displaynames):
-    return get_uuids(
-        token, displaynames, USER_CATALOG_URL, AUTHENTICATION_USERS)
+def retrieve_uuids(token, displaynames, return_dict=False, fail_silently=True):
+    catalog = get_uuids(
+            token, displaynames, USER_CATALOG_URL, AUTHENTICATION_USERS) or {}
+    missing = list(set(displaynames) - set(catalog))
+    if missing and not fail_silently:
+        raise ItemNotExists('Unknown uuids: %s' % ', '.join(missing))
+    return catalog if return_dict else [catalog.get(i) for i in displaynames]
 
 def replace_permissions_displayname(token, holder):
+    if holder == '*':
+        return holder
     try:
         # check first for a group permission
         account, group = holder.split(':')
     except ValueError:
-        return retrieve_uuid(holder)
+        return retrieve_uuid(token, holder)
     else:
-        return ':'.join([retrieve_uuid(account), group])
+        return ':'.join([retrieve_uuid(token, account), group])
 
 def replace_permissions_uuid(token, holder):
+    if holder == '*':
+        return holder
     try:
         # check first for a group permission
         account, group = holder.split(':')
     except ValueError:
-        return retrieve_displayname(holder)
+        return retrieve_displayname(token, holder)
     else:
-        return ':'.join([retrieve_displayname(account), group])
+        return ':'.join([retrieve_displayname(token, account), group])
 
 def update_sharing_meta(request, permissions, v_account, v_container, v_object, meta):
     if permissions is None:
@@ -343,9 +358,13 @@ def update_sharing_meta(request, permissions, v_account, v_container, v_object,
         return
 
     # replace uuid with displayname
-#    perms['read'] = [replace_permissions_uuid(request.token, x) for x in perms.get('read', [])]
-#    perms['write'] = \
-#        [replace_permissions_uuid(request.token, x) for x in perms.get('write', [])]
+    if TRANSLATE_UUIDS:
+        perms['read'] = [replace_permissions_uuid(
+                getattr(request, 'token', None), x) \
+                    for x in perms.get('read', [])]
+        perms['write'] = [replace_permissions_uuid(
+                getattr(request, 'token', None), x) \
+                    for x in perms.get('write', [])]
 
     ret = []
 
@@ -598,14 +617,17 @@ def get_sharing(request):
                 'Bad X-Object-Sharing header value: missing prefix')
 
     # replace displayname with uuid
-#    try:
-#        ret['read'] = \
-#            [replace_permissions_displayname(request.token, x) for x in ret.get('read', [])]
-#        ret['write'] = \
-#            [replace_permissions_displayname(request.token, x) for x in ret.get('write', [])]
-#    except ItemNotExists, e:
-#        raise BadRequest(
-#            'Bad X-Object-Sharing header value: unknown account: %s' % e)
+    if TRANSLATE_UUIDS:
+        try:
+            ret['read'] = [replace_permissions_displayname(
+                    getattr(request, 'token', None), x) \
+                        for x in ret.get('read', [])]
+            ret['write'] = [replace_permissions_displayname(
+                    getattr(request, 'token', None), x) \
+                        for x in ret.get('write', [])]
+        except ItemNotExists, e:
+            raise BadRequest(
+                'Bad X-Object-Sharing header value: unknown account: %s' % e)
 
     # Keep duplicates only in write list.
     dups = [x for x in ret.get(
@@ -879,7 +901,8 @@ def object_data_response(request, sizes, hashmaps, meta, public=False):
         boundary = ''
     wrapper = ObjectWrapper(request.backend, ranges, sizes, hashmaps, boundary)
     response = HttpResponse(wrapper, status=ret)
-    put_object_headers(response, meta, public)
+    put_object_headers(
+            response, meta, restricted=public, token=getattr(request, 'token', None))
     if ret == 206:
         if len(ranges) == 1:
             offset, length = ranges[0]
