Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ b6852727

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.utils.importlib import import_module
35
from django.core.exceptions import ImproperlyConfigured
36
from django.utils.translation import ugettext as _
37

    
38
from astakos.im.models import AstakosUser
39
from astakos.im.forms import LocalUserCreationForm, ShibbolethUserCreationForm
40
from astakos.im.util import get_invitation
41
from astakos.im.functions import (send_verification, send_activation,
42
                                  send_account_creation_notification,
43
                                  send_group_creation_notification, activate)
44
from astakos.im.settings import (INVITATIONS_ENABLED, MODERATION_ENABLED,
45
    SITENAME, RE_USER_EMAIL_PATTERNS
46
)
47

    
48
import astakos.im.messages as astakos_messages
49

    
50
import logging
51
import re
52

    
53
logger = logging.getLogger(__name__)
54

    
55

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

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

    
80

    
81
class ActivationBackend(object):
82
    def __init__(self, request):
83
        self.request = request
84

    
85
    def _is_preaccepted(self, user):
86
        # return True if user email matches specific patterns
87
        for pattern in RE_USER_EMAIL_PATTERNS:
88
            if re.match(pattern, user.email):
89
                return True
90
        return False
91

    
92
    def get_signup_form(self, provider='local', instance=None):
93
        """
94
        Returns a form instance of the relevant class
95
        """
96
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
97
        suffix = 'UserCreationForm'
98
        formclass = '%s%s' % (main, suffix)
99
        request = self.request
100
        initial_data = None
101
        if request.method == 'POST':
102
            if provider == request.POST.get('provider', ''):
103
                initial_data = request.POST
104
        return globals()[formclass](initial_data, instance=instance, request=request)
105

    
106
    def handle_activation(self, user,
107
                          activation_template_name='im/activation_email.txt',
108
                          greeting_template_name='im/welcome_email.txt',
109
                          admin_email_template_name='im/account_notification.txt',
110
                          switch_accounts_email_template_name='im/switch_accounts_email.txt'):
111
        """
112
        If the user is already active returns immediately.
113
        If the user is not active and there is another account associated with
114
        the specific email, it sends an informative email to the user whether
115
        wants to switch to this account.
116
        If the user is preaccepted and the email is verified, the account is
117
        activated automatically. Otherwise, if the email is not verified,
118
        it sends a verification email to the user.
119
        If the user is not preaccepted, it sends an email to the administrators
120
        and informs the user that the account is pending activation.
121
        """
122
        try:
123
            if user.is_active:
124
                return RegistationCompleted()
125
            if user.conflicting_email():
126
                send_verification(user, switch_accounts_email_template_name)
127
                return SwitchAccountsVerificationSent(user.email)
128

    
129
            if self._is_preaccepted(user):
130
                if user.email_verified:
131
                    activate(user, greeting_template_name)
132
                    return RegistationCompleted()
133
                else:
134
                    send_activation(user, activation_template_name)
135
                    return VerificationSent()
136
            else:
137
                send_account_creation_notification(
138
                    template_name=admin_email_template_name,
139
                    dictionary={'user': user.__dict__, 'group_creation': True}
140
                )
141
                return NotificationSent()
142
        except BaseException, e:
143
            logger.exception(e)
144
            raise e
145

    
146

    
147
class InvitationsBackend(ActivationBackend):
148
    """
149
    A activation backend which implements the following workflow: a user
150
    supplies the necessary registation information, if the request contains a valid
151
    inivation code the user is automatically activated otherwise an inactive user
152
    account is created and the user is going to receive an email as soon as an
153
    administrator activates his/her account.
154
    """
155

    
156
    def get_signup_form(self, provider='local', instance=None):
157
        """
158
        Returns a form instance of the relevant class
159

160
        raises Invitation.DoesNotExist and ValueError if invitation is consumed
161
        or invitation username is reserved.
162
        """
163
        self.invitation = get_invitation(self.request)
164
        invitation = self.invitation
165
        initial_data = self.get_signup_initial_data(provider)
166
        prefix = 'Invited' if invitation else ''
167
        main = provider.capitalize()
168
        suffix = 'UserCreationForm'
169
        formclass = '%s%s%s' % (prefix, main, suffix)
170
        return globals()[formclass](initial_data, instance=instance, request=self.request)
171

    
172
    def get_signup_initial_data(self, provider):
173
        """
174
        Returns the necassary activation form depending the user is invited or not
175

176
        Throws Invitation.DoesNotExist in case ``code`` is not valid.
177
        """
178
        request = self.request
179
        invitation = self.invitation
180
        initial_data = None
181
        if request.method == 'GET':
182
            if invitation:
183
                # create a tmp user with the invitation realname
184
                # to extract first and last name
185
                u = AstakosUser(realname=invitation.realname)
186
                initial_data = {'email': invitation.username,
187
                                'inviter': invitation.inviter.realname,
188
                                'first_name': u.first_name,
189
                                'last_name': u.last_name,
190
                                'provider': provider}
191
        else:
192
            if provider == request.POST.get('provider', ''):
193
                initial_data = request.POST
194
        return initial_data
195

    
196
    def _is_preaccepted(self, user):
197
        """
198
        If there is a valid, not-consumed invitation code for the specific user
199
        returns True else returns False.
200
        """
201
        if super(InvitationsBackend, self)._is_preaccepted(user):
202
            return True
203
        invitation = self.invitation
204
        if not invitation:
205
            return False
206
        if invitation.username == user.email and not invitation.is_consumed:
207
            invitation.consume()
208
            return True
209
        return False
210

    
211

    
212
class SimpleBackend(ActivationBackend):
213
    """
214
    A activation backend which implements the following workflow: a user
215
    supplies the necessary registation information, an incative user account is
216
    created and receives an email in order to activate his/her account.
217
    """
218
    def _is_preaccepted(self, user):
219
        if super(SimpleBackend, self)._is_preaccepted(user):
220
            return True
221
        if MODERATION_ENABLED:
222
            return False
223
        return True
224

    
225

    
226
class ActivationResult(object):
227
    def __init__(self, message):
228
        self.message = message
229

    
230

    
231
class VerificationSent(ActivationResult):
232
    def __init__(self):
233
        message = _(astakos_messages.VERIFICATION_SENT)
234
        super(VerificationSent, self).__init__(message)
235

    
236

    
237
class SwitchAccountsVerificationSent(ActivationResult):
238
    def __init__(self, email):
239
        message = _(astakos_messages.SWITCH_ACCOUNT_LINK_SENT)
240
        super(SwitchAccountsVerificationSent, self).__init__(message)
241

    
242

    
243
class NotificationSent(ActivationResult):
244
    def __init__(self):
245
        message = _(astakos_messages.NOTIFACATION_SENT)
246
        super(NotificationSent, self).__init__(message)
247

    
248

    
249
class RegistationCompleted(ActivationResult):
250
    def __init__(self):
251
        message = _(astakos_messages.REGISTRATION_COMPLETED)
252
        super(RegistationCompleted, self).__init__(message)