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 random import randint
44 from django.db import models
45 from django.contrib.auth.models import User, UserManager, Group
47 from astakos.im.settings import DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_CONNECTION
48 from astakos.im.queue.userevent import UserEvent
49 from synnefo.lib.queue import exchange_connect, exchange_send, exchange_close, Receipt
51 QUEUE_CLIENT_ID = 3 # Astakos.
53 logger = logging.getLogger(__name__)
55 class AstakosUser(User):
57 Extends ``django.contrib.auth.models.User`` by defining additional fields.
59 # Use UserManager to get the create_user method, etc.
60 objects = UserManager()
62 affiliation = models.CharField('Affiliation', max_length=255, blank=True)
63 provider = models.CharField('Provider', max_length=255, blank=True)
66 user_level = DEFAULT_USER_LEVEL
67 level = models.IntegerField('Inviter level', default=user_level)
68 invitations = models.IntegerField('Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
70 auth_token = models.CharField('Authentication Token', max_length=32,
71 null=True, blank=True)
72 auth_token_created = models.DateTimeField('Token creation date', null=True)
73 auth_token_expires = models.DateTimeField('Token expiration date', null=True)
75 updated = models.DateTimeField('Update date')
76 is_verified = models.BooleanField('Is verified?', default=False)
78 # ex. screen_name for twitter, eppn for shibboleth
79 third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
81 email_verified = models.BooleanField('Email verified?', default=False)
83 has_credits = models.BooleanField('Has credits?', default=False)
84 has_signed_terms = models.BooleanField('Agree with the terms?', default=False)
85 date_signed_terms = models.DateTimeField('Signed terms date', null=True)
87 __has_signed_terms = False
90 def __init__(self, *args, **kwargs):
91 super(AstakosUser, self).__init__(*args, **kwargs)
92 self.__has_signed_terms = self.has_signed_terms
94 self.__groupnames = [g.name for g in self.groups.all()]
96 self.is_active = False
100 return '%s %s' %(self.first_name, self.last_name)
103 def realname(self, value):
104 parts = value.split(' ')
106 self.first_name = parts[0]
107 self.last_name = parts[1]
109 self.last_name = parts[0]
112 def invitation(self):
114 return Invitation.objects.get(username=self.email)
115 except Invitation.DoesNotExist:
118 def save(self, update_timestamps=True, **kwargs):
119 if update_timestamps:
121 self.date_joined = datetime.now()
122 self.updated = datetime.now()
124 # update date_signed_terms if necessary
125 if self.__has_signed_terms != self.has_signed_terms:
126 self.date_signed_terms = datetime.now()
130 while not self.username:
131 username = uuid.uuid4().hex[:30]
133 AstakosUser.objects.get(username = username)
134 except AstakosUser.DoesNotExist, e:
135 self.username = username
136 if not self.provider:
137 self.provider = 'local'
138 report_user_event(self)
139 super(AstakosUser, self).save(**kwargs)
141 # set group if does not exist
142 groupname = 'shibboleth' if self.provider == 'shibboleth' else 'default'
143 if groupname not in self.__groupnames:
145 group = Group.objects.get(name = groupname)
146 self.groups.add(group)
147 except Group.DoesNotExist, e:
150 def renew_token(self):
152 md5.update(self.username)
153 md5.update(self.realname.encode('ascii', 'ignore'))
154 md5.update(asctime())
156 self.auth_token = b64encode(md5.digest())
157 self.auth_token_created = datetime.now()
158 self.auth_token_expires = self.auth_token_created + \
159 timedelta(hours=AUTH_TOKEN_DURATION)
161 def __unicode__(self):
164 class ApprovalTerms(models.Model):
166 Model for approval terms
169 date = models.DateTimeField('Issue date', db_index=True, default=datetime.now())
170 location = models.CharField('Terms location', max_length=255)
172 class Invitation(models.Model):
174 Model for registring invitations
176 inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
178 realname = models.CharField('Real name', max_length=255)
179 username = models.CharField('Unique ID', max_length=255, unique=True)
180 code = models.BigIntegerField('Invitation code', db_index=True)
181 #obsolete: we keep it just for transfering the data
182 is_accepted = models.BooleanField('Accepted?', default=False)
183 is_consumed = models.BooleanField('Consumed?', default=False)
184 created = models.DateTimeField('Creation date', auto_now_add=True)
185 #obsolete: we keep it just for transfering the data
186 accepted = models.DateTimeField('Acceptance date', null=True, blank=True)
187 consumed = models.DateTimeField('Consumption date', null=True, blank=True)
189 def __init__(self, *args, **kwargs):
190 super(Invitation, self).__init__(*args, **kwargs)
192 self.code = _generate_invitation_code()
195 self.is_consumed = True
196 self.consumed = datetime.now()
199 def __unicode__(self):
200 return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
202 def report_user_event(user):
203 def should_send(user):
204 # report event incase of new user instance
205 # or if specific fields are modified
208 db_instance = AstakosUser.objects.get(id = user.id)
209 for f in BILLING_FIELDS:
210 if (db_instance.__getattribute__(f) != user.__getattribute__(f)):
214 if QUEUE_CONNECTION and should_send(user):
215 eventType = 'create' if not user.id else 'modify'
216 body = UserEvent(QUEUE_CLIENT_ID, user, eventType, {}).format()
217 conn = exchange_connect(QUEUE_CONNECTION)
218 parts = urlparse(QUEUE_CONNECTION)
219 exchange = parts.path[1:]
220 routing_key = '%s.user' % exchange
221 exchange_send(conn, routing_key, body)
224 def _generate_invitation_code():
226 code = randint(1, 2L**63 - 1)
228 Invitation.objects.get(code=code)
229 # An invitation with this code already exists, try again
230 except Invitation.DoesNotExist: