Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / functions.py @ 992b81b6

History | View | Annotate | Download (29.7 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, users_quotas)
74
from astakos.im.project_notif import (
75
    membership_change_notify, membership_enroll_notify,
76
    membership_request_notify, membership_leave_request_notify,
77
    application_submit_notify, application_approve_notify,
78
    application_deny_notify,
79
    project_termination_notify, project_suspension_notify)
80
from astakos.im.endpoints.qh import (
81
    register_quotas, qh_get_quota)
82

    
83
import astakos.im.messages as astakos_messages
84

    
85
logger = logging.getLogger(__name__)
86

    
87

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

    
97

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

    
103

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

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

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

    
131

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

    
137

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

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

    
161

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

    
167

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

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

    
190

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

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

    
219

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

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

    
244

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

    
263

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

    
283

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

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

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

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

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

    
330

    
331
class SendMailError(Exception):
332
    pass
333

    
334

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

    
340

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

    
346

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

    
352

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

    
358

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

    
364

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

    
370

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

    
376

    
377
def register_user_quotas(user):
378
    quotas = users_quotas([user])
379
    register_quotas(quotas)
380

    
381

    
382
def get_quota(users):
383
    resources = get_resource_names()
384
    return qh_get_quota(users, resources)
385

    
386

    
387
### PROJECT VIEWS ###
388

    
389
AUTO_ACCEPT_POLICY = 1
390
MODERATED_POLICY   = 2
391
CLOSED_POLICY      = 3
392

    
393
POLICIES = [ AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY ]
394

    
395
def get_project_by_application_id(project_application_id):
396
    try:
397
        return Project.objects.get(application__id=project_application_id)
398
    except Project.DoesNotExist:
399
        raise IOError(
400
            _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
401

    
402
def get_related_project_id(application_id):
403
    try:
404
        app = ProjectApplication.objects.get(id=application_id)
405
        chain = app.chain
406
        project = Project.objects.get(id=chain)
407
        return chain
408
    except:
409
        return None
410

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

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

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

    
433

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

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

    
448
def get_user_by_id(user_id):
449
    try:
450
        return AstakosUser.objects.get(id=user_id)
451
    except AstakosUser.DoesNotExist:
452
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % user_id)
453

    
454
def get_user_by_uuid(uuid):
455
    try:
456
        return AstakosUser.objects.get(uuid=uuid)
457
    except AstakosUser.DoesNotExist:
458
        raise IOError(_(astakos_messages.UNKNOWN_USER_ID) % uuid)
459

    
460
def create_membership(project, user):
461
    if isinstance(user, (int, long)):
462
        user = get_user_by_id(user)
463

    
464
    if not user.is_active:
465
        m = _(astakos_messages.ACCOUNT_NOT_ACTIVE)
466
        raise PermissionDenied(m)
467

    
468
    m, created = ProjectMembership.objects.get_or_create(
469
        project=project,
470
        person=user)
471

    
472
    if created:
473
        return m
474
    else:
475
        msg = _(astakos_messages.MEMBERSHIP_REQUEST_EXISTS)
476
        raise PermissionDenied(msg)
477

    
478

    
479
def get_membership_for_update(project, user):
480
    if isinstance(user, (int, long)):
481
        user = get_user_by_id(user)
482
    try:
483
        objs = ProjectMembership.objects
484
        m = objs.get_for_update(project=project, person=user)
485
        return m
486
    except ProjectMembership.DoesNotExist:
487
        raise IOError(_(astakos_messages.NOT_MEMBERSHIP_REQUEST))
488

    
489
def checkAllowed(entity, request_user):
490
    if isinstance(entity, Project):
491
        application = entity.application
492
    elif isinstance(entity, ProjectApplication):
493
        application = entity
494
    else:
495
        m = "%s not a Project nor a ProjectApplication" % (entity,)
496
        raise ValueError(m)
497

    
498
    if request_user and \
