Commit 0bf959de authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

Merge remote-tracking branch 'origin/0.12' into devel-0.13

Conflicts:
	snf-astakos-app/astakos/im/activation_backends.py
	snf-astakos-app/astakos/im/api/admin.py
	snf-astakos-app/astakos/im/auth_backends.py
	snf-astakos-app/astakos/im/forms.py
	snf-astakos-app/astakos/im/functions.py
	snf-astakos-app/astakos/im/management/commands/group-list.py
	snf-astakos-app/astakos/im/management/commands/invitation-list.py
	snf-astakos-app/astakos/im/management/commands/service-list.py
	snf-astakos-app/astakos/im/management/commands/user-add.py
	snf-astakos-app/astakos/im/management/commands/user-list.py
	snf-astakos-app/astakos/im/middleware.py
	snf-astakos-app/astakos/im/models.py
	snf-astakos-app/astakos/im/settings.py
	snf-astakos-app/astakos/im/target/local.py
	snf-astakos-app/astakos/im/target/redirect.py
	snf-astakos-app/astakos/im/target/shibboleth.py
	snf-astakos-app/astakos/im/urls.py
	snf-astakos-app/astakos/im/util.py
	snf-astakos-app/astakos/im/views.py
	snf-astakos-app/conf/20-snf-astakos-app-settings.conf~
