Revision 5d56107c

b/README
13 13
To build the documentation you need to have Sphinx (http://sphinx.pocoo.org/) installed.
14 14

  
15 15
On a typical debian-based Linux system run:
16
    apt-get install python-django python-django-south python-setuptools python-sphinx python-httplib2
16
    apt-get install python-django python-setuptools python-sphinx
17 17
    apt-get install python-sqlalchemy python-mysqldb python-psycopg2
18 18

  
19 19
Then run:
......
30 30
------------------
31 31

  
32 32
Make sure you have all required packages installed:
33
    apt-get install python-django python-setuptools python-sphinx python-httplib2
33
    apt-get install python-django python-setuptools python-sphinx
34 34
    apt-get install python-sqlalchemy python-mysqldb python-psycopg2
35 35

  
36 36
Then run:
37 37
    python manage.py syncdb
38
    python manage.py migrate im
39
    python manage.py loaddata admin_user
40 38
    python manage.py runserver
41 39

  
42
Go to:
43
	http://127.0.0.1:8000/im/admin?user=admin&token=0000
44

  
45 40
This server is useful during development, but should not be used for deployment.
46 41
To deploy Pithos using Apache, take a look at the Administrator Guide in docs.
47 42

  
b/docs/source/adminguide.rst
8 8

  
9 9
Install packages::
10 10

  
11
  apt-get install git python-django python-django-south python-setuptools python-sphinx python-httplib2
11
  apt-get install git python-django python-setuptools python-sphinx
12 12
  apt-get install python-sqlalchemy python-mysqldb python-psycopg2
13 13
  apt-get install apache2 libapache2-mod-wsgi
14 14

  
......
21 21

  
22 22
  cd /pithos/pithos
23 23
  python manage.py syncdb
24
  python manage.py migrate im
25 24
  cd /pithos
26 25
  python setup.py build_sphinx
27 26

  
......
126 125

  
127 126
Useful alias to add in ``~/.bashrc``::
128 127

  
129
  alias pithos-sync='cd /pithos && git pull && python setup.py build_sphinx && cd pithos && python manage.py migrate im && /etc/init.d/apache2 restart'
128
  alias pithos-sync='cd /pithos && git pull && python setup.py build_sphinx && /etc/init.d/apache2 restart'
130 129

  
131 130
Gunicorn Setup
132 131
--------------
/dev/null
1
#!/usr/bin/env python
2

  
3
import sys
4

  
5
if len(sys.argv) != 4:
6
	print "Usage: %s <inviter token> <invitee name> <invitee email>" % (sys.argv[0],)
7
	sys.exit(-1)
8

  
9
import httplib2
10
http = httplib2.Http(disable_ssl_certificate_validation=True)
11

  
12
url = 'https://pithos.dev.grnet.gr/im/invite'
13

  
14
import urllib
15
params = urllib.urlencode({
16
	'uniq': sys.argv[3],
17
	'realname': sys.argv[2]
18
})
19

  
20
response, content = http.request(url, 'POST', params,
21
	headers={'Content-type': 'application/x-www-form-urlencoded', 'X-Auth-Token': sys.argv[1]}
22
)
23

  
24
if response['status'] == '200':
25
	print 'OK'
26
	sys.exit(0)
27
else:
28
	print response, content
29
	sys.exit(-1)
/dev/null
1
# Copyright 2011 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
from traceback import format_exc
35
from time import time, mktime
36
from django.conf import settings
37
from django.http import HttpResponse
38
from django.utils import simplejson as json
39

  
40
from pithos.im.faults import BadRequest, Unauthorized, ServiceUnavailable
41
from pithos.im.models import User
42

  
43
import datetime
44

  
45
def render_fault(request, fault):
46
    if settings.DEBUG or settings.TEST:
47
        fault.details = format_exc(fault)
48
    
49
    request.serialization = 'text'
50
    data = '\n'.join((fault.message, fault.details)) + '\n'
51
    response = HttpResponse(data, status=fault.code)
52
    return response
53

  
54
def update_response_headers(response):
55
    response['Content-Type'] = 'application/json; charset=UTF-8'
56
    response['Content-Length'] = len(response.content)
57

  
58
def authenticate(request):
59
    # Normal Response Codes: 204
60
    # Error Response Codes: serviceUnavailable (503)
61
    #                       badRequest (400)
62
    #                       unauthorised (401)
63
    try:
64
        if request.method != 'GET':
65
            raise BadRequest('Method not allowed.')
66
        x_auth_token = request.META.get('HTTP_X_AUTH_TOKEN')
67
        if not x_auth_token:
68
            return render_fault(request, BadRequest('Missing X-Auth-Token'))
69
        
70
        try:
71
            user = User.objects.get(auth_token=x_auth_token)
72
        except User.DoesNotExist, e:
73
            return render_fault(request, Unauthorized('Invalid X-Auth-Token')) 
74
        
75
        # Check if the is active.
76
        if user.state != 'ACTIVE':
77
            return render_fault(request, Unauthorized('User inactive'))
78
        
79
        # Check if the token has expired.
80
        if (time() - mktime(user.auth_token_expires.timetuple())) > 0:
81
            return render_fault(request, Unauthorized('Authentication expired'))
82
        
83
        response = HttpResponse()
84
        response.status=204
85
        user_info = user.__dict__
86
        for k,v in user_info.items():
87
            if isinstance(v,  datetime.datetime):
88
                user_info[k] = v.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
89
        user_info.pop('_state')
90
        response.content = json.dumps(user_info)
91
        update_response_headers(response)
92
        return response
93
    except BaseException, e:
94
        fault = ServiceUnavailable('Unexpected error')
95
        return render_fault(request, fault)
/dev/null
1
# Copyright 2011 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
def camelCase(s):
35
    return s[0].lower() + s[1:]
36

  
37
class Fault(Exception):
38
    def __init__(self, message='', details='', name=''):
39
        Exception.__init__(self, message, details, name)
40
        self.message = message
41
        self.details = details
42
        self.name = name or camelCase(self.__class__.__name__)
43

  
44
class BadRequest(Fault):
45
    code = 400
46

  
47
class Unauthorized(Fault):
48
    code = 401
49

  
50
class ServiceUnavailable(Fault):
51
    code = 503
/dev/null
1
[
2
    {
3
        "model": "im.User",
4
        "pk": 1,
5
        "fields": {
6
            "uniq": "admin",
7
            "level": 0,
8
            "invitations": 10000,
9
			"is_admin": true,
10
            "auth_token": "0000",
11
            "auth_token_created": "2011-09-11 09:17:14",
12
            "auth_token_expires": "2012-09-11 09:17:14",
13
            "created": "2011-09-11",
14
            "updated": "2011-09-11"
15
   	    }
16
    }
17
]
/dev/null
1
[
2
    {
3
        "model": "im.User",
4
        "pk": 1,
5
        "fields": {
6
            "uniq": "test",
7
            "level": 0,
8
            "invitations": 10000,
9
            "auth_token": "0000",
10
            "auth_token_created": "2011-04-07 09:17:14",
11
            "auth_token_expires": "2015-04-07 09:17:14",
12
            "created": "2011-02-06",
13
            "updated": "2011-02-06"
14
   	    }
15
    },
16
    {
17
        "model": "im.User",
18
        "pk": 2,
19
        "fields": {
20
            "uniq": "verigak",
21
            "level": 1,
22
            "invitations": 3,
23
            "is_admin": 1,
24
            "auth_token": "0001",
25
            "auth_token_created": "2011-04-07 09:17:14",
26
            "auth_token_expires": "2015-04-07 09:17:14",
27
            "created": "2011-02-06",
28
            "updated": "2011-02-06"
29
   	    }
30
    },
31
    {
32
        "model": "im.User",
33
        "pk": 3,
34
        "fields": {
35
            "uniq": "chazapis",
36
            "level": 1,
37
            "invitations": 3,
38
            "auth_token": "0002",
39
            "auth_token_created": "2011-04-07 09:17:14",
40
            "auth_token_expires": "2015-04-07 09:17:14",
41
            "created": "2011-02-06",
42
            "updated": "2011-02-06"
43
   	    }
44
    },
45
    {
46
        "model": "im.User",
47
        "pk": 4,
48
        "fields": {
49
            "uniq": "gtsouk",
50
            "level": 1,
51
            "invitations": 3,
52
            "auth_token": "0003",
53
            "auth_token_created": "2011-04-07 09:17:14",
54
            "auth_token_expires": "2015-04-07 09:17:14",
55
            "created": "2011-02-06",
56
            "updated": "2011-02-06"
57
   	    }
58
    },
59
    {
60
        "model": "im.User",
61
        "pk": 5,
62
        "fields": {
63
            "uniq": "papagian",
64
            "level": 1,
65
            "invitations": 3,
66
            "auth_token": "0004",
67
            "auth_token_created": "2011-04-07 09:17:14",
68
            "auth_token_expires": "2015-04-07 09:17:14",
69
            "created": "2011-02-06",
70
            "updated": "2011-02-06"
71
   	    }
72
    },
73
    {
74
        "model": "im.User",
75
        "pk": 6,
76
        "fields": {
77
            "uniq": "louridas",
78
            "level": 1,
79
            "invitations": 3,
80
            "auth_token": "0005",
81
            "auth_token_created": "2011-04-07 09:17:14",
82
            "auth_token_expires": "2015-04-07 09:17:14",
83
            "created": "2011-02-06",
84
            "updated": "2011-02-06"
85
   	    }
86
    },
87
    {
88
        "model": "im.User",
89
        "pk": 7,
90
        "fields": {
91
            "uniq": "chstath",
92
            "level": 1,
93
            "invitations": 3,
94
            "auth_token": "0006",
95
            "auth_token_created": "2011-04-07 09:17:14",
96
            "auth_token_expires": "2015-04-07 09:17:14",
97
            "created": "2011-02-06",
98
            "updated": "2011-02-06"
99
   	    }
100
    },
101
    {
102
        "model": "im.User",
103
        "pk": 8,
104
        "fields": {
105
            "uniq": "pkanavos",
106
            "level": 1,
107
            "invitations": 3,
108
            "auth_token": "0007",
109
            "auth_token_created": "2011-04-07 09:17:14",
110
            "auth_token_expires": "2015-04-07 09:17:14",
111
            "created": "2011-02-06",
112
            "updated": "2011-02-06"
113
   	    }
114
    },
115
    {
116
        "model": "im.User",
117
        "pk": 9,
118
        "fields": {
119
            "uniq": "mvasilak",
120
            "level": 1,
121
            "invitations": 3,
122
            "auth_token": "0008",
123
            "auth_token_created": "2011-04-07 09:17:14",
124
            "auth_token_expires": "2015-04-07 09:17:14",
125
            "created": "2011-02-06",
126
            "updated": "2011-02-06"
127
   	    }
128
    },
129
    {
130
        "model": "im.User",
131
        "pk": 10,
132
        "fields": {
133
            "uniq": "διογένης",
134
            "level": 2,
135
            "invitations": 2,
136
            "auth_token": "0009",
137
            "auth_token_created": "2011-04-07 09:17:14",
138
            "auth_token_expires": "2015-04-07 09:17:14",
139
            "created": "2011-02-06",
140
            "updated": "2011-02-06"
141
   	    }
142
    }
143
]
/dev/null
1
# Copyright 2011 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
from django import forms
35
from django.utils.translation import ugettext as _
36
from django.conf import settings
37

  
38
from pithos.im.models import User
39

  
40
openid_providers = (
41
('Google','https://www.google.com/accounts/o8/id'),
42
('Yahoo', 'http://yahoo.com/'),
43
('AOL','http://openid.aol.com/%s/'),
44
('OpenID', None),
45
('MyOpenID','http://%s.myopenid.com/'),
46
('LiveJournal', 'http://%s.livejournal.com/'),
47
('Flickr', 'http://flickr.com/%s/'),
48
('Technorati', 'http://technorati.com/people/technorati/%s/'),
49
('Wordpress', 'http://%s.wordpress.com/'),
50
('Blogger', 'http://%s.blogspot.com/'),
51
('Verisign', 'http://%s.pip.verisignlabs.com/'),
52
('Vidoop', 'http://%s.myvidoop.com/'),
53
('ClaimID','http://claimid.com/%s')    
54
)
55

  
56
class RegisterForm(forms.Form):
57
    uniq = forms.CharField(widget=forms.widgets.TextInput())
58
    provider = forms.CharField(widget=forms.TextInput(),
59
                                label=u'Identity Provider')
60
    email = forms.EmailField(widget=forms.TextInput(),
61
                             label=_('Email address'))
62
    realname = forms.CharField(widget=forms.TextInput(),
63
                                label=u'Real Name')
64
    
65
    def __init__(self, *args, **kwargs):
66
        super(forms.Form, self).__init__(*args, **kwargs)
67
        
68
        #set readonly form fields
69
        self.fields['provider'].widget.attrs['readonly'] = True
70
    
71
    def clean_uniq(self):
72
        """
73
        Validate that the uniq is alphanumeric and is not already
74
        in use.
75
        
76
        """
77
        try:
78
            user = User.objects.get(uniq__iexact=self.cleaned_data['uniq'])
79
        except User.DoesNotExist:
80
            return self.cleaned_data['uniq']
81
        raise forms.ValidationError(_("A user with that uniq already exists."))
82

  
83
class ShibbolethRegisterForm(RegisterForm):
84
    pass
85

  
86
class TwitterRegisterForm(RegisterForm):
87
    pass
88

  
89
class OpenidRegisterForm(RegisterForm):
90
    openidurl = forms.ChoiceField(widget=forms.Select,
91
                                  choices=((url, l) for l, url in openid_providers))
92

  
93
class LocalRegisterForm(RegisterForm):
94
    """ local signup form"""
95
    password = forms.CharField(widget=forms.PasswordInput(render_value=False),
96
                                label=_('Password'))
97
    password2 = forms.CharField(widget=forms.PasswordInput(render_value=False),
98
                                label=_('Confirm Password'))
99
    
100
    def __init__(self, *args, **kwargs):
101
        super(LocalRegisterForm, self).__init__(*args, **kwargs)
102
    
103
    def clean_uniq(self):
104
        """
105
        Validate that the uniq is alphanumeric and is not already
106
        in use.
107
        
108
        """
109
        try:
110
            user = User.objects.get(uniq__iexact=self.cleaned_data['uniq'])
111
        except User.DoesNotExist:
112
            return self.cleaned_data['uniq']
113
        raise forms.ValidationError(_("A user with that uniq already exists."))
114
    
115
    def clean(self):
116
        """
117
        Verifiy that the values entered into the two password fields
118
        match. Note that an error here will end up in
119
        ``non_field_errors()`` because it doesn't apply to a single
120
        field.
121
        
122
        """
123
        if 'password' in self.cleaned_data and 'password2' in self.cleaned_data:
124
            if self.cleaned_data['password'] != self.cleaned_data['password2']:
125
                raise forms.ValidationError(_("The two password fields didn't match."))
126
        return self.cleaned_data
127

  
128
class InvitedRegisterForm(RegisterForm):
129
    inviter = forms.CharField(widget=forms.TextInput(),
130
                                label=_('Inviter Real Name'))
131
    
132
    def __init__(self, *args, **kwargs):
133
        super(RegisterForm, self).__init__(*args, **kwargs)
134
        
135
        #set readonly form fields
136
        self.fields['uniq'].widget.attrs['readonly'] = True
137
        self.fields['inviter'].widget.attrs['readonly'] = True
138
        self.fields['provider'].widget.attrs['provider'] = True
139

  
140
class InvitedLocalRegisterForm(LocalRegisterForm, InvitedRegisterForm):
141
    pass
142

  
143
class InvitedOpenidRegisterForm(OpenidRegisterForm, InvitedRegisterForm):
144
    pass
145

  
146
class InvitedTwitterRegisterForm(TwitterRegisterForm, InvitedRegisterForm):
147
    pass
148

  
149
class InvitedShibbolethRegisterForm(ShibbolethRegisterForm, InvitedRegisterForm):
150
    pass
/dev/null
1
# Copyright 2011 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
from pithos.backends import connect_backend
35

  
36
def get_quota(user):
37
    backend = connect_backend()
38
    quota = backend.get_account_policy(user, user)['quota']
39
    backend.close()
40
    return quota
41

  
42
def set_quota(user, quota):
43
    backend = connect_backend()
44
    backend.update_account_policy(user, user, {'quota': quota})
45
    backend.close()
46
    return quota
/dev/null
1
# encoding: utf-8
2
import datetime
3
from south.db import db
4
from south.v2 import SchemaMigration
5
from django.db import models
6

  
7
class Migration(SchemaMigration):
8

  
9
    def forwards(self, orm):
10
        
11
        # Adding model 'User'
12
        db.create_table('im_user', (
13
            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14
            ('uniq', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)),
15
            ('realname', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
16
            ('email', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
17
            ('affiliation', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
18
            ('state', self.gf('django.db.models.fields.CharField')(default='ACTIVE', max_length=16)),
19
            ('level', self.gf('django.db.models.fields.IntegerField')(default=4)),
20
            ('invitations', self.gf('django.db.models.fields.IntegerField')(default=0)),
21
            ('password', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
22
            ('is_admin', self.gf('django.db.models.fields.BooleanField')(default=False)),
23
            ('auth_token', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
24
            ('auth_token_created', self.gf('django.db.models.fields.DateTimeField')(null=True)),
25
            ('auth_token_expires', self.gf('django.db.models.fields.DateTimeField')(null=True)),
26
            ('created', self.gf('django.db.models.fields.DateTimeField')()),
27
            ('updated', self.gf('django.db.models.fields.DateTimeField')()),
28
        ))
29
        db.send_create_signal('im', ['User'])
30

  
31
        # Adding model 'Invitation'
32
        db.create_table('im_invitation', (
33
            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
34
            ('inviter', self.gf('django.db.models.fields.related.ForeignKey')(related_name='invitations_sent', null=True, to=orm['im.User'])),
35
            ('realname', self.gf('django.db.models.fields.CharField')(max_length=255)),
36
            ('uniq', self.gf('django.db.models.fields.CharField')(max_length=255)),
37
            ('code', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
38
            ('is_accepted', self.gf('django.db.models.fields.BooleanField')(default=False)),
39
            ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
40
            ('accepted', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
41
        ))
42
        db.send_create_signal('im', ['Invitation'])
43

  
44

  
45
    def backwards(self, orm):
46
        
47
        # Deleting model 'User'
48
        db.delete_table('im_user')
49

  
50
        # Deleting model 'Invitation'
51
        db.delete_table('im_invitation')
52

  
53

  
54
    models = {
55
        'im.invitation': {
56
            'Meta': {'object_name': 'Invitation'},
57
            'accepted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
58
            'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
59
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
60
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
61
            'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.User']"}),
62
            'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
63
            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
64
            'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255'})
65
        },
66
        'im.user': {
67
            'Meta': {'object_name': 'User'},
68
            'affiliation': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
69
            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
70
            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
71
            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
72
            'created': ('django.db.models.fields.DateTimeField', [], {}),
73
            'email': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
74
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
75
            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
76
            'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
77
            'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
78
            'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
79
            'realname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
80
            'state': ('django.db.models.fields.CharField', [], {'default': "'ACTIVE'", 'max_length': '16'}),
81
            'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
82
            'updated': ('django.db.models.fields.DateTimeField', [], {})
83
        }
84
    }
85

  
86
    complete_apps = ['im']
/dev/null
1
# encoding: utf-8
2
import datetime
3
from south.db import db
4
from south.v2 import SchemaMigration
5
from django.db import models
6

  
7
class Migration(SchemaMigration):
8

  
9
    def forwards(self, orm):
10
        
11
        # Adding field 'User.is_verified'
12
        db.add_column('im_user', 'is_verified', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
13

  
14

  
15
    def backwards(self, orm):
16
        
17
        # Deleting field 'User.is_verified'
18
        db.delete_column('im_user', 'is_verified')
19

  
20

  
21
    models = {
22
        'im.invitation': {
23
            'Meta': {'object_name': 'Invitation'},
24
            'accepted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
25
            'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
26
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
27
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
28
            'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.User']"}),
29
            'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
30
            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
31
            'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255'})
32
        },
33
        'im.user': {
34
            'Meta': {'object_name': 'User'},
35
            'affiliation': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
36
            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
37
            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
38
            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
39
            'created': ('django.db.models.fields.DateTimeField', [], {}),
40
            'email': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
41
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
42
            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
43
            'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
44
            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
45
            'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
46
            'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
47
            'realname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
48
            'state': ('django.db.models.fields.CharField', [], {'default': "'ACTIVE'", 'max_length': '16'}),
49
            'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
50
            'updated': ('django.db.models.fields.DateTimeField', [], {})
51
        }
52
    }
53

  
54
    complete_apps = ['im']
/dev/null
1
# encoding: utf-8
2
import datetime
3
from south.db import db
4
from south.v2 import SchemaMigration
5
from django.db import models
6

  
7
class Migration(SchemaMigration):
8

  
9
    def forwards(self, orm):
10
        
11
        # Adding field 'User.provider'
12
        db.add_column('im_user', 'provider', self.gf('django.db.models.fields.CharField')(default='', max_length=255), keep_default=False)
13

  
14
        # Adding field 'User.openidurl'
15
        db.add_column('im_user', 'openidurl', self.gf('django.db.models.fields.CharField')(default='', max_length=255), keep_default=False)
16

  
17
        # Adding field 'Invitation.is_consumed'
18
        db.add_column('im_invitation', 'is_consumed', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
19

  
20
        # Adding field 'Invitation.consumed'
21
        db.add_column('im_invitation', 'consumed', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), keep_default=False)
22

  
23

  
24
    def backwards(self, orm):
25
        
26
        # Deleting field 'User.provider'
27
        db.delete_column('im_user', 'provider')
28

  
29
        # Deleting field 'User.openidurl'
30
        db.delete_column('im_user', 'openidurl')
31

  
32
        # Deleting field 'Invitation.is_consumed'
33
        db.delete_column('im_invitation', 'is_consumed')
34

  
35
        # Deleting field 'Invitation.consumed'
36
        db.delete_column('im_invitation', 'consumed')
37

  
38

  
39
    models = {
40
        'im.invitation': {
41
            'Meta': {'object_name': 'Invitation'},
42
            'accepted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
43
            'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
44
            'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
45
            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
46
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
47
            'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.User']"}),
48
            'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
49
            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
50
            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
51
            'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255'})
52
        },
