Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 7233d542

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, 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/admin_notification.txt'
111
    ):
112
        """
113
        If the user is already active returns immediately.
114
        If the user is preaccepted and the email is verified, the account is
115
        activated automatically. Otherwise, if the email is not verified,
116
        it sends a verification email to the user.
117
        If the user is not preaccepted, it sends an email to the administrators
118
        and informs the user that the account is pending activation.
119
        """
120
        try:
121
            if user.is_active:
122
                return RegistationCompleted()
123

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

    
141

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

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

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

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

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

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

    
206

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

    
220

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

    
225

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

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

    
236

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