Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / util.py @ 43332a76

History | View | Annotate | Download (9.5 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
from django.template import RequestContext
44
from django.contrib.auth import authenticate
45
from django.core.urlresolvers import reverse
46
from django.core.exceptions import ValidationError, ObjectDoesNotExist
47
from django.utils.translation import ugettext as _
48

    
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

    
54
import astakos.im.messages as astakos_messages
55

    
56
logger = logging.getLogger(__name__)
57

    
58

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

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

    
66
    def dst(self, dt):
67
        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()
74

    
75

    
76
def epoch(datetime):
77
    return int(time.mktime(datetime.timetuple()) * 1000)
78

    
79

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

    
85

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

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

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

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

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

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

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

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

    
181
    response = HttpResponse()
182

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

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

    
191
    response['Location'] = next
192
    response.status_code = 302
193
    return response
194

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

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

    
206

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

    
210

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

    
214

    
215
def reserved_verified_email(email):
216
    return AstakosUser.objects.verified_user_exists(email)
217

    
218

    
219
def get_query(request):
220
    try:
221
        return request.__getattribute__(request.method)
222
    except AttributeError:
223
        return {}
224

    
225
def get_properties(obj):
226
    return (i for i in vars(obj.__class__) \
227
        if isinstance(getattr(obj.__class__, i), property))
228

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

234
        author: Vadym Zakovinko <vp@zakovinko.com>
235
        date: January 31, 2011
236
        http://djangosnippets.org/snippets/2342/
237
    '''
238
    tree = {}
239
    for field_name in obj._meta.get_all_field_names():
240
        try:
241
            field = getattr(obj, field_name)
242
        except (ObjectDoesNotExist, AttributeError):
243
            continue
244

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

    
249
            if field.__class__.__name__ == 'ManyRelatedManager':
250
                exclude.append(obj.__class__.__name__)
251
            subtree = []
252
            for related_obj in getattr(obj, field_name).all():
253
                value = model_to_dict(related_obj, exclude=exclude)
254
                if value or include_empty:
255
                    subtree.append(value)
256
            if subtree or include_empty:
257
                tree[field_name] = subtree
258
            continue
259

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

    
264
        if field.__class__.__name__ == 'RelatedObject':
265
            exclude.append(field.model.__name__)
266
            tree[field_name] = model_to_dict(getattr(obj, field_name),
267
                                             exclude=exclude)
268
            continue
269

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

    
280
    return tree
281

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