Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 8f5a3a06

History | View | Annotate | Download (10.8 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 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 a registration 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 registration 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 registration 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 registration 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() if provider == 'local' else 'ThirdParty'
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 registration 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
                initial_data = {'email':invitation.username,
135
                                'inviter':invitation.inviter.realname,
136
                                'first_name':u.first_name,
137
                                'last_name':u.last_name}
138
        else:
139
            if provider == request.POST.get('provider', ''):
140
                initial_data = request.POST
141
        return initial_data
142

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

    
158
    @transaction.commit_manually
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
        result = None
170
        try:
171
            if self._is_preaccepted(user):
172
                if user.email_verified:
173
                    activate(user, greeting_template_name)
174
                    result = RegistationCompleted()
175
                else:
176
                    send_verification(user, verification_template_name)
177
                    result = VerificationSent()
178
            else:
179
                send_admin_notification(user, admin_email_template_name)
180
                result = NotificationSent()
181
        except Invitation.DoesNotExist, e:
182
            raise InvitationCodeError()
183
        else:
184
            return result
185

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

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

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

226
        ** Arguments **
227

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

233
        ** Templates **
234
            im/activation_email.txt or ``email_template_name`` keyword argument
235

236
        ** Settings **
237

238
        * DEFAULT_CONTACT_EMAIL: service support email
239
        * DEFAULT_FROM_EMAIL: from email
240
        """
241
        result = None
242
        if not self._is_preaccepted(user):
243
            send_admin_notification(user, admin_email_template_name)
244
            result = NotificationSent()
245
        else:
246
            send_verification(user, email_template_name)
247
            result = VerificationSend()
248
        return result
249

    
250
class ActivationResult(object):
251
    def __init__(self, message):
252
        self.message = message
253

    
254
class VerificationSent(ActivationResult):
255
    def __init__(self):
256
        message = _('Verification sent.')
257
        super(VerificationSent, self).__init__(message)
258

    
259
class NotificationSent(ActivationResult):
260
    def __init__(self):
261
        message = _('Your request for an account was successfully received and is now pending \
262
                    approval. You will be notified by email in the next few days. Thanks for \
263
                    your interest in ~okeanos! The GRNET team.')
264
        super(NotificationSent, self).__init__(message)
265

    
266
class RegistationCompleted(ActivationResult):
267
    def __init__(self):
268
        message = _('Registration completed. You can now login.')
269
        super(RegistationCompleted, self).__init__(message)
270

    
271