499
        (not application.owner == request_user and \
500
            not request_user.is_superuser):
501
        raise PermissionDenied(_(astakos_messages.NOT_ALLOWED))
502

    
503
def checkAlive(project):
504
    if not project.is_alive:
505
        raise PermissionDenied(
506
            _(astakos_messages.NOT_ALIVE_PROJECT) % project.__dict__)
507

    
508
def accept_membership_checks(project, request_user):
509
    checkAllowed(project, request_user)
510
    checkAlive(project)
511

    
512
    join_policy = project.application.member_join_policy
513
    if join_policy == CLOSED_POLICY:
514
        raise PermissionDenied(_(astakos_messages.MEMBER_JOIN_POLICY_CLOSED))
515

    
516
    if project.violates_members_limit(adding=1):
517
        raise PermissionDenied(_(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED))
518

    
519
def accept_membership(project_id, user, request_user=None):
520
    project = get_project_for_update(project_id)
521
    accept_membership_checks(project, request_user)
522

    
523
    membership = get_membership_for_update(project, user)
524
    if not membership.can_accept():
525
        m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
526
        raise PermissionDenied(m)
527

    
528
    membership.accept()
529
    logger.info("User %s has been accepted in %s." %
530
                (membership.person.log_display, project))
531

    
532
    membership_change_notify(project, membership.person, 'accepted')
533

    
534
    return membership
535

    
536
def reject_membership_checks(project, request_user):
537
    checkAllowed(project, request_user)
538
    checkAlive(project)
539

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

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

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

    
554
    return membership
555

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

    
559
def cancel_membership(project_id, user_id):
560
    project = get_project_for_update(project_id)
561
    cancel_membership_checks(project)
