Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.2 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 890b0eaf Sofia Papagiannaki
from urllib import quote
46 374611bc Sofia Papagiannaki
from urlparse import urljoin
47 890b0eaf Sofia Papagiannaki
48 890b0eaf Sofia Papagiannaki
from astakos.im.models import AstakosUser, Invitation
49 15efc749 Sofia Papagiannaki
from astakos.im.forms import *
50 15efc749 Sofia Papagiannaki
from astakos.im.util import get_invitation
51 2ecbbc70 Sofia Papagiannaki
from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, BASEURL, DEFAULT_ADMIN_EMAIL
52 0905ccd2 Sofia Papagiannaki
53 5ed6816e Sofia Papagiannaki
import socket
54 5ed6816e Sofia Papagiannaki
import logging
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 65d85494 Sofia Papagiannaki
    Returns an instance of a registration backend,
61 890b0eaf Sofia Papagiannaki
    according to the INVITATIONS_ENABLED setting
62 890b0eaf Sofia Papagiannaki
    (if True returns ``astakos.im.backends.InvitationsBackend`` and if False
63 890b0eaf Sofia Papagiannaki
    returns ``astakos.im.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 890b0eaf Sofia Papagiannaki
    module = 'astakos.im.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 0905ccd2 Sofia Papagiannaki
        raise ImproperlyConfigured('Error loading registration 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 0905ccd2 Sofia Papagiannaki
        raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
79 5ed6816e Sofia Papagiannaki
    return backend_class(request)
80 890b0eaf Sofia Papagiannaki
81 890b0eaf Sofia Papagiannaki
class InvitationsBackend(object):
82 890b0eaf Sofia Papagiannaki
    """
83 890b0eaf Sofia Papagiannaki
    A registration backend which implements the following workflow: a user
84 890b0eaf Sofia Papagiannaki
    supplies the necessary registation information, if the request contains a valid
85 890b0eaf Sofia Papagiannaki
    inivation code the user is automatically activated otherwise an inactive user
86 890b0eaf Sofia Papagiannaki
    account is created and the user is going to receive an email as soon as an
87 890b0eaf Sofia Papagiannaki
    administrator activates his/her account.
88 890b0eaf Sofia Papagiannaki
    """
89 5ed6816e Sofia Papagiannaki
    def __init__(self, request):
90 15efc749 Sofia Papagiannaki
        """
91 15efc749 Sofia Papagiannaki
        raises Invitation.DoesNotExist and ValueError if invitation is consumed
92 15efc749 Sofia Papagiannaki
        or invitation username is reserved.
93 15efc749 Sofia Papagiannaki
        """
94 5ed6816e Sofia Papagiannaki
        self.request = request
95 15efc749 Sofia Papagiannaki
        self.invitation = get_invitation(request)
96 bef3bf46 Kostas Papadimitriou
97 15efc749 Sofia Papagiannaki
    def get_signup_form(self, provider):
98 15efc749 Sofia Papagiannaki
        """
99 bef3bf46 Kostas Papadimitriou
        Returns the form class name
100 15efc749 Sofia Papagiannaki
        """
101 15efc749 Sofia Papagiannaki
        invitation = self.invitation
102 15efc749 Sofia Papagiannaki
        initial_data = self.get_signup_initial_data(provider)
103 15efc749 Sofia Papagiannaki
        prefix = 'Invited' if invitation else ''
104 15efc749 Sofia Papagiannaki
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
105 15efc749 Sofia Papagiannaki
        suffix  = 'UserCreationForm'
106 15efc749 Sofia Papagiannaki
        formclass = '%s%s%s' % (prefix, main, suffix)
107 bef3bf46 Kostas Papadimitriou
        ip = self.request.META.get('REMOTE_ADDR',
108 bef3bf46 Kostas Papadimitriou
                self.request.META.get('HTTP_X_REAL_IP', None))
109 bef3bf46 Kostas Papadimitriou
        return globals()[formclass](initial_data, ip=ip)
110 bef3bf46 Kostas Papadimitriou
111 15efc749 Sofia Papagiannaki
    def get_signup_initial_data(self, provider):
112 890b0eaf Sofia Papagiannaki
        """
113 890b0eaf Sofia Papagiannaki
        Returns the necassary registration form depending the user is invited or not
114 bef3bf46 Kostas Papadimitriou

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

155 15efc749 Sofia Papagiannaki
        The method uses commit_manually decorator in order to ensure the user
156 15efc749 Sofia Papagiannaki
        will be created only if the procedure has been completed successfully.
157 890b0eaf Sofia Papagiannaki
        """
158 15efc749 Sofia Papagiannaki
        user = None
159 890b0eaf Sofia Papagiannaki
        try:
160 15efc749 Sofia Papagiannaki
            user = form.save()
161 890b0eaf Sofia Papagiannaki
            if self._is_preaccepted(user):
162 890b0eaf Sofia Papagiannaki
                user.is_active = True
163 794852f2 Sofia Papagiannaki
                user.save()
164 890b0eaf Sofia Papagiannaki
                message = _('Registration completed. You can now login.')
165 890b0eaf Sofia Papagiannaki
            else:
166 2ecbbc70 Sofia Papagiannaki
                _send_notification(user, admin_email_template_name)
167 15efc749 Sofia Papagiannaki
                message = _('Registration completed. You will receive an email upon your account\'s activation.')
168 890b0eaf Sofia Papagiannaki
            status = messages.SUCCESS
169 890b0eaf Sofia Papagiannaki
        except Invitation.DoesNotExist, e:
170 890b0eaf Sofia Papagiannaki
            status = messages.ERROR
171 890b0eaf Sofia Papagiannaki
            message = _('Invalid invitation code')
172 15efc749 Sofia Papagiannaki
        except socket.error, e:
173 15efc749 Sofia Papagiannaki
            status = messages.ERROR
174 15efc749 Sofia Papagiannaki
            message = _(e.strerror)
175 bef3bf46 Kostas Papadimitriou
176 15efc749 Sofia Papagiannaki
        # rollback in case of error
177 15efc749 Sofia Papagiannaki
        if status == messages.ERROR:
178 15efc749 Sofia Papagiannaki
            transaction.rollback()
179 15efc749 Sofia Papagiannaki
        else:
180 15efc749 Sofia Papagiannaki
            transaction.commit()
181 15efc749 Sofia Papagiannaki
        return status, message, user
182 890b0eaf Sofia Papagiannaki
183 890b0eaf Sofia Papagiannaki
class SimpleBackend(object):
184 890b0eaf Sofia Papagiannaki
    """
185 890b0eaf Sofia Papagiannaki
    A registration backend which implements the following workflow: a user
186 890b0eaf Sofia Papagiannaki
    supplies the necessary registation information, an incative user account is
187 890b0eaf Sofia Papagiannaki
    created and receives an email in order to activate his/her account.
188 890b0eaf Sofia Papagiannaki
    """
189 5ed6816e Sofia Papagiannaki
    def __init__(self, request):
190 5ed6816e Sofia Papagiannaki
        self.request = request
191 bef3bf46 Kostas Papadimitriou
192 15efc749 Sofia Papagiannaki
    def get_signup_form(self, provider):
193 890b0eaf Sofia Papagiannaki
        """
194 15efc749 Sofia Papagiannaki
        Returns the form class name
195 890b0eaf Sofia Papagiannaki
        """
196 15efc749 Sofia Papagiannaki
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
197 15efc749 Sofia Papagiannaki
        suffix  = 'UserCreationForm'
198 15efc749 Sofia Papagiannaki
        formclass = '%s%s' % (main, suffix)
199 5ed6816e Sofia Papagiannaki
        request = self.request
200 15efc749 Sofia Papagiannaki
        initial_data = None
201 15efc749 Sofia Papagiannaki
        if request.method == 'POST':
202 15efc749 Sofia Papagiannaki
            if provider == request.POST.get('provider', ''):
203 15efc749 Sofia Papagiannaki
                initial_data = request.POST
204 bef3bf46 Kostas Papadimitriou
        ip = self.request.META.get('REMOTE_ADDR',
205 bef3bf46 Kostas Papadimitriou
                self.request.META.get('HTTP_X_REAL_IP', None))
206 bef3bf46 Kostas Papadimitriou
        return globals()[formclass](initial_data, ip=ip)
207 bef3bf46 Kostas Papadimitriou
208 15efc749 Sofia Papagiannaki
    @transaction.commit_manually
209 2ecbbc70 Sofia Papagiannaki
    def signup(self, form, email_template_name='im/activation_email.txt', admin_email_template_name='im/admin_notification.txt'):
210 890b0eaf Sofia Papagiannaki
        """
211 890b0eaf Sofia Papagiannaki
        Creates an inactive user account and sends a verification email.
212 bef3bf46 Kostas Papadimitriou

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

216 890b0eaf Sofia Papagiannaki
        ** Arguments **
217 bef3bf46 Kostas Papadimitriou

218 890b0eaf Sofia Papagiannaki
        ``email_template_name``
219 890b0eaf Sofia Papagiannaki
            A custom template for the verification email body to use. This is
220 890b0eaf Sofia Papagiannaki
            optional; if not specified, this will default to
221 1e685275 Sofia Papagiannaki
            ``im/activation_email.txt``.
222 bef3bf46 Kostas Papadimitriou

223 890b0eaf Sofia Papagiannaki
        ** Templates **
224 1e685275 Sofia Papagiannaki
            im/activation_email.txt or ``email_template_name`` keyword argument
225 bef3bf46 Kostas Papadimitriou

226 890b0eaf Sofia Papagiannaki
        ** Settings **
227 bef3bf46 Kostas Papadimitriou

228 890b0eaf Sofia Papagiannaki
        * DEFAULT_CONTACT_EMAIL: service support email
229 890b0eaf Sofia Papagiannaki
        * DEFAULT_FROM_EMAIL: from email
230 890b0eaf Sofia Papagiannaki
        """
231 1463659a Sofia Papagiannaki
        user = form.save()
232 1463659a Sofia Papagiannaki
        status = messages.SUCCESS
233 1463659a Sofia Papagiannaki
        if MODERATION_ENABLED:
234 2ecbbc70 Sofia Papagiannaki
            try:
235 2ecbbc70 Sofia Papagiannaki
                _send_notification(user, admin_email_template_name)
236 2ecbbc70 Sofia Papagiannaki
                message = _('Registration completed. You will receive an email upon your account\'s activation.')
237 2ecbbc70 Sofia Papagiannaki
            except (SMTPException, socket.error) as e:
238 2ecbbc70 Sofia Papagiannaki
                status = messages.ERROR
239 2ecbbc70 Sofia Papagiannaki
                name = 'strerror'
240 2ecbbc70 Sofia Papagiannaki
                message = getattr(e, name) if hasattr(e, name) else e
241 1463659a Sofia Papagiannaki
        else:
242 1463659a Sofia Papagiannaki
            try:
243 1463659a Sofia Papagiannaki
                _send_verification(self.request, user, email_template_name)
244 1463659a Sofia Papagiannaki
                message = _('Verification sent to %s' % user.email)
245 1463659a Sofia Papagiannaki
            except (SMTPException, socket.error) as e:
246 1463659a Sofia Papagiannaki
                status = messages.ERROR
247 1463659a Sofia Papagiannaki
                name = 'strerror'
248 1463659a Sofia Papagiannaki
                message = getattr(e, name) if hasattr(e, name) else e
249 bef3bf46 Kostas Papadimitriou
250 15efc749 Sofia Papagiannaki
        # rollback in case of error
251 15efc749 Sofia Papagiannaki
        if status == messages.ERROR:
252 15efc749 Sofia Papagiannaki
            transaction.rollback()
253 15efc749 Sofia Papagiannaki
        else:
254 15efc749 Sofia Papagiannaki
            transaction.commit()
255 15efc749 Sofia Papagiannaki
        return status, message, user
256 890b0eaf Sofia Papagiannaki
257 5ed6816e Sofia Papagiannaki
def _send_verification(request, user, template_name):
258 374611bc Sofia Papagiannaki
    url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('astakos.im.views.activate')),
259 1463659a Sofia Papagiannaki
                                    quote(user.auth_token),
260 374611bc Sofia Papagiannaki
                                    quote(BASEURL))
