Commit cc1fc541 authored by Giorgos Korfiatis's avatar Giorgos Korfiatis
Browse files

Merge branch 'feature-user-create' into develop

parents a2c6c210 d89626f2
......@@ -35,7 +35,7 @@ from django.utils.importlib import import_module
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext as _
from astakos.im.models import AstakosUser
from astakos.im import models
from astakos.im import functions
from astakos.im import settings
from astakos.im import forms
......@@ -90,9 +90,7 @@ class ActivationBackend(object):
>>> backend = get_backend()
>>> formCls = backend.get_signup_form(request.POST)
>>> if form.is_valid():
>>> user = form.save(commit=False)
>>> # this creates auth provider objects
>>> form.store_user(user)
>>> user = form.create_user()
>>> activation = backend.handle_registration(user)
>>> # activation.status is one of backend.Result.{*} activation result
>>> # types
......@@ -444,13 +442,11 @@ class InvitationsBackend(ActivationBackend):
initial_data = None
if request.method == 'GET':
if invitation:
# create a tmp user with the invitation realname
# to extract first and last name
u = AstakosUser(realname=invitation.realname)
first, last = models.split_realname(invitation.realname)
initial_data = {'email': invitation.username,
'inviter': invitation.inviter.realname,
'first_name': u.first_name,
'last_name': u.last_name,
'first_name': first,
'last_name': last,
'provider': provider}
else:
if provider == request.POST.get('provider', ''):
......
# Copyright 2013 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 uuid
import datetime
from astakos.im import models
def _finalize_astakosuser_object(user, has_signed_terms=False):
user.fix_username()
if has_signed_terms:
user.has_signed_terms = True
user.date_signed_terms = datetime.datetime.now()
user.renew_verification_code()
user.uuid = str(uuid.uuid4())
user.renew_token()
user.save()
def set_local_auth(user):
user.add_auth_provider('local', auth_backend='astakos')
def make_user(email, first_name="", last_name="", password=None,
has_signed_terms=False):
# delete previously unverified accounts
models.AstakosUser.objects.unverified_namesakes(email).delete()
user = models.AstakosUser(
email=email, first_name=first_name, last_name=last_name,
is_active=False)
if password is None:
user.set_unusable_password()
else:
user.set_password(password)
user.date_joined = datetime.datetime.now()
_finalize_astakosuser_object(user, has_signed_terms)
return user
def make_local_user(email, **kwargs):
user = make_user(email, **kwargs)
set_local_auth(user)
return user
def extend_superuser(user):
extended_user = models.AstakosUser(user_ptr_id=user.pk)
extended_user.__dict__.update(user.__dict__)
_finalize_astakosuser_object(extended_user, has_signed_terms=True)
set_local_auth(extended_user)
return extended_user
def fix_superusers():
admins = models.User.objects.filter(is_superuser=True)
fixed = []
for u in admins:
try:
models.AstakosUser.objects.get(user_ptr=u.pk)
except models.AstakosUser.DoesNotExist:
fixed.append(extend_superuser(u))
return fixed
# Copyright 2011-2012 GRNET S.A. All rights reserved.
# Copyright 2011, 2012, 2013 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
......@@ -58,6 +58,7 @@ from astakos.im.functions import send_change_email, submit_application, \
from astakos.im.util import reserved_verified_email, model_to_dict
from astakos.im import auth_providers
from astakos.im import settings
from astakos.im import auth
import astakos.im.messages as astakos_messages
......@@ -73,25 +74,7 @@ DOMAIN_VALUE_REGEX = re.compile(
re.IGNORECASE)
class StoreUserMixin(object):
def store_user(self, user, request=None):
"""
WARNING: this should be wrapped inside a transactional view/method.
"""
user.save()
self.post_store_user(user, request)
return user
def post_store_user(self, user, request):
"""
Interface method for descendant backends to be able to do stuff within
the transaction enabled by store_user.
"""
pass
class LocalUserCreationForm(UserCreationForm, StoreUserMixin):
class LocalUserCreationForm(UserCreationForm):
"""
Extends the built in UserCreationForm in several ways:
......@@ -177,58 +160,21 @@ class LocalUserCreationForm(UserCreationForm, StoreUserMixin):
raise forms.ValidationError(_(
astakos_messages.CAPTCHA_VALIDATION_ERR))
def post_store_user(self, user, request=None):
"""
Interface method for descendant backends to be able to do stuff within
the transaction enabled by store_user.
"""
user.add_auth_provider('local', auth_backend='astakos')
user.set_password(self.cleaned_data['password1'])
def save(self, commit=True, **kwargs):
"""
Saves the email, first_name and last_name properties, after the normal
save behavior is complete.
"""
user = super(LocalUserCreationForm, self).save(commit=False, **kwargs)
user.date_signed_terms = datetime.now()
user.renew_token()
if commit:
user.save(**kwargs)
logger.info('Created user %s', user.log_display)
return user
class InvitedLocalUserCreationForm(LocalUserCreationForm):
"""
Extends the LocalUserCreationForm: email is readonly.
"""
class Meta:
model = AstakosUser
fields = ("email", "first_name", "last_name", "has_signed_terms")
def __init__(self, *args, **kwargs):
"""
Changes the order of fields, and removes the username field.
"""
super(InvitedLocalUserCreationForm, self).__init__(*args, **kwargs)
#set readonly form fields
ro = ('email', 'username',)
for f in ro:
self.fields[f].widget.attrs['readonly'] = True
def create_user(self):
try:
data = self.cleaned_data
except AttributeError:
self.is_valid()
data = self.cleaned_data
def save(self, commit=True, **kwargs):
user = super(InvitedLocalUserCreationForm, self).save(commit=False,
**kwargs)
user.set_invitations_level()
user.email_verified = True
if commit:
user.save(**kwargs)
user = auth.make_local_user(
email=data['email'], password=data['password1'],
first_name=data['first_name'], last_name=data['last_name'],
has_signed_terms=True)
return user
class ThirdPartyUserCreationForm(forms.ModelForm, StoreUserMixin):
class ThirdPartyUserCreationForm(forms.ModelForm):
email = forms.EmailField(
label='Contact email',
help_text='This is needed for contact purposes. '
......@@ -292,70 +238,24 @@ class ThirdPartyUserCreationForm(forms.ModelForm, StoreUserMixin):
def _get_pending_user(self):
return PendingThirdPartyUser.objects.get(token=self.third_party_token)
def post_store_user(self, user, request=None):
def create_user(self):
try:
data = self.cleaned_data
except AttributeError:
self.is_valid()
data = self.cleaned_data
user = auth.make_user(
email=data["email"],
first_name=data["first_name"], last_name=data["last_name"],
has_signed_terms=True)
pending = self._get_pending_user()
provider = pending.get_provider(user)
provider.add_to_user()
pending.delete()
def save(self, commit=True, **kwargs):
user = super(ThirdPartyUserCreationForm, self).save(commit=False,
**kwargs)
user.set_unusable_password()
user.renew_token()
user.date_signed_terms = datetime.now()
if commit:
user.save(**kwargs)
logger.info('Created user %s' % user.log_display)
return user
class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
"""
Extends the ThirdPartyUserCreationForm: email is readonly.
"""
def __init__(self, *args, **kwargs):
"""
Changes the order of fields, and removes the username field.
"""
super(
InvitedThirdPartyUserCreationForm, self).__init__(*args, **kwargs)
#set readonly form fields
ro = ('email',)
for f in ro:
self.fields[f].widget.attrs['readonly'] = True
def save(self, commit=True, **kwargs):
user = \
super(InvitedThirdPartyUserCreationForm, self).save(commit=False,
**kwargs)
user.set_invitation_level()
user.email_verified = True
if commit:
user.save(**kwargs)
return user
class ShibbolethUserCreationForm(ThirdPartyUserCreationForm):
additional_email = forms.CharField(
widget=forms.HiddenInput(), label='', required=False)
def __init__(self, *args, **kwargs):
super(ShibbolethUserCreationForm, self).__init__(*args, **kwargs)
# copy email value to additional_mail in case user will change it
name = 'email'
field = self.fields[name]
self.initial['additional_email'] = self.initial.get(
name, field.initial)
self.initial['email'] = None
class InvitedShibbolethUserCreationForm(ShibbolethUserCreationForm,
InvitedThirdPartyUserCreationForm):
pass
class LoginForm(AuthenticationForm):
username = forms.EmailField(label=_("Email"))
recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
......
......@@ -34,16 +34,23 @@
from optparse import make_option
from datetime import datetime
from django.db import transaction
from django.core.management.base import NoArgsCommand, CommandError
from astakos.im.models import fix_superusers
from astakos.im.auth import fix_superusers
class Command(NoArgsCommand):
help = "Transform superusers created by syncdb into AstakosUser instances"
@transaction.commit_on_success
def handle(self, **options):
try:
fix_superusers()
fixed = fix_superusers()
count = len(fixed)
if count != 0:
self.stdout.write("Fixed %s superuser(s).\n" % count)
else:
self.stdout.write("No superuser needed a fix.\n")
except BaseException, e:
raise CommandError(e)
......@@ -34,14 +34,16 @@
from os.path import abspath
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from astakos.im.models import ApprovalTerms
from astakos.im.models import ApprovalTerms, AstakosUser
class Command(BaseCommand):
args = "<location>"
help = "Insert approval terms"
@transaction.commit_on_success
def handle(self, *args, **options):
if len(args) != 1:
raise CommandError("Invalid number of arguments")
......@@ -54,6 +56,9 @@ class Command(BaseCommand):
terms = ApprovalTerms(location=location)
terms.save()
AstakosUser.objects.select_for_update().\
filter(has_signed_terms=True).\
update(has_signed_terms=False, date_signed_terms=None)
msg = "Created term id %d" % (terms.id,)
self.stdout.write(msg + '\n')
# Copyright 2012 GRNET S.A. All rights reserved.
# Copyright 2012, 2013 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
......@@ -32,13 +32,15 @@
# or implied, of GRNET S.A.
from optparse import make_option
from datetime import datetime
from django.db import transaction
from django.core.management.base import BaseCommand, CommandError
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from astakos.im.models import AstakosUser
from astakos.im.models import AstakosUser, get_latest_terms
from astakos.im.auth import make_local_user
class Command(BaseCommand):
......@@ -83,13 +85,15 @@ class Command(BaseCommand):
except ValidationError:
raise CommandError("Invalid email")
has_signed_terms = not(get_latest_terms())
try:
u = AstakosUser(email=email,
first_name=first_name,
last_name=last_name,
is_superuser=options['is_superuser'])
u.set_password(password)
u.save()
user = make_local_user(
email, first_name=first_name, last_name=last_name,
password=password, has_signed_terms=has_signed_terms)
if options['is_superuser']:
user.is_superuser = True
user.save()
except BaseException, e:
raise CommandError(e)
......@@ -101,8 +105,7 @@ class Command(BaseCommand):
self.stdout.write('\n')
try:
u.add_auth_provider('local')
map(u.add_permission, options['permissions'])
map(u.add_group, options['groups'])
map(user.add_permission, options['permissions'])
map(user.add_group, options['groups'])
except BaseException, e:
raise CommandError(e)
......@@ -32,6 +32,7 @@
# or implied, of GRNET S.A.
import string
from datetime import datetime
from optparse import make_option
......@@ -138,6 +139,10 @@ class Command(BaseCommand):
make_option('--reject-reason',
dest='reject_reason',
help="Reason user got rejected"),
make_option('--sign-terms',
default=False,
action='store_true',
help="Sign terms"),
make_option('--base-quota',
dest='set_base_quota',
metavar='<resource> <capacity>',
......@@ -311,6 +316,10 @@ class Command(BaseCommand):
if options['renew_token']:
user.renew_token()
if options['sign_terms']:
user.has_signed_terms = True
user.date_signed_terms = datetime.now()
try:
user.save()
except ValidationError, e:
......@@ -352,7 +361,7 @@ class Command(BaseCommand):
m = "A user with this email address already exists."
raise CommandError(m)
user.email = newemail
user.set_email(newemail)
user.save()
def confirm(self):
......
This diff is collapsed.
This diff is collapsed.
......@@ -303,6 +303,14 @@ def get_resource_names():
return _RESOURCE_NAMES
def split_realname(value):
parts = value.split(' ')
if len(parts) == 2:
return parts
else:
return ('', value)
class AstakosUserManager(UserManager):
def get_auth_provider_user(self, provider, **kwargs):
......@@ -329,6 +337,11 @@ class AstakosUserManager(UserManager):
qextra = Q(**kwargs)
return self.filter((qemail | qusername) & qextra).exists()
def unverified_namesakes(self, email_or_username):
q = Q(email__iexact=email_or_username)
q |= Q(username__iexact=email_or_username)
return self.filter(q & Q(email_verified=False))
def verified_user_exists(self, email_or_username):
return self.user_exists(email_or_username, email_verified=True)
......@@ -446,7 +459,7 @@ class AstakosUser(User):
date_signed_terms = models.DateTimeField(_('Signed terms date'),
null=True, blank=True)
# permanent unique user identifier
uuid = models.CharField(max_length=255, null=True, blank=False,
uuid = models.CharField(max_length=255, null=False, blank=False,
unique=True)
policy = models.ManyToManyField(
......@@ -457,11 +470,6 @@ class AstakosUser(User):
objects = AstakosUserManager()
def __init__(self, *args, **kwargs):
super(AstakosUser, self).__init__(*args, **kwargs)
if not self.id:
self.is_active = False
@property
def realname(self):
return '%s %s' % (self.first_name, self.last_name)
......@@ -476,12 +484,9 @@ class AstakosUser(User):
@realname.setter
def realname(self, value):
parts = value.split(' ')
if len(parts) == 2:
self.first_name = parts[0]
self.last_name = parts[1]
else:
self.last_name = parts[0]
first, last = split_realname(value)
self.first_name = first
self.last_name = last
def add_permission(self, pname):
if self.has_perm(pname):
......@@ -524,30 +529,17 @@ class AstakosUser(User):
return AstakosUserQuota.objects.select_related("resource").\
get(user=self, resource__name=resource)
def update_uuid(self):
while not self.uuid:
uuid_val = str(uuid.uuid4())
try:
AstakosUser.objects.get(uuid=uuid_val)
except AstakosUser.DoesNotExist:
self.uuid = uuid_val
return self.uuid
def fix_username(self):
self.username = self.email.lower()
def set_email(self, email):
self.email = email
self.fix_username()
def save(self, update_timestamps=True, **kwargs):
if update_timestamps:
if not self.id:
self.date_joined = datetime.now()
self.updated = datetime.now()
self.update_uuid()
if not self.verification_code:
self.renew_verification_code()
# username currently matches email
if self.username != self.email.lower():
self.username = self.email.lower()
super(AstakosUser, self).save(**kwargs)
def renew_verification_code(self):
......@@ -631,19 +623,7 @@ class AstakosUser(User):
@property
def signed_terms(self):
term = get_latest_terms()
if not term:
return True
if not self.has_signed_terms:
return False
if not self.date_signed_terms:
return False
if self.date_signed_terms < term.date:
self.has_signed_terms = False
self.date_signed_terms = None
self.save()
return False
return True
return self.has_signed_terms
def set_invitations_level(self):
"""
......@@ -1098,9 +1078,10 @@ class EmailChangeManager(models.Manager):
else:
raise ValueError(_('The new email address is reserved.'))
# update user
user = AstakosUser.objects.get(pk=email_change.user_id)
user = AstakosUser.objects.select_for_update().\
get(pk=email_change.user_id)
old_email = user.email