Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 9d20fe23

History | View | Annotate | Download (9.5 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.util import get_invitation
40
from astakos.im.functions import (
41
    send_activation, send_account_creation_notification, activate)
42
from astakos.im.settings import (
43
    INVITATIONS_ENABLED, RE_USER_EMAIL_PATTERNS)
44
from astakos.im import settings as astakos_settings
45
from astakos.im.forms import *
46

    
47
import astakos.im.messages 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``
60
    and if False
61
    returns ``astakos.im.activation_backends.SimpleBackend``).
62

63
    If the backend cannot be located
64
    ``django.core.exceptions.ImproperlyConfigured`` 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(
78
            'Module "%s" does not define a activation backend named "%s"' % (
79
                module, backend_class_name))
80
    return backend_class(request)
81

    
82

    
83
class ActivationBackend(object):
84
    def __init__(self, request):
85
        self.request = request
86

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

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

    
108
    def handle_activation(
109
        self, user, activation_template_name='im/activation_email.txt',
110
        greeting_template_name='im/welcome_email.txt',
111
        admin_email_template_name='im/account_creation_notification.txt',
112
        helpdesk_email_template_name='im/helpdesk_notification.txt'
113
    ):
114
        """
115
        If the user is already active returns immediately.
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

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

    
150

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

    
160
    def get_signup_form(self, provider='local', instance=None):
161
        """
162
        Returns a form instance of the relevant class
163

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

    
176
    def get_signup_initial_data(self, provider):
177
        """
178
        Returns the necassary activation form depending the user is invited or not
179

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

    
200
    def _is_preaccepted(self, user):
201
        """
202
        Extends _is_preaccepted and if there is a valid, not-consumed invitation
203
        code for the specific user returns True else returns False.
204
        """
205
        if super(InvitationsBackend, self)._is_preaccepted(user):
206
            return True
207
        invitation = self.invitation
208
        if not invitation:
209
            return not astakos_settings.MODERATION_ENABLED
210
        if invitation.username == user.email and not invitation.is_consumed:
211
            invitation.consume()
212
            return True
213
        return False
214

    
215

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

    
226
        return user.get_auth_provider().get_automoderate_policy
227

    
228

    
229
class ActivationResult(object):
230
    def __init__(self, message):
231
        self.message = message
232

    
233

    
234
class VerificationSent(ActivationResult):
235
    def __init__(self):
236
        message = _(astakos_messages.VERIFICATION_SENT)
237
        super(VerificationSent, self).__init__(message)
238

    
239
class NotificationSent(ActivationResult):
240
    def __init__(self):
241
        message = _(astakos_messages.NOTIFICATION_SENT)
242
        super(NotificationSent, self).__init__(message)
243

    
244

    
245
class RegistationCompleted(ActivationResult):
246
    def __init__(self):
247
        message = _(astakos_messages.REGISTRATION_COMPLETED)
248
        super(RegistationCompleted, self).__init__(message)