53
        'im.user': {
54
            'Meta': {'object_name': 'User'},
55
            'affiliation': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
56
            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
57
            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
58
            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
59
            'created': ('django.db.models.fields.DateTimeField', [], {}),
60
            'email': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
61
            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
62
            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
63
            'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
64
            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
65
            'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
66
            'openidurl': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
67
            'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
68
            'provider': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
69
            'realname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
70
            'state': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '16'}),
71
            'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
72
            'updated': ('django.db.models.fields.DateTimeField', [], {})
73
        }
74
    }
75

  
76
    complete_apps = ['im']
/dev/null
1
# Copyright 2011 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 hashlib
36

  
37
from time import asctime
38
from datetime import datetime, timedelta
39
from base64 import b64encode
40

  
41
from django.conf import settings
42
from django.db import models
43

  
44
from pithos.im.interface import get_quota, set_quota
45

  
46
from hashlib import new as newhasher
47

  
48
class User(models.Model):
49
    ACCOUNT_STATE = (
50
        ('ACTIVE', 'Active'),
51
        ('DELETED', 'Deleted'),
52
        ('SUSPENDED', 'Suspended'),
53
        ('UNVERIFIED', 'Unverified'),
54
        ('PENDING', 'Pending')
55
    )
56
    
57
    uniq = models.CharField('Unique ID', max_length=255, null=True)
