Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.3 kB)

1 0905ccd2 Sofia Papagiannaki
# Copyright 2011 GRNET S.A. All rights reserved.
2 0905ccd2 Sofia Papagiannaki
#
3 0905ccd2 Sofia Papagiannaki
# Redistribution and use in source and binary forms, with or
4 0905ccd2 Sofia Papagiannaki
# without modification, are permitted provided that the following
5 0905ccd2 Sofia Papagiannaki
# conditions are met:
6 0905ccd2 Sofia Papagiannaki
#
7 0905ccd2 Sofia Papagiannaki
#   1. Redistributions of source code must retain the above
8 0905ccd2 Sofia Papagiannaki
#      copyright notice, this list of conditions and the following
9 0905ccd2 Sofia Papagiannaki
#      disclaimer.
10 0905ccd2 Sofia Papagiannaki
#
11 0905ccd2 Sofia Papagiannaki
#   2. Redistributions in binary form must reproduce the above
12 0905ccd2 Sofia Papagiannaki
#      copyright notice, this list of conditions and the following
13 0905ccd2 Sofia Papagiannaki
#      disclaimer in the documentation and/or other materials
14 0905ccd2 Sofia Papagiannaki
#      provided with the distribution.
15 0905ccd2 Sofia Papagiannaki
#
16 0905ccd2 Sofia Papagiannaki
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 0905ccd2 Sofia Papagiannaki
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 0905ccd2 Sofia Papagiannaki
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 0905ccd2 Sofia Papagiannaki
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 0905ccd2 Sofia Papagiannaki
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 0905ccd2 Sofia Papagiannaki
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 0905ccd2 Sofia Papagiannaki
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 0905ccd2 Sofia Papagiannaki
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 0905ccd2 Sofia Papagiannaki
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 0905ccd2 Sofia Papagiannaki
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 0905ccd2 Sofia Papagiannaki
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 0905ccd2 Sofia Papagiannaki
# POSSIBILITY OF SUCH DAMAGE.
28 0905ccd2 Sofia Papagiannaki
#
29 0905ccd2 Sofia Papagiannaki
# The views and conclusions contained in the software and
30 0905ccd2 Sofia Papagiannaki
# documentation are those of the authors and should not be
31 0905ccd2 Sofia Papagiannaki
# interpreted as representing official policies, either expressed
32 0905ccd2 Sofia Papagiannaki
# or implied, of GRNET S.A.
33 0905ccd2 Sofia Papagiannaki
34 0905ccd2 Sofia Papagiannaki
from django.utils.importlib import import_module
35 890b0eaf Sofia Papagiannaki
from django.core.exceptions import ImproperlyConfigured
36 18ffbee1 Sofia Papagiannaki
from django.utils.translation import ugettext as _
37 890b0eaf Sofia Papagiannaki
38 aab4d540 Sofia Papagiannaki
from astakos.im.models import AstakosUser
39 705a9ae0 Sofia Papagiannaki
from astakos.im.forms import LocalUserCreationForm, ShibbolethUserCreationForm
40 15efc749 Sofia Papagiannaki
from astakos.im.util import get_invitation
41 751d24cf Sofia Papagiannaki
from astakos.im.functions import send_verification, send_activation, \
42 aab4d540 Sofia Papagiannaki
    send_admin_notification, activate
