Statistics
| Branch: | Tag: | Revision:

root / astakos / im / backends.py @ 1a3675a0

History | View | Annotate | Download (9.9 kB)

1
# Copyright 2011 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
from django.conf import settings
35
from django.utils.importlib import import_module
36
from django.core.exceptions import ImproperlyConfigured
37
from django.core.mail import send_mail
38
from django.template.loader import render_to_string
39
from django.utils.translation import ugettext as _
40
from django.contrib.sites.models import Site
41
from django.contrib import messages
42
from django.db import transaction
43

    
44
from smtplib import SMTPException
45
from urllib import quote
46

    
47
from astakos.im.models import AstakosUser, Invitation
48
from astakos.im.forms import *
49
from astakos.im.util import get_invitation
50

    
51
import socket
52
import logging
53

    
54
def get_backend(request):
55
    """
56
    Returns an instance of a registration backend,
57
    according to the INVITATIONS_ENABLED setting
58
    (if True returns ``astakos.im.backends.InvitationsBackend`` and if False
59
    returns ``astakos.im.backends.SimpleBackend``).
60
    
61
    If the backend cannot be located ``django.core.exceptions.ImproperlyConfigured``
62
    is raised.
63
    """
64
    module = 'astakos.im.backends'
65
    prefix = 'Invitations' if settings.INVITATIONS_ENABLED else 'Simple'
66
    backend_class_name = '%sBackend' %prefix
67
    try:
68
        mod = import_module(module)
69
    except ImportError, e:
70
        raise ImproperlyConfigured('Error loading registration backend %s: "%s"' % (module, e))
71
    try:
72
        backend_class = getattr(mod, backend_class_name)
73
    except AttributeError:
74
        raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
75
    return backend_class(request)
76

    
77
class InvitationsBackend(object):
78
    """
79
    A registration backend which implements the following workflow: a user
80
    supplies the necessary registation information, if the request contains a valid
81
    inivation code the user is automatically activated otherwise an inactive user
82
    account is created and the user is going to receive an email as soon as an
83
    administrator activates his/her account.
84
    """
85
    def __init__(self, request):
86
        """
87
        raises Invitation.DoesNotExist and ValueError if invitation is consumed
88
        or invitation username is reserved.
89
        """
90
        self.request = request
91
        self.invitation = get_invitation(request)
92
    
93
    def get_signup_form(self, provider):
94
        """
95
        Returns the form class name 
96
        """
97
        invitation = self.invitation
98
        initial_data = self.get_signup_initial_data(provider)
99
        prefix = 'Invited' if invitation else ''
100
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
101
        suffix  = 'UserCreationForm'
102
        formclass = '%s%s%s' % (prefix, main, suffix)
103
        return globals()[formclass](initial_data)
104
    
105
    def get_signup_initial_data(self, provider):
106
        """
107
        Returns the necassary registration form depending the user is invited or not
108
        
109
        Throws Invitation.DoesNotExist in case ``code`` is not valid.
110
        """
111
        request = self.request
112
        invitation = self.invitation
113
        initial_data = None
114
        if request.method == 'GET':
115
            if invitation:
116
                # create a tmp user with the invitation realname
117
                # to extract first and last name
118
                u = AstakosUser(realname = invitation.realname)
119
                initial_data = {'email':invitation.username,
120
                                'inviter':invitation.inviter.realname,
121
                                'first_name':u.first_name,
122
                                'last_name':u.last_name}
123
        else:
124
            if provider == request.POST.get('provider', ''):
125
                initial_data = request.POST
126
        return initial_data
127
    
128
    def _is_preaccepted(self, user):
129
        """
130
        If there is a valid, not-consumed invitation code for the specific user
131
        returns True else returns False.
132
        """
133
        invitation = self.invitation
134
        if not invitation:
135
            return False
136
        if invitation.username == user.email and not invitation.is_consumed:
137
            invitation.consume()
138
            return True
139
        return False
140
    
141
    @transaction.commit_manually
