Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.2 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 send_verification, send_activation, \
41
    send_admin_notification, activate
42
from astakos.im.settings import INVITATIONS_ENABLED, MODERATION_ENABLED, SITENAME, RE_USER_EMAIL_PATTERNS
43

    
44
import logging
45
import re
46

    
47
logger = logging.getLogger(__name__)
48

    
49
def get_backend(request):
50
    """
51
    Returns an instance of an activation backend,
52
    according to the INVITATIONS_ENABLED setting
53
    (if True returns ``astakos.im.activation_backends.InvitationsBackend`` and if False
54
    returns ``astakos.im.activation_backends.SimpleBackend``).
55

56
    If the backend cannot be located ``django.core.exceptions.ImproperlyConfigured``
57
    is raised.
58
    """
59
    module = 'astakos.im.activation_backends'
60
    prefix = 'Invitations' if INVITATIONS_ENABLED else 'Simple'
61
    backend_class_name = '%sBackend' %prefix
62
    try:
63
        mod = import_module(module)
64
    except ImportError, e:
65
        raise ImproperlyConfigured('Error loading activation backend %s: "%s"' % (module, e))
66
    try:
67
        backend_class = getattr(mod, backend_class_name)
68
    except AttributeError:
69
        raise ImproperlyConfigured('Module "%s" does not define a activation backend named "%s"' % (module, backend_class_name))
70
    return backend_class(request)
71

    
72
class ActivationBackend(object):
73
    def __init__(self, request):
74
        self.request = request
75
    
76
    def _is_preaccepted(self, user):
77
        # return True if user email matches specific patterns
78
        for pattern in RE_USER_EMAIL_PATTERNS:
79
            if re.match(pattern, user.email):
80
                return True
81
        return False
82
    
83
    def get_signup_form(self, provider='local', instance=None):
84
        """
85
        Returns a form instance of the relevant class
86
        """
87
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
88
        suffix  = 'UserCreationForm'
89
        formclass = '%s%s' % (main, suffix)
90
        request = self.request
91
        initial_data = None
92
        if request.method == 'POST':
93
            if provider == request.POST.get('provider', ''):
94
                initial_data = request.POST
95
        return globals()[formclass](initial_data, instance=instance, request=request)
96
    
97
    def handle_activation(self, user, \
98
                          activation_template_name='im/activation_email.txt', \
99
                          greeting_template_name='im/welcome_email.txt', \
100
                          admin_email_template_name='im/account_notification.txt', \
101
                          switch_accounts_email_template_name='im/switch_accounts_email.txt'):
102
        """
103
        If the user is already active returns immediately.
104
        If the user is not active and there is another account associated with
105
        the specific email, it sends an informative email to the user whether
106
        wants to switch to this account.
107
        If the user is preaccepted and the email is verified, the account is
108
        activated automatically. Otherwise, if the email is not verified,
109
        it sends a verification email to the user.
110
        If the user is not preaccepted, it sends an email to the administrators
111
        and informs the user that the account is pending activation.
112
        """
113
        try:
114
            if user.is_active:
115
                return RegistationCompleted()
116
            if user.conflicting_email():
117
                send_verification(user, switch_accounts_email_template_name)
118
                return SwitchAccountsVerificationSent(user.email)
119
            
120
            if self._is_preaccepted(user):
121
                if user.email_verified:
122
                    activate(user, greeting_template_name)
123
                    return RegistationCompleted()
124
                else:
125
                    send_activation(user, activation_template_name)
126
                    return VerificationSent()
127
            else:
128
                send_admin_notification(
129
                    template_name=admin_email_template_name,
130
                    dictionary={'user':user, 'group_creation':True},
131
                    subject='%s alpha2 testing account notification' % SITENAME
132
                )
133
                return NotificationSent()
134
        except BaseException, e:
135
            logger.exception(e)
136
            raise e
137

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

    
147
    def get_signup_form(self, provider='local', instance=None):
148
        """
149
        Returns a form instance of the relevant class
150
        
151
        raises Invitation.DoesNotExist and ValueError if invitation is consumed
152
        or invitation username is reserved.
153
        """
154
        self.invitation = get_invitation(self.request)
155
        invitation = self.invitation
156
        initial_data = self.get_signup_initial_data(provider)
157
        prefix = 'Invited' if invitation else ''
158
        main = provider.capitalize()
159
        suffix  = 'UserCreationForm'
160
        formclass = '%s%s%s' % (prefix, main, suffix)
161
        return globals()[formclass](initial_data, instance=instance, request=self.request)
162

    
163
    def get_signup_initial_data(self, provider):
164
        """
165
        Returns the necassary activation form depending the user is invited or not
166

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

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

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

    
215
class ActivationResult(object):
216
    def __init__(self, message):
217
        self.message = message
218

    
219
class VerificationSent(ActivationResult):
220
    def __init__(self):
221
        message = _('Verification sent.')
222
        super(VerificationSent, self).__init__(message)
223

    
224
class SwitchAccountsVerificationSent(ActivationResult):
225
    def __init__(self, email):
226
        message = _('This email is already associated with another \
227
                    local account. To change this account to a shibboleth \
228
                    one follow the link in the verification email sent \
229
                    to %s. Otherwise just ignore it.' % email)
230
        super(SwitchAccountsVerificationSent, self).__init__(message)
231

    
232
class NotificationSent(ActivationResult):
233
    def __init__(self):
234
        message = _('Your request for an account was successfully received and is now pending \
235
                    approval. You will be notified by email in the next few days. Thanks for \
236
                    your interest in ~okeanos! The GRNET team.')
237
        super(NotificationSent, self).__init__(message)
238

    
239
class RegistationCompleted(ActivationResult):
240
    def __init__(self):
241
        message = _('Registration completed. You can now login.')
242
        super(RegistationCompleted, self).__init__(message)