Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / util.py @ 0dd46210

History | View | Annotate | Download (9.4 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 get_query(request):
216
    try:
217
        return request.__getattribute__(request.method)
218
    except AttributeError:
219
        return {}
220

    
221
def get_properties(obj):
222
    return (i for i in vars(obj.__class__) \
223
        if isinstance(getattr(obj.__class__, i), property))
224

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

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

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

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

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

    
260
        if field.__class__.__name__ == 'RelatedObject':
261
            exclude.append(field.model.__name__)
262
            tree[field_name] = model_to_dict(getattr(obj, field_name),
263
                                             exclude=exclude)
264
            continue
265

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

    
276
    return tree
277

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