58
    
59
    realname = models.CharField('Real Name', max_length=255, default='')
60
    email = models.CharField('Email', max_length=255, default='')
61
    affiliation = models.CharField('Affiliation', max_length=255, default='')
62
    provider = models.CharField('Provider', max_length=255, default='')
63
    state = models.CharField('Account state', choices=ACCOUNT_STATE,
64
                                max_length=16, default='PENDING')
65
    
66
    #for invitations
67
    level = models.IntegerField('Inviter level', default=4)
68
    invitations = models.IntegerField('Invitations left', default=0)
69
    
70
    #for local
71
    password = models.CharField('Password', max_length=255, default='')
72
    
73
    is_admin = models.BooleanField('Admin?', default=False)
74
    
75
    auth_token = models.CharField('Authentication Token', max_length=32,
76
                                    null=True, blank=True)
77
    auth_token_created = models.DateTimeField('Token creation date',
78
                                                null=True)
79
    auth_token_expires = models.DateTimeField('Token expiration date',
80
                                                null=True)
81
    
82
    created = models.DateTimeField('Creation date')
83
    updated = models.DateTimeField('Update date')
84
    
85
    is_verified = models.BooleanField('Verified?', default=False)
86
    
87
    openidurl = models.CharField('OpenID url', max_length=255, default='')
