Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 73fbaec4

History | View | Annotate | Download (20.6 kB)

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
from django.conf import settings
46
from django.contrib.auth.models import AnonymousUser
47
from django.core.exceptions import PermissionDenied
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
    PROJECT_CREATION_SUBJECT, PROJECT_APPROVED_SUBJECT,
62
    PROJECT_TERMINATION_SUBJECT, PROJECT_SUSPENSION_SUBJECT,
63
    PROJECT_MEMBERSHIP_CHANGE_SUBJECT)
64
from astakos.im.notifications import build_notification, NotificationError
65
from astakos.im.models import (
66
    ProjectMembership, ProjectApplication)
67

    
68
import astakos.im.messages as astakos_messages
69

    
70
logger = logging.getLogger(__name__)
71

    
72

    
73
def logged(func, msg):
74
    @wraps(func)
75
    def with_logging(*args, **kwargs):
76
        email = ''
77
        user = None
78
        try:
79
            request = args[0]
80
            email = request.user.email
81
        except (KeyError, AttributeError), e:
82
            email = ''
83
        r = func(*args, **kwargs)
84
        if LOGGING_LEVEL:
85
            logger.log(LOGGING_LEVEL, msg % email)
86
        return r
87
    return with_logging
88

    
89

    
90
def login(request, user):
91
    auth_login(request, user)
92
    from astakos.im.models import SessionCatalog
93
    SessionCatalog(
94
        session_key=request.session.session_key,
95
        user=user
96
    ).save()
97

    
98
login = logged(login, '%s logged in.')
99
logout = logged(auth_logout, '%s logged out.')
100

    
101

    
102
def send_verification(user, template_name='im/activation_email.txt'):
103
    """
104
    Send email to user to verify his/her email and activate his/her account.
105

106
    Raises SendVerificationError
107
    """
108
    url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('activate')),
109
                                  quote(user.auth_token),
110
                                  quote(urljoin(BASEURL, reverse('index'))))
111
    message = render_to_string(template_name, {
112
                               'user': user,
113
                               'url': url,
114
                               'baseurl': BASEURL,
115
                               'site_name': SITENAME,
116
                               'support': DEFAULT_CONTACT_EMAIL})
117
    sender = settings.SERVER_EMAIL
118
    try:
119
        send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email])
120
    except (SMTPException, socket.error) as e:
121
        logger.exception(e)
122
        raise SendVerificationError()
123
    else:
124
        msg = 'Sent activation %s' % user.email
125
        logger.log(LOGGING_LEVEL, msg)
126

    
127

    
128
def send_activation(user, template_name='im/activation_email.txt'):
129
    send_verification(user, template_name)
130
    user.activation_sent = datetime.now()
131
    user.save()
132

    
133

    
134
def _send_admin_notification(template_name,
135
                             dictionary=None,
136
                             subject='alpha2 testing notification',):
137
    """
138
    Send notification email to settings.ADMINS.
139

140
    Raises SendNotificationError
141
    """
142
    if not settings.ADMINS:
143
        return
144
    dictionary = dictionary or {}
145
    message = render_to_string(template_name, dictionary)
146
    sender = settings.SERVER_EMAIL
147
    try:
148
        send_mail(subject,
149
                  message, sender, [i[1] for i in settings.ADMINS])
150
    except (SMTPException, socket.error) as e:
151
        logger.exception(e)
152
        raise SendNotificationError()
153
    else:
154
        msg = 'Sent admin notification for user %s' % dictionary
155
        logger.log(LOGGING_LEVEL, msg)
156

    
157

    
158
def send_account_creation_notification(template_name, dictionary=None):
159
    user = dictionary.get('user', AnonymousUser())
160
    subject = _(ACCOUNT_CREATION_SUBJECT) % {'user':user.get('email', '')}
161
    return _send_admin_notification(template_name, dictionary, subject=subject)
162

    
163

    
164
def send_group_creation_notification(template_name, dictionary=None):
165
    group = dictionary.get('group')
166
    if not group:
167
        return
168
    subject = _(GROUP_CREATION_SUBJECT) % {'group':group.get('name', '')}
169
    return _send_admin_notification(template_name, dictionary, subject=subject)
170

    
171

    
172
def send_helpdesk_notification(user, template_name='im/helpdesk_notification.txt'):
173
    """
174
    Send email to DEFAULT_CONTACT_EMAIL to notify for a new user activation.
175

176
    Raises SendNotificationError
177
    """
178
    if not DEFAULT_CONTACT_EMAIL:
179
        return
180
    message = render_to_string(
181
        template_name,
182
        {'user': user}
183
    )
184
    sender = settings.SERVER_EMAIL
185
    try:
186
        send_mail(
187
            _(HELPDESK_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email},
188
            message, sender, [DEFAULT_CONTACT_EMAIL])
