Update snf-manage commands in email templates. Fix authentication token renewal.
[astakos] / snf-astakos-app / astakos / im / functions.py
1 # Copyright 2011 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
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.
15 #
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.
28 #
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.
33
34 import logging
35 import socket
36
37 from django.utils.translation import ugettext as _
38 from django.template.loader import render_to_string
39 from django.core.mail import send_mail
40 from django.core.urlresolvers import reverse
41 from django.template import Context, loader
42 from django.contrib.auth import (
43     login as auth_login,
44     logout as auth_logout
45 )
46 from django.conf import settings
47 from django.contrib.auth.models import AnonymousUser
48
49 from urllib import quote
50 from urlparse import urljoin
51 from smtplib import SMTPException
52 from datetime import datetime
53 from functools import wraps
54
55 from astakos.im.settings import (
56     DEFAULT_CONTACT_EMAIL, SITENAME, BASEURL, LOGGING_LEVEL,
57     VERIFICATION_EMAIL_SUBJECT, ACCOUNT_CREATION_SUBJECT,
58     GROUP_CREATION_SUBJECT, HELPDESK_NOTIFICATION_EMAIL_SUBJECT,
59     INVITATION_EMAIL_SUBJECT, GREETING_EMAIL_SUBJECT, FEEDBACK_EMAIL_SUBJECT,
60     EMAIL_CHANGE_EMAIL_SUBJECT
61 )
62 import astakos.im.messages as astakos_messages
63
64 logger = logging.getLogger(__name__)
65
66
67 def logged(func, msg):
68     @wraps(func)
69     def with_logging(*args, **kwargs):
70         email = ''
71         user = None
72         try:
73             request = args[0]
74             email = request.user.email
75         except (KeyError, AttributeError), e:
76             email = ''
77         r = func(*args, **kwargs)
78         if LOGGING_LEVEL:
79             logger.log(LOGGING_LEVEL, msg % email)
80         return r
81     return with_logging
82
83
84 def login(request, user):
85     auth_login(request, user)
86     from astakos.im.models import SessionCatalog
87     SessionCatalog(
88         session_key=request.session.session_key,
89         user=user
90     ).save()
91
92 login = logged(login, '%s logged in.')
93 logout = logged(auth_logout, '%s logged out.')
94
95
96 def send_verification(user, template_name='im/activation_email.txt'):
97     """
98     Send email to user to verify his/her email and activate his/her account.
99
100     Raises SendVerificationError
101     """
102     url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('activate')),
103                                   quote(user.auth_token),
104                                   quote(urljoin(BASEURL, reverse('index'))))
105     message = render_to_string(template_name, {
106                                'user': user,
107                                'url': url,
108                                'baseurl': BASEURL,
109                                'site_name': SITENAME,
110                                'support': DEFAULT_CONTACT_EMAIL})
111     sender = settings.SERVER_EMAIL
112     try:
113         send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email])
114     except (SMTPException, socket.error) as e:
115         logger.exception(e)
116         raise SendVerificationError()
117     else:
118         msg = 'Sent activation %s' % user.email
119         logger.log(LOGGING_LEVEL, msg)
120
121
122 def send_activation(user, template_name='im/activation_email.txt'):
123     send_verification(user, template_name)
124     user.activation_sent = datetime.now()
125     user.save()
126
127
128 def _send_admin_notification(template_name,
129                              dictionary=None,
130                              subject='alpha2 testing notification',):
131     """
132     Send notification email to settings.ADMINS.
133
134     Raises SendNotificationError
135     """
136     if not settings.ADMINS:
137         return
138     dictionary = dictionary or {}
139     message = render_to_string(template_name, dictionary)
140     sender = settings.SERVER_EMAIL
141     try:
142         send_mail(subject,
143                   message, sender, [i[1] for i in settings.ADMINS])
144     except (SMTPException, socket.error) as e:
145         logger.exception(e)
146         raise SendNotificationError()
147     else:
148         msg = 'Sent admin notification for user %s' % dictionary
149         logger.log(LOGGING_LEVEL, msg)
150
151
152 def send_account_creation_notification(template_name, dictionary=None):
153     user = dictionary.get('user', AnonymousUser())
154     subject = _(ACCOUNT_CREATION_SUBJECT) % {'user':user.get('email', '')}
155     return _send_admin_notification(template_name, dictionary, subject=subject)
156
157
158 def send_group_creation_notification(template_name, dictionary=None):
159     group = dictionary.get('group')
160     if not group:
161         return
162     subject = _(GROUP_CREATION_SUBJECT) % {'group':group.get('name', '')}
163     return _send_admin_notification(template_name, dictionary, subject=subject)
164
165
166 def send_helpdesk_notification(user, template_name='im/helpdesk_notification.txt'):
167     """
168     Send email to DEFAULT_CONTACT_EMAIL to notify for a new user activation.
169
170     Raises SendNotificationError
171     """
172     if not DEFAULT_CONTACT_EMAIL:
173         return
174     message = render_to_string(
175         template_name,
176         {'user': user}
177     )
178     sender = settings.SERVER_EMAIL
179     try:
180         send_mail(
181             _(HELPDESK_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email},
182             message, sender, [DEFAULT_CONTACT_EMAIL])
183     except (SMTPException, socket.error) as e:
184         logger.exception(e)
185         raise SendNotificationError()
186     else:
187         msg = 'Sent helpdesk admin notification for %s' % user.email
188         logger.log(LOGGING_LEVEL, msg)
189
190
191 def send_invitation(invitation, template_name='im/invitation.txt'):
192     """
193     Send invitation email.
194
195     Raises SendInvitationError
196     """
197     subject = _(INVITATION_EMAIL_SUBJECT)
198     url = '%s?code=%d' % (urljoin(BASEURL, reverse('index')), invitation.code)
199     message = render_to_string(template_name, {
200                                'invitation': invitation,
201                                'url': url,
202                                'baseurl': BASEURL,
203                                'site_name': SITENAME,
204                                'support': DEFAULT_CONTACT_EMAIL})
205     sender = settings.SERVER_EMAIL
206     try:
207         send_mail(subject, message, sender, [invitation.username])
208     except (SMTPException, socket.error) as e:
209         logger.exception(e)
210         raise SendInvitationError()
211     else:
212         msg = 'Sent invitation %s' % invitation
213         logger.log(LOGGING_LEVEL, msg)
214         invitation.inviter.invitations = max(0, invitation.inviter.invitations - 1)
215         invitation.inviter.save()
216
217
218 def send_greeting(user, email_template_name='im/welcome_email.txt'):
219     """
220     Send welcome email.
221
222     Raises SMTPException, socket.error
223     """
224     subject = _(GREETING_EMAIL_SUBJECT)
225     message = render_to_string(email_template_name, {
226                                'user': user,
227                                'url': urljoin(BASEURL, reverse('index')),
228                                'baseurl': BASEURL,
229                                'site_name': SITENAME,
230                                'support': DEFAULT_CONTACT_EMAIL})
231     sender = settings.SERVER_EMAIL
232     try:
233         send_mail(subject, message, sender, [user.email])
234     except (SMTPException, socket.error) as e:
235         logger.exception(e)
236         raise SendGreetingError()
237     else:
238         msg = 'Sent greeting %s' % user.email
239         logger.log(LOGGING_LEVEL, msg)
240
241
242 def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
243     subject = _(FEEDBACK_EMAIL_SUBJECT)
244     from_email = user.email
245     recipient_list = [DEFAULT_CONTACT_EMAIL]
246     content = render_to_string(email_template_name, {
247         'message': msg,
248         'data': data,
249         'user': user})
250     try:
251         send_mail(subject, content, from_email, recipient_list)
252     except (SMTPException, socket.error) as e:
253         logger.exception(e)
254         raise SendFeedbackError()
255     else:
256         msg = 'Sent feedback from %s' % user.email
257         logger.log(LOGGING_LEVEL, msg)
258
259
260 def send_change_email(ec, request, email_template_name='registration/email_change_email.txt'):
261     try:
262         url = reverse('email_change_confirm',
263                       kwargs={'activation_key': ec.activation_key})
264         url = request.build_absolute_uri(url)
265         t = loader.get_template(email_template_name)
266         c = {'url': url, 'site_name': SITENAME}
267         from_email = settings.SERVER_EMAIL
268         send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT),
269                   t.render(Context(c)), from_email, [ec.new_email_address])
270     except (SMTPException, socket.error) as e:
271         logger.exception(e)
272         raise ChangeEmailError()
273     else:
274         msg = 'Sent change email for %s' % ec.user.email
275         logger.log(LOGGING_LEVEL, msg)
276
277
278 def activate(
279     user,
280     email_template_name='im/welcome_email.txt',
281     helpdesk_email_template_name='im/helpdesk_notification.txt',
282     verify_email=False
283 ):
284     """
285     Activates the specific user and sends email.
286
287     Raises SendGreetingError, ValidationError
288     """
289     user.is_active = True
290     if verify_email:
291         user.email_verified = True
292     user.save()
293     send_helpdesk_notification(user, helpdesk_email_template_name)
294     send_greeting(user, email_template_name)
295
296
297 def switch_account_to_shibboleth(user, local_user,
298                                  greeting_template_name='im/welcome_email.txt'):
299     try:
300         provider = user.provider
301     except AttributeError:
302         return
303     else:
304         if not provider == 'shibboleth':
305             return
306         user.delete()
307         local_user.provider = 'shibboleth'
308         local_user.third_party_identifier = user.third_party_identifier
309         local_user.save()
310         send_greeting(local_user, greeting_template_name)
311         return local_user
312
313
314 class SendMailError(Exception):
315     pass
316
317
318 class SendAdminNotificationError(SendMailError):
319     def __init__(self):
320         self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
321         super(SendAdminNotificationError, self).__init__()
322
323
324 class SendVerificationError(SendMailError):
325     def __init__(self):
326         self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
327         super(SendVerificationError, self).__init__()
328
329
330 class SendInvitationError(SendMailError):
331     def __init__(self):
332         self.message = _(astakos_messages.INVITATION_SEND_ERR)
333         super(SendInvitationError, self).__init__()
334
335
336 class SendGreetingError(SendMailError):
337     def __init__(self):
338         self.message = _(astakos_messages.GREETING_SEND_ERR)
339         super(SendGreetingError, self).__init__()
340
341
342 class SendFeedbackError(SendMailError):
343     def __init__(self):
344         self.message = _(astakos_messages.FEEDBACK_SEND_ERR)
345         super(SendFeedbackError, self).__init__()
346
347
348 class ChangeEmailError(SendMailError):
349     def __init__(self):
350         self.message = self.message = _(astakos_messages.CHANGE_EMAIL_SEND_ERR)
351         super(ChangeEmailError, self).__init__()
352
353
354 class SendNotificationError(SendMailError):
355     def __init__(self):
356         self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
357         super(SendNotificationError, self).__init__()