Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 9770ba6c

History | View | Annotate | Download (30.3 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
                               register_pending_apps, resolve_pending_serial)
76
from astakos.im.project_notif import (
77
    membership_change_notify, membership_enroll_notify,
78
    membership_request_notify, membership_leave_request_notify,
79
    application_submit_notify, application_approve_notify,
80
    application_deny_notify,
81
    project_termination_notify, project_suspension_notify)
82

    
83
import astakos.im.messages as astakos_messages
84
from astakos.quotaholder.exception import NoCapacityError
85

    
86
logger = logging.getLogger(__name__)
87

    
88

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

    
98

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

    
104

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

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

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

    
132

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

    
138

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

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

    
162

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

    
168

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

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

    
191

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

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

    
220

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

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

    
245

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

    
264

    
265
def send_change_email(
266
    ec, request, email_template_name='registration/email_change_email.txt'):
267
    try:
268
        url = ec.get_url()
269
        url = request.build_absolute_uri(url)
270
        t = loader.get_template(email_template_name)
271
        c = {'url': url, 'site_name': SITENAME,
272
             'support': CONTACT_EMAIL, 'ec': ec}
273
        from_email = settings.SERVER_EMAIL
274
        send_mail(_(EMAIL_CHANGE_EMAIL_SUBJECT), t.render(Context(c)),
275
                  from_email, [ec.new_email_address],
276
                  connection=get_connection())
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.log_display
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
    qh_sync_user(user.id)
301
    send_helpdesk_notification(user, helpdesk_email_template_name)
302
    send_greeting(user, email_template_name)
303

    
304
def deactivate(user):
305
    user.is_active = False
306
    user.save()
307

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

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

    
331

    
332
class SendMailError(Exception):
333
    pass
334

    
335

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

    
341

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

    
347

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

    
353

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

    
359

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

    
365

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

    
371

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

    
377

    
378
def get_quota(users):
379
    pass
380

    
381

    
382
### PROJECT FUNCTIONS ###
383

    
384
AUTO_ACCEPT_POLICY = 1
385
MODERATED_POLICY = 2
386
CLOSED_POLICY = 3
387

    
388
POLICIES = [AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY]
389

    
390

    
391
def get_project_by_application_id(project_application_id):
392
    try:
393
        return Project.objects.get(application__id=project_application_id)
394
    except Project.DoesNotExist:
395
        m = (_(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) %
396
             project_application_id)
397
        raise IOError(m)
398

    
399

    
400
def get_related_project_id(application_id):
401
    try:
402
        app = ProjectApplication.objects.get(id=application_id)
403
        chain = app.chain
404
        Project.objects.get(id=chain)
405
        return chain
406
    except ProjectApplication.DoesNotExist, Project.DoesNotExist:
407
        return None
408

    
409

    
410
def get_chain_of_application_id(application_id):
411
    try:
412
        app = ProjectApplication.objects.get(id=application_id)
413
        chain = app.chain
414
        return chain.chain
415
    except ProjectApplication.DoesNotExist:
416
        return None
417

    
418

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

    
426

    
427
def get_project_by_name(name):
428
    try:
429
        return Project.objects.get(name=name)
430
    except Project.DoesNotExist:
431
        m = _(astakos_messages.UNKNOWN_PROJECT_ID) % name
432
        raise IOError(m)
433

    
434

    
435
def get_project_for_update(project_id):
436
    try:
437
        return Project.objects.get_for_update(id=project_id)
438
    except Project.DoesNotExist:
439
        m = _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id
440
        raise IOError(m)
441

    
442

    
443
def get_application_for_update(application_id):
444
    try:
445
        return ProjectApplication.objects.get_for_update(id=application_id)
446
    except ProjectApplication.DoesNotExist:
447
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
448
        raise IOError(m)
449

    
450

    
451
def get_user_by_id(user_id):
452
    try:
453
        return AstakosUser.objects.get(id=user_id)
454
    except AstakosUser.DoesNotExist:
455
        m = _(astakos_messages.UNKNOWN_USER_ID) % user_id
456
        raise IOError(m)
457

    
458

    
459
def get_user_by_uuid(uuid):
460
    try:
461
        return AstakosUser.objects.get(uuid=uuid)
462
    except AstakosUser.DoesNotExist:
463
        m = _(astakos_messages.UNKNOWN_USER_ID) % uuid
464
        raise IOError(m)
465

    
466

    
467
def get_membership_for_update(project_id, user_id):
468
    try:
469
        objs = ProjectMembership.objects
470
        return objs.get_for_update(project__id=project_id,
471
                                   person__id=user_id)
472
    except ProjectMembership.DoesNotExist:
473
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
474
        raise IOError(m)
475

    
476

    
477
def checkAllowed(entity, request_user, admin_only=False):
478
    if isinstance(entity, Project):
479
        application = entity.application
480
    elif isinstance(entity, ProjectApplication):
481
        application = entity
482
    else:
483
        m = "%s not a Project nor a ProjectApplication" % (entity,)
484
        raise ValueError(m)
485

    
486
    if not request_user or request_user.is_project_admin():
487
        return
488

    
489
    if not admin_only and application.owner == request_user:
490
        return
491

    
492
    m = _(astakos_messages.NOT_ALLOWED)
493
    raise PermissionDenied(m)
494

    
495

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

    
501

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

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

    
511
    if project.violates_members_limit(adding=1):
512
        m = _(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED)
513
        raise PermissionDenied(m)
514

    
515

    
516
def accept_membership(project_id, user_id, request_user=None):
517
    project = get_project_for_update(project_id)
518
    accept_membership_checks(project, request_user)
519

    
520
    membership = get_membership_for_update(project_id, user_id)
521
    if not membership.can_accept():
522
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
523
        raise PermissionDenied(m)
524

    
525
    membership.accept()
526
    qh_sync_user(user_id)
527
    logger.info("User %s has been accepted in %s." %
528
                (membership.person.log_display, project))
529

    
530
    membership_change_notify(project, membership.person, 'accepted')
531
    return membership
532

    
533

    
534
def reject_membership_checks(project, request_user):
535
    checkAllowed(project, request_user)
536
    checkAlive(project)
537

    
538

    
539
def reject_membership(project_id, user_id, request_user=None):
540
    project = get_project_for_update(project_id)
541
    reject_membership_checks(project, request_user)
542
    membership = get_membership_for_update(project_id, user_id)
543
    if not membership.can_reject():
544
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
545
        raise PermissionDenied(m)
546

    
547
    membership.reject()
548
    logger.info("Request of user %s for %s has been rejected." %
549
                (membership.person.log_display, project))
550

    
551
    membership_change_notify(project, membership.person, 'rejected')
552
    return membership
553

    
554

    
555
def cancel_membership_checks(project):
556
    checkAlive(project)
557

    
558

    
559
def cancel_membership(project_id, request_user):
560
    project = get_project_for_update(project_id)
561
    cancel_membership_checks(project)
562
    membership = get_membership_for_update(project_id, request_user.id)
563
    if not membership.can_cancel():
564
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
565
        raise PermissionDenied(m)
566

    
567
    membership.cancel()
568
    logger.info("Request of user %s for %s has been cancelled." %
569
                (membership.person.log_display, project))
570

    
571

    
572
def remove_membership_checks(project, request_user=None):
573
    checkAllowed(project, request_user)
574
    checkAlive(project)
575

    
576
    leave_policy = project.application.member_leave_policy
577
    if leave_policy == CLOSED_POLICY:
578
        m = _(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED)
579
        raise PermissionDenied(m)
580

    
581

    
582
def remove_membership(project_id, user_id, request_user=None):
583
    project = get_project_for_update(project_id)
584
    remove_membership_checks(project, request_user)
585
    membership = get_membership_for_update(project_id, user_id)
586
    if not membership.can_remove():
587
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
588
        raise PermissionDenied(m)
589

    
590
    membership.remove()
591
    qh_sync_user(user_id)
592
    logger.info("User %s has been removed from %s." %
593
                (membership.person.log_display, project))
594

    
595
    membership_change_notify(project, membership.person, 'removed')
596
    return membership
597

    
598

    
599
def enroll_member(project_id, user, request_user=None):
600
    project = get_project_for_update(project_id)
601
    accept_membership_checks(project, request_user)
602

    
603
    membership, created = ProjectMembership.objects.get_or_create(
604
        project=project,
605
        person=user)
606

    
607
    if not membership.can_accept():
608
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
609
        raise PermissionDenied(m)
610

    
611
    membership.accept()
612
    qh_sync_user(user.id)
613
    logger.info("User %s has been enrolled in %s." %
614
                (membership.person.log_display, project))
615

    
616
    membership_enroll_notify(project, membership.person)
617
    return membership
618

    
619

    
620
def leave_project_checks(project):
621
    checkAlive(project)
622

    
623
    leave_policy = project.application.member_leave_policy
624
    if leave_policy == CLOSED_POLICY:
625
        m = _(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED)
626
        raise PermissionDenied(m)
627

    
628

    
629
def can_leave_request(project, user):
630
    leave_policy = project.application.member_leave_policy
631
    if leave_policy == CLOSED_POLICY:
632
        return False
633
    m = user.get_membership(project)
634
    if m is None:
635
        return False
636
    if m.state != ProjectMembership.ACCEPTED:
637
        return False
638
    return True
639

    
640

    
641
def leave_project(project_id, request_user):
642
    project = get_project_for_update(project_id)
643
    leave_project_checks(project)
644
    membership = get_membership_for_update(project_id, request_user.id)
645
    if not membership.can_leave():
646
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
647
        raise PermissionDenied(m)
648

    
649
    auto_accepted = False
650
    leave_policy = project.application.member_leave_policy
651
    if leave_policy == AUTO_ACCEPT_POLICY:
652
        membership.remove()
653
        qh_sync_user(request_user.id)
654
        logger.info("User %s has left %s." %
655
                    (membership.person.log_display, project))
656
        auto_accepted = True
657
    else:
658
        membership.leave_request()
659
        logger.info("User %s requested to leave %s." %
660
                    (membership.person.log_display, project))
661
        membership_leave_request_notify(project, membership.person)
662
    return auto_accepted
663

    
664

    
665
def join_project_checks(project):
666
    checkAlive(project)
667

    
668
    join_policy = project.application.member_join_policy
669
    if join_policy == CLOSED_POLICY:
670
        m = _(astakos_messages.MEMBER_JOIN_POLICY_CLOSED)
671
        raise PermissionDenied(m)
672

    
673

    
674
def can_join_request(project, user):
675
    join_policy = project.application.member_join_policy
676
    if join_policy == CLOSED_POLICY:
677
        return False
678
    m = user.get_membership(project)
679
    if m:
680
        return False
681
    return True
682

    
683

    
684
def join_project(project_id, request_user):
685
    project = get_project_for_update(project_id)
686
    join_project_checks(project)
687

    
688
    membership, created = ProjectMembership.objects.get_or_create(
689
        project=project,
690
        person=request_user)
691

    
692
    if not created:
693
        msg = _(astakos_messages.MEMBERSHIP_REQUEST_EXISTS)
694
        raise PermissionDenied(msg)
695

    
696
    auto_accepted = False
697
    join_policy = project.application.member_join_policy
698
    if (join_policy == AUTO_ACCEPT_POLICY and (
699
            not project.violates_members_limit(adding=1))):
700
        membership.accept()
701
        qh_sync_user(request_user.id)
702
        logger.info("User %s joined %s." %
703
                    (membership.person.log_display, project))
704
        auto_accepted = True
705
    else:
706
        membership_request_notify(project, membership.person)
707
        logger.info("User %s requested to join %s." %
708
                    (membership.person.log_display, project))
709
    return auto_accepted
710

    
711

    
712
def submit_application(kw, request_user=None):
713

    
714
    kw['applicant'] = request_user
715
    resource_policies = kw.pop('resource_policies', None)
716

    
717
    precursor = None
718
    precursor_id = kw.get('precursor_application', None)
719
    if precursor_id is not None:
720
        objs = ProjectApplication.objects
721
        precursor = objs.get_for_update(id=precursor_id)
722
        kw['precursor_application'] = precursor
723

    
724
        if (request_user and
725
            (not precursor.owner == request_user and
726
             not request_user.is_superuser
727
             and not request_user.is_project_admin())):
728
            m = _(astakos_messages.NOT_ALLOWED)
729
            raise PermissionDenied(m)
730

    
731
    owner = kw['owner']
732
    force = request_user.is_project_admin()
733
    ok, limit = qh_add_pending_app(owner, precursor, force)
734
    if not ok:
735
        m = _(astakos_messages.REACHED_PENDING_APPLICATION_LIMIT) % limit
736
        raise PermissionDenied(m)
737

    
738
    application = ProjectApplication(**kw)
739

    
740
    if precursor is None:
741
        application.chain = new_chain()
742
    else:
743
        chain = precursor.chain
744
        application.chain = chain
745
        objs = ProjectApplication.objects
746
        q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
747
        pending = q.select_for_update()
748
        for app in pending:
749
            app.state = ProjectApplication.REPLACED
750
            app.save()
751

    
752
    application.save()
753
    application.resource_policies = resource_policies
754
    logger.info("User %s submitted %s." %
755
                (request_user.log_display, application.log_display))
756
    application_submit_notify(application)
757
    return application
758

    
759

    
760
def cancel_application(application_id, request_user=None):
761
    application = get_application_for_update(application_id)
762
    checkAllowed(application, request_user)
763

    
764
    if not application.can_cancel():
765
        m = _(astakos_messages.APPLICATION_CANNOT_CANCEL %
766
              (application.id, application.state_display()))
767
        raise PermissionDenied(m)
768

    
769
    qh_release_pending_app(application.owner)
770

    
771
    application.cancel()
772
    logger.info("%s has been cancelled." % (application.log_display))
773

    
774

    
775
def dismiss_application(application_id, request_user=None):
776
    application = get_application_for_update(application_id)
777
    checkAllowed(application, request_user)
778

    
779
    if not application.can_dismiss():
780
        m = _(astakos_messages.APPLICATION_CANNOT_DISMISS %
781
              (application.id, application.state_display()))
782
        raise PermissionDenied(m)
783

    
784
    application.dismiss()
785
    logger.info("%s has been dismissed." % (application.log_display))
786

    
787

    
788
def deny_application(application_id, request_user=None, reason=None):
789
    application = get_application_for_update(application_id)
790

    
791
    checkAllowed(application, request_user, admin_only=True)
792

    
793
    if not application.can_deny():
794
        m = _(astakos_messages.APPLICATION_CANNOT_DENY %
795
              (application.id, application.state_display()))
796
        raise PermissionDenied(m)
797

    
798
    qh_release_pending_app(application.owner)
799

    
800
    if reason is None:
801
        reason = ""
802
    application.deny(reason)
803
    logger.info("%s has been denied with reason \"%s\"." %
804
                (application.log_display, reason))
805
    application_deny_notify(application)
806

    
807

    
808
def approve_application(app_id, request_user=None):
809

    
810
    try:
811
        objects = ProjectApplication.objects
812
        application = objects.get_for_update(id=app_id)
813
    except ProjectApplication.DoesNotExist:
814
        m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID % (app_id,))
815
        raise PermissionDenied(m)
816

    
817
    checkAllowed(application, request_user, admin_only=True)
818

    
819
    if not application.can_approve():
