Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 09e7393c

History | View | Annotate | Download (10.1 kB)

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
import logging
37
import json
38

    
39
from time import asctime
40
from datetime import datetime, timedelta
41
from base64 import b64encode
42
from urlparse import urlparse, urlunparse
43
from random import randint
44

    
45
from django.db import models
46
from django.contrib.auth.models import User, UserManager, Group
47
from django.utils.translation import ugettext as _
48
from django.core.exceptions import ValidationError
49

    
50
from astakos.im.settings import DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_CONNECTION
51

    
52
QUEUE_CLIENT_ID = 3 # Astakos.
53

    
54
logger = logging.getLogger(__name__)
55

    
56
class AstakosUser(User):
57
    """
58
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
59
    """
60
    # Use UserManager to get the create_user method, etc.
61
    objects = UserManager()
62

    
63
    affiliation = models.CharField('Affiliation', max_length=255, blank=True)
64
    provider = models.CharField('Provider', max_length=255, blank=True)
65

    
66
    #for invitations
67
    user_level = DEFAULT_USER_LEVEL
68
    level = models.IntegerField('Inviter level', default=user_level)
69
    invitations = models.IntegerField('Invitations left', default=INVITATIONS_PER_LEVEL.get(user_level, 0))
70

    
71
    auth_token = models.CharField('Authentication Token', max_length=32,
72
                                  null=True, blank=True)
73
    auth_token_created = models.DateTimeField('Token creation date', null=True)
74
    auth_token_expires = models.DateTimeField('Token expiration date', null=True)
75

    
76
    updated = models.DateTimeField('Update date')
77
    is_verified = models.BooleanField('Is verified?', default=False)
78

    
79
    # ex. screen_name for twitter, eppn for shibboleth
80
    third_party_identifier = models.CharField('Third-party identifier', max_length=255, null=True, blank=True)
81

    
82
    email_verified = models.BooleanField('Email verified?', default=False)
83

    
84
    has_credits = models.BooleanField('Has credits?', default=False)
85
    has_signed_terms = models.BooleanField('Agree with the terms?', default=False)
86
    date_signed_terms = models.DateTimeField('Signed terms date', null=True, blank=True)
87
    
88
    __has_signed_terms = False
89
    __groupnames = []
90
    
91
    def __init__(self, *args, **kwargs):
92
        super(AstakosUser, self).__init__(*args, **kwargs)
93
        self.__has_signed_terms = self.has_signed_terms
94
        if self.id:
95
            self.__groupnames = [g.name for g in self.groups.all()]
96
        else:
97
            self.is_active = False
98
    
99
    @property
100
    def realname(self):
101
        return '%s %s' %(self.first_name, self.last_name)
102

    
103
    @realname.setter
104
    def realname(self, value):
105
        parts = value.split(' ')
106
        if len(parts) == 2:
107
            self.first_name = parts[0]
108
            self.last_name = parts[1]
109
        else:
110
            self.last_name = parts[0]
111

    
112
    @property
113
    def invitation(self):
114
        try:
115
            return Invitation.objects.get(username=self.email)
116
        except Invitation.DoesNotExist:
117
            return None
118

    
119
    def save(self, update_timestamps=True, **kwargs):
120
        if update_timestamps:
121
            if not self.id:
122
                self.date_joined = datetime.now()
123
            self.updated = datetime.now()
124
        
125
        # update date_signed_terms if necessary
126
        if self.__has_signed_terms != self.has_signed_terms:
127
            self.date_signed_terms = datetime.now()
128
        
129
        if not self.id:
130
            # set username
131
            while not self.username:
132
                username =  uuid.uuid4().hex[:30]
133
                try:
134
                    AstakosUser.objects.get(username = username)
135
                except AstakosUser.DoesNotExist, e:
136
                    self.username = username
137
            if not self.provider:
138
                self.provider = 'local'
139
        report_user_event(self)
140
        self.full_clean()
141
        super(AstakosUser, self).save(**kwargs)
142
        
143
        # set group if does not exist
144
        groupname = 'shibboleth' if self.provider == 'shibboleth' else 'default'
145
        if groupname not in self.__groupnames:
146
            try:
147
                group = Group.objects.get(name = groupname)
148
                self.groups.add(group)
149
            except Group.DoesNotExist, e:
150
                logger.exception(e)
151
    
152
    def renew_token(self):
153
        md5 = hashlib.md5()
154
        md5.update(self.username)
155
        md5.update(self.realname.encode('ascii', 'ignore'))
