1 # Copyright 2011 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials
14 # provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
34 from django.conf import settings
35 from django.utils.importlib import import_module
36 from django.core.exceptions import ImproperlyConfigured
37 from django.core.mail import send_mail
38 from django.template.loader import render_to_string
39 from django.utils.translation import ugettext as _
40 from django.contrib.auth.forms import UserCreationForm
41 from django.contrib.sites.models import Site
42 from django.contrib import messages
43 from django.shortcuts import redirect
45 from smtplib import SMTPException
46 from urllib import quote
48 from astakos.im.util import get_or_create_user
49 from astakos.im.models import AstakosUser, Invitation
50 from astakos.im.forms import ExtendedUserCreationForm, InvitedExtendedUserCreationForm
55 def get_backend(request):
57 Return an instance of a registration backend,
58 according to the INVITATIONS_ENABLED setting
59 (if True returns ``astakos.im.backends.InvitationsBackend`` and if False
60 returns ``astakos.im.backends.SimpleBackend``).
62 If the backend cannot be located ``django.core.exceptions.ImproperlyConfigured``
65 module = 'astakos.im.backends'
66 prefix = 'Invitations' if settings.INVITATIONS_ENABLED else 'Simple'
67 backend_class_name = '%sBackend' %prefix
69 mod = import_module(module)
70 except ImportError, e:
71 raise ImproperlyConfigured('Error loading registration backend %s: "%s"' % (module, e))
73 backend_class = getattr(mod, backend_class_name)
74 except AttributeError:
75 raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
76 return backend_class(request)
78 class InvitationsBackend(object):
80 A registration backend which implements the following workflow: a user
81 supplies the necessary registation information, if the request contains a valid
82 inivation code the user is automatically activated otherwise an inactive user
83 account is created and the user is going to receive an email as soon as an
84 administrator activates his/her account.
86 def __init__(self, request):
87 self.request = request
88 self.invitation = None
91 def set_invitation(self):
92 code = self.request.GET.get('code', '')
94 code = self.request.POST.get('code', '')
96 self.invitation = Invitation.objects.get(code=code)
97 if self.invitation.is_consumed:
98 raise Exception('Invitation has beeen used')
100 def get_signup_form(self):
102 Returns the necassary registration form depending the user is invited or not
104 Throws Invitation.DoesNotExist in case ``code`` is not valid.
106 request = self.request
107 formclass = 'ExtendedUserCreationForm'
109 if request.method == 'GET':
111 formclass = 'Invited%s' %formclass
112 initial_data = {'username':self.invitation.username,
113 'email':self.invitation.username,
114 'realname':self.invitation.realname}
115 inviter = AstakosUser.objects.get(username=self.invitation.inviter)
116 initial_data['inviter'] = inviter.realname
117 initial_data['level'] = inviter.level + 1
119 initial_data = request.POST
120 return globals()[formclass](initial_data)
122 def _is_preaccepted(self, user):
124 If there is a valid, not-consumed invitation code for the specific user
125 returns True else returns False.
127 It should be called after ``get_signup_form`` which sets invitation if exists.
129 invitation = self.invitation
132 if invitation.username == user.email and not invitation.is_consumed:
137 def signup(self, form):
139 Initially creates an inactive user account. If the user is preaccepted
140 (has a valid invitation code) the user is activated and if the request
141 param ``next`` is present redirects to it.
142 In any other case the method returns the action status and a message.
147 if self._is_preaccepted(user):
148 user.is_active = True
150 message = _('Registration completed. You can now login.')
152 message = _('Registration completed. You will receive an email upon your account\'s activation')
153 status = messages.SUCCESS
154 except Invitation.DoesNotExist, e:
155 status = messages.ERROR
156 message = _('Invalid invitation code')
157 return status, message
159 class SimpleBackend(object):
161 A registration backend which implements the following workflow: a user
162 supplies the necessary registation information, an incative user account is
163 created and receives an email in order to activate his/her account.
165 def __init__(self, request):
166 self.request = request
168 def get_signup_form(self):
170 Returns the UserCreationForm
172 request = self.request
173 initial_data = request.POST if request.method == 'POST' else None
174 return ExtendedUserCreationForm(initial_data)
176 def signup(self, form, email_template_name='activation_email.txt'):
178 Creates an inactive user account and sends a verification email.
182 ``email_template_name``
183 A custom template for the verification email body to use. This is
184 optional; if not specified, this will default to
185 ``activation_email.txt``.
188 activation_email.txt or ``email_template_name`` keyword argument
192 * ACTIVATION_LOGIN_TARGET: Where users should activate their local account
193 * DEFAULT_CONTACT_EMAIL: service support email
194 * DEFAULT_FROM_EMAIL: from email
199 status = messages.SUCCESS
201 _send_verification(self.request, user, email_template_name)
202 message = _('Verification sent to %s' % user.email)
203 except (SMTPException, socket.error) as e:
204 status = messages.ERROR
206 message = getattr(e, name) if hasattr(e, name) else e
207 return status, message
209 def _send_verification(request, user, template_name):
210 site = Site.objects.get_current()
211 baseurl = request.build_absolute_uri('/').rstrip('/')
212 url = settings.ACTIVATION_LOGIN_TARGET % (baseurl,
213 quote(user.auth_token),
215 message = render_to_string(template_name, {
219 'site_name': site.name,
220 'support': settings.DEFAULT_CONTACT_EMAIL})
221 sender = settings.DEFAULT_FROM_EMAIL
222 send_mail('Pithos account activation', message, sender, [user.email])
223 logging.info('Sent activation %s', user)