Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 3abf6c78

History | View | Annotate | Download (10.7 kB)

1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from django.utils.importlib import import_module
35
from django.core.exceptions import ImproperlyConfigured
36
from django.core.mail import send_mail
37
from django.template.loader import render_to_string
38
from django.contrib import messages
39
from django.core.urlresolvers import reverse
40
from django.utils.translation import ugettext as _
41
from django.db import transaction
42

    
43
from urlparse import urljoin
44

    
45
from astakos.im.models import AstakosUser, Invitation
46
from astakos.im.forms import *
47
from astakos.im.util import get_invitation
48
from astakos.im.functions import send_verification, send_activation, \
49
    send_admin_notification, activate, SendMailError
50
from astakos.im.settings import INVITATIONS_ENABLED, DEFAULT_CONTACT_EMAIL, \
51
    DEFAULT_FROM_EMAIL, MODERATION_ENABLED, SITENAME, DEFAULT_ADMIN_EMAIL, RE_USER_EMAIL_PATTERNS
52

    
53
import socket
54
import logging
55
import re
56

    
57
logger = logging.getLogger(__name__)
58

    
59
def get_backend(request):
60
    """
61
    Returns an instance of an activation backend,
62
    according to the INVITATIONS_ENABLED setting
63
    (if True returns ``astakos.im.activation_backends.InvitationsBackend`` and if False
64
    returns ``astakos.im.activation_backends.SimpleBackend``).
65

66
    If the backend cannot be located ``django.core.exceptions.ImproperlyConfigured``
67
    is raised.
68
    """
69
    module = 'astakos.im.activation_backends'
70
    prefix = 'Invitations' if INVITATIONS_ENABLED else 'Simple'
71
    backend_class_name = '%sBackend' %prefix
72
    try:
73
        mod = import_module(module)
74
    except ImportError, e:
75
        raise ImproperlyConfigured('Error loading activation backend %s: "%s"' % (module, e))
76
    try:
77
        backend_class = getattr(mod, backend_class_name)
78
    except AttributeError:
79
        raise ImproperlyConfigured('Module "%s" does not define a activation backend named "%s"' % (module, attr))
80
    return backend_class(request)
81

    
82
class ActivationBackend(object):
83
    def _is_preaccepted(self, user):
84
        # return True if user email matches specific patterns
85
        for pattern in RE_USER_EMAIL_PATTERNS:
86
            if re.match(pattern, user.email):
87
                return True
88
        return False
89
    
90
    def get_signup_form(self, provider='local', instance=None):
91
        """
92
        Returns a form instance of the relevant class
93
        """
94
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
95
        suffix  = 'UserCreationForm'
96
        formclass = '%s%s' % (main, suffix)
97
        request = self.request
98
        initial_data = None
99
        if request.method == 'POST':
100
            if provider == request.POST.get('provider', ''):
101
                initial_data = request.POST
102
        return globals()[formclass](initial_data, instance=instance, request=request)
103
    
104
    def handle_activation(self, user, \
105
                          activation_template_name='im/activation_email.txt', \
106
                          greeting_template_name='im/welcome_email.txt', \
107
                          admin_email_template_name='im/account_notification.txt', \
108
                          switch_accounts_email_template_name='im/switch_accounts_email.txt'):
109
        """
110
        If the user is already active returns immediately.
111
        If the user is not active and there is another account associated with
112
        the specific email, it sends an informative email to the user whether
113
        wants to switch to this account.
114
        If the user is preaccepted and the email is verified, the account is
115
        activated automatically. Otherwise, if the email is not verified,
116
        it sends a verification email to the user.
117
        If the user is not preaccepted, it sends an email to the administrators
118
        and informs the user that the account is pending activation.
119
        """
120
        try:
121
            if user.is_active:
122
                return RegistationCompleted()
123
            if user.conflicting_email():
124
                send_verification(user, switch_accounts_email_template_name)
125
                return SwitchAccountsVerificationSent(user.email)
126
            
127
            if self._is_preaccepted(user):
128
                if user.email_verified:
129
                    activate(user, greeting_template_name)
130
                    return RegistationCompleted()
131
                else:
132
                    send_activation(user, activation_template_name)
133
                    return VerificationSent()
134
            else:
135
                send_admin_notification(
136
                    template_name=admin_email_template_name,
137
                    dictionary={'user':user, 'group_creation':True},
138
                    subject='%s alpha2 testing account notification' % SITENAME
139
                )
