Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / models.py @ 18ffbee1

History | View | Annotate | Download (8.7 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

    
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
43

    
44
from django.db import models
45
from django.contrib.auth.models import User, UserManager, Group
46

    
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
50

    
51
QUEUE_CLIENT_ID = 3 # Astakos.
52

    
53
logger = logging.getLogger(__name__)
54

    
55
class AstakosUser(User):
56
    """
57
    Extends ``django.contrib.auth.models.User`` by defining additional fields.
58
    """
59
    # Use UserManager to get the create_user method, etc.
60
    objects = UserManager()
61
    
62
    affiliation = models.CharField('Affiliation', max_length=255, blank=True)
63
    provider = models.CharField('Provider', max_length=255, blank=True)
64
    
65
    #for invitations
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))
69
    
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)
74
    
75
    updated = models.DateTimeField('Update date')
76
    is_verified = models.BooleanField('Is verified?', default=False)
77
    
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)
80
    
81
    email_verified = models.BooleanField('Email verified?', default=False)
82
    
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)
86
    
87
    __has_signed_terms = False
88
    __groupnames = []
89
    
90
    def __init__(self, *args, **kwargs):
91
        super(AstakosUser, self).__init__(*args, **kwargs)
92
        self.__has_signed_terms = self.has_signed_terms
93
        if self.id:
94
            self.__groupnames = [g.name for g in self.groups.all()]
95
        else:
96
            self.is_active = False
97
    
98
    @property
99
    def realname(self):
100
        return '%s %s' %(self.first_name, self.last_name)
101
    
102
    @realname.setter
103
    def realname(self, value):
104
        parts = value.split(' ')
105
        if len(parts) == 2:
106
            self.first_name = parts[0]
107
            self.last_name = parts[1]
108
        else:
109
            self.last_name = parts[0]
110
    
111
    @property
112
    def invitation(self):
113
        try:
114
            return Invitation.objects.get(username=self.email)
115
        except Invitation.DoesNotExist:
116
            return None
117
    
118
    def save(self, update_timestamps=True, **kwargs):
119
        if update_timestamps:
120
            if not self.id:
121
                self.date_joined = datetime.now()
122
            self.updated = datetime.now()
123
        
124
        # update date_signed_terms if necessary
125
        if self.__has_signed_terms != self.has_signed_terms:
126
            self.date_signed_terms = datetime.now()
127
        
128
        if not self.id:
129
            # set username
130
            while not self.username:
131
                username =  uuid.uuid4().hex[:30]
132
                try:
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)
140
        
141
        # set group if does not exist
142
        groupname = 'shibboleth' if self.provider == 'shibboleth' else 'default'
143
        if groupname not in self.__groupnames:
144
            try:
145
                group = Group.objects.get(name = groupname)
146
                self.groups.add(group)
147
            except Group.DoesNotExist, e:
148
                logger.exception(e)
149
    
150
    def renew_token(self):
151
        md5 = hashlib.md5()
152
        md5.update(self.username)
153
        md5.update(self.realname.encode('ascii', 'ignore'))
154
        md5.update(asctime())
155
        
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)
160
    
161
    def __unicode__(self):
162
        return self.username
163

    
164
class ApprovalTerms(models.Model):
165
    """
166
    Model for approval terms
167
    """
168
    
169
    date = models.DateTimeField('Issue date', db_index=True, default=datetime.now())
170
    location = models.CharField('Terms location', max_length=255)
171

    
172
class Invitation(models.Model):
173
    """
174
    Model for registring invitations
175
    """
176
    inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
177
                                null=True)
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)
188
    
189
    def __init__(self, *args, **kwargs):
190
        super(Invitation, self).__init__(*args, **kwargs)
191
        if not self.id:
192
            self.code = _generate_invitation_code()
193
    
194
    def consume(self):
195
        self.is_consumed = True
196
        self.consumed = datetime.now()
197
        self.save()
198
        
199
    def __unicode__(self):
200
        return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
201

    
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
206
        if not user.id:
207
            return True
208
        db_instance = AstakosUser.objects.get(id = user.id)
209
        for f in BILLING_FIELDS:
210
            if (db_instance.__getattribute__(f) != user.__getattribute__(f)):
211
                return True
212
        return False
213
    
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)
222
        exchange_close(conn)
223

    
224
def _generate_invitation_code():
225
    while True:
226
        code = randint(1, 2L**63 - 1)
227
        try:
228
            Invitation.objects.get(code=code)
229
            # An invitation with this code already exists, try again
230
        except Invitation.DoesNotExist:
231
            return code