Revision b4c241e6

b/pithos/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/pithos/im/models.py
79 79
    created = models.DateTimeField('Creation date')
80 80
    updated = models.DateTimeField('Update date')
81 81
    
82
    is_verified = models.BooleanField('Verified?', default=False)
83
    
82 84
    @property
83 85
    def quota(self):
84 86
        return get_quota(self.uniq)
b/pithos/im/target/invitation.py
37 37

  
38 38
from django.conf import settings
39 39
from django.http import HttpResponseBadRequest
40
from django.core.urlresolvers import reverse
41
from django.utils.http import urlencode
40 42

  
41 43
from pithos.im.models import Invitation
42 44
from pithos.im.target.util import get_or_create_user, prepare_response
......
59 61
                                invitation.realname,
60 62
                                'Invitation',
61 63
                                invitation.inviter.level + 1)
64
    
62 65
    next = request.GET.get('next')
66
    if settings.FORCE_PROFILE_UPDATE and not user.is_verified:
67
        profile_url = reverse('pithos.im.views.users_profile', args=(user.id,))
68
        next = urlencode({'next': next})
69
        profile_url = profile_url + '?' + next
70
        return prepare_response(request, user, profile_url)
71
    
63 72
    return prepare_response(request, user, next, 'renew' in request.GET)
b/pithos/im/target/local.py
34 34
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
35 35
from django.conf import settings
36 36
from django.template.loader import render_to_string
37
from django.core.urlresolvers import reverse
38
from django.utils.http import urlencode
37 39

  
38 40
from pithos.im.target.util import prepare_response
39 41
from pithos.im.models import User
......
62 64
        return HttpResponseBadRequest('Unverified account')
63 65
    
64 66
    next = request.POST.get('next')
67
    if settings.FORCE_PROFILE_UPDATE and not user.is_verified:
68
        profile_url = reverse('pithos.im.views.users_profile', args=(user.id,))
69
        next = urlencode({'next': next})
70
        profile_url = profile_url + '?' + next
71
        return prepare_response(request, user, profile_url)
72
    
65 73
    return prepare_response(request, user, next)
66 74

  
67 75
def activate(request):
b/pithos/im/target/shibboleth.py
32 32
# or implied, of GRNET S.A.
33 33

  
34 34
from django.http import HttpResponseBadRequest
35
from django.core.urlresolvers import reverse
36
from django.utils.http import urlencode
37
from django.conf import settings
35 38

  
36 39
from pithos.im.target.util import get_or_create_user, prepare_response
37 40

  
......
66 69
    
67 70
    affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
68 71
    
72
    user = get_or_create_user(eppn, realname, affiliation, 0)
73
    next = request.GET.get('next')
74
    if settings.FORCE_PROFILE_UPDATE and not user.is_verified:
75
        profile_url = reverse('pithos.im.views.users_profile', args=(user.id,))
76
        next = urlencode({'next': next})
77
        profile_url = profile_url + '?' + next
78
        return prepare_response(request, user, profile_url)
79
    
69 80
    return prepare_response(request,
70
                            get_or_create_user(eppn, realname, affiliation, 0),
71
                            request.GET.get('next'),
81
                            user,
82
                            next,
72 83
                            'renew' in request.GET)
b/pithos/im/target/util.py
80 80
        # TODO: Avoid redirect loops.
81 81
        parts = list(urlsplit(next))
82 82
        # Do not pass on user and token if we are on the same server.
83
        if request.get_host() != parts[1]:
83
        if parts[1] and request.get_host() != parts[1]:
84 84
            parts[3] = urlencode({'user': user.uniq, 'token': user.auth_token})
85 85
            next = urlunsplit(parts)
86 86
    
b/pithos/im/templates/admin.html
1
{% extends "base.html" %}
1
{% extends "admin_base.html" %}
2 2

  
3 3
{% block body %}
4 4
<ul class="unstyled">
b/pithos/im/templates/admin_base.html
1
{% extends "base.html" %}
2
    
3
{% block tabs %}
4
<ul class="tabs">
5
  <li{% ifequal tab "home" %} class="active"{% endifequal %}>
6
    <a href="{% url pithos.im.views.admin %}">Home</a>
7
  </li>
8
  <li{% ifequal tab "users" %} class="active"{% endifequal %}>
9
    <a href="{% url pithos.im.views.users_list %}">Users</a>
10
  </li>
11
  <li{% ifequal tab "invitations" %} class="active"{% endifequal %}>
12
    <a href="{% url pithos.im.views.invitations_list %}">Invitations</a>
13
  </li>
14
</ul>
15
{% endblock %}
16

  
17
{% block body %}{% endblock %}
b/pithos/im/templates/base.html
14 14
    <div style="padding: 5px 0px 0px 0px">
15 15
      <img src="/im/static/banner.png" width="900" height="200">
16 16
    </div>
17
    {% block title %}{% endblock %}
18
    
19
    {% if message %}
20
    <br />
21
    <div class="alert-message.{{ status }}">
22
      <p>{{ message }}</p>
23
    </div>
24
    {% endif %}
17 25

  
18
    <ul class="tabs">