parents d9ab2975 743ac2c1
......@@ -124,6 +124,7 @@ ASTAKOS_PAGINATE_BY 10
ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN True Enforce token renewal on password change/reset. If set to False, user can optionally decide
whether to renew the token or not.
ASTAKOS_ENABLE_LOCAL_ACCOUNT_MIGRATION True Permit local account migration to third party account
=========================================== ============================================================================= ===========================================================================================
Administrator functions
......
......@@ -36,13 +36,12 @@ from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext as _
from astakos.im.models import AstakosUser
from astakos.im.forms import LocalUserCreationForm, ShibbolethUserCreationForm
from astakos.im.util import get_invitation
from astakos.im.functions import (send_verification, send_activation,
send_account_creation_notification,
send_group_creation_notification, activate)
from astakos.im.settings import (INVITATIONS_ENABLED, MODERATION_ENABLED,
SITENAME, RE_USER_EMAIL_PATTERNS
from astakos.im.functions import (
send_activation, send_account_creation_notification, activate
)
from astakos.im.settings import (
INVITATIONS_ENABLED, MODERATION_ENABLED, RE_USER_EMAIL_PATTERNS
)
import astakos.im.messages as astakos_messages
......@@ -102,17 +101,14 @@ class ActivationBackend(object):
if provider == request.POST.get('provider', ''):
initial_data = request.POST
return globals()[formclass](initial_data, instance=instance, request=request)
def handle_activation(self, user,
activation_template_name='im/activation_email.txt',
greeting_template_name='im/welcome_email.txt',
admin_email_template_name='im/account_notification.txt',
switch_accounts_email_template_name='im/switch_accounts_email.txt'):
def handle_activation(
self, user, activation_template_name='im/activation_email.txt',
greeting_template_name='im/welcome_email.txt',
admin_email_template_name='im/admin_notification.txt'
):
"""
If the user is already active returns immediately.
If the user is not active and there is another account associated with
the specific email, it sends an informative email to the user whether
wants to switch to this account.
If the user is preaccepted and the email is verified, the account is
activated automatically. Otherwise, if the email is not verified,
it sends a verification email to the user.
......@@ -122,10 +118,7 @@ class ActivationBackend(object):
try:
if user.is_active:
return RegistationCompleted()
if user.conflicting_email():
send_verification(user, switch_accounts_email_template_name)
return SwitchAccountsVerificationSent(user.email)
if self._is_preaccepted(user):
if user.email_verified:
activate(user, greeting_template_name)
......@@ -195,8 +188,8 @@ class InvitationsBackend(ActivationBackend):
def _is_preaccepted(self, user):
"""
If there is a valid, not-consumed invitation code for the specific user
returns True else returns False.
Extends _is_preaccepted and if there is a valid, not-consumed invitation
code for the specific user returns True else returns False.
"""
if super(InvitationsBackend, self)._is_preaccepted(user):
return True
......@@ -233,20 +226,13 @@ class VerificationSent(ActivationResult):
message = _(astakos_messages.VERIFICATION_SENT)
super(VerificationSent, self).__init__(message)
class SwitchAccountsVerificationSent(ActivationResult):
def __init__(self, email):
message = _(astakos_messages.SWITCH_ACCOUNT_LINK_SENT)
super(SwitchAccountsVerificationSent, self).__init__(message)
class NotificationSent(ActivationResult):
def __init__(self):
message = _(astakos_messages.NOTIFACATION_SENT)
message = _(astakos_messages.NOTIFICATION_SENT)
super(NotificationSent, self).__init__(message)
class RegistationCompleted(ActivationResult):
def __init__(self):
message = _(astakos_messages.REGISTRATION_COMPLETED)
super(RegistationCompleted, self).__init__(message)
super(RegistationCompleted, self).__init__(message)
\ No newline at end of file
......@@ -156,18 +156,9 @@ def get_services(request):
@api_method()
def get_menu(request, with_extra_links=False, with_signout=True):
user = request.user
if not isinstance(user, AstakosUser):
cookie = unquote(request.COOKIES.get(COOKIE_NAME, ''))
email = cookie.partition('|')[0]
try:
if email:
user = AstakosUser.objects.get(email=email, is_active=True)
except AstakosUser.DoesNotExist:
pass
if not isinstance(user, AstakosUser):
index_url = reverse('index')
l = [{'url': absolute(request, index_url), 'name': "Sign in"}]
else:
index_url = reverse('index')
l = [{'url': absolute(request, index_url), 'name': "Sign in"}]
if user.is_authenticated():
l = []
append = l.append
item = MenuItem
......@@ -178,22 +169,18 @@ def get_menu(request, with_extra_links=False, with_signout=True):
append(item(url=absolute(request, reverse('edit_profile')),
name="My account"))
if with_extra_links:
"""
if user.has_usable_password() and user.provider in ('local', ''):
append(item(
url=absolute(request, reverse('password_change')),
name="Change password"))
"""
if EMAILCHANGE_ENABLED:
append(item(
url=absolute(request, reverse('email_change')),
name="Change email"))
"""
if INVITATIONS_ENABLED:
append(item(
url=absolute(request, reverse('invite')),
name="Invitations"))
"""
append(item(
url=absolute(request, reverse('group_list')),
......@@ -216,14 +203,12 @@ def get_menu(request, with_extra_links=False, with_signout=True):
append(item(
url=absolute(request, reverse('feedback')),
name="Feedback"))
"""
append(item(
url=absolute(request, reverse('billing')),
name="Billing"))
append(item(
url=absolute(request, reverse('timeline')),
name="Timeline"))
"""
if with_signout:
append(item(
url=absolute(request, reverse('logout')),
......
......@@ -41,7 +41,8 @@ from django.utils import simplejson as json
from astakos.im.api.faults import (
Fault, Unauthorized, InternalServerError, BadRequest,
Forbidden)
Forbidden
)
from astakos.im.api import render_fault, _get_user_by_email, _get_user_by_username
from astakos.im.models import AstakosUser
from astakos.im.util import epoch
......
......@@ -206,8 +206,10 @@ class DjangoBackend(BaseBackend):
rejected = []
append = rejected.append
for r in recipients:
email = r.get('email')
realname = r.get('realname')
try:
user.invite(r.get('email'), r.get('realname'))
user.invite(email, realname)
except (IntegrityError, SMTPException), e:
append((email, e))
return rejected
......
# 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
import socket
from smtplib import SMTPException
from django.conf import settings
from django.core.mail import send_mail
from django.template.loader import render_to_string
import astakos.im.messages as astakos_messages
from astakos.im.settings import LOGGING_LEVEL
logger = logging.getLogger(__name__)
def _send_admin_notification(template_name,
dictionary=None,
subject='alpha2 testing notification',):
......@@ -29,7 +77,6 @@ class EmailNotification(Notification):
sender,
recipients
)
)
class Notification(object):
def __init__(self, sender, recipients, subject, message):
......@@ -39,4 +86,12 @@ class Notification(object):
self.message = message
def send(self):
pass
\ No newline at end of file
pass
class SendMailError(Exception):
pass
class SendNotificationError(SendMailError):
def __init__(self):
self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
super(SendNotificationError, self).__init__()
......@@ -34,20 +34,9 @@
from astakos.im.api.spec import AstakosAPI
from astakos.im.api.backends import get_backend
from commissioning import (Callpoint,
# CommissionException,
# CorruptedError, InvalidDataError,
# InvalidKeyError, NoEntityError,
# NoQuantityError, NoCapacityError,
# ExportLimitError, ImportLimitError
)
# from commissioning.utils.newname import newname
# from django.db.models import Model, BigIntegerField, CharField, ForeignKey, Q
# from django.db import transaction, IntegrityError
# from .models import (Holder, Entity, Policy, Holding,
# Commission, Provision, ProvisionLog, now)
from commissioning import CorruptedError
from django.db import transaction
class AstakosCallpoint():
......
from commissioning.specificator import (
CanonifyException, SpecifyException,
Specificator, Null, Integer, Text,
Tuple, ListOf, Dict, Args)
Specificator, Integer, Text, ListOf
)
class Name(Text):
......@@ -411,10 +410,10 @@ class AstakosAPI(Specificator):
def list_resource_units(self):
return ListOf(Name)
def get_approval_terms(term=Nonnegative):
def get_approval_terms(self, term=Nonnegative):
return Text()
def add_approval_terms(location=Filepath):
def add_approval_terms(self, location=Filepath):
return Nonnegative
# def change_emails():
......
......@@ -35,6 +35,11 @@ from django.contrib.auth.backends import ModelBackend
from django.core.validators import email_re
from astakos.im.models import AstakosUser
from astakos.im.settings import LOGGING_LEVEL
import logging
logger = logging.getLogger(__name__)
class TokenBackend(ModelBackend):
......@@ -48,6 +53,9 @@ class TokenBackend(ModelBackend):
return user
except AstakosUser.DoesNotExist:
return None
else:
msg = 'Invalid token during authentication for %s' % email
logger._log(LOGGING_LEVEL, msg, [])
def get_user(self, user_id):
try:
......@@ -66,10 +74,12 @@ class EmailBackend(ModelBackend):
def authenticate(self, username=None, password=None):
#If username is an email address, then try to pull it up
if email_re.search(username):
try:
user = AstakosUser.objects.get(email=username, is_active=True)
except AstakosUser.DoesNotExist:
users = AstakosUser.objects.filter(email=username)
if not users:
return None
for user in users:
if user.check_password(password):
return user
else:
#We have a non-email address username we
#should try username
......@@ -79,6 +89,10 @@ class EmailBackend(ModelBackend):
return None
if user.check_password(password):
return user
else:
msg = 'Invalid password during authentication for %s' % username
logger._log(LOGGING_LEVEL, msg, [])
def get_user(self, user_id):
try:
......
......@@ -31,9 +31,11 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, IM_STATIC_URL, \
LOGIN_MESSAGES, SIGNUP_MESSAGES, PROFILE_MESSAGES, \
from astakos.im.settings import (
IM_MODULES, INVITATIONS_ENABLED, IM_STATIC_URL,
LOGIN_MESSAGES, SIGNUP_MESSAGES, PROFILE_MESSAGES,
GLOBAL_MESSAGES, PROFILE_EXTRA_LINKS
)
from astakos.im.api import get_menu
from astakos.im.util import get_query
from astakos.im.models import GroupKind
......
# 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 urllib import quote, unquote
from django.contrib.auth.models import AnonymousUser
from django.http import HttpRequest
from django.utils.translation import ugettext as _
from astakos.im.settings import (
COOKIE_NAME, COOKIE_DOMAIN, COOKIE_SECURE, LOGGING_LEVEL
)
import astakos.im.messages as astakos_messages
logger = logging.getLogger(__name__)
class Cookie():
def __init__(self, request, response=None):
cookies = getattr(request, 'COOKIES', {})
cookie = unquote(cookies.get(COOKIE_NAME, ''))
self.email, sep, self.auth_token = cookie.partition('|')
self.request = request
self.response = response
@property
def email(self):
return getattr(self, 'email', '')
@property
def auth_token(self):
return getattr(self, 'auth_token', '')
@property
def is_set(self):
no_token = not self.auth_token
return not no_token
@property
def is_valid(self):
return self.email == getattr(self.user, 'email', '') and \
self.auth_token == getattr(self.user, 'auth_token', '')
@property
def user(self):
return getattr(self.request, 'user', AnonymousUser())
def __set(self):
if not self.response:
raise ValueError(_(astakos_messages.NO_RESPONSE))
user = self.user
expire_fmt = user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
cookie_value = quote(user.email + '|' + user.auth_token)
self.response.set_cookie(
COOKIE_NAME, value=cookie_value, expires=expire_fmt, path='/',
domain=COOKIE_DOMAIN, secure=COOKIE_SECURE
)
msg = 'Cookie [expiring %(auth_token_expires)s] set for %(email)s' % user.__dict__
logger._log(LOGGING_LEVEL, msg, [])
def __delete(self):
if not self.response:
raise ValueError(_(astakos_messages.NO_RESPONSE))
self.response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
msg = 'Cookie deleted for %(email)s' % self.__dict__
logger._log(LOGGING_LEVEL, msg, [])
def fix(self, response=None):
self.response = response or self.response
if self.user.is_authenticated():
if not self.is_set or not self.is_valid:
self.__set()
else:
if self.is_set:
self.__delete()
\ No newline at end of file
......@@ -54,7 +54,7 @@ logging.basicConfig(format='%(asctime)s [%(levelname)s] %(name)s %(message)s',
logger = logging.getLogger('endpoint.aquarium')
def wrapper(func):
def call(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not QUEUE_CONNECTION:
......@@ -73,7 +73,7 @@ def wrapper(func):
return wrapper
@wrapper
@call
def report_user_event(user, create=False):
eventType = 'create' if not create else 'modify'
body = UserEvent(QUEUE_CLIENT_ID, user.email, user.is_active, eventType, {}
......@@ -82,7 +82,7 @@ def report_user_event(user, create=False):
return body, routing_key
@wrapper
@call
def report_user_credits_event(user):
body = Receipt(QUEUE_CLIENT_ID, user.email, INSTANCE_ID, RESOURCE,
DEFAULT_CREDITS, details={}
......
......@@ -31,12 +31,10 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
import socket
import logging
import itertools
from functools import wraps
from itertools import tee
from django.utils.translation import ugettext as _
......
......@@ -44,17 +44,17 @@ from django.utils.http import int_to_base36
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.utils.encoding import smart_str
from django.forms.extras.widgets import SelectDateWidget
from django.conf import settings
from astakos.im.models import (AstakosUser, EmailChange, AstakosGroup,
Invitation, Membership, GroupKind, Resource,
get_latest_terms, RESOURCE_SEPARATOR)
from astakos.im.settings import (INVITATIONS_PER_LEVEL, BASEURL, SITENAME,
RECAPTCHA_PRIVATE_KEY, RECAPTCHA_ENABLED,
DEFAULT_CONTACT_EMAIL, LOGGING_LEVEL,
PASSWORD_RESET_EMAIL_SUBJECT,
NEWPASSWD_INVALIDATE_TOKEN)
from astakos.im.models import (
AstakosUser, EmailChange, AstakosGroup, Invitation, GroupKind,
Resource, PendingThirdPartyUser, get_latest_terms, RESOURCE_SEPARATOR
)
from astakos.im.settings import (
INVITATIONS_PER_LEVEL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY,
RECAPTCHA_ENABLED, DEFAULT_CONTACT_EMAIL, LOGGING_LEVEL,
PASSWORD_RESET_EMAIL_SUBJECT, NEWPASSWD_INVALIDATE_TOKEN
)
from astakos.im.widgets import DummyWidget, RecaptchaWidget
from astakos.im.functions import send_change_email
......@@ -91,9 +91,8 @@ class LocalUserCreationForm(UserCreationForm):
"""
Changes the order of fields, and removes the username field.
"""
request = kwargs.get('request', None)
request = kwargs.pop('request', None)
if request:
kwargs.pop('request')
self.ip = request.META.get('REMOTE_ADDR',
request.META.get('HTTP_X_REAL_IP', None))
......@@ -152,7 +151,6 @@ class LocalUserCreationForm(UserCreationForm):
save behavior is complete.
"""
user = super(LocalUserCreationForm, self).save(commit=False)
user.renew_token()
if commit:
user.save()
logger.log(LOGGING_LEVEL, 'Created user %s' % user.email)
......@@ -190,10 +188,18 @@ class InvitedLocalUserCreationForm(LocalUserCreationForm):
class ThirdPartyUserCreationForm(forms.ModelForm):
id = forms.CharField(
widget=forms.HiddenInput(),
label='',
required=False
)
third_party_identifier = forms.CharField(
widget=forms.HiddenInput(),
label=''
)
class Meta:
model = AstakosUser
fields = ("email", "first_name", "last_name",
"third_party_identifier", "has_signed_terms")
fields = ['id', 'email', 'third_party_identifier', 'first_name', 'last_name']
def __init__(self, *args, **kwargs):
"""
......@@ -202,24 +208,24 @@ class ThirdPartyUserCreationForm(forms.ModelForm):
self.request = kwargs.get('request', None)
if self.request:
kwargs.pop('request')
latest_terms = get_latest_terms()
if latest_terms:
self._meta.fields.append('has_signed_terms')
super(ThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['email', 'first_name', 'last_name',
'third_party_identifier']
if get_latest_terms():
if latest_terms:
self.fields.keyOrder.append('has_signed_terms')
#set readonly form fields
ro = ["third_party_identifier"]
for f in ro:
self.fields[f].widget.attrs['readonly'] = True
if 'has_signed_terms'<