integration with aquarium: Progress III & merge master
[astakos] / snf-astakos-app / astakos / im / models.py
1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
2
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10
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.
15
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.
28
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.
33
34 import hashlib
35 import uuid
36
37 from time import asctime
38 from datetime import datetime, timedelta
39 from base64 import b64encode
40
41 from django.db import models
42 from django.contrib.auth.models import User, UserManager
43
44 from astakos.im.settings import DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_CONNECTION
45 from synnefo.lib.queue import exchange_connect, exchange_send, exchange_close, Receipt
46
47 QUEUE_CLIENT_ID = 3 # Astakos.
48
49 class AstakosUser(User):
50     """
51     Extends ``django.contrib.auth.models.User`` by defining additional fields.
52     """
53     # Use UserManager to get the create_user method, etc.
54     objects = UserManager()
55     
56     affiliation = models.CharField('Affiliation', max_length=255, blank=True)
57     provider = models.CharField('Provider', max_length=255, blank=True)
58     
59     #for invitations
60     user_level = DEFAULT_USER_LEVEL
61     level = models.IntegerField('Inviter level', default=user_level)
62     invitations = models.IntegerField('Invitations left', default=INVITATIONS_PER_LEVEL[user_level])
63     
64     auth_token = models.CharField('Authentication Token', max_length=32,
65                                   null=True, blank=True)
66     auth_token_created = models.DateTimeField('Token creation date', null=True)
67     auth_token_expires = models.DateTimeField('Token expiration date', null=True)
68     
69     updated = models.DateTimeField('Update date')
70     is_verified = models.BooleanField('Is verified?', default=False)
71     
72     # ex. screen_name for twitter, eppn for shibboleth
73     third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
74     
75     @property
76     def realname(self):
77         return '%s %s' %(self.first_name, self.last_name)
78     
79     @realname.setter
80     def realname(self, value):
81         parts = value.split(' ')
82         if len(parts) == 2:
83             self.first_name = parts[0]
84             self.last_name = parts[1]
85         else:
86             self.last_name = parts[0]
87     
88     @property
89     def invitation(self):
90         try:
91             return Invitation.objects.get(username=self.email)
92         except Invitation.DoesNotExist:
93             return None
94     
95     def save(self, update_timestamps=True, **kwargs):
96         if update_timestamps:
97             if not self.id:
98                 self.date_joined = datetime.now()
99             self.updated = datetime.now()
100         if not self.id:
101             # set username
102             while not self.username:
103                 username =  uuid.uuid4().hex[:30]
104                 try:
105                     AstakosUser.objects.get(username = username)
106                 except AstakosUser.DoesNotExist, e:
107                     self.username = username
108             self.is_active = False
109             if not self.provider:
110                 self.provider = 'local'
111         report_user_event(self)
112         super(AstakosUser, self).save(**kwargs)
113     
114     def renew_token(self):
115         md5 = hashlib.md5()
116         md5.update(self.username)
117         md5.update(self.realname.encode('ascii', 'ignore'))
118         md5.update(asctime())
119         
120         self.auth_token = b64encode(md5.digest())
121         self.auth_token_created = datetime.now()
122         self.auth_token_expires = self.auth_token_created + \
123                                   timedelta(hours=AUTH_TOKEN_DURATION)
124     
125     def __unicode__(self):
126         return self.username
127
128 class Invitation(models.Model):
129     """
130     Model for registring invitations
131     """
132     inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
133                                 null=True)
134     realname = models.CharField('Real name', max_length=255)
135     username = models.CharField('Unique ID', max_length=255)
136     code = models.BigIntegerField('Invitation code', db_index=True)
137     #obsolete: we keep it just for transfering the data
138     is_accepted = models.BooleanField('Accepted?', default=False)
139     is_consumed = models.BooleanField('Consumed?', default=False)
140     created = models.DateTimeField('Creation date', auto_now_add=True)
141     #obsolete: we keep it just for transfering the data
142     accepted = models.DateTimeField('Acceptance date', null=True, blank=True)
143     consumed = models.DateTimeField('Consumption date', null=True, blank=True)
144     
145     def consume(self):
146         self.is_consumed = True
147         self.consumed = datetime.now()
148         self.save()
149         
150     def __unicode__(self):
151         return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
152
153 def report_user_event(user):
154     def should_send(user):
155         # report event incase of new user instance
156         # or if specific fields are modified
157         if not user.id:
158             return True
159         db_instance = AstakosUser.objects.get(id = user.id)
160         for f in BILLING_FIELDS:
161             if (db_instance.__getattribute__(f) != user.__getattribute__(f)):
162                 return True
163         return False
164     
165     if QUEUE_CONNECTION and should_send(user):
166         l = [[elem, str(user.__getattribute__(elem))] for elem in BILLING_FIELDS]
167         details = dict(l)
168         details['eventType'] = 'create' if not user.id else 'modify'
169         body = Receipt(QUEUE_CLIENT_ID, user.email, '', 0, details).format()
170         conn = exchange_connect(QUEUE_CONNECTION)
171         routing_key = QUEUE_CONNECTION.replace('#', body['id'])
172         exchange_send(conn, routing_key, body)
173         exchange_close(conn)