Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.4 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`` 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(
106
        self, user, activation_template_name='im/activation_email.txt',
107
        greeting_template_name='im/welcome_email.txt',
108
        admin_email_template_name='im/account_creation_notification.txt',
109
        helpdesk_email_template_name='im/helpdesk_notification.txt'
110
    ):
111
        """
112
        If the user is already active returns immediately.
113
        If the user is preaccepted and the email is verified, the account is
114
        activated automatically. Otherwise, if the email is not verified,
115
        it sends a verification email to the user.
116
        If the user is not preaccepted, it sends an email to the administrators
117
        and informs the user that the account is pending activation.
118
        """
119
        try:
120
            if user.is_active:
121
                return RegistationCompleted()
122

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

    
147

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

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

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

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

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

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

    
212

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

    
226

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

    
231

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

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

    
242

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