189
    except (SMTPException, socket.error) as e:
190
        logger.exception(e)
191
        raise SendNotificationError()
192
    else:
193
        msg = 'Sent helpdesk admin notification for %s' % user.email
194
        logger.log(LOGGING_LEVEL, msg)
195

    
196

    
197
def send_invitation(invitation, template_name='im/invitation.txt'):
198
    """
199
    Send invitation email.
200

201
    Raises SendInvitationError
202
    """
203
    subject = _(INVITATION_EMAIL_SUBJECT)
204
    url = '%s?code=%d' % (urljoin(BASEURL, reverse('index')), invitation.code)
205
    message = render_to_string(template_name, {
206
                               'invitation': invitation,
207
                               'url': url,
208
                               'baseurl': BASEURL,
209
                               'site_name': SITENAME,
210
                               'support': DEFAULT_CONTACT_EMAIL})
211
    sender = settings.SERVER_EMAIL
212
    try:
213
        send_mail(subject, message, sender, [invitation.username])
214
    except (SMTPException, socket.error) as e:
215
        logger.exception(e)
216
        raise SendInvitationError()
217
    else:
218
        msg = 'Sent invitation %s' % invitation
219
        logger.log(LOGGING_LEVEL, msg)
220
        invitation.inviter.invitations = max(0, invitation.inviter.invitations - 1)
221
        invitation.inviter.save()
222

    
223

    
224
def send_greeting(user, email_template_name='im/welcome_email.txt'):
225
    """
226
    Send welcome email.
227

228
    Raises SMTPException, socket.error
229
    """
230
    subject = _(GREETING_EMAIL_SUBJECT)
231
    message = render_to_string(email_template_name, {
232
                               'user': user,
233
                               'url': urljoin(BASEURL, reverse('index')),
234
                               'baseurl': BASEURL,
235
                               'site_name': SITENAME,
236
                               'support': DEFAULT_CONTACT_EMAIL})
237
    sender = settings.SERVER_EMAIL
238
    try:
239
        send_mail(subject, message, sender, [user.email])
240
    except (SMTPException, socket.error) as e:
241
        logger.exception(e)
242
        raise SendGreetingError()
243
    else:
244
        msg = 'Sent greeting %s' % user.email
245
        logger.log(LOGGING_LEVEL, msg)
246

    
247

    
248
def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
249
    subject = _(FEEDBACK_EMAIL_SUBJECT)
250
    from_email = user.email
251
    recipient_list = [DEFAULT_CONTACT_EMAIL]
252
    content = render_to_string(email_template_name, {
253
        'message': msg,
254
        'data': data,
255
        'user': user})
256
    try:
257
        send_mail(subject, content, from_email, recipient_list)
258
    except (SMTPException, socket.error) as e:
259
        logger.exception(e)
260
        raise SendFeedbackError()
261
    else:
262
        msg = 'Sent feedback from %s' % user.email
263
        logger.log(LOGGING_LEVEL, msg)
264

    
265

    
266
def send_change_email(
267
    ec, request, email_template_name='registration/email_change_email.txt'):
268
    try:
269
        url = reverse('email_change_confirm',
270
                      kwargs={'activation_key': ec.activation_key})
271
        url = request.build_absolute_uri(url)
272
        t = loader.get_template(email_template_name)
273
        c = {'url': url, 'site_name': SITENAME}
274
        from_email = settings.SERVER_EMAIL
275
        send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT),
276
                  t.render(Context(c)), from_email, [ec.new_email_address])
277
    except (SMTPException, socket.error) as e:
278
        logger.exception(e)
279
        raise ChangeEmailError()
280
    else:
281
        msg = 'Sent change email for %s' % ec.user.email
282
        logger.log(LOGGING_LEVEL, msg)
283

    
284

    
285
def activate(
286
    user,
287
    email_template_name='im/welcome_email.txt',
288
    helpdesk_email_template_name='im/helpdesk_notification.txt',
289
    verify_email=False):
290
    """
291
    Activates the specific user and sends email.
292

293
    Raises SendGreetingError, ValidationError
294
    """
295
    user.is_active = True
296
    if verify_email:
297
        user.email_verified = True
298
    user.save()
299
    send_helpdesk_notification(user, helpdesk_email_template_name)
300
    send_greeting(user, email_template_name)
301

    
302
def invite(inviter, email, realname):
303
    inv = Invitation(inviter=inviter, username=email, realname=realname)
304
    inv.save()
305
    send_invitation(inv)
306
    inviter.invitations = max(0, self.invitations - 1)
307
    inviter.save()
308

    
309
def switch_account_to_shibboleth(user, local_user,
310
                                 greeting_template_name='im/welcome_email.txt'):
311
    try:
312
        provider = user.provider
313
    except AttributeError:
314
        return
315
    else:
316
        if not provider == 'shibboleth':
317
            return
318
        user.delete()
319
        local_user.provider = 'shibboleth'
320
        local_user.third_party_identifier = user.third_party_identifier
321
        local_user.save()
322
        send_greeting(local_user, greeting_template_name)
323
        return local_user
324

    
325

    
326
class SendMailError(Exception):
327
    pass
328

    
329

    
330
class SendAdminNotificationError(SendMailError):
331
    def __init__(self):
332
        self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
333
        super(SendAdminNotificationError, self).__init__()
334

    
335

    
336
class SendVerificationError(SendMailError):
337
    def __init__(self):
338
        self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
339
        super(SendVerificationError, self).__init__()
340

    
341

    
342
class SendInvitationError(SendMailError):
343
    def __init__(self):
344
        self.message = _(astakos_messages.INVITATION_SEND_ERR)
345
        super(SendInvitationError, self).__init__()
346

    
347

    
348
class SendGreetingError(SendMailError):
349
    def __init__(self):
350
        self.message = _(astakos_messages.GREETING_SEND_ERR)
351
        super(SendGreetingError, self).__init__()
352

    
353

    
354
class SendFeedbackError(SendMailError):
355
    def __init__(self):
356
        self.message = _(astakos_messages.FEEDBACK_SEND_ERR)
357
        super(SendFeedbackError, self).__init__()
358

    
359

    
360
class ChangeEmailError(SendMailError):
361
    def __init__(self):
362
        self.message = _(astakos_messages.CHANGE_EMAIL_SEND_ERR)
363
        super(ChangeEmailError, self).__init__()
364

    
365

    
366
class SendNotificationError(SendMailError):
367
    def __init__(self):
368
        self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
369
        super(SendNotificationError, self).__init__()
370

    
371

    
372
### PROJECT VIEWS ###
373
def get_join_policy(str_policy):
374
    try:
375
        return MemberJoinPolicy.objects.get(policy=str_policy)
376
    except:
377
        return None
378

    
379
def get_leave_policy(str_policy):
380
    try:
381
        return MemberLeavePolicy.objects.get(policy=str_policy)
382
    except:
383
        return None
384
    
385
_auto_accept_join = False
386
def get_auto_accept_join_policy():
387
    global _auto_accept_join
388
    if _auto_accept_join is not False:
389
        return _auto_accept_join
390
    _auto_accept = get_join_policy('auto_accept')
391
    return _auto_accept
392

    
393
_closed_join = False
394
def get_closed_join_policy():
395
    global _closed_join
396
    if _closed_join is not False:
397
        return _closed_join
398
    _closed_join = get_join_policy('closed')
399
    return _closed_join
400

    
401
_auto_accept_leave = False
402
def get_auto_accept_leave_policy():
403
    global _auto_accept_leave
404
    if _auto_accept_leave is not False:
405
        return _auto_accept_leave
406
    _auto_accept_leave = get_leave_policy('auto_accept')
407
    return _auto_accept_leave
408

    
409
_closed_leave = False
410
def get_closed_leave_policy():
411
    global _closed_leave
412
    if _closed_leave is not False:
413
        return _closed_leave
414
    _closed_leave = get_leave_policy('closed')
415
    return _closed_leave
416

    
417
def get_project_by_application_id(project_application_id):
418
    try:
419
        return Project.objects.get(application__id=project_application_id)
420
    except Project.DoesNotExist:
