Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (8.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
213

    
214

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

    
221

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

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

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

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

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

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

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

    
269
    return tree