Statistics
| Branch: | Tag: | Revision:

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

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
)
43
from astakos.im.settings import (
44
    INVITATIONS_ENABLED, RE_USER_EMAIL_PATTERNS
45
)
46
from astakos.im import settings as astakos_settings
47
from astakos.im.forms import *
48

    
49
import astakos.im.messages as astakos_messages
50

    
51
import logging
52
import re
53

    
54
logger = logging.getLogger(__name__)
55

    
56

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

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

    
81

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

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

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

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

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

    
149

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

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

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

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

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

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

    
214

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