Commit 4b100dbb authored by Kostas Papadimitriou's avatar Kostas Papadimitriou
Browse files

Merge branch 'master' into grnetstyles

Conflicts:
	astakos/im/context_processors.py
	astakos/im/templates/account_base.html
	astakos/im/templates/invitations.html
	astakos/im/templates/registration/password_change_form.html
	astakos/im/views.py
parents db78f0cb 1a3675a0
......@@ -33,12 +33,8 @@
from django import forms
from django.utils.translation import ugettext as _
from django.contrib.auth.forms import UserCreationForm
from django.conf import settings
from hashlib import new as newhasher
from astakos.im.models import AstakosUser
from astakos.im.util import get_or_create_user
from astakos.im.forms import LocalUserCreationForm
import logging
......
......@@ -39,7 +39,7 @@
<a href="{% url astakos.im.admin.views.invitations_list %}">Invitations</a>
</li>
<li{% ifequal tab "logout" %} class="active"{% endifequal %}>
<a href="{% url django.contrib.auth.views.logout %}">Logout</a>
<a href="{% url astakos.im.views.logout %}">Logout</a>
</li>
</ul>
{% endblock %}
......
......@@ -31,23 +31,17 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
import json
import logging
import socket
import csv
import sys
from datetime import datetime
from functools import wraps
from math import ceil
from random import randint
from smtplib import SMTPException
from hashlib import new as newhasher
from urllib import quote
from django.conf import settings
from django.core.mail import send_mail
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.utils.http import urlencode
......@@ -55,13 +49,10 @@ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from django.contrib import messages
from django.db import transaction
from django.contrib.auth.models import AnonymousUser
from django.contrib.sites.models import Site
from astakos.im.models import AstakosUser, Invitation
from astakos.im.util import isoformat, get_context, get_current_site
from astakos.im.util import get_context, get_current_site
from astakos.im.forms import *
from astakos.im.backends import get_backend
from astakos.im.views import render_response, index
from astakos.im.admin.forms import AdminProfileForm
from astakos.im.admin.forms import AdminUserCreationForm
......@@ -469,7 +460,7 @@ def invitations_export(request):
'Inviter Real Name',
'Is_accepted',
'Created',
'Accepted',])
'Consumed',])
invitations = Invitation.objects.order_by('id')
for inv in invitations:
......@@ -481,7 +472,7 @@ def invitations_export(request):
inv.inviter.realname.encode("utf-8"),
inv.is_accepted,
inv.created,
inv.accepted])
inv.consumed])
return response
......
......@@ -40,8 +40,6 @@ from django.utils import simplejson as json
from astakos.im.faults import BadRequest, Unauthorized, ServiceUnavailable
from astakos.im.models import AstakosUser
import datetime
def render_fault(request, fault):
if settings.DEBUG or settings.TEST:
fault.details = format_exc(fault)
......
from django.conf import settings
from django.contrib.auth.backends import ModelBackend
from django.core.validators import email_re
......
......@@ -37,13 +37,9 @@ from django.core.exceptions import ImproperlyConfigured
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from django.contrib.auth import authenticate, login
from django.contrib.auth.forms import UserCreationForm
from django.contrib.sites.models import Site
from django.contrib import messages
from django.shortcuts import redirect
from django.db import transaction
from django.core.urlresolvers import reverse
from smtplib import SMTPException
from urllib import quote
......@@ -159,9 +155,6 @@ class InvitationsBackend(object):
if self._is_preaccepted(user):
user.is_active = True
user.save()
# get the raw password from the form
user = authenticate(email=user.email, auth_token=user.auth_token)
login(self.request, user)
message = _('Registration completed. You can now login.')
else:
message = _('Registration completed. You will receive an email upon your account\'s activation.')
......@@ -223,7 +216,6 @@ class SimpleBackend(object):
** Settings **
* ACTIVATION_LOGIN_TARGET: Where users should activate their local account
* DEFAULT_CONTACT_EMAIL: service support email
* DEFAULT_FROM_EMAIL: from email
"""
......@@ -248,9 +240,9 @@ class SimpleBackend(object):
def _send_verification(request, user, template_name):
site = Site.objects.get_current()
baseurl = request.build_absolute_uri('/').rstrip('/')
url = settings.ACTIVATION_LOGIN_TARGET % (baseurl,
quote(user.auth_token),
quote(baseurl))
url = '%s%s?auth=%s&next=%s' % (baseurl,
reverse('astakos.im.target.activate'),
quote(user.auth_token))
message = render_to_string(template_name, {
'user': user,
'url': url,
......
......@@ -51,3 +51,4 @@ def media(request):
def current_url(request):
from django.core.urlresolvers import resolve
return {'CURRENT_URL' : request.get_full_path()}
......@@ -35,15 +35,11 @@ from django import forms
from django.utils.translation import ugettext as _
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordResetForm
from django.conf import settings
from django.core.validators import email_re
from django.conf import settings
from django.core.mail import send_mail
from django.contrib.auth.tokens import default_token_generator
from django.template import Context, loader
from django.utils.http import int_to_base36
from hashlib import new as newhasher
from astakos.im.models import AstakosUser
from astakos.im.util import get_current_site
......
......@@ -31,7 +31,6 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
import logging
import hashlib
import uuid
......@@ -42,7 +41,6 @@ from base64 import b64encode
from django.conf import settings
from django.db import models
from django.contrib.auth.models import User, UserManager
from django.utils.translation import ugettext_lazy as _
from astakos.im.interface import get_quota, set_quota
......@@ -115,7 +113,6 @@ class AstakosUser(User):
self.provider = 'local'
self.date_joined = datetime.now()
self.updated = datetime.now()
super(AstakosUser, self).save(**kwargs)
def renew_token(self):
......
# Copyright 2011-2012 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.
import logging
from datetime import datetime
from django.conf import settings
from django.http import HttpResponseBadRequest
from django.contrib.auth import authenticate
from astakos.im.models import Invitation
from astakos.im.target.util import prepare_response
from astakos.im.util import get_or_create_user
def login(request):
code = request.GET.get('code')
try:
invitation = Invitation.objects.get(code=code)
except Invitation.DoesNotExist:
return HttpResponseBadRequest('Wrong invitation code')
if not invitation.is_accepted:
invitation.is_accepted = True
invitation.accepted = datetime.now()
invitation.save()
logging.info('Accepted invitation %s', invitation)
user = get_or_create_user(invitation.uniq,
realname = invitation.realname,
affiliation = 'Invitation',
provider = 'invitation',
level = invitation.inviter.level + 1)
# in order to login the user we must call authenticate first
authenticate(email=user.email, auth_token=user.auth_token)
next = request.GET.get('next')
return prepare_response(request, user, next, 'renew' in request.GET)
......@@ -31,21 +31,17 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
from django.conf import settings
from django.template.loader import render_to_string
from django.http import HttpResponseBadRequest
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.contrib.auth import authenticate
from django.contrib import messages
from django.utils.translation import ugettext as _
from astakos.im.target.util import prepare_response, requires_anonymous
from astakos.im.util import prepare_response
from astakos.im.views import requires_anonymous
from astakos.im.models import AstakosUser
from astakos.im.forms import LoginForm
from urllib import unquote
from hashlib import new as newhasher
@requires_anonymous
def login(request, on_failure='login.html'):
......@@ -73,16 +69,3 @@ def login(request, on_failure='login.html'):
next = request.POST.get('next')
return prepare_response(request, user, next)
def activate(request):
token = request.GET.get('auth')
next = request.GET.get('next')
try:
user = AstakosUser.objects.get(auth_token=token)
except AstakosUser.DoesNotExist:
return HttpResponseBadRequest('No such user')
user.is_active = True
user.save()
user = authenticate(email=user.email, auth_token=user.auth_token)
return prepare_response(request, user, next, renew=True)
......@@ -31,7 +31,6 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from django.http import HttpResponseBadRequest
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
......@@ -41,8 +40,6 @@ from django.utils.http import urlencode
from urllib import quote
from urlparse import urlunsplit, urlsplit
from astakos.im.target.util import prepare_response
def login(request):
"""
If the request user is authenticated, redirects to `next` request parameter
......
......@@ -32,11 +32,9 @@
# or implied, of GRNET S.A.
from django.http import HttpResponseBadRequest
from django.core.urlresolvers import reverse
from django.contrib.auth import authenticate
from astakos.im.target.util import prepare_response
from astakos.im.util import get_or_create_user
from astakos.im.util import get_or_create_user, prepare_response
from astakos.im.views import requires_anonymous
class Tokens:
# these are mapped by the Shibboleth SP software
......@@ -48,7 +46,7 @@ class Tokens:
SHIB_EP_AFFILIATION = "HTTP_SHIB_EP_AFFILIATION"
SHIB_SESSION_ID = "HTTP_SHIB_SESSION_ID"
@requires_anonymous
def login(request):
tokens = request.META
......@@ -69,8 +67,6 @@ def login(request):
affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
user = get_or_create_user(eppn, realname=realname, affiliation=affiliation, provider='shibboleth', level=0)
# in order to login the user we must call authenticate first
user = authenticate(email=user.email, auth_token=user.auth_token)
return prepare_response(request,
user,
request.GET.get('next'),
......
......@@ -35,22 +35,18 @@
import oauth2 as oauth
import urlparse
import traceback
from django.conf import settings
from django.http import HttpResponse
from django.utils import simplejson as json
from django.contrib.auth import authenticate
from django.contrib import messages
from django.shortcuts import redirect
from astakos.im.target.util import prepare_response, requires_anonymous
from astakos.im.util import get_or_create_user, get_context
from astakos.im.util import get_context, prepare_response
from astakos.im.models import AstakosUser, Invitation
from astakos.im.views import render_response, create_user
from astakos.im.backends import get_backend
from astakos.im.views import render_response, requires_anonymous
from astakos.im.forms import LocalUserCreationForm, ThirdPartyUserCreationForm
from astakos.im.faults import BadRequest
from astakos.im.backends import get_backend
# It's probably a good idea to put your consumer's OAuth token and
# OAuth secret into your project's settings.
......@@ -64,7 +60,7 @@ access_token_url = 'http://twitter.com/oauth/access_token'
authenticate_url = 'http://twitter.com/oauth/authenticate'
@requires_anonymous
def login(request, template_name='signup.html', extra_context={}):
def login(request, extra_context={}):
# store invitation code and email
request.session['email'] = request.GET.get('email')
request.session['invitation_code'] = request.GET.get('code')
......@@ -89,7 +85,7 @@ def login(request, template_name='signup.html', extra_context={}):
return response
@requires_anonymous
def authenticated(request, backend=None, template_name='login.html', extra_context={}):
def authenticated(request, backend=None, login_template='login.html', on_signup_failure='signup.html', on_signup_success='signup_complete.html', extra_context={}):
# Step 1. Use the request token in the session to build a new client.
data = request.session.get('Twitter-Request-Token')
if not data:
......@@ -138,8 +134,7 @@ def authenticated(request, backend=None, template_name='login.html', extra_conte
user = None
email = request.session.pop('email')
# signup mode
if email:
if email: # signup mode
if not reserved_screen_name(screen_name):
try:
user = AstakosUser.objects.get(email = email)
......@@ -148,25 +143,30 @@ def authenticated(request, backend=None, template_name='login.html', extra_conte
post_data = {'provider':'Twitter', 'affiliation':'twitter',
'third_party_identifier':screen_name}
form = ThirdPartyUserCreationForm({'email':email})
return create_user(request, form, backend, post_data, next, template_name, extra_context)
return create_user(request, form, backend, post_data, next, on_signup_failure, on_signup_success, extra_context)
else:
status = messages.ERROR
message = '%s@twitter is already registered' % screen_name
messages.add_message(request, messages.ERROR, message)
else:
# login mode
prefix = 'Invited' if request.session['invitation_code'] else ''
suffix = 'UserCreationForm'
for provider in settings.IM_MODULES:
main = provider.capitalize() if provider == 'local' else 'ThirdParty'
formclass = '%s%s%s' % (prefix, main, suffix)
extra_context['%s_form' % provider] = globals()[formclass]()
return render_response(on_signup_failure,
context_instance=get_context(request, extra_context))
else: # login mode
try:
user = AstakosUser.objects.get(third_party_identifier = screen_name,
provider = 'Twitter')
except AstakosUser.DoesNotExist:
messages.add_message(request, messages.ERROR, 'Not registered user')
if user and user.is_active:
#in order to login the user we must call authenticate first
user = authenticate(email=user.email, auth_token=user.auth_token)
return prepare_response(request, user, next)
elif user and not user.is_active:
messages.add_message(request, messages.ERROR, 'Inactive account: %s' % user.email)
return render_response(template_name,
return render_response(login_template,
form = LocalUserCreationForm(),
context_instance=get_context(request, extra_context))
......@@ -176,4 +176,60 @@ def reserved_screen_name(screen_name):
third_party_identifier=screen_name)
return True
except AstakosUser.DoesNotExist, e:
return False
\ No newline at end of file
return False
def create_user(request, form, backend=None, post_data={}, next = None, on_failure='signup.html', on_success='signup_complete.html', extra_context={}):
"""
Create a user.
The user activation will be delegated to the backend specified by the ``backend`` keyword argument
if present, otherwise to the ``astakos.im.backends.InvitationBackend``
if settings.INVITATIONS_ENABLED is True or ``astakos.im.backends.SimpleBackend`` if not
(see backends);
Upon successful user creation if ``next`` url parameter is present the user is redirected there
otherwise renders the ``on_success`` template (if exists) or signup_complete.html.
On unsuccessful creation, renders the ``on_failure`` template (if exists) or signup.html with an error message.
**Arguments**
``on_failure``
A custom template to render in case of failure. This is optional;
if not specified, this will default to ``signup.html``.
``on_success``
A custom template to render in case of success. This is optional;
if not specified, this will default to ``signup_complete.html``.
``extra_context``
An dictionary of variables to add to the template context.
**Template:**
signup.html or ``on_failure`` keyword argument.
signup_complete.html or ``on_success`` keyword argument.
"""
try:
if not backend:
backend = get_backend(request)
if form.is_valid():
status, message, user = backend.signup(form)
if status == messages.SUCCESS:
for k,v in post_data.items():
setattr(user,k, v)
user.save()
if user.is_active:
return prepare_response(request, user, next=next)
messages.add_message(request, status, message)
return render_response(on_success,
context_instance=get_context(request, extra_context))
else:
messages.add_message(request, messages.ERROR, form.errors)
except (Invitation.DoesNotExist, ValueError), e:
messages.add_message(request, messages.ERROR, e)
for provider in settings.IM_MODULES:
extra_context['%s_form' % provider] = backend.get_signup_form(provider)
return render_response(on_failure,
form = LocalUserCreationForm(),
context_instance=get_context(request, extra_context))
\ No newline at end of file
# Copyright 2011-2012 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.
import datetime
from urlparse import urlsplit, urlunsplit
from urllib import quote
from functools import wraps
from django.http import HttpResponse
from django.utils.http import urlencode
from django.core.urlresolvers import reverse
from django.conf import settings
from django.contrib.auth import login, logout
def prepare_response(request, user, next='', renew=False, skip_login=False):
"""Return the unique username and the token
as 'X-Auth-User' and 'X-Auth-Token' headers,
or redirect to the URL provided in 'next'
with the 'user' and 'token' as parameters.
Reissue the token even if it has not yet
expired, if the 'renew' parameter is present
or user has not a valid token.
"""
renew = renew or (not user.auth_token)
renew = renew or (user.auth_token_expires and user.auth_token_expires < datetime.datetime.now())
if renew:
user.renew_token()
user.save()
if next:
# TODO: Avoid redirect loops.
parts = list(urlsplit(next))
if not parts[1] or (parts[1] and request.get_host() != parts[1]):
parts[3] = urlencode({'user': user.email, 'token': user.auth_token})
next = urlunsplit(parts)
if settings.FORCE_PROFILE_UPDATE and not user.is_verified and not user.is_superuser:
params = ''
if next:
params = '?' + urlencode({'next': next})
next = reverse('astakos.im.views.edit_profile') + params
# user login
if not skip_login:
login(request, user)
response = HttpResponse()
# set cookie
expire_fmt = user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
cookie_value = quote(user.email + '|' + user.auth_token)
response.set_cookie(settings.COOKIE_NAME, value=cookie_value,
expires=expire_fmt, path='/',
domain = settings.COOKIE_DOMAIN)
if not next:
next = reverse('astakos.im.views.index')
response['Location'] = next
response.status_code = 302
return response
def requires_anonymous(func):
"""
Decorator checkes whether the request.user is an Anonymous and if not
logouts the request.user.
"""
@wraps(func)
def wrapper(request, *args):
if not request.user.is_anonymous():
logout(request)
return func(request, *args)
return wrapper
......@@ -3,7 +3,8 @@
{% load formatters %}
{% block body %}
<h3>You have {{ user.invitations }} invitation{{ user.invitations|pluralize }} left.</h3>
<h3>You have {{ inviter.invitations }} invitation{{ inviter.invitations|pluralize }} left.</h3>