activation_backends.py 9.94 KB
Newer Older
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Copyright 2011 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
#
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.

from django.utils.importlib import import_module
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
35
from django.core.exceptions import ImproperlyConfigured
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
36
from django.utils.translation import ugettext as _
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
37

38
from astakos.im.models import AstakosUser
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
39
from astakos.im.forms import LocalUserCreationForm, ShibbolethUserCreationForm
40
from astakos.im.util import get_invitation
Olga Brani's avatar
Olga Brani committed
41
42
43
from astakos.im.functions import (send_verification, send_activation,
                                  send_account_creation_notification,
                                  send_group_creation_notification, activate)
44
45
46
47
from astakos.im.settings import (INVITATIONS_ENABLED, MODERATION_ENABLED,
    SITENAME, RE_USER_EMAIL_PATTERNS
)
from astakos.im.messages import as astakos_messages
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
48

49
import logging
50
import re
51

52
53
logger = logging.getLogger(__name__)

54

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

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

79

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

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

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

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

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

145

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

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

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

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

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

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

210

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

224

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

229

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

235

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

241

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

247

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