Commit dc76f9af authored by Georgios D. Tsoukalas's avatar Georgios D. Tsoukalas
Browse files

ui compatibility: translate uuids to display names

parent 433271b5
......@@ -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
......
......@@ -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())
......@@ -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
......@@ -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
......@@ -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)
......
......@@ -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
......@@ -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
......
......@@ -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).
......
......@@ -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]
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment