Merge branch 'refs/heads/0.6'
authorSofia Papagiannaki <papagian@gmail.com>
Fri, 1 Jun 2012 14:40:55 +0000 (17:40 +0300)
committerSofia Papagiannaki <papagian@gmail.com>
Fri, 1 Jun 2012 14:40:55 +0000 (17:40 +0300)
Conflicts:
docs/source/backends.rst
docs/source/conf.py
docs/source/devguide.rst

1  2 
snf-astakos-app/astakos/im/api/admin.py
snf-astakos-app/astakos/im/functions.py
snf-astakos-app/conf/20-snf-astakos-app-settings.conf

@@@ -39,18 -39,21 +39,21 @@@ from traceback import format_ex
  from time import time, mktime
  from urllib import quote
  from urlparse import urlparse
+ from collections import defaultdict
  
  from django.conf import settings
  from django.http import HttpResponse
  from django.utils import simplejson as json
  from django.core.urlresolvers import reverse
  
- from astakos.im.faults import BadRequest, Unauthorized, InternalServerError, Fault
- from astakos.im.models import AstakosUser
- from astakos.im.settings import CLOUD_SERVICES, INVITATIONS_ENABLED, COOKIE_NAME
+ from astakos.im.api.faults import *
+ from astakos.im.models import AstakosUser, Service
+ from astakos.im.settings import INVITATIONS_ENABLED, COOKIE_NAME, EMAILCHANGE_ENABLED
  from astakos.im.util import epoch
+ from astakos.im.api import _get_user_by_email, _get_user_by_username
  
  logger = logging.getLogger(__name__)
+ format = ('%a, %d %b %Y %H:%M:%S GMT')
  
  def render_fault(request, fault):
      if isinstance(fault, InternalServerError) and settings.DEBUG:
      response['Content-Length'] = len(response.content)
      return response
  
- def api_method(http_method=None, token_required=False, perms=[]):
+ def api_method(http_method=None, token_required=False, perms=None):
      """Decorator function for views that implement an API method."""
+     if not perms:
+         perms = []
      
      def decorator(func):
          @wraps(func)
                          raise Unauthorized('Access denied')
                      try:
                          user = AstakosUser.objects.get(auth_token=x_auth_token)
+                         ## Check if the token has expired.
+                         #if (time() - mktime(user.auth_token_expires.timetuple())) > 0:
+                         #    raise Unauthorized('Authentication expired')
                          if not user.has_perms(perms):
-                             raise Unauthorized('Unauthorized request')
+                             raise Forbidden('Unauthorized request')
                      except AstakosUser.DoesNotExist, e:
                          raise Unauthorized('Invalid X-Auth-Token')
                      kwargs['user'] = user
@@@ -123,8 -131,7 +131,8 @@@ def authenticate_old(request, user=None
                   'auth_token_created':user.auth_token_created.isoformat(),
                   'auth_token_expires':user.auth_token_expires.isoformat(),
                   'has_credits':user.has_credits,
 -                 'has_signed_terms':user.signed_terms()}
 +                 'has_signed_terms':user.signed_terms(),
 +                 'groups':[g.name for g in user.groups.all()]}
      response.content = json.dumps(user_info)
      response['Content-Type'] = 'application/json; charset=UTF-8'
      response['Content-Length'] = len(response.content)
@@@ -169,7 -176,9 +177,9 @@@ def authenticate(request, user=None)
  @api_method(http_method='GET')
  def get_services(request):
      callback = request.GET.get('callback', None)
-     data = json.dumps(CLOUD_SERVICES)
+     services = Service.objects.all()
+     data = tuple({'name':s.name, 'url':s.url, 'icon':s.icon} for s in services)
+     data = json.dumps(data)
      mimetype = 'application/json'
  
      if callback:
  
  @api_method()
  def get_menu(request, with_extra_links=False, with_signout=True):
-     exclude = []
      index_url = reverse('index')
      absolute = lambda (url): request.build_absolute_uri(url)
      l = [{ 'url': absolute(index_url), 'name': "Sign in"}]
              if user.has_usable_password():
                  l.append({ 'url': absolute(reverse('password_change')),
                            'name': "Change password" })
+             if EMAILCHANGE_ENABLED:
+                 l.append({'url':absolute(reverse('email_change')),
+                           'name': "Change email"})
              if INVITATIONS_ENABLED:
                  l.append({ 'url': absolute(reverse('astakos.im.views.invite')),
                            'name': "Invitations" })
  
      return HttpResponse(content=data, mimetype=mimetype)
  
- @api_method(http_method='GET', token_required=True, perms=['astakos.im.can_find_userid'])
- def find_userid(request):
-     # Normal Response Codes: 204
+ @api_method(http_method='GET', token_required=True, perms=['im.can_access_userinfo'])
+ def get_user_by_email(request, user=None):
+     # Normal Response Codes: 200
      # Error Response Codes: internalServerError (500)
      #                       badRequest (400)
      #                       unauthorised (401)
-     email = request.GET.get('email')
-     if not email:
-         raise BadRequest('Email missing')
-     try:
-         user = AstakosUser.objects.get(email = email, is_active=True)
-     except AstakosUser.DoesNotExist, e:
-         raise BadRequest('Invalid email')
-     else:
-         response = HttpResponse()
-         response.status=204
-         user_info = {'userid':user.username}
-         response.content = json.dumps(user_info)
-         response['Content-Type'] = 'application/json; charset=UTF-8'
-         response['Content-Length'] = len(response.content)
-         return response
- @api_method(http_method='GET', token_required=True, perms=['astakos.im.can_find_email'])
- def find_email(request):
-     # Normal Response Codes: 204
+     #                       forbidden (403)
+     #                       itemNotFound (404)
+     email = request.GET.get('name')
+     return _get_user_by_email(email)
+ @api_method(http_method='GET', token_required=True, perms=['im.can_access_userinfo'])
+ def get_user_by_username(request, user_id, user=None):
+     # Normal Response Codes: 200
      # Error Response Codes: internalServerError (500)
      #                       badRequest (400)
      #                       unauthorised (401)
-     userid = request.GET.get('userid')
-     if not userid:
-         raise BadRequest('Userid missing')
-     try:
-         user = AstakosUser.objects.get(username = userid)
-     except AstakosUser.DoesNotExist, e:
-         raise BadRequest('Invalid userid')
-     else:
-         response = HttpResponse()
-         response.status=204
-         user_info = {'userid':user.email}
-         response.content = json.dumps(user_info)
-         response['Content-Type'] = 'application/json; charset=UTF-8'
-         response['Content-Length'] = len(response.content)
-         return response
+     #                       forbidden (403)
+     #                       itemNotFound (404)
 -    return _get_user_by_username(user_id)
++    return _get_user_by_username(user_id)
@@@ -39,16 -39,42 +39,42 @@@ from django.template.loader import rend
  from django.core.mail import send_mail
  from django.core.urlresolvers import reverse
  from django.core.exceptions import ValidationError
+ from django.template import Context, loader
+ from django.contrib.auth import login as auth_login, logout as auth_logout
+ from django.http import HttpRequest
  
  from urllib import quote
  from urlparse import urljoin
  from smtplib import SMTPException
+ from datetime import datetime
+ from functools import wraps
  
- from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, SITENAME, BASEURL, DEFAULT_ADMIN_EMAIL
+ from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, \
+     SITENAME, BASEURL, DEFAULT_ADMIN_EMAIL, LOGGING_LEVEL
  from astakos.im.models import Invitation, AstakosUser
  
  logger = logging.getLogger(__name__)
  