261 5ed6816e Sofia Papagiannaki
    message = render_to_string(template_name, {
262 5ed6816e Sofia Papagiannaki
            'user': user,
263 5ed6816e Sofia Papagiannaki
            'url': url,
264 374611bc Sofia Papagiannaki
            'baseurl': BASEURL,
265 10df1a91 Sofia Papagiannaki
            'site_name': SITENAME,
266 10df1a91 Sofia Papagiannaki
            'support': DEFAULT_CONTACT_EMAIL})
267 d552ecb7 Antony Chazapis
    sender = DEFAULT_FROM_EMAIL
268 10df1a91 Sofia Papagiannaki
    send_mail('%s account activation' % SITENAME, message, sender, [user.email])
269 e015e9e6 Sofia Papagiannaki
    logger.info('Sent activation %s', user)
270 2ecbbc70 Sofia Papagiannaki
271 2ecbbc70 Sofia Papagiannaki
def _send_notification(user, template_name):
272 0e17d1ad Sofia Papagiannaki
    if not DEFAULT_ADMIN_EMAIL:
273 0e17d1ad Sofia Papagiannaki
        return
274 2ecbbc70 Sofia Papagiannaki
    message = render_to_string(template_name, {
275 2ecbbc70 Sofia Papagiannaki
            'user': user,
276 2ecbbc70 Sofia Papagiannaki
            'baseurl': BASEURL,
277 2ecbbc70 Sofia Papagiannaki
            'site_name': SITENAME,
278 2ecbbc70 Sofia Papagiannaki
            'support': DEFAULT_CONTACT_EMAIL})
279 2ecbbc70 Sofia Papagiannaki
    sender = DEFAULT_FROM_EMAIL
280 2ecbbc70 Sofia Papagiannaki
    send_mail('%s account notification' % SITENAME, message, sender, [DEFAULT_ADMIN_EMAIL])
281 2ecbbc70 Sofia Papagiannaki
    logger.info('Sent admin notification for user %s', user)