Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 672d445a

History | View | Annotate | Download (10.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.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, 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
        super(InvitationsBackend, self).__init__()
104

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

    
124
    def get_signup_initial_data(self, provider):
125
        """
126
        Returns the necassary activation form depending the user is invited or not
127

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

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

    
163
    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'):
164
        """
165
        Initially creates an inactive user account. If the user is preaccepted
166
        (has a valid invitation code) the user is activated and if the request
167
        param ``next`` is present redirects to it.
168
        In any other case the method returns the action status and a message.
169
        """
170
        try:
171
            if user.is_active:
172
                return RegistationCompleted()
173
            if self._is_preaccepted(user):
174
                if user.email_verified:
175
                    activate(user, greeting_template_name)
176
                    return RegistationCompleted()
177
                else:
178
                    send_verification(user, verification_template_name)
179
                    return VerificationSent()
180
            else:
181
                send_admin_notification(user, admin_email_template_name)
182
                return NotificationSent()
183
        except Invitation.DoesNotExist, e:
184
            raise InvitationCodeError()
185
        except BaseException, e:
186
            logger.exception(e)
187
            raise e
188

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

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

224
        ** Arguments **
225

226
        ``email_template_name``
227
            A custom template for the verification email body to use. This is
228
            optional; if not specified, this will default to
229
            ``im/activation_email.txt``.
230

231
        ** Templates **
232
            im/activation_email.txt or ``email_template_name`` keyword argument
233

234
        ** Settings **
235

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

    
257
class ActivationResult(object):
258
    def __init__(self, message):
259
        self.message = message
260

    
261
class VerificationSent(ActivationResult):
262
    def __init__(self):
263
        message = _('Verification sent.')
264
        super(VerificationSent, self).__init__(message)
265

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

    
273
class RegistationCompleted(ActivationResult):
274
    def __init__(self):
275
        message = _('Registration completed. You can now login.')
276
        super(RegistationCompleted, self).__init__(message)