Commit 3e963f99 authored by Christos Stavrakakis's avatar Christos Stavrakakis

Merge branch 'feature-snf-django' into develop

Conflicts:
	docs/older/Changelog.pithos-app
	snf-astakos-app/astakos/im/api/__init__.py
	snf-pithos-app/pithos/api/util.py
parents 79766cfc f1122a60
......@@ -34,4 +34,5 @@ snf-quotaholder-app/quotaholder_django/version.py
*.iml
*.graffle
snf-stats-app/synnefo_stats/version.py
snf-astakos-client/astakosclient/version.py
astakosclient/astakosclient/version.py
snf-django-lib/snf_django/version.py
......@@ -16,6 +16,8 @@ Released: UNRELEASED
Synnefo-wide
------------
* Create 'snf_django' Python package to hold common code for all Synnefo
components.
Astakos
-------
......@@ -25,6 +27,8 @@ Cyclades
Pithos
------
* Remove PITHOS_AUTHENTICATION_USERS setting, which was used to override
astakos users.
Tools
-----
......
......@@ -145,7 +145,7 @@ class AstakosClient():
# Get the connection object
with self.conn_class(self.netloc) as conn:
# Send request
(data, status) = \
(message, data, status) = \
_do_request(conn, method, request_path, **kwargs)
except Exception as err:
self.logger.error("Failed to send request: %s" % repr(err))
......@@ -154,15 +154,15 @@ class AstakosClient():
# Return
self.logger.debug("Request returned with status %s" % status)
if status == 400:
raise BadRequest(data)
raise BadRequest(message, data)
elif status == 401:
raise Unauthorized(data)
raise Unauthorized(message, data)
elif status == 403:
raise Forbidden(data)
raise Forbidden(message, data)
elif status == 404:
raise NotFound(data)
raise NotFound(message, data)
elif status < 200 or status >= 300:
raise AstakosClientException(data, status)
raise AstakosClientException(message, data, status)
return simplejson.loads(unicode(data))
# ------------------------
......@@ -314,4 +314,5 @@ def _do_request(conn, method, url, **kwargs):
length = response.getheader('content-length', None)
data = response.read(length)
status = int(response.status)
return (data, status)
message = response.reason
return (message, data, status)
......@@ -33,36 +33,29 @@
class AstakosClientException(Exception):
def __init__(self, message, status=0):
def __init__(self, message='', details='', status=None):
self.message = message
self.status = status
def __str__(self):
return repr(self.message)
self.details = details
if not hasattr(self, 'status'):
self.status = status
super(AstakosClientException,
self).__init__(self.message, self.details, self.status)
class BadRequest(AstakosClientException):
def __init__(self, message):
"""400 Bad Request"""
super(BadRequest, self).__init__(message, 400)
status = 400
class Unauthorized(AstakosClientException):
def __init__(self, message):
"""401 Invalid X-Auth-Token"""
super(Unauthorized, self).__init__(message, 401)
status = 401
class Forbidden(AstakosClientException):
def __init__(self, message):
"""403 Forbidden"""
super(Forbidden, self).__init__(message, 403)
status = 403
class NotFound(AstakosClientException):
def __init__(self, message):
"""404 Not Found"""
super(NotFound, self).__init__(message, 404)
status = 404
class NoUserName(AstakosClientException):
......
......@@ -71,35 +71,39 @@ def _request_offline(conn, method, url, **kwargs):
def _request_status_302(conn, method, url, **kwargs):
"""This request returns 302"""
message = "FOUND"
status = 302
data = '<html>\r\n<head><title>302 Found</title></head>\r\n' \
'<body bgcolor="white">\r\n<center><h1>302 Found</h1></center>\r\n' \
'<hr><center>nginx/0.7.67</center>\r\n</body>\r\n</html>\r\n'
return (data, status)
return (message, data, status)
def _request_status_404(conn, method, url, **kwargs):
"""This request returns 404"""
message = "Not Found"
status = 404
data = '<html><head><title>404 Not Found</title></head>' \
'<body><h1>Not Found</h1><p>The requested URL /foo was ' \
'not found on this server.</p><hr><address>Apache Server ' \
'at example.com Port 80</address></body></html>'
return (data, status)
return (message, data, status)
def _request_status_401(conn, method, url, **kwargs):
"""This request returns 401"""
message = "UNAUTHORIZED"
status = 401
data = "Invalid X-Auth-Token\n"
return (data, status)
return (message, data, status)
def _request_status_400(conn, method, url, **kwargs):
"""This request returns 400"""
message = "BAD REQUEST"
status = 400
data = "Method not allowed.\n"
return (data, status)
return (message, data, status)
def _request_ok(conn, method, url, **kwargs):
......@@ -136,7 +140,7 @@ def _req_authenticate(conn, method, url, **kwargs):
if "usage=1" not in url:
# Strip `usage' key from `user'
del user['usage']
return (simplejson.dumps(user), 200)
return ("", simplejson.dumps(user), 200)
def _req_catalogs(conn, method, url, **kwargs):
......@@ -176,7 +180,7 @@ def _req_catalogs(conn, method, url, **kwargs):
return_catalog = {"displayname_catalog": catalogs, "uuid_catalog": {}}
else:
return_catalog = {"displayname_catalog": {}, "uuid_catalog": {}}
return (simplejson.dumps(return_catalog), 200)
return ("", simplejson.dumps(return_catalog), 200)
# ----------------------------
......
PROJECTS="\
snf-common\
snf-webproject\
astakosclient\
snf-django-lib\
snf-astakos-app\
snf-astakos-client\
snf-pithos-backend\
snf-cyclades-gtools\
snf-cyclades-app\
......
......@@ -41,7 +41,8 @@ from stat import S_IFDIR, S_IFREG
from sys import argv
from time import time
from synnefo.lib.parsedate import parse_http_date
from snf_django.lib.api.parsedate import parse_http_date
from pithos.tools.lib.client import OOS_Client, Fault
from pithos.tools.lib.fuse import FUSE, FuseOSError, Operations
......
......@@ -96,7 +96,7 @@ class Command(object):
if email_re.match(self.user):
try:
from synnefo.lib.astakos import get_user_uuid
from snf_django.lib.astakos import get_user_uuid
from pithos.api.settings import SERVICE_TOKEN
self.user = get_user_uuid(SERVICE_TOKEN, self.user)
except ImportError:
......
......@@ -19,5 +19,7 @@
version_file = "snf-quotaholder-app/quotaholder_django/version.py"
[[snf-stats-app]]
version_file = "snf-stats-app/synnefo_stats/version.py"
[[snf-astakos-client]]
version_file = "snf-astakos-client/astakosclient/version.py"
[[python-astakosclient]]
version_file = "astakosclient/astakosclient/version.py"
[[snf-django-lib]]
version_file = "snf-django-lib/snf_django/version.py"
......@@ -3,8 +3,10 @@ Changelog
v0.14.0
------
(* Unreleased *)
* settings *PITHOS_BACKEND_POOL_SIZE*
* setting *PITHOS_UPDATE_MD5* is set to False by default
* Remove PITHOS_AUTHENTICATION_USERS setting
v0.13.0
------
......
# Copyright 2011-2012 GRNET S.A. All rights reserved.
# Copyright 2011-2013 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
......@@ -31,20 +31,17 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from functools import wraps
from traceback import format_exc
from urllib import quote, unquote
from functools import partial
from django.http import HttpResponse
from django.utils import simplejson as json
from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.contrib import messages
from astakos.im.models import AstakosUser, Service, Resource
from astakos.im.api.faults import (
Fault, ItemNotFound, InternalServerError, BadRequest)
from astakos.im.models import AstakosUser, Service
from snf_django.lib import api
from snf_django.lib.api import faults
from astakos.im.settings import (
INVITATIONS_ENABLED, COOKIE_NAME, EMAILCHANGE_ENABLED, QUOTAHOLDER_URL,
PROJECTS_VISIBLE)
......@@ -59,44 +56,17 @@ format = ('%a, %d %b %Y %H:%M:%S GMT')
absolute = lambda request, url: request.build_absolute_uri(url)
def render_fault(request, fault):
if isinstance(fault, InternalServerError) and settings.DEBUG:
fault.details = format_exc(fault)
request.serialization = 'text'
data = fault.message + '\n'
if fault.details:
data += '\n' + fault.details
response = HttpResponse(data, status=fault.code)
response['Content-Length'] = len(response.content)
return response
def api_method(http_method=None):
"""Decorator function for views that implement an API method."""
def decorator(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
try:
if http_method and request.method != http_method:
raise BadRequest('Method not allowed.')
response = func(request, *args, **kwargs)
return response
except Fault, fault:
return render_fault(request, fault)
except BaseException, e:
logger.exception('Unexpected error: %s' % e)
fault = InternalServerError('Unexpected error')
return render_fault(request, fault)
return wrapper
return decorator
# Decorator for API methods, using common utils.api_method decorator.
# It is used for 'get_services' and 'get_menu' methods that do not
# require any sort of authentication
api_method = partial(api.api_method, user_required=False,
token_required=False, logger=logger)
def get_services_dict():
services = Service.objects.all()
data = tuple({'id': s.pk, 'name': s.name, 'url': s.url, 'icon':
s.icon} for s in services)
return data
"""Return dictionary with information about available Services."""
return list(Service.objects.values("id", "name", "url", "icon"))
@api_method(http_method=None)
def get_services(request):
......@@ -123,55 +93,48 @@ def get_services(request):
@api_method()
def get_menu(request, with_extra_links=False, with_signout=True):
user = request.user
from_location = request.GET.get('location')
index_url = reverse('index')
l = [{'url': absolute(request, index_url), 'name': _("Sign in")}]
if user.is_authenticated():
l = []
append = l.append
item = MenuItem
item.current_path = absolute(request, request.path)
append(item(
url=absolute(request, reverse('index')),
name=user.email))
append(item(url=absolute(request, reverse('index')),
name=user.email))
if with_extra_links:
append(item(
url=absolute(request, reverse('landing')),
name="Overview"))
append(item(url=absolute(request, reverse('landing')),
name="Overview"))
if with_signout:
append(item(
url=absolute(request, reverse('landing')),
name="Dashboard"))
append(item(url=absolute(request, reverse('landing')),
name="Dashboard"))
if with_extra_links:
append(item(url=absolute(request, reverse('edit_profile')),
name="Profile"))
name="Profile"))
if with_extra_links:
if INVITATIONS_ENABLED:
append(item(
url=absolute(request, reverse('invite')),
name="Invitations"))
append(item(url=absolute(request, reverse('invite')),
name="Invitations"))
append(item(url=absolute(request, reverse('resource_usage')),
name="Usage"))
append(item(
url=absolute(request, reverse('resource_usage')),
name="Usage"))
if QUOTAHOLDER_URL and PROJECTS_VISIBLE:
append(item(
url=absolute(request, reverse('project_list')),
name="Projects"))
append(item(url=absolute(request, reverse('project_list')),
name="Projects"))
#append(item(
#url=absolute(request, reverse('api_access')),
#name="API Access"))
append(item(
url=absolute(request, reverse('feedback')),
name="Contact"))
append(item(url=absolute(request, reverse('feedback')),
name="Contact"))
if with_signout:
append(item(
url=absolute(request, reverse('logout')),
name="Sign out"))
append(item(url=absolute(request, reverse('logout')),
name="Sign out"))
else:
l = [{'url': absolute(request, index_url),
'name': _("Sign in")}]
callback = request.GET.get('callback', None)
data = json.dumps(tuple(l))
......@@ -218,14 +181,15 @@ class MenuItem(dict):
if name == 'current_path':
self.__set_is_active__()
def __get_uuid_displayname_catalogs(request, user_call=True):
# Normal Response Codes: 200
# Error Response Codes: badRequest (400)
# Error Response Codes: BadRequest (400)
try:
input_data = json.loads(request.raw_post_data)
except:
raise BadRequest('Request body should be json formatted.')
raise faults.BadRequest('Request body should be json formatted.')
else:
uuids = input_data.get('uuids', [])
if uuids == None and user_call:
......@@ -233,8 +197,9 @@ def __get_uuid_displayname_catalogs(request, user_call=True):
displaynames = input_data.get('displaynames', [])
if displaynames == None and user_call:
displaynames = []
d = {'uuid_catalog':AstakosUser.objects.uuid_catalog(uuids),
'displayname_catalog':AstakosUser.objects.displayname_catalog(displaynames)}
user_obj = AstakosUser.objects
d = {'uuid_catalog': user_obj.uuid_catalog(uuids),
'displayname_catalog': user_obj.displayname_catalog(displaynames)}
response = HttpResponse()
response.content = json.dumps(d)
......@@ -242,21 +207,23 @@ def __get_uuid_displayname_catalogs(request, user_call=True):
response['Content-Length'] = len(response.content)
return response
def __send_feedback(request, email_template_name='im/feedback_mail.txt', user=None):
def __send_feedback(request, email_template_name='im/feedback_mail.txt',
user=None):
if not user:
auth_token = request.POST.get('auth', '')
if not auth_token:
raise BadRequest('Missing user authentication')
raise faults.BadRequest('Missing user authentication')
try:
user = AstakosUser.objects.get(auth_token=auth_token)
except AstakosUser.DoesNotExist:
raise BadRequest('Invalid user authentication')
raise faults.BadRequest('Invalid user authentication')
form = FeedbackForm(request.POST)
if not form.is_valid():
logger.error("Invalid feedback request: %r", form.errors)
raise BadRequest('Invalid data')
raise faults.BadRequest('Invalid data')
msg = form.cleaned_data['feedback_msg']
data = form.cleaned_data['feedback_data']
......
# Copyright 2011-2012 GRNET S.A. All rights reserved.
# Copyright 2011-2013 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
......@@ -31,71 +31,71 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
import logging
from functools import wraps
from time import time, mktime
from django.http import HttpResponse
from functools import wraps
from django.views.decorators.csrf import csrf_exempt
from django.utils import simplejson as json
from . import render_fault, __get_uuid_displayname_catalogs, __send_feedback
from .faults import (
Fault, Unauthorized, InternalServerError, BadRequest, ItemNotFound)
from . import __get_uuid_displayname_catalogs, __send_feedback
from snf_django.lib import api
from snf_django.lib.api import faults
from astakos.im.models import Service
import logging
logger = logging.getLogger(__name__)
def api_method(http_method=None, token_required=False):
"""Decorator function for views that implement an API method."""
def decorator(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
try:
if http_method and request.method != http_method:
raise BadRequest('Method not allowed.')
x_auth_token = request.META.get('HTTP_X_AUTH_TOKEN')
if token_required:
if not x_auth_token:
raise Unauthorized('Access denied')
try:
service = Service.objects.get(auth_token=x_auth_token)
def service_from_token(func):
"""Decorator for authenticating service by it's token.
Check that a service with the corresponding token exists. Also,
if service's token has an expiration token, check that it has not
expired.
"""
@wraps(func)
def wrapper(request, *args, **kwargs):
try:
token = request.x_auth_token
except AttributeError:
raise faults.Unauthorized("No authentication token")
if not token:
raise faults.Unauthorized("Invalid X-Auth-Token")
try:
service = Service.objects.get(auth_token=token)
except Service.DoesNotExist:
raise faults.Unauthorized("Invalid X-Auth-Token")
# Check if the token has expired
expiration_date = service.auth_token_expires
if expiration_date:
expires_at = mktime(expiration_date.timetuple())
if time() > expires_at:
raise faults.Unauthorized("Authentication expired")
return func(request, *args, **kwargs)
return wrapper
# Check if the token has expired.
if service.auth_token_expires:
if (time() - mktime(service.auth_token_expires.timetuple())) > 0:
raise Unauthorized('Authentication expired')
except Service.DoesNotExist, e:
raise Unauthorized('Invalid X-Auth-Token')
response = func(request, *args, **kwargs)
return response
except Fault, fault:
return render_fault(request, fault)
except BaseException, e:
logger.exception('Unexpected error: %s' % e)
fault = InternalServerError('Unexpected error')
return render_fault(request, fault)
return wrapper
return decorator
@csrf_exempt
@api_method(http_method='POST', token_required=True)
@api.api_method(http_method='POST', token_required=True, user_required=False,
logger=logger)
@service_from_token # Authenticate service !!
def get_uuid_displayname_catalogs(request):
# Normal Response Codes: 200
# Error Response Codes: internalServerError (500)
# badRequest (400)
# unauthorised (401)
return __get_uuid_displayname_catalogs(request, user_call=False)
@csrf_exempt
@api_method(http_method='POST', token_required=True)
@api.api_method(http_method='POST', token_required=True, user_required=False,
logger=logger)
@service_from_token # Authenticate service !!
def send_feedback(request, email_template_name='im/feedback_mail.txt'):
# Normal Response Codes: 200
# Error Response Codes: internalServerError (500)
# badRequest (400)