140
                return NotificationSent()
141
        except BaseException, e:
142
            logger.exception(e)
143
            raise e
144

    
145
class InvitationsBackend(ActivationBackend):
146
    """
147
    A activation backend which implements the following workflow: a user
148
    supplies the necessary registation information, if the request contains a valid
149
    inivation code the user is automatically activated otherwise an inactive user
150
    account is created and the user is going to receive an email as soon as an
151
    administrator activates his/her account.
152
    """
153
    def __init__(self, request):
154
        self.request = request
155
        super(InvitationsBackend, self).__init__()
156

    
157
    def get_signup_form(self, provider='local', instance=None):
158
        """
159
        Returns a form instance of the relevant class
160
        
161
        raises Invitation.DoesNotExist and ValueError if invitation is consumed
162
        or invitation username is reserved.
163
        """
164
        self.invitation = get_invitation(self.request)
165
        invitation = self.invitation
166
        initial_data = self.get_signup_initial_data(provider)
167
        prefix = 'Invited' if invitation else ''
168
        main = provider.capitalize()
169
        suffix  = 'UserCreationForm'
170
        formclass = '%s%s%s' % (prefix, main, suffix)
171
        return globals()[formclass](initial_data, instance=instance, request=self.request)
172

    
173
    def get_signup_initial_data(self, provider):
174
        """
175
        Returns the necassary activation form depending the user is invited or not
176

177
        Throws Invitation.DoesNotExist in case ``code`` is not valid.
178
        """
179
        request = self.request
180
        invitation = self.invitation
181
        initial_data = None
182
        if request.method == 'GET':
183
            if invitation:
184
                # create a tmp user with the invitation realname
185
                # to extract first and last name
186
                u = AstakosUser(realname = invitation.realname)
187
                initial_data = {'email':invitation.username,
188
                                'inviter':invitation.inviter.realname,
189
                                'first_name':u.first_name,
190
                                'last_name':u.last_name,
191
                                'provider':provider}
192
        else:
193
            if provider == request.POST.get('provider', ''):
194
                initial_data = request.POST
195
        return initial_data
196

    
197
    def _is_preaccepted(self, user):
198
        """
199
        If there is a valid, not-consumed invitation code for the specific user
200
        returns True else returns False.
201
        """
202
        if super(InvitationsBackend, self)._is_preaccepted(user):
203
            return True
204
        invitation = self.invitation
205
        if not invitation:
206
            return False
207
        if invitation.username == user.email and not invitation.is_consumed:
208
            invitation.consume()
209
            return True
210
        return False
211

    
212
class SimpleBackend(ActivationBackend):
213
    """
214
    A activation backend which implements the following workflow: a user
215
    supplies the necessary registation information, an incative user account is
216
    created and receives an email in order to activate his/her account.
217
    """
218
    def __init__(self, request):
219
        self.request = request
220
        super(SimpleBackend, self).__init__()
221
    
222
    def _is_preaccepted(self, user):
223
        if super(SimpleBackend, self)._is_preaccepted(user):
224
            return True
225
        if MODERATION_ENABLED:
226
            return False
227
        return True
228

    
229
class ActivationResult(object):
230
    def __init__(self, message):
231
        self.message = message
232

    
233
class VerificationSent(ActivationResult):
234
    def __init__(self):
235
        message = _('Verification sent.')
236
        super(VerificationSent, self).__init__(message)
237

    
238
class SwitchAccountsVerificationSent(ActivationResult):
239
    def __init__(self, email):
240
        message = _('This email is already associated with another \
241
                    local account. To change this account to a shibboleth \
242
                    one follow the link in the verification email sent \
243
                    to %s. Otherwise just ignore it.' % email)
244
        super(SwitchAccountsVerificationSent, self).__init__(message)
245

    
246
class NotificationSent(ActivationResult):
247
    def __init__(self):
248
        message = _('Your request for an account was successfully received and is now pending \
249
                    approval. You will be notified by email in the next few days. Thanks for \
250
                    your interest in ~okeanos! The GRNET team.')
251
        super(NotificationSent, self).__init__(message)
252

    
253
class RegistationCompleted(ActivationResult):
254
    def __init__(self):
255
        message = _('Registration completed. You can now login.')
256
        super(RegistationCompleted, self).__init__(message)