Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 674f9a52

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
from astakos.im.messages import as astakos_messages
48

    
49
import logging
50
import re
51

    
52
logger = logging.getLogger(__name__)
53

    
54

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

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

    
79

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

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

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

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

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

    
145

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

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

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

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

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

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

    
210

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

    
224

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

    
229

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

    
235

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

    
241

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

    
247

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