Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / activation_backends.py @ 27e51b28

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 f72ba65d Giorgos Korfiatis
from astakos.im.quotas import qh_sync_new_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 1808f7bc Giorgos Korfiatis
    >>>     user = form.create_user()
94 e7cb4085 Kostas Papadimitriou
    >>>     activation = backend.handle_registration(user)
95 e7cb4085 Kostas Papadimitriou
    >>>     # activation.status is one of backend.Result.{*} activation result
96 e7cb4085 Kostas Papadimitriou
    >>>     # types
97 e7cb4085 Kostas Papadimitriou
    >>>
98 e7cb4085 Kostas Papadimitriou
    >>>     # sending activation notifications is not done automatically
99 e7cb4085 Kostas Papadimitriou
    >>>     # we need to call send_result_notifications
100 e7cb4085 Kostas Papadimitriou
    >>>     backend.send_result_notifications(activation)
101 e7cb4085 Kostas Papadimitriou
    >>>     return HttpResponse(activation.message)
102 e7cb4085 Kostas Papadimitriou
    """
103 e7cb4085 Kostas Papadimitriou
104 e7cb4085 Kostas Papadimitriou
    verification_template_name = 'im/activation_email.txt'
105 e7cb4085 Kostas Papadimitriou
    greeting_template_name = 'im/welcome_email.txt'
106 e7cb4085 Kostas Papadimitriou
    pending_moderation_template_name = \
107 e7cb4085 Kostas Papadimitriou
        'im/account_pending_moderation_notification.txt'
108 e7cb4085 Kostas Papadimitriou
    activated_email_template_name = 'im/account_activated_notification.txt'
109 e7cb4085 Kostas Papadimitriou
110 e7cb4085 Kostas Papadimitriou
    class Result:
111 e7cb4085 Kostas Papadimitriou
        # user created, email verification sent
112 e7cb4085 Kostas Papadimitriou
        PENDING_VERIFICATION = 1
113 e7cb4085 Kostas Papadimitriou
        # email verified
114 e7cb4085 Kostas Papadimitriou
        PENDING_MODERATION = 2
115 e7cb4085 Kostas Papadimitriou
        # user moderated
116 e7cb4085 Kostas Papadimitriou
        ACCEPTED = 3
117 e7cb4085 Kostas Papadimitriou
        # user rejected
118 e7cb4085 Kostas Papadimitriou
        REJECTED = 4
119 e7cb4085 Kostas Papadimitriou
        # inactive user activated
120 e7cb4085 Kostas Papadimitriou
        ACTIVATED = 5
121 e7cb4085 Kostas Papadimitriou
        # active user deactivated
122 e7cb4085 Kostas Papadimitriou
        DEACTIVATED = 6
123 e7cb4085 Kostas Papadimitriou
        # something went wrong
124 e7cb4085 Kostas Papadimitriou
        ERROR = -1
125 e7cb4085 Kostas Papadimitriou
126 e7cb4085 Kostas Papadimitriou
    def __init__(self, moderation_enabled):
127 e7cb4085 Kostas Papadimitriou
        self.moderation_enabled = moderation_enabled
128 5ce3ce4f Sofia Papagiannaki
129 8316698a Sofia Papagiannaki
    def _is_preaccepted(self, user):
130 e7cb4085 Kostas Papadimitriou
        """
131 e7cb4085 Kostas Papadimitriou
        Decide whether user should be automatically moderated. The method gets
132 e7cb4085 Kostas Papadimitriou
        called only when self.moderation_enabled is set to True.
133 e7cb4085 Kostas Papadimitriou

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

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

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

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

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

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

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