Revision 64cd4730

b/.gitignore
1
docs/build
2
*.db
3
*.pyc
4
.DS_Store
b/LICENSE
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.
b/MANIFEST.in
1
global-include */templates/* */fixtures/* */static/*
2
global-exclude */.DS_Store
3
include astakos/settings.d/*
4
prune docs
5
prune other
b/README
1
README
2
======
3

  
4
Astakos is an identity management service, built by GRNET using Django (https://www.djangoproject.com/).
5
Learn more about Astakos at: http://code.grnet.gr/projects/astakos
6
Consult LICENSE for licensing information.
7

  
8
Documentation
9
-------------
10

  
11
All docs are in the docs/source directory. The .rst files are perfectly readable in source form.
12

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

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

  
18
Then run:
19
    python setup.py build_sphinx
20

  
21
The documentation will be built in the docs/build/html directory.
22

  
23
Also run:
24
    python setup.py build_sphinx -b text
25

  
26
Then find the plain text version of the docs in docs/build/text.
27

  
28
Running the server
29
------------------
30

  
31
Make sure you have all required packages installed:
32
    apt-get install python-django python-setuptools python-sphinx python-httplib2
33

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

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

  
43
This server is useful during development, but should not be used for deployment.
44
To deploy Astakos using Apache, take a look at the Administrator Guide in docs.
b/README.upgrade
1
UPGRADE
2
=======
3

  
4
0.7.9 -> 0.7.10
5
---------------
6
* Update settings.py (BACKEND_*, SERVICE_NAME, *_EMAIL, *_TARGET, IM_*)
7
* Update 'attributes' table in mysql (backend):
8
    
9
    mysql> update attributes set `key`='ETag' where `key`='hash';
10

  
11
* Upgrade 'im_user' table (im app):
12
    
13
    ALTER TABLE im_user ADD COLUMN 'password' VARCHAR(255);
14

  
15
0.7.10 -> 0.8.0
16
---------------
17
* Upgrade 'public' table in mysql (backend):
18
	* Run: mysqldump pithosdb public > public-table.sql
19
	* mysql> drop table public;
20
	* Update the codebase and run the server so the new public table is created
21
	* From the sql dump above, take the row:
22
	    
23
		INSERT INTO `public` VALUES (...);
24
      
25
          Rewrite as:
26
	    
27
		INSERT INTO `public`(`path`) VALUES (...);
28
	  
29
	  And execute in the database
30
* Create settings.local with local setting overrides
31
* Install python-django-south
32
* Setup south:
33
    python manage.py syncdb
34
    python manage.py migrate im 0001 --fake
35
    python manage.py migrate im
36

  
37
0.8.0 -> 0.8.1
38
--------------
39
* Reset 'policy' table in mysql (backend):
40
    
41
    mysql> update policy set `value`='auto' where `key`='versioning';
42

  
43
0.8.1 -> 0.8.2
44
--------------
45
* Add the 'X-Forwarded-Protocol' header directive in the apache configuration, as described in the admin guide
46
* Update 'attributes' table in mysql (backend):
47
    
48
    mysql> CREATE TABLE `attributes_new` (
49
               `serial` int(11) NOT NULL,
50
               `domain` varchar(255) COLLATE utf8_bin NOT NULL,
51
               `key` varchar(255) COLLATE utf8_bin NOT NULL,
52
               `value` varchar(255) COLLATE utf8_bin DEFAULT NULL,
53
               PRIMARY KEY (`serial`,`domain`,`key`),
54
               CONSTRAINT FOREIGN KEY (`serial`) REFERENCES `versions` (`serial`) ON DELETE CASCADE ON UPDATE CASCADE
55
           ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
56
    mysql> insert into attributes_new select `serial`, 'pithos', `key`, `value` from attributes;
57
    mysql> drop table attributes;
58
    mysql> alter table attributes_new rename to attributes;
59

  
60
* Update 'versions' table in mysql (backend):
61
    
62
    mysql> create temporary table tmp_uuids as select distinct node, uuid() as `uuid` from versions;
63
    mysql> alter table versions add column `uuid` varchar(64) DEFAULT '' NOT NULL after `muser`;
64
    mysql> update versions v, tmp_uuids u set v.`uuid` = u.`uuid` where v.`node` = u.`node`;
65
    mysql> create index idx_versions_node_uuid on versions(uuid);
b/astakos/__init__.py
1
# Copyright (c) Django Software Foundation and individual contributors.
2
# All rights reserved.
3
# 
4
# Redistribution and use in source and binary forms, with or without modification,
5
# are permitted provided that the following conditions are met:
6
# 
7
#     1. Redistributions of source code must retain the above copyright notice, 
8
#        this list of conditions and the following disclaimer.
9
#     
10
#     2. Redistributions in binary form must reproduce the above copyright 
11
#        notice, this list of conditions and the following disclaimer in the
12
#        documentation and/or other materials provided with the distribution.
13
# 
14
#     3. Neither the name of Django nor the names of its contributors may be used
15
#        to endorse or promote products derived from this software without
16
#        specific prior written permission.
17
# 
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28

  
29
VERSION = (0, 1, 0, 'alpha', 0)
30

  
31
def get_version():
32
    version = '%s.%s' % (VERSION[0], VERSION[1])
33
    if VERSION[2]:
34
        version = '%s.%s' % (version, VERSION[2])
35
    if VERSION[3:] == ('alpha', 0):
36
        version = '%s pre-alpha' % version
37
    else:
38
        if VERSION[3] != 'final':
39
            version = '%s %s %s' % (version, VERSION[3], VERSION[4])
40
    return version
b/astakos/im/api.py
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 astakos.im.faults import BadRequest, Unauthorized, ServiceUnavailable
41
from astakos.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)
b/astakos/im/faults.py
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
b/astakos/im/fixtures/admin_user.json
1
[
2
    {
3
        "model": "im.User",
4
        "pk": 1,
5
        "fields": {
6
            "uniq": "admin",
7
            "password": "admin",
8
            "level": 0,
9
            "state": ACTIVE,
10
            "invitations": 10000,
11
			"is_admin": true,
12
            "auth_token": "0000",
13
            "auth_token_created": "2011-09-11 09:17:14",
14
            "auth_token_expires": "2012-09-11 09:17:14",
15
            "created": "2011-09-11",
16
            "updated": "2011-09-11"
17
   	    }
18
    }
19
]
b/astakos/im/fixtures/auth_test_data.json
1
[
2
    {
3
        "model": "im.User",
4
        "pk": 1,
5
        "fields": {
6
            "uniq": "test",
7
            "level": 0,
8
            "state": "ACTIVE",
9
            "invitations": 10000,
10
            "auth_token": "0000",
11
            "auth_token_created": "2011-04-07 09:17:14",
12
            "auth_token_expires": "2015-04-07 09:17:14",
13
            "created": "2011-02-06",
14
            "updated": "2011-02-06"
15
   	    }
16
    },
17
    {
18
        "model": "im.User",
19
        "pk": 2,
20
        "fields": {
21
            "uniq": "verigak",
22
            "level": 1,
23
            "state": "ACTIVE",
24
            "invitations": 3,
25
            "is_admin": 1,
26
            "auth_token": "0001",
27
            "auth_token_created": "2011-04-07 09:17:14",
28
            "auth_token_expires": "2015-04-07 09:17:14",
29
            "created": "2011-02-06",
30
            "updated": "2011-02-06"
31
   	    }
32
    },
33
    {
34
        "model": "im.User",
35
        "pk": 3,
36
        "fields": {
37
            "uniq": "chazapis",
38
            "level": 1,
39
            "state": "ACTIVE",
40
            "invitations": 3,
41
            "auth_token": "0002",
42
            "auth_token_created": "2011-04-07 09:17:14",
43
            "auth_token_expires": "2015-04-07 09:17:14",
44
            "created": "2011-02-06",
45
            "updated": "2011-02-06"
46
   	    }
47
    },
48
    {
49
        "model": "im.User",
50
        "pk": 4,
51
        "fields": {
52
            "uniq": "gtsouk",
53
            "level": 1,
54
            "state": "ACTIVE",
55
            "invitations": 3,
56
            "auth_token": "0003",
57
            "auth_token_created": "2011-04-07 09:17:14",
58
            "auth_token_expires": "2015-04-07 09:17:14",
59
            "created": "2011-02-06",
60
            "updated": "2011-02-06"
61
   	    }
62
    },
63
    {
64
        "model": "im.User",
65
        "pk": 5,
66
        "fields": {
67
            "uniq": "papagian",
68
            "level": 1,
69
            "state": "ACTIVE",
70
            "invitations": 3,
71
            "auth_token": "0004",
72
            "auth_token_created": "2011-04-07 09:17:14",
73
            "auth_token_expires": "2015-04-07 09:17:14",
74
            "created": "2011-02-06",
75
            "updated": "2011-02-06"
76
   	    }
77
    },
78
    {
79
        "model": "im.User",
80
        "pk": 6,
81
        "fields": {
82
            "uniq": "louridas",
83
            "level": 1,
84
            "state": "ACTIVE",
85
            "invitations": 3,
86
            "auth_token": "0005",
87
            "auth_token_created": "2011-04-07 09:17:14",
88
            "auth_token_expires": "2015-04-07 09:17:14",
89
            "created": "2011-02-06",
90
            "updated": "2011-02-06"
91
   	    }
92
    },
93
    {
94
        "model": "im.User",
95
        "pk": 7,
96
        "fields": {
97
            "uniq": "chstath",
98
            "level": 1,
99
            "state": "ACTIVE",
100
            "invitations": 3,
101
            "auth_token": "0006",
102
            "auth_token_created": "2011-04-07 09:17:14",
103
            "auth_token_expires": "2015-04-07 09:17:14",
104
            "created": "2011-02-06",
105
            "updated": "2011-02-06"
106
   	    }
107
    },
108
    {
109
        "model": "im.User",
110
        "pk": 8,
111
        "fields": {
112
            "uniq": "pkanavos",
113
            "level": 1,
114
            "state": "ACTIVE",
115
            "invitations": 3,
116
            "auth_token": "0007",
117
            "auth_token_created": "2011-04-07 09:17:14",
118
            "auth_token_expires": "2015-04-07 09:17:14",
119
            "created": "2011-02-06",
120
            "updated": "2011-02-06"
121
   	    }
122
    },
123
    {
124
        "model": "im.User",
125
        "pk": 9,
126
        "fields": {
127
            "uniq": "mvasilak",
128
            "level": 1,
129
            "state": "ACTIVE",
130
            "invitations": 3,
131
            "auth_token": "0008",
132
            "auth_token_created": "2011-04-07 09:17:14",
133
            "auth_token_expires": "2015-04-07 09:17:14",
134
            "created": "2011-02-06",
135
            "updated": "2011-02-06"
136
   	    }
137
    },
138
    {
139
        "model": "im.User",
140
        "pk": 10,
141
        "fields": {
142
            "uniq": "ฮดฮนฮฟฮณฮญฮฝฮทฯ‚",
143
            "level": 2,
144
            "state": "ACTIVE",
145
            "invitations": 2,
146
            "auth_token": "0009",
147
            "auth_token_created": "2011-04-07 09:17:14",
148
            "auth_token_expires": "2015-04-07 09:17:14",
149
            "created": "2011-02-06",
150
            "updated": "2011-02-06"
151
   	    }
152
    }
153
]
b/astakos/im/forms.py
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 astakos.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
b/astakos/im/interface.py
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
b/astakos/im/migrations/0001_initial.py
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']
b/astakos/im/migrations/0002_auto__add_field_user_is_verified.py
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']
b/astakos/im/migrations/0003_auto__add_field_user_provider__add_field_user_openidurl__add_field_inv.py
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']
b/astakos/im/models.py
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 astakos.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)
b/astakos/im/static/bootstrap.css
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 {
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff