Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ f6e8cca4

History | View | Annotate | Download (10.5 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/admin_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(user, admin_email_template_name)
136
                return NotificationSent()
137
        except BaseException, e:
138
            logger.exception(e)
139
            raise e
140

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

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

    
169
    def get_signup_initial_data(self, provider):
170
        """
171
        Returns the necassary activation form depending the user is invited or not
172

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

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

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

    
225
class ActivationResult(object):
226
    def __init__(self, message):
227
        self.message = message
228

    
229
class VerificationSent(ActivationResult):
230
    def __init__(self):
231
        message = _('Verification sent.')
232
        super(VerificationSent, self).__init__(message)
233

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

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

    
249
class RegistationCompleted(ActivationResult):
250
    def __init__(self):
251
        message = _('Registration completed. You can now login.')
252
        super(RegistationCompleted, self).__init__(message)