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.sites.models import Site
39 from django.contrib import messages
40 from django.core.urlresolvers import reverse
41 from django.utils.translation import ugettext as _
42 from django.db import transaction
44 from urlparse import urljoin
46 from astakos.im.models import AstakosUser, Invitation
47 from astakos.im.forms import *
48 from astakos.im.util import get_invitation
49 from astakos.im.functions import send_verification, send_admin_notification, activate, SendMailError
50 from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, DEFAULT_ADMIN_EMAIL, RE_USER_EMAIL_PATTERNS
56 logger = logging.getLogger(__name__)
58 def get_backend(request):
60 Returns an instance of an activation backend,
61 according to the INVITATIONS_ENABLED setting
62 (if True returns ``astakos.im.activation_backends.InvitationsBackend`` and if False
63 returns ``astakos.im.activation_backends.SimpleBackend``).
65 If the backend cannot be located ``django.core.exceptions.ImproperlyConfigured``
68 module = 'astakos.im.activation_backends'
69 prefix = 'Invitations' if INVITATIONS_ENABLED else 'Simple'
70 backend_class_name = '%sBackend' %prefix
72 mod = import_module(module)
73 except ImportError, e:
74 raise ImproperlyConfigured('Error loading activation backend %s: "%s"' % (module, e))
76 backend_class = getattr(mod, backend_class_name)
77 except AttributeError:
78 raise ImproperlyConfigured('Module "%s" does not define a activation backend named "%s"' % (module, attr))
79 return backend_class(request)
81 class ActivationBackend(object):
82 def _is_preaccepted(self, user):
83 # return True if user email matches specific patterns
84 for pattern in RE_USER_EMAIL_PATTERNS:
85 if re.match(pattern, user.email):
89 def get_signup_form(self, provider='local', instance=None):
91 Returns the form class name
93 main = provider.capitalize() if provider == 'local' else 'ThirdParty'
94 suffix = 'UserCreationForm'
95 formclass = '%s%s' % (main, suffix)
96 request = self.request
98 if request.method == 'POST':
99 if provider == request.POST.get('provider', ''):
100 initial_data = request.POST
101 return globals()[formclass](initial_data, instance=instance, request=request)
103 def handle_activation(self, user, \
104 verification_template_name='im/activation_email.txt', \
105 greeting_template_name='im/welcome_email.txt', \
106 admin_email_template_name='im/admin_notification.txt', \
107 switch_accounts_email_template_name='im/switch_accounts_email.txt'):
109 If the user is already active returns immediately.
110 If the user is not active and there is another account associated with
111 the specific email, it sends an informative email to the user whether
112 wants to switch to this account.
113 If the user is preaccepted and the email is verified, the account is
114 activated automatically. Otherwise, if the email is not verified,
115 it sends a verification email to the user.
116 If the user is not preaccepted, it sends an email to the administrators
117 and informs the user that the account is pending activation.
121 return RegistationCompleted()
122 if user.conflicting_email():
123 send_verification(user, switch_accounts_email_template_name)
124 return SwitchAccountsVerificationSent(user.email)
126 if self._is_preaccepted(user):
127 if user.email_verified:
128 activate(user, greeting_template_name)
129 return RegistationCompleted()
131 send_verification(user, verification_template_name)
132 return VerificationSent()
134 send_admin_notification(user, admin_email_template_name)
135 return NotificationSent()
136 except BaseException, e:
140 class InvitationsBackend(ActivationBackend):
142 A activation backend which implements the following workflow: a user
143 supplies the necessary registation information, if the request contains a valid
144 inivation code the user is automatically activated otherwise an inactive user
145 account is created and the user is going to receive an email as soon as an
146 administrator activates his/her account.
148 def __init__(self, request):
149 self.request = request
150 super(InvitationsBackend, self).__init__()
152 def get_signup_form(self, provider='local', instance=None):
154 Returns the form class
156 raises Invitation.DoesNotExist and ValueError if invitation is consumed
157 or invitation username is reserved.
159 self.invitation = get_invitation(self.request)
160 invitation = self.invitation
161 initial_data = self.get_signup_initial_data(provider)
162 prefix = 'Invited' if invitation else ''
163 main = provider.capitalize()
164 suffix = 'UserCreationForm'
165 formclass = '%s%s%s' % (prefix, main, suffix)
166 return globals()[formclass](initial_data, instance=instance, request=self.request)
168 def get_signup_initial_data(self, provider):
170 Returns the necassary activation form depending the user is invited or not
172 Throws Invitation.DoesNotExist in case ``code`` is not valid.
174 request = self.request
175 invitation = self.invitation
177 if request.method == 'GET':
179 # create a tmp user with the invitation realname
180 # to extract first and last name
181 u = AstakosUser(realname = invitation.realname)
182 initial_data = {'email':invitation.username,
183 'inviter':invitation.inviter.realname,
184 'first_name':u.first_name,
185 'last_name':u.last_name,
188 if provider == request.POST.get('provider', ''):
189 initial_data = request.POST
192 def _is_preaccepted(self, user):
194 If there is a valid, not-consumed invitation code for the specific user
195 returns True else returns False.
197 if super(InvitationsBackend, self)._is_preaccepted(user):
199 invitation = self.invitation
202 if invitation.username == user.email and not invitation.is_consumed:
207 class SimpleBackend(ActivationBackend):
209 A activation backend which implements the following workflow: a user
210 supplies the necessary registation information, an incative user account is
211 created and receives an email in order to activate his/her account.
213 def __init__(self, request):
214 self.request = request
215 super(SimpleBackend, self).__init__()
217 def _is_preaccepted(self, user):
218 if super(SimpleBackend, self)._is_preaccepted(user):
220 if MODERATION_ENABLED:
224 class ActivationResult(object):
225 def __init__(self, message):
226 self.message = message
228 class VerificationSent(ActivationResult):
230 message = _('Verification sent.')
231 super(VerificationSent, self).__init__(message)
233 class SwitchAccountsVerificationSent(ActivationResult):
234 def __init__(self, email):
235 message = _('This email is already associated with another \
236 local account. To change this account to a shibboleth \
237 one follow the link in the verification email sent \
238 to %s. Otherwise just ignore it.' % email)
239 super(SwitchAccountsVerificationSent, self).__init__(message)
241 class NotificationSent(ActivationResult):
243 message = _('Your request for an account was successfully received and is now pending \
244 approval. You will be notified by email in the next few days. Thanks for \
245 your interest in ~okeanos! The GRNET team.')
246 super(NotificationSent, self).__init__(message)
248 class RegistationCompleted(ActivationResult):
250 message = _('Registration completed. You can now login.')
251 super(RegistationCompleted, self).__init__(message)