1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials
14 # provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
38 from time import asctime
39 from datetime import datetime, timedelta
40 from base64 import b64encode
41 from urlparse import urlparse
42 from urllib import quote
43 from random import randint
44 from collections import defaultdict
46 from django.db import models, IntegrityError
47 from django.contrib.auth.models import User, UserManager, Group, Permission
48 from django.utils.translation import ugettext as _
49 from django.db import transaction
50 from django.core.exceptions import ValidationError
51 from django.db.models.signals import (
52 pre_save, post_save, post_syncdb, post_delete
54 from django.contrib.contenttypes.models import ContentType
56 from django.dispatch import Signal
57 from django.db.models import Q
58 from django.core.urlresolvers import reverse
59 from django.utils.http import int_to_base36
60 from django.contrib.auth.tokens import default_token_generator
61 from django.conf import settings
62 from django.utils.importlib import import_module
63 from django.core.validators import email_re
65 from astakos.im.settings import (DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL,
66 AUTH_TOKEN_DURATION, BILLING_FIELDS,
67 EMAILCHANGE_ACTIVATION_DAYS, LOGGING_LEVEL)
68 from astakos.im.endpoints.qh import (
69 register_users, send_quota, register_resources
71 from astakos.im import auth_providers
72 from astakos.im.endpoints.aquarium.producer import report_user_event
73 from astakos.im.functions import send_invitation
74 from astakos.im.tasks import propagate_groupmembers_quota
76 import astakos.im.messages as astakos_messages
78 logger = logging.getLogger(__name__)
80 DEFAULT_CONTENT_TYPE = None
82 content_type = ContentType.objects.get(app_label='im', model='astakosuser')
84 content_type = DEFAULT_CONTENT_TYPE
86 RESOURCE_SEPARATOR = '.'
90 class Service(models.Model):
91 name = models.CharField('Name', max_length=255, unique=True, db_index=True)
92 url = models.FilePathField()
93 icon = models.FilePathField(blank=True)
94 auth_token = models.CharField('Authentication Token', max_length=32,
95 null=True, blank=True)
96 auth_token_created = models.DateTimeField('Token creation date', null=True)
97 auth_token_expires = models.DateTimeField(
98 'Token expiration date', null=True)
100 def renew_token(self):
102 md5.update(self.name.encode('ascii', 'ignore'))
103 md5.update(self.url.encode('ascii', 'ignore'))
104 md5.update(asctime())
106 self.auth_token = b64encode(md5.digest())
107 self.auth_token_created = datetime.now()
108 self.auth_token_expires = self.auth_token_created + \
109 timedelta(hours=AUTH_TOKEN_DURATION)
116 return self.resource_set.all()
119 def resources(self, resources):
121 self.resource_set.create(**s)
123 def add_resource(self, service, resource, uplimit, update=True):
124 """Raises ObjectDoesNotExist, IntegrityError"""
125 resource = Resource.objects.get(service__name=service, name=resource)
127 AstakosUserQuota.objects.update_or_create(user=self,
129 defaults={'uplimit': uplimit})
131 q = self.astakosuserquota_set
132 q.create(resource=resource, uplimit=uplimit)
135 class ResourceMetadata(models.Model):
136 key = models.CharField('Name', max_length=255, unique=True, db_index=True)
137 value = models.CharField('Value', max_length=255)
140 class Resource(models.Model):
141 name = models.CharField('Name', max_length=255, unique=True, db_index=True)
142 meta = models.ManyToManyField(ResourceMetadata)
143 service = models.ForeignKey(Service)
144 desc = models.TextField('Description', null=True)
145 unit = models.CharField('Name', null=True, max_length=255)
146 group = models.CharField('Group', null=True, max_length=255)
149 return '%s%s%s' % (self.service, RESOURCE_SEPARATOR, self.name)
152 class GroupKind(models.Model):
153 name = models.CharField('Name', max_length=255, unique=True, db_index=True)
159 class AstakosGroup(Group):
160 kind = models.ForeignKey(GroupKind)
161 homepage = models.URLField(
162 'Homepage Url', max_length=255, null=True, blank=True)
163 desc = models.TextField('Description', null=True)
164 policy = models.ManyToManyField(
168 through='AstakosGroupQuota'
170 creation_date = models.DateTimeField(
172 default=datetime.now()
174 issue_date = models.DateTimeField('Issue date', null=True)
175 expiration_date = models.DateTimeField(
179 moderation_enabled = models.BooleanField(
180 'Moderated membership?',
183 approval_date = models.DateTimeField(
188 estimated_participants = models.PositiveIntegerField(
189 'Estimated #members',
193 max_participants = models.PositiveIntegerField(
194 'Maximum numder of participants',
200 def is_disabled(self):
201 if not self.approval_date:
206 def is_enabled(self):
209 if not self.issue_date:
211 if not self.expiration_date:
214 if self.issue_date > now:
216 if now >= self.expiration_date:
223 self.approval_date = datetime.now()
225 quota_disturbed.send(sender=self, users=self.approved_members)
226 propagate_groupmembers_quota.apply_async(
227 args=[self], eta=self.issue_date)
228 propagate_groupmembers_quota.apply_async(
229 args=[self], eta=self.expiration_date)
234 self.approval_date = None
236 quota_disturbed.send(sender=self, users=self.approved_members)
238 @transaction.commit_manually
239 def approve_member(self, person):
240 m, created = self.membership_set.get_or_create(person=person)
244 transaction.rollback()
249 # def disapprove_member(self, person):
250 # self.membership_set.remove(person=person)
254 q = self.membership_set.select_related().all()
255 return [m.person for m in q]
258 def approved_members(self):
259 q = self.membership_set.select_related().all()
260 return [m.person for m in q if m.is_approved]
265 for q in self.astakosgroupquota_set.select_related().all():
266 d[q.resource] += q.uplimit or inf
269 def add_policy(self, service, resource, uplimit, update=True):
270 """Raises ObjectDoesNotExist, IntegrityError"""
271 resource = Resource.objects.get(service__name=service, name=resource)
273 AstakosGroupQuota.objects.update_or_create(
276 defaults={'uplimit': uplimit}
279 q = self.astakosgroupquota_set
280 q.create(resource=resource, uplimit=uplimit)
284 return self.astakosgroupquota_set.select_related().all()
287 def policies(self, policies):
289 service = p.get('service', None)
290 resource = p.get('resource', None)
291 uplimit = p.get('uplimit', 0)
292 update = p.get('update', True)
293 self.add_policy(service, resource, uplimit, update)
297 return self.owner.all()
300 def owner_details(self):
301 return self.owner.select_related().all()
306 map(self.approve_member, l)
310 class AstakosUserManager(UserManager):
312 def get_auth_provider_user(self, provider, **kwargs):
314 Retrieve AstakosUser instance associated with the specified third party
317 kwargs = dict(map(lambda x: ('auth_providers__%s' % x[0], x[1]),
319 return self.get(auth_providers__module=provider, **kwargs)
321 class AstakosUser(User):
323 Extends ``django.contrib.auth.models.User`` by defining additional fields.
325 affiliation = models.CharField('Affiliation', max_length=255, blank=True,
328 # DEPRECATED FIELDS: provider, third_party_identifier moved in
329 # AstakosUserProvider model.
330 provider = models.CharField('Provider', max_length=255, blank=True,
332 # ex. screen_name for twitter, eppn for shibboleth
333 third_party_identifier = models.CharField('Third-party identifier',
334 max_length=255, null=True,
339 user_level = DEFAULT_USER_LEVEL
340 level = models.IntegerField('Inviter level', default=user_level)
341 invitations = models.IntegerField(
342 'Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
344 auth_token = models.CharField('Authentication Token', max_length=32,
345 null=True, blank=True)
346 auth_token_created = models.DateTimeField('Token creation date', null=True)
347 auth_token_expires = models.DateTimeField(
348 'Token expiration date', null=True)
350 updated = models.DateTimeField('Update date')
351 is_verified = models.BooleanField('Is verified?', default=False)
353 email_verified = models.BooleanField('Email verified?', default=False)
355 has_credits = models.BooleanField('Has credits?', default=False)
356 has_signed_terms = models.BooleanField(
357 'I agree with the terms', default=False)
358 date_signed_terms = models.DateTimeField(
359 'Signed terms date', null=True, blank=True)
361 activation_sent = models.DateTimeField(
362 'Activation sent data', null=True, blank=True)
364 policy = models.ManyToManyField(
365 Resource, null=True, through='AstakosUserQuota')
367 astakos_groups = models.ManyToManyField(
368 AstakosGroup, verbose_name=_('agroups'), blank=True,
369 help_text=_(astakos_messages.ASTAKOSUSER_GROUPS_HELP),
370 through='Membership')
372 __has_signed_terms = False
373 disturbed_quota = models.BooleanField('Needs quotaholder syncing',
374 default=False, db_index=True)
376 objects = AstakosUserManager()
378 owner = models.ManyToManyField(
379 AstakosGroup, related_name='owner', null=True)
382 unique_together = ("provider", "third_party_identifier")
384 def __init__(self, *args, **kwargs):
385 super(AstakosUser, self).__init__(*args, **kwargs)
386 self.__has_signed_terms = self.has_signed_terms
388 self.is_active = False
392 return '%s %s' % (self.first_name, self.last_name)
395 def realname(self, value):
396 parts = value.split(' ')
398 self.first_name = parts[0]
399 self.last_name = parts[1]
401 self.last_name = parts[0]
403 def add_permission(self, pname):
404 if self.has_perm(pname):
406 p, created = Permission.objects.get_or_create(codename=pname,
407 name=pname.capitalize(),
408 content_type=content_type)
409 self.user_permissions.add(p)
411 def remove_permission(self, pname):
412 if self.has_perm(pname):
414 p = Permission.objects.get(codename=pname,
415 content_type=content_type)
416 self.user_permissions.remove(p)
419 def invitation(self):
421 return Invitation.objects.get(username=self.email)
422 except Invitation.DoesNotExist:
425 def invite(self, email, realname):
426 inv = Invitation(inviter=self, username=email, realname=realname)
429 self.invitations = max(0, self.invitations - 1)
434 """Returns a dict with the sum of quota limits per resource"""
436 for q in self.policies:
437 d[q.resource] += q.uplimit or inf
438 for m in self.extended_groups:
439 if not m.is_approved:
444 for r, uplimit in g.quota.iteritems():
445 d[r] += uplimit or inf
446 # TODO set default for remaining
451 return self.astakosuserquota_set.select_related().all()
454 def policies(self, policies):
456 service = policies.get('service', None)
457 resource = policies.get('resource', None)
458 uplimit = policies.get('uplimit', 0)
459 update = policies.get('update', True)
460 self.add_policy(service, resource, uplimit, update)
462 def add_policy(self, service, resource, uplimit, update=True):
463 """Raises ObjectDoesNotExist, IntegrityError"""
464 resource = Resource.objects.get(service__name=service, name=resource)
466 AstakosUserQuota.objects.update_or_create(user=self,
468 defaults={'uplimit': uplimit})
470 q = self.astakosuserquota_set
471 q.create(resource=resource, uplimit=uplimit)
473 def remove_policy(self, service, resource):
474 """Raises ObjectDoesNotExist, IntegrityError"""
475 resource = Resource.objects.get(service__name=service, name=resource)
476 q = self.policies.get(resource=resource).delete()
479 def extended_groups(self):
480 return self.membership_set.select_related().all()
482 @extended_groups.setter
483 def extended_groups(self, groups):
485 for name in (groups or ()):
486 group = AstakosGroup.objects.get(name=name)
487 self.membership_set.create(group=group)
489 def save(self, update_timestamps=True, **kwargs):
490 if update_timestamps:
492 self.date_joined = datetime.now()
493 self.updated = datetime.now()
495 # update date_signed_terms if necessary
496 if self.__has_signed_terms != self.has_signed_terms:
497 self.date_signed_terms = datetime.now()
501 while not self.username:
502 username = self.email
504 AstakosUser.objects.get(username=username)
505 except AstakosUser.DoesNotExist:
506 self.username = username
508 self.validate_unique_email_isactive()
509 if self.is_active and self.activation_sent:
510 # reset the activation sent
511 self.activation_sent = None
513 super(AstakosUser, self).save(**kwargs)
515 def renew_token(self, flush_sessions=False, current_key=None):
517 md5.update(settings.SECRET_KEY)
518 md5.update(self.username)
519 md5.update(self.realname.encode('ascii', 'ignore'))
520 md5.update(asctime())
522 self.auth_token = b64encode(md5.digest())
523 self.auth_token_created = datetime.now()
524 self.auth_token_expires = self.auth_token_created + \
525 timedelta(hours=AUTH_TOKEN_DURATION)
527 self.flush_sessions(current_key)
528 msg = 'Token renewed for %s' % self.email
529 logger.log(LOGGING_LEVEL, msg)
531 def flush_sessions(self, current_key=None):
534 q = q.exclude(session_key=current_key)
536 keys = q.values_list('session_key', flat=True)
538 msg = 'Flushing sessions: %s' % ','.join(keys)
539 logger.log(LOGGING_LEVEL, msg, [])
540 engine = import_module(settings.SESSION_ENGINE)
542 s = engine.SessionStore(k)
545 def __unicode__(self):
546 return '%s (%s)' % (self.realname, self.email)
548 def conflicting_email(self):
549 q = AstakosUser.objects.exclude(username=self.username)
550 q = q.filter(email__iexact=self.email)
555 def validate_unique_email_isactive(self):
557 Implements a unique_together constraint for email and is_active fields.
559 q = AstakosUser.objects.all()
560 q = q.filter(email = self.email)
561 q = q.filter(is_active = self.is_active)
563 q = q.filter(~Q(id = self.id))
565 raise ValidationError({'__all__': [_(astakos_messages.UNIQUE_EMAIL_IS_ACTIVE_CONSTRAIN_ERR)]})
568 def signed_terms(self):
569 term = get_latest_terms()
572 if not self.has_signed_terms:
574 if not self.date_signed_terms:
576 if self.date_signed_terms < term.date:
577 self.has_signed_terms = False
578 self.date_signed_terms = None
583 def set_invitations_level(self):
585 Update user invitation level
587 level = self.invitation.inviter.level + 1
589 self.invitations = INVITATIONS_PER_LEVEL.get(level, 0)
591 def can_login_with_auth_provider(self, provider):
592 if not self.has_auth_provider(provider):
595 return auth_providers.get_provider(provider).is_available_for_login()
597 def can_add_auth_provider(self, provider, **kwargs):
598 provider_settings = auth_providers.get_provider(provider)
599 if not provider_settings.is_available_for_login():
602 if self.has_auth_provider(provider) and \
603 provider_settings.one_per_user:
606 if 'identifier' in kwargs:
608 # provider with specified params already exist
609 existing_user = AstakosUser.objects.get_auth_provider_user(provider,
611 except AstakosUser.DoesNotExist:
618 def can_remove_auth_provider(self, provider):
619 if len(self.get_active_auth_providers()) <= 1:
623 def can_change_password(self):
624 return self.has_auth_provider('local', auth_backend='astakos')
626 def has_auth_provider(self, provider, **kwargs):
627 return bool(self.auth_providers.filter(module=provider,
630 def add_auth_provider(self, provider, **kwargs):
631 if self.can_add_auth_provider(provider, **kwargs):
632 self.auth_providers.create(module=provider, active=True, **kwargs)
634 raise Exception('Cannot add provider')
636 def add_pending_auth_provider(self, pending):
638 Convert PendingThirdPartyUser object to AstakosUserAuthProvider entry for
641 if not isinstance(pending, PendingThirdPartyUser):
642 pending = PendingThirdPartyUser.objects.get(token=pending)
644 provider = self.add_auth_provider(pending.provider,
645 identifier=pending.third_party_identifier)
647 if email_re.match(pending.email) and pending.email != self.email:
648 self.additionalmail_set.get_or_create(email=pending.email)
653 def remove_auth_provider(self, provider, **kwargs):
654 self.auth_providers.get(module=provider, **kwargs).delete()
657 def get_resend_activation_url(self):
658 return reverse('send_activation', {'user_id': self.pk})
660 def get_activation_url(self, nxt=False):
661 url = "%s?auth=%s" % (reverse('astakos.im.views.activate'),
662 quote(self.auth_token))
664 url += "&next=%s" % quote(nxt)
667 def get_password_reset_url(self, token_generator=default_token_generator):
668 return reverse('django.contrib.auth.views.password_reset_confirm',
669 kwargs={'uidb36':int_to_base36(self.id),
670 'token':token_generator.make_token(self)})
672 def get_auth_providers(self):
673 return self.auth_providers.all()
675 def get_available_auth_providers(self):
677 Returns a list of providers available for user to connect to.
680 for module, provider_settings in auth_providers.PROVIDERS.iteritems():
681 if self.can_add_auth_provider(module):
682 providers.append(provider_settings(self))
686 def get_active_auth_providers(self):
688 for provider in self.auth_providers.active():
689 if auth_providers.get_provider(provider.module).is_available_for_login():
690 providers.append(provider)
694 def auth_providers_display(self):
695 return ",".join(map(lambda x:unicode(x), self.auth_providers.active()))
698 class AstakosUserAuthProviderManager(models.Manager):
701 return self.filter(active=True)
704 class AstakosUserAuthProvider(models.Model):
706 Available user authentication methods.
708 affiliation = models.CharField('Affiliation', max_length=255, blank=True,
709 null=True, default=None)
710 user = models.ForeignKey(AstakosUser, related_name='auth_providers')
711 module = models.CharField('Provider', max_length=255, blank=False,
713 identifier = models.CharField('Third-party identifier',
714 max_length=255, null=True,
716 active = models.BooleanField(default=True)
717 auth_backend = models.CharField('Backend', max_length=255, blank=False,
720 objects = AstakosUserAuthProviderManager()
723 unique_together = (('identifier', 'module', 'user'), )
727 return auth_providers.get_provider(self.module)
730 def details_display(self):
731 return self.settings.details_tpl % self.__dict__
733 def can_remove(self):
734 return self.user.can_remove_auth_provider(self.module)
736 def delete(self, *args, **kwargs):
737 ret = super(AstakosUserAuthProvider, self).delete(*args, **kwargs)
738 self.user.set_unusable_password()
743 return '<AstakosUserAuthProvider %s:%s>' % (self.module, self.identifier)
745 def __unicode__(self):
747 return "%s:%s" % (self.module, self.identifier)
748 if self.auth_backend:
749 return "%s:%s" % (self.module, self.auth_backend)
754 class Membership(models.Model):
755 person = models.ForeignKey(AstakosUser)
756 group = models.ForeignKey(AstakosGroup)
757 date_requested = models.DateField(default=datetime.now(), blank=True)
758 date_joined = models.DateField(null=True, db_index=True, blank=True)
761 unique_together = ("person", "group")
763 def save(self, *args, **kwargs):
765 if not self.group.moderation_enabled:
766 self.date_joined = datetime.now()
767 super(Membership, self).save(*args, **kwargs)
770 def is_approved(self):
778 if self.group.max_participants:
779 assert len(self.group.approved_members) + 1 <= self.group.max_participants, \
780 'Maximum participant number has been reached.'
781 self.date_joined = datetime.now()
783 quota_disturbed.send(sender=self, users=(self.person,))
785 def disapprove(self):
787 quota_disturbed.send(sender=self, users=(self.person,))
789 class AstakosQuotaManager(models.Manager):
790 def _update_or_create(self, **kwargs):
792 'update_or_create() must be passed at least one keyword argument'
793 obj, created = self.get_or_create(**kwargs)
794 defaults = kwargs.pop('defaults', {})
796 return obj, True, False
800 [(k, v) for k, v in kwargs.items() if '__' not in k])
801 params.update(defaults)
802 for attr, val in params.items():
803 if hasattr(obj, attr):
804 setattr(obj, attr, val)
805 sid = transaction.savepoint()
806 obj.save(force_update=True)
807 transaction.savepoint_commit(sid)
808 return obj, False, True
809 except IntegrityError, e:
810 transaction.savepoint_rollback(sid)
812 return self.get(**kwargs), False, False
813 except self.model.DoesNotExist:
816 update_or_create = _update_or_create
818 class AstakosGroupQuota(models.Model):
819 objects = AstakosQuotaManager()
820 limit = models.PositiveIntegerField('Limit', null=True) # obsolete field
821 uplimit = models.BigIntegerField('Up limit', null=True)
822 resource = models.ForeignKey(Resource)
823 group = models.ForeignKey(AstakosGroup, blank=True)
826 unique_together = ("resource", "group")
828 class AstakosUserQuota(models.Model):
829 objects = AstakosQuotaManager()
830 limit = models.PositiveIntegerField('Limit', null=True) # obsolete field
831 uplimit = models.BigIntegerField('Up limit', null=True)
832 resource = models.ForeignKey(Resource)
833 user = models.ForeignKey(AstakosUser)
836 unique_together = ("resource", "user")
839 class ApprovalTerms(models.Model):
841 Model for approval terms
844 date = models.DateTimeField(
845 'Issue date', db_index=True, default=datetime.now())
846 location = models.CharField('Terms location', max_length=255)
849 class Invitation(models.Model):
851 Model for registring invitations
853 inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
855 realname = models.CharField('Real name', max_length=255)
856 username = models.CharField('Unique ID', max_length=255, unique=True)
857 code = models.BigIntegerField('Invitation code', db_index=True)
858 is_consumed = models.BooleanField('Consumed?', default=False)
859 created = models.DateTimeField('Creation date', auto_now_add=True)
860 consumed = models.DateTimeField('Consumption date', null=True, blank=True)
862 def __init__(self, *args, **kwargs):
863 super(Invitation, self).__init__(*args, **kwargs)
865 self.code = _generate_invitation_code()
868 self.is_consumed = True
869 self.consumed = datetime.now()
872 def __unicode__(self):
873 return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
876 class EmailChangeManager(models.Manager):
877 @transaction.commit_on_success
878 def change_email(self, activation_key):
880 Validate an activation key and change the corresponding
883 If the key is valid and has not expired, return the ``User``
886 If the key is not valid or has expired, return ``None``.
888 If the key is valid but the ``User`` is already active,
891 After successful email change the activation record is deleted.
893 Throws ValueError if there is already
896 email_change = self.model.objects.get(
897 activation_key=activation_key)
898 if email_change.activation_key_expired():
899 email_change.delete()
900 raise EmailChange.DoesNotExist
901 # is there an active user with this address?
903 AstakosUser.objects.get(email__iexact=email_change.new_email_address)
904 except AstakosUser.DoesNotExist:
907 raise ValueError(_(astakos_messages.NEW_EMAIL_ADDR_RESERVED))
909 user = AstakosUser.objects.get(pk=email_change.user_id)
910 user.email = email_change.new_email_address
912 email_change.delete()
914 except EmailChange.DoesNotExist:
915 raise ValueError(_(astakos_messages.INVALID_ACTIVATION_KEY))
918 class EmailChange(models.Model):
919 new_email_address = models.EmailField(_(u'new e-mail address'),
920 help_text=_(astakos_messages.EMAIL_CHANGE_NEW_ADDR_HELP))
921 user = models.ForeignKey(
922 AstakosUser, unique=True, related_name='emailchange_user')
923 requested_at = models.DateTimeField(default=datetime.now())
924 activation_key = models.CharField(
925 max_length=40, unique=True, db_index=True)
927 objects = EmailChangeManager()
929 def activation_key_expired(self):
930 expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
931 return self.requested_at + expiration_date < datetime.now()
934 class AdditionalMail(models.Model):
936 Model for registring invitations
938 owner = models.ForeignKey(AstakosUser)
939 email = models.EmailField()
942 def _generate_invitation_code():
944 code = randint(1, 2L ** 63 - 1)
946 Invitation.objects.get(code=code)
947 # An invitation with this code already exists, try again
948 except Invitation.DoesNotExist:
952 def get_latest_terms():
954 term = ApprovalTerms.objects.order_by('-id')[0]
960 class PendingThirdPartyUser(models.Model):
962 Model for registring successful third party user authentications
964 third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
965 provider = models.CharField('Provider', max_length=255, blank=True)
966 email = models.EmailField(_('e-mail address'), blank=True, null=True)
967 first_name = models.CharField(_('first name'), max_length=30, blank=True)
968 last_name = models.CharField(_('last name'), max_length=30, blank=True)
969 affiliation = models.CharField('Affiliation', max_length=255, blank=True)
970 username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
971 token = models.CharField('Token', max_length=255, null=True, blank=True)
972 created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
975 unique_together = ("provider", "third_party_identifier")
979 return '%s %s' %(self.first_name, self.last_name)
982 def realname(self, value):
983 parts = value.split(' ')
985 self.first_name = parts[0]
986 self.last_name = parts[1]
988 self.last_name = parts[0]
990 def save(self, **kwargs):
993 while not self.username:
994 username = uuid.uuid4().hex[:30]
996 AstakosUser.objects.get(username = username)
997 except AstakosUser.DoesNotExist, e:
998 self.username = username
999 super(PendingThirdPartyUser, self).save(**kwargs)
1001 def generate_token(self):
1002 self.password = self.third_party_identifier
1003 self.last_login = datetime.now()
1004 self.token = default_token_generator.make_token(self)
1006 class SessionCatalog(models.Model):
1007 session_key = models.CharField(_('session key'), max_length=40)
1008 user = models.ForeignKey(AstakosUser, related_name='sessions', null=True)
1011 def create_astakos_user(u):
1013 AstakosUser.objects.get(user_ptr=u.pk)
1014 except AstakosUser.DoesNotExist:
1015 extended_user = AstakosUser(user_ptr_id=u.pk)
1016 extended_user.__dict__.update(u.__dict__)
1017 extended_user.save()
1018 except BaseException, e:
1022 def fix_superusers(sender, **kwargs):
1023 # Associate superusers with AstakosUser
1024 admins = User.objects.filter(is_superuser=True)
1026 create_astakos_user(u)
1029 def user_post_save(sender, instance, created, **kwargs):
1032 create_astakos_user(instance)
1035 def set_default_group(user):
1037 default = AstakosGroup.objects.get(name='default')
1039 group=default, person=user, date_joined=datetime.now()).save()
1040 except AstakosGroup.DoesNotExist, e:
1044 def astakosuser_pre_save(sender, instance, **kwargs):
1045 instance.aquarium_report = False
1046 instance.new = False
1048 db_instance = AstakosUser.objects.get(id=instance.id)
1049 except AstakosUser.DoesNotExist:
1051 instance.aquarium_report = True
1054 get = AstakosUser.__getattribute__
1055 l = filter(lambda f: get(db_instance, f) != get(instance, f),
1057 instance.aquarium_report = True if l else False
1060 def astakosuser_post_save(sender, instance, created, **kwargs):
1061 if instance.aquarium_report:
1062 report_user_event(instance, create=instance.new)
1065 set_default_group(instance)
1066 # TODO handle socket.error & IOError
1067 register_users((instance,))
1068 instance.renew_token()
1071 def resource_post_save(sender, instance, created, **kwargs):
1074 register_resources((instance,))
1077 def send_quota_disturbed(sender, instance, **kwargs):
1079 extend = users.extend
1080 if sender == Membership:
1081 if not instance.group.is_enabled:
1083 extend([instance.person])
1084 elif sender == AstakosUserQuota:
1085 extend([instance.user])
1086 elif sender == AstakosGroupQuota:
1087 if not instance.group.is_enabled:
1089 extend(instance.group.astakosuser_set.all())
1090 elif sender == AstakosGroup:
1091 if not instance.is_enabled:
1093 quota_disturbed.send(sender=sender, users=users)
1096 def on_quota_disturbed(sender, users, **kwargs):
1097 # print '>>>', locals()
1102 def renew_token(sender, instance, **kwargs):
1104 instance.renew_token()
1106 post_syncdb.connect(fix_superusers)
1107 post_save.connect(user_post_save, sender=User)
1108 pre_save.connect(astakosuser_pre_save, sender=AstakosUser)
1109 post_save.connect(astakosuser_post_save, sender=AstakosUser)
1110 post_save.connect(resource_post_save, sender=Resource)
1112 quota_disturbed = Signal(providing_args=["users"])
1113 quota_disturbed.connect(on_quota_disturbed)
1115 post_delete.connect(send_quota_disturbed, sender=AstakosGroup)
1116 post_delete.connect(send_quota_disturbed, sender=Membership)
1117 post_save.connect(send_quota_disturbed, sender=AstakosUserQuota)
1118 post_delete.connect(send_quota_disturbed, sender=AstakosUserQuota)
1119 post_save.connect(send_quota_disturbed, sender=AstakosGroupQuota)
1120 post_delete.connect(send_quota_disturbed, sender=AstakosGroupQuota)
1122 pre_save.connect(renew_token, sender=AstakosUser)
1123 pre_save.connect(renew_token, sender=Service)