Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 11c48149

History | View | Annotate | Download (9.1 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, MODERATION_ENABLED, RE_USER_EMAIL_PATTERNS
45
)
46
from astakos.im.forms import *
47

    
48
import astakos.im.messages as astakos_messages
49

    
50
import logging
51
import re
52

    
53
logger = logging.getLogger(__name__)
54

    
55

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

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

    
80

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

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

    
92
    def get_signup_form(self, provider='local', instance=None):
93
        """
94
        Returns a form instance of the relevant class
95
        """
96
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
97
        suffix = 'UserCreationForm'
98
        formclass = '%s%s' % (main, suffix)
99
        request = self.request
100
        initial_data = None
101
        if request.method == 'POST':
102
            if provider == request.POST.get('provider', ''):
103
                initial_data = request.POST
104
        return globals()[formclass](initial_data, instance=instance, request=request)
105
    
106
    def handle_activation(
107
        self, user, activation_template_name='im/activation_email.txt',
108
        greeting_template_name='im/welcome_email.txt',
109
        admin_email_template_name='im/admin_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(user, greeting_template_name)
126
                    return RegistationCompleted()
127
                else:
128
                    send_activation(user, activation_template_name)
129
                    return VerificationSent()
130
            else:
131
                send_account_creation_notification(
132
                    template_name=admin_email_template_name,
133
                    dictionary={'user': user.__dict__, 'group_creation': True}
134
                )
135
                return NotificationSent()
136
        except BaseException, e:
137
            logger.exception(e)
138
            raise e
139

    
140

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

    
150
    def get_signup_form(self, provider='local', instance=None):
151
        """
152
        Returns a form instance of the relevant class
153

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

    
166
    def get_signup_initial_data(self, provider):
167
        """
168
        Returns the necassary activation form depending the user is invited or not
169

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

    
190
    def _is_preaccepted(self, user):
191
        """
192
        Extends _is_preaccepted and if there is a valid, not-consumed invitation
193
        code for the specific user returns True else returns False.
194
        """
195
        if super(InvitationsBackend, self)._is_preaccepted(user):
196
            return True
197
        invitation = self.invitation
198
        if not invitation:
199
            return False
200
        if invitation.username == user.email and not invitation.is_consumed:
201
            invitation.consume()
202
            return True
203
        return False
204

    
205

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

    
219

    
220
class ActivationResult(object):
221
    def __init__(self, message):
222
        self.message = message
223

    
224

    
225
class VerificationSent(ActivationResult):
226
    def __init__(self):
227
        message = _(astakos_messages.VERIFICATION_SENT)
228
        super(VerificationSent, self).__init__(message)
229

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

    
235

    
236
class RegistationCompleted(ActivationResult):
237
    def __init__(self):
238
        message = _(astakos_messages.REGISTRATION_COMPLETED)
239
        super(RegistationCompleted, self).__init__(message)