156
        md5.update(asctime())
157

    
158
        self.auth_token = b64encode(md5.digest())
159
        self.auth_token_created = datetime.now()
160
        self.auth_token_expires = self.auth_token_created + \
161
                                  timedelta(hours=AUTH_TOKEN_DURATION)
162

    
163
    def __unicode__(self):
164
        return self.username
165
    
166
    def conflicting_email(self):
167
        q = AstakosUser.objects.exclude(username = self.username)
168
        q = q.filter(email = self.email)
169
        if q.count() != 0:
170
            return True
171
        return False
172
    
173
    def validate_unique(self, exclude=None):
174
        """
175
        Implements a unique_together constraint for email and is_active fields.
176
        """
177
        super(AstakosUser, self).validate_unique(exclude)
178
        
179
        q = AstakosUser.objects.exclude(username = self.username)
180
        q = q.filter(email = self.email)
181
        q = q.filter(is_active = self.is_active)
182
        if q.count() != 0:
183
            raise ValidationError({'__all__':[_('Another account with the same email & is_active combination found.')]})
184
    
185
    def signed_terms(self):
186
        term = get_latest_terms()
187
        if not term:
188
            return True
189
        if not self.has_signed_terms:
190
            return False
191
        if not self.date_signed_terms:
192
            return False
193
        if self.date_signed_terms < term.date:
194
            self.has_signed_terms = False
195
            self.save()
196
            return False
197
        return True
198

    
199
class ApprovalTerms(models.Model):
200
    """
201
    Model for approval terms
202
    """
203

    
204
    date = models.DateTimeField('Issue date', db_index=True, default=datetime.now())
205
    location = models.CharField('Terms location', max_length=255)
206

    
207
class Invitation(models.Model):
208
    """
209
    Model for registring invitations
210
    """
211
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
212
                                null=True)
213
    realname = models.CharField('Real name', max_length=255)
214
    username = models.CharField('Unique ID', max_length=255, unique=True)
215
    code = models.BigIntegerField('Invitation code', db_index=True)
216
    #obsolete: we keep it just for transfering the data
217
    is_accepted = models.BooleanField('Accepted?', default=False)
218
    is_consumed = models.BooleanField('Consumed?', default=False)
219
    created = models.DateTimeField('Creation date', auto_now_add=True)
220
    #obsolete: we keep it just for transfering the data
221
    accepted = models.DateTimeField('Acceptance date', null=True, blank=True)
222
    consumed = models.DateTimeField('Consumption date', null=True, blank=True)
223
    
224
    def __init__(self, *args, **kwargs):
225
        super(Invitation, self).__init__(*args, **kwargs)
226
        if not self.id:
227
            self.code = _generate_invitation_code()
228
    
229
    def consume(self):
230
        self.is_consumed = True
231
        self.consumed = datetime.now()
232
        self.save()
233

    
234
    def __unicode__(self):
235
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
236

    
237
def report_user_event(user):
238
    def should_send(user):
239
        # report event incase of new user instance
240
        # or if specific fields are modified
241
        if not user.id:
242
            return True
243
        db_instance = AstakosUser.objects.get(id = user.id)
244
        for f in BILLING_FIELDS:
245
            if (db_instance.__getattribute__(f) != user.__getattribute__(f)):
246
                return True
247
        return False
248

    
249
    if QUEUE_CONNECTION and should_send(user):
250

    
251
        from astakos.im.queue.userevent import UserEvent
252
        from synnefo.lib.queue import exchange_connect, exchange_send, \
253
                exchange_close
254

    
255
        eventType = 'create' if not user.id else 'modify'
256
        body = UserEvent(QUEUE_CLIENT_ID, user, eventType, {}).format()
257
        conn = exchange_connect(QUEUE_CONNECTION)
258
        parts = urlparse(QUEUE_CONNECTION)
259
        exchange = parts.path[1:]
260
        routing_key = '%s.user' % exchange
261
        exchange_send(conn, routing_key, body)
262
        exchange_close(conn)
263

    
264
def _generate_invitation_code():
265
    while True:
266
        code = randint(1, 2L**63 - 1)
267
        try:
268
            Invitation.objects.get(code=code)
269
            # An invitation with this code already exists, try again
270
        except Invitation.DoesNotExist:
271
            return code
272

    
273
def get_latest_terms():
274
    try:
275
        term = ApprovalTerms.objects.order_by('-id')[0]
276
        return term
277
    except IndexError:
278
        pass
279
    return None