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

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
34
import logging
35
import datetime
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
36
import time
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
37
import urllib
38

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

42
from django.http import HttpResponse, HttpResponseBadRequest, urlencode
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
43
from django.template import RequestContext
44
from django.contrib.auth import authenticate
45
from django.core.urlresolvers import reverse
Olga Brani's avatar
Olga Brani committed
46
from django.core.exceptions import ValidationError, ObjectDoesNotExist
Olga Brani's avatar
Olga Brani committed
47
48
from django.utils.translation import ugettext as _

49
from astakos.im.models import AstakosUser, Invitation
50
from astakos.im.settings import (
51
    COOKIE_DOMAIN, FORCE_PROFILE_UPDATE)
52
from astakos.im.functions import login
53

Olga Brani's avatar
Olga Brani committed
54
55
import astakos.im.messages as astakos_messages

56
57
logger = logging.getLogger(__name__)

58

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

63
64
    def tzname(self, dt):
        return 'UTC'
65

66
67
    def dst(self, dt):
        return timedelta(0)
68

69

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

73
    return d.replace(tzinfo=UTC()).isoformat()
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
74

75

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
76
def epoch(datetime):
77
78
    return int(time.mktime(datetime.timetuple()) * 1000)

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
79

80
81
def get_context(request, extra_context=None, **kwargs):
    extra_context = extra_context or {}
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
82
83
    extra_context.update(kwargs)
    return RequestContext(request, extra_context)
84

85

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

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

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

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

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

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

173
    next = restrict_next(next, domain=COOKIE_DOMAIN)
174

Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
175
    if FORCE_PROFILE_UPDATE and not user.is_verified and not user.is_superuser:
176
177
178
        params = ''
        if next:
            params = '?' + urlencode({'next': next})
Sofia Papagiannaki's avatar
Sofia Papagiannaki committed
179
        next = reverse('edit_profile') + params
180

181
    response = HttpResponse()
182

root's avatar
root committed
183
184
185
    # authenticate before login
    user = authenticate(email=user.email, auth_token=user.auth_token)
    login(request, user)
186
    request.session.set_expiry(user.auth_token_expires)
187

188
189
    if not next:
        next = reverse('astakos.im.views.index')
190

191
192
    response['Location'] = next
    response.status_code = 302
root's avatar
root committed
193
    return response
194

195
196
class lazy_string(object):
    def __init__(self, function, *args, **kwargs):
197
198
199
200
        self.function = function
        self.args = args
        self.kwargs = kwargs

201
202
    def __str__(self):
        if not hasattr(self, 'str'):
203
            self.str = self.function(*self.args, **self.kwargs)
204
205
        return self.str

206

207
208
209
def reverse_lazy(*args, **kwargs):
    return lazy_string(reverse, *args, **kwargs)

210

211
def reserved_email(email):
212
    return AstakosUser.objects.user_exists(email)
213

214
215

def get_query(request):
216
217
218
    try:
        return request.__getattribute__(request.method)
    except AttributeError:
Olga Brani's avatar
Olga Brani committed
219
220
        return {}

221
222
223
def get_properties(obj):
    return (i for i in vars(obj.__class__) \
        if isinstance(getattr(obj.__class__, i), property))
Olga Brani's avatar
Olga Brani committed
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270

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

        author: Vadym Zakovinko <vp@zakovinko.com>
        date: January 31, 2011
        http://djangosnippets.org/snippets/2342/
    '''
    tree = {}
    for field_name in obj._meta.get_all_field_names():
        try:
            field = getattr(obj, field_name)
        except (ObjectDoesNotExist, AttributeError):
            continue

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

            if field.__class__.__name__ == 'ManyRelatedManager':
                exclude.append(obj.__class__.__name__)
            subtree = []
            for related_obj in getattr(obj, field_name).all():
                value = model_to_dict(related_obj, exclude=exclude)
                if value or include_empty:
                    subtree.append(value)
            if subtree or include_empty:
                tree[field_name] = subtree
            continue

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

        if field.__class__.__name__ == 'RelatedObject':
            exclude.append(field.model.__name__)
            tree[field_name] = model_to_dict(getattr(obj, field_name),
                                             exclude=exclude)
            continue

        value = getattr(obj, field_name)
        if field.__class__.__name__ == 'ForeignKey':
            value = unicode(value) if value is not None else value
        if value or include_empty:
            tree[field_name] = value
271
272
273
274
    properties = list(get_properties(obj))
    for p in properties:
       tree[p] = getattr(obj, p)
    tree['str_repr'] = obj.__str__()
Olga Brani's avatar
Olga Brani committed
275
276

    return tree
Kostas Papadimitriou's avatar
Kostas Papadimitriou committed
277
278
279
280
281
282
283
284

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