Revision 890b0eaf
b/astakos/im/admin/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.contrib.auth.forms import UserCreationForm |
|
37 |
from django.conf import settings |
|
38 |
from hashlib import new as newhasher |
|
39 |
|
|
40 |
from astakos.im.models import AstakosUser |
|
41 |
from astakos.im.util import get_or_create_user |
|
42 |
|
|
43 |
class AdminProfileForm(forms.ModelForm): |
|
44 |
""" |
|
45 |
Subclass of ``ModelForm`` for permiting user to edit his/her profile. |
|
46 |
Most of the fields are readonly since the user is not allowed to change them. |
|
47 |
|
|
48 |
The class defines a save method which sets ``is_verified`` to True so as the user |
|
49 |
during the next login will not to be redirected to profile page. |
|
50 |
""" |
|
51 |
class Meta: |
|
52 |
model = AstakosUser |
|
53 |
|
|
54 |
def __init__(self, *args, **kwargs): |
|
55 |
super(AdminProfileForm, self).__init__(*args, **kwargs) |
|
56 |
instance = getattr(self, 'instance', None) |
|
57 |
ro_fields = ('username','date_joined', 'auth_token', 'last_login', 'email') |
|
58 |
if instance and instance.id: |
|
59 |
for field in ro_fields: |
|
60 |
if isinstance(self.fields[field].widget, forms.CheckboxInput): |
|
61 |
self.fields[field].widget.attrs['disabled'] = True |
|
62 |
self.fields[field].widget.attrs['readonly'] = True |
b/astakos/im/admin/templates/pending_users.html | ||
---|---|---|
36 | 36 |
<td>{{ user.email }}</td> |
37 | 37 |
<td>{{ user.inviter.realname }}</td> |
38 | 38 |
<td> |
39 |
<form action="{% url astakos.im.admin.views.users_activate user.id %}" method="post"> |
|
39 |
<form action="{% url astakos.im.admin.views.users_activate user.id %}" method="post">{% csrf_token %}
|
|
40 | 40 |
<input type="hidden" name="page" value="{{ page }}"> |
41 | 41 |
<button type="submit" class="btn primary">Activate</button> |
42 | 42 |
</form> |
b/astakos/im/admin/templates/users_create.html | ||
---|---|---|
2 | 2 |
|
3 | 3 |
{% block body %} |
4 | 4 |
|
5 |
<form action="{% url astakos.im.admin.views.users_create %}" method="post"> |
|
5 |
<form action="{% url astakos.im.admin.views.users_create %}" method="post">{% csrf_token %}
|
|
6 | 6 |
<div class="clearfix"> |
7 | 7 |
<label for="user-username">Username</label> |
8 | 8 |
<div class="input"> |
b/astakos/im/admin/templates/users_info.html | ||
---|---|---|
4 | 4 |
|
5 | 5 |
{% block body %} |
6 | 6 |
|
7 |
<form action="{% url astakos.im.admin.views.users_modify user.id %}" method="post"> |
|
8 |
<div class="clearfix"> |
|
9 |
<label for="user-id">ID</label> |
|
10 |
<div class="input"> |
|
11 |
<span class="uneditable-input" id="user-id">{{ user.id }}</span> |
|
12 |
</div> |
|
13 |
</div> |
|
14 |
|
|
15 |
<div class="clearfix"> |
|
16 |
<label for="user-username">Username</label> |
|
17 |
<div class="input"> |
|
18 |
<input class="span4" id="user-username" name="username" value="{{ user.username }}" type="text" /> |
|
19 |
</div> |
|
20 |
</div> |
|
21 |
|
|
22 |
<div class="clearfix"> |
|
23 |
<label for="user-first-name">First Name</label> |
|
24 |
<div class="input"> |
|
25 |
<input class="span4" id="user-first-name" name="first_name" value="{{ user.first_name }}" type="text" /> |
|
26 |
</div> |
|
27 |
</div> |
|
28 |
|
|
29 |
<div class="clearfix"> |
|
30 |
<label for="user-last-name">Last Name</label> |
|
31 |
<div class="input"> |
|
32 |
<input class="span4" id="user-last-name" name="last_name" value="{{ user.last_name }}" type="text" /> |
|
33 |
</div> |
|
34 |
</div> |
|
35 |
|
|
36 |
<div class="clearfix"> |
|
37 |
<label for="user-admin">Admin</label> |
|
38 |
<div class="input"> |
|
39 |
<ul class="inputs-list"> |
|
40 |
<li> |
|
41 |
<label> |
|
42 |
<input type="checkbox" id="user-admin" name="admin"{% if user.is_superuser %} checked{% endif %}> |
|
43 |
</label> |
|
44 |
</li> |
|
45 |
</ul> |
|
46 |
</div> |
|
47 |
</div> |
|
48 |
|
|
49 |
<div class="clearfix"> |
|
50 |
<label for="user-affiliation">Affiliation</label> |
|
51 |
<div class="input"> |
|
52 |
<input class="span4" id="user-affiliation" name="affiliation" value="{{ user.affiliation }}" type="text" /> |
|
53 |
</div> |
|
54 |
</div> |
|
55 |
|
|
56 |
<div class="clearfix"> |
|
57 |
<label for="user-is-active">Is active?</label> |
|
58 |
<div class="input"> |
|
59 |
<ul class="inputs-list"> |
|
60 |
<li> |
|
61 |
<label> |
|
62 |
<input type="checkbox" id="user-is-active" name="is_active"{% if user.is_active %} checked{% endif %}> |
|
63 |
</label> |
|
64 |
</li> |
|
65 |
</ul> |
|
66 |
</div> |
|
67 |
</div> |
|
68 |
|
|
69 |
<div class="clearfix"> |
|
70 |
<label for="user-invitations">Invitations</label> |
|
71 |
<div class="input"> |
|
72 |
<input class="span2" id="user-invitations" name="invitations" value="{{ user.invitations }}" type="text" /> |
|
73 |
</div> |
|
74 |
</div> |
|
75 |
|
|
76 |
<div class="clearfix"> |
|
77 |
<label for="user-quota">Quota</label> |
|
78 |
<div class="input"> |
|
79 |
<div class="input-append"> |
|
80 |
<input class="span2" id="user-quota" name="quota" value="{{ user.quota|GiB }}" type="text" /> |
|
81 |
<span class="add-on">GiB</span> |
|
82 |
</div> |
|
83 |
</div> |
|
84 |
</div> |
|
85 |
|
|
86 |
<div class="clearfix"> |
|
87 |
<label for="user-token">Token</label> |
|
88 |
<div class="input"> |
|
89 |
<input class="span4" id="user-token" name="auth_token" value="{{ user.auth_token }}" type="text" /> |
|
90 |
</div> |
|
91 |
</div> |
|
92 |
|
|
93 |
<div class="clearfix"> |
|
94 |
<label for="token-created">Token Created</label> |
|
95 |
<div class="input"> |
|
96 |
<span class="uneditable-input" id="token-created">{{ user.auth_token_created }}</span> |
|
97 |
</div> |
|
98 |
</div> |
|
99 |
|
|
100 |
<div class="clearfix"> |
|
101 |
<label for="token-expires">Token Expires</label> |
|
102 |
<div class="input"> |
|
103 |
<input type="datetime" class="span4" id="token-expires" name="auth_token_expires" value="{{ user.auth_token_expires|isoformat }}" /> |
|
104 |
</div> |
|
105 |
</div> |
|
106 |
|
|
107 |
<div class="clearfix"> |
|
108 |
<label for="user-date-joined">Created</label> |
|
109 |
<div class="input"> |
|
110 |
<span class="uneditable-input" id="user-date-joined">{{ user.date_joined }}</span> |
|
111 |
</div> |
|
112 |
</div> |
|
113 |
|
|
114 |
<div class="clearfix"> |
|
115 |
<label for="user-updated">Updated</label> |
|
116 |
<div class="input"> |
|
117 |
<span class="uneditable-input" id="user-updated">{{ user.updated }}</span> |
|
118 |
</div> |
|
119 |
</div> |
|
7 |
<form action="{% url astakos.im.admin.views.users_modify user.id %}" method="post">{% csrf_token %} |
|
8 |
{{ form.as_p }} |
|
120 | 9 |
|
121 | 10 |
<div class="actions"> |
122 | 11 |
<button type="submit" class="btn primary">Save Changes</button> |
b/astakos/im/admin/templates/welcome_email.txt | ||
---|---|---|
1 |
--- A translation in English follows --- |
|
2 |
|
|
3 |
Αγαπητέ/η {{ user.realname }}, |
|
4 |
|
|
5 |
Ο λογαρισμός σας για την υπηρεσία {{ site_name }} της ΕΔΕΤκατά την Alpha (πιλοτική) |
|
6 |
φάση λειτουργίας της έχει ενεργοποιηθεί. |
|
7 |
|
|
8 |
Για να συνδεθείτε, χρησιμοποιήστε τον παρακάτω σύνδεσμο: |
|
9 |
|
|
10 |
{{ url }} |
|
11 |
|
|
12 |
Σημείωση: |
|
13 |
|
|
14 |
Η υπηρεσία θα είναι για μερικές εβδομάδες σε φάση λειτουργίας Alpha. Αν |
|
15 |
και έχουμε κάνει ότι είναι δυνατό για να εξασφαλίσουμε την ποιότητα της |
|
16 |
υπηρεσίας, δεν αποκλείεται να εμφανιστούν προβλήματα στο λογισμικό |
|
17 |
διαχείρισης ή η υπηρεσία να μην είναι διαθέσιμη κατά διαστήματα. Για |
|
18 |
αυτό το λόγο, σας παρακαλούμε να μη μεταφέρετε ακόμη σημαντικά κομμάτια |
|
19 |
της δουλειάς σας στην υπηρεσία {{ site_name }}. Επίσης, παρακαλούμε να έχετε |
|
20 |
υπόψη σας ότι όλα τα δεδομένα, θα διαγραφούν κατά τη μετάβαση από την |
|
21 |
έκδοση Alpha στην έκδοση Beta. Θα υπάρξει έγκαιρη ειδοποίησή σας πριν |
|
22 |
από τη μετάβαση αυτή. |
|
23 |
|
|
24 |
Περισσότερα για την υπηρεσία θα βρείτε στο {{ baseurl }}, αφού |
|
25 |
έχετε ενεργοποιήσει την πρόσκλησή σας. |
|
26 |
|
|
27 |
Αναμένουμε τα σχόλια και τις παρατηρήσεις σας, για να βελτιώσουμε τη |
|
28 |
λειτουργικότητα και την αξιοπιστία της καινοτομικής αυτής υπηρεσίας. |
|
29 |
|
|
30 |
Για όποιες παρατηρήσεις ή προβλήματα στη λειτουργεία της υπηρεσίας μπορείτε να |
|
31 |
απευθυνθείτε στο {{ support }}. |
|
32 |
|
|
33 |
Σας ευχαριστούμε πολύ για τη συμμετοχή στην Alpha λειτουργία του {{ site_name }}. |
|
34 |
|
|
35 |
Με εκτίμηση, |
|
36 |
|
|
37 |
Εθνικό Δίκτυο Έρευνας και Τεχνολογίας (ΕΔΕΤ/GRNET) |
|
38 |
|
|
39 |
-- |
|
40 |
|
|
41 |
Dear {{ user.realname }}, |
|
42 |
|
|
43 |
Your account for GRNET's {{ site_name }} service for its Alpha test phase has been |
|
44 |
activated. |
|
45 |
|
|
46 |
To login, please use the following link: |
|
47 |
|
|
48 |
{{ url }} |
|
49 |
|
|
50 |
Please note: |
|
51 |
|
|
52 |
This service is, for a few weeks, in Alpha. Although every care has been |
|
53 |
taken to ensure high quality, it is possible there may still be software |
|
54 |
bugs, or periods of limited service availability. For this reason, we |
|
55 |
kindly request you do not transfer important parts of your work to |
|
56 |
{{ site_name }}, yet. Also, please bear in mind that all data, will be deleted |
|
57 |
when the service moves to Beta test. Before the transition, you will be |
|
58 |
notified in time. |
|
59 |
|
|
60 |
For more information, please visit {{ baseurl }}, after |
|
61 |
activating your invitation. |
|
62 |
|
|
63 |
We look forward to your feedback, to improve the functionality and |
|
64 |
reliability of this innovative service. |
|
65 |
|
|
66 |
For any remarks or problems you can contact {{ support }}. |
|
67 |
|
|
68 |
Thank you for participating in the Alpha test of {{ site_name }}. |
|
69 |
|
|
70 |
Greek Research and Technonogy Network - GRNET |
b/astakos/im/admin/views.py | ||
---|---|---|
53 | 53 |
from django.utils.http import urlencode |
54 | 54 |
from django.utils.translation import ugettext as _ |
55 | 55 |
from django.core.urlresolvers import reverse |
56 |
from django.contrib import messages |
|
57 |
from django.db import transaction |
|
58 |
from django.contrib.auth.models import AnonymousUser |
|
59 |
from django.contrib.sites.models import get_current_site |
|
56 | 60 |
|
57 |
#from astakos.im.openid_store import PithosOpenIDStore |
|
58 | 61 |
from astakos.im.models import AstakosUser, Invitation |
59 | 62 |
from astakos.im.util import isoformat, get_or_create_user, get_context |
60 | 63 |
from astakos.im.forms import * |
61 | 64 |
from astakos.im.backends import get_backend |
62 | 65 |
from astakos.im.views import render_response, index |
66 |
from astakos.im.admin.forms import AdminProfileForm |
|
63 | 67 |
|
64 | 68 |
def requires_admin(func): |
69 |
""" |
|
70 |
Decorator checkes whether the request.user is a superuser and if not |
|
71 |
redirects to login page. |
|
72 |
""" |
|
65 | 73 |
@wraps(func) |
66 | 74 |
def wrapper(request, *args): |
67 | 75 |
if not settings.BYPASS_ADMIN_AUTH: |
68 |
if not request.user:
|
|
76 |
if isinstance(request.user, AnonymousUser):
|
|
69 | 77 |
next = urlencode({'next': request.build_absolute_uri()}) |
70 | 78 |
login_uri = reverse(index) + '?' + next |
71 | 79 |
return HttpResponseRedirect(login_uri) |
... | ... | |
76 | 84 |
|
77 | 85 |
@requires_admin |
78 | 86 |
def admin(request, template_name='admin.html', extra_context={}): |
87 |
""" |
|
88 |
Renders the admin page |
|
89 |
|
|
90 |
If the ``request.user`` is not a superuser redirects to login page. |
|
91 |
|
|
92 |
**Arguments** |
|
93 |
|
|
94 |
``template_name`` |
|
95 |
A custom template to use. This is optional; if not specified, |
|
96 |
this will default to ``admin.html``. |
|
97 |
|
|
98 |
``extra_context`` |
|
99 |
An dictionary of variables to add to the template context. |
|
100 |
|
|
101 |
**Template:** |
|
102 |
|
|
103 |
admin.html or ``template_name`` keyword argument. |
|
104 |
|
|
105 |
**Template Context:** |
|
106 |
|
|
107 |
The template context is extended by: |
|
108 |
|
|
109 |
* tab: the name of the active tab |
|
110 |
* stats: dictionary containing the number of all and prending users |
|
111 |
""" |
|
79 | 112 |
stats = {} |
80 | 113 |
stats['users'] = AstakosUser.objects.count() |
81 | 114 |
stats['pending'] = AstakosUser.objects.filter(is_active = False).count() |
... | ... | |
85 | 118 |
stats['invitations_consumed'] = invitations.filter(is_consumed=True).count() |
86 | 119 |
|
87 | 120 |
kwargs = {'tab': 'home', 'stats': stats} |
88 |
context = get_context(request, extra_context, **kwargs)
|
|
121 |
context = get_context(request, extra_context,**kwargs) |
|
89 | 122 |
return render_response(template_name, context_instance = context) |
90 | 123 |
|
91 | 124 |
@requires_admin |
92 | 125 |
def users_list(request, template_name='users_list.html', extra_context={}): |
126 |
""" |
|
127 |
Displays the list of all users. |
|
128 |
|
|
129 |
If the ``request.user`` is not a superuser redirects to login page. |
|
130 |
|
|
131 |
**Arguments** |
|
132 |
|
|
133 |
``template_name`` |
|
134 |
A custom template to use. This is optional; if not specified, |
|
135 |
this will default to ``users_list.html``. |
|
136 |
|
|
137 |
``extra_context`` |
|
138 |
An dictionary of variables to add to the template context. |
|
139 |
|
|
140 |
**Template:** |
|
141 |
|
|
142 |
users_list.html or ``template_name`` keyword argument. |
|
143 |
|
|
144 |
**Template Context:** |
|
145 |
|
|
146 |
The template context is extended by: |
|
147 |
|
|
148 |
* users: list of users fitting in current page |
|
149 |
* filter: search key |
|
150 |
* pages: the number of pages |
|
151 |
* prev: the previous page |
|
152 |
* next: the current page |
|
153 |
|
|
154 |
**Settings:** |
|
155 |
|
|
156 |
* ADMIN_PAGE_LIMIT: Show these many users per page in admin interface |
|
157 |
""" |
|
93 | 158 |
users = AstakosUser.objects.order_by('id') |
94 | 159 |
|
95 | 160 |
filter = request.GET.get('filter', '') |
... | ... | |
115 | 180 |
'pages':range(1, npages + 1), |
116 | 181 |
'prev':prev, |
117 | 182 |
'next':next} |
118 |
context = get_context(request, extra_context, **kwargs)
|
|
183 |
context = get_context(request, extra_context,**kwargs) |
|
119 | 184 |
return render_response(template_name, context_instance = context) |
120 | 185 |
|
121 | 186 |
@requires_admin |
122 | 187 |
def users_info(request, user_id, template_name='users_info.html', extra_context={}): |
188 |
""" |
|
189 |
Displays the specific user profile. |
|
190 |
|
|
191 |
If the ``request.user`` is not a superuser redirects to login page. |
|
192 |
|
|
193 |
**Arguments** |
|
194 |
|
|
195 |
``template_name`` |
|
196 |
A custom template to use. This is optional; if not specified, |
|
197 |
this will default to ``users_info.html``. |
|
198 |
|
|
199 |
``extra_context`` |
|
200 |
An dictionary of variables to add to the template context. |
|
201 |
|
|
202 |
**Template:** |
|
203 |
|
|
204 |
users_info.html or ``template_name`` keyword argument. |
|
205 |
|
|
206 |
**Template Context:** |
|
207 |
|
|
208 |
The template context is extended by: |
|
209 |
|
|
210 |
* user: the user instance identified by ``user_id`` keyword argument |
|
211 |
""" |
|
123 | 212 |
if not extra_context: |
124 | 213 |
extra_context = {} |
125 |
kwargs = {'user':AstakosUser.objects.get(id=user_id)} |
|
126 |
context = get_context(request, extra_context, **kwargs) |
|
127 |
return render_response(template_name, context_instance = context) |
|
214 |
user = AstakosUser.objects.get(id=user_id) |
|
215 |
return render_response(template_name, |
|
216 |
form = AdminProfileForm(instance=user), |
|
217 |
context_instance = get_context(request, extra_context)) |
|
128 | 218 |
|
129 | 219 |
@requires_admin |
130 |
def users_modify(request, user_id): |
|
131 |
user = AstakosUser.objects.get(id=user_id) |
|
132 |
user.username = request.POST.get('username') |
|
133 |
user.first_name = request.POST.get('first_name') |
|
134 |
user.last_name = request.POST.get('last_name') |
|
135 |
user.is_superuser = True if request.POST.get('admin') else False |
|
136 |
user.affiliation = request.POST.get('affiliation') |
|
137 |
user.is_active = True if request.POST.get('is_active') else False |
|
138 |
user.invitations = int(request.POST.get('invitations') or 0) |
|
139 |
#user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3) # In GiB |
|
140 |
user.auth_token = request.POST.get('auth_token') |
|
141 |
try: |
|
142 |
auth_token_expires = request.POST.get('auth_token_expires') |
|
143 |
d = datetime.strptime(auth_token_expires, '%Y-%m-%dT%H:%MZ') |
|
144 |
user.auth_token_expires = d |
|
145 |
except ValueError: |
|
146 |
pass |
|
147 |
user.save() |
|
148 |
return redirect(users_info, user.id) |
|
220 |
def users_modify(request, user_id, template_name='users_info.html', extra_context={}): |
|
221 |
""" |
|
222 |
Update the specific user information. Upon success redirects to ``user_info`` view. |
|
223 |
|
|
224 |
If the ``request.user`` is not a superuser redirects to login page. |
|
225 |
""" |
|
226 |
form = AdminProfileForm(request.POST) |
|
227 |
if form.is_valid(): |
|
228 |
form.save() |
|
229 |
return redirect(users_info, user.id, template_name, extra_context) |
|
230 |
return render_response(template_name, |
|
231 |
form = form, |
|
232 |
context_instance = get_context(request, extra_context)) |
|
149 | 233 |
|
150 | 234 |
@requires_admin |
151 | 235 |
def users_delete(request, user_id): |
236 |
""" |
|
237 |
Deletes the specified user |
|
238 |
|
|
239 |
If the ``request.user`` is not a superuser redirects to login page. |
|
240 |
""" |
|
152 | 241 |
user = AstakosUser.objects.get(id=user_id) |
153 | 242 |
user.delete() |
154 | 243 |
return redirect(users_list) |
155 | 244 |
|
156 | 245 |
@requires_admin |
157 | 246 |
def pending_users(request, template_name='pending_users.html', extra_context={}): |
247 |
""" |
|
248 |
Displays the list of the pending users. |
|
249 |
|
|
250 |
If the ``request.user`` is not a superuser redirects to login page. |
|
251 |
|
|
252 |
**Arguments** |
|
253 |
|
|
254 |
``template_name`` |
|
255 |
A custom template to use. This is optional; if not specified, |
|
256 |
this will default to ``users_list.html``. |
|
257 |
|
|
258 |
``extra_context`` |
|
259 |
An dictionary of variables to add to the template context. |
|
260 |
|
|
261 |
**Template:** |
|
262 |
|
|
263 |
pending_users.html or ``template_name`` keyword argument. |
|
264 |
|
|
265 |
**Template Context:** |
|
266 |
|
|
267 |
The template context is extended by: |
|
268 |
|
|
269 |
* users: list of pending users fitting in current page |
|
270 |
* filter: search key |
|
271 |
* pages: the number of pages |
|
272 |
* prev: the previous page |
|
273 |
* next: the current page |
|
274 |
|
|
275 |
**Settings:** |
|
276 |
|
|
277 |
* ADMIN_PAGE_LIMIT: Show these many users per page in admin interface |
|
278 |
""" |
|
158 | 279 |
users = AstakosUser.objects.order_by('id') |
159 | 280 |
|
160 | 281 |
users = users.filter(is_active = False) |
... | ... | |
183 | 304 |
'prev':prev, |
184 | 305 |
'next':next} |
185 | 306 |
return render_response(template_name, |
186 |
context_instance = get_context(request, extra_context, **kwargs))
|
|
307 |
context_instance = get_context(request, extra_context,**kwargs)) |
|
187 | 308 |
|
188 |
def _send_greeting(baseurl, user):
|
|
309 |
def _send_greeting(request, user, template_name):
|
|
189 | 310 |
url = reverse('astakos.im.views.index') |
190 | 311 |
subject = _('Welcome to %s' %settings.SERVICE_NAME) |
191 |
message = render_to_string('welcome.txt', { |
|
312 |
site = get_current_site(request) |
|
313 |
baseurl = request.build_absolute_uri('/').rstrip('/') |
|
314 |
message = render_to_string(template_name, { |
|
192 | 315 |
'user': user, |
193 | 316 |
'url': url, |
194 | 317 |
'baseurl': baseurl, |
195 |
'service': settings.SERVICE_NAME,
|
|
318 |
'site_name': site.name,
|
|
196 | 319 |
'support': settings.DEFAULT_CONTACT_EMAIL}) |
197 | 320 |
sender = settings.DEFAULT_FROM_EMAIL |
198 | 321 |
send_mail(subject, message, sender, [user.email]) |
199 | 322 |
logging.info('Sent greeting %s', user) |
200 | 323 |
|
201 | 324 |
@requires_admin |
202 |
def users_activate(request, user_id, template_name='pending_users.html', extra_context={}): |
|
325 |
@transaction.commit_manually |
|
326 |
def users_activate(request, user_id, template_name='pending_users.html', extra_context={}, email_template_name='welcome_email.txt'): |
|
327 |
""" |
|
328 |
Activates the specific user and sends an email. Upon success renders the |
|
329 |
``template_name`` keyword argument if exists else renders ``pending_users.html``. |
|
330 |
|
|
331 |
If the ``request.user`` is not a superuser redirects to login page. |
|
332 |
|
|
333 |
**Arguments** |
|
334 |
|
|
335 |
``template_name`` |
|
336 |
A custom template to use. This is optional; if not specified, |
|
337 |
this will default to ``users_list.html``. |
|
338 |
|
|
339 |
``extra_context`` |
|
340 |
An dictionary of variables to add to the template context. |
|
341 |
|
|
342 |
**Templates:** |
|
343 |
|
|
344 |
pending_users.html or ``template_name`` keyword argument. |
|
345 |
welcome_email.txt or ``email_template_name`` keyword argument. |
|
346 |
|
|
347 |
**Template Context:** |
|
348 |
|
|
349 |
The template context is extended by: |
|
350 |
|
|
351 |
* users: list of pending users fitting in current page |
|
352 |
* filter: search key |
|
353 |
* pages: the number of pages |
|
354 |
* prev: the previous page |
|
355 |
* next: the current page |
|
356 |
""" |
|
203 | 357 |
user = AstakosUser.objects.get(id=user_id) |
204 | 358 |
user.is_active = True |
205 |
status = 'success' |
|
359 |
user.save() |
|
360 |
status = messages.SUCCESS |
|
206 | 361 |
try: |
207 |
_send_greeting(request.build_absolute_uri('/').rstrip('/'), user)
|
|
362 |
_send_greeting(request, user, email_template_name)
|
|
208 | 363 |
message = _('Greeting sent to %s' % user.email) |
209 |
user.save()
|
|
364 |
transaction.commit()
|
|
210 | 365 |
except (SMTPException, socket.error) as e: |
211 |
status = 'error'
|
|
366 |
status = messages.ERROR
|
|
212 | 367 |
name = 'strerror' |
213 | 368 |
message = getattr(e, name) if hasattr(e, name) else e |
369 |
transaction.rollback() |
|
370 |
messages.add_message(request, status, message) |
|
214 | 371 |
|
215 | 372 |
users = AstakosUser.objects.order_by('id') |
216 | 373 |
users = users.filter(is_active = False) |
... | ... | |
230 | 387 |
'pages':range(1, npages + 1), |
231 | 388 |
'page':page, |
232 | 389 |
'prev':prev, |
233 |
'next':next, |
|
234 |
'message':message} |
|
390 |
'next':next} |
|
235 | 391 |
return render_response(template_name, |
236 |
context_instance = get_context(request, extra_context, **kwargs))
|
|
392 |
context_instance = get_context(request, extra_context,**kwargs)) |
|
237 | 393 |
|
238 | 394 |
@requires_admin |
239 | 395 |
def invitations_list(request, template_name='invitations_list.html', extra_context={}): |
396 |
""" |
|
397 |
Displays a list with the Invitations. |
|
398 |
|
|
399 |
If the ``request.user`` is not a superuser redirects to login page. |
|
400 |
|
|
401 |
**Arguments** |
|
402 |
|
|
403 |
``template_name`` |
|
404 |
A custom template to use. This is optional; if not specified, |
|
405 |
this will default to ``invitations_list.html``. |
|
406 |
|
|
407 |
``extra_context`` |
|
408 |
An dictionary of variables to add to the template context. |
|
409 |
|
|
410 |
**Templates:** |
|
411 |
|
|
412 |
invitations_list.html or ``template_name`` keyword argument. |
|
413 |
|
|
414 |
**Template Context:** |
|
415 |
|
|
416 |
The template context is extended by: |
|
417 |
|
|
418 |
* invitations: list of invitations fitting in current page |
|
419 |
* filter: search key |
|
420 |
* pages: the number of pages |
|
421 |
* prev: the previous page |
|
422 |
* next: the current page |
|
423 |
""" |
|
240 | 424 |
invitations = Invitation.objects.order_by('id') |
241 | 425 |
|
242 | 426 |
filter = request.GET.get('filter', '') |
... | ... | |
263 | 447 |
'prev':prev, |
264 | 448 |
'next':next} |
265 | 449 |
return render_response(template_name, |
266 |
context_instance = get_context(request, extra_context, **kwargs))
|
|
450 |
context_instance = get_context(request, extra_context,**kwargs)) |
|
267 | 451 |
|
268 | 452 |
@requires_admin |
269 | 453 |
def invitations_export(request): |
454 |
""" |
|
455 |
Exports the invitation list in csv file. |
|
456 |
""" |
|
270 | 457 |
# Create the HttpResponse object with the appropriate CSV header. |
271 | 458 |
response = HttpResponse(mimetype='text/csv') |
272 | 459 |
response['Content-Disposition'] = 'attachment; filename=invitations.csv' |
... | ... | |
299 | 486 |
|
300 | 487 |
@requires_admin |
301 | 488 |
def users_export(request): |
489 |
""" |
|
490 |
Exports the user list in csv file. |
|
491 |
""" |
|
302 | 492 |
# Create the HttpResponse object with the appropriate CSV header. |
303 | 493 |
response = HttpResponse(mimetype='text/csv') |
304 | 494 |
response['Content-Disposition'] = 'attachment; filename=users.csv' |
... | ... | |
327 | 517 |
|
328 | 518 |
@requires_admin |
329 | 519 |
def users_create(request, template_name='users_create.html', extra_context={}): |
520 |
""" |
|
521 |
Creates a user. Upon success redirect to ``users_info`` view. |
|
522 |
|
|
523 |
**Arguments** |
|
524 |
|
|
525 |
``template_name`` |
|
526 |
A custom template to use. This is optional; if not specified, |
|
527 |
this will default to ``users_create.html``. |
|
528 |
|
|
529 |
``extra_context`` |
|
530 |
An dictionary of variables to add to the template context. |
|
531 |
|
|
532 |
**Templates:** |
|
533 |
|
|
534 |
users_create.html or ``template_name`` keyword argument. |
|
535 |
""" |
|
330 | 536 |
if request.method == 'GET': |
331 | 537 |
return render_response(template_name, |
332 | 538 |
context_instance=get_context(request, extra_context)) |
... | ... | |
338 | 544 |
user.last_name = request.POST.get('last_name') |
339 | 545 |
user.is_superuser = True if request.POST.get('admin') else False |
340 | 546 |
user.affiliation = request.POST.get('affiliation') |
341 |
user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3) # In GiB
|
|
547 |
user.quota = int(request.POST.get('quota') or 0) * (1024**3) # In GiB
|
|
342 | 548 |
user.renew_token() |
343 | 549 |
user.provider = 'local' |
344 | 550 |
user.save() |
b/astakos/im/api.py | ||
---|---|---|
82 | 82 |
|
83 | 83 |
response = HttpResponse() |
84 | 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') |
|
85 |
user_info = {'uniq':user.username, |
|
86 |
'auth_token':user.auth_token, |
|
87 |
'auth_token_created':user.auth_token_created, |
|
88 |
'auth_token_expires':user.auth_token_expires} |
|
90 | 89 |
response.content = json.dumps(user_info) |
91 | 90 |
update_response_headers(response) |
92 | 91 |
return response |
b/astakos/im/auth_backends.py | ||
---|---|---|
5 | 5 |
|
6 | 6 |
from astakos.im.models import AstakosUser |
7 | 7 |
|
8 |
class AstakosUserModelBackend(ModelBackend): |
|
8 |
class AstakosUserModelCredentialsBackend(ModelBackend):
|
|
9 | 9 |
def authenticate(self, username=None, password=None): |
10 | 10 |
try: |
11 | 11 |
user = AstakosUser.objects.get(username=username) |
... | ... | |
19 | 19 |
return AstakosUser.objects.get(pk=user_id) |
20 | 20 |
except AstakosUser.DoesNotExist: |
21 | 21 |
return None |
22 |
|
|
22 |
|
|
23 | 23 |
#@property |
24 | 24 |
#def user_class(self): |
25 | 25 |
# if not hasattr(self, '_user_class'): |
... | ... | |
28 | 28 |
# print '#', self._user_class |
29 | 29 |
# if not self._user_class: |
30 | 30 |
# raise ImproperlyConfigured('Could not get custom user model') |
31 |
# return self._user_class |
|
31 |
# return self._user_class |
|
32 |
|
|
33 |
class AstakosUserModelTokenBackend(ModelBackend): |
|
34 |
def authenticate(self, username=None, auth_token=None): |
|
35 |
try: |
|
36 |
user = AstakosUser.objects.get(username=username) |
|
37 |
if user.auth_token == auth_token: |
|
38 |
return user |
|
39 |
except AstakosUser.DoesNotExist: |
|
40 |
return None |
|
41 |
|
|
42 |
def get_user(self, user_id): |
|
43 |
try: |
|
44 |
return AstakosUser.objects.get(pk=user_id) |
|
45 |
except AstakosUser.DoesNotExist: |
|
46 |
return None |
b/astakos/im/backends/__init__.py | ||
---|---|---|
33 | 33 |
|
34 | 34 |
from django.conf import settings |
35 | 35 |
from django.utils.importlib import import_module |
36 |
from django.core.exceptions import ImproperlyConfigured |
|
37 |
from django.core.mail import send_mail |
|
38 |
from django.template.loader import render_to_string |
|
39 |
from django.utils.translation import ugettext as _ |
|
40 |
from django.contrib.auth.forms import UserCreationForm |
|
41 |
from django.contrib import messages |
|
42 |
|
|
43 |
from smtplib import SMTPException |
|
44 |
from urllib import quote |
|
45 |
|
|
46 |
from astakos.im.util import get_or_create_user |
|
47 |
from astakos.im.models import AstakosUser, Invitation |
|
48 |
from astakos.im.forms import ExtendedUserCreationForm, InvitedExtendedUserCreationForm |
|
36 | 49 |
|
37 | 50 |
def get_backend(): |
38 | 51 |
""" |
39 | 52 |
Return an instance of a registration backend, |
40 |
according to the INVITATIONS_ENABLED setting. |
|
41 |
|
|
53 |
according to the INVITATIONS_ENABLED setting |
|
54 |
(if True returns ``astakos.im.backends.InvitationsBackend`` and if False |
|
55 |
returns ``astakos.im.backends.SimpleBackend``). |
|
56 |
|
|
57 |
If the backend cannot be located ``django.core.exceptions.ImproperlyConfigured`` |
|
58 |
is raised. |
|
42 | 59 |
""" |
43 |
module = 'invitations' if settings.INVITATIONS_ENABLED else 'simple'
|
|
44 |
module = 'astakos.im.backends.%s' %module
|
|
45 |
backend_class_name = 'Backend'
|
|
60 |
module = 'astakos.im.backends'
|
|
61 |
prefix = 'Invitations' if settings.INVITATIONS_ENABLED else 'Simple'
|
|
62 |
backend_class_name = '%sBackend' %prefix
|
|
46 | 63 |
try: |
47 | 64 |
mod = import_module(module) |
48 | 65 |
except ImportError, e: |
... | ... | |
51 | 68 |
backend_class = getattr(mod, backend_class_name) |
52 | 69 |
except AttributeError: |
53 | 70 |
raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr)) |
54 |
return backend_class() |
|
71 |
return backend_class() |
|
72 |
|
|
73 |
class InvitationsBackend(object): |
|
74 |
""" |
|
75 |
A registration backend which implements the following workflow: a user |
|
76 |
supplies the necessary registation information, if the request contains a valid |
|
77 |
inivation code the user is automatically activated otherwise an inactive user |
|
78 |
account is created and the user is going to receive an email as soon as an |
|
79 |
administrator activates his/her account. |
|
80 |
""" |
|
81 |
def get_signup_form(self, request): |
|
82 |
""" |
|
83 |
Returns the necassary registration form depending the user is invited or not |
|
84 |
|
|
85 |
Throws Invitation.DoesNotExist in case ``code`` is not valid. |
|
86 |
""" |
|
87 |
code = request.GET.get('code', '') |
|
88 |
formclass = 'ExtendedUserCreationForm' |
|
89 |
if request.method == 'GET': |
|
90 |
initial_data = None |
|
91 |
if code: |
|
92 |
formclass = 'Invited%s' %formclass |
|
93 |
self.invitation = Invitation.objects.get(code=code) |
|
94 |
if self.invitation.is_consumed: |
|
95 |
return HttpResponseBadRequest('Invitation has beeen used') |
|
96 |
initial_data.update({'username':self.invitation.username, |
|
97 |
'email':self.invitation.username, |
|
98 |
'realname':self.invitation.realname}) |
|
99 |
inviter = AstakosUser.objects.get(username=self.invitation.inviter) |
|
100 |
initial_data['inviter'] = inviter.realname |
|
101 |
else: |
|
102 |
initial_data = request.POST |
|
103 |
self.form = globals()[formclass](initial_data) |
|
104 |
return self.form |
|
105 |
|
|
106 |
def _is_preaccepted(self, user): |
|
107 |
""" |
|
108 |
If there is a valid, not-consumed invitation code for the specific user |
|
109 |
returns True else returns False. |
|
110 |
|
|
111 |
It should be called after ``get_signup_form`` which sets invitation if exists. |
|
112 |
""" |
|
113 |
invitation = getattr(self, 'invitation') if hasattr(self, 'invitation') else None |
|
114 |
if not invitation: |
|
115 |
return False |
|
116 |
if invitation.username == user.username and not invitation.is_consumed: |
|
117 |
return True |
|
118 |
return False |
|
119 |
|
|
120 |
def signup(self, request): |
|
121 |
""" |
|
122 |
Creates a incative user account. If the user is preaccepted (has a valid |
|
123 |
invitation code) the user is activated and if the request param ``next`` |
|
124 |
is present redirects to it. |
|
125 |
In any other case the method returns the action status and a message. |
|
126 |
""" |
|
127 |
kwargs = {} |
|
128 |
form = self.form |
|
129 |
user = form.save(commit=False) |
|
130 |
|
|
131 |
try: |
|
132 |
if self._is_preaccepted(user): |
|
133 |
user.is_active = True |
|
134 |
message = _('Registration completed. You can now login.') |
|
135 |
next = request.POST.get('next') |
|
136 |
if next: |
|
137 |
return redirect(next) |
|
138 |
else: |
|
139 |
message = _('Registration completed. You will receive an email upon your account\'s activation') |
|
140 |
status = messages.SUCCESS |
|
141 |
except Invitation.DoesNotExist, e: |
|
142 |
status = messages.ERROR |
|
143 |
message = _('Invalid invitation code') |
|
144 |
return status, message |
|
145 |
|
|
146 |
class SimpleBackend(object): |
|
147 |
""" |
|
148 |
A registration backend which implements the following workflow: a user |
|
149 |
supplies the necessary registation information, an incative user account is |
|
150 |
created and receives an email in order to activate his/her account. |
|
151 |
""" |
|
152 |
def get_signup_form(self, request): |
|
153 |
""" |
|
154 |
Returns the UserCreationForm |
|
155 |
""" |
|
156 |
initial_data = request.POST if request.method == 'POST' else None |
|
157 |
return UserCreationForm(initial_data) |
|
158 |
|
|
159 |
def signup(self, request, email_template_name='activation_email.txt'): |
|
160 |
""" |
|
161 |
Creates an inactive user account and sends a verification email. |
|
162 |
|
|
163 |
** Arguments ** |
|
164 |
|
|
165 |
``email_template_name`` |
|
166 |
A custom template for the verification email body to use. This is |
|
167 |
optional; if not specified, this will default to |
|
168 |
``activation_email.txt``. |
|
169 |
|
|
170 |
** Templates ** |
|
171 |
activation_email.txt or ``email_template_name`` keyword argument |
|
172 |
|
|
173 |
** Settings ** |
|
174 |
|
|
175 |
* ACTIVATION_LOGIN_TARGET: Where users should activate their local account |
|
176 |
* DEFAULT_CONTACT_EMAIL: service support email |
|
177 |
* DEFAULT_FROM_EMAIL: from email |
|
178 |
""" |
|
179 |
kwargs = {} |
|
180 |
form = self.form |
|
181 |
user = form.save(commit=False) |
|
182 |
status = messages.SUCCESS |
|
183 |
try: |
|
184 |
_send_verification(request, user, email_template_name) |
|
185 |
message = _('Verification sent to %s' % user.email) |
|
186 |
except (SMTPException, socket.error) as e: |
|
187 |
status = messages.ERROR |
|
188 |
name = 'strerror' |
|
189 |
message = getattr(e, name) if hasattr(e, name) else e |
|
190 |
return status, message |
|
191 |
|
|
192 |
def _send_verification(request, user, template_name): |
|
193 |
site = get_current_site(request) |
|
194 |
baseurl = request.build_absolute_uri('/').rstrip('/') |
|
195 |
url = settings.ACTIVATION_LOGIN_TARGET % (baseurl, |
|
196 |
quote(user.auth_token), |
|
197 |
quote(baseurl)) |
|
198 |
message = render_to_string(template_name, { |
|
199 |
'user': user, |
|
200 |
'url': url, |
|
201 |
'baseurl': baseurl, |
|
202 |
'site_name': site.name, |
|
203 |
'support': settings.DEFAULT_CONTACT_EMAIL}) |
|
204 |
sender = settings.DEFAULT_FROM_EMAIL |
|
205 |
send_mail('Pithos account activation', message, sender, [user.email]) |
|
206 |
logging.info('Sent activation %s', user) |
/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 astakos.im.forms import InvitedLocalRegisterForm, LocalRegisterForm |
|
35 |
from astakos.im.models import AstakosUser, Invitation |
|
36 |
|
|
37 |
class Backend(object): |
|
38 |
def get_signup_form(self, request): |
|
39 |
code = request.GET.get('code', '') |
|
40 |
formclass = 'LocalRegisterForm' |
|
41 |
if request.method == 'GET': |
|
42 |
initial_data = None |
|
43 |
if code: |
|
44 |
formclass = 'InvitedLocalRegiterForm' |
|
45 |
invitation = Invitation.objects.get(code=code) |
|
46 |
if invitation.is_consumed: |
|
47 |
return HttpResponseBadRequest('Invitation has beeen used') |
|
48 |
initial_data.update({'username':invitation.username, |
|
49 |
'email':invitation.username, |
|
50 |
'realname':invitation.realname}) |
|
51 |
inviter = AstakosUser.objects.get(username=invitation.inviter) |
|
52 |
initial_data['inviter'] = inviter.realname |
|
53 |
else: |
|
54 |
initial_data = request.POST |
|
55 |
return globals()[formclass](initial_data) |
|
56 |
|
|
57 |
def is_preaccepted(user, code): |
|
58 |
invitation = self.invitation |
|
59 |
if invitation and not invitation.is_consumed and invitation.code == code: |
|
60 |
return True |
|
61 |
return False |
|
62 |
|
|
63 |
def signup(self, request, form): |
|
64 |
kwargs = {} |
|
65 |
for field in form.fields: |
|
66 |
if hasattr(AstakosUser(), field): |
|
67 |
kwargs[field] = form.cleaned_data[field] |
|
68 |
user = get_or_create_user(**kwargs) |
|
69 |
|
|
70 |
code = request.POST.get('code') |
|
71 |
if is_preaccepted(user, code): |
|
72 |
user.is_active = True |
|
73 |
user.save() |
|
74 |
message = _('Registration completed. You can now login.') |
|
75 |
next = request.POST.get('next') |
|
76 |
if next: |
|
77 |
return redirect(next) |
|
78 |
else: |
|
79 |
message = _('Registration completed. You will receive an email upon your account\'s activation') |
|
80 |
status = 'success' |
|
81 |
return status, message |
/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 |
import socket |
|
34 |
import logging |
|
35 |
|
|
36 |
from django.core.mail import send_mail |
|
37 |
from django.conf import settings |
|
38 |
from django.template.loader import render_to_string |
|
39 |
from django.utils.translation import ugettext as _ |
|
40 |
from smtplib import SMTPException |
|
41 |
from urllib import quote |
|
42 |
|
|
43 |
from astakos.im.forms import LocalRegisterForm |
|
44 |
from astakos.im.util import get_or_create_user |
|
45 |
from astakos.im.models import AstakosUser |
|
46 |
|
|
47 |
class Backend(object): |
|
48 |
def get_signup_form(self, request): |
|
49 |
initial_data = request.POST if request.method == 'POST' else None |
|
50 |
return LocalRegisterForm(initial_data) |
|
51 |
|
|
52 |
def signup(self, request, form, success_url): |
|
53 |
kwargs = {} |
|
54 |
for field in form.fields: |
|
55 |
if hasattr(AstakosUser(), field): |
|
56 |
kwargs[field] = form.cleaned_data[field] |
|
57 |
user = get_or_create_user(**kwargs) |
|
58 |
|
|
59 |
status = 'success' |
|
60 |
try: |
|
61 |
send_verification(request.build_absolute_uri('/').rstrip('/'), user) |
|
62 |
message = _('Verification sent to %s' % user.email) |
|
63 |
except (SMTPException, socket.error) as e: |
|
64 |
status = 'error' |
|
65 |
name = 'strerror' |
|
66 |
message = getattr(e, name) if hasattr(e, name) else e |
|
67 |
|
|
68 |
if user and status == 'error': |
|
69 |
#delete created user |
|
70 |
user.delete() |
|
71 |
return status, message |
|
72 |
|
|
73 |
def send_verification(baseurl, user): |
|
74 |
url = settings.ACTIVATION_LOGIN_TARGET % (baseurl, |
|
75 |
quote(user.auth_token), |
|
76 |
quote(baseurl)) |
|
77 |
message = render_to_string('activation.txt', { |
|
78 |
'user': user, |
|
79 |
'url': url, |
|
80 |
'baseurl': baseurl, |
|
81 |
'service': settings.SERVICE_NAME, |
|
82 |
'support': settings.DEFAULT_CONTACT_EMAIL}) |
|
83 |
sender = settings.DEFAULT_FROM_EMAIL |
|
84 |
send_mail('Pithos account activation', message, sender, [user.email]) |
|
85 |
logging.info('Sent activation %s', user) |
b/astakos/im/context_processors.py | ||
---|---|---|
41 | 41 |
|
42 | 42 |
def code(request): |
43 | 43 |
return {'code' : request.GET.get('code', '')} |
44 |
|
|
45 |
def invitations(request): |
|
46 |
return {'invitations_enabled' :settings.INVITATIONS_ENABLED} |
|
44 | 47 |
|
b/astakos/im/fixtures/auth_test_data.json | ||
---|---|---|
1 | 1 |
[ |
2 | 2 |
{ |
3 | 3 |
"model": "im.AstakosUser", |
4 |
"pk": 1,
|
|
4 |
"pk": 2,
|
|
5 | 5 |
"fields": { |
6 | 6 |
"username": "test", |
7 | 7 |
"level": 0, |
... | ... | |
15 | 15 |
}, |
16 | 16 |
{ |
17 | 17 |
"model": "im.AstakosUser", |
18 |
"pk": 2,
|
|
18 |
"pk": 3,
|
|
19 | 19 |
"fields": { |
20 | 20 |
"username": "verigak", |
21 | 21 |
"level": 1, |
... | ... | |
30 | 30 |
}, |
31 | 31 |
{ |
32 | 32 |
"model": "im.AstakosUser", |
33 |
"pk": 3,
|
|
33 |
"pk": 4,
|
|
34 | 34 |
"fields": { |
35 | 35 |
"username": "chazapis", |
36 | 36 |
"level": 1, |
... | ... | |
44 | 44 |
}, |
45 | 45 |
{ |
46 | 46 |
"model": "im.AstakosUser", |
47 |
"pk": 4,
|
|
47 |
"pk": 5,
|
|
48 | 48 |
"fields": { |
49 | 49 |
"username": "gtsouk", |
50 | 50 |
"level": 1, |
... | ... | |
58 | 58 |
}, |
59 | 59 |
{ |
60 | 60 |
"model": "im.AstakosUser", |
61 |
"pk": 5,
|
|
61 |
"pk": 6,
|
|
62 | 62 |
"fields": { |
63 | 63 |
"username": "papagian", |
64 | 64 |
"level": 1, |
... | ... | |
72 | 72 |
}, |
73 | 73 |
{ |
74 | 74 |
"model": "im.AstakosUser", |
75 |
"pk": 6,
|
|
75 |
"pk": 7,
|
|
76 | 76 |
"fields": { |
77 | 77 |
"username": "louridas", |
78 | 78 |
"level": 1, |
... | ... | |
86 | 86 |
}, |
87 | 87 |
{ |
88 | 88 |
"model": "im.AstakosUser", |
89 |
"pk": 7,
|
|
89 |
"pk": 8,
|
|
90 | 90 |
"fields": { |
91 | 91 |
"username": "chstath", |
92 | 92 |
"level": 1, |
... | ... | |
100 | 100 |
}, |
101 | 101 |
{ |
102 | 102 |
"model": "im.AstakosUser", |
103 |
"pk": 8,
|
|
103 |
"pk": 9,
|
|
104 | 104 |
"fields": { |
105 | 105 |
"username": "pkanavos", |
106 | 106 |
"level": 1, |
... | ... | |
114 | 114 |
}, |
115 | 115 |
{ |
116 | 116 |
"model": "im.AstakosUser", |
117 |
"pk": 9,
|
|
117 |
"pk": 10,
|
|
118 | 118 |
"fields": { |
119 | 119 |
"username": "mvasilak", |
120 | 120 |
"level": 1, |
... | ... | |
128 | 128 |
}, |
129 | 129 |
{ |
130 | 130 |
"model": "im.AstakosUser", |
131 |
"pk": 10,
|
|
131 |
"pk": 11,
|
|
132 | 132 |
"fields": { |
133 | 133 |
"username": "διογένης", |
134 | 134 |
"level": 2, |
b/astakos/im/forms.py | ||
---|---|---|
33 | 33 |
|
34 | 34 |
from django import forms |
35 | 35 |
from django.utils.translation import ugettext as _ |
36 |
from django.contrib.auth.forms import UserCreationForm |
|
36 | 37 |
from django.conf import settings |
37 | 38 |
from hashlib import new as newhasher |
38 | 39 |
|
39 | 40 |
from astakos.im.models import AstakosUser |
41 |
from astakos.im.util import get_or_create_user |
|
40 | 42 |
|
41 |
class RegisterForm(forms.Form): |
|
42 |
username = forms.CharField(widget=forms.widgets.TextInput()) |
|
43 |
email = forms.EmailField(widget=forms.TextInput(), |
|
44 |
label=_('Email address')) |
|
45 |
first_name = forms.CharField(widget=forms.TextInput(), |
|
46 |
label=u'First Name', required=False) |
|
47 |
last_name = forms.CharField(widget=forms.TextInput(), |
|
48 |
label=u'Last Name', required=False) |
|
49 |
|
|
50 |
def __init__(self, *args, **kwargs): |
|
51 |
super(forms.Form, self).__init__(*args, **kwargs) |
|
52 |
|
|
53 |
def clean_username(self): |
|
54 |
""" |
|
55 |
Validate that the username is alphanumeric and is not already |
|
56 |
in use. |
|
57 |
|
|
58 |
""" |
|
43 |
class UniqueUserEmailField(forms.EmailField): |
|
44 |
""" |
|
45 |
An EmailField which only is valid if no User has that email. |
|
46 |
""" |
|
47 |
def validate(self, value): |
|
48 |
super(forms.EmailField, self).validate(value) |
|
59 | 49 |
try: |
60 |
user = AstakosUser.objects.get(username__iexact=self.cleaned_data['username']) |
|
50 |
AstakosUser.objects.get(email = value) |
|
51 |
raise forms.ValidationError("Email already exists") |
|
52 |
except AstakosUser.MultipleObjectsReturned: |
|
53 |
raise forms.ValidationError("Email already exists") |
|
61 | 54 |
except AstakosUser.DoesNotExist: |
62 |
return self.cleaned_data['username'] |
|
63 |
raise forms.ValidationError(_("A user with that username already exists.")) |
|
55 |
pass |
|
64 | 56 |
|
65 |
class LocalRegisterForm(RegisterForm): |
|
66 |
""" local signup form""" |
|
67 |
password = forms.CharField(widget=forms.PasswordInput(render_value=False), |
|
68 |
label=_('Password')) |
|
69 |
password2 = forms.CharField(widget=forms.PasswordInput(render_value=False), |
|
70 |
label=_('Confirm Password')) |
|
57 |
class ExtendedUserCreationForm(UserCreationForm): |
|
58 |
""" |
|
59 |
Extends the built in UserCreationForm in several ways: |
|
71 | 60 |
|
72 |
def __init__(self, *args, **kwargs): |
|
73 |
super(LocalRegisterForm, self).__init__(*args, **kwargs) |
|
61 |
* Adds an email field, which uses the custom UniqueUserEmailField, |
|
62 |
that is, the form does not validate if the email address already exists |
|
63 |
in the User table. |
|
64 |
* The username field is generated based on the email, and isn't visible. |
|
65 |
* first_name and last_name fields are added. |
|
66 |
* Data not saved by the default behavior of UserCreationForm is saved. |
|
67 |
""" |
|
68 |
|
|
69 |
username = forms.CharField(required = False, max_length = 30) |
|
70 |
email = UniqueUserEmailField(required = True, label = 'Email address') |
|
71 |
first_name = forms.CharField(required = False, max_length = 30) |
|
72 |
last_name = forms.CharField(required = False, max_length = 30) |
|
74 | 73 |
|
75 |
def clean_username(self):
|
|
74 |
def __init__(self, *args, **kwargs):
|
|
76 | 75 |
""" |
77 |
Validate that the username is alphanumeric and is not already |
|
78 |
in use. |
|
79 |
|
|
76 |
Changes the order of fields, and removes the username field. |
|
80 | 77 |
""" |
81 |
try: |
|
82 |
user = AstakosUser.objects.get(username__iexact=self.cleaned_data['username']) |
|
83 |
except AstakosUser.DoesNotExist: |
|
84 |
return self.cleaned_data['username'] |
|
85 |
raise forms.ValidationError(_("A user with that username already exists.")) |
|
78 |
super(UserCreationForm, self).__init__(*args, **kwargs) |
|
79 |
self.fields.keyOrder = ['email', 'first_name', 'last_name', |
|
80 |
'password1', 'password2'] |
|
86 | 81 |
|
87 |
def clean(self): |
|
82 |
def clean(self, *args, **kwargs): |
|
83 |
""" |
|
84 |
Normal cleanup + username generation. |
|
88 | 85 |
""" |
89 |
Verifiy that the values entered into the two password fields |
|
90 |
match. Note that an error here will end up in |
|
91 |
``non_field_errors()`` because it doesn't apply to a single |
|
92 |
field. |
|
86 |
cleaned_data = super(UserCreationForm, self).clean(*args, **kwargs) |
|
87 |
if cleaned_data.has_key('email'): |
|
88 |
#cleaned_data['username'] = self.__generate_username( |
|
89 |
# cleaned_data['email']) |
|
90 |
cleaned_data['username'] = cleaned_data['email'] |
|
91 |
return cleaned_data |
|
93 | 92 |
|
93 |
def save(self, commit=True): |
|
94 | 94 |
""" |
95 |
if 'password' in self.cleaned_data and 'password2' in self.cleaned_data: |
|
96 |
if self.cleaned_data['password'] != self.cleaned_data['password2']: |
|
97 |
raise forms.ValidationError(_("The two password fields didn't match.")) |
|
98 |
return self.cleaned_data |
|
95 |
Saves the email, first_name and last_name properties, after the normal |
|
96 |
save behavior is complete. |
|
97 |
""" |
|
98 |
user = super(UserCreationForm, self).save(commit) |
|
99 |
if user: |
|
100 |
kwargs = {} |
|
101 |
for field in self.fields: |
|
102 |
if hasattr(AstakosUser(), field): |
|
103 |
kwargs[field] = self.cleaned_data[field] |
|
104 |
user = get_or_create_user(username=self.cleaned_data['email'], **kwargs) |
|
105 |
return user |
|
99 | 106 |
|
100 |
class InvitedRegisterForm(RegisterForm): |
|
107 |
class InvitedExtendedUserCreationForm(ExtendedUserCreationForm): |
|
108 |
""" |
|
109 |
Subclass of ``RegistrationForm`` for registring a invited user. Adds a |
|
110 |
readonly field for inviter's name. The email is also readonly since |
|
111 |
it will be the invitation username. |
|
112 |
""" |
|
101 | 113 |
inviter = forms.CharField(widget=forms.TextInput(), |
102 | 114 |
label=_('Inviter Real Name')) |
103 | 115 |
|
... | ... | |
105 | 117 |
super(RegisterForm, self).__init__(*args, **kwargs) |
106 | 118 |
|
107 | 119 |
#set readonly form fields |
108 |
self.fields['username'].widget.attrs['readonly'] = True |
|
109 | 120 |
self.fields['inviter'].widget.attrs['readonly'] = True |
110 | 121 |
|
111 |
class InvitedLocalRegisterForm(LocalRegisterForm, InvitedRegisterForm): |
|
112 |
pass |
|
122 |
class ProfileForm(forms.ModelForm): |
|
123 |
""" |
|
124 |
Subclass of ``ModelForm`` for permiting user to edit his/her profile. |
|
125 |
Most of the fields are readonly since the user is not allowed to change them. |
|
126 |
|
|
127 |
The class defines a save method which sets ``is_verified`` to True so as the user |
|
128 |
during the next login will not to be redirected to profile page. |
|
129 |
""" |
|
130 |
class Meta: |
|
131 |
model = AstakosUser |
|
132 |
exclude = ('groups', 'user_permissions') |
|
133 |
|
|
134 |
def __init__(self, *args, **kwargs): |
|
135 |
super(ProfileForm, self).__init__(*args, **kwargs) |
|
136 |
instance = getattr(self, 'instance', None) |
|
137 |
ro_fields = ('username','date_joined', 'updated', 'auth_token', |
|
138 |
'auth_token_created', 'auth_token_expires', 'invitations', |
|
139 |
'level', 'last_login', 'email', 'is_active', 'is_superuser', |
|
140 |
'is_staff') |
|
141 |
if instance and instance.id: |
|
142 |
for field in ro_fields: |
|
143 |
if isinstance(self.fields[field].widget, forms.CheckboxInput): |
|
144 |
self.fields[field].widget.attrs['disabled'] = True |
|
145 |
self.fields[field].widget.attrs['readonly'] = True |
|
146 |
|
|
147 |
def save(self, commit=True): |
|
148 |
user = super(ProfileForm, self).save(commit=False) |
|
149 |
user.is_verified = True |
|
150 |
if commit: |
|
151 |
user.save() |
|
152 |
return user |
|
153 |
|
|
113 | 154 |
|
114 |
class LoginForm(forms.Form): |
|
115 |
username = forms.CharField(widget=forms.widgets.TextInput()) |
|
116 |
password = forms.CharField(widget=forms.PasswordInput(render_value=False), |
|
117 |
label=_('Password')) |
|
155 |
class FeedbackForm(forms.Form): |
|
156 |
""" |
|
157 |
Form for writing feedback. |
|
158 |
""" |
|
159 |
feedback_msg = forms.CharField(widget=forms.Textarea(), |
|
160 |
label=u'Message', required=False) |
|
161 |
feedback_data = forms.CharField(widget=forms.Textarea(), |
|
162 |
label=u'Data', required=False) |
|
163 |
|
b/astakos/im/models.py | ||
---|---|---|
45 | 45 |
from astakos.im.interface import get_quota, set_quota |
46 | 46 |
|
47 | 47 |
class AstakosUser(User): |
48 |
""" |
|
49 |
Extends ``django.contrib.auth.models.User`` by defining additional fields. |
|
50 |
""" |
|
48 | 51 |
# Use UserManager to get the create_user method, etc. |
49 | 52 |
objects = UserManager() |
50 | 53 |
|
... | ... | |
61 | 64 |
auth_token_expires = models.DateTimeField('Token expiration date', null=True) |
62 | 65 |
|
63 | 66 |
updated = models.DateTimeField('Update date') |
67 |
is_verified = models.BooleanField('Is verified?', default=False) |
|
64 | 68 |
|
65 | 69 |
@property |
66 | 70 |
def realname(self): |
... | ... | |
117 | 121 |
return self.username |
118 | 122 |
|
119 | 123 |
class Invitation(models.Model): |
124 |
""" |
|
125 |
Model for registring invitations |
|
126 |
""" |
|
120 | 127 |
inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent', |
121 | 128 |
null=True) |
122 | 129 |
realname = models.CharField('Real name', max_length=255) |
b/astakos/im/target/invitation.py | ||
---|---|---|
37 | 37 |
|
38 | 38 |
from django.conf import settings |
39 | 39 |
from django.http import HttpResponseBadRequest |
40 |
from django.contrib.auth import authenticate |
Also available in: Unified diff