Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / backends.py @ 683cf244

History | View | Annotate | Download (12 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.utils.importlib import import_module
35
from django.core.exceptions import ImproperlyConfigured
36
from django.core.mail import send_mail
37
from django.template.loader import render_to_string
38
from django.utils.translation import ugettext as _
39
from django.contrib.sites.models import Site
40
from django.contrib import messages
41
from django.db import transaction
42
from django.core.urlresolvers import reverse
43

    
44
from smtplib import SMTPException
45
from urlparse import urljoin
46

    
47
from astakos.im.models import AstakosUser, Invitation
48
from astakos.im.forms import *
49
from astakos.im.util import get_invitation
50
from astakos.im.functions import send_verification, send_notification, activate
51
from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, BASEURL, DEFAULT_ADMIN_EMAIL, RE_USER_EMAIL_PATTERNS
52

    
53
import socket
54
import logging
55
import re
56

    
57
logger = logging.getLogger(__name__)
58

    
59
def get_backend(request):
60
    """
61
    Returns an instance of a registration backend,
62
    according to the INVITATIONS_ENABLED setting
63
    (if True returns ``astakos.im.backends.InvitationsBackend`` and if False
64
    returns ``astakos.im.backends.SimpleBackend``).
65

66
    If the backend cannot be located ``django.core.exceptions.ImproperlyConfigured``
67
    is raised.
68
    """
69
    module = 'astakos.im.backends'
70
    prefix = 'Invitations' if INVITATIONS_ENABLED else 'Simple'
71
    backend_class_name = '%sBackend' %prefix
72
    try:
73
        mod = import_module(module)
74
    except ImportError, e:
75
        raise ImproperlyConfigured('Error loading registration backend %s: "%s"' % (module, e))
76
    try:
77
        backend_class = getattr(mod, backend_class_name)
78
    except AttributeError:
79
        raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
80
    return backend_class(request)
81

    
82
class SignupBackend(object):
83
    def _is_preaccepted(self, user):
84
        # return True if user email matches specific patterns
85
        for pattern in RE_USER_EMAIL_PATTERNS:
86
            if re.match(pattern, user.email):
87
                return True
88
        return False
89

    
90
class InvitationsBackend(SignupBackend):
91
    """
92
    A registration backend which implements the following workflow: a user
93
    supplies the necessary registation information, if the request contains a valid
94
    inivation code the user is automatically activated otherwise an inactive user
95
    account is created and the user is going to receive an email as soon as an
96
    administrator activates his/her account.
97
    """
98
    def __init__(self, request):
99
        """
100
        raises Invitation.DoesNotExist and ValueError if invitation is consumed
101
        or invitation username is reserved.
102
        """
103
        self.request = request
104
        self.invitation = get_invitation(request)
105
        super(InvitationsBackend, self).__init__()
106

    
107
    def get_signup_form(self, provider):
108
        """
109
        Returns the form class name
110
        """
111
        invitation = self.invitation
112
        initial_data = self.get_signup_initial_data(provider)
113
        prefix = 'Invited' if invitation else ''
114
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
115
        suffix  = 'UserCreationForm'
116
        formclass = '%s%s%s' % (prefix, main, suffix)
117
        ip = self.request.META.get('REMOTE_ADDR',
118
                self.request.META.get('HTTP_X_REAL_IP', None))
119
        return globals()[formclass](initial_data, ip=ip)
120

    
121
    def get_signup_initial_data(self, provider):
122
        """
123
        Returns the necassary registration form depending the user is invited or not
124

125
        Throws Invitation.DoesNotExist in case ``code`` is not valid.
126
        """
127
        request = self.request
128
        invitation = self.invitation
129
        initial_data = None
130
        if request.method == 'GET':
131
            if invitation:
132
                # create a tmp user with the invitation realname
133
                # to extract first and last name
134
                u = AstakosUser(realname = invitation.realname)
135
                initial_data = {'email':invitation.username,
136
                                'inviter':invitation.inviter.realname,
137
                                'first_name':u.first_name,
138
                                'last_name':u.last_name}
139
        else:
140
            if provider == request.POST.get('provider', ''):
141
                initial_data = request.POST
142
        return initial_data
143

    
144
    def _is_preaccepted(self, user):
145
        """
146
        If there is a valid, not-consumed invitation code for the specific user
147
        returns True else returns False.
148
        """
149
        if super(InvitationsBackend, self)._is_preaccepted(user):
150
            return True
151
        invitation = self.invitation
152
        if not invitation:
153
            return False
154
        if invitation.username == user.email and not invitation.is_consumed:
155
            invitation.consume()
156
            return True
157
        return False
158

    
159
    @transaction.commit_manually
160
    def signup(self, form, verification_template_name='im/activation_email.txt', greeting_template_name='im/welcome_email.txt', admin_email_template_name='im/admin_notification.txt'):
161
        """
162
        Initially creates an inactive user account. If the user is preaccepted
163
        (has a valid invitation code) the user is activated and if the request
164
        param ``next`` is present redirects to it.
165
        In any other case the method returns the action status and a message.
166

167
        The method uses commit_manually decorator in order to ensure the user
168
        will be created only if the procedure has been completed successfully.
169
        """
170
        user = None
171
        try:
172
            user = form.save()
173
            if self._is_preaccepted(user):
174
                if user.email_verified:
175
                    try:
176
                        activate(user, greeting_template_name)
177
                        message = _('Registration completed. You can now login.')
178
                    except (SMTPException, socket.error) as e:
179
                        status = messages.ERROR
180
                        name = 'strerror'
181
                        message = getattr(e, 'name') if hasattr(e, 'name') else e
182
                else:
183
                    try:
184
                        send_verification(user, verification_template_name)
185
                        message = _('Verification sent to %s' % user.email)
186
                    except (SMTPException, socket.error) as e:
187
                        status = messages.ERROR
188
                        name = 'strerror'
189
                        message = getattr(e, 'name') if hasattr(e, 'name') else e
190
            else:
191
                send_notification(user, admin_email_template_name)
192
                message = _('Your request for an account was successfully received and is now pending \
193
                            approval. You will be notified by email in the next few days. Thanks for \
194
                            your interest in ~okeanos! The GRNET team.')
195
            status = messages.SUCCESS
196
        except Invitation.DoesNotExist, e:
197
            status = messages.ERROR
198
            message = _('Invalid invitation code')
199
        except socket.error, e:
200
            status = messages.ERROR
201
            message = _(e.strerror)
202

    
203
        # rollback in case of error
204
        if status == messages.ERROR:
205
            transaction.rollback()
206
        else:
207
            transaction.commit()
208
        return status, message, user
209

    
210
class SimpleBackend(SignupBackend):
211
    """
212
    A registration backend which implements the following workflow: a user
213
    supplies the necessary registation information, an incative user account is
214
    created and receives an email in order to activate his/her account.
215
    """
216
    def __init__(self, request):
217
        self.request = request
218
        super(SimpleBackend, self).__init__()
219

    
220
    def get_signup_form(self, provider):
221
        """
222
        Returns the form class name
223
        """
224
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
225
        suffix  = 'UserCreationForm'
226
        formclass = '%s%s' % (main, suffix)
227
        request = self.request
228
        initial_data = None
229
        if request.method == 'POST':
230
            if provider == request.POST.get('provider', ''):
231
                initial_data = request.POST
232
        ip = self.request.META.get('REMOTE_ADDR',
233
                self.request.META.get('HTTP_X_REAL_IP', None))
234
        return globals()[formclass](initial_data, ip=ip)
235
    
236
    def _is_preaccepted(self, user):
237
        if super(SimpleBackend, self)._is_preaccepted(user):
238
            return True
239
        if MODERATION_ENABLED:
240
            return False
241
        return True
242
    
243
    @transaction.commit_manually
244
    def signup(self, form, email_template_name='im/activation_email.txt', admin_email_template_name='im/admin_notification.txt'):
245
        """
246
        Creates an inactive user account and sends a verification email.
247

248
        The method uses commit_manually decorator in order to ensure the user
249
        will be created only if the procedure has been completed successfully.
250

251
        ** Arguments **
252

253
        ``email_template_name``
254
            A custom template for the verification email body to use. This is
255
            optional; if not specified, this will default to
256
            ``im/activation_email.txt``.
257

258
        ** Templates **
259
            im/activation_email.txt or ``email_template_name`` keyword argument
260

261
        ** Settings **
262

263
        * DEFAULT_CONTACT_EMAIL: service support email
264
        * DEFAULT_FROM_EMAIL: from email
265
        """
266
        user = form.save()
267
        status = messages.SUCCESS
268
        if not self._is_preaccepted(user):
269
            try:
270
                send_notification(user, admin_email_template_name)
271
                message = _('Your request for an account was successfully received and is now pending \
272
                            approval. You will be notified by email in the next few days. Thanks for \
273
                            your interest in ~okeanos! The GRNET team.')
274
            except (SMTPException, socket.error) as e:
275
                status = messages.ERROR
276
                name = 'strerror'
277
                message = getattr(e, 'name') if hasattr(e, 'name') else e
278
        else:
279
            try:
280
                send_verification(user, email_template_name)
281
                message = _('Verification sent to %s' % user.email)
282
            except (SMTPException, socket.error) as e:
283
                status = messages.ERROR
284
                name = 'strerror'
285
                message = getattr(e, 'name') if hasattr(e, 'name') else e
286

    
287
        # rollback in case of error
288
        if status == messages.ERROR:
289
            transaction.rollback()
290
        else:
291
            transaction.commit()
292
        return status, message, user