88
    
89
    @property
90
    def quota(self):
91
        return get_quota(self.uniq)
92

  
93
    @quota.setter
94
    def quota(self, value):
95
        set_quota(self.uniq, value)
96
    
97
    @property
98
    def invitation(self):
99
        try:
100
            return Invitation.objects.get(uniq=self.uniq)
101
        except Invitation.DoesNotExist:
102
            return None
103
    
104
    def save(self, update_timestamps=True, **kwargs):
105
        if update_timestamps:
106
            if not self.id:
107
                self.created = datetime.now()
108
            self.updated = datetime.now()
109
        
110
        super(User, self).save(**kwargs)
111
        
112
        #invitation consume
113
        if self.invitation and not self.invitation.is_consumed:
114
            self.invitation.consume()
115
    
116
    def renew_token(self):
117
        md5 = hashlib.md5()
118
        md5.update(self.uniq)
119
        md5.update(self.realname.encode('ascii', 'ignore'))
120
        md5.update(asctime())
121
        
122
        self.auth_token = b64encode(md5.digest())
123
        self.auth_token_created = datetime.now()
124
        self.auth_token_expires = self.auth_token_created + \
125
                                  timedelta(hours=settings.AUTH_TOKEN_DURATION)
126
    
127
    def __unicode__(self):
