Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 705a9ae0

History | View | Annotate | Download (10.3 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.forms import LocalUserCreationForm, ShibbolethUserCreationForm
40
from astakos.im.util import get_invitation
41
from astakos.im.functions import send_verification, send_activation, \
42
    send_admin_notification, activate
43
from astakos.im.settings import INVITATIONS_ENABLED, MODERATION_ENABLED, SITENAME, RE_USER_EMAIL_PATTERNS
44

    
45
import logging
46
import re
47

    
48
logger = logging.getLogger(__name__)
49

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

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

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

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

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

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

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

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

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

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

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

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

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

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