562
    membership = get_membership_for_update(project, 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
def remove_membership_checks(project, request_user=None):
572
    checkAllowed(project, request_user)
573
    checkAlive(project)
574

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

    
579
def remove_membership(project_id, user, request_user=None):
580
    project = get_project_for_update(project_id)
581
    remove_membership_checks(project, request_user)
582
    membership = get_membership_for_update(project, user)
583
    if not membership.can_remove():
584
        m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
585
        raise PermissionDenied(m)
586

    
587
    membership.remove()
588
    logger.info("User %s has been removed from %s." %
589
                (membership.person.log_display, project))
590

    
591
    membership_change_notify(project, membership.person, 'removed')
592

    
593
    return membership
594

    
595
def enroll_member(project_id, user, request_user=None):
596
    project = get_project_for_update(project_id)
597
    accept_membership_checks(project, request_user)
598
    membership = create_membership(project, user)
599

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

    
604
    membership.accept()
605
    logger.info("User %s has been enrolled in %s." %
606
                (membership.person.log_display, project))
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
        logger.info("User %s has left %s." %
642
                    (membership.person.log_display, project))
643
        auto_accepted = True
644
    else:
645
        membership.leave_request()
646
        logger.info("User %s requested to leave %s." %
647
                    (membership.person.log_display, project))
648
        membership_leave_request_notify(project, membership.person)
649
    return auto_accepted
650

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

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

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

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

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

    
685
    return auto_accepted
686

    
687
def submit_application(kw, request_user=None):
688

    
689
    kw['applicant'] = request_user
690
    resource_policies = kw.pop('resource_policies', None)
691

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

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

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

    
712
    application = ProjectApplication(**kw)
713

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

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

    
733
def cancel_application(application_id, request_user=None):
734
    application = get_application_for_update(application_id)
735
    checkAllowed(application, request_user)
736

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

    
742
    application.cancel()
743
    logger.info("%s has been cancelled." % (application.log_display))
744

    
745
def dismiss_application(application_id, request_user=None):
746
    application = get_application_for_update(application_id)
747
    checkAllowed(application, request_user)
748

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

    
754
    application.dismiss()
755
    logger.info("%s has been dismissed." % (application.log_display))
756

    
757
def deny_application(application_id, reason=None):
758
    application = get_application_for_update(application_id)
759

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

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

    
772
def approve_application(app_id):
773

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

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

    
786
    application.approve()
787
    logger.info("%s has been approved." % (application.log_display))
788
    application_approve_notify(application)
789

    
790
def check_expiration(execute=False):
791
    objects = Project.objects
792
    expired = objects.expired_projects()
793
    if execute:
794
        for project in expired:
795
            terminate(project.id)
796

    
797
    return [project.expiration_info() for project in expired]
798

    
799
def terminate(project_id):
800
    project = get_project_for_update(project_id)
801
    checkAlive(project)
802

    
803
    project.terminate()
804
    logger.info("%s has been terminated." % (project))
805
    project_termination_notify(project)
806

    
807
def suspend(project_id):
808
    project = get_project_by_id(project_id)
809
    checkAlive(project)
810

    
811
    project.suspend()
812
    logger.info("%s has been suspended." % (project))
813
    project_suspension_notify(project)
814

    
815
def resume(project_id):
816
    project = get_project_for_update(project_id)
817

    
818
    if not project.is_suspended:
819
        m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.__dict__
820
        raise PermissionDenied(m)
821

    
822
    project.resume()
823
    logger.info("%s has been unsuspended." % (project))
824

    
825
def get_by_chain_or_404(chain_id):
826
    try:
827
        project = Project.objects.get(id=chain_id)
828
        application = project.application
829
        return project, application
830
    except:
831
        application = ProjectApplication.objects.latest_of_chain(chain_id)
832
        if application is None:
833
            raise Http404
834
        else:
835
            return None, application
836

    
837

    
838
def get_user_setting(user_id, key):
839
    try:
840
        setting = UserSetting.objects.get(
841
            user=user_id, setting=key)
842
        return setting.value
843
    except UserSetting.DoesNotExist:
844
        return getattr(astakos_settings, key)
845

    
846

    
847
def set_user_setting(user_id, key, value):
848
    try:
849
        setting = UserSetting.objects.get_for_update(
850
            user=user_id, setting=key)
851
    except UserSetting.DoesNotExist:
852
        setting = UserSetting(user_id=user_id, setting=key)
853
    setting.value = value
854
    setting.save()
855

    
856

    
857
def unset_user_setting(user_id, key):
858
    UserSetting.objects.filter(user=user_id, setting=key).delete()
859

    
860

    
861
PENDING_APPLICATION_LIMIT_SETTING = 'PENDING_APPLICATION_LIMIT'
862

    
863
def get_pending_application_limit(user_id):
864
    key = PENDING_APPLICATION_LIMIT_SETTING
865
    return get_user_setting(user_id, key)
866

    
867

    
868
def set_pending_application_limit(user_id, value):
869
    key = PENDING_APPLICATION_LIMIT_SETTING
870
    return set_user_setting(user_id, key, value)
871

    
872

    
873
def unset_pending_application_limit(user_id):
874
    key = PENDING_APPLICATION_LIMIT_SETTING
875
    return unset_user_setting(user_id, key)
876

    
877

    
878
def _reached_pending_application_limit(user_id):
879
    limit = get_pending_application_limit(user_id)
880

    
881
    PENDING = ProjectApplication.PENDING
882
    pending = ProjectApplication.objects.filter(
883
        owner__id=user_id, state=PENDING).count()
884

    
885
    return pending >= limit, limit
886

    
887

    
888
def reached_pending_application_limit(user_id, precursor=None):
889
    reached, limit = _reached_pending_application_limit(user_id)
890

    
891
    if precursor is None:
892
        return reached, limit
893

    
894
    chain = precursor.chain
895
    objs = ProjectApplication.objects
896
    q = objs.filter(chain=chain, state=ProjectApplication.PENDING)
897
    has_pending = q.exists()
898

    
899
    if not has_pending:
900
        return reached, limit
901

    
902
    return False, limit