128
        return self.uniq
129

  
130
class Invitation(models.Model):
131
    inviter = models.ForeignKey(User, related_name='invitations_sent',
132
                                null=True)
133
    realname = models.CharField('Real name', max_length=255)
134
    uniq = models.CharField('Unique ID', max_length=255)
135
    code = models.BigIntegerField('Invitation code', db_index=True)
136
    #obsolete: we keep it just for transfering the data
137
    is_accepted = models.BooleanField('Accepted?', default=False)
138
    is_consumed = models.BooleanField('Consumed?', default=False)
139
    created = models.DateTimeField('Creation date', auto_now_add=True)
140
    #obsolete: we keep it just for transfering the data
141
    accepted = models.DateTimeField('Acceptance date', null=True, blank=True)
142
    consumed = models.DateTimeField('Consumption date', null=True, blank=True)
143
    
144
    def consume(self):
145
        self.is_consumed = True
146
        self.consumed = datetime.now()
147
        self.save()
148
        
149
    def __unicode__(self):
150
        return '%s -> %s [%d]' % (self.inviter, self.uniq, self.code)
/dev/null
1
/*!
2
 * Bootstrap v1.3.0
3
 *
4
 * Copyright 2011 Twitter, Inc
5
 * Licensed under the Apache License v2.0
6
 * http://www.apache.org/licenses/LICENSE-2.0
7
 *
8
 * Designed and built with all the love in the world @twitter by @mdo and @fat.
9
 * Date: Thu Sep 22 12:52:42 PDT 2011
10
 */
