Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 3dabf097

History | View | Annotate | Download (9.8 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.validate_unique_email_isactive()
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_email_isactive(self):
174
        """
175
        Implements a unique_together constraint for email and is_active fields.
176
        """
177
        q = AstakosUser.objects.exclude(username = self.username)
178
        q = q.filter(email = self.email)
179
        q = q.filter(is_active = self.is_active)
180
        if q.count() != 0:
181
            raise ValidationError({'__all__':[_('Another account with the same email & is_active combination found.')]})
182
    
183
    def signed_terms(self):
184
        term = get_latest_terms()
185
        if not term:
186
            return True
187
        if not self.has_signed_terms:
188
            return False
189
        if not self.date_signed_terms:
190
            return False
191
        if self.date_signed_terms < term.date:
192
            self.has_signed_terms = False
193
            self.save()
194
            return False
195
        return True
196

    
197
class ApprovalTerms(models.Model):
198
    """
199
    Model for approval terms
200
    """
201

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

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

    
228
    def __unicode__(self):
229
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
230

    
231
def report_user_event(user):
232
    def should_send(user):
233
        # report event incase of new user instance
234
        # or if specific fields are modified
235
        if not user.id:
236
            return True
237
        db_instance = AstakosUser.objects.get(id = user.id)
238
        for f in BILLING_FIELDS:
239
            if (db_instance.__getattribute__(f) != user.__getattribute__(f)):
240
                return True
241
        return False
242

    
243
    if QUEUE_CONNECTION and should_send(user):
244

    
245
        from astakos.im.queue.userevent import UserEvent
246
        from synnefo.lib.queue import exchange_connect, exchange_send, \
247
                exchange_close
248

    
249
        eventType = 'create' if not user.id else 'modify'
250
        body = UserEvent(QUEUE_CLIENT_ID, user, eventType, {}).format()
251
        conn = exchange_connect(QUEUE_CONNECTION)
252
        parts = urlparse(QUEUE_CONNECTION)
253
        exchange = parts.path[1:]
254
        routing_key = '%s.user' % exchange
255
        exchange_send(conn, routing_key, body)
256
        exchange_close(conn)
257

    
258
def _generate_invitation_code():
259
    while True:
260
        code = randint(1, 2L**63 - 1)
261
        try:
262
            Invitation.objects.get(code=code)
263
            # An invitation with this code already exists, try again
264
        except Invitation.DoesNotExist:
265
            return code
266

    
267
def get_latest_terms():
268
    try:
269
        term = ApprovalTerms.objects.order_by('-id')[0]
270
        return term
271
    except IndexError:
272
        pass
273
    return None