Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ e05c541e

History | View | Annotate | Download (30 kB)

1
# Copyright 2011, 2012, 2013 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, get_connection
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
from django.db import IntegrityError
49
from django.http import Http404
50

    
51
from urllib import quote
52
from urlparse import urljoin
53
from smtplib import SMTPException
54
from datetime import datetime
55
from functools import wraps
56

    
57
import astakos.im.settings as astakos_settings
58
from astakos.im.settings import (
59
    CONTACT_EMAIL, SITENAME, BASEURL, LOGGING_LEVEL,
60
    VERIFICATION_EMAIL_SUBJECT, ACCOUNT_CREATION_SUBJECT,
61
    GROUP_CREATION_SUBJECT, HELPDESK_NOTIFICATION_EMAIL_SUBJECT,
62
    INVITATION_EMAIL_SUBJECT, GREETING_EMAIL_SUBJECT, FEEDBACK_EMAIL_SUBJECT,
63
    EMAIL_CHANGE_EMAIL_SUBJECT,
64
    PROJECT_CREATION_SUBJECT, PROJECT_APPROVED_SUBJECT,
65
    PROJECT_TERMINATION_SUBJECT, PROJECT_SUSPENSION_SUBJECT,
66
    PROJECT_MEMBERSHIP_CHANGE_SUBJECT,
67
    PROJECT_MEMBER_JOIN_POLICIES, PROJECT_MEMBER_LEAVE_POLICIES, HELPDESK,
68
    ADMINS, MANAGERS)
69
from astakos.im.notifications import build_notification, NotificationError
70
from astakos.im.models import (
71
    AstakosUser, Invitation, ProjectMembership, ProjectApplication, Project,
72
    UserSetting,
73
    get_resource_names, new_chain)
74
from astakos.im.quotas import qh_sync_user, qh_sync_users
75
from astakos.im.project_notif import (
76
    membership_change_notify, membership_enroll_notify,
77
    membership_request_notify, membership_leave_request_notify,
78
    application_submit_notify, application_approve_notify,
79
    application_deny_notify,
80
    project_termination_notify, project_suspension_notify)
81

    
82
import astakos.im.messages as astakos_messages
83

    
84
logger = logging.getLogger(__name__)
85

    
86

    
87
def login(request, user):
88
    auth_login(request, user)
89
    from astakos.im.models import SessionCatalog
90
    SessionCatalog(
91
        session_key=request.session.session_key,
92
        user=user
93
    ).save()
94
    logger.info('%s logged in.', user.log_display)
95

    
96

    
97
def logout(request, *args, **kwargs):
98
    user = request.user
99
    auth_logout(request, *args, **kwargs)
100
    logger.info('%s logged out.', user.log_display)
101

    
102

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

107
    Raises SendVerificationError
108
    """
109
    url = '%s?auth=%s&next=%s' % (urljoin(BASEURL, reverse('activate')),
110
                                  quote(user.auth_token),
111
                                  quote(urljoin(BASEURL, reverse('index'))))
112
    message = render_to_string(template_name, {
113
                               'user': user,
114
                               'url': url,
115
                               'baseurl': BASEURL,
116
                               'site_name': SITENAME,
117
                               'support': CONTACT_EMAIL})
118
    sender = settings.SERVER_EMAIL
119
    try:
120
        send_mail(_(VERIFICATION_EMAIL_SUBJECT), message, sender, [user.email],
121
                  connection=get_connection())
122

    
123
    except (SMTPException, socket.error) as e:
124
        logger.exception(e)
125
        raise SendVerificationError()
126
    else:
127
        msg = 'Sent activation %s' % user.email
128
        logger.log(LOGGING_LEVEL, msg)
129

    
130

    
131
def send_activation(user, template_name='im/activation_email.txt'):
132
    send_verification(user, template_name)
133
    user.activation_sent = datetime.now()
134
    user.save()
135

    
136

    
137
def _send_admin_notification(template_name,
138
                             dictionary=None,
139
                             subject='alpha2 testing notification',):
140
    """
141
    Send notification email to settings.HELPDESK + settings.MANAGERS.
142

143
    Raises SendNotificationError
144
    """
145
    dictionary = dictionary or {}
146
    message = render_to_string(template_name, dictionary)
147
    sender = settings.SERVER_EMAIL
148
    recipient_list = [e[1] for e in HELPDESK + MANAGERS]
149
    try:
150
        send_mail(subject, message, sender, recipient_list,
151
                  connection=get_connection())
152
    except (SMTPException, socket.error) as e:
153
        logger.exception(e)
154
        raise SendNotificationError()
155
    else:
156
        user = dictionary.get('user')
157
        msg = 'Sent admin notification for user %s' % user.log_display
158
        logger.log(LOGGING_LEVEL, msg)
159

    
160

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

    
166

    
167
def send_helpdesk_notification(user, template_name='im/helpdesk_notification.txt'):
168
    """
169
    Send email to settings.HELPDESK list to notify for a new user activation.
170

171
    Raises SendNotificationError
172
    """
173
    message = render_to_string(
174
        template_name,
175
        {'user': user}
176
    )
177
    sender = settings.SERVER_EMAIL
178
    recipient_list = [e[1] for e in HELPDESK + MANAGERS]
179
    try:
180
        send_mail(_(HELPDESK_NOTIFICATION_EMAIL_SUBJECT) % {'user': user.email},
181
                  message, sender, recipient_list, connection=get_connection())
182
    except (SMTPException, socket.error) as e:
183
        logger.exception(e)
184
        raise SendNotificationError()
185
    else:
186
        msg = 'Sent helpdesk admin notification for %s' % user.email
187
        logger.log(LOGGING_LEVEL, msg)
188

    
189

    
190
def send_invitation(invitation, template_name='im/invitation.txt'):
191
    """
192
    Send invitation email.
193

194
    Raises SendInvitationError
195
    """
196
    subject = _(INVITATION_EMAIL_SUBJECT)
197
    url = '%s?code=%d' % (urljoin(BASEURL, reverse('index')), invitation.code)
198
    message = render_to_string(template_name, {
199
                               'invitation': invitation,
200
                               'url': url,
201
                               'baseurl': BASEURL,
202
                               'site_name': SITENAME,
203
                               'support': CONTACT_EMAIL})
204
    sender = settings.SERVER_EMAIL
205
    try:
206
        send_mail(subject, message, sender, [invitation.username],
207
                  connection=get_connection())
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
        inviter_invitations = invitation.inviter.invitations
215
        invitation.inviter.invitations = max(0, inviter_invitations - 1)
216
        invitation.inviter.save()
217

    
218

    
219
def send_greeting(user, email_template_name='im/welcome_email.txt'):
220
    """
221
    Send welcome email.
222

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

    
243

    
244
def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
245
    subject = _(FEEDBACK_EMAIL_SUBJECT)
246
    from_email = settings.SERVER_EMAIL
247
    recipient_list = [e[1] for e in HELPDESK]
248
    content = render_to_string(email_template_name, {
249
        'message': msg,
250
        'data': data,
251
        'user': user})
252
    try:
253
        send_mail(subject, content, from_email, recipient_list,
254
                  connection=get_connection())
255
    except (SMTPException, socket.error) as e:
256
        logger.exception(e)
257
        raise SendFeedbackError()
258
    else:
259
        msg = 'Sent feedback from %s' % user.log_display
260
        logger.log(LOGGING_LEVEL, msg)
261

    
262

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

    
282

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

291
    Raises SendGreetingError, ValidationError
292
    """
293
    user.is_active = True
294
    user.email_verified = True
295
    if not user.activation_sent:
296
        user.activation_sent = datetime.now()
297
    user.save()
298
    qh_sync_user(user.id)
299
    send_helpdesk_notification(user, helpdesk_email_template_name)
300
    send_greeting(user, email_template_name)
301

    
302
def deactivate(user):
303
    user.is_active = False
304
    user.save()
305

    
306
def invite(inviter, email, realname):
307
    inv = Invitation(inviter=inviter, username=email, realname=realname)
308
    inv.save()
309
    send_invitation(inv)
310
    inviter.invitations = max(0, inviter.invitations - 1)
311
    inviter.save()
312

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

    
329

    
330
class SendMailError(Exception):
331
    pass
332

    
333

    
334
class SendAdminNotificationError(SendMailError):
335
    def __init__(self):
336
        self.message = _(astakos_messages.ADMIN_NOTIFICATION_SEND_ERR)
337
        super(SendAdminNotificationError, self).__init__()
338

    
339

    
340
class SendVerificationError(SendMailError):
341
    def __init__(self):
342
        self.message = _(astakos_messages.VERIFICATION_SEND_ERR)
343
        super(SendVerificationError, self).__init__()
344

    
345

    
346
class SendInvitationError(SendMailError):
347
    def __init__(self):
348
        self.message = _(astakos_messages.INVITATION_SEND_ERR)
349
        super(SendInvitationError, self).__init__()
350

    
351

    
352
class SendGreetingError(SendMailError):
353
    def __init__(self):
354
        self.message = _(astakos_messages.GREETING_SEND_ERR)
355
        super(SendGreetingError, self).__init__()
356

    
357

    
358
class SendFeedbackError(SendMailError):
359
    def __init__(self):
360
        self.message = _(astakos_messages.FEEDBACK_SEND_ERR)
361
        super(SendFeedbackError, self).__init__()
362

    
363

    
364
class ChangeEmailError(SendMailError):
365
    def __init__(self):
366
        self.message = _(astakos_messages.CHANGE_EMAIL_SEND_ERR)
367
        super(ChangeEmailError, self).__init__()
368

    
369

    
370
class SendNotificationError(SendMailError):
371
    def __init__(self):
372
        self.message = _(astakos_messages.NOTIFICATION_SEND_ERR)
373
        super(SendNotificationError, self).__init__()
374

    
375

    
376
def get_quota(users):
377
    pass
378

    
379

    
380
### PROJECT VIEWS ###
381

    
382
AUTO_ACCEPT_POLICY = 1
383
MODERATED_POLICY   = 2
384
CLOSED_POLICY      = 3
385

    
386
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
387

    
388
def get_project_by_application_id(project_application_id):
389
    try:
390
        return Project.objects.get(application__id=project_application_id)
391
    except Project.DoesNotExist:
392
        raise IOError(
393
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
394

    
395
def get_related_project_id(application_id):
396
    try:
397
        app = ProjectApplication.objects.get(id=application_id)
398
        chain = app.chain
399
        project = Project.objects.get(id=chain)
400
        return chain
401
    except:
402
        return None
403

    
404
def get_chain_of_application_id(application_id):
405
    try:
406
        app = ProjectApplication.objects.get(id=application_id)
407
        chain = app.chain
408
        return chain.chain
409
    except:
410
        return None
411

    
412
def get_project_by_id(project_id):
413
    try:
414
        return Project.objects.get(id=project_id)
415
    except Project.DoesNotExist:
416
        raise IOError(
417
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
418

    
419
def get_project_by_name(name):
420
    try:
421
        return Project.objects.get(name=name)
422
    except Project.DoesNotExist:
423
        raise IOError(
424
            _(astakos_messages.UNKNOWN_PROJECT_ID) % name)
425

    
426

    
427
def get_project_for_update(project_id):
428
    try:
429
        return Project.objects.get_for_update(id=project_id)
430
    except Project.DoesNotExist:
431
        raise IOError(
432
            _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id)
433

    
434
def get_application_for_update(application_id):
435
    try:
436
        return ProjectApplication.objects.get_for_update(id=application_id)
437
    except ProjectApplication.DoesNotExist:
438
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
439
        raise IOError(m)
440

    
441
def get_user_by_id(user_id):
442
    try:
443
        return AstakosUser.objects.get(id=user_id)
444
    except AstakosUser.DoesNotExist:
445
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
446

    
447
def get_user_by_uuid(uuid):
448
    try:
449
        return AstakosUser.objects.get(uuid=uuid)
450
    except AstakosUser.DoesNotExist:
451
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % uuid)
452

    
453
def create_membership(project, user):
454
    if isinstance(user, (int, long)):
455
        user = get_user_by_id(user)
456

    
457
    if not user.is_active:
458
        m = _(astakos_messages.ACCOUNT_NOT_ACTIVE)
459
        raise PermissionDenied(m)
460

    
461
    m, created = ProjectMembership.objects.get_or_create(
462
        project=project,
463
        person=user)
464

    
465
    if created:
466
        return m
467
    else:
468
        msg = _(astakos_messages.MEMBERSHIP_REQUEST_EXISTS)
469
        raise PermissionDenied(msg)
470

    
471

    
472
def get_membership_for_update(project, user):
473
    if isinstance(user, (int, long)):
474
        user = get_user_by_id(user)
475
    try:
476
        objs = ProjectMembership.objects
477
        m = objs.get_for_update(project=project, person=user)
478
        return m
479
    except ProjectMembership.DoesNotExist:
480
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
481

    
482
def checkAllowed(entity, request_user):
483
    if isinstance(entity, Project):
484
        application = entity.application
485
    elif isinstance(entity, ProjectApplication):
486
        application = entity
487
    else:
488
        m = "%s not a Project nor a ProjectApplication" % (entity,)
489
        raise ValueError(m)
490

    
491
    if request_user and \
492
        (not application.owner == request_user and \
493
            not request_user.is_superuser):
494
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
495

    
496
def checkAlive(project):
497
    if not project.is_alive:
498
        raise PermissionDenied(
499
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
500

    
501
def accept_membership_checks(project, request_user):
502
    checkAllowed(project, request_user)
503
    checkAlive(project)
504

    
505
    join_policy = project.application.member_join_policy
506
    if join_policy == CLOSED_POLICY:
507
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
508

    
509
    if project.violates_members_limit(adding=1):
510
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
511

    
512
def accept_membership(project_id, user, request_user=None):
513
    project = get_project_for_update(project_id)
514
    accept_membership_checks(project, request_user)
515

    
516
    membership = get_membership_for_update(project, user)
517
    if not membership.can_accept():
518
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
519
        raise PermissionDenied(m)
520

    
521
    membership.accept()
522
    qh_sync_user(user)
523
    logger.info("User %s has been accepted in %s." %
524
                (membership.person.log_display, project))
525

    
526
    membership_change_notify(project, membership.person, 'accepted')
527

    
528
    return membership
529

    
530
def reject_membership_checks(project, request_user):
531
    checkAllowed(project, request_user)
532
    checkAlive(project)
533

    
534
def reject_membership(project_id, user, request_user=None):
535
    project = get_project_for_update(project_id)
536
    reject_membership_checks(project, request_user)
537
    membership = get_membership_for_update(project, user)
538
    if not membership.can_reject():
539
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
540
        raise PermissionDenied(m)
541

    
542
    membership.reject()
543
    logger.info("Request of user %s for %s has been rejected." %
544
                (membership.person.log_display, project))
545

    
546
    membership_change_notify(project, membership.person, 'rejected')
547

    
548
    return membership
549

    
550
def cancel_membership_checks(project):
551
    checkAlive(project)
552

    
553
def cancel_membership(project_id, user_id):
554
    project = get_project_for_update(project_id)
555
    cancel_membership_checks(project)
556
    membership = get_membership_for_update(project, user_id)
557
    if not membership.can_cancel():
558
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
559
        raise PermissionDenied(m)
560

    
561
    membership.cancel()
562
    logger.info("Request of user %s for %s has been cancelled." %
563
                (membership.person.log_display, project))
564

    
565
def remove_membership_checks(project, request_user=None):
566
    checkAllowed(project, request_user)
567
    checkAlive(project)
568

    
569
    leave_policy = project.application.member_leave_policy
570
    if leave_policy == CLOSED_POLICY:
571
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
572

    
573
def remove_membership(project_id, user, request_user=None):
574
    project = get_project_for_update(project_id)
575
    remove_membership_checks(project, request_user)
576
    membership = get_membership_for_update(project, user)
577
    if not membership.can_remove():
578
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
579
        raise PermissionDenied(m)
580

    
581
    membership.remove()
582
    qh_sync_user(user)
583
    logger.info("User %s has been removed from %s." %
584
                (membership.person.log_display, project))
585

    
586
    membership_change_notify(project, membership.person, 'removed')
587

    
588
    return membership
589

    
590
def enroll_member(project_id, user, request_user=None):
591
    project = get_project_for_update(project_id)
592
    accept_membership_checks(project, request_user)
593

    
594
    membership, created = ProjectMembership.objects.get_or_create(
595
        project=project,
596
        person=user)
597

    
598
    if not membership.can_accept():
599
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
600
        raise PermissionDenied(m)
601

    
602
    membership.accept()
603
    qh_sync_user(user.id)
604
    logger.info("User %s has been enrolled in %s." %
605
                (membership.person.log_display, project))
606

    
607
    membership_enroll_notify(project, membership.person)
608

    
609
    return membership
610

    
611
def leave_project_checks(project):
612
    checkAlive(project)
613

    
614
    leave_policy = project.application.member_leave_policy
615
    if leave_policy == CLOSED_POLICY:
616
        raise PermissionDenied(_(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED))
617

    
618
def can_leave_request(project, user):
619
    leave_policy = project.application.member_leave_policy
620
    if leave_policy == CLOSED_POLICY:
621
        return False
622
    m = user.get_membership(project)
623
    if m is None:
624
        return False
625
    if m.state != ProjectMembership.ACCEPTED:
626
        return False
627
    return True
628

    
629
def leave_project(project_id, user_id):
630
    project = get_project_for_update(project_id)
631
    leave_project_checks(project)
632
    membership = get_membership_for_update(project, user_id)
633
    if not membership.can_leave():
634
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
635
        raise PermissionDenied(m)
636

    
637
    auto_accepted = False
638
    leave_policy = project.application.member_leave_policy
639
    if leave_policy == AUTO_ACCEPT_POLICY:
640
        membership.remove()
641
        qh_sync_user(user_id)
642
        logger.info("User %s has left %s." %
643
                    (membership.person.log_display, project))
644
        auto_accepted = True
645
    else:
646
        membership.leave_request()
647
        logger.info("User %s requested to leave %s." %
648
                    (membership.person.log_display, project))
649
        membership_leave_request_notify(project, membership.person)
650
    return auto_accepted
651

    
652
def join_project_checks(project):
653
    checkAlive(project)
654

    
655
    join_policy = project.application.member_join_policy
656
    if join_policy == CLOSED_POLICY:
657
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
658

    
659
def can_join_request(project, user):
660
    join_policy = project.application.member_join_policy
661
    if join_policy == CLOSED_POLICY:
662
        return False
663
    m = user.get_membership(project)
664
    if m:
665
        return False
666
    return True
667

    
668
def join_project(project_id, user_id):
669
    project = get_project_for_update(project_id)
670
    join_project_checks(project)
671
    membership = create_membership(project, user_id)
672

    
673
    auto_accepted = False
674
    join_policy = project.application.member_join_policy
675
    if (join_policy == AUTO_ACCEPT_POLICY and
676
        not project.violates_members_limit(adding=1)):
677
        membership.accept()
678
        qh_sync_user(user_id)
679
        logger.info("User %s joined %s." %
680
                    (membership.person.log_display, project))
681
        auto_accepted = True
682
    else:
683
        membership_request_notify(project, membership.person)
684
        logger.info("User %s requested to join %s." %
685
                    (membership.person.log_display, project))
686

    
687
    return auto_accepted
688

    
689
def submit_application(kw, request_user=None):
690

    
691
    kw['applicant'] = request_user
692
    resource_policies = kw.pop('resource_policies', None)
693

    
694
    precursor = None
695
    precursor_id = kw.get('precursor_application', None)
696
    if precursor_id is not None:
697
        objs = ProjectApplication.objects
698
        precursor = objs.get_for_update(id=precursor_id)
699
        kw['precursor_application'] = precursor
700

    
701
        if (request_user and
702
            (not precursor.owner == request_user and
703
             not request_user.is_superuser
704
             and not request_user.is_project_admin())):
705
            m = _(astakos_messages.NOT_ALLOWED)
706
            raise PermissionDenied(m)
707

    
708
    owner = kw['owner']
709
    reached, limit = reached_pending_application_limit(owner.id, precursor)
710
    if not request_user.is_project_admin() and reached:
711
        m = _(astakos_messages.REACHED_PENDING_APPLICATION_LIMIT) % limit
712
        raise PermissionDenied(m)
713

    
714
    application = ProjectApplication(**kw)
715

    
716
    if precursor is None:
717
        application.chain = new_chain()
718
    else:
719
        chain = precursor.chain
720
        application.chain = chain
721
        objs = ProjectApplication.objects
722
        q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
723
        pending = q.select_for_update()
724
        for app in pending:
725
            app.state = ProjectApplication.REPLACED
726
            app.save()
727

    
728
    application.save()
729
    application.resource_policies = resource_policies
730
    logger.info("User %s submitted %s." %
731
                (request_user.log_display, application.log_display))
732
    application_submit_notify(application)
733
    return application
734

    
735
def cancel_application(application_id, request_user=None):
736
    application = get_application_for_update(application_id)
737
    checkAllowed(application, request_user)
738

    
739
    if not application.can_cancel():
740
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL % (
741
                application.id, application.state_display()))
742
        raise PermissionDenied(m)
743

    
744
    application.cancel()
745
    logger.info("%s has been cancelled." % (application.log_display))
746

    
747
def dismiss_application(application_id, request_user=None):
748
    application = get_application_for_update(application_id)
749
    checkAllowed(application, request_user)
750

    
751
    if not application.can_dismiss():
752
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS % (
753
                application.id, application.state_display()))
754
        raise PermissionDenied(m)
755

    
756
    application.dismiss()
757
    logger.info("%s has been dismissed." % (application.log_display))
758

    
759
def deny_application(application_id, reason=None):
760
    application = get_application_for_update(application_id)
761

    
762
    if not application.can_deny():
763
        m = _(astakos_messages.APPLICATION_CANNOT_DENY % (
764
                application.id, application.state_display()))
765
        raise PermissionDenied(m)
766

    
767
    if reason is None:
768
        reason = ""
769
    application.deny(reason)
770
    logger.info("%s has been denied with reason \"%s\"." %
771
                (application.log_display, reason))
772
    application_deny_notify(application)
773

    
774
def approve_application(app_id):
775

    
776
    try:
777
        objects = ProjectApplication.objects
778
        application = objects.get_for_update(id=app_id)
779
    except ProjectApplication.DoesNotExist:
780
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID % (app_id,))
781
        raise PermissionDenied(m)
782

    
783
    if not application.can_approve():
784
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE % (
785
                application.id, application.state_display()))
786
        raise PermissionDenied(m)
787

    
788
    project = application.approve()
789
    qh_sync_projects([project])
790
    logger.info("%s has been approved." % (application.log_display))
791
    application_approve_notify(application)
792

    
793
def check_expiration(execute=False):
794
    objects = Project.objects
795
    expired = objects.expired_projects()
796
    if execute:
797
        for project in expired:
798
            terminate(project.id)
799

    
800
    return [project.expiration_info() for project in expired]
801

    
802
def terminate(project_id):
803
    project = get_project_for_update(project_id)
804
    checkAlive(project)
805

    
806
    project.terminate()
807
    qh_sync_projects([project])
808
    logger.info("%s has been terminated." % (project))
809

    
810
    project_termination_notify(project)
811

    
812
def suspend(project_id):
813
    project = get_project_by_id(project_id)
814
    checkAlive(project)
815

    
816
    project.suspend()
817
    qh_sync_projects([project])
818
    logger.info("%s has been suspended." % (project))
819

    
820
    project_suspension_notify(project)
821

    
822
def resume(project_id):
823
    project = get_project_for_update(project_id)
824

    
825
    if not project.is_suspended:
826
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
827
        raise PermissionDenied(m)
828

    
829
    project.resume()
830
    qh_sync_projects([project])
831
    logger.info("%s has been unsuspended." % (project))
832

    
833
def get_by_chain_or_404(chain_id):
834
    try:
835
        project = Project.objects.get(id=chain_id)
836
        application = project.application
837
        return project, application
838
    except:
839
        application = ProjectApplication.objects.latest_of_chain(chain_id)
840
        if application is None:
841
            raise Http404
842
        else:
843
            return None, application
844

    
845

    
846
def get_user_setting(user_id, key):
847
    try:
848
        setting = UserSetting.objects.get(
849
            user=user_id, setting=key)
850
        return setting.value
851
    except UserSetting.DoesNotExist:
852
        return getattr(astakos_settings, key)
853

    
854

    
855
def set_user_setting(user_id, key, value):
856
    try:
857
        setting = UserSetting.objects.get_for_update(
858
            user=user_id, setting=key)
859
    except UserSetting.DoesNotExist:
860
        setting = UserSetting(user_id=user_id, setting=key)
861
    setting.value = value
862
    setting.save()
863

    
864

    
865
def unset_user_setting(user_id, key):
866
    UserSetting.objects.filter(user=user_id, setting=key).delete()
867

    
868

    
869
PENDING_APPLICATION_LIMIT_SETTING = 'PENDING_APPLICATION_LIMIT'
870

    
871
def get_pending_application_limit(user_id):
872
    key = PENDING_APPLICATION_LIMIT_SETTING
873
    return get_user_setting(user_id, key)
874

    
875

    
876
def set_pending_application_limit(user_id, value):
877
    key = PENDING_APPLICATION_LIMIT_SETTING
878
    return set_user_setting(user_id, key, value)
879

    
880

    
881
def unset_pending_application_limit(user_id):
882
    key = PENDING_APPLICATION_LIMIT_SETTING
883
    return unset_user_setting(user_id, key)
884

    
885

    
886
def _reached_pending_application_limit(user_id):
887
    limit = get_pending_application_limit(user_id)
888

    
889
    PENDING = ProjectApplication.PENDING
890
    pending = ProjectApplication.objects.filter(
891
        owner__id=user_id, state=PENDING).count()
892

    
893
    return pending >= limit, limit
894

    
895

    
896
def reached_pending_application_limit(user_id, precursor=None):
897
    reached, limit = _reached_pending_application_limit(user_id)
898

    
899
    if precursor is None:
900
        return reached, limit
901

    
902
    chain = precursor.chain
903
    objs = ProjectApplication.objects
904
    q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
905
    has_pending = q.exists()
906

    
907
    if not has_pending:
908
        return reached, limit
909

    
910
    return False, limit
911

    
912

    
913
def qh_sync_projects(projects):
914

    
915
    memberships = ProjectMembership.objects.filter(project__in=projects)
916
    user_ids = set(m.person_id for m in memberships)
917

    
918
    qh_sync_users(user_ids)