From 35c827799c8fe34d449d736dec16762866bf36ad Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki Date: Thu, 22 Dec 2011 20:08:41 +0200 Subject: [PATCH] provide authentication service & change pithos to contact it to authenticate users Refs: #1689 --- pithos/im/api.py | 62 +++++++++++++++++++++++++++++++++ pithos/im/faults.py | 51 +++++++++++++++++++++++++++ pithos/im/urls.py | 5 +++ pithos/lib/client.py | 16 +++++++-- pithos/middleware/auth.py | 75 ++++++++++++++++++++++------------------ pithos/settings.d/00-apps.conf | 3 ++ 6 files changed, 176 insertions(+), 36 deletions(-) create mode 100644 pithos/im/api.py create mode 100644 pithos/im/faults.py diff --git a/pithos/im/api.py b/pithos/im/api.py new file mode 100644 index 0000000..97ae7ca --- /dev/null +++ b/pithos/im/api.py @@ -0,0 +1,62 @@ +from traceback import format_exc +from time import time, mktime +from django.conf import settings +from django.http import HttpResponse +from django.utils import simplejson as json + +from pithos.im.faults import BadRequest, Unauthorized, ServiceUnavailable +from pithos.im.models import User + +import datetime + +def render_fault(request, fault): + if settings.DEBUG or settings.TEST: + fault.details = format_exc(fault) + + request.serialization = 'text' + data = '\n'.join((fault.message, fault.details)) + '\n' + response = HttpResponse(data, status=fault.code) + return response + +def update_response_headers(response): + response['Content-Type'] = 'application/json; charset=UTF-8' + response['Content-Length'] = len(response.content) + +def authenticate(request): + # Normal Response Codes: 204 + # Error Response Codes: serviceUnavailable (503) + # badRequest (400) + # unauthorised (401) + try: + if request.method != 'GET': + raise BadRequest('Method not allowed.') + x_auth_token = request.META.get('HTTP_X_AUTH_TOKEN') + if not x_auth_token: + return render_fault(request, BadRequest('Missing X-Auth-Token')) + + try: + user = User.objects.get(auth_token=x_auth_token) + except User.DoesNotExist, e: + return render_fault(request, Unauthorized('Invalid X-Auth-Token')) + + # Check if the is active. + if user.state != 'ACTIVE': + return render_fault(request, Unauthorized('User inactive')) + + # Check if the token has expired. + if (time() - mktime(user.auth_token_expires.timetuple())) > 0: + return render_fault(request, Unauthorized('Authentication expired')) + + response = HttpResponse() + response.status=204 + user_info = user.__dict__ + for k,v in user_info.items(): + if isinstance(v, datetime.datetime): + user_info[k] = v.strftime('%a, %d-%b-%Y %H:%M:%S %Z') + user_info.pop('_state') + response.content = json.dumps(user_info) + update_response_headers(response) + return response + except BaseException, e: + fault = ServiceUnavailable('Unexpected error') + return render_fault(request, fault) \ No newline at end of file diff --git a/pithos/im/faults.py b/pithos/im/faults.py new file mode 100644 index 0000000..1975468 --- /dev/null +++ b/pithos/im/faults.py @@ -0,0 +1,51 @@ +# Copyright 2011 GRNET S.A. All rights reserved. +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# 1. Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials +# provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# The views and conclusions contained in the software and +# documentation are those of the authors and should not be +# interpreted as representing official policies, either expressed +# or implied, of GRNET S.A. + +def camelCase(s): + return s[0].lower() + s[1:] + +class Fault(Exception): + def __init__(self, message='', details='', name=''): + Exception.__init__(self, message, details, name) + self.message = message + self.details = details + self.name = name or camelCase(self.__class__.__name__) + +class BadRequest(Fault): + code = 400 + +class Unauthorized(Fault): + code = 401 + +class ServiceUnavailable(Fault): + code = 503 \ No newline at end of file diff --git a/pithos/im/urls.py b/pithos/im/urls.py index 35ffcfe..f2d51e5 100644 --- a/pithos/im/urls.py +++ b/pithos/im/urls.py @@ -100,3 +100,8 @@ if 'twitter' in settings.IM_MODULES: (r'^login/twitter/?$', 'twitter.login'), (r'^login/twitter/authenticated/?$', 'twitter.authenticated') ) + +urlpatterns += patterns('pithos.im.api', + (r'^authenticate/?$', 'authenticate') +) + diff --git a/pithos/lib/client.py b/pithos/lib/client.py index 4cc735a..d3b5498 100644 --- a/pithos/lib/client.py +++ b/pithos/lib/client.py @@ -932,7 +932,7 @@ def _prepare_headers(headers): headers[quote(k)] = quote(v, safe='/=,:@ *"') if type(v) == types.StringType else v return headers -def _handle_response(response, verbose, debug): +def _handle_response(response, verbose=False, debug=False): headers = response.getheaders() headers = dict((unquote(h), unquote(v)) for h,v in headers) @@ -952,4 +952,16 @@ def _handle_response(response, verbose, debug): raise Fault(data, int(response.status)) #print '**', response.status, headers, data, '\n' - return response.status, headers, data \ No newline at end of file + return response.status, headers, data + +def authenticate(authentication_host, token): + con = HTTPConnection(authentication_host) + kwargs = {} + kwargs['headers'] = {} + kwargs['headers']['X-Auth-Token'] = token + kwargs['headers']['Content-Length'] = 0 + + path = '/im/authenticate' + con.request('GET', path, **kwargs) + response = con.getresponse() + return _handle_response(response) \ No newline at end of file diff --git a/pithos/middleware/auth.py b/pithos/middleware/auth.py index 16afee7..9808907 100644 --- a/pithos/middleware/auth.py +++ b/pithos/middleware/auth.py @@ -33,51 +33,58 @@ from time import time, mktime from urllib import quote, unquote +from django.conf import settings +from django.utils import simplejson as json from pithos.im.models import User - +from pithos.lib.client import authenticate, Fault def get_user_from_token(token): + if not token: + return None + + host = settings.AUTHENTICATION_HOST try: - return User.objects.get(auth_token=token) - except User.DoesNotExist: + status, headers, user = authenticate(host, token) + return json.loads(user) + except Fault, f: return None - class AuthMiddleware(object): def process_request(self, request): - request.user = None - request.user_uniq = None - - # Try to find token in a parameter, in a request header, or in a cookie. - user = get_user_from_token(request.GET.get('X-Auth-Token')) - if not user: - user = get_user_from_token(request.META.get('HTTP_X_AUTH_TOKEN')) - if not user: - # Back from an im login target. - if request.GET.get('user', None): - token = request.GET.get('token', None) - if token: - request.set_auth_cookie = True - user = get_user_from_token(token) + try: + host, port = settings.AUTHENTICATION_HOST.split(':') + if request.META['SERVER_NAME'] and request.META['SERVER_PORT'] == port: + #bypass authentication + #if it is an authentication request + return + + request.user = None + request.user_uniq = None + + # Try to find token in a parameter, in a request header, or in a cookie. + user = get_user_from_token(request.GET.get('X-Auth-Token')) + if not user: + user = get_user_from_token(request.META.get('HTTP_X_AUTH_TOKEN')) if not user: - cookie_value = unquote(request.COOKIES.get('_pithos2_a', '')) - if cookie_value and '|' in cookie_value: - token = cookie_value.split('|', 1)[1] + # Back from an im login target. + if request.GET.get('user', None): + token = request.GET.get('token', None) + if token: + request.set_auth_cookie = True user = get_user_from_token(token) - if not user: - return - - # Check if the is active. - if user.state != 'ACTIVE': - return - - # Check if the token has expired. - if (time() - mktime(user.auth_token_expires.timetuple())) > 0: - return - - request.user = user - request.user_uniq = user.uniq + if not user: + cookie_value = unquote(request.COOKIES.get('_pithos2_a', '')) + if cookie_value and '|' in cookie_value: + token = cookie_value.split('|', 1)[1] + user = get_user_from_token(token) + if not user: + return + + request.user = user + request.user_uniq = user['uniq'] + except Exception, e: + print e def process_response(self, request, response): if getattr(request, 'user', None) and getattr(request, 'set_auth_cookie', False): diff --git a/pithos/settings.d/00-apps.conf b/pithos/settings.d/00-apps.conf index e5c8d50..bb0a280 100644 --- a/pithos/settings.d/00-apps.conf +++ b/pithos/settings.d/00-apps.conf @@ -25,3 +25,6 @@ INSTALLED_APPS = ( 'pithos.ui', 'south' ) + +#where astakos is hosted +AUTHENTICATION_HOST = '127.0.0.1:10000' -- 1.7.10.4