Statistics
| Branch: | Tag: | Revision:

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

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.sites.models import Site
39
from django.contrib import messages
40
from django.core.urlresolvers import reverse
41
from django.utils.translation import ugettext as _
42
from django.db import transaction
43

    
44
from urlparse import urljoin
45

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

    
52
import socket
53
import logging
54
import re
55

    
56
logger = logging.getLogger(__name__)
57

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

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

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

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

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

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

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

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

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

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

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

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

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

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