43 aab4d540 Sofia Papagiannaki
from astakos.im.settings import INVITATIONS_ENABLED, MODERATION_ENABLED, SITENAME, RE_USER_EMAIL_PATTERNS
44 0905ccd2 Sofia Papagiannaki
45 5ed6816e Sofia Papagiannaki
import logging
46 8316698a Sofia Papagiannaki
import re
47 5ed6816e Sofia Papagiannaki
48 e015e9e6 Sofia Papagiannaki
logger = logging.getLogger(__name__)
49 e015e9e6 Sofia Papagiannaki
50 5ed6816e Sofia Papagiannaki
def get_backend(request):
51 0905ccd2 Sofia Papagiannaki
    """
52 18ffbee1 Sofia Papagiannaki
    Returns an instance of an activation backend,
53 890b0eaf Sofia Papagiannaki
    according to the INVITATIONS_ENABLED setting
54 8f5a3a06 Sofia Papagiannaki
    (if True returns ``astakos.im.activation_backends.InvitationsBackend`` and if False
55 8f5a3a06 Sofia Papagiannaki
    returns ``astakos.im.activation_backends.SimpleBackend``).
56 bef3bf46 Kostas Papadimitriou

57 890b0eaf Sofia Papagiannaki
    If the backend cannot be located ``django.core.exceptions.ImproperlyConfigured``
58 890b0eaf Sofia Papagiannaki
    is raised.
59 0905ccd2 Sofia Papagiannaki
    """
60 8f5a3a06 Sofia Papagiannaki
    module = 'astakos.im.activation_backends'
61 92defad4 Sofia Papagiannaki
    prefix = 'Invitations' if INVITATIONS_ENABLED else 'Simple'
62 890b0eaf Sofia Papagiannaki
    backend_class_name = '%sBackend' %prefix
63 0905ccd2 Sofia Papagiannaki
    try:
64 0905ccd2 Sofia Papagiannaki
        mod = import_module(module)
65 0905ccd2 Sofia Papagiannaki
    except ImportError, e:
66 18ffbee1 Sofia Papagiannaki
        raise ImproperlyConfigured('Error loading activation backend %s: "%s"' % (module, e))
67 0905ccd2 Sofia Papagiannaki
    try:
68 0905ccd2 Sofia Papagiannaki
        backend_class = getattr(mod, backend_class_name)
69 0905ccd2 Sofia Papagiannaki
    except AttributeError:
70 aab4d540 Sofia Papagiannaki
        raise ImproperlyConfigured('Module "%s" does not define a activation backend named "%s"' % (module, backend_class_name))
71 5ed6816e Sofia Papagiannaki
    return backend_class(request)
72 890b0eaf Sofia Papagiannaki
73 0a569195 Sofia Papagiannaki
class ActivationBackend(object):
74 aab4d540 Sofia Papagiannaki
    def __init__(self, request):
75 aab4d540 Sofia Papagiannaki
        self.request = request
76 aab4d540 Sofia Papagiannaki
    
77 8316698a Sofia Papagiannaki
    def _is_preaccepted(self, user):
78 8316698a Sofia Papagiannaki
        # return True if user email matches specific patterns
79 8316698a Sofia Papagiannaki
        for pattern in RE_USER_EMAIL_PATTERNS:
80 8316698a Sofia Papagiannaki
            if re.match(pattern, user.email):
81 8316698a Sofia Papagiannaki
                return True
82 8316698a Sofia Papagiannaki
        return False
83 0a569195 Sofia Papagiannaki
    
84 0a569195 Sofia Papagiannaki
    def get_signup_form(self, provider='local', instance=None):
85 0a569195 Sofia Papagiannaki
        """
86 b669d9c0 Sofia Papagiannaki
        Returns a form instance of the relevant class
87 0a569195 Sofia Papagiannaki
        """
88 0a569195 Sofia Papagiannaki
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
89 0a569195 Sofia Papagiannaki
        suffix  = 'UserCreationForm'
90 0a569195 Sofia Papagiannaki
        formclass = '%s%s' % (main, suffix)
91 0a569195 Sofia Papagiannaki
        request = self.request
92 0a569195 Sofia Papagiannaki
        initial_data = None
93 0a569195 Sofia Papagiannaki
        if request.method == 'POST':
94 0a569195 Sofia Papagiannaki
            if provider == request.POST.get('provider', ''):
95 0a569195 Sofia Papagiannaki
                initial_data = request.POST
96 0a569195 Sofia Papagiannaki
        return globals()[formclass](initial_data, instance=instance, request=request)
97 0a569195 Sofia Papagiannaki
    