11
/* Reset.less
12
 * Props to Eric Meyer (meyerweb.com) for his CSS reset file. We're using an adapted version here	that cuts out some of the reset HTML elements we will never need here (i.e., dfn, samp, etc).
13
 * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
14
html, body {
15
  margin: 0;
16
  padding: 0;
17
}
18
h1,
19
h2,
20
h3,
21
h4,
22
h5,
23
h6,
24
p,
25
blockquote,
26
pre,
27
a,
28
abbr,
29
acronym,
30
address,
31
cite,
32
code,
33
del,
34
dfn,
35
em,
36
img,
37
q,
38
s,
39
samp,
40
small,
41
strike,
42
strong,
43
sub,
44
sup,
45
tt,
46
var,
47
dd,
48
dl,
49
dt,
50
li,
51
ol,
52
ul,
53
fieldset,
54
form,
55
label,
56
legend,
57
button,
58
table,
59
caption,
60
tbody,
61
tfoot,
62
thead,
63
tr,
64
th,
65
td {
66
  margin: 0;
67
  padding: 0;
68
  border: 0;
69
  font-weight: normal;
70
  font-style: normal;
71
  font-size: 100%;
72
  line-height: 1;
73
  font-family: inherit;
74
}
75
table {
76
  border-collapse: collapse;
77
  border-spacing: 0;
78
}
79
ol, ul {
80
  list-style: none;
81
}
82
q:before,
83
q:after,
84
blockquote:before,
85
blockquote:after {
86
  content: "";
87
}
88
html {
89
  overflow-y: scroll;
90
  font-size: 100%;
91
  -webkit-text-size-adjust: 100%;
92
  -ms-text-size-adjust: 100%;
93
}
94
a:focus {
95
  outline: thin dotted;
96
}
97
a:hover, a:active {
98
  outline: 0;
99
}
100
article,
101
aside,
102
details,
103
figcaption,
104
figure,
105
footer,
106
header,
107
hgroup,
108
nav,
109
section {
110
  display: block;
111
}
112
audio, canvas, video {
113
  display: inline-block;
114
  *display: inline;
115
  *zoom: 1;
116
}
117
audio:not([controls]) {
118
  display: none;
119
}
120
sub, sup {
121
  font-size: 75%;
122
  line-height: 0;
123
  position: relative;
124
  vertical-align: baseline;
125
}
126
sup {
127
  top: -0.5em;
128
}
129
sub {
130
  bottom: -0.25em;
131
}
132
img {
133
  border: 0;
134
  -ms-interpolation-mode: bicubic;
135
}
136
button,
137
input,
138
select,
139
textarea {
140
  font-size: 100%;
141
  margin: 0;
142
  vertical-align: baseline;
143
  *vertical-align: middle;
144
}
145
button, input {
146
  line-height: normal;
147
  *overflow: visible;
148
}
149
button::-moz-focus-inner, input::-moz-focus-inner {
150
  border: 0;
151
  padding: 0;
152
}
153
button,
154
input[type="button"],
155
input[type="reset"],
156
input[type="submit"] {
157
  cursor: pointer;
158
  -webkit-appearance: button;
159
}
160
input[type="search"] {
161
  -webkit-appearance: textfield;
162
  -webkit-box-sizing: content-box;
163
  -moz-box-sizing: content-box;
164
  box-sizing: content-box;
165
}
166
input[type="search"]::-webkit-search-decoration {
167
  -webkit-appearance: none;
168
}
169
textarea {
170
  overflow: auto;
171
  vertical-align: top;
172
}
173
/* Variables.less
174
 * Variables to customize the look and feel of Bootstrap
175
 * ----------------------------------------------------- */