142
    def signup(self, form):
143
        """
144
        Initially creates an inactive user account. If the user is preaccepted
145
        (has a valid invitation code) the user is activated and if the request
146
        param ``next`` is present redirects to it.
147
        In any other case the method returns the action status and a message.
148
        
149
        The method uses commit_manually decorator in order to ensure the user
150
        will be created only if the procedure has been completed successfully.
151
        """
152
        user = None
153
        try:
154
            user = form.save()
155
            if self._is_preaccepted(user):
156
                user.is_active = True
157
                user.save()
158
                message = _('Registration completed. You can now login.')
159
            else:
160
                message = _('Registration completed. You will receive an email upon your account\'s activation.')
161
            status = messages.SUCCESS
162
        except Invitation.DoesNotExist, e:
163
            status = messages.ERROR
164
            message = _('Invalid invitation code')
165
        except socket.error, e:
166
            status = messages.ERROR
167
            message = _(e.strerror)
168
        
169
        # rollback in case of error
170
        if status == messages.ERROR:
171
            transaction.rollback()
172
        else:
173
            transaction.commit()
174
        return status, message, user
175

    
176
class SimpleBackend(object):
177
    """
178
    A registration backend which implements the following workflow: a user
179
    supplies the necessary registation information, an incative user account is
180
    created and receives an email in order to activate his/her account.
181
    """
182
    def __init__(self, request):
183
        self.request = request
184
    
185
    def get_signup_form(self, provider):
186
        """
187
        Returns the form class name
188
        """
189
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
190
        suffix  = 'UserCreationForm'
191
        formclass = '%s%s' % (main, suffix)
192
        request = self.request
193
        initial_data = None
194
        if request.method == 'POST':
195
            if provider == request.POST.get('provider', ''):
196
                initial_data = request.POST
197
        return globals()[formclass](initial_data)
198
    
199
    @transaction.commit_manually
200
    def signup(self, form, email_template_name='activation_email.txt'):
201
        """
202
        Creates an inactive user account and sends a verification email.
203
        
204
        The method uses commit_manually decorator in order to ensure the user
205
        will be created only if the procedure has been completed successfully.
206
        
207
        ** Arguments **
208
        
209
        ``email_template_name``
210
            A custom template for the verification email body to use. This is
211
            optional; if not specified, this will default to
212
            ``activation_email.txt``.
213
        
214
        ** Templates **
215
            activation_email.txt or ``email_template_name`` keyword argument
216
        
217
        ** Settings **
218
        
219
        * DEFAULT_CONTACT_EMAIL: service support email
220
        * DEFAULT_FROM_EMAIL: from email
221
        """
222
        user = None
223
        try:
224
            user = form.save()
225
            status = messages.SUCCESS
226
            _send_verification(self.request, user, email_template_name)
227
            message = _('Verification sent to %s' % user.email)
228
        except (SMTPException, socket.error) as e:
229
            status = messages.ERROR
230
            name = 'strerror'
231
            message = getattr(e, name) if hasattr(e, name) else e
232
        
233
        # rollback in case of error
234
        if status == messages.ERROR:
235
            transaction.rollback()
236
        else:
237
            transaction.commit()
238
        return status, message, user
239

    
240
def _send_verification(request, user, template_name):
241
    site = Site.objects.get_current()
242
    baseurl = request.build_absolute_uri('/').rstrip('/')
243
    url = '%s%s?auth=%s&next=%s' % (baseurl,
244
                                    reverse('astakos.im.target.activate'),
245
                                    quote(user.auth_token))
246
    message = render_to_string(template_name, {
247
            'user': user,
248
            'url': url,
249
            'baseurl': baseurl,
250
            'site_name': site.name,
251
            'support': settings.DEFAULT_CONTACT_EMAIL % site.name.lower()})
252
    sender = settings.DEFAULT_FROM_EMAIL % site.name
253
    send_mail('%s account activation' % site.name, message, sender, [user.email])
254
    logging.info('Sent activation %s', user)