820
        m = _(astakos_messages.APPLICATION_CANNOT_APPROVE %
821
              (application.id, application.state_display()))
822
        raise PermissionDenied(m)
823

    
824
    qh_release_pending_app(application.owner)
825
    project = application.approve()
826
    qh_sync_projects([project])
827
    logger.info("%s has been approved." % (application.log_display))
828
    application_approve_notify(application)
829

    
830

    
831
def check_expiration(execute=False):
832
    objects = Project.objects
833
    expired = objects.expired_projects()
834
    if execute:
835
        for project in expired:
836
            terminate(project.id)
837

    
838
    return [project.expiration_info() for project in expired]
839

    
840

    
841
def terminate(project_id, request_user=None):
842
    project = get_project_for_update(project_id)
843
    checkAllowed(project, request_user, admin_only=True)
844
    checkAlive(project)
845

    
846
    project.terminate()
847
    qh_sync_projects([project])
848
    logger.info("%s has been terminated." % (project))
849

    
850
    project_termination_notify(project)
851

    
852

    
853
def suspend(project_id, request_user=None):
854
    project = get_project_by_id(project_id)
855
    checkAllowed(project, request_user, admin_only=True)
856
    checkAlive(project)
857

    
858
    project.suspend()
859
    qh_sync_projects([project])
860
    logger.info("%s has been suspended." % (project))
861

    
862
    project_suspension_notify(project)
863

    
864

    
865
def resume(project_id, request_user=None):
866
    project = get_project_for_update(project_id)
867
    checkAllowed(project, request_user, admin_only=True)
868

    
869
    if not project.is_suspended:
870
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
871
        raise PermissionDenied(m)
872

    
873
    project.resume()
874
    qh_sync_projects([project])
875
    logger.info("%s has been unsuspended." % (project))
876

    
877

    
878
def get_by_chain_or_404(chain_id):
879
    try:
880
        project = Project.objects.get(id=chain_id)
881
        application = project.application
882
        return project, application
883
    except:
884
        application = ProjectApplication.objects.latest_of_chain(chain_id)
885
        if application is None:
886
            raise Http404
887
        else:
888
            return None, application
889

    
890

    
891
def get_user_setting(user_id, key):
892
    try:
893
        setting = UserSetting.objects.get(
894
            user=user_id, setting=key)
895
        return setting.value
896
    except UserSetting.DoesNotExist:
897
        return getattr(astakos_settings, key)
898

    
899

    
900
def set_user_setting(user_id, key, value):
901
    try:
902
        setting = UserSetting.objects.get_for_update(
903
            user=user_id, setting=key)
904
    except UserSetting.DoesNotExist:
905
        setting = UserSetting(user_id=user_id, setting=key)
906
    setting.value = value
907
    setting.save()
908

    
909

    
910
def unset_user_setting(user_id, key):
911
    UserSetting.objects.filter(user=user_id, setting=key).delete()
912

    
913

    
914
def qh_add_pending_app(user, precursor=None, force=False, dry_run=False):
915
    if precursor is None:
916
        diff = 1
917
    else:
918
        chain = precursor.chain
919
        objs = ProjectApplication.objects
920
        q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
921
        count = q.count()
922
        diff = 1 - count
923

    
924
    try:
925
        name = "DRYRUN" if dry_run else ""
926
        serial = register_pending_apps(user, diff, force, name=name)
927
    except NoCapacityError as e:
928
        limit = e.data['limit']
929
        return False, limit
930
    else:
931
        accept = not dry_run
932
        resolve_pending_serial(serial, accept=accept)
933
        return True, None
934

    
935

    
936
def qh_release_pending_app(user):
937
    serial = register_pending_apps(user, -1)
938
    resolve_pending_serial(serial)
939

    
940

    
941
def qh_sync_projects(projects):
942

    
943
    memberships = ProjectMembership.objects.filter(project__in=projects)
944
    user_ids = set(m.person_id for m in memberships)
945

    
946
    qh_sync_users(user_ids)