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.utils.importlib import import_module
35 from django.core.exceptions import ImproperlyConfigured
36 from django.core.mail import send_mail
37 from django.template.loader import render_to_string
38 from django.contrib import messages
39 from django.core.urlresolvers import reverse
40 from django.utils.translation import ugettext as _
41 from django.db import transaction
43 from urlparse import urljoin
45 from astakos.im.models import AstakosUser, Invitation
46 from astakos.im.forms import *
47 from astakos.im.util import get_invitation
48 from astakos.im.functions import send_verification, send_activation, \
49 send_admin_notification, activate, SendMailError
50 from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, \
51 DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, DEFAULT_ADMIN_EMAIL, RE_USER_EMAIL_PATTERNS
57 logger = logging.getLogger(__name__)
59 def get_backend(request):
61 Returns an instance of an activation backend,
62 according to the INVITATIONS_ENABLED setting
63 (if True returns ``astakos.im.activation_backends.InvitationsBackend`` and if False
64 returns ``astakos.im.activation_backends.SimpleBackend``).
66 If the backend cannot be located ``django.core.exceptions.ImproperlyConfigured``
69 module = 'astakos.im.activation_backends'
70 prefix = 'Invitations' if INVITATIONS_ENABLED else 'Simple'
71 backend_class_name = '%sBackend' %prefix
73 mod = import_module(module)
74 except ImportError, e:
75 raise ImproperlyConfigured('Error loading activation backend %s: "%s"' % (module, e))
77 backend_class = getattr(mod, backend_class_name)
78 except AttributeError:
79 raise ImproperlyConfigured('Module "%s" does not define a activation backend named "%s"' % (module, attr))
80 return backend_class(request)
82 class ActivationBackend(object):
83 def _is_preaccepted(self, user):
84 # return True if user email matches specific patterns
85 for pattern in RE_USER_EMAIL_PATTERNS:
86 if re.match(pattern, user.email):
90 def get_signup_form(self, provider='local', instance=None):
92 Returns a form instance of the relevant class
94 main = provider.capitalize() if provider == 'local' else 'ThirdParty'
95 suffix = 'UserCreationForm'
96 formclass = '%s%s' % (main, suffix)
97 request = self.request
99 if request.method == 'POST':
100 if provider == request.POST.get('provider', ''):
101 initial_data = request.POST
102 return globals()[formclass](initial_data, instance=instance, request=request)
104 def handle_activation(self, user, \
105 activation_template_name='im/activation_email.txt', \
106 greeting_template_name='im/welcome_email.txt', \
107 admin_email_template_name='im/admin_notification.txt', \
108 switch_accounts_email_template_name='im/switch_accounts_email.txt'):
110 If the user is already active returns immediately.
111 If the user is not active and there is another account associated with
112 the specific email, it sends an informative email to the user whether
113 wants to switch to this account.
114 If the user is preaccepted and the email is verified, the account is
115 activated automatically. Otherwise, if the email is not verified,
116 it sends a verification email to the user.
117 If the user is not preaccepted, it sends an email to the administrators
118 and informs the user that the account is pending activation.
122 return RegistationCompleted()
123 if user.conflicting_email():
124 send_verification(user, switch_accounts_email_template_name)
125 return SwitchAccountsVerificationSent(user.email)
127 if self._is_preaccepted(user):
128 if user.email_verified:
129 activate(user, greeting_template_name)
130 return RegistationCompleted()
132 send_activation(user, activation_template_name)
133 return VerificationSent()
135 send_admin_notification(user, admin_email_template_name)
136 return NotificationSent()
137 except BaseException, e:
141 class InvitationsBackend(ActivationBackend):
143 A activation backend which implements the following workflow: a user
144 supplies the necessary registation information, if the request contains a valid
145 inivation code the user is automatically activated otherwise an inactive user
146 account is created and the user is going to receive an email as soon as an
147 administrator activates his/her account.
149 def __init__(self, request):
150 self.request = request
151 super(InvitationsBackend, self).__init__()
153 def get_signup_form(self, provider='local', instance=None):
155 Returns a form instance of the relevant class
157 raises Invitation.DoesNotExist and ValueError if invitation is consumed
158 or invitation username is reserved.
160 self.invitation = get_invitation(self.request)
161 invitation = self.invitation
162 initial_data = self.get_signup_initial_data(provider)
163 prefix = 'Invited' if invitation else ''
164 main = provider.capitalize()
165 suffix = 'UserCreationForm'
166 formclass = '%s%s%s' % (prefix, main, suffix)
167 return globals()[formclass](initial_data, instance=instance, request=self.request)
169 def get_signup_initial_data(self, provider):
171 Returns the necassary activation form depending the user is invited or not
173 Throws Invitation.DoesNotExist in case ``code`` is not valid.
175 request = self.request
176 invitation = self.invitation
178 if request.method == 'GET':
180 # create a tmp user with the invitation realname
181 # to extract first and last name
182 u = AstakosUser(realname = invitation.realname)
183 initial_data = {'email':invitation.username,
184 'inviter':invitation.inviter.realname,
185 'first_name':u.first_name,
186 'last_name':u.last_name,
189 if provider == request.POST.get('provider', ''):
190 initial_data = request.POST
193 def _is_preaccepted(self, user):
195 If there is a valid, not-consumed invitation code for the specific user
196 returns True else returns False.
198 if super(InvitationsBackend, self)._is_preaccepted(user):
200 invitation = self.invitation
203 if invitation.username == user.email and not invitation.is_consumed:
208 class SimpleBackend(ActivationBackend):
210 A activation backend which implements the following workflow: a user
211 supplies the necessary registation information, an incative user account is
212 created and receives an email in order to activate his/her account.
214 def __init__(self, request):
215 self.request = request
216 super(SimpleBackend, self).__init__()
218 def _is_preaccepted(self, user):
219 if super(SimpleBackend, self)._is_preaccepted(user):
221 if MODERATION_ENABLED:
225 class ActivationResult(object):
226 def __init__(self, message):
227 self.message = message
229 class VerificationSent(ActivationResult):
231 message = _('Verification sent.')
232 super(VerificationSent, self).__init__(message)
234 class SwitchAccountsVerificationSent(ActivationResult):
235 def __init__(self, email):
236 message = _('This email is already associated with another \
237 local account. To change this account to a shibboleth \
238 one follow the link in the verification email sent \
239 to %s. Otherwise just ignore it.' % email)
240 super(SwitchAccountsVerificationSent, self).__init__(message)
242 class NotificationSent(ActivationResult):
244 message = _('Your request for an account was successfully received and is now pending \
245 approval. You will be notified by email in the next few days. Thanks for \
246 your interest in ~okeanos! The GRNET team.')
247 super(NotificationSent, self).__init__(message)
249 class RegistationCompleted(ActivationResult):
251 message = _('Registration completed. You can now login.')
252 super(RegistationCompleted, self).__init__(message)