+ def logged(func, msg):
+     @wraps(func)
+     def with_logging(*args, **kwargs):
+         email = ''
+         user = None
+         if len(args) == 2 and isinstance(args[1], AstakosUser):
+             user = args[1]
+         elif len(args) == 1 and isinstance(args[0], HttpRequest):
+             request = args[0]
+             user = request.user
+         email = user.email if user and user.is_authenticated() else ''
+         r = func(*args, **kwargs)
+         if LOGGING_LEVEL:
+             logger._log(LOGGING_LEVEL, msg % email, [])
+         return r
+     return with_logging
+ login = logged(auth_login, '%s logged in.')
+ logout = logged(auth_logout, '%s logged out.')
  def send_verification(user, template_name='im/activation_email.txt'):
      """
      Send email to user to verify his/her email and activate his/her account.
@@@ -57,7 -83,7 +83,7 @@@
      """
      url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('astakos.im.views.activate')),
                                      quote(user.auth_token),
 -                                    quote(BASEURL))
 +                                    quote(urljoin(BASEURL, reverse('astakos.im.views.index'))))
      message = render_to_string(template_name, {
              'user': user,
              'url': url,
          logger.exception(e)
          raise SendVerificationError()
      else:
-         logger.info('Sent activation %s', user)
+         msg = 'Sent activation %s' % user.email
+         logger._log(LOGGING_LEVEL, msg, [])
+ def send_activation(user, template_name='im/activation_email.txt'):
+     send_verification(user, template_name)
+     user.activation_sent = datetime.now()
+     user.save()
  
  def send_admin_notification(user, template_name='im/admin_notification.txt'):
      """
          logger.exception(e)
          raise SendNotificationError()
      else:
-         logger.info('Sent admin notification for user %s', user)
+         msg = 'Sent admin notification for user %s' % user.email
+         logger._log(LOGGING_LEVEL, msg, [])
  
  def send_invitation(invitation, template_name='im/invitation.txt'):
      """
          logger.exception(e)
          raise SendInvitationError()
      else:
-         logger.info('Sent invitation %s', invitation)
+         msg = 'Sent invitation %s' % invitation
+         logger._log(LOGGING_LEVEL, msg, [])
  
  def send_greeting(user, email_template_name='im/welcome_email.txt'):
      """
          logger.exception(e)
          raise SendGreetingError()
      else:
-         logger.info('Sent greeting %s', user)
+         msg = 'Sent greeting %s' % user.email
+         logger._log(LOGGING_LEVEL, msg, [])
  
  def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
      subject = _("Feedback from %s alpha2 testing" % SITENAME)
          logger.exception(e)
          raise SendFeedbackError()
      else:
-         logger.info('Sent feedback from %s', user.email)
+         msg = 'Sent feedback from %s' % user.email
+         logger._log(LOGGING_LEVEL, msg, [])
+ def send_change_email(ec, request, email_template_name='registration/email_change_email.txt'):
+     try:
+         url = reverse('email_change_confirm',
+                       kwargs={'activation_key':ec.activation_key})
+         url = request.build_absolute_uri(url)
+         t = loader.get_template(email_template_name)
+         c = {'url': url, 'site_name': SITENAME}
+         from_email = DEFAULT_FROM_EMAIL
+         send_mail(_("Email change on %s alpha2 testing") % SITENAME,
+             t.render(Context(c)), from_email, [ec.new_email_address])
+     except (SMTPException, socket.error) as e:
+         logger.exception(e)
+         raise ChangeEmailError()
+     else:
+         msg = 'Sent change email for %s' % ec.user.email
+         logger._log(LOGGING_LEVEL, msg, [])
  
  def activate(user, email_template_name='im/welcome_email.txt'):
      """
@@@ -214,4 -267,9 +267,9 @@@ class SendGreetingError(SendMailError)
  class SendFeedbackError(SendMailError):
      def __init__(self):
          self.message = _('Failed to send feedback')
-         super(SendFeedbackError, self).__init__()
+         super(SendFeedbackError, self).__init__()
+ class ChangeEmailError(SendMailError):
+     def __init__(self):
+         self.message = _('Failed to send change email')
 -        super(ChangeEmailError, self).__init__()
++        super(ChangeEmailError, self).__init__()
  # Set service name
  #ASTAKOS_SITENAME = 'GRNET Cloud'
  
- # Set cloud services appear in the horizontal bar
- #ASTAKOS_CLOUD_SERVICES = (
- #        { 'url':'/', 'name':'grnet cloud', 'id':'cloud', 'icon':'home-icon.png' },
- #        { 'url':'/okeanos.html', 'name':'~okeanos', 'id':'okeanos' },
- #        { 'url':'/ui/', 'name':'pithos+', 'id':'pithos' })
- #
  # Set recaptcha keys
  # http://www.google.com/recaptcha/whyrecaptcha 
  #ASTAKOS_RECAPTCHA_PUBLIC_KEY = ''
@@@ -82,6 -75,3 +75,6 @@@
  # e.g. {'https://cms.okeanos.grnet.gr/': 'Back to ~okeanos'}
  #ASTAKOS_PROFILE_EXTRA_LINKS = {}
  
 +# The number of unsuccessful login requests per minute allowed for a specific email
 +#ASTAKOS_RATELIMIT_RETRIES_ALLOWED = 3
 +