Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / util.py @ 4bdd7e3d

History | View | Annotate | Download (9 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

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

    
41
from django.http import HttpResponse, HttpResponseBadRequest, urlencode
42
from django.template import RequestContext
43
from django.contrib.auth import authenticate
44
from django.core.urlresolvers import reverse
45
from django.core.exceptions import ValidationError, ObjectDoesNotExist
46
from django.utils.translation import ugettext as _
47

    
48
from astakos.im.models import AstakosUser, Invitation
49
from astakos.im.settings import (
50
    COOKIE_DOMAIN, FORCE_PROFILE_UPDATE
51
)
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.filter(email__iexact=email).count() > 0 or \
213
        AstakosUser.objects.filter(username__iexact=email).count() > 0
214

    
215

    
216
def get_query(request):
217
    try:
218
        return request.__getattribute__(request.method)
219
    except AttributeError:
220
        return {}
221

    
222

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

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

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

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

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

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

    
264
        value = getattr(obj, field_name)
265
        if field.__class__.__name__ == 'ForeignKey':
266
            value = unicode(value) if value is not None else value
267
        if value or include_empty:
268
            tree[field_name] = value
269

    
270
    return tree