176
/* Variables.less
177
 * Snippets of reusable CSS to develop faster and keep code readable
178
 * ----------------------------------------------------------------- */
179
/*
180
 * Scaffolding
181
 * Basic and global styles for generating a grid system, structural layout, and page templates
182
 * ------------------------------------------------------------------------------------------- */
183
html, body {
184
  background-color: #ffffff;
185
}
186
body {
187
  margin: 0;
188
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
189
  font-size: 13px;
190
  font-weight: normal;
191
  line-height: 18px;
192
  color: #404040;
193
}
194
.container {
195
  width: 940px;
196
  margin-left: auto;
197
  margin-right: auto;
198
  zoom: 1;
199
}
200
.container:before, .container:after {
201
  display: table;
202
  content: "";
203
  zoom: 1;
204
  *display: inline;
205
}
206
.container:after {
207
  clear: both;
208
}
209
.container-fluid {
210
  position: relative;
211
  min-width: 940px;
212
  padding-left: 20px;
213
  padding-right: 20px;
214
  zoom: 1;
215
}
216
.container-fluid:before, .container-fluid:after {
217
  display: table;
218
  content: "";
219
  zoom: 1;
220
  *display: inline;
221
}
222
.container-fluid:after {
223
  clear: both;
224
}
225
.container-fluid > .sidebar {
226
  float: left;
227
  width: 220px;
228
}
229
.container-fluid > .content {
230
  margin-left: 240px;
231
}
232
a {
233
  color: #0069d6;
234
  text-decoration: none;
235
  line-height: inherit;
236
  font-weight: inherit;
237
}
238
a:hover {
239
  color: #00438a;
240
  text-decoration: underline;
241
}
242
.pull-right {
243
  float: right;
244
}
245
.pull-left {
246
  float: left;
247
}
248
.hide {
249
  display: none;
250
}
251
.show {
252
  display: block;
253
}
254
.row {
255
  zoom: 1;
256
  margin-left: -20px;
257
}
258
.row:before, .row:after {
259
  display: table;
260
  content: "";
261
  zoom: 1;
262
  *display: inline;
263
}
264
.row:after {
265
  clear: both;
266
}
267
[class*="span"] {
268
  display: inline;
269
  float: left;
270
  margin-left: 20px;
271
}
272
.span1 {
273
  width: 40px;
274
}
275
.span2 {
276
  width: 100px;
277
}
278
.span3 {
279
  width: 160px;
280
}
281
.span4 {
282
  width: 220px;
283
}
284
.span5 {
285
  width: 280px;
286
}
287
.span6 {
288
  width: 340px;
289
}
290
.span7 {
291
  width: 400px;
292
}
293
.span8 {
294
  width: 460px;
295
}
296
.span9 {
297
  width: 520px;
298
}
299
.span10 {
300
  width: 580px;
301
}
302
.span11 {
303
  width: 640px;
304
}
305
.span12 {
306
  width: 700px;
307
}
308
.span13 {
309
  width: 760px;
310
}
311
.span14 {
312
  width: 820px;
313
}
314
.span15 {
315
  width: 880px;
316
}
317
.span16 {
318
  width: 940px;
319
}
320
.span17 {
321
  width: 1000px;
322
}
323
.span18 {
324
  width: 1060px;
325
}
326
.span19 {
327
  width: 1120px;
328
}
329
.span20 {
330
  width: 1180px;
331
}
332
.span21 {
333
  width: 1240px;
334
}
335
.span22 {
336
  width: 1300px;
337
}
338
.span23 {
339
  width: 1360px;
340
}
341
.span24 {
342
  width: 1420px;
343
}
344
.offset1 {
345
  margin-left: 80px;
346
}
347
.offset2 {
348
  margin-left: 140px;
349
}
350
.offset3 {
351
  margin-left: 200px;
352
}
353
.offset4 {
354
  margin-left: 260px;
355
}
356
.offset5 {
357
  margin-left: 320px;
358
}
359
.offset6 {
360
  margin-left: 380px;
361
}
362
.offset7 {
363
  margin-left: 440px;
364
}
365
.offset8 {
366
  margin-left: 500px;
367
}
368
.offset9 {
369
  margin-left: 560px;
370
}
371
.offset10 {
372
  margin-left: 620px;
373
}
374
.offset11 {
375
  margin-left: 680px;
376
}
377
.offset12 {
378
  margin-left: 740px;
379
}
380
.span-one-third {
381
  width: 300px;
382
}
383
.span-two-thirds {
384
  width: 620px;
385
}
386
.offset-one-third {
387
  margin-left: 340px;
388
}
389
.offset-two-thirds {
390
  margin-left: 660px;
391
}
392
/* Typography.less
393
 * Headings, body text, lists, code, and more for a versatile and durable typography system
394
 * ---------------------------------------------------------------------------------------- */
395
p {
396
  font-size: 13px;
397
  font-weight: normal;
398
  line-height: 18px;
399
  margin-bottom: 9px;
400
}
401
p small {
402
  font-size: 11px;
403
  color: #bfbfbf;
404
}
405
h1,
406
h2,
407
h3,
408
h4,
409
h5,
410
h6 {
411
  font-weight: bold;
412
  color: #404040;
413
}
414
h1 small,
415
h2 small,
416
h3 small,
417
h4 small,
418
h5 small,
419
h6 small {
420
  color: #bfbfbf;
421
}
422
h1 {
423
  margin-bottom: 18px;
424
  font-size: 30px;
425
  line-height: 36px;
426
}
427
h1 small {
428
  font-size: 18px;
429
}
430
h2 {
431
  font-size: 24px;
432
  line-height: 36px;
433
}
434
h2 small {
435
  font-size: 14px;
436
}
437
h3,
438
h4,
439
h5,
440
h6 {
441
  line-height: 36px;
442
}
443
h3 {
444
  font-size: 18px;
445
}
446
h3 small {
447
  font-size: 14px;
448
}
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff