Commit bc431e96 authored by Olga Brani's avatar Olga Brani
Browse files

Merge branch 'devel-0.13' of https://code.grnet.gr/git/astakos into devel-0.13

Conflicts:
	snf-astakos-app/astakos/im/api/__init__.py
	snf-astakos-app/astakos/im/templates/im/astakosgroup_list.html
parents 8dfdb7bf 0125fccb
...@@ -107,7 +107,8 @@ class ActivationBackend(object): ...@@ -107,7 +107,8 @@ class ActivationBackend(object):
def handle_activation( def handle_activation(
self, user, activation_template_name='im/activation_email.txt', self, user, activation_template_name='im/activation_email.txt',
greeting_template_name='im/welcome_email.txt', greeting_template_name='im/welcome_email.txt',
admin_email_template_name='im/admin_notification.txt' admin_email_template_name='im/account_creation_notification.txt',
helpdesk_email_template_name='im/helpdesk_notification.txt'
): ):
""" """
If the user is already active returns immediately. If the user is already active returns immediately.
...@@ -123,10 +124,17 @@ class ActivationBackend(object): ...@@ -123,10 +124,17 @@ class ActivationBackend(object):
if self._is_preaccepted(user): if self._is_preaccepted(user):
if user.email_verified: if user.email_verified:
activate(user, greeting_template_name) activate(
user,
greeting_template_name,
helpdesk_email_template_name
)
return RegistationCompleted() return RegistationCompleted()
else: else:
send_activation(user, activation_template_name) send_activation(
user,
activation_template_name
)
return VerificationSent() return VerificationSent()
else: else:
send_account_creation_notification( send_account_creation_notification(
......
...@@ -169,38 +169,40 @@ def get_menu(request, with_extra_links=False, with_signout=True): ...@@ -169,38 +169,40 @@ def get_menu(request, with_extra_links=False, with_signout=True):
append(item(url=absolute(request, reverse('edit_profile')), append(item(url=absolute(request, reverse('edit_profile')),
name="My account")) name="My account"))
if with_extra_links: if with_extra_links:
# if user.has_usable_password() and user.provider in ('local', ''): if user.has_usable_password() and user.provider in ('local', ''):
# append(item( append(item(
# url=absolute(request, reverse('password_change')), url=absolute(request, reverse('password_change')),
# name="Change password")) name="Change password"))
# if EMAILCHANGE_ENABLED: if EMAILCHANGE_ENABLED:
# append(item( append(item(
# url=absolute(request, reverse('email_change')), url=absolute(request, reverse('email_change')),
# name="Change email")) name="Change email"))
# if INVITATIONS_ENABLED: if INVITATIONS_ENABLED:
# append(item( append(item(
# url=absolute(request, reverse('invite')), url=absolute(request, reverse('invite')),
# name="Invitations")) name="Invitations"))
append(item( append(item(
url=absolute(request, reverse('group_list')), url=absolute(request, reverse('group_list')),
name="Projects", name="Projects",
# submenu=(item( # submenu=(item(
# url=absolute(request, # url=absolute(request,
# reverse('group_list')), # reverse('group_list')),
# name="Overview"), # name="Overview"),
# item( # item(
# url=absolute(request, # url=absolute(request,
# reverse('group_create_list')), # reverse('group_create_list')),
# name="Create"), # name="Create"),
# item( # item(
# url=absolute(request, # url=absolute(request,
# reverse('group_search')), # reverse('group_search')),
# name="Join"),) # name="Join"),
)) # )
)
)
append(item( append(item(
url=absolute(request, reverse('resource_list')), url=absolute(request, reverse('resource_usage')),
name="Report")) name="Usage"))
append(item( append(item(
url=absolute(request, reverse('feedback')), url=absolute(request, reverse('feedback')),
name="Feedback")) name="Feedback"))
......
...@@ -110,7 +110,7 @@ class AstakosCallpoint(): ...@@ -110,7 +110,7 @@ class AstakosCallpoint():
b = get_backend() b = get_backend()
return b.list_users(filter) return b.list_users(filter)
def get_user_status(self, user_id): def get_user_usage(self, user_id):
b = get_backend() b = get_backend()
return b.get_resource_usage(user_id) return b.get_resource_usage(user_id)
......
...@@ -200,7 +200,7 @@ class AstakosAPI(Specificator): ...@@ -200,7 +200,7 @@ class AstakosAPI(Specificator):
) )
) )
def get_user_status( def get_user_usage(
self, self,
user_id=Nonnegative user_id=Nonnegative
): ):
......
...@@ -44,8 +44,7 @@ import logging ...@@ -44,8 +44,7 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# providers registry # providers registry
PROVIDERS = SortedDict() PROVIDERS = {}
_PROVIDERS = {}
class AuthProviderBase(type): class AuthProviderBase(type):
...@@ -60,7 +59,7 @@ class AuthProviderBase(type): ...@@ -60,7 +59,7 @@ class AuthProviderBase(type):
newcls = super(AuthProviderBase, cls).__new__(cls, name, bases, dct) newcls = super(AuthProviderBase, cls).__new__(cls, name, bases, dct)
if include: if include:
_PROVIDERS[type_id] = newcls PROVIDERS[type_id] = newcls
return newcls return newcls
...@@ -152,14 +151,23 @@ class ShibbolethAuthProvider(AuthProvider): ...@@ -152,14 +151,23 @@ class ShibbolethAuthProvider(AuthProvider):
login_prompt_template = 'im/auth/shibboleth_login_prompt.html' login_prompt_template = 'im/auth/shibboleth_login_prompt.html'
class TwitterAuthProvider(AuthProvider):
module = 'twitter'
title = _('Twitter')
description = _('Allows you to login to your account using your twitter '
'account')
add_prompt = _('Connect with your Twitter account.')
@property
def add_url(self):
return reverse('astakos.im.target.twitter.login')
login_template = 'im/auth/twitter_login.html'
login_prompt_template = 'im/auth/twitter_login_prompt.html'
def get_provider(id, user_obj=None, default=None): def get_provider(id, user_obj=None, default=None):
""" """
Return a provider instance from the auth providers registry. Return a provider instance from the auth providers registry.
""" """
return PROVIDERS.get(id, default)(user_obj) return PROVIDERS.get(id, default)(user_obj)
for module in astakos_settings.IM_MODULES:
if module in _PROVIDERS:
PROVIDERS[module] = _PROVIDERS[module]
...@@ -48,11 +48,14 @@ def im_modules(request): ...@@ -48,11 +48,14 @@ def im_modules(request):
return {'im_modules': IM_MODULES} return {'im_modules': IM_MODULES}
def auth_providers(request): def auth_providers(request):
active_auth_providers = filter(lambda p:p.module_enabled, active_auth_providers = []
AUTH_PROVIDERS.itervalues()) for module in IM_MODULES:
auth_providers = map(lambda p: p(), active_auth_providers) provider = AUTH_PROVIDERS.get(module, None)
return {'auth_providers': auth_providers, if provider:
'master_auth_provider': auth_providers[0]} active_auth_providers.append(provider())
return {'auth_providers': active_auth_providers,
'master_auth_provider': active_auth_providers[0]}
def next(request): def next(request):
return {'next': get_query(request).get('next', '')} return {'next': get_query(request).get('next', '')}
......
...@@ -163,7 +163,7 @@ def send_group_creation_notification(template_name, dictionary=None): ...@@ -163,7 +163,7 @@ def send_group_creation_notification(template_name, dictionary=None):
return _send_admin_notification(template_name, dictionary, subject=subject) return _send_admin_notification(template_name, dictionary, subject=subject)
def send_helpdesk_notification(user, template_name='im/account_notification.txt'): def send_helpdesk_notification(user, template_name='im/helpdesk_notification.txt'):
""" """
Send email to DEFAULT_CONTACT_EMAIL to notify for a new user activation. Send email to DEFAULT_CONTACT_EMAIL to notify for a new user activation.
...@@ -275,8 +275,12 @@ def send_change_email(ec, request, email_template_name='registration/email_chang ...@@ -275,8 +275,12 @@ def send_change_email(ec, request, email_template_name='registration/email_chang
logger.log(LOGGING_LEVEL, msg) logger.log(LOGGING_LEVEL, msg)
def activate(user, email_template_name='im/welcome_email.txt', def activate(
helpdesk_email_template_name='im/helpdesk_notification.txt', verify_email=False): user,
email_template_name='im/welcome_email.txt',
helpdesk_email_template_name='im/helpdesk_notification.txt',
verify_email=False
):
""" """
Activates the specific user and sends email. Activates the specific user and sends email.
......
...@@ -374,7 +374,7 @@ class AstakosUser(User): ...@@ -374,7 +374,7 @@ class AstakosUser(User):
default=False, db_index=True) default=False, db_index=True)
objects = AstakosUserManager() objects = AstakosUserManager()
owner = models.ManyToManyField( owner = models.ManyToManyField(
AstakosGroup, related_name='owner', null=True) AstakosGroup, related_name='owner', null=True)
...@@ -499,7 +499,7 @@ class AstakosUser(User): ...@@ -499,7 +499,7 @@ class AstakosUser(User):
if not self.id: if not self.id:
# set username # set username
self.username = self.email self.username = self.email
self.validate_unique_email_isactive() self.validate_unique_email_isactive()
if self.is_active and self.activation_sent: if self.is_active and self.activation_sent:
# reset the activation sent # reset the activation sent
...@@ -639,7 +639,7 @@ class AstakosUser(User): ...@@ -639,7 +639,7 @@ class AstakosUser(User):
provider = self.add_auth_provider(pending.provider, provider = self.add_auth_provider(pending.provider,
identifier=pending.third_party_identifier) identifier=pending.third_party_identifier)
if email_re.match(pending.email) and pending.email != self.email: if email_re.match(pending.email or '') and pending.email != self.email:
self.additionalmail_set.get_or_create(email=pending.email) self.additionalmail_set.get_or_create(email=pending.email)
pending.delete() pending.delete()
...@@ -1061,7 +1061,6 @@ def astakosuser_post_save(sender, instance, created, **kwargs): ...@@ -1061,7 +1061,6 @@ def astakosuser_post_save(sender, instance, created, **kwargs):
set_default_group(instance) set_default_group(instance)
# TODO handle socket.error & IOError # TODO handle socket.error & IOError
register_users((instance,)) register_users((instance,))
instance.renew_token()
def resource_post_save(sender, instance, created, **kwargs): def resource_post_save(sender, instance, created, **kwargs):
...@@ -1096,7 +1095,7 @@ def on_quota_disturbed(sender, users, **kwargs): ...@@ -1096,7 +1095,7 @@ def on_quota_disturbed(sender, users, **kwargs):
send_quota(users) send_quota(users)
def renew_token(sender, instance, **kwargs): def renew_token(sender, instance, **kwargs):
if not instance.id: if not instance.auth_token:
instance.renew_token() instance.renew_token()
post_syncdb.connect(fix_superusers) post_syncdb.connect(fix_superusers)
......
...@@ -5,7 +5,7 @@ from django.conf import settings ...@@ -5,7 +5,7 @@ from django.conf import settings
AUTH_TOKEN_DURATION = getattr(settings, 'ASTAKOS_AUTH_TOKEN_DURATION', 30 * 24) AUTH_TOKEN_DURATION = getattr(settings, 'ASTAKOS_AUTH_TOKEN_DURATION', 30 * 24)
# Authenticate via Twitter. # Authenticate via Twitter.
TWITTER_KEY = getattr(settings, 'ASTAKOS_TWITTER_KEY', '') TWITTER_TOKEN = getattr(settings, 'ASTAKOS_TWITTER_TOKEN', '')
TWITTER_SECRET = getattr(settings, 'ASTAKOS_TWITTER_SECRET', '') TWITTER_SECRET = getattr(settings, 'ASTAKOS_TWITTER_SECRET', '')
DEFAULT_USER_LEVEL = getattr(settings, 'ASTAKOS_DEFAULT_USER_LEVEL', 4) DEFAULT_USER_LEVEL = getattr(settings, 'ASTAKOS_DEFAULT_USER_LEVEL', 4)
......
...@@ -160,6 +160,7 @@ def login( ...@@ -160,6 +160,7 @@ def login(
extra_context['provider'] = 'shibboleth' extra_context['provider'] = 'shibboleth'
extra_context['token'] = user.token extra_context['token'] = user.token
extra_context['signup_url'] = reverse('shibboleth_signup', args=(user.token,))
return render_response( return render_response(
template, template,
......
# 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.
from django.http import HttpResponseBadRequest
from django.utils.translation import ugettext as _
from django.contrib import messages
from django.template import RequestContext
from django.views.decorators.http import require_http_methods
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.core.exceptions import ImproperlyConfigured
from django.shortcuts import get_object_or_404
from urlparse import urlunsplit, urlsplit
from astakos.im.util import prepare_response, get_context
from astakos.im.views import requires_anonymous, render_response, \
requires_auth_provider
from astakos.im.settings import ENABLE_LOCAL_ACCOUNT_MIGRATION, BASEURL
from astakos.im.models import AstakosUser, PendingThirdPartyUser
from astakos.im.forms import LoginForm
from astakos.im.activation_backends import get_backend, SimpleBackend
from astakos.im import settings
import astakos.im.messages as astakos_messages
import logging
logger = logging.getLogger(__name__)
import oauth2 as oauth
import cgi
consumer = oauth.Consumer(settings.TWITTER_TOKEN, settings.TWITTER_SECRET)
client = oauth.Client(consumer)
request_token_url = 'http://twitter.com/oauth/request_token'
access_token_url = 'http://twitter.com/oauth/access_token'
authenticate_url = 'http://twitter.com/oauth/authenticate'
@requires_auth_provider('twitter', login=True)
@require_http_methods(["GET", "POST"])
def login(request):
resp, content = client.request(request_token_url, "GET")
if resp['status'] != '200':
messages.error(request, 'Invalid Twitter response')
return HttpResponseRedirect(reverse('edit_profile'))
request.session['request_token'] = dict(cgi.parse_qsl(content))
url = "%s?oauth_token=%s" % (authenticate_url,
request.session['request_token']['oauth_token'])
return HttpResponseRedirect(url)
@requires_auth_provider('twitter', login=True)
@require_http_methods(["GET", "POST"])
def authenticated(
request,
template='im/third_party_check_local.html',
extra_context={}
):
if not 'request_token' in request.session:
messages.error(request, 'Twitter handshake failed')
return HttpResponseRedirect(reverse('edit_profile'))
token = oauth.Token(request.session['request_token']['oauth_token'],
request.session['request_token']['oauth_token_secret'])
client = oauth.Client(consumer, token)
# Step 2. Request the authorized access token from Twitter.
resp, content = client.request(access_token_url, "GET")
if resp['status'] != '200':
try:
del request.session['request_token']
except:
pass
messages.error(request, 'Invalid Twitter response')
return HttpResponseRedirect(reverse('edit_profile'))
access_token = dict(cgi.parse_qsl(content))
userid = access_token['user_id']
# an existing user accessed the view
if request.user.is_authenticated():
if request.user.has_auth_provider('twitter', identifier=userid):
return HttpResponseRedirect(reverse('edit_profile'))
# automatically add eppn provider to user
user = request.user
if not request.user.can_add_auth_provider('twitter',
identifier=userid):
messages.error(request, 'Account already exists.')
return HttpResponseRedirect(reverse('edit_profile'))
user.add_auth_provider('twitter', identifier=userid)
return HttpResponseRedirect(reverse('edit_profile'))
try:
# astakos user exists ?
user = AstakosUser.objects.get_auth_provider_user(
'twitter',
identifier=userid
)
if user.is_active:
# authenticate user
return prepare_response(request,
user,
request.GET.get('next'),
'renew' in request.GET)
elif not user.activation_sent:
message = _('Your request is pending activation')
#TODO: use astakos_messages
if not settings.MODERATION_ENABLED:
url = user.get_resend_activation_url()
msg_extra = _('<a href="%s">Resend activation email?</a>') % url
message = message + u' ' + msg_extra
messages.error(request, message)
return HttpResponseRedirect(reverse('login'))
else:
#TODO: use astakos_messages
message = _(u'Account disabled. Please contact support')
messages.error(request, message)
return HttpResponseRedirect(reverse('login'))
except AstakosUser.DoesNotExist, e:
#TODO: use astakos_messages
# eppn not stored in astakos models, create pending profile
user, created = PendingThirdPartyUser.objects.get_or_create(
third_party_identifier=userid,
provider='twitter',
)
# update pending user
user.affiliation = 'Twitter'
user.generate_token()
user.save()
extra_context['provider'] = 'twitter'
extra_context['token'] = user.token
extra_context['signup_url'] = reverse('twitter_signup', args=(user.token,))
return render_response(
template,
context_instance=get_context(request, extra_context)
)
@requires_auth_provider('twitter', login=True, create=True)
@require_http_methods(["GET"])
@requires_anonymous
def signup(
request,
token,
backend=None,
on_creation_template='im/third_party_registration.html',
extra_context={}):
extra_context = extra_context or {}
if not token:
#TODO: use astakos_messages
return HttpResponseBadRequest(_('Missing key parameter.'))
pending = get_object_or_404(PendingThirdPartyUser, token=token)
d = pending.__dict__
d.pop('_state', None)
d.pop('id', None)
d.pop('token', None)
d.pop('created', None)
user = AstakosUser(**d)
try:
backend = backend or get_backend(request)
except ImproperlyConfigured, e:
messages.error(request, e)
else:
extra_context['form'] = backend.get_signup_form(
provider='twitter',
instance=user
)
extra_context['provider'] = 'twitter'
extra_context['third_party_token'] = token
return render_response(
on_creation_template,
context_instance=get_context(request, extra_context)
)
...@@ -37,9 +37,10 @@ from celery.schedules import crontab ...@@ -37,9 +37,10 @@ from celery.schedules import crontab
from functools import wraps from functools import wraps
from astakos.im.endpoints.qh import send_quota from astakos.im.endpoints.qh import send_quota
from astakos.im.endpoints.aquarium.producer import (report_credits_event, from astakos.im.endpoints.aquarium.producer import (
report_user_event report_credits_event,
) report_user_event
)
from astakos.im.endpoints.aquarium.client import AquariumClient from astakos.im.endpoints.aquarium.client import AquariumClient
import logging import logging
......