Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.5 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 890b0eaf Sofia Papagiannaki
from django.core.mail import send_mail
37 890b0eaf Sofia Papagiannaki
from django.template.loader import render_to_string
38 890b0eaf Sofia Papagiannaki
from django.contrib import messages
39 1463659a Sofia Papagiannaki
from django.core.urlresolvers import reverse
40 18ffbee1 Sofia Papagiannaki
from django.utils.translation import ugettext as _
41 18ffbee1 Sofia Papagiannaki
from django.db import transaction
42 890b0eaf Sofia Papagiannaki
43 374611bc Sofia Papagiannaki
from urlparse import urljoin
44 890b0eaf Sofia Papagiannaki
45 890b0eaf Sofia Papagiannaki
from astakos.im.models import AstakosUser, Invitation
46 15efc749 Sofia Papagiannaki
from astakos.im.forms import *
47 15efc749 Sofia Papagiannaki
from astakos.im.util import get_invitation
48 751d24cf Sofia Papagiannaki
from astakos.im.functions import send_verification, send_activation, \
49 751d24cf Sofia Papagiannaki
    send_admin_notification, activate, SendMailError
50 751d24cf Sofia Papagiannaki
from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, \
51 751d24cf Sofia Papagiannaki
    DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, DEFAULT_ADMIN_EMAIL, RE_USER_EMAIL_PATTERNS
52 0905ccd2 Sofia Papagiannaki
53 5ed6816e Sofia Papagiannaki
import socket
54 5ed6816e Sofia Papagiannaki
import logging
55 8316698a Sofia Papagiannaki
import re
56 5ed6816e Sofia Papagiannaki
57 e015e9e6 Sofia Papagiannaki
logger = logging.getLogger(__name__)
58 e015e9e6 Sofia Papagiannaki
59 5ed6816e Sofia Papagiannaki
def get_backend(request):
60 0905ccd2 Sofia Papagiannaki
    """
61 18ffbee1 Sofia Papagiannaki
    Returns an instance of an activation backend,
62 890b0eaf Sofia Papagiannaki
    according to the INVITATIONS_ENABLED setting
63 8f5a3a06 Sofia Papagiannaki
    (if True returns ``astakos.im.activation_backends.InvitationsBackend`` and if False
64 8f5a3a06 Sofia Papagiannaki
    returns ``astakos.im.activation_backends.SimpleBackend``).
65 bef3bf46 Kostas Papadimitriou

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

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