Statistics
| Branch: | Tag: | Revision:

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

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

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

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