Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 07ad7b1c

History | View | Annotate | Download (20 kB)

1 0905ccd2 Sofia Papagiannaki
# Copyright 2011 GRNET S.A. All rights reserved.
2 0905ccd2 Sofia Papagiannaki
#
3 0905ccd2 Sofia Papagiannaki
# Redistribution and use in source and binary forms, with or
4 0905ccd2 Sofia Papagiannaki
# without modification, are permitted provided that the following
5 0905ccd2 Sofia Papagiannaki
# conditions are met:
6 0905ccd2 Sofia Papagiannaki
#
7 0905ccd2 Sofia Papagiannaki
#   1. Redistributions of source code must retain the above
8 0905ccd2 Sofia Papagiannaki
#      copyright notice, this list of conditions and the following
9 0905ccd2 Sofia Papagiannaki
#      disclaimer.
10 0905ccd2 Sofia Papagiannaki
#
11 0905ccd2 Sofia Papagiannaki
#   2. Redistributions in binary form must reproduce the above
12 0905ccd2 Sofia Papagiannaki
#      copyright notice, this list of conditions and the following
13 0905ccd2 Sofia Papagiannaki
#      disclaimer in the documentation and/or other materials
14 0905ccd2 Sofia Papagiannaki
#      provided with the distribution.
15 0905ccd2 Sofia Papagiannaki
#
16 0905ccd2 Sofia Papagiannaki
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 0905ccd2 Sofia Papagiannaki
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 0905ccd2 Sofia Papagiannaki
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 0905ccd2 Sofia Papagiannaki
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 0905ccd2 Sofia Papagiannaki
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 0905ccd2 Sofia Papagiannaki
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 0905ccd2 Sofia Papagiannaki
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 0905ccd2 Sofia Papagiannaki
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 0905ccd2 Sofia Papagiannaki
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 0905ccd2 Sofia Papagiannaki
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 0905ccd2 Sofia Papagiannaki
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 0905ccd2 Sofia Papagiannaki
# POSSIBILITY OF SUCH DAMAGE.
28 0905ccd2 Sofia Papagiannaki
#
29 0905ccd2 Sofia Papagiannaki
# The views and conclusions contained in the software and
30 0905ccd2 Sofia Papagiannaki
# documentation are those of the authors and should not be
31 0905ccd2 Sofia Papagiannaki
# interpreted as representing official policies, either expressed
32 0905ccd2 Sofia Papagiannaki
# or implied, of GRNET S.A.
33 0905ccd2 Sofia Papagiannaki
34 0905ccd2 Sofia Papagiannaki
from django.utils.importlib import import_module
35 890b0eaf Sofia Papagiannaki
from django.core.exceptions import ImproperlyConfigured
36 18ffbee1 Sofia Papagiannaki
from django.utils.translation import ugettext as _
37 890b0eaf Sofia Papagiannaki
38 aab4d540 Sofia Papagiannaki
from astakos.im.models import AstakosUser
39 e7cb4085 Kostas Papadimitriou
from astakos.im import functions
40 e7cb4085 Kostas Papadimitriou
from astakos.im import settings
41 e7cb4085 Kostas Papadimitriou
from astakos.im import forms
42 e7cb4085 Kostas Papadimitriou
43 2e7924de Giorgos Korfiatis
from astakos.im.quotas import qh_sync_user
44 d1a767f7 Olga Brani
45 d1a767f7 Olga Brani
import astakos.im.messages as astakos_messages
46 0905ccd2 Sofia Papagiannaki
47 e7cb4085 Kostas Papadimitriou
import datetime
48 5ed6816e Sofia Papagiannaki
import logging
49 8316698a Sofia Papagiannaki
import re
50 e7cb4085 Kostas Papadimitriou
import json
51 5ed6816e Sofia Papagiannaki
52 e015e9e6 Sofia Papagiannaki
logger = logging.getLogger(__name__)
53 e015e9e6 Sofia Papagiannaki
54 5ce3ce4f Sofia Papagiannaki
55 e7cb4085 Kostas Papadimitriou
def get_backend():
56 0905ccd2 Sofia Papagiannaki
    """
57 18ffbee1 Sofia Papagiannaki
    Returns an instance of an activation backend,
58 890b0eaf Sofia Papagiannaki
    according to the INVITATIONS_ENABLED setting
59 0dd46210 Sofia Papagiannaki
    (if True returns ``astakos.im.activation_backends.InvitationsBackend``
60 0dd46210 Sofia Papagiannaki
    and if False
61 8f5a3a06 Sofia Papagiannaki
    returns ``astakos.im.activation_backends.SimpleBackend``).
62 bef3bf46 Kostas Papadimitriou

63 0dd46210 Sofia Papagiannaki
    If the backend cannot be located
64 0dd46210 Sofia Papagiannaki
    ``django.core.exceptions.ImproperlyConfigured`` is raised.
65 0905ccd2 Sofia Papagiannaki
    """
66 8f5a3a06 Sofia Papagiannaki
    module = 'astakos.im.activation_backends'
67 e7cb4085 Kostas Papadimitriou
    prefix = 'Invitations' if settings.INVITATIONS_ENABLED else 'Simple'
68 5ce3ce4f Sofia Papagiannaki
    backend_class_name = '%sBackend' % prefix
69 0905ccd2 Sofia Papagiannaki
    try:
70 0905ccd2 Sofia Papagiannaki
        mod = import_module(module)
71 0905ccd2 Sofia Papagiannaki
    except ImportError, e:
72 5ce3ce4f Sofia Papagiannaki
        raise ImproperlyConfigured(
73 5ce3ce4f Sofia Papagiannaki
            'Error loading activation backend %s: "%s"' % (module, e))
74 0905ccd2 Sofia Papagiannaki
    try:
75 0905ccd2 Sofia Papagiannaki
        backend_class = getattr(mod, backend_class_name)
76 0905ccd2 Sofia Papagiannaki
    except AttributeError:
77 0dd46210 Sofia Papagiannaki
        raise ImproperlyConfigured(
78 0dd46210 Sofia Papagiannaki
            'Module "%s" does not define a activation backend named "%s"' % (
79 0dd46210 Sofia Papagiannaki
                module, backend_class_name))
80 e7cb4085 Kostas Papadimitriou
    return backend_class(settings.MODERATION_ENABLED)
81 890b0eaf Sofia Papagiannaki
82 5ce3ce4f Sofia Papagiannaki
83 0a569195 Sofia Papagiannaki
class ActivationBackend(object):
84 e7cb4085 Kostas Papadimitriou
    """
85 e7cb4085 Kostas Papadimitriou
    ActivationBackend handles user verification/activation.
86 e7cb4085 Kostas Papadimitriou

87 e7cb4085 Kostas Papadimitriou
    Example usage::
88 e7cb4085 Kostas Papadimitriou
    >>> # it is wise to not instantiate a backend class directly but use
89 e7cb4085 Kostas Papadimitriou
    >>> # get_backend method instead.
90 e7cb4085 Kostas Papadimitriou
    >>> backend = get_backend()
91 e7cb4085 Kostas Papadimitriou
    >>> formCls = backend.get_signup_form(request.POST)
92 e7cb4085 Kostas Papadimitriou
    >>> if form.is_valid():
93 e7cb4085 Kostas Papadimitriou
    >>>     user = form.save(commit=False)
94 e7cb4085 Kostas Papadimitriou
    >>>     # this creates auth provider objects
95 e7cb4085 Kostas Papadimitriou
    >>>     form.store_user(user)
96 e7cb4085 Kostas Papadimitriou
    >>>     activation = backend.handle_registration(user)
97 e7cb4085 Kostas Papadimitriou
    >>>     # activation.status is one of backend.Result.{*} activation result
98 e7cb4085 Kostas Papadimitriou
    >>>     # types
99 e7cb4085 Kostas Papadimitriou
    >>>
100 e7cb4085 Kostas Papadimitriou
    >>>     # sending activation notifications is not done automatically
101 e7cb4085 Kostas Papadimitriou
    >>>     # we need to call send_result_notifications
102 e7cb4085 Kostas Papadimitriou
    >>>     backend.send_result_notifications(activation)
103 e7cb4085 Kostas Papadimitriou
    >>>     return HttpResponse(activation.message)
104 e7cb4085 Kostas Papadimitriou
    """
105 e7cb4085 Kostas Papadimitriou
106 e7cb4085 Kostas Papadimitriou
    verification_template_name = 'im/activation_email.txt'
107 e7cb4085 Kostas Papadimitriou
    greeting_template_name = 'im/welcome_email.txt'
108 e7cb4085 Kostas Papadimitriou
    pending_moderation_template_name = \
109 e7cb4085 Kostas Papadimitriou
        'im/account_pending_moderation_notification.txt'
110 e7cb4085 Kostas Papadimitriou
    activated_email_template_name = 'im/account_activated_notification.txt'
111 e7cb4085 Kostas Papadimitriou
112 e7cb4085 Kostas Papadimitriou
    class Result:
113 e7cb4085 Kostas Papadimitriou
        # user created, email verification sent
114 e7cb4085 Kostas Papadimitriou
        PENDING_VERIFICATION = 1
115 e7cb4085 Kostas Papadimitriou
        # email verified
116 e7cb4085 Kostas Papadimitriou
        PENDING_MODERATION = 2
117 e7cb4085 Kostas Papadimitriou
        # user moderated
118 e7cb4085 Kostas Papadimitriou
        ACCEPTED = 3
119 e7cb4085 Kostas Papadimitriou
        # user rejected
120 e7cb4085 Kostas Papadimitriou
        REJECTED = 4
121 e7cb4085 Kostas Papadimitriou
        # inactive user activated
122 e7cb4085 Kostas Papadimitriou
        ACTIVATED = 5
123 e7cb4085 Kostas Papadimitriou
        # active user deactivated
124 e7cb4085 Kostas Papadimitriou
        DEACTIVATED = 6
125 e7cb4085 Kostas Papadimitriou
        # something went wrong
126 e7cb4085 Kostas Papadimitriou
        ERROR = -1
127 e7cb4085 Kostas Papadimitriou
128 e7cb4085 Kostas Papadimitriou
    def __init__(self, moderation_enabled):
129 e7cb4085 Kostas Papadimitriou
        self.moderation_enabled = moderation_enabled
130 5ce3ce4f Sofia Papagiannaki
131 8316698a Sofia Papagiannaki
    def _is_preaccepted(self, user):
132 e7cb4085 Kostas Papadimitriou
        """
133 e7cb4085 Kostas Papadimitriou
        Decide whether user should be automatically moderated. The method gets
134 e7cb4085 Kostas Papadimitriou
        called only when self.moderation_enabled is set to True.
135 e7cb4085 Kostas Papadimitriou

136 e7cb4085 Kostas Papadimitriou
        The method returns False or a string identifier which later will be
137 e7cb4085 Kostas Papadimitriou
        stored in user's accepted_policy field. This is helpfull for
138 e7cb4085 Kostas Papadimitriou
        administrators to be aware of the reason a created user was
139 e7cb4085 Kostas Papadimitriou
        automatically activated.
140 e7cb4085 Kostas Papadimitriou
        """
141 e7cb4085 Kostas Papadimitriou
142 e7cb4085 Kostas Papadimitriou
        # check preaccepted mail patterns
143 e7cb4085 Kostas Papadimitriou
        for pattern in settings.RE_USER_EMAIL_PATTERNS:
144 8316698a Sofia Papagiannaki
            if re.match(pattern, user.email):
145 e7cb4085 Kostas Papadimitriou
                return 'email'
146 e7cb4085 Kostas Papadimitriou
147 e7cb4085 Kostas Papadimitriou
        # provider automoderate policy is on
148 e7cb4085 Kostas Papadimitriou
        if user.get_auth_provider().get_automoderate_policy:
149 e7cb4085 Kostas Papadimitriou
            return 'auth_provider_%s' % user.get_auth_provider().module
150 e7cb4085 Kostas Papadimitriou
151 8316698a Sofia Papagiannaki
        return False
152 5ce3ce4f Sofia Papagiannaki
153 e7cb4085 Kostas Papadimitriou
    def get_signup_form(self, provider='local', initial_data=None, **kwargs):
154 0a569195 Sofia Papagiannaki
        """
155 e7cb4085 Kostas Papadimitriou
        Returns a form instance for the type of registration the user chosen.
156 e7cb4085 Kostas Papadimitriou
        This can be either a LocalUserCreationForm for classic method signups
157 e7cb4085 Kostas Papadimitriou
        or ThirdPartyUserCreationForm for users who chosen to signup using a
158 e7cb4085 Kostas Papadimitriou
        federated login method.
159 0a569195 Sofia Papagiannaki
        """
160 0a569195 Sofia Papagiannaki
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
161 5ce3ce4f Sofia Papagiannaki
        suffix = 'UserCreationForm'
162 e7cb4085 Kostas Papadimitriou
        formclass = getattr(forms, '%s%s' % (main, suffix))
163 e7cb4085 Kostas Papadimitriou
        kwargs['provider'] = provider
164 e7cb4085 Kostas Papadimitriou
        return formclass(initial_data, **kwargs)
165 e7cb4085 Kostas Papadimitriou
166 e7cb4085 Kostas Papadimitriou
    def prepare_user(self, user, email_verified=None):
167 e7cb4085 Kostas Papadimitriou
        """
168 e7cb4085 Kostas Papadimitriou
        Initialization of a newly registered user. The method sets email
169 e7cb4085 Kostas Papadimitriou
        verification code. If email_verified is set to True we automatically
170 e7cb4085 Kostas Papadimitriou
        process user through the verification step.
171 e7cb4085 Kostas Papadimitriou
        """
172 e7cb4085 Kostas Papadimitriou
        logger.info("Initializing user registration %s", user.log_display)
173 e7cb4085 Kostas Papadimitriou
174 e7cb4085 Kostas Papadimitriou
        if not email_verified:
175 e7cb4085 Kostas Papadimitriou
            email_verified = settings.SKIP_EMAIL_VERIFICATION
176 e7cb4085 Kostas Papadimitriou
177 e7cb4085 Kostas Papadimitriou
        user.renew_verification_code()
178 e7cb4085 Kostas Papadimitriou
        user.save()
179 e7cb4085 Kostas Papadimitriou
180 e7cb4085 Kostas Papadimitriou
        if email_verified:
181 e7cb4085 Kostas Papadimitriou
            logger.info("Auto verifying user email. %s",
182 e7cb4085 Kostas Papadimitriou
                        user.log_display)
183 e7cb4085 Kostas Papadimitriou
            return self.verify_user(user,
184 e7cb4085 Kostas Papadimitriou
                                    user.verification_code)
185 e7cb4085 Kostas Papadimitriou
186 e7cb4085 Kostas Papadimitriou
        return ActivationResult(self.Result.PENDING_VERIFICATION)
187 e7cb4085 Kostas Papadimitriou
188 e7cb4085 Kostas Papadimitriou
    def verify_user(self, user, verification_code):
189 e7cb4085 Kostas Papadimitriou
        """
190 e7cb4085 Kostas Papadimitriou
        Process user verification using provided verification_code. This
191 e7cb4085 Kostas Papadimitriou
        should take place in user activation view. If no moderation is enabled
192 e7cb4085 Kostas Papadimitriou
        we automatically process user through activation process.
193 e7cb4085 Kostas Papadimitriou
        """
194 e7cb4085 Kostas Papadimitriou
        logger.info("Verifying user: %s", user.log_display)
195 e7cb4085 Kostas Papadimitriou
196 e7cb4085 Kostas Papadimitriou
        if user.email_verified:
197 e7cb4085 Kostas Papadimitriou
            logger.warning("User email already verified: %s",
198 e7cb4085 Kostas Papadimitriou
                           user.log_display)
199 e7cb4085 Kostas Papadimitriou
            msg = astakos_messages.ACCOUNT_ALREADY_VERIFIED
200 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.ERROR, msg)
201 e7cb4085 Kostas Papadimitriou
202 e7cb4085 Kostas Papadimitriou
        if user.verification_code and \
203 e7cb4085 Kostas Papadimitriou
                user.verification_code == verification_code:
204 e7cb4085 Kostas Papadimitriou
            user.email_verified = True
205 e7cb4085 Kostas Papadimitriou
            user.verified_at = datetime.datetime.now()
206 e7cb4085 Kostas Papadimitriou
            # invalidate previous code
207 e7cb4085 Kostas Papadimitriou
            user.renew_verification_code()
208 e7cb4085 Kostas Papadimitriou
            user.save()
209 e7cb4085 Kostas Papadimitriou
            logger.info("User email verified: %s", user.log_display)
210 e7cb4085 Kostas Papadimitriou
        else:
211 e7cb4085 Kostas Papadimitriou
            logger.error("User email verification failed "
212 e7cb4085 Kostas Papadimitriou
                         "(invalid verification code): %s", user.log_display)
213 e7cb4085 Kostas Papadimitriou
            msg = astakos_messages.VERIFICATION_FAILED
214 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.ERROR, msg)
215 e7cb4085 Kostas Papadimitriou
216 e7cb4085 Kostas Papadimitriou
        if not self.moderation_enabled:
217 e7cb4085 Kostas Papadimitriou
            logger.warning("User preaccepted (%s): %s", 'auto_moderation',
218 e7cb4085 Kostas Papadimitriou
                           user.log_display)
219 e7cb4085 Kostas Papadimitriou
            return self.accept_user(user, policy='auto_moderation')
220 e7cb4085 Kostas Papadimitriou
221 e7cb4085 Kostas Papadimitriou
        preaccepted = self._is_preaccepted(user)
222 e7cb4085 Kostas Papadimitriou
        if preaccepted:
223 e7cb4085 Kostas Papadimitriou
            logger.warning("User preaccepted (%s): %s", preaccepted,
224 e7cb4085 Kostas Papadimitriou
                           user.log_display)
225 e7cb4085 Kostas Papadimitriou
            return self.accept_user(user, policy=preaccepted)
226 e7cb4085 Kostas Papadimitriou
227 e7cb4085 Kostas Papadimitriou
        if user.moderated:
228 e7cb4085 Kostas Papadimitriou
            # set moderated to false because accept_user will return error
229 e7cb4085 Kostas Papadimitriou
            # result otherwise.
230 e7cb4085 Kostas Papadimitriou
            user.moderated = False
231 e7cb4085 Kostas Papadimitriou
            return self.accept_user(user, policy='already_moderated')
232 e7cb4085 Kostas Papadimitriou
        else:
233 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.PENDING_MODERATION)
234 e7cb4085 Kostas Papadimitriou
235 e7cb4085 Kostas Papadimitriou
    def accept_user(self, user, policy='manual'):
236 e7cb4085 Kostas Papadimitriou
        logger.info("Moderating user: %s", user.log_display)
237 e7cb4085 Kostas Papadimitriou
        if user.moderated and user.is_active:
238 e7cb4085 Kostas Papadimitriou
            logger.warning("User already accepted, moderation"
239 e7cb4085 Kostas Papadimitriou
                           " skipped: %s", user.log_display)
240 e7cb4085 Kostas Papadimitriou
            msg = _(astakos_messages.ACCOUNT_ALREADY_MODERATED)
241 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.ERROR, msg)
242 e7cb4085 Kostas Papadimitriou
243 e7cb4085 Kostas Papadimitriou
        if not user.email_verified:
244 e7cb4085 Kostas Papadimitriou
            logger.warning("Cannot accept unverified user: %s",
245 e7cb4085 Kostas Papadimitriou
                           user.log_display)
246 e7cb4085 Kostas Papadimitriou
            msg = _(astakos_messages.ACCOUNT_NOT_VERIFIED)
247 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.ERROR, msg)
248 e7cb4085 Kostas Papadimitriou
249 e7cb4085 Kostas Papadimitriou
        # store a snapshot of user details by the time he
250 e7cb4085 Kostas Papadimitriou
        # got accepted.
251 e7cb4085 Kostas Papadimitriou
        if not user.accepted_email:
252 e7cb4085 Kostas Papadimitriou
            user.accepted_email = user.email
253 e7cb4085 Kostas Papadimitriou
        user.accepted_policy = policy
254 e7cb4085 Kostas Papadimitriou
        user.moderated = True
255 e7cb4085 Kostas Papadimitriou
        user.moderated_at = datetime.datetime.now()
256 e7cb4085 Kostas Papadimitriou
        user.moderated_data = json.dumps(user.__dict__,
257 e7cb4085 Kostas Papadimitriou
                                         default=lambda obj:
258 e7cb4085 Kostas Papadimitriou
                                         str(obj))
259 e7cb4085 Kostas Papadimitriou
        user.save()
260 e7cb4085 Kostas Papadimitriou
        qh_sync_user(user)
261 e7cb4085 Kostas Papadimitriou
262 e7cb4085 Kostas Papadimitriou
        if user.is_rejected:
263 e7cb4085 Kostas Papadimitriou
            logger.warning("User has previously been "
264 e7cb4085 Kostas Papadimitriou
                           "rejected, reseting rejection state: %s",
265 e7cb4085 Kostas Papadimitriou
                           user.log_display)
266 e7cb4085 Kostas Papadimitriou
            user.is_rejected = False
267 e7cb4085 Kostas Papadimitriou
            user.rejected_at = None
268 e7cb4085 Kostas Papadimitriou
269 e7cb4085 Kostas Papadimitriou
        user.save()
270 e7cb4085 Kostas Papadimitriou
        logger.info("User accepted: %s", user.log_display)
271 e7cb4085 Kostas Papadimitriou
        self.activate_user(user)
272 e7cb4085 Kostas Papadimitriou
        return ActivationResult(self.Result.ACCEPTED)
273 e7cb4085 Kostas Papadimitriou
274 e7cb4085 Kostas Papadimitriou
    def activate_user(self, user):
275 e7cb4085 Kostas Papadimitriou
        if not user.email_verified:
276 e7cb4085 Kostas Papadimitriou
            msg = _(astakos_messages.ACCOUNT_NOT_VERIFIED)
277 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.ERROR, msg)
278 e7cb4085 Kostas Papadimitriou
279 e7cb4085 Kostas Papadimitriou
        if not user.moderated:
280 e7cb4085 Kostas Papadimitriou
            msg = _(astakos_messages.ACCOUNT_NOT_MODERATED)
281 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.ERROR, msg)
282 e7cb4085 Kostas Papadimitriou
283 e7cb4085 Kostas Papadimitriou
        if user.is_rejected:
284 e7cb4085 Kostas Papadimitriou
            msg = _(astakos_messages.ACCOUNT_REJECTED)
285 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.ERROR, msg)
286 e7cb4085 Kostas Papadimitriou
287 e7cb4085 Kostas Papadimitriou
        if user.is_active:
288 e7cb4085 Kostas Papadimitriou
            msg = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
289 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.ERROR, msg)
290 e7cb4085 Kostas Papadimitriou
291 e7cb4085 Kostas Papadimitriou
        user.is_active = True
292 e7cb4085 Kostas Papadimitriou
        user.deactivated_reason = None
293 e7cb4085 Kostas Papadimitriou
        user.deactivated_at = None
294 e7cb4085 Kostas Papadimitriou
        user.save()
295 e7cb4085 Kostas Papadimitriou
        logger.info("User activated: %s", user.log_display)
296 e7cb4085 Kostas Papadimitriou
        return ActivationResult(self.Result.ACTIVATED)
297 e7cb4085 Kostas Papadimitriou
298 e7cb4085 Kostas Papadimitriou
    def deactivate_user(self, user, reason=''):
299 e7cb4085 Kostas Papadimitriou
        user.is_active = False
300 e7cb4085 Kostas Papadimitriou
        user.deactivated_reason = reason
301 e7cb4085 Kostas Papadimitriou
        if user.is_active:
302 e7cb4085 Kostas Papadimitriou
            user.deactivated_at = datetime.datetime.now()
303 e7cb4085 Kostas Papadimitriou
        user.save()
304 e7cb4085 Kostas Papadimitriou
        logger.info("User deactivated: %s", user.log_display)
305 e7cb4085 Kostas Papadimitriou
        return ActivationResult(self.Result.DEACTIVATED)
306 e7cb4085 Kostas Papadimitriou
307 e7cb4085 Kostas Papadimitriou
    def reject_user(self, user, reason):
308 e7cb4085 Kostas Papadimitriou
        logger.info("Rejecting user: %s", user.log_display)
309 e7cb4085 Kostas Papadimitriou
        if user.moderated:
310 e7cb4085 Kostas Papadimitriou
            logger.warning("User already moderated: %s", user.log_display)
311 e7cb4085 Kostas Papadimitriou
            msg = _(astakos_messages.ACCOUNT_ALREADY_MODERATED)
312 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.ERROR, msg)
313 e7cb4085 Kostas Papadimitriou
314 e7cb4085 Kostas Papadimitriou
        if user.is_active:
315 e7cb4085 Kostas Papadimitriou
            logger.warning("Cannot reject unverified user: %s",
316 e7cb4085 Kostas Papadimitriou
                           user.log_display)
317 e7cb4085 Kostas Papadimitriou
            msg = _(astakos_messages.ACCOUNT_NOT_VERIFIED)
318 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.ERROR, msg)
319 e7cb4085 Kostas Papadimitriou
320 e7cb4085 Kostas Papadimitriou
        if not user.email_verified:
321 e7cb4085 Kostas Papadimitriou
            logger.warning("Cannot reject unverified user: %s",
322 e7cb4085 Kostas Papadimitriou
                           user.log_display)
323 e7cb4085 Kostas Papadimitriou
            msg = _(astakos_messages.ACCOUNT_NOT_VERIFIED)
324 e7cb4085 Kostas Papadimitriou
            return ActivationResult(self.Result.ERROR, msg)
325 e7cb4085 Kostas Papadimitriou
326 e7cb4085 Kostas Papadimitriou
        user.moderated = True
327 e7cb4085 Kostas Papadimitriou
        user.moderated_at = datetime.datetime.now()
328 e7cb4085 Kostas Papadimitriou
        user.moderated_data = json.dumps(user.__dict__,
329 e7cb4085 Kostas Papadimitriou
                                         default=lambda obj:
330 e7cb4085 Kostas Papadimitriou
                                         str(obj))
331 e7cb4085 Kostas Papadimitriou
        user.is_rejected = True
332 e7cb4085 Kostas Papadimitriou
        user.rejected_reason = reason
333 e7cb4085 Kostas Papadimitriou
        logger.info("User rejected: %s", user.log_display)
334 e7cb4085 Kostas Papadimitriou
        return ActivationResult(self.Result.REJECTED)
335 e7cb4085 Kostas Papadimitriou
336 e7cb4085 Kostas Papadimitriou
    def handle_registration(self, user, email_verified=False):
337 e7cb4085 Kostas Papadimitriou
        logger.info("Handling new user registration: %s", user.log_display)
338 e7cb4085 Kostas Papadimitriou
        return self.prepare_user(user, email_verified=email_verified)
339 e7cb4085 Kostas Papadimitriou
340 e7cb4085 Kostas Papadimitriou
    def handle_verification(self, user, activation_code):
341 e7cb4085 Kostas Papadimitriou
        logger.info("Handling user email verirfication: %s", user.log_display)
342 e7cb4085 Kostas Papadimitriou
        return self.verify_user(user, activation_code)
343 e7cb4085 Kostas Papadimitriou
344 e7cb4085 Kostas Papadimitriou
    def handle_moderation(self, user, accept=True, reject_reason=None):
345 e7cb4085 Kostas Papadimitriou
        logger.info("Handling user moderation (%r): %s",
346 e7cb4085 Kostas Papadimitriou
                    accept, user.log_display)
347 e7cb4085 Kostas Papadimitriou
        if accept:
348 e7cb4085 Kostas Papadimitriou
            return self.accept_user(user)
349 e7cb4085 Kostas Papadimitriou
        else:
350 e7cb4085 Kostas Papadimitriou
            return self.reject_user(user, reject_reason)
351 e7cb4085 Kostas Papadimitriou
352 e7cb4085 Kostas Papadimitriou
    def send_user_verification_email(self, user):
353 e7cb4085 Kostas Papadimitriou
        if user.is_active:
354 e7cb4085 Kostas Papadimitriou
            raise Exception("User already active")
355 e7cb4085 Kostas Papadimitriou
356 e7cb4085 Kostas Papadimitriou
        # invalidate previous code
357 e7cb4085 Kostas Papadimitriou
        user.renew_verification_code()
358 e7cb4085 Kostas Papadimitriou
        user.save()
359 e7cb4085 Kostas Papadimitriou
        functions.send_verification(user)
360 e7cb4085 Kostas Papadimitriou
        user.activation_sent = datetime.datetime.now()
361 e7cb4085 Kostas Papadimitriou
        user.save()
362 e7cb4085 Kostas Papadimitriou
363 e7cb4085 Kostas Papadimitriou
    def send_result_notifications(self, result, user):
364 e7cb4085 Kostas Papadimitriou
        """
365 e7cb4085 Kostas Papadimitriou
        Send corresponding notifications based on the status of activation
366 e7cb4085 Kostas Papadimitriou
        result.
367 e7cb4085 Kostas Papadimitriou

368 e7cb4085 Kostas Papadimitriou
        Result.PENDING_VERIRFICATION
369 e7cb4085 Kostas Papadimitriou
            * Send user the email verification url
370 e7cb4085 Kostas Papadimitriou

371 e7cb4085 Kostas Papadimitriou
        Result.PENDING_MODERATION
372 e7cb4085 Kostas Papadimitriou
            * Notify admin for account moderation
373 e7cb4085 Kostas Papadimitriou

374 e7cb4085 Kostas Papadimitriou
        Result.ACCEPTED
375 e7cb4085 Kostas Papadimitriou
            * Send user greeting notification
376 e7cb4085 Kostas Papadimitriou

377 e7cb4085 Kostas Papadimitriou
        Result.REJECTED
378 e7cb4085 Kostas Papadimitriou
            * Send nothing
379 e7cb4085 Kostas Papadimitriou
        """
380 e7cb4085 Kostas Papadimitriou
        if result.status == self.Result.PENDING_VERIFICATION:
381 e7cb4085 Kostas Papadimitriou
            logger.info("Sending notifications for user"
382 e7cb4085 Kostas Papadimitriou
                        " creation: %s", user.log_display)
383 e7cb4085 Kostas Papadimitriou
            # email user that contains the activation link
384 e7cb4085 Kostas Papadimitriou
            self.send_user_verification_email(user)
385 e7cb4085 Kostas Papadimitriou
            # TODO: optionally notify admins for new accounts
386 e7cb4085 Kostas Papadimitriou
387 e7cb4085 Kostas Papadimitriou
        if result.status == self.Result.PENDING_MODERATION:
388 e7cb4085 Kostas Papadimitriou
            logger.info("Sending notifications for user"
389 e7cb4085 Kostas Papadimitriou
                        " verification: %s", user.log_display)
390 e7cb4085 Kostas Papadimitriou
            functions.send_account_pending_moderation_notification(user,
391 e7cb4085 Kostas Papadimitriou
                                        self.pending_moderation_template_name)
392 e7cb4085 Kostas Papadimitriou
            # TODO: notify user
393 e7cb4085 Kostas Papadimitriou
394 e7cb4085 Kostas Papadimitriou
        if result.status == self.Result.ACCEPTED:
395 e7cb4085 Kostas Papadimitriou
            logger.info("Sending notifications for user"
396 e7cb4085 Kostas Papadimitriou
                        " moderation: %s", user.log_display)
397 e7cb4085 Kostas Papadimitriou
            functions.send_account_activated_notification(user,
398 e7cb4085 Kostas Papadimitriou
                                         self.activated_email_template_name)
399 e7cb4085 Kostas Papadimitriou
            functions.send_greeting(user,
400 e7cb4085 Kostas Papadimitriou
                                    self.greeting_template_name)
401 e7cb4085 Kostas Papadimitriou
            # TODO: notify admins
402 e7cb4085 Kostas Papadimitriou
403 e7cb4085 Kostas Papadimitriou
        if result.status == self.Result.REJECTED:
404 e7cb4085 Kostas Papadimitriou
            logger.info("Sending notifications for user"
405 e7cb4085 Kostas Papadimitriou
                        " rejection: %s", user.log_display)
406 e7cb4085 Kostas Papadimitriou
            # TODO: notify user and admins
407 8316698a Sofia Papagiannaki
408 5ce3ce4f Sofia Papagiannaki
409 0a569195 Sofia Papagiannaki
class InvitationsBackend(ActivationBackend):
410 890b0eaf Sofia Papagiannaki
    """
411 18ffbee1 Sofia Papagiannaki
    A activation backend which implements the following workflow: a user
412 e7cb4085 Kostas Papadimitriou
    supplies the necessary registation information, if the request contains a
413 e7cb4085 Kostas Papadimitriou
    valid inivation code the user is automatically activated otherwise an
414 e7cb4085 Kostas Papadimitriou
    inactive user account is created and the user is going to receive an email
415 e7cb4085 Kostas Papadimitriou
    as soon as an administrator activates his/her account.
416 890b0eaf Sofia Papagiannaki
    """
417 bef3bf46 Kostas Papadimitriou
418 0d48fd8f Kostas Papadimitriou
    def get_signup_form(self, invitation, provider='local', initial_data=None,
419 e7cb4085 Kostas Papadimitriou
                        instance=None):
420 15efc749 Sofia Papagiannaki
        """
421 b669d9c0 Sofia Papagiannaki
        Returns a form instance of the relevant class
422 5ce3ce4f Sofia Papagiannaki

423 0a569195 Sofia Papagiannaki
        raises Invitation.DoesNotExist and ValueError if invitation is consumed
424 0a569195 Sofia Papagiannaki
        or invitation username is reserved.
425 15efc749 Sofia Papagiannaki
        """
426 e7cb4085 Kostas Papadimitriou
        self.invitation = invitation
427 0a569195 Sofia Papagiannaki
        prefix = 'Invited' if invitation else ''
428 d271dd21 Kostas Papadimitriou
        main = provider.capitalize() if provider == 'local' else 'ThirdParty'
429 5ce3ce4f Sofia Papagiannaki
        suffix = 'UserCreationForm'
430 e7cb4085 Kostas Papadimitriou
        formclass = getattr(forms, '%s%s%s' % (prefix, main, suffix))
431 e7cb4085 Kostas Papadimitriou
        return formclass(initial_data, instance=instance)
432 bef3bf46 Kostas Papadimitriou
433 e7cb4085 Kostas Papadimitriou
    def get_signup_initial_data(self, request, provider):
434 890b0eaf Sofia Papagiannaki
        """
435 e7cb4085 Kostas Papadimitriou
        Returns the necassary activation form depending the user is invited or
436 e7cb4085 Kostas Papadimitriou
        not.
437 bef3bf46 Kostas Papadimitriou

438 890b0eaf Sofia Papagiannaki
        Throws Invitation.DoesNotExist in case ``code`` is not valid.
439 890b0eaf Sofia Papagiannaki
        """
440 15efc749 Sofia Papagiannaki
        invitation = self.invitation
441 65d85494 Sofia Papagiannaki
        initial_data = None
442 890b0eaf Sofia Papagiannaki
        if request.method == 'GET':
443 15efc749 Sofia Papagiannaki
            if invitation:
444 65d85494 Sofia Papagiannaki
                # create a tmp user with the invitation realname
445 65d85494 Sofia Papagiannaki
                # to extract first and last name
446 5ce3ce4f Sofia Papagiannaki
                u = AstakosUser(realname=invitation.realname)
447 5ce3ce4f Sofia Papagiannaki
                initial_data = {'email': invitation.username,
448 5ce3ce4f Sofia Papagiannaki
                                'inviter': invitation.inviter.realname,
449 5ce3ce4f Sofia Papagiannaki
                                'first_name': u.first_name,
450 5ce3ce4f Sofia Papagiannaki
                                'last_name': u.last_name,
451 5ce3ce4f Sofia Papagiannaki
                                'provider': provider}
452 15efc749 Sofia Papagiannaki
        else:
453 15efc749 Sofia Papagiannaki
            if provider == request.POST.get('provider', ''):
454 15efc749 Sofia Papagiannaki
                initial_data = request.POST
455 15efc749 Sofia Papagiannaki
        return initial_data
456 bef3bf46 Kostas Papadimitriou
457 890b0eaf Sofia Papagiannaki
    def _is_preaccepted(self, user):
458 890b0eaf Sofia Papagiannaki
        """
459 e7cb4085 Kostas Papadimitriou
        Extends _is_preaccepted and if there is a valid, not-consumed
460 e7cb4085 Kostas Papadimitriou
        invitation code for the specific user returns True else returns False.
461 890b0eaf Sofia Papagiannaki
        """
462 e7cb4085 Kostas Papadimitriou
        preaccepted = super(InvitationsBackend, self)._is_preaccepted(user)
463 e7cb4085 Kostas Papadimitriou
        if preaccepted:
464 e7cb4085 Kostas Papadimitriou
            return preaccepted
465 5ed6816e Sofia Papagiannaki
        invitation = self.invitation
466 890b0eaf Sofia Papagiannaki
        if not invitation:
467 e7cb4085 Kostas Papadimitriou
            if not self.moderation_enabled:
468 e7cb4085 Kostas Papadimitriou
                return 'auto_moderation'
469 5ed6816e Sofia Papagiannaki
        if invitation.username == user.email and not invitation.is_consumed:
470 5ed6816e Sofia Papagiannaki
            invitation.consume()
471 e7cb4085 Kostas Papadimitriou
            return 'invitation'
472 890b0eaf Sofia Papagiannaki
        return False
473 bef3bf46 Kostas Papadimitriou
474 5ce3ce4f Sofia Papagiannaki
475 0a569195 Sofia Papagiannaki
class SimpleBackend(ActivationBackend):
476 890b0eaf Sofia Papagiannaki
    """
477 e7cb4085 Kostas Papadimitriou
    The common activation backend.
478 890b0eaf Sofia Papagiannaki
    """
479 9d20fe23 Kostas Papadimitriou
480 e7cb4085 Kostas Papadimitriou
# shortcut
481 e7cb4085 Kostas Papadimitriou
ActivationResultStatus = ActivationBackend.Result
482 8f5a3a06 Sofia Papagiannaki
483 5ce3ce4f Sofia Papagiannaki
484 8f5a3a06 Sofia Papagiannaki
class ActivationResult(object):
485 8f5a3a06 Sofia Papagiannaki
486 e7cb4085 Kostas Papadimitriou
    MESSAGE_BY_STATUS = {
487 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.PENDING_VERIFICATION:
488 e7cb4085 Kostas Papadimitriou
        _(astakos_messages.VERIFICATION_SENT),
489 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.PENDING_MODERATION:
490 e7cb4085 Kostas Papadimitriou
        _(astakos_messages.NOTIFICATION_SENT),
491 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.ACCEPTED:
492 e7cb4085 Kostas Papadimitriou
        _(astakos_messages.ACCOUNT_ACTIVATED),
493 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.ACTIVATED:
494 e7cb4085 Kostas Papadimitriou
        _(astakos_messages.ACCOUNT_ACTIVATED),
495 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.DEACTIVATED:
496 e7cb4085 Kostas Papadimitriou
        _(astakos_messages.ACCOUNT_DEACTIVATED),
497 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.ERROR:
498 e7cb4085 Kostas Papadimitriou
        _(astakos_messages.GENERIC_ERROR)
499 e7cb4085 Kostas Papadimitriou
    }
500 e7cb4085 Kostas Papadimitriou
501 e7cb4085 Kostas Papadimitriou
    STATUS_DISPLAY = {
502 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.PENDING_VERIFICATION: 'PENDING_VERIFICATION',
503 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.PENDING_MODERATION: 'PENDING_MODERATION',
504 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.ACCEPTED: 'ACCEPTED',
505 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.ACTIVATED: 'ACTIVATED',
506 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.DEACTIVATED: 'DEACTIVATED',
507 e7cb4085 Kostas Papadimitriou
        ActivationResultStatus.ERROR: 'ERROR'
508 e7cb4085 Kostas Papadimitriou
    }
509 5ce3ce4f Sofia Papagiannaki
510 e7cb4085 Kostas Papadimitriou
    def __init__(self, status, message=None):
511 e7cb4085 Kostas Papadimitriou
        if message is None:
512 e7cb4085 Kostas Papadimitriou
            message = self.MESSAGE_BY_STATUS.get(status)
513 e7cb4085 Kostas Papadimitriou
514 e7cb4085 Kostas Papadimitriou
        self.message = message
515 e7cb4085 Kostas Papadimitriou
        self.status = status
516 8f5a3a06 Sofia Papagiannaki
517 e7cb4085 Kostas Papadimitriou
    def status_display(self):
518 e7cb4085 Kostas Papadimitriou
        return self.STATUS_DISPLAY.get(self.status)
519 8f5a3a06 Sofia Papagiannaki
520 e7cb4085 Kostas Papadimitriou
    def __repr__(self):
521 e7cb4085 Kostas Papadimitriou
        return "ActivationResult [%s]: %s" % (self.status_display(),
522 e7cb4085 Kostas Papadimitriou
                                              self.message)
523 5ce3ce4f Sofia Papagiannaki
524 e7cb4085 Kostas Papadimitriou
    def is_error(self):
525 e7cb4085 Kostas Papadimitriou
        return self.status == ActivationResultStatus.ERROR