19
      <li{% ifequal tab "home" %} class="active"{% endifequal %}>
20
        <a href="{% url pithos.im.views.admin %}">Home</a>
21
      </li>
22
      <li{% ifequal tab "users" %} class="active"{% endifequal %}>
23
        <a href="{% url pithos.im.views.users_list %}">Users</a>
24
      </li>
25
      <li{% ifequal tab "invitations" %} class="active"{% endifequal %}>
26
        <a href="{% url pithos.im.views.invitations_list %}">Invitations</a>
27
      </li>
28
    </ul>
26
    {% block tabs %}{% endblock %}
29 27

  
30 28
    {% block body %}{% endblock %}
31 29
  </div>
b/pithos/im/templates/index.html
1
{% extends 'local_base.html'%}
1
{% extends 'base.html'%}
2 2

  
3 3
{% block title%}
4 4
        <h2>Welcome</h2>
b/pithos/im/templates/invitations_list.html
1
{% extends "base.html" %}
1
{% extends "admin_base.html" %}
2 2

  
3 3
{% load formatters %}
4 4

  
/dev/null
1
<!DOCTYPE html>
2
<html>
3
<head>
4
  <meta charset="utf-8" />
5
  <title>{{ title|default:"User Login" }}</title>
6
  <link rel="stylesheet" href="/im/static/bootstrap.css">
7
  <script src="/im/static/jquery.js"></script>
8
  <script src="/im/static/jquery.tablesorter.js"></script>
9
  <script src="/im/static/main.js"></script>
10
</head>
11
<body>
12
  <div class="container">
13
    <div style="padding: 5px 0px 0px 0px">
14
      <img src="/im/static/banner.png" width="900" height="200">
15
    </div>
16
    {% block title%}{% endblock title%}
17
    
18
    {% if message %}
19
    <br />
20
    <div class="alert-message.{{ status }}">
21
      <p>{{ message }}</p>
22
    </div>
23
    {% endif %}
24
    
25
    {% block body%}{% endblock %}
26
  </div>
27
</body>
28
</html>
b/pithos/im/templates/local_create.html
1
{% extends "local_base.html" %}
1
{% extends "base.html" %}
2 2

  
3 3
{% block title%}
4 4
        <h2>Sign up</h2>
b/pithos/im/templates/reclaim.html
1
{% extends "local_base.html" %}
1
{% extends "base.html" %}
2 2

  
3 3
{% block title%}
4 4
        <h2>Reclaim password</h2>
b/pithos/im/templates/reset.html
1
{% extends "local_base.html" %}
1
{% extends "base.html" %}
2 2

  
3 3
{% block title%}
4 4
        <h2>Reset password</h2>
b/pithos/im/templates/users_create.html
1
{% extends "base.html" %}
1
{% extends "admin_base.html" %}
2 2

  
3 3
{% block body %}
4 4

  
b/pithos/im/templates/users_info.html
1
{% extends "base.html" %}
1
{% extends "admin_base.html" %}
2 2

  
3 3
{% load formatters %}
4 4

  
b/pithos/im/templates/users_list.html
1
{% extends "base.html" %}
1
{% extends "admin_base.html" %}
2 2

  
3 3
{% load formatters %}
4 4

  
b/pithos/im/templates/users_profile.html
1
{% extends "base.html" %}
2

  
3
{% load formatters %}
4

  
5
{% block title%}
6
        <h2>User Profile</h2>
7
{% endblock title%}
8

  
9
{% block body %}
10

  
11
<form action="{% url pithos.im.views.users_edit user.id %}" method="post">
12
  <div class="clearfix">
13
    <label for="user-id">ID</label>
14
    <div class="input">
15
      <span class="uneditable-input" id="user-id">{{ user.id }}</span>
16
    </div>
17
  </div>
18

  
19
  <div class="clearfix">
20
    <label for="user-uniq">Uniq</label>
21
    <div class="input">
22
        <span class="uneditable-input" id="user-uniq">{{ user.uniq }}</span>
23
    </div>
24
  </div>
25

  
26
  <div class="clearfix">
27
    <label for="user-realname">Real Name</label>
28
    <div class="input">
29
      <input class="span4" id="user-realname" name="realname" value="{{ user.realname }}" type="text" />
30
    </div>
31
  </div>
32

  
33
  <div class="clearfix">
34
    <label for="user-admin">Admin</label>
35
    <div class="input">
36
      <ul class="inputs-list">
37
        <li>
38
          <label>
39
            <input type="checkbox" id="user-admin" name="admin"{% if user.is_admin %} checked{% endif %} disabled="disabled">
40
          </label>
41
        </li>
42
      </ul>
43
    </div>
44
  </div>
45

  
46
  <div class="clearfix">
47
    <label for="user-affiliation">Affiliation</label>
48
    <div class="input">
49
      <input class="span4" id="user-affiliation" name="affiliation" value="{{ user.affiliation }}" type="text" />
50
    </div>
51
  </div>
52

  
53
  <div class="clearfix">
54
    <label for="user-state">State</label>
55
    <div class="input">
