Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / backends.py @ a9164f06

History | View | Annotate | Download (12 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.utils.translation import ugettext as _
39 dbe090ba root
from django.contrib.sites.models import Site
40 890b0eaf Sofia Papagiannaki
from django.contrib import messages
41 15efc749 Sofia Papagiannaki
from django.db import transaction
42 1463659a Sofia Papagiannaki
from django.core.urlresolvers import reverse
43 890b0eaf Sofia Papagiannaki
44 890b0eaf Sofia Papagiannaki
from smtplib import SMTPException
45 374611bc Sofia Papagiannaki
from urlparse import urljoin
46 890b0eaf Sofia Papagiannaki
47 890b0eaf Sofia Papagiannaki
from astakos.im.models import AstakosUser, Invitation
48 15efc749 Sofia Papagiannaki
from astakos.im.forms import *
49 15efc749 Sofia Papagiannaki
from astakos.im.util import get_invitation
50 683cf244 Sofia Papagiannaki
from astakos.im.functions import send_verification, send_notification, activate
51 8316698a Sofia Papagiannaki
from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, BASEURL, 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 65d85494 Sofia Papagiannaki
    Returns an instance of a registration backend,
62 890b0eaf Sofia Papagiannaki
    according to the INVITATIONS_ENABLED setting
63 890b0eaf Sofia Papagiannaki
    (if True returns ``astakos.im.backends.InvitationsBackend`` and if False
64 890b0eaf Sofia Papagiannaki
    returns ``astakos.im.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 890b0eaf Sofia Papagiannaki
    module = 'astakos.im.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 0905ccd2 Sofia Papagiannaki
        raise ImproperlyConfigured('Error loading registration 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 0905ccd2 Sofia Papagiannaki
        raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
80 5ed6816e Sofia Papagiannaki
    return backend_class(request)
81 890b0eaf Sofia Papagiannaki
82 8316698a Sofia Papagiannaki
class SignupBackend(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 8316698a Sofia Papagiannaki
90 8316698a Sofia Papagiannaki
class InvitationsBackend(SignupBackend):
91 890b0eaf Sofia Papagiannaki
    """
92 890b0eaf Sofia Papagiannaki
    A registration backend which implements the following workflow: a user
93 890b0eaf Sofia Papagiannaki
    supplies the necessary registation information, if the request contains a valid
94 890b0eaf Sofia Papagiannaki
    inivation code the user is automatically activated otherwise an inactive user
95 890b0eaf Sofia Papagiannaki
    account is created and the user is going to receive an email as soon as an
96 890b0eaf Sofia Papagiannaki
    administrator activates his/her account.
97 890b0eaf Sofia Papagiannaki
    """
98 5ed6816e Sofia Papagiannaki
    def __init__(self, request):
99 15efc749 Sofia Papagiannaki
        """
100 15efc749 Sofia Papagiannaki
        raises Invitation.DoesNotExist and ValueError if invitation is consumed
101 15efc749 Sofia Papagiannaki
        or invitation username is reserved.
102 15efc749 Sofia Papagiannaki
        """
103 5ed6816e Sofia Papagiannaki
        self.request = request
104 15efc749 Sofia Papagiannaki
        self.invitation = get_invitation(request)
105 8316698a Sofia Papagiannaki
        super(InvitationsBackend, self).__init__()
106 bef3bf46 Kostas Papadimitriou
107 15efc749 Sofia Papagiannaki
    def get_signup_form(self, provider):
108 15efc749 Sofia Papagiannaki
        """
109 bef3bf46 Kostas Papadimitriou
        Returns the form class name
110 15efc749 Sofia Papagiannaki
        """
111 15efc749 Sofia Papagiannaki
        invitation = self.invitation
112 15efc749 Sofia Papagiannaki
        initial_data = self.get_signup_initial_data(provider)
113 15efc749 Sofia Papagiannaki
        prefix = 'Invited' if invitation else ''
114 15efc749 Sofia Papagiannaki
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
115 15efc749 Sofia Papagiannaki
        suffix  = 'UserCreationForm'
116 15efc749 Sofia Papagiannaki
        formclass = '%s%s%s' % (prefix, main, suffix)
117 bef3bf46 Kostas Papadimitriou
        ip = self.request.META.get('REMOTE_ADDR',
118 bef3bf46 Kostas Papadimitriou
                self.request.META.get('HTTP_X_REAL_IP', None))
119 bef3bf46 Kostas Papadimitriou
        return globals()[formclass](initial_data, ip=ip)
120 bef3bf46 Kostas Papadimitriou
121 15efc749 Sofia Papagiannaki
    def get_signup_initial_data(self, provider):
122 890b0eaf Sofia Papagiannaki
        """
123 890b0eaf Sofia Papagiannaki
        Returns the necassary registration form depending the user is invited or not
124 bef3bf46 Kostas Papadimitriou

125 890b0eaf Sofia Papagiannaki
        Throws Invitation.DoesNotExist in case ``code`` is not valid.
126 890b0eaf Sofia Papagiannaki
        """
127 5ed6816e Sofia Papagiannaki
        request = self.request
128 15efc749 Sofia Papagiannaki
        invitation = self.invitation
129 65d85494 Sofia Papagiannaki
        initial_data = None
130 890b0eaf Sofia Papagiannaki
        if request.method == 'GET':
131 15efc749 Sofia Papagiannaki
            if invitation:
132 65d85494 Sofia Papagiannaki
                # create a tmp user with the invitation realname
133 65d85494 Sofia Papagiannaki
                # to extract first and last name
134 15efc749 Sofia Papagiannaki
                u = AstakosUser(realname = invitation.realname)
135 15efc749 Sofia Papagiannaki
                initial_data = {'email':invitation.username,
136 15efc749 Sofia Papagiannaki
                                'inviter':invitation.inviter.realname,
137 65d85494 Sofia Papagiannaki
                                'first_name':u.first_name,
138 65d85494 Sofia Papagiannaki
                                'last_name':u.last_name}
139 15efc749 Sofia Papagiannaki
        else:
140 15efc749 Sofia Papagiannaki
            if provider == request.POST.get('provider', ''):
141 15efc749 Sofia Papagiannaki
                initial_data = request.POST
142 15efc749 Sofia Papagiannaki
        return initial_data
143 bef3bf46 Kostas Papadimitriou
144 890b0eaf Sofia Papagiannaki
    def _is_preaccepted(self, user):
145 890b0eaf Sofia Papagiannaki
        """
146 890b0eaf Sofia Papagiannaki
        If there is a valid, not-consumed invitation code for the specific user
147 890b0eaf Sofia Papagiannaki
        returns True else returns False.
148 890b0eaf Sofia Papagiannaki
        """
149 8316698a Sofia Papagiannaki
        if super(InvitationsBackend, self)._is_preaccepted(user):
150 8316698a Sofia Papagiannaki
            return True
151 5ed6816e Sofia Papagiannaki
        invitation = self.invitation
152 890b0eaf Sofia Papagiannaki
        if not invitation:
153 890b0eaf Sofia Papagiannaki
            return False
154 5ed6816e Sofia Papagiannaki
        if invitation.username == user.email and not invitation.is_consumed:
155 5ed6816e Sofia Papagiannaki
            invitation.consume()
156 890b0eaf Sofia Papagiannaki
            return True
157 890b0eaf Sofia Papagiannaki
        return False
158 bef3bf46 Kostas Papadimitriou
159 15efc749 Sofia Papagiannaki
    @transaction.commit_manually
160 683cf244 Sofia Papagiannaki
    def signup(self, form, verification_template_name='im/activation_email.txt', greeting_template_name='im/welcome_email.txt', admin_email_template_name='im/admin_notification.txt'):
161 890b0eaf Sofia Papagiannaki
        """
162 5ed6816e Sofia Papagiannaki
        Initially creates an inactive user account. If the user is preaccepted
163 5ed6816e Sofia Papagiannaki
        (has a valid invitation code) the user is activated and if the request
164 5ed6816e Sofia Papagiannaki
        param ``next`` is present redirects to it.
165 890b0eaf Sofia Papagiannaki
        In any other case the method returns the action status and a message.
166 bef3bf46 Kostas Papadimitriou

167 15efc749 Sofia Papagiannaki
        The method uses commit_manually decorator in order to ensure the user
168 15efc749 Sofia Papagiannaki
        will be created only if the procedure has been completed successfully.
169 890b0eaf Sofia Papagiannaki
        """
170 15efc749 Sofia Papagiannaki
        user = None
171 890b0eaf Sofia Papagiannaki
        try:
172 15efc749 Sofia Papagiannaki
            user = form.save()
173 890b0eaf Sofia Papagiannaki
            if self._is_preaccepted(user):
174 8316698a Sofia Papagiannaki
                if user.email_verified:
175 683cf244 Sofia Papagiannaki
                    try:
176 683cf244 Sofia Papagiannaki
                        activate(user, greeting_template_name)
177 683cf244 Sofia Papagiannaki
                        message = _('Registration completed. You can now login.')
178 683cf244 Sofia Papagiannaki
                    except (SMTPException, socket.error) as e:
179 683cf244 Sofia Papagiannaki
                        status = messages.ERROR
180 683cf244 Sofia Papagiannaki
                        name = 'strerror'
181 683cf244 Sofia Papagiannaki
                        message = getattr(e, 'name') if hasattr(e, 'name') else e
182 8316698a Sofia Papagiannaki
                else:
183 8316698a Sofia Papagiannaki
                    try:
184 683cf244 Sofia Papagiannaki
                        send_verification(user, verification_template_name)
185 8316698a Sofia Papagiannaki
                        message = _('Verification sent to %s' % user.email)
186 8316698a Sofia Papagiannaki
                    except (SMTPException, socket.error) as e:
187 8316698a Sofia Papagiannaki
                        status = messages.ERROR
188 8316698a Sofia Papagiannaki
                        name = 'strerror'
189 683cf244 Sofia Papagiannaki
                        message = getattr(e, 'name') if hasattr(e, 'name') else e
190 890b0eaf Sofia Papagiannaki
            else:
191 683cf244 Sofia Papagiannaki
                send_notification(user, admin_email_template_name)
192 683cf244 Sofia Papagiannaki
                message = _('Your request for an account was successfully received and is now pending \
193 683cf244 Sofia Papagiannaki
                            approval. You will be notified by email in the next few days. Thanks for \
194 683cf244 Sofia Papagiannaki
                            your interest in ~okeanos! The GRNET team.')
195 890b0eaf Sofia Papagiannaki
            status = messages.SUCCESS
196 890b0eaf Sofia Papagiannaki
        except Invitation.DoesNotExist, e:
197 890b0eaf Sofia Papagiannaki
            status = messages.ERROR
198 890b0eaf Sofia Papagiannaki
            message = _('Invalid invitation code')
199 15efc749 Sofia Papagiannaki
        except socket.error, e:
200 15efc749 Sofia Papagiannaki
            status = messages.ERROR
201 15efc749 Sofia Papagiannaki
            message = _(e.strerror)
202 bef3bf46 Kostas Papadimitriou
203 15efc749 Sofia Papagiannaki
        # rollback in case of error
204 15efc749 Sofia Papagiannaki
        if status == messages.ERROR:
205 15efc749 Sofia Papagiannaki
            transaction.rollback()
206 15efc749 Sofia Papagiannaki
        else:
207 15efc749 Sofia Papagiannaki
            transaction.commit()
208 15efc749 Sofia Papagiannaki
        return status, message, user
209 890b0eaf Sofia Papagiannaki
210 8316698a Sofia Papagiannaki
class SimpleBackend(SignupBackend):
211 890b0eaf Sofia Papagiannaki
    """
212 890b0eaf Sofia Papagiannaki
    A registration backend which implements the following workflow: a user
213 890b0eaf Sofia Papagiannaki
    supplies the necessary registation information, an incative user account is
214 890b0eaf Sofia Papagiannaki
    created and receives an email in order to activate his/her account.
215 890b0eaf Sofia Papagiannaki
    """
216 5ed6816e Sofia Papagiannaki
    def __init__(self, request):
217 5ed6816e Sofia Papagiannaki
        self.request = request
218 8316698a Sofia Papagiannaki
        super(SimpleBackend, self).__init__()
219 bef3bf46 Kostas Papadimitriou
220 15efc749 Sofia Papagiannaki
    def get_signup_form(self, provider):
221 890b0eaf Sofia Papagiannaki
        """
222 15efc749 Sofia Papagiannaki
        Returns the form class name
223 890b0eaf Sofia Papagiannaki
        """
224 15efc749 Sofia Papagiannaki
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
225 15efc749 Sofia Papagiannaki
        suffix  = 'UserCreationForm'
226 15efc749 Sofia Papagiannaki
        formclass = '%s%s' % (main, suffix)
227 5ed6816e Sofia Papagiannaki
        request = self.request
228 15efc749 Sofia Papagiannaki
        initial_data = None
229 15efc749 Sofia Papagiannaki
        if request.method == 'POST':
230 15efc749 Sofia Papagiannaki
            if provider == request.POST.get('provider', ''):
231 15efc749 Sofia Papagiannaki
                initial_data = request.POST
232 bef3bf46 Kostas Papadimitriou
        ip = self.request.META.get('REMOTE_ADDR',
233 bef3bf46 Kostas Papadimitriou
                self.request.META.get('HTTP_X_REAL_IP', None))
234 bef3bf46 Kostas Papadimitriou
        return globals()[formclass](initial_data, ip=ip)
235 8316698a Sofia Papagiannaki
    
236 8316698a Sofia Papagiannaki
    def _is_preaccepted(self, user):
237 8316698a Sofia Papagiannaki
        if super(SimpleBackend, self)._is_preaccepted(user):
238 8316698a Sofia Papagiannaki
            return True
239 8316698a Sofia Papagiannaki
        if MODERATION_ENABLED:
240 8316698a Sofia Papagiannaki
            return False
241 8316698a Sofia Papagiannaki
        return True
242 8316698a Sofia Papagiannaki
    
243 15efc749 Sofia Papagiannaki
    @transaction.commit_manually
244 2ecbbc70 Sofia Papagiannaki
    def signup(self, form, email_template_name='im/activation_email.txt', admin_email_template_name='im/admin_notification.txt'):
245 890b0eaf Sofia Papagiannaki
        """
246 890b0eaf Sofia Papagiannaki
        Creates an inactive user account and sends a verification email.
247 bef3bf46 Kostas Papadimitriou

248 15efc749 Sofia Papagiannaki
        The method uses commit_manually decorator in order to ensure the user
249 15efc749 Sofia Papagiannaki
        will be created only if the procedure has been completed successfully.
250 bef3bf46 Kostas Papadimitriou

251 890b0eaf Sofia Papagiannaki
        ** Arguments **
252 bef3bf46 Kostas Papadimitriou

253 890b0eaf Sofia Papagiannaki
        ``email_template_name``
254 890b0eaf Sofia Papagiannaki
            A custom template for the verification email body to use. This is
255 890b0eaf Sofia Papagiannaki
            optional; if not specified, this will default to
256 1e685275 Sofia Papagiannaki
            ``im/activation_email.txt``.
257 bef3bf46 Kostas Papadimitriou

258 890b0eaf Sofia Papagiannaki
        ** Templates **
259 1e685275 Sofia Papagiannaki
            im/activation_email.txt or ``email_template_name`` keyword argument
260 bef3bf46 Kostas Papadimitriou

261 890b0eaf Sofia Papagiannaki
        ** Settings **
262 bef3bf46 Kostas Papadimitriou

263 890b0eaf Sofia Papagiannaki
        * DEFAULT_CONTACT_EMAIL: service support email
264 890b0eaf Sofia Papagiannaki
        * DEFAULT_FROM_EMAIL: from email
265 890b0eaf Sofia Papagiannaki
        """
266 1463659a Sofia Papagiannaki
        user = form.save()
267 1463659a Sofia Papagiannaki
        status = messages.SUCCESS
268 8316698a Sofia Papagiannaki
        if not self._is_preaccepted(user):
269 2ecbbc70 Sofia Papagiannaki
            try:
270 683cf244 Sofia Papagiannaki
                send_notification(user, admin_email_template_name)
271 683cf244 Sofia Papagiannaki
                message = _('Your request for an account was successfully received and is now pending \
272 683cf244 Sofia Papagiannaki
                            approval. You will be notified by email in the next few days. Thanks for \
273 683cf244 Sofia Papagiannaki
                            your interest in ~okeanos! The GRNET team.')
274 2ecbbc70 Sofia Papagiannaki
            except (SMTPException, socket.error) as e:
275 2ecbbc70 Sofia Papagiannaki
                status = messages.ERROR
276 2ecbbc70 Sofia Papagiannaki
                name = 'strerror'
277 683cf244 Sofia Papagiannaki
                message = getattr(e, 'name') if hasattr(e, 'name') else e
278 1463659a Sofia Papagiannaki
        else:
279 1463659a Sofia Papagiannaki
            try:
280 683cf244 Sofia Papagiannaki
                send_verification(user, email_template_name)
281 1463659a Sofia Papagiannaki
                message = _('Verification sent to %s' % user.email)
282 1463659a Sofia Papagiannaki
            except (SMTPException, socket.error) as e:
283 1463659a Sofia Papagiannaki
                status = messages.ERROR
284 1463659a Sofia Papagiannaki
                name = 'strerror'
285 683cf244 Sofia Papagiannaki
                message = getattr(e, 'name') if hasattr(e, 'name') else e
286 bef3bf46 Kostas Papadimitriou
287 15efc749 Sofia Papagiannaki
        # rollback in case of error
288 15efc749 Sofia Papagiannaki
        if status == messages.ERROR:
289 15efc749 Sofia Papagiannaki
            transaction.rollback()
290 15efc749 Sofia Papagiannaki
        else:
291 15efc749 Sofia Papagiannaki
            transaction.commit()
292 15efc749 Sofia Papagiannaki
        return status, message, user