Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 3d4a0a9b

History | View | Annotate | Download (20.7 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.get('email',
155
                                                                     None)
156
        logger.log(LOGGING_LEVEL, msg)
157

    
158

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

    
164

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

    
172

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

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

    
197

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

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

    
224

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

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

    
248

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

    
266

    
267
def send_change_email(
268
    ec, request, email_template_name='registration/email_change_email.txt'):
269
    try:
270
        url = ec.get_url()
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
    user.email_verified = True
297
    if not user.activation_sent:
298
        user.activation_sent = datetime.now()
299
    user.save()
300
    send_helpdesk_notification(user, helpdesk_email_template_name)
301
    send_greeting(user, email_template_name)
302

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

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

    
326

    
327
class SendMailError(Exception):
328
    pass
329

    
330

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

    
336

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

    
342

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

    
348

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

    
354

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

    
360

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

    
366

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

    
372

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

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

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

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

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

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

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

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

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

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

    
473
    membership.accept()
474

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

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

    
501
    membership.reject()
502

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

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

    
529
    membership.remove()
530

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

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

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

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

    
573
    membership = create_membership(project_application_id, user_id)
574

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

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

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

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