56
      <select class="medium" id="user-state" name="state" disabled="disabled">
57
        {% for state in states %}
58
        <option{% ifequal state user.state %} selected{% endifequal %}>{{ state }}</option>
59
        {% endfor %}
60
      </select>
61
    </div>
62
  </div>
63

  
64
  <div class="clearfix">
65
    <label for="user-invitations">Invitations</label>
66
    <div class="input">
67
        <span class="uneditable-input" id="user-invitations">{{ user.invitations }}</span>
68
    </div>
69
  </div>
70

  
71
  <div class="clearfix">
72
    <label for="user-quota">Quota</label>
73
    <div class="input">
74
      <div class="input-append">
75
        <input class="span2" id="user-quota" name="quota" value="{{ user.quota|GiB }}" type="text" readonly="readonly"/>
76
        <span class="add-on">GiB</span>
77
      </div>
78
    </div>
79
  </div>
80

  
81
  <div class="clearfix">
82
    <label for="user-token">Token</label>
83
    <div class="input">
84
        <span class="uneditable-input" id="user-token">{{ user.auth_token }}</span>
85
    </div>
86
  </div>
87

  
88
  <div class="clearfix">
89
    <label for="token-created">Token Created</label>
90
    <div class="input">
91
      <span class="uneditable-input" id="token-created">{{ user.auth_token_created }}</span>
92
    </div>
93
  </div>
94

  
95
  <div class="clearfix">
96
    <label for="token-expires">Token Expires</label>
97
    <div class="input">
98
      <span class="uneditable-input" id="token-expires">{{ user.auth_token_expires }}</span>
99
    </div>
100
  </div>
101

  
102
  <div class="clearfix">
103
    <label for="user-created">Created</label>
104
    <div class="input">
105
      <span class="uneditable-input" id="user-created">{{ user.created }}</span>
106
    </div>
107
  </div>
108

  
109
  <div class="clearfix">
110
    <label for="user-updated">Updated</label>
111
    <div class="input">
112
      <span class="uneditable-input" id="user-updated">{{ user.updated }}</span>
113
    </div>
114
  </div>
115

  
116
  <div class="actions">
117
    <input type="hidden" name="next" value="{{ next }}">
118
    <button type="submit" class="btn primary">Verify</button>
119
  </div>
120

  
121
</form>
122
{% endblock body %}
b/pithos/im/urls.py
50 50
    
51 51
    (r'^admin/invitations/?$', 'invitations_list'),
52 52
    (r'^admin/invitations/export/?$', 'invitations_export'),
53
    
54
    (r'^profile/(\d+)/?$', 'users_profile'),
55
    (r'^profile/(\d+)/edit/?$', 'users_edit'),
53 56
)
54 57

  
55 58
urlpatterns += patterns('pithos.im.target',
b/pithos/im/views.py
76 76
        return func(request, *args)
77 77
    return wrapper
78 78

  
79
def requires_my_login(func):
80
    @wraps(func)
81
    def wrapper(request, *args):
82
        print '>', request.user, args
83
        if not settings.BYPASS_ADMIN_AUTH:
84
            if not request.user:
85
                next = urlencode({'next': request.build_absolute_uri()})
86
                login_uri = reverse(index) + '?' + next
87
                return HttpResponseRedirect(login_uri)
88
            else:
89
                user = User.objects.get(uniq=request.user)
90
                if user.id != int(args[0]):
91
                    next = urlencode({'next': request.build_absolute_uri()})
92
                    login_uri = reverse(index) + '?' + next
93
                    return HttpResponseRedirect(login_uri)
94
        return func(request, *args)
95
    return wrapper
96

  
79 97

  
80 98
def requires_admin(func):
81 99
    @wraps(func)
......
455 473
        user.renew_token()
456 474
        user.save()
457 475
        return redirect(users_info, user.id)
476

  
477
@requires_my_login
478
def users_profile(request, user_id):
479
    next = request.GET.get('next')
480
    user = User.objects.get(id=user_id)
481
    states = [x[0] for x in User.ACCOUNT_STATE]
482
    return render_response('users_profile.html',
483
                            user=user,
484
                            states=states,
485
                            next=next)
486

  
487
@requires_my_login
488
def users_edit(request, user_id):
489
    user = User.objects.get(id=user_id)
490
    user.realname = request.POST.get('realname')
491
    user.affiliation = request.POST.get('affiliation')
492
    user.is_verified = True
493
    user.save()
494
    next = request.POST.get('next')
495
    if next:
496
        return redirect(next)
497
    
498
    status = 'success'
499
    message = _('Profile has been updated')
500
    html = render_to_string('users_profile.html', {
501
            'user': user,
502
            'status': status,
503
            'message': message})
504
    return HttpResponse(html)
505
    
b/pithos/settings.py.dist
204 204
                            '?username=%s' \
205 205
                            '&next=%s'
206 206

  
207
# Force user profile verification
208
FORCE_PROFILE_UPDATE = False
209

  
207 210
# The server is behind a proy (apache and gunicorn setup).
208 211
USE_X_FORWARDED_HOST = False
209 212

  

Also available in: Unified diff