Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / util.py @ fb9ba8d5

History | View | Annotate | Download (10.1 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import logging
35
import datetime
36
import time
37
import urllib
38

    
39
from urlparse import urlparse
40
from datetime import tzinfo, timedelta
41

    
42
from django.http import HttpResponse, HttpResponseBadRequest, urlencode, \
43
                        HttpResponseRedirect
44
from django.template import RequestContext
45
from django.contrib.auth import authenticate
46
from django.core.urlresolvers import reverse
47
from django.shortcuts import redirect
48
from django.core.exceptions import ValidationError, ObjectDoesNotExist
49
from django.utils.translation import ugettext as _
50
from django.core.urlresolvers import reverse
51

    
52
from astakos.im.models import AstakosUser, Invitation
53
from astakos.im.functions import login
54
from astakos.im import settings
55

    
56
import astakos.im.messages as astakos_messages
57

    
58
logger = logging.getLogger(__name__)
59

    
60

    
61
class UTC(tzinfo):
62
    def utcoffset(self, dt):
63
        return timedelta(0)
64

    
65
    def tzname(self, dt):
66
        return 'UTC'
67

    
68
    def dst(self, dt):
69
        return timedelta(0)
70

    
71

    
72
def isoformat(d):
73
    """Return an ISO8601 date string that includes a timezone."""
74

    
75
    return d.replace(tzinfo=UTC()).isoformat()
76

    
77

    
78
def epoch(datetime):
79
    return int(time.mktime(datetime.timetuple()) * 1000)
80

    
81

    
82
def get_context(request, extra_context=None, **kwargs):
83
    extra_context = extra_context or {}
84
    extra_context.update(kwargs)
85
    return RequestContext(request, extra_context)
86

    
87

    
88
def get_invitation(request):
89
    """
90
    Returns the invitation identified by the ``code``.
91

92
    Raises ValueError if the invitation is consumed or there is another account
93
    associated with this email.
94
    """
95
    code = request.GET.get('code')
96
    if request.method == 'POST':
97
        code = request.POST.get('code')
98
    if not code:
99
        return
100
    invitation = Invitation.objects.get(code=code)
101
    if invitation.is_consumed:
102
        raise ValueError(_(astakos_messages.INVITATION_CONSUMED_ERR))
103
    if reserved_email(invitation.username):
104
        email = invitation.username
105
        raise ValueError(_(astakos_messages.EMAIL_RESERVED) % locals())
106
    return invitation
107

    
108
def restrict_next(url, domain=None, allowed_schemes=()):
109
    """
110
    Return url if having the supplied ``domain`` (if present) or one of the ``allowed_schemes``.
111
    Otherwise return None.
112

113
    >>> print restrict_next('/im/feedback', '.okeanos.grnet.gr')
114
    /im/feedback
115
    >>> print restrict_next('pithos.okeanos.grnet.gr/im/feedback', '.okeanos.grnet.gr')
116
    //pithos.okeanos.grnet.gr/im/feedback
117
    >>> print restrict_next('https://pithos.okeanos.grnet.gr/im/feedback', '.okeanos.grnet.gr')
118
    https://pithos.okeanos.grnet.gr/im/feedback
119
    >>> print restrict_next('pithos://127.0.0.1', '.okeanos.grnet.gr')
120
    None
121
    >>> print restrict_next('pithos://127.0.0.1', '.okeanos.grnet.gr', allowed_schemes=('pithos'))
122
    pithos://127.0.0,1
123
    >>> print restrict_next('node1.example.com', '.okeanos.grnet.gr')
124
    None
125
    >>> print restrict_next('//node1.example.com', '.okeanos.grnet.gr')
126
    None
127
    >>> print restrict_next('https://node1.example.com', '.okeanos.grnet.gr')
128
    None
129
    >>> print restrict_next('https://node1.example.com')
130
    https://node1.example.com
131
    >>> print restrict_next('//node1.example.com')
132
    //node1.example.com
133
    >>> print restrict_next('node1.example.com')
134
    //node1.example.com
135
    """
136
    if not url:
137
        return
138
    parts = urlparse(url, scheme='http')
139
    if not parts.netloc and not parts.path.startswith('/'):
140
        # fix url if does not conforms RFC 1808
141
        url = '//%s' % url
142
        parts = urlparse(url, scheme='http')
143
    # TODO more scientific checks?
144
    if not parts.netloc:    # internal url
145
        return url
146
    elif not domain:
147
        return url
148
    elif parts.netloc.endswith(domain):
149
        return url
150
    elif parts.scheme in allowed_schemes:
151
        return url
152

    
153
def prepare_response(request, user, next='', renew=False):
154
    """Return the unique username and the token
155
       as 'X-Auth-User' and 'X-Auth-Token' headers,
156
       or redirect to the URL provided in 'next'
157
       with the 'user' and 'token' as parameters.
158

159
       Reissue the token even if it has not yet
160
       expired, if the 'renew' parameter is present
161
       or user has not a valid token.
162
    """
163
    renew = renew or (not user.auth_token)
164
    renew = renew or user.token_expired()
165
    if renew:
166
        user.renew_token(
167
            flush_sessions=True,
168
            current_key=request.session.session_key
169
        )
170
        try:
171
            user.save()
172
        except ValidationError, e:
173
            return HttpResponseBadRequest(e)
174

    
175
    next = restrict_next(next, domain=settings.COOKIE_DOMAIN)
176

    
177
    if settings.FORCE_PROFILE_UPDATE and \
178
            not user.is_verified and not user.is_superuser:
179
        params = ''
180
        if next:
181
            params = '?' + urlencode({'next': next})
182
        next = reverse('edit_profile') + params
183

    
184
    response = HttpResponse()
185

    
186
    # authenticate before login
187
    user = authenticate(email=user.email, auth_token=user.auth_token)
188
    login(request, user)
189
    request.session.set_expiry(user.auth_token_expires)
190

    
191
    if not next:
192
        next = settings.LOGIN_SUCCESS_URL
193

    
194
    response['Location'] = next
195
    response.status_code = 302
196
    return response
197

    
198
class lazy_string(object):
199
    def __init__(self, function, *args, **kwargs):
200
        self.function = function
201
        self.args = args
202
        self.kwargs = kwargs
203

    
204
    def __str__(self):
205
        if not hasattr(self, 'str'):
206
            self.str = self.function(*self.args, **self.kwargs)
207
        return self.str
208

    
209

    
210
def reverse_lazy(*args, **kwargs):
211
    return lazy_string(reverse, *args, **kwargs)
212

    
213

    
214
def reserved_email(email):
215
    return AstakosUser.objects.user_exists(email)
216

    
217

    
218
def reserved_verified_email(email):
219
    return AstakosUser.objects.verified_user_exists(email)
220

    
221

    
222
def get_query(request):
223
    try:
224
        return request.__getattribute__(request.method)
225
    except AttributeError:
226
        return {}
227

    
228
def get_properties(obj):
229
    def get_class_attr(_class, attr):
230
        try:
231
            return getattr(_class, attr)
232
        except AttributeError:
233
            return
234

    
235
    return (i for i in vars(obj.__class__) \
236
        if isinstance(get_class_attr(obj.__class__, i), property))
237

    
238
def model_to_dict(obj, exclude=['AutoField', 'ForeignKey', 'OneToOneField'],
239
                  include_empty=True):
240
    '''
241
        serialize model object to dict with related objects
242

243
        author: Vadym Zakovinko <vp@zakovinko.com>
244
        date: January 31, 2011
245
        http://djangosnippets.org/snippets/2342/
246
    '''
247
    tree = {}
248
    for field_name in obj._meta.get_all_field_names():
249
        try:
250
            field = getattr(obj, field_name)
251
        except (ObjectDoesNotExist, AttributeError):
252
            continue
253

    
254
        if field.__class__.__name__ in ['RelatedManager', 'ManyRelatedManager']:
255
            if field.model.__name__ in exclude:
256
                continue
257

    
258
            if field.__class__.__name__ == 'ManyRelatedManager':
259
                exclude.append(obj.__class__.__name__)
260
            subtree = []
261
            for related_obj in getattr(obj, field_name).all():
262
                value = model_to_dict(related_obj, exclude=exclude)
263
                if value or include_empty:
264
                    subtree.append(value)
265
            if subtree or include_empty:
266
                tree[field_name] = subtree
267
            continue
268

    
269
        field = obj._meta.get_field_by_name(field_name)[0]
270
        if field.__class__.__name__ in exclude:
271
            continue
272

    
273
        if field.__class__.__name__ == 'RelatedObject':
274
            exclude.append(field.model.__name__)
275
            tree[field_name] = model_to_dict(getattr(obj, field_name),
276
                                             exclude=exclude)
277
            continue
278

    
279
        value = getattr(obj, field_name)
280
        if field.__class__.__name__ == 'ForeignKey':
281
            value = unicode(value) if value is not None else value
282
        if value or include_empty:
283
            tree[field_name] = value
284
    properties = list(get_properties(obj))
285
    for p in properties:
286
       tree[p] = getattr(obj, p)
287
    tree['str_repr'] = obj.__str__()
288

    
289
    return tree
290

    
291
def login_url(request):
292
    attrs = {}
293
    for attr in ['login', 'key', 'code']:
294
        val = request.REQUEST.get(attr, None)
295
        if val:
296
            attrs[attr] = val
297
    return "%s?%s" % (reverse('login'), urllib.urlencode(attrs))
298

    
299

    
300
def redirect_back(request, default='index'):
301
    """
302
    Redirect back to referer if safe and possible.
303
    """
304
    referer = request.META.get('HTTP_REFERER')
305

    
306
    safedomain = settings.BASE_URL.replace("https://", "").replace(
307
        "http://", "")
308
    safe = restrict_next(referer, safedomain)
309
    # avoid redirect loop
310
    loops = referer == request.get_full_path()
311
    if referer and safe and not loops:
312
        return redirect(referer)
313
    return redirect(reverse(default))