Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 9202a57d

History | View | Annotate | Download (19.8 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 e066eedb Giorgos Korfiatis
from astakos.im import models
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 d1a767f7 Olga Brani
import astakos.im.messages as astakos_messages
44 0905ccd2 Sofia Papagiannaki
45 e7cb4085 Kostas Papadimitriou
import datetime
46 5ed6816e Sofia Papagiannaki
import logging
47 8316698a Sofia Papagiannaki
import re
48 e7cb4085 Kostas Papadimitriou
import json
49 5ed6816e Sofia Papagiannaki
50 e015e9e6 Sofia Papagiannaki
logger = logging.getLogger(__name__)
51 e015e9e6 Sofia Papagiannaki
52 5ce3ce4f Sofia Papagiannaki
53 e7cb4085 Kostas Papadimitriou
def get_backend():
54 0905ccd2 Sofia Papagiannaki
    """
55 18ffbee1 Sofia Papagiannaki
    Returns an instance of an activation backend,
56 890b0eaf Sofia Papagiannaki
    according to the INVITATIONS_ENABLED setting
57 0dd46210 Sofia Papagiannaki
    (if True returns ``astakos.im.activation_backends.InvitationsBackend``
58 0dd46210 Sofia Papagiannaki
    and if False
59 8f5a3a06 Sofia Papagiannaki
    returns ``astakos.im.activation_backends.SimpleBackend``).
60 bef3bf46 Kostas Papadimitriou

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

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

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

365 e7cb4085 Kostas Papadimitriou
        Result.PENDING_VERIRFICATION
366 e7cb4085 Kostas Papadimitriou
            * Send user the email verification url
367 e7cb4085 Kostas Papadimitriou

368 e7cb4085 Kostas Papadimitriou
        Result.PENDING_MODERATION
369 e7cb4085 Kostas Papadimitriou
            * Notify admin for account moderation
370 e7cb4085 Kostas Papadimitriou

371 e7cb4085 Kostas Papadimitriou
        Result.ACCEPTED
372 e7cb4085 Kostas Papadimitriou
            * Send user greeting notification
373 e7cb4085 Kostas Papadimitriou

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

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

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