421
        raise IOError(
422
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
423

    
424
def get_user_by_id(user_id):
425
    try:
426
        return AstakosUser.objects.get(user__id=user_id)
427
    except AstakosUser.DoesNotExist:
428
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
429

    
430
def create_membership(project_application_id, user_id):
431
    try:
432
        project = get_project_by_application_id(project_application_id)
433
        m = ProjectMembership(
434
            project=project,
435
            person__id=user_id,
436
            request_date=datetime.now())
437
    except IntegrityError, e:
438
        raise IOError(_(astakos_messages.MEMBERSHIP_REQUEST_EXISTS))
439
    else:
440
        m.save()
441

    
442
def get_membership(project, user):
443
    if isinstace(project, int):
444
        project = get_project_by_application_id(project)
445
    if isinstace(user, int):
446
        user = get_user_by_id(user)
447
    try:
448
        return ProjectMembership.objects.select_related().get(
449
            project=project,
450
            person=user)
451
    except ProjectMembership.DoesNotExist:
452
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
453

    
454
def accept_membership(request, project, user, request_user=None):
455
    """
456
        Raises:
457
            django.core.exceptions.PermissionDenied
458
            IOError
459
    """
460
    membership = get_membership(project, user)
461
    if request_user and \
462
        (not membership.project.current_application.owner == request_user and \
463
            not request_user.is_superuser):
464
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
465
    if not self.project.is_alive:
466
        raise PermissionDenied(
467
            _(astakos_messages.NOT_ALIVE_PROJECT) % membership.project.__dict__)
468
    if len(self.project.approved_members) + 1 > \
469
        self.project.definition.limit_on_members_number:
470
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
471

    
472
    membership.accept()
473

    
474
    try:
475
        notification = build_notification(
476
            settings.SERVER_EMAIL,
477
            [self.person.email],
478
            _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % membership.project.definition.__dict__,
479
            template='im/projects/project_membership_change_notification.txt',
480
            dictionary={'object':membership.project.current_application, 'action':'accepted'})
481
        notification.send()
482
    except NotificationError, e:
483
        logger.error(e.messages)
484
    return membership
485

    
486
def reject_membership(project, user, request_user=None):
487
    """
488
        Raises:
489
            django.core.exceptions.PermissionDenied
490
            IOError
491
    """
492
    membership = get_membership(project, user)
493
    if request_user and \
494
        (not membership.project.current_application.owner == request_user and \
495
            not request_user.is_superuser):
496
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
497
    if not membership.project.is_alive:
498
        raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
499

    
500
    membership.reject()
501

    
502
    try:
503
        notification = build_notification(
504
            settings.SERVER_EMAIL,
505
            [self.person.email],
506
            _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % self.project.definition.__dict__,
507
            template='im/projects/project_membership_change_notification.txt',
508
            dictionary={'object':self.project.current_application, 'action':'rejected'})
509
        notification.send()
510
    except NotificationError, e:
511
        logger.error(e.messages)
512
    return membership
513

    
514
def remove_membership(project, user, request_user=None):
515
    """
516
        Raises:
517
            django.core.exceptions.PermissionDenied
518
            IOError
519
    """
520
    membership = get_membership(project, user)
521
    if request_user and \
522
        (not membership.project.current_application.owner == request_user and \
523
            not request_user.is_superuser):
524
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
525
    if not self.project.is_alive:
526
        raise PermissionDenied(_(astakos_messages.NOT_ALIVE_PROJECT) % membership.project.__dict__)
527

    
528
    membership.remove()
529

    
530
    try:
531
        notification = build_notification(
532
            settings.SERVER_EMAIL,
533
            [self.person.email],
534
            _(PROJECT_MEMBERSHIP_CHANGE_SUBJECT) % membership.project.definition.__dict__,
535
            template='im/projects/project_membership_change_notification.txt',
536
            dictionary={'object':membership.project.current_application, 'action':'removed'})
537
        notification.send()
538
    except NotificationError, e:
539
        logger.error(e.messages)
540
    return membership
541

    
542
def leave_project(project_application_id, user_id):
543
    """
544
        Raises:
545
            django.core.exceptions.PermissionDenied
546
            IOError
547
    """
548
    project = get_project_by_application_id(project_application_id)
549
    leave_policy = project.current_application.definition.member_join_policy
550
    if leave_policy == get_closed_leave():
551
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
552

    
553
    membership = get_membership(project_application_id, user_id)
554
    if leave_policy == get_auto_accept_leave():
555
        membership.remove()
556
    else:
557
        membership.leave_request_date = datetime.now()
558
        membership.save()
559
    return membership
560

    
561
def join_project(project_application_id, user_id):
562
    """
563
        Raises:
564
            django.core.exceptions.PermissionDenied
565
            IOError
566
    """
567
    project = get_project_by_application_id(project_application_id)
568
    join_policy = project.current_application.definition.member_join_policy
569
    if join_policy == get_closed_join():
570
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
571

    
572
    membership = create_membership(project_application_id, user_id)
573

    
574
    if join_policy == get_auto_accept_join():
575
        membership.accept()
576
    return membership
577
    
578
def submit_application(
579
    application, resource_policies, applicant, comments, precursor_application=None):
580

    
581
    application.submit(
582
        resource_policies, applicant, comments, precursor_application)
583
    
584
    try:
585
        notification = build_notification(
586
            settings.SERVER_EMAIL,
587
            [i[1] for i in settings.ADMINS],
588
            _(PROJECT_CREATION_SUBJECT) % application.__dict__,
589
            template='im/projects/project_creation_notification.txt',
590
            dictionary={'object':application})
591
        notification.send()
592
    except NotificationError, e:
593
        logger.error(e.messages)
594
    return application
595

    
596
def approve_application(application):
597
    application.approve()
598
#     rejected = application.project.sync()
599

    
600
    try:
601
        notification = build_notification(
602
            settings.SERVER_EMAIL,
603
            [self.owner.email],
604
            _(PROJECT_APPROVED_SUBJECT) % application.definition.__dict__,
605
            template='im/projects/project_approval_notification.txt',
606
            dictionary={'object':application})
607
        notification.send()
608
    except NotificationError, e:
609
        logger.error(e.messages)