Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 672d445a

History | View | Annotate | Download (10.9 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 8f5a3a06 Sofia Papagiannaki
from astakos.im.functions import send_verification, send_admin_notification, activate
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 8316698a Sofia Papagiannaki
class SignupBackend(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 8316698a Sofia Papagiannaki
89 8316698a Sofia Papagiannaki
class InvitationsBackend(SignupBackend):
90 890b0eaf Sofia Papagiannaki
    """
91 18ffbee1 Sofia Papagiannaki
    A activation backend which implements the following workflow: a user
92 890b0eaf Sofia Papagiannaki
    supplies the necessary registation information, if the request contains a valid
93 890b0eaf Sofia Papagiannaki
    inivation code the user is automatically activated otherwise an inactive user
94 890b0eaf Sofia Papagiannaki
    account is created and the user is going to receive an email as soon as an
95 890b0eaf Sofia Papagiannaki
    administrator activates his/her account.
96 890b0eaf Sofia Papagiannaki
    """
97 5ed6816e Sofia Papagiannaki
    def __init__(self, request):
98 15efc749 Sofia Papagiannaki
        """
99 15efc749 Sofia Papagiannaki
        raises Invitation.DoesNotExist and ValueError if invitation is consumed
100 15efc749 Sofia Papagiannaki
        or invitation username is reserved.
101 15efc749 Sofia Papagiannaki
        """
102 5ed6816e Sofia Papagiannaki
        self.request = request
103 8316698a Sofia Papagiannaki
        super(InvitationsBackend, self).__init__()
104 bef3bf46 Kostas Papadimitriou
105 4e30244e Sofia Papagiannaki
    def get_signup_form(self, provider='local', instance=None):
106 15efc749 Sofia Papagiannaki
        """
107 bef3bf46 Kostas Papadimitriou
        Returns the form class name
108 15efc749 Sofia Papagiannaki
        """
109 4e30244e Sofia Papagiannaki
        try:
110 4e30244e Sofia Papagiannaki
            self.invitation = get_invitation(self.request)
111 4e30244e Sofia Papagiannaki
        except (Invitation, ValueError), e:
112 4e30244e Sofia Papagiannaki
            self.invitation = None
113 4e30244e Sofia Papagiannaki
        else:
114 4e30244e Sofia Papagiannaki
            invitation = self.invitation
115 4e30244e Sofia Papagiannaki
            initial_data = self.get_signup_initial_data(provider)
116 4e30244e Sofia Papagiannaki
            prefix = 'Invited' if invitation else ''
117 4e30244e Sofia Papagiannaki
            main = provider.capitalize()
118 4e30244e Sofia Papagiannaki
            suffix  = 'UserCreationForm'
119 4e30244e Sofia Papagiannaki
            formclass = '%s%s%s' % (prefix, main, suffix)
120 4e30244e Sofia Papagiannaki
            ip = self.request.META.get('REMOTE_ADDR',
121 4e30244e Sofia Papagiannaki
                    self.request.META.get('HTTP_X_REAL_IP', None))
122 4e30244e Sofia Papagiannaki
            return globals()[formclass](initial_data, instance=instance, ip=ip)
123 bef3bf46 Kostas Papadimitriou
124 15efc749 Sofia Papagiannaki
    def get_signup_initial_data(self, provider):
125 890b0eaf Sofia Papagiannaki
        """
126 18ffbee1 Sofia Papagiannaki
        Returns the necassary activation form depending the user is invited or not
127 bef3bf46 Kostas Papadimitriou

128 890b0eaf Sofia Papagiannaki
        Throws Invitation.DoesNotExist in case ``code`` is not valid.
129 890b0eaf Sofia Papagiannaki
        """
130 5ed6816e Sofia Papagiannaki
        request = self.request
131 15efc749 Sofia Papagiannaki
        invitation = self.invitation
132 65d85494 Sofia Papagiannaki
        initial_data = None
133 890b0eaf Sofia Papagiannaki
        if request.method == 'GET':
134 15efc749 Sofia Papagiannaki
            if invitation:
135 65d85494 Sofia Papagiannaki
                # create a tmp user with the invitation realname
136 65d85494 Sofia Papagiannaki
                # to extract first and last name
137 15efc749 Sofia Papagiannaki
                u = AstakosUser(realname = invitation.realname)
138 15efc749 Sofia Papagiannaki
                initial_data = {'email':invitation.username,
139 15efc749 Sofia Papagiannaki
                                'inviter':invitation.inviter.realname,
140 65d85494 Sofia Papagiannaki
                                'first_name':u.first_name,
141 4e30244e Sofia Papagiannaki
                                'last_name':u.last_name,
142 4e30244e Sofia Papagiannaki
                                'provider':provider}
143 15efc749 Sofia Papagiannaki
        else:
144 15efc749 Sofia Papagiannaki
            if provider == request.POST.get('provider', ''):
145 15efc749 Sofia Papagiannaki
                initial_data = request.POST
146 15efc749 Sofia Papagiannaki
        return initial_data
147 bef3bf46 Kostas Papadimitriou
148 890b0eaf Sofia Papagiannaki
    def _is_preaccepted(self, user):
149 890b0eaf Sofia Papagiannaki
        """
150 890b0eaf Sofia Papagiannaki
        If there is a valid, not-consumed invitation code for the specific user
151 890b0eaf Sofia Papagiannaki
        returns True else returns False.
152 890b0eaf Sofia Papagiannaki
        """
153 8316698a Sofia Papagiannaki
        if super(InvitationsBackend, self)._is_preaccepted(user):
154 8316698a Sofia Papagiannaki
            return True
155 5ed6816e Sofia Papagiannaki
        invitation = self.invitation
156 890b0eaf Sofia Papagiannaki
        if not invitation:
157 890b0eaf Sofia Papagiannaki
            return False
158 5ed6816e Sofia Papagiannaki
        if invitation.username == user.email and not invitation.is_consumed:
159 5ed6816e Sofia Papagiannaki
            invitation.consume()
160 890b0eaf Sofia Papagiannaki
            return True
161 890b0eaf Sofia Papagiannaki
        return False
162 bef3bf46 Kostas Papadimitriou
163 8f5a3a06 Sofia Papagiannaki
    def handle_activation(self, user, verification_template_name='im/activation_email.txt', greeting_template_name='im/welcome_email.txt', admin_email_template_name='im/admin_notification.txt'):
164 890b0eaf Sofia Papagiannaki
        """
165 5ed6816e Sofia Papagiannaki
        Initially creates an inactive user account. If the user is preaccepted
166 5ed6816e Sofia Papagiannaki
        (has a valid invitation code) the user is activated and if the request
167 5ed6816e Sofia Papagiannaki
        param ``next`` is present redirects to it.
168 890b0eaf Sofia Papagiannaki
        In any other case the method returns the action status and a message.
169 890b0eaf Sofia Papagiannaki
        """
170 890b0eaf Sofia Papagiannaki
        try:
171 18ffbee1 Sofia Papagiannaki
            if user.is_active:
172 18ffbee1 Sofia Papagiannaki
                return RegistationCompleted()
173 890b0eaf Sofia Papagiannaki
            if self._is_preaccepted(user):
174 8316698a Sofia Papagiannaki
                if user.email_verified:
175 8f5a3a06 Sofia Papagiannaki
                    activate(user, greeting_template_name)
176 18ffbee1 Sofia Papagiannaki
                    return RegistationCompleted()
177 8316698a Sofia Papagiannaki
                else:
178 8f5a3a06 Sofia Papagiannaki
                    send_verification(user, verification_template_name)
179 18ffbee1 Sofia Papagiannaki
                    return VerificationSent()
180 890b0eaf Sofia Papagiannaki
            else:
181 8f5a3a06 Sofia Papagiannaki
                send_admin_notification(user, admin_email_template_name)
182 18ffbee1 Sofia Papagiannaki
                return NotificationSent()
183 890b0eaf Sofia Papagiannaki
        except Invitation.DoesNotExist, e:
184 8f5a3a06 Sofia Papagiannaki
            raise InvitationCodeError()
185 18ffbee1 Sofia Papagiannaki
        except BaseException, e:
186 18ffbee1 Sofia Papagiannaki
            logger.exception(e)
187 18ffbee1 Sofia Papagiannaki
            raise e
188 890b0eaf Sofia Papagiannaki
189 8316698a Sofia Papagiannaki
class SimpleBackend(SignupBackend):
190 890b0eaf Sofia Papagiannaki
    """
191 18ffbee1 Sofia Papagiannaki
    A activation backend which implements the following workflow: a user
192 890b0eaf Sofia Papagiannaki
    supplies the necessary registation information, an incative user account is
193 890b0eaf Sofia Papagiannaki
    created and receives an email in order to activate his/her account.
194 890b0eaf Sofia Papagiannaki
    """
195 5ed6816e Sofia Papagiannaki
    def __init__(self, request):
196 5ed6816e Sofia Papagiannaki
        self.request = request
197 8316698a Sofia Papagiannaki
        super(SimpleBackend, self).__init__()
198 bef3bf46 Kostas Papadimitriou
199 4e30244e Sofia Papagiannaki
    def get_signup_form(self, provider='local', instance=None):
200 890b0eaf Sofia Papagiannaki
        """
201 15efc749 Sofia Papagiannaki
        Returns the form class name
202 890b0eaf Sofia Papagiannaki
        """
203 15efc749 Sofia Papagiannaki
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
204 15efc749 Sofia Papagiannaki
        suffix  = 'UserCreationForm'
205 15efc749 Sofia Papagiannaki
        formclass = '%s%s' % (main, suffix)
206 5ed6816e Sofia Papagiannaki
        request = self.request
207 15efc749 Sofia Papagiannaki
        initial_data = None
208 15efc749 Sofia Papagiannaki
        if request.method == 'POST':
209 15efc749 Sofia Papagiannaki
            if provider == request.POST.get('provider', ''):
210 15efc749 Sofia Papagiannaki
                initial_data = request.POST
211 672d445a Sofia Papagiannaki
        return globals()[formclass](initial_data, instance=instance, request=request)
212 8316698a Sofia Papagiannaki
    
213 8316698a Sofia Papagiannaki
    def _is_preaccepted(self, user):
214 8316698a Sofia Papagiannaki
        if super(SimpleBackend, self)._is_preaccepted(user):
215 8316698a Sofia Papagiannaki
            return True
216 8316698a Sofia Papagiannaki
        if MODERATION_ENABLED:
217 8316698a Sofia Papagiannaki
            return False
218 8316698a Sofia Papagiannaki
        return True
219 8316698a Sofia Papagiannaki
    
220 8f5a3a06 Sofia Papagiannaki
    def handle_activation(self, user, email_template_name='im/activation_email.txt', admin_email_template_name='im/admin_notification.txt'):
221 890b0eaf Sofia Papagiannaki
        """
222 890b0eaf Sofia Papagiannaki
        Creates an inactive user account and sends a verification email.
223 bef3bf46 Kostas Papadimitriou

224 890b0eaf Sofia Papagiannaki
        ** Arguments **
225 bef3bf46 Kostas Papadimitriou

226 890b0eaf Sofia Papagiannaki
        ``email_template_name``
227 890b0eaf Sofia Papagiannaki
            A custom template for the verification email body to use. This is
228 890b0eaf Sofia Papagiannaki
            optional; if not specified, this will default to
229 1e685275 Sofia Papagiannaki
            ``im/activation_email.txt``.
230 bef3bf46 Kostas Papadimitriou

231 890b0eaf Sofia Papagiannaki
        ** Templates **
232 1e685275 Sofia Papagiannaki
            im/activation_email.txt or ``email_template_name`` keyword argument
233 bef3bf46 Kostas Papadimitriou

234 890b0eaf Sofia Papagiannaki
        ** Settings **
235 bef3bf46 Kostas Papadimitriou

236 890b0eaf Sofia Papagiannaki
        * DEFAULT_CONTACT_EMAIL: service support email
237 890b0eaf Sofia Papagiannaki
        * DEFAULT_FROM_EMAIL: from email
238 890b0eaf Sofia Papagiannaki
        """
239 18ffbee1 Sofia Papagiannaki
        try:
240 18ffbee1 Sofia Papagiannaki
            if user.is_active:
241 18ffbee1 Sofia Papagiannaki
                return RegistrationCompeted()
242 18ffbee1 Sofia Papagiannaki
            if not self._is_preaccepted(user):
243 18ffbee1 Sofia Papagiannaki
                send_admin_notification(user, admin_email_template_name)
244 18ffbee1 Sofia Papagiannaki
                return NotificationSent()
245 18ffbee1 Sofia Papagiannaki
            else:
246 18ffbee1 Sofia Papagiannaki
                send_verification(user, email_template_name)
247 18ffbee1 Sofia Papagiannaki
                return VerificationSend()
248 18ffbee1 Sofia Papagiannaki
        except SendEmailError, e:
249 18ffbee1 Sofia Papagiannaki
            transaction.rollback()
250 18ffbee1 Sofia Papagiannaki
            raise e
251 18ffbee1 Sofia Papagiannaki
        except BaseException, e:
252 18ffbee1 Sofia Papagiannaki
            logger.exception(e)
253 18ffbee1 Sofia Papagiannaki
            raise e
254 1463659a Sofia Papagiannaki
        else:
255 18ffbee1 Sofia Papagiannaki
            transaction.commit()
256 8f5a3a06 Sofia Papagiannaki
257 8f5a3a06 Sofia Papagiannaki
class ActivationResult(object):
258 8f5a3a06 Sofia Papagiannaki
    def __init__(self, message):
259 8f5a3a06 Sofia Papagiannaki
        self.message = message
260 8f5a3a06 Sofia Papagiannaki
261 8f5a3a06 Sofia Papagiannaki
class VerificationSent(ActivationResult):
262 8f5a3a06 Sofia Papagiannaki
    def __init__(self):
263 8f5a3a06 Sofia Papagiannaki
        message = _('Verification sent.')
264 8f5a3a06 Sofia Papagiannaki
        super(VerificationSent, self).__init__(message)
265 8f5a3a06 Sofia Papagiannaki
266 8f5a3a06 Sofia Papagiannaki
class NotificationSent(ActivationResult):
267 8f5a3a06 Sofia Papagiannaki
    def __init__(self):
268 8f5a3a06 Sofia Papagiannaki
        message = _('Your request for an account was successfully received and is now pending \
269 8f5a3a06 Sofia Papagiannaki
                    approval. You will be notified by email in the next few days. Thanks for \
270 8f5a3a06 Sofia Papagiannaki
                    your interest in ~okeanos! The GRNET team.')
271 8f5a3a06 Sofia Papagiannaki
        super(NotificationSent, self).__init__(message)
272 8f5a3a06 Sofia Papagiannaki
273 8f5a3a06 Sofia Papagiannaki
class RegistationCompleted(ActivationResult):
274 8f5a3a06 Sofia Papagiannaki
    def __init__(self):
275 8f5a3a06 Sofia Papagiannaki
        message = _('Registration completed. You can now login.')
276 18ffbee1 Sofia Papagiannaki
        super(RegistationCompleted, self).__init__(message)