98 0a569195 Sofia Papagiannaki
    def handle_activation(self, user, \
99 751d24cf Sofia Papagiannaki
                          activation_template_name='im/activation_email.txt', \
100 0a569195 Sofia Papagiannaki
                          greeting_template_name='im/welcome_email.txt', \
101 3abf6c78 Sofia Papagiannaki
                          admin_email_template_name='im/account_notification.txt', \
102 0a569195 Sofia Papagiannaki
                          switch_accounts_email_template_name='im/switch_accounts_email.txt'):
103 0a569195 Sofia Papagiannaki
        """
104 0a569195 Sofia Papagiannaki
        If the user is already active returns immediately.
105 0a569195 Sofia Papagiannaki
        If the user is not active and there is another account associated with
106 0a569195 Sofia Papagiannaki
        the specific email, it sends an informative email to the user whether
107 0a569195 Sofia Papagiannaki
        wants to switch to this account.
108 0a569195 Sofia Papagiannaki
        If the user is preaccepted and the email is verified, the account is
109 0a569195 Sofia Papagiannaki
        activated automatically. Otherwise, if the email is not verified,
110 0a569195 Sofia Papagiannaki
        it sends a verification email to the user.
111 0a569195 Sofia Papagiannaki
        If the user is not preaccepted, it sends an email to the administrators
112 0a569195 Sofia Papagiannaki
        and informs the user that the account is pending activation.
113 0a569195 Sofia Papagiannaki
        """
114 0a569195 Sofia Papagiannaki
        try:
115 0a569195 Sofia Papagiannaki
            if user.is_active:
116 0a569195 Sofia Papagiannaki
                return RegistationCompleted()
117 0a569195 Sofia Papagiannaki
            if user.conflicting_email():
118 0a569195 Sofia Papagiannaki
                send_verification(user, switch_accounts_email_template_name)
119 0a569195 Sofia Papagiannaki
                return SwitchAccountsVerificationSent(user.email)
120 0a569195 Sofia Papagiannaki
            
121 0a569195 Sofia Papagiannaki
            if self._is_preaccepted(user):
122 0a569195 Sofia Papagiannaki
                if user.email_verified:
123 0a569195 Sofia Papagiannaki
                    activate(user, greeting_template_name)
124 0a569195 Sofia Papagiannaki
                    return RegistationCompleted()
125 0a569195 Sofia Papagiannaki
                else:
126 751d24cf Sofia Papagiannaki
                    send_activation(user, activation_template_name)
127 0a569195 Sofia Papagiannaki
                    return VerificationSent()
128 0a569195 Sofia Papagiannaki
            else:
129 3abf6c78 Sofia Papagiannaki
                send_admin_notification(
130 3abf6c78 Sofia Papagiannaki
                    template_name=admin_email_template_name,
131 3abf6c78 Sofia Papagiannaki
                    dictionary={'user':user, 'group_creation':True},
132 3abf6c78 Sofia Papagiannaki
                    subject='%s alpha2 testing account notification' % SITENAME
133 3abf6c78 Sofia Papagiannaki
                )
134 0a569195 Sofia Papagiannaki
                return NotificationSent()
135 0a569195 Sofia Papagiannaki
        except BaseException, e:
136 0a569195 Sofia Papagiannaki
            logger.exception(e)
137 0a569195 Sofia Papagiannaki
            raise e
138 8316698a Sofia Papagiannaki
139 0a569195 Sofia Papagiannaki
class InvitationsBackend(ActivationBackend):
140 890b0eaf Sofia Papagiannaki
    """
141 18ffbee1 Sofia Papagiannaki
    A activation backend which implements the following workflow: a user
142 890b0eaf Sofia Papagiannaki
    supplies the necessary registation information, if the request contains a valid
143 890b0eaf Sofia Papagiannaki
    inivation code the user is automatically activated otherwise an inactive user
144 890b0eaf Sofia Papagiannaki
    account is created and the user is going to receive an email as soon as an
145 890b0eaf Sofia Papagiannaki
    administrator activates his/her account.
146 890b0eaf Sofia Papagiannaki
    """
147 bef3bf46 Kostas Papadimitriou
148 4e30244e Sofia Papagiannaki
    def get_signup_form(self, provider='local', instance=None):
149 15efc749 Sofia Papagiannaki
        """
150 b669d9c0 Sofia Papagiannaki
        Returns a form instance of the relevant class
151 0a569195 Sofia Papagiannaki
        
152 0a569195 Sofia Papagiannaki
        raises Invitation.DoesNotExist and ValueError if invitation is consumed
153 0a569195 Sofia Papagiannaki
        or invitation username is reserved.
154 15efc749 Sofia Papagiannaki
        """
155 0a569195 Sofia Papagiannaki
        self.invitation = get_invitation(self.request)
156 0a569195 Sofia Papagiannaki
        invitation = self.invitation
157 0a569195 Sofia Papagiannaki
        initial_data = self.get_signup_initial_data(provider)
158 0a569195 Sofia Papagiannaki
        prefix = 'Invited' if invitation else ''
159 0a569195 Sofia Papagiannaki
        main = provider.capitalize()
160 0a569195 Sofia Papagiannaki
        suffix  = 'UserCreationForm'
161 0a569195 Sofia Papagiannaki
        formclass = '%s%s%s' % (prefix, main, suffix)
162 0a569195 Sofia Papagiannaki
        return globals()[formclass](initial_data, instance=instance, request=self.request)
163 bef3bf46 Kostas Papadimitriou
164 15efc749 Sofia Papagiannaki
    def get_signup_initial_data(self, provider):
165 890b0eaf Sofia Papagiannaki
        """
166 18ffbee1 Sofia Papagiannaki
        Returns the necassary activation form depending the user is invited or not
167 bef3bf46 Kostas Papadimitriou

168 890b0eaf Sofia Papagiannaki
        Throws Invitation.DoesNotExist in case ``code`` is not valid.
169 890b0eaf Sofia Papagiannaki
        """
170 5ed6816e Sofia Papagiannaki
        request = self.request
171 15efc749 Sofia Papagiannaki
        invitation = self.invitation
172 65d85494 Sofia Papagiannaki
        initial_data = None
173 890b0eaf Sofia Papagiannaki
        if request.method == 'GET':
174 15efc749 Sofia Papagiannaki
            if invitation:
175 65d85494 Sofia Papagiannaki
                # create a tmp user with the invitation realname
176 65d85494 Sofia Papagiannaki
                # to extract first and last name
177 15efc749 Sofia Papagiannaki
                u = AstakosUser(realname = invitation.realname)
178 15efc749 Sofia Papagiannaki
                initial_data = {'email':invitation.username,
179 15efc749 Sofia Papagiannaki
                                'inviter':invitation.inviter.realname,
180 65d85494 Sofia Papagiannaki
                                'first_name':u.first_name,
181 4e30244e Sofia Papagiannaki
                                'last_name':u.last_name,
182 4e30244e Sofia Papagiannaki
                                'provider':provider}
183 15efc749 Sofia Papagiannaki
        else:
184 15efc749 Sofia Papagiannaki
            if provider == request.POST.get('provider', ''):
185 15efc749 Sofia Papagiannaki
                initial_data = request.POST
186 15efc749 Sofia Papagiannaki
        return initial_data
187 bef3bf46 Kostas Papadimitriou
188 890b0eaf Sofia Papagiannaki
    def _is_preaccepted(self, user):
189 890b0eaf Sofia Papagiannaki
        """
190 890b0eaf Sofia Papagiannaki
        If there is a valid, not-consumed invitation code for the specific user
191 890b0eaf Sofia Papagiannaki
        returns True else returns False.
192 890b0eaf Sofia Papagiannaki
        """
193 8316698a Sofia Papagiannaki
        if super(InvitationsBackend, self)._is_preaccepted(user):
194 8316698a Sofia Papagiannaki
            return True
195 5ed6816e Sofia Papagiannaki
        invitation = self.invitation
196 890b0eaf Sofia Papagiannaki
        if not invitation:
197 890b0eaf Sofia Papagiannaki
            return False
198 5ed6816e Sofia Papagiannaki
        if invitation.username == user.email and not invitation.is_consumed:
199 5ed6816e Sofia Papagiannaki
            invitation.consume()
200 890b0eaf Sofia Papagiannaki
            return True
201 890b0eaf Sofia Papagiannaki
        return False
202 bef3bf46 Kostas Papadimitriou
203 0a569195 Sofia Papagiannaki
class SimpleBackend(ActivationBackend):
204 890b0eaf Sofia Papagiannaki
    """
205 18ffbee1 Sofia Papagiannaki
    A activation backend which implements the following workflow: a user
206 890b0eaf Sofia Papagiannaki
    supplies the necessary registation information, an incative user account is
207 890b0eaf Sofia Papagiannaki
    created and receives an email in order to activate his/her account.
208 890b0eaf Sofia Papagiannaki
    """
209 8316698a Sofia Papagiannaki
    def _is_preaccepted(self, user):
210 8316698a Sofia Papagiannaki
        if super(SimpleBackend, self)._is_preaccepted(user):
211 8316698a Sofia Papagiannaki
            return True
212 8316698a Sofia Papagiannaki
        if MODERATION_ENABLED:
213 8316698a Sofia Papagiannaki
            return False
214 8316698a Sofia Papagiannaki
        return True
215 8f5a3a06 Sofia Papagiannaki
216 8f5a3a06 Sofia Papagiannaki
class ActivationResult(object):
217 8f5a3a06 Sofia Papagiannaki
    def __init__(self, message):
218 8f5a3a06 Sofia Papagiannaki
        self.message = message
219 8f5a3a06 Sofia Papagiannaki
220 8f5a3a06 Sofia Papagiannaki
class VerificationSent(ActivationResult):
221 8f5a3a06 Sofia Papagiannaki
    def __init__(self):
222 8f5a3a06 Sofia Papagiannaki
        message = _('Verification sent.')
223 8f5a3a06 Sofia Papagiannaki
        super(VerificationSent, self).__init__(message)
224 8f5a3a06 Sofia Papagiannaki
225 0a569195 Sofia Papagiannaki
class SwitchAccountsVerificationSent(ActivationResult):
226 0a569195 Sofia Papagiannaki
    def __init__(self, email):
227 0a569195 Sofia Papagiannaki
        message = _('This email is already associated with another \
228 0a569195 Sofia Papagiannaki
                    local account. To change this account to a shibboleth \
229 0a569195 Sofia Papagiannaki
                    one follow the link in the verification email sent \
230 0a569195 Sofia Papagiannaki
                    to %s. Otherwise just ignore it.' % email)
231 0a569195 Sofia Papagiannaki
        super(SwitchAccountsVerificationSent, self).__init__(message)
232 0a569195 Sofia Papagiannaki
233 8f5a3a06 Sofia Papagiannaki
class NotificationSent(ActivationResult):
234 8f5a3a06 Sofia Papagiannaki
    def __init__(self):
235 8f5a3a06 Sofia Papagiannaki
        message = _('Your request for an account was successfully received and is now pending \
236 8f5a3a06 Sofia Papagiannaki
                    approval. You will be notified by email in the next few days. Thanks for \
237 8f5a3a06 Sofia Papagiannaki
                    your interest in ~okeanos! The GRNET team.')
238 8f5a3a06 Sofia Papagiannaki
        super(NotificationSent, self).__init__(message)
239 8f5a3a06 Sofia Papagiannaki
240 8f5a3a06 Sofia Papagiannaki
class RegistationCompleted(ActivationResult):
241 8f5a3a06 Sofia Papagiannaki
    def __init__(self):
242 8f5a3a06 Sofia Papagiannaki
        message = _('Registration completed. You can now login.')
243 18ffbee1 Sofia Papagiannaki
        super(RegistationCompleted, self).__init__(message)