Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.2 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.contrib.sites.models import Site
39
from django.contrib import messages
40
from django.core.urlresolvers import reverse
41
from django.utils.translation import ugettext as _
42
from django.db import transaction
43

    
44
from urlparse import urljoin
45

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

    
52
import socket
53
import logging
54
import re
55

    
56
logger = logging.getLogger(__name__)
57

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

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

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

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

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

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

124
        Throws Invitation.DoesNotExist in case ``code`` is not valid.
125
        """
126
        request = self.request
127
        invitation = self.invitation
128
        initial_data = None
129
        if request.method == 'GET':
130
            if invitation:
131
                # create a tmp user with the invitation realname
132
                # to extract first and last name
133
                u = AstakosUser(realname = invitation.realname)
134
                print '>>>', invitation, invitation.inviter
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
    def handle_activation(self, user, verification_template_name='im/activation_email.txt', greeting_template_name='im/welcome_email.txt', admin_email_template_name='im/admin_notification.txt'):
160
        """
161
        Initially creates an inactive user account. If the user is preaccepted
162
        (has a valid invitation code) the user is activated and if the request
163
        param ``next`` is present redirects to it.
164
        In any other case the method returns the action status and a message.
165

166
        The method uses commit_manually decorator in order to ensure the user
167
        will be created only if the procedure has been completed successfully.
168
        """
169
        try:
170
            if user.is_active:
171
                return RegistationCompleted()
172
            if self._is_preaccepted(user):
173
                if user.email_verified:
174
                    activate(user, greeting_template_name)
175
                    return RegistationCompleted()
176
                else:
177
                    send_verification(user, verification_template_name)
178
                    return VerificationSent()
179
            else:
180
                send_admin_notification(user, admin_email_template_name)
181
                return NotificationSent()
182
        except Invitation.DoesNotExist, e:
183
            raise InvitationCodeError()
184
        except BaseException, e:
185
            logger.exception(e)
186
            raise e
187

    
188
class SimpleBackend(SignupBackend):
189
    """
190
    A activation backend which implements the following workflow: a user
191
    supplies the necessary registation information, an incative user account is
192
    created and receives an email in order to activate his/her account.
193
    """
194
    def __init__(self, request):
195
        self.request = request
196
        super(SimpleBackend, self).__init__()
197

    
198
    def get_signup_form(self, provider='local'):
199
        """
200
        Returns the form class name
201
        """
202
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
203
        suffix  = 'UserCreationForm'
204
        formclass = '%s%s' % (main, suffix)
205
        request = self.request
206
        initial_data = None
207
        if request.method == 'POST':
208
            if provider == request.POST.get('provider', ''):
209
                initial_data = request.POST
210
        ip = self.request.META.get('REMOTE_ADDR',
211
                self.request.META.get('HTTP_X_REAL_IP', None))
212
        return globals()[formclass](initial_data, ip=ip)
213
    
214
    def _is_preaccepted(self, user):
215
        if super(SimpleBackend, self)._is_preaccepted(user):
216
            return True
217
        if MODERATION_ENABLED:
218
            return False
219
        return True
220
    
221
    def handle_activation(self, user, email_template_name='im/activation_email.txt', admin_email_template_name='im/admin_notification.txt'):
222
        """
223
        Creates an inactive user account and sends a verification email.
224

225
        The method uses commit_manually decorator in order to ensure the user
226
        will be created only if the procedure has been completed successfully.
227

228
        ** Arguments **
229

230
        ``email_template_name``
231
            A custom template for the verification email body to use. This is
232
            optional; if not specified, this will default to
233
            ``im/activation_email.txt``.
234

235
        ** Templates **
236
            im/activation_email.txt or ``email_template_name`` keyword argument
237

238
        ** Settings **
239

240
        * DEFAULT_CONTACT_EMAIL: service support email
241
        * DEFAULT_FROM_EMAIL: from email
242
        """
243
        try:
244
            if user.is_active:
245
                return RegistrationCompeted()
246
            if not self._is_preaccepted(user):
247
                send_admin_notification(user, admin_email_template_name)
248
                return NotificationSent()
249
            else:
250
                send_verification(user, email_template_name)
251
                return VerificationSend()
252
        except SendEmailError, e:
253
            transaction.rollback()
254
            raise e
255
        except BaseException, e:
256
            logger.exception(e)
257
            raise e
258
        else:
259
            transaction.commit()
260

    
261
class ActivationResult(object):
262
    def __init__(self, message):
263
        self.message = message
264

    
265
class VerificationSent(ActivationResult):
266
    def __init__(self):
267
        message = _('Verification sent.')
268
        super(VerificationSent, self).__init__(message)
269

    
270
class NotificationSent(ActivationResult):
271
    def __init__(self):
272
        message = _('Your request for an account was successfully received and is now pending \
273
                    approval. You will be notified by email in the next few days. Thanks for \
274
                    your interest in ~okeanos! The GRNET team.')
275
        super(NotificationSent, self).__init__(message)
276

    
277
class RegistationCompleted(ActivationResult):
278
    def __init__(self):
279
        message = _('Registration completed. You can now login.')
280
        super(RegistationCompleted, self).__init__(message)