Revision 18ffbee1
b/docs/source/devguide.rst | ||
---|---|---|
165 | 165 |
|
166 | 166 |
:: |
167 | 167 |
|
168 |
{"username": "4ad9f34d6e7a4992b34502d40f40cb",
|
|
169 |
"uniq": "papagian@example.com"
|
|
170 |
"auth_token": "0000",
|
|
171 |
"auth_token_expires": "Tue, 11-Sep-2012 09:17:14 ",
|
|
172 |
"auth_token_created": "Sun, 11-Sep-2011 09:17:14 ",
|
|
173 |
"has_credits": false,
|
|
174 |
"has_signed_terms": true}
|
|
168 |
{"userid": "270d191e09834408b7af65885f46a3",
|
|
169 |
"email": ["user111@example.com"],
|
|
170 |
"name": "user1 User1",
|
|
171 |
"auth_token_created": 1333372365000,
|
|
172 |
"auth_token_expires": 1335964365000,
|
|
173 |
"auth_token": "uiWDLAgtJOGW4mI4q9R/8w==",
|
|
174 |
"has_credits": true}
|
|
175 | 175 |
|
176 | 176 |
| |
177 | 177 |
|
b/snf-astakos-app/README | ||
---|---|---|
83 | 83 |
=============== =========================== |
84 | 84 |
Name Description |
85 | 85 |
=============== =========================== |
86 |
activateuser Activates one or more users |
|
86 |
addgroup Add new group |
|
87 |
addterms Add new approval terms |
|
87 | 88 |
createuser Create a user |
88 | 89 |
inviteuser Invite a user |
90 |
listgroups List groups |
|
89 | 91 |
listinvitations List invitations |
90 | 92 |
listusers List users |
91 | 93 |
modifyuser Modify a user's attributes |
94 |
sendactivation Send activation email |
|
92 | 95 |
showinvitation Show invitation info |
93 | 96 |
showuser Show user info |
94 |
addterms Add new approval terms |
|
95 | 97 |
=============== =========================== |
98 |
|
|
99 |
To update user credibility from the billing system (Aquarium), enable the queue, install snf-pithos-tools and use ``pithos-dispatcher``:: |
|
100 |
|
|
101 |
pithos-dispatcher --exchange=aquarium --callback=astakos.im.queue.listener.on_creditevent |
|
102 |
|
|
103 |
Load groups: |
|
104 |
------------ |
|
105 |
|
|
106 |
To set the initial user groups load the followind fixture: |
|
107 |
|
|
108 |
snf-manage loaddata groups |
b/snf-astakos-app/astakos/im/activation_backends.py | ||
---|---|---|
35 | 35 |
from django.core.exceptions import ImproperlyConfigured |
36 | 36 |
from django.core.mail import send_mail |
37 | 37 |
from django.template.loader import render_to_string |
38 |
from django.utils.translation import ugettext as _ |
|
39 | 38 |
from django.contrib.sites.models import Site |
40 | 39 |
from django.contrib import messages |
41 |
from django.db import transaction |
|
42 | 40 |
from django.core.urlresolvers import reverse |
41 |
from django.utils.translation import ugettext as _ |
|
42 |
from django.db import transaction |
|
43 | 43 |
|
44 | 44 |
from urlparse import urljoin |
45 | 45 |
|
... | ... | |
57 | 57 |
|
58 | 58 |
def get_backend(request): |
59 | 59 |
""" |
60 |
Returns an instance of a registration backend,
|
|
60 |
Returns an instance of an activation backend,
|
|
61 | 61 |
according to the INVITATIONS_ENABLED setting |
62 | 62 |
(if True returns ``astakos.im.activation_backends.InvitationsBackend`` and if False |
63 | 63 |
returns ``astakos.im.activation_backends.SimpleBackend``). |
... | ... | |
71 | 71 |
try: |
72 | 72 |
mod = import_module(module) |
73 | 73 |
except ImportError, e: |
74 |
raise ImproperlyConfigured('Error loading registration backend %s: "%s"' % (module, e))
|
|
74 |
raise ImproperlyConfigured('Error loading activation backend %s: "%s"' % (module, e))
|
|
75 | 75 |
try: |
76 | 76 |
backend_class = getattr(mod, backend_class_name) |
77 | 77 |
except AttributeError: |
78 |
raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
|
|
78 |
raise ImproperlyConfigured('Module "%s" does not define a activation backend named "%s"' % (module, attr))
|
|
79 | 79 |
return backend_class(request) |
80 | 80 |
|
81 | 81 |
class SignupBackend(object): |
... | ... | |
88 | 88 |
|
89 | 89 |
class InvitationsBackend(SignupBackend): |
90 | 90 |
""" |
91 |
A registration backend which implements the following workflow: a user
|
|
91 |
A activation backend which implements the following workflow: a user
|
|
92 | 92 |
supplies the necessary registation information, if the request contains a valid |
93 | 93 |
inivation code the user is automatically activated otherwise an inactive user |
94 | 94 |
account is created and the user is going to receive an email as soon as an |
... | ... | |
110 | 110 |
invitation = self.invitation |
111 | 111 |
initial_data = self.get_signup_initial_data(provider) |
112 | 112 |
prefix = 'Invited' if invitation else '' |
113 |
main = provider.capitalize() if provider == 'local' else 'ThirdParty'
|
|
113 |
main = provider.capitalize() |
|
114 | 114 |
suffix = 'UserCreationForm' |
115 | 115 |
formclass = '%s%s%s' % (prefix, main, suffix) |
116 | 116 |
ip = self.request.META.get('REMOTE_ADDR', |
... | ... | |
119 | 119 |
|
120 | 120 |
def get_signup_initial_data(self, provider): |
121 | 121 |
""" |
122 |
Returns the necassary registration form depending the user is invited or not
|
|
122 |
Returns the necassary activation form depending the user is invited or not
|
|
123 | 123 |
|
124 | 124 |
Throws Invitation.DoesNotExist in case ``code`` is not valid. |
125 | 125 |
""" |
... | ... | |
131 | 131 |
# create a tmp user with the invitation realname |
132 | 132 |
# to extract first and last name |
133 | 133 |
u = AstakosUser(realname = invitation.realname) |
134 |
print '>>>', invitation, invitation.inviter |
|
134 | 135 |
initial_data = {'email':invitation.username, |
135 | 136 |
'inviter':invitation.inviter.realname, |
136 | 137 |
'first_name':u.first_name, |
... | ... | |
155 | 156 |
return True |
156 | 157 |
return False |
157 | 158 |
|
158 |
@transaction.commit_manually |
|
159 | 159 |
def handle_activation(self, user, verification_template_name='im/activation_email.txt', greeting_template_name='im/welcome_email.txt', admin_email_template_name='im/admin_notification.txt'): |
160 | 160 |
""" |
161 | 161 |
Initially creates an inactive user account. If the user is preaccepted |
... | ... | |
166 | 166 |
The method uses commit_manually decorator in order to ensure the user |
167 | 167 |
will be created only if the procedure has been completed successfully. |
168 | 168 |
""" |
169 |
result = None |
|
170 | 169 |
try: |
170 |
if user.is_active: |
|
171 |
return RegistationCompleted() |
|
171 | 172 |
if self._is_preaccepted(user): |
172 | 173 |
if user.email_verified: |
173 | 174 |
activate(user, greeting_template_name) |
174 |
result = RegistationCompleted()
|
|
175 |
return RegistationCompleted()
|
|
175 | 176 |
else: |
176 | 177 |
send_verification(user, verification_template_name) |
177 |
result = VerificationSent()
|
|
178 |
return VerificationSent()
|
|
178 | 179 |
else: |
179 | 180 |
send_admin_notification(user, admin_email_template_name) |
180 |
result = NotificationSent()
|
|
181 |
return NotificationSent()
|
|
181 | 182 |
except Invitation.DoesNotExist, e: |
182 | 183 |
raise InvitationCodeError() |
183 |
else: |
|
184 |
return result |
|
184 |
except BaseException, e: |
|
185 |
logger.exception(e) |
|
186 |
raise e |
|
185 | 187 |
|
186 | 188 |
class SimpleBackend(SignupBackend): |
187 | 189 |
""" |
188 |
A registration backend which implements the following workflow: a user
|
|
190 |
A activation backend which implements the following workflow: a user
|
|
189 | 191 |
supplies the necessary registation information, an incative user account is |
190 | 192 |
created and receives an email in order to activate his/her account. |
191 | 193 |
""" |
... | ... | |
238 | 240 |
* DEFAULT_CONTACT_EMAIL: service support email |
239 | 241 |
* DEFAULT_FROM_EMAIL: from email |
240 | 242 |
""" |
241 |
result = None |
|
242 |
if not self._is_preaccepted(user): |
|
243 |
send_admin_notification(user, admin_email_template_name) |
|
244 |
result = NotificationSent() |
|
243 |
try: |
|
244 |
if user.is_active: |
|
245 |
return RegistrationCompeted() |
|
246 |
if not self._is_preaccepted(user): |
|
247 |
send_admin_notification(user, admin_email_template_name) |
|
248 |
return NotificationSent() |
|
249 |
else: |
|
250 |
send_verification(user, email_template_name) |
|
251 |
return VerificationSend() |
|
252 |
except SendEmailError, e: |
|
253 |
transaction.rollback() |
|
254 |
raise e |
|
255 |
except BaseException, e: |
|
256 |
logger.exception(e) |
|
257 |
raise e |
|
245 | 258 |
else: |
246 |
send_verification(user, email_template_name) |
|
247 |
result = VerificationSend() |
|
248 |
return result |
|
259 |
transaction.commit() |
|
249 | 260 |
|
250 | 261 |
class ActivationResult(object): |
251 | 262 |
def __init__(self, message): |
... | ... | |
266 | 277 |
class RegistationCompleted(ActivationResult): |
267 | 278 |
def __init__(self): |
268 | 279 |
message = _('Registration completed. You can now login.') |
269 |
super(RegistationCompleted, self).__init__(message) |
|
270 |
|
|
271 |
|
|
280 |
super(RegistationCompleted, self).__init__(message) |
b/snf-astakos-app/astakos/im/api.py | ||
---|---|---|
33 | 33 |
|
34 | 34 |
import logging |
35 | 35 |
|
36 |
from functools import wraps |
|
36 | 37 |
from traceback import format_exc |
37 | 38 |
from time import time, mktime |
38 | 39 |
from urllib import quote |
... | ... | |
43 | 44 |
from django.utils import simplejson as json |
44 | 45 |
from django.core.urlresolvers import reverse |
45 | 46 |
|
46 |
from astakos.im.faults import BadRequest, Unauthorized, InternalServerError |
|
47 |
from astakos.im.faults import BadRequest, Unauthorized, InternalServerError, Fault
|
|
47 | 48 |
from astakos.im.models import AstakosUser |
48 | 49 |
from astakos.im.settings import CLOUD_SERVICES, INVITATIONS_ENABLED |
49 |
from astakos.im.util import has_signed_terms |
|
50 |
from astakos.im.util import has_signed_terms, epoch
|
|
50 | 51 |
|
51 | 52 |
logger = logging.getLogger(__name__) |
52 | 53 |
|
... | ... | |
62 | 63 |
response['Content-Length'] = len(response.content) |
63 | 64 |
return response |
64 | 65 |
|
65 |
def authenticate(request): |
|
66 |
def api_method(http_method=None, token_required=False, perms=[]): |
|
67 |
"""Decorator function for views that implement an API method.""" |
|
68 |
|
|
69 |
def decorator(func): |
|
70 |
@wraps(func) |
|
71 |
def wrapper(request, *args, **kwargs): |
|
72 |
try: |
|
73 |
if http_method and request.method != http_method: |
|
74 |
raise BadRequest('Method not allowed.') |
|
75 |
x_auth_token = request.META.get('HTTP_X_AUTH_TOKEN') |
|
76 |
if token_required: |
|
77 |
if not x_auth_token: |
|
78 |
raise Unauthorized('Access denied') |
|
79 |
try: |
|
80 |
user = AstakosUser.objects.get(auth_token=x_auth_token) |
|
81 |
if not user.has_perms(perms): |
|
82 |
raise Unauthorized('Unauthorized request') |
|
83 |
except AstakosUser.DoesNotExist, e: |
|
84 |
raise Unauthorized('Invalid X-Auth-Token') |
|
85 |
kwargs['user'] = user |
|
86 |
response = func(request, *args, **kwargs) |
|
87 |
return response |
|
88 |
except Fault, fault: |
|
89 |
return render_fault(request, fault) |
|
90 |
except BaseException, e: |
|
91 |
logger.exception('Unexpected error: %s' % e) |
|
92 |
fault = InternalServerError('Unexpected error') |
|
93 |
return render_fault(request, fault) |
|
94 |
return wrapper |
|
95 |
return decorator |
|
96 |
|
|
97 |
@api_method(http_method='GET', token_required=True) |
|
98 |
def authenticate_old(request, user=None): |
|
66 | 99 |
# Normal Response Codes: 204 |
67 | 100 |
# Error Response Codes: internalServerError (500) |
68 | 101 |
# badRequest (400) |
69 | 102 |
# unauthorised (401) |
70 |
try: |
|
71 |
if request.method != 'GET': |
|
72 |
raise BadRequest('Method not allowed.') |
|
73 |
x_auth_token = request.META.get('HTTP_X_AUTH_TOKEN') |
|
74 |
if not x_auth_token: |
|
75 |
return render_fault(request, BadRequest('Missing X-Auth-Token')) |
|
76 |
|
|
77 |
try: |
|
78 |
user = AstakosUser.objects.get(auth_token=x_auth_token) |
|
79 |
except AstakosUser.DoesNotExist, e: |
|
80 |
return render_fault(request, Unauthorized('Invalid X-Auth-Token')) |
|
81 |
|
|
82 |
# Check if the is active. |
|
83 |
if not user.is_active: |
|
84 |
return render_fault(request, Unauthorized('User inactive')) |
|
85 |
|
|
86 |
# Check if the token has expired. |
|
87 |
if (time() - mktime(user.auth_token_expires.timetuple())) > 0: |
|
88 |
return render_fault(request, Unauthorized('Authentication expired')) |
|
89 |
|
|
90 |
if not has_signed_terms(user): |
|
91 |
return render_fault(request, Unauthorized('Pending approval terms')) |
|
92 |
|
|
93 |
response = HttpResponse() |
|
94 |
response.status=204 |
|
95 |
user_info = {'username':user.username, |
|
96 |
'uniq':user.email, |
|
97 |
'auth_token':user.auth_token, |
|
98 |
'auth_token_created':user.auth_token_created.isoformat(), |
|
99 |
'auth_token_expires':user.auth_token_expires.isoformat(), |
|
100 |
'has_credits':user.has_credits, |
|
101 |
'has_signed_terms':has_signed_terms(user)} |
|
102 |
response.content = json.dumps(user_info) |
|
103 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
|
104 |
response['Content-Length'] = len(response.content) |
|
105 |
return response |
|
106 |
except BaseException, e: |
|
107 |
logger.exception(e) |
|
108 |
fault = InternalServerError('Unexpected error') |
|
109 |
return render_fault(request, fault) |
|
103 |
if not user: |
|
104 |
raise BadRequest('No user') |
|
105 |
|
|
106 |
# Check if the is active. |
|
107 |
if not user.is_active: |
|
108 |
raise Unauthorized('User inactive') |
|
110 | 109 |
|
111 |
def get_services(request): |
|
112 |
if request.method != 'GET': |
|
113 |
raise BadRequest('Method not allowed.') |
|
110 |
# Check if the token has expired. |
|
111 |
if (time() - mktime(user.auth_token_expires.timetuple())) > 0: |
|
112 |
raise Unauthorized('Authentication expired') |
|
113 |
|
|
114 |
if not has_signed_terms(user): |
|
115 |
raise Unauthorized('Pending approval terms') |
|
116 |
|
|
117 |
response = HttpResponse() |
|
118 |
response.status=204 |
|
119 |
user_info = {'username':user.username, |
|
120 |
'uniq':user.email, |
|
121 |
'auth_token':user.auth_token, |
|
122 |
'auth_token_created':user.auth_token_created.isoformat(), |
|
123 |
'auth_token_expires':user.auth_token_expires.isoformat(), |
|
124 |
'has_credits':user.has_credits, |
|
125 |
'has_signed_terms':has_signed_terms(user)} |
|
126 |
response.content = json.dumps(user_info) |
|
127 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
|
128 |
response['Content-Length'] = len(response.content) |
|
129 |
return response |
|
130 |
|
|
131 |
@api_method(http_method='GET', token_required=True) |
|
132 |
def authenticate(request, user=None): |
|
133 |
# Normal Response Codes: 204 |
|
134 |
# Error Response Codes: internalServerError (500) |
|
135 |
# badRequest (400) |
|
136 |
# unauthorised (401) |
|
137 |
if not user: |
|
138 |
raise BadRequest('No user') |
|
139 |
|
|
140 |
# Check if the is active. |
|
141 |
if not user.is_active: |
|
142 |
raise Unauthorized('User inactive') |
|
143 |
|
|
144 |
# Check if the token has expired. |
|
145 |
if (time() - mktime(user.auth_token_expires.timetuple())) > 0: |
|
146 |
raise Unauthorized('Authentication expired') |
|
147 |
|
|
148 |
if not has_signed_terms(user): |
|
149 |
raise Unauthorized('Pending approval terms') |
|
150 |
|
|
151 |
response = HttpResponse() |
|
152 |
response.status=204 |
|
153 |
user_info = {'userid':user.username, |
|
154 |
'email':[user.email], |
|
155 |
'name':user.realname, |
|
156 |
'auth_token':user.auth_token, |
|
157 |
'auth_token_created':epoch(user.auth_token_created), |
|
158 |
'auth_token_expires':epoch(user.auth_token_expires), |
|
159 |
'has_credits':user.has_credits, |
|
160 |
'is_active':user.is_active, |
|
161 |
'groups':[g.name for g in user.groups.all()]} |
|
162 |
response.content = json.dumps(user_info) |
|
163 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
|
164 |
response['Content-Length'] = len(response.content) |
|
165 |
return response |
|
114 | 166 |
|
167 |
@api_method(http_method='GET') |
|
168 |
def get_services(request): |
|
115 | 169 |
callback = request.GET.get('callback', None) |
116 | 170 |
data = json.dumps(CLOUD_SERVICES) |
117 | 171 |
mimetype = 'application/json' |
... | ... | |
122 | 176 |
|
123 | 177 |
return HttpResponse(content=data, mimetype=mimetype) |
124 | 178 |
|
179 |
@api_method() |
|
125 | 180 |
def get_menu(request, with_extra_links=False, with_signout=True): |
126 | 181 |
location = request.GET.get('location', '') |
127 | 182 |
exclude = [] |
... | ... | |
144 | 199 |
l.append({ 'url': absolute(reverse('astakos.im.views.edit_profile')), |
145 | 200 |
'name': "My account" }) |
146 | 201 |
if with_extra_links: |
147 |
if request.user.password:
|
|
202 |
if request.user.has_usable_password():
|
|
148 | 203 |
l.append({ 'url': absolute(reverse('password_change')), |
149 | 204 |
'name': "Change password" }) |
150 | 205 |
if INVITATIONS_ENABLED: |
... | ... | |
165 | 220 |
data = '%s(%s)' % (callback, data) |
166 | 221 |
|
167 | 222 |
return HttpResponse(content=data, mimetype=mimetype) |
223 |
|
|
224 |
@api_method(http_method='GET', token_required=True, perms=['astakos.im.can_find_userid']) |
|
225 |
def find_userid(request): |
|
226 |
# Normal Response Codes: 204 |
|
227 |
# Error Response Codes: internalServerError (500) |
|
228 |
# badRequest (400) |
|
229 |
# unauthorised (401) |
|
230 |
email = request.GET.get('email') |
|
231 |
if not email: |
|
232 |
raise BadRequest('Email missing') |
|
233 |
try: |
|
234 |
user = AstakosUser.objects.get(email = email) |
|
235 |
except AstakosUser.DoesNotExist, e: |
|
236 |
raise BadRequest('Invalid email') |
|
237 |
else: |
|
238 |
response = HttpResponse() |
|
239 |
response.status=204 |
|
240 |
user_info = {'userid':user.username} |
|
241 |
response.content = json.dumps(user_info) |
|
242 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
|
243 |
response['Content-Length'] = len(response.content) |
|
244 |
return response |
|
245 |
|
|
246 |
@api_method(http_method='GET', token_required=True, perms=['astakos.im.can_find_email']) |
|
247 |
def find_email(request): |
|
248 |
# Normal Response Codes: 204 |
|
249 |
# Error Response Codes: internalServerError (500) |
|
250 |
# badRequest (400) |
|
251 |
# unauthorised (401) |
|
252 |
userid = request.GET.get('userid') |
|
253 |
if not userid: |
|
254 |
raise BadRequest('Userid missing') |
|
255 |
try: |
|
256 |
user = AstakosUser.objects.get(username = userid) |
|
257 |
except AstakosUser.DoesNotExist, e: |
|
258 |
raise BadRequest('Invalid userid') |
|
259 |
else: |
|
260 |
response = HttpResponse() |
|
261 |
response.status=204 |
|
262 |
user_info = {'userid':user.email} |
|
263 |
response.content = json.dumps(user_info) |
|
264 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
|
265 |
response['Content-Length'] = len(response.content) |
|
266 |
return response |
b/snf-astakos-app/astakos/im/fixtures/groups.json | ||
---|---|---|
1 |
[ |
|
2 |
{ |
|
3 |
"model": "auth.group", |
|
4 |
"pk": 1, |
|
5 |
"fields": { |
|
6 |
"name": "default" |
|
7 |
} |
|
8 |
}, |
|
9 |
{ |
|
10 |
"model": "auth.group", |
|
11 |
"pk": 2, |
|
12 |
"fields": { |
|
13 |
"name": "academic" |
|
14 |
} |
|
15 |
}, |
|
16 |
{ |
|
17 |
"model": "auth.group", |
|
18 |
"pk": 3, |
|
19 |
"fields": { |
|
20 |
"name": "shibboleth" |
|
21 |
} |
|
22 |
} |
|
23 |
] |
b/snf-astakos-app/astakos/im/forms.py | ||
---|---|---|
1 | 1 |
# Copyright 2011-2012 GRNET S.A. All rights reserved. |
2 |
#
|
|
2 |
# |
|
3 | 3 |
# Redistribution and use in source and binary forms, with or |
4 | 4 |
# without modification, are permitted provided that the following |
5 | 5 |
# conditions are met: |
6 |
#
|
|
6 |
# |
|
7 | 7 |
# 1. Redistributions of source code must retain the above |
8 | 8 |
# copyright notice, this list of conditions and the following |
9 | 9 |
# disclaimer. |
10 |
#
|
|
10 |
# |
|
11 | 11 |
# 2. Redistributions in binary form must reproduce the above |
12 | 12 |
# copyright notice, this list of conditions and the following |
13 | 13 |
# disclaimer in the documentation and/or other materials |
14 | 14 |
# provided with the distribution. |
15 |
#
|
|
15 |
# |
|
16 | 16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
17 | 17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
18 | 18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
... | ... | |
25 | 25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
26 | 26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
27 | 27 |
# POSSIBILITY OF SUCH DAMAGE. |
28 |
#
|
|
28 |
# |
|
29 | 29 |
# The views and conclusions contained in the software and |
30 | 30 |
# documentation are those of the authors and should not be |
31 | 31 |
# interpreted as representing official policies, either expressed |
... | ... | |
42 | 42 |
from django.utils.http import int_to_base36 |
43 | 43 |
from django.core.urlresolvers import reverse |
44 | 44 |
from django.utils.functional import lazy |
45 |
from django.utils.safestring import mark_safe |
|
45 | 46 |
|
46 | 47 |
from astakos.im.models import AstakosUser, Invitation |
47 | 48 |
from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL, RECAPTCHA_ENABLED |
... | ... | |
58 | 59 |
class LocalUserCreationForm(UserCreationForm): |
59 | 60 |
""" |
60 | 61 |
Extends the built in UserCreationForm in several ways: |
61 |
|
|
62 |
|
|
62 | 63 |
* Adds email, first_name, last_name, recaptcha_challenge_field, recaptcha_response_field field. |
63 | 64 |
* The username field isn't visible and it is assigned a generated id. |
64 |
* User created is not active.
|
|
65 |
* User created is not active. |
|
65 | 66 |
""" |
66 | 67 |
recaptcha_challenge_field = forms.CharField(widget=DummyWidget) |
67 | 68 |
recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='') |
68 |
|
|
69 |
|
|
69 | 70 |
class Meta: |
70 | 71 |
model = AstakosUser |
71 | 72 |
fields = ("email", "first_name", "last_name", "has_signed_terms") |
72 | 73 |
widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))} |
73 |
|
|
74 |
|
|
74 | 75 |
def __init__(self, *args, **kwargs): |
75 | 76 |
""" |
76 | 77 |
Changes the order of fields, and removes the username field. |
... | ... | |
86 | 87 |
if RECAPTCHA_ENABLED: |
87 | 88 |
self.fields.keyOrder.extend(['recaptcha_challenge_field', |
88 | 89 |
'recaptcha_response_field',]) |
89 |
|
|
90 |
|
|
91 |
if 'has_signed_terms' in self.fields: |
|
92 |
# Overriding field label since we need to apply a link |
|
93 |
# to the terms within the label |
|
94 |
terms_link_html = '<a href="%s" target="_blank">%s</a>' \ |
|
95 |
% (reverse('latest_terms'), _("the terms")) |
|
96 |
self.fields['has_signed_terms'].label = \ |
|
97 |
mark_safe("I agree with %s" % terms_link_html) |
|
98 |
|
|
90 | 99 |
def clean_email(self): |
91 | 100 |
email = self.cleaned_data['email'] |
92 | 101 |
if not email: |
... | ... | |
96 | 105 |
raise forms.ValidationError(_("This email is already used")) |
97 | 106 |
except AstakosUser.DoesNotExist: |
98 | 107 |
return email |
99 |
|
|
108 |
|
|
100 | 109 |
def clean_has_signed_terms(self): |
101 | 110 |
has_signed_terms = self.cleaned_data['has_signed_terms'] |
102 | 111 |
if not has_signed_terms: |
103 | 112 |
raise forms.ValidationError(_('You have to agree with the terms')) |
104 | 113 |
return has_signed_terms |
105 |
|
|
114 |
|
|
106 | 115 |
def clean_recaptcha_response_field(self): |
107 | 116 |
if 'recaptcha_challenge_field' in self.cleaned_data: |
108 | 117 |
self.validate_captcha() |
... | ... | |
119 | 128 |
check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip) |
120 | 129 |
if not check.is_valid: |
121 | 130 |
raise forms.ValidationError(_('You have not entered the correct words')) |
122 |
|
|
131 |
|
|
123 | 132 |
def save(self, commit=True): |
124 | 133 |
""" |
125 | 134 |
Saves the email, first_name and last_name properties, after the normal |
... | ... | |
127 | 136 |
""" |
128 | 137 |
user = super(LocalUserCreationForm, self).save(commit=False) |
129 | 138 |
user.renew_token() |
130 |
user.date_signed_terms = datetime.now() |
|
131 | 139 |
if commit: |
132 | 140 |
user.save() |
133 | 141 |
logger.info('Created user %s', user) |
... | ... | |
137 | 145 |
""" |
138 | 146 |
Extends the LocalUserCreationForm: adds an inviter readonly field. |
139 | 147 |
""" |
140 |
|
|
148 |
|
|
141 | 149 |
inviter = forms.CharField(widget=forms.TextInput(), label=_('Inviter Real Name')) |
142 |
|
|
150 |
|
|
143 | 151 |
class Meta: |
144 | 152 |
model = AstakosUser |
145 | 153 |
fields = ("email", "first_name", "last_name", "has_signed_terms") |
146 | 154 |
widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))} |
147 |
|
|
155 |
|
|
148 | 156 |
def __init__(self, *args, **kwargs): |
149 | 157 |
""" |
150 | 158 |
Changes the order of fields, and removes the username field. |
151 | 159 |
""" |
152 | 160 |
super(InvitedLocalUserCreationForm, self).__init__(*args, **kwargs) |
153 |
|
|
161 |
|
|
154 | 162 |
#set readonly form fields |
155 | 163 |
self.fields['inviter'].widget.attrs['readonly'] = True |
156 | 164 |
self.fields['email'].widget.attrs['readonly'] = True |
157 | 165 |
self.fields['username'].widget.attrs['readonly'] = True |
158 |
|
|
166 |
|
|
159 | 167 |
def save(self, commit=True): |
160 | 168 |
user = super(InvitedLocalUserCreationForm, self).save(commit=False) |
161 | 169 |
level = user.invitation.inviter.level + 1 |
... | ... | |
166 | 174 |
user.save() |
167 | 175 |
return user |
168 | 176 |
|
169 |
class ThirdPartyUserCreationForm(UserCreationForm):
|
|
177 |
class ThirdPartyUserCreationForm(forms.ModelForm):
|
|
170 | 178 |
class Meta: |
171 | 179 |
model = AstakosUser |
172 |
fields = ("email", "has_signed_terms") |
|
180 |
fields = ("email", "first_name", "last_name", "third_party_identifier", |
|
181 |
"has_signed_terms", "provider") |
|
173 | 182 |
widgets = {"has_signed_terms":ApprovalTermsWidget(terms_uri=reverse_lazy('latest_terms'))} |
174 | 183 |
|
175 | 184 |
def __init__(self, *args, **kwargs): |
... | ... | |
177 | 186 |
Changes the order of fields, and removes the username field. |
178 | 187 |
""" |
179 | 188 |
if 'ip' in kwargs: |
180 |
self.ip = kwargs['ip'] |
|
181 | 189 |
kwargs.pop('ip') |
182 | 190 |
super(ThirdPartyUserCreationForm, self).__init__(*args, **kwargs) |
183 |
self.fields.keyOrder = ['email'] |
|
191 |
self.fields.keyOrder = ['email', 'first_name', 'last_name', |
|
192 |
'provider', 'third_party_identifier'] |
|
184 | 193 |
if get_latest_terms(): |
185 | 194 |
self.fields.keyOrder.append('has_signed_terms') |
195 |
#set readonly form fields |
|
196 |
ro = ["provider", "third_party_identifier", "first_name", "last_name"] |
|
197 |
for f in ro: |
|
198 |
self.fields[f].widget.attrs['readonly'] = True |
|
199 |
|
|
200 |
if 'has_signed_terms' in self.fields: |
|
201 |
# Overriding field label since we need to apply a link |
|
202 |
# to the terms within the label |
|
203 |
terms_link_html = '<a href="%s" target="_blank">%s</a>' \ |
|
204 |
% (reverse('latest_terms'), _("the terms")) |
|
205 |
self.fields['has_signed_terms'].label = \ |
|
206 |
mark_safe("I agree with %s" % terms_link_html) |
|
207 |
|
|
208 |
def clean_email(self): |
|
209 |
email = self.cleaned_data['email'] |
|
210 |
if not email: |
|
211 |
raise forms.ValidationError(_("This field is required")) |
|
212 |
try: |
|
213 |
AstakosUser.objects.get(email = email) |
|
214 |
raise forms.ValidationError(_("This email is already used")) |
|
215 |
except AstakosUser.DoesNotExist: |
|
216 |
return email |
|
217 |
|
|
218 |
def clean_has_signed_terms(self): |
|
219 |
has_signed_terms = self.cleaned_data['has_signed_terms'] |
|
220 |
if not has_signed_terms: |
|
221 |
raise forms.ValidationError(_('You have to agree with the terms')) |
|
222 |
return has_signed_terms |
|
186 | 223 |
|
187 | 224 |
def save(self, commit=True): |
188 | 225 |
user = super(ThirdPartyUserCreationForm, self).save(commit=False) |
189 | 226 |
user.set_unusable_password() |
227 |
user.renew_token() |
|
190 | 228 |
if commit: |
191 | 229 |
user.save() |
192 | 230 |
logger.info('Created user %s', user) |
... | ... | |
198 | 236 |
#set readonly form fields |
199 | 237 |
self.fields['email'].widget.attrs['readonly'] = True |
200 | 238 |
|
239 |
class ShibbolethUserCreationForm(ThirdPartyUserCreationForm): |
|
240 |
def clean_email(self): |
|
241 |
email = self.cleaned_data['email'] |
|
242 |
if not email: |
|
243 |
raise forms.ValidationError(_("This field is required")) |
|
244 |
try: |
|
245 |
user = AstakosUser.objects.get(email = email) |
|
246 |
if user.provider == 'local': |
|
247 |
self.instance = user |
|
248 |
return email |
|
249 |
else: |
|
250 |
raise forms.ValidationError(_("This email is already associated with another shibboleth account.")) |
|
251 |
except AstakosUser.DoesNotExist: |
|
252 |
return email |
|
253 |
|
|
201 | 254 |
class LoginForm(AuthenticationForm): |
202 | 255 |
username = forms.EmailField(label=_("Email")) |
203 | 256 |
|
... | ... | |
205 | 258 |
""" |
206 | 259 |
Subclass of ``ModelForm`` for permiting user to edit his/her profile. |
207 | 260 |
Most of the fields are readonly since the user is not allowed to change them. |
208 |
|
|
261 |
|
|
209 | 262 |
The class defines a save method which sets ``is_verified`` to True so as the user |
210 | 263 |
during the next login will not to be redirected to profile page. |
211 | 264 |
""" |
212 | 265 |
renew = forms.BooleanField(label='Renew token', required=False) |
213 |
|
|
266 |
|
|
214 | 267 |
class Meta: |
215 | 268 |
model = AstakosUser |
216 |
fields = ('email', 'first_name', 'last_name', 'auth_token', 'auth_token_expires') |
|
217 |
|
|
269 |
fields = ('email', 'first_name', 'last_name', 'auth_token', 'auth_token_expires', 'groups')
|
|
270 |
|
|
218 | 271 |
def __init__(self, *args, **kwargs): |
219 | 272 |
super(ProfileForm, self).__init__(*args, **kwargs) |
220 | 273 |
instance = getattr(self, 'instance', None) |
221 |
ro_fields = ('auth_token', 'auth_token_expires', 'email')
|
|
274 |
ro_fields = ('auth_token', 'auth_token_expires', 'groups')
|
|
222 | 275 |
if instance and instance.id: |
223 | 276 |
for field in ro_fields: |
224 | 277 |
self.fields[field].widget.attrs['readonly'] = True |
225 |
|
|
278 |
|
|
226 | 279 |
def save(self, commit=True): |
227 | 280 |
user = super(ProfileForm, self).save(commit=False) |
228 | 281 |
user.is_verified = True |
... | ... | |
244 | 297 |
""" |
245 | 298 |
Form for sending an invitations |
246 | 299 |
""" |
247 |
|
|
300 |
|
|
248 | 301 |
email = forms.EmailField(required = True, label = 'Email address') |
249 | 302 |
first_name = forms.EmailField(label = 'First name') |
250 | 303 |
last_name = forms.EmailField(label = 'Last name') |
... | ... | |
253 | 306 |
""" |
254 | 307 |
Extends PasswordResetForm by overriding save method: |
255 | 308 |
passes a custom from_email in send_mail. |
256 |
|
|
309 |
|
|
257 | 310 |
Since Django 1.3 this is useless since ``django.contrib.auth.views.reset_password`` |
258 | 311 |
accepts a from_email argument. |
259 | 312 |
""" |
... | ... | |
263 | 316 |
Generates a one-use only link for resetting password and sends to the user. |
264 | 317 |
""" |
265 | 318 |
for user in self.users_cache: |
266 |
url = urljoin(BASEURL, |
|
267 |
'/im/local/reset/confirm/%s-%s' %(int_to_base36(user.id), |
|
268 |
token_generator.make_token(user))) |
|
319 |
url = reverse('django.contrib.auth.views.password_reset_confirm', |
|
320 |
kwargs={'uidb36':int_to_base36(user.id), |
|
321 |
'token':token_generator.make_token(user)}) |
|
322 |
url = request.build_absolute_uri(url) |
|
269 | 323 |
t = loader.get_template(email_template_name) |
270 | 324 |
c = { |
271 | 325 |
'email': user.email, |
... | ... | |
283 | 337 |
class Meta: |
284 | 338 |
model = AstakosUser |
285 | 339 |
fields = ("has_signed_terms",) |
286 |
|
|
340 |
|
|
287 | 341 |
def __init__(self, *args, **kwargs): |
288 | 342 |
super(SignApprovalTermsForm, self).__init__(*args, **kwargs) |
289 |
|
|
343 |
|
|
290 | 344 |
def clean_has_signed_terms(self): |
291 | 345 |
has_signed_terms = self.cleaned_data['has_signed_terms'] |
292 | 346 |
if not has_signed_terms: |
293 | 347 |
raise forms.ValidationError(_('You have to agree with the terms')) |
294 | 348 |
return has_signed_terms |
295 |
|
|
296 |
def save(self, commit=True): |
|
297 |
""" |
|
298 |
Updates date_signed_terms & has_signed_terms fields. |
|
299 |
""" |
|
300 |
user = super(SignApprovalTermsForm, self).save(commit=False) |
|
301 |
user.date_signed_terms = datetime.now() |
|
302 |
if commit: |
|
303 |
user.save() |
|
304 |
return user |
|
305 | 349 |
|
306 | 350 |
class InvitationForm(forms.ModelForm): |
307 | 351 |
username = forms.EmailField(label=_("Email")) |
308 | 352 |
|
353 |
def __init__(self, *args, **kwargs): |
|
354 |
super(InvitationForm, self).__init__(*args, **kwargs) |
|
355 |
|
|
309 | 356 |
class Meta: |
310 | 357 |
model = Invitation |
311 | 358 |
fields = ('username', 'realname') |
... | ... | |
317 | 364 |
raise forms.ValidationError(_('There is already invitation for this email.')) |
318 | 365 |
except Invitation.DoesNotExist: |
319 | 366 |
pass |
320 |
return username |
|
367 |
return username |
b/snf-astakos-app/astakos/im/functions.py | ||
---|---|---|
170 | 170 |
|
171 | 171 |
Raises SendInvitationError |
172 | 172 |
""" |
173 |
invitation.inviter = inviter |
|
174 |
invitation.save() |
|
173 | 175 |
send_invitation(invitation, email_template_name) |
174 | 176 |
inviter.invitations = max(0, inviter.invitations - 1) |
175 | 177 |
inviter.save() |
... | ... | |
183 | 185 |
logger.exception(e) |
184 | 186 |
|
185 | 187 |
class SendMailError(Exception): |
186 |
def __init__(self, message): |
|
187 |
Exception.__init__(self) |
|
188 |
pass |
|
188 | 189 |
|
189 | 190 |
class SendAdminNotificationError(SendMailError): |
190 | 191 |
def __init__(self): |
191 | 192 |
self.message = _('Failed to send notification') |
192 |
SendMailError.__init__(self)
|
|
193 |
super(SendAdminNotificationError, self).__init__()
|
|
193 | 194 |
|
194 |
class SendVerificationError(Exception):
|
|
195 |
class SendVerificationError(SendMailError):
|
|
195 | 196 |
def __init__(self): |
196 | 197 |
self.message = _('Failed to send verification') |
197 |
SendMailError.__init__(self)
|
|
198 |
super(SendVerificationError, self).__init__()
|
|
198 | 199 |
|
199 |
class SendInvitationError(Exception):
|
|
200 |
class SendInvitationError(SendMailError):
|
|
200 | 201 |
def __init__(self): |
201 | 202 |
self.message = _('Failed to send invitation') |
202 |
SendMailError.__init__(self)
|
|
203 |
super(SendInvitationError, self).__init__()
|
|
203 | 204 |
|
204 |
class SendGreetingError(Exception):
|
|
205 |
class SendGreetingError(SendMailError):
|
|
205 | 206 |
def __init__(self): |
206 | 207 |
self.message = _('Failed to send greeting') |
207 |
SendMailError.__init__(self)
|
|
208 |
super(SendGreetingError, self).__init__()
|
|
208 | 209 |
|
209 |
class SendFeedbackError(Exception):
|
|
210 |
class SendFeedbackError(SendMailError):
|
|
210 | 211 |
def __init__(self): |
211 | 212 |
self.message = _('Failed to send feedback') |
212 |
SendMailError.__init__(self) |
|
213 |
super(SendFeedbackError, self).__init__() |
b/snf-astakos-app/astakos/im/management/commands/addgroup.py | ||
---|---|---|
1 |
# Copyright 2012 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
from optparse import make_option |
|
35 |
from random import choice |
|
36 |
from string import digits, lowercase, uppercase |
|
37 |
from uuid import uuid4 |
|
38 |
from time import time |
|
39 |
from os.path import abspath |
|
40 |
|
|
41 |
from django.core.management.base import BaseCommand, CommandError |
|
42 |
|
|
43 |
from django.contrib.auth.models import Group |
|
44 |
|
|
45 |
class Command(BaseCommand): |
|
46 |
args = "<name>" |
|
47 |
help = "Insert group" |
|
48 |
|
|
49 |
def handle(self, *args, **options): |
|
50 |
if len(args) != 1: |
|
51 |
raise CommandError("Invalid number of arguments") |
|
52 |
|
|
53 |
name = args[0].decode('utf8') |
|
54 |
|
|
55 |
try: |
|
56 |
Group.objects.get(name=name) |
|
57 |
raise CommandError("A group with this name already exists") |
|
58 |
except Group.DoesNotExist, e: |
|
59 |
group = Group(name=name) |
|
60 |
group.save() |
|
61 |
|
|
62 |
msg = "Created group id %d" % (group.id,) |
|
63 |
self.stdout.write(msg + '\n') |
b/snf-astakos-app/astakos/im/management/commands/createuser.py | ||
---|---|---|
31 | 31 |
# interpreted as representing official policies, either expressed |
32 | 32 |
# or implied, of GRNET S.A. |
33 | 33 |
|
34 |
import socket |
|
35 |
|
|
34 | 36 |
from optparse import make_option |
35 | 37 |
from random import choice |
36 | 38 |
from string import digits, lowercase, uppercase |
... | ... | |
97 | 99 |
if options['admin']: |
98 | 100 |
user.is_admin = True |
99 | 101 |
|
100 |
user.save() |
|
101 |
|
|
102 |
msg = "Created user id %d" % (user.id,) |
|
103 |
if options['password'] is None: |
|
104 |
msg += " with password '%s'" % (password,) |
|
105 |
self.stdout.write(msg + '\n') |
|
102 |
try: |
|
103 |
user.save() |
|
104 |
except socket.error, e: |
|
105 |
raise CommandError(e) |
|
106 |
else: |
|
107 |
msg = "Created user id %d" % (user.id,) |
|
108 |
if options['password'] is None: |
|
109 |
msg += " with password '%s'" % (password,) |
|
110 |
self.stdout.write(msg + '\n') |
b/snf-astakos-app/astakos/im/management/commands/inviteuser.py | ||
---|---|---|
35 | 35 |
|
36 | 36 |
from django.core.management.base import BaseCommand, CommandError |
37 | 37 |
from django.db.utils import IntegrityError |
38 |
from django.db import transaction |
|
38 | 39 |
|
39 | 40 |
from astakos.im.functions import invite, SendMailError |
41 |
from astakos.im.models import Invitation |
|
40 | 42 |
|
41 | 43 |
from ._common import get_user |
42 | 44 |
|
43 |
|
|
45 |
@transaction.commit_manually |
|
44 | 46 |
class Command(BaseCommand): |
45 | 47 |
args = "<inviter id or email> <email> <real name>" |
46 | 48 |
help = "Invite a user" |
... | ... | |
62 | 64 |
invite(invitation, inviter) |
63 | 65 |
self.stdout.write("Invitation sent to '%s'\n" % (email,)) |
64 | 66 |
except SendMailError, e: |
67 |
transaction.rollback() |
|
65 | 68 |
raise CommandError(e.message) |
66 | 69 |
except IntegrityError, e: |
70 |
transaction.rollback() |
|
67 | 71 |
raise CommandError("There is already an invitation for %s" % (email,)) |
72 |
else: |
|
73 |
transaction.commit() |
|
68 | 74 |
else: |
69 | 75 |
raise CommandError("No invitations left") |
b/snf-astakos-app/astakos/im/management/commands/listgroups.py | ||
---|---|---|
1 |
# Copyright 2012 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
from optparse import make_option |
|
35 |
|
|
36 |
from django.core.management.base import BaseCommand, CommandError |
|
37 |
from django.contrib.auth.models import Group |
|
38 |
|
|
39 |
from astakos.im.models import AstakosUser |
|
40 |
|
|
41 |
from ._common import format_bool |
|
42 |
|
|
43 |
|
|
44 |
class Command(BaseCommand): |
|
45 |
help = "List g" |
|
46 |
|
|
47 |
option_list = BaseCommand.option_list + ( |
|
48 |
make_option('-c', |
|
49 |
action='store_true', |
|
50 |
dest='csv', |
|
51 |
default=False, |
|
52 |
help="Use pipes to separate values"), |
|
53 |
) |
|
54 |
|
|
55 |
def handle(self, *args, **options): |
|
56 |
if args: |
|
57 |
raise CommandError("Command doesn't accept any arguments") |
|
58 |
|
|
59 |
groups = Group.objects.all() |
|
60 |
|
|
61 |
labels = ('id', 'name') |
|
62 |
columns = (1, 2) |
|
63 |
|
|
64 |
if not options['csv']: |
|
65 |
line = ' '.join(l.rjust(w) for l, w in zip(labels, columns)) |
|
66 |
self.stdout.write(line + '\n') |
|
67 |
sep = '-' * len(line) |
|
68 |
self.stdout.write(sep + '\n') |
|
69 |
|
|
70 |
for group in groups: |
|
71 |
fields = (str(group.id), group.name) |
|
72 |
|
|
73 |
if options['csv']: |
|
74 |
line = '|'.join(fields) |
|
75 |
else: |
|
76 |
line = ' '.join(f.rjust(w) for f, w in zip(fields, columns)) |
|
77 |
|
|
78 |
self.stdout.write(line.encode('utf8') + '\n') |
b/snf-astakos-app/astakos/im/management/commands/listinvitations.py | ||
---|---|---|
57 | 57 |
|
58 | 58 |
invitations = Invitation.objects.all() |
59 | 59 |
|
60 |
labels = ('id', 'email', 'real name', 'code', 'used', 'consumed') |
|
61 |
columns = (3, 24, 24, 20, 4, 8) |
|
60 |
labels = ('id', 'inviter', 'email', 'real name', 'code', 'used', 'consumed')
|
|
61 |
columns = (3, 24, 24, 24, 20, 4, 8)
|
|
62 | 62 |
|
63 | 63 |
if not options['csv']: |
64 | 64 |
line = ' '.join(l.rjust(w) for l, w in zip(labels, columns)) |
... | ... | |
71 | 71 |
code = str(invitation.code) |
72 | 72 |
used = format_bool(invitation.is_accepted) |
73 | 73 |
consumed = format_bool(invitation.is_consumed) |
74 |
fields = (id, invitation.username, invitation.realname, |
|
74 |
fields = (id, invitation.inviter.email, invitation.username, invitation.realname,
|
|
75 | 75 |
code, used, consumed) |
76 | 76 |
|
77 | 77 |
if options['csv']: |
b/snf-astakos-app/astakos/im/management/commands/listusers.py | ||
---|---|---|
64 | 64 |
if options['pending']: |
65 | 65 |
users = users.filter(is_active=False) |
66 | 66 |
|
67 |
labels = ('id', 'email', 'real name', 'affiliation', 'active', 'admin') |
|
68 |
columns = (3, 24, 24, 12, 6, 5) |
|
67 |
labels = ('id', 'email', 'real name', 'affiliation', 'active', 'admin', 'provider')
|
|
68 |
columns = (3, 24, 24, 12, 6, 5, 12)
|
|
69 | 69 |
|
70 | 70 |
if not options['csv']: |
71 | 71 |
line = ' '.join(l.rjust(w) for l, w in zip(labels, columns)) |
... | ... | |
78 | 78 |
active = format_bool(user.is_active) |
79 | 79 |
admin = format_bool(user.is_superuser) |
80 | 80 |
fields = (id, user.email, user.realname, user.affiliation, active, |
81 |
admin) |
|
81 |
admin, user.provider)
|
|
82 | 82 |
|
83 | 83 |
if options['csv']: |
84 | 84 |
line = '|'.join(fields) |
b/snf-astakos-app/astakos/im/management/commands/modifyuser.py | ||
---|---|---|
34 | 34 |
from optparse import make_option |
35 | 35 |
|
36 | 36 |
from django.core.management.base import BaseCommand, CommandError |
37 |
from django.contrib.auth.models import Group |
|
37 | 38 |
|
38 | 39 |
from ._common import get_user |
39 | 40 |
|
... | ... | |
80 | 81 |
dest='inactive', |
81 | 82 |
default=False, |
82 | 83 |
help="Change user's state to inactive"), |
84 |
make_option('--group', |
|
85 |
dest='group', |
|
86 |
help="Extend user groups"), |
|
83 | 87 |
) |
84 | 88 |
|
85 | 89 |
def handle(self, *args, **options): |
... | ... | |
104 | 108 |
if invitations is not None: |
105 | 109 |
user.invitations = int(invitations) |
106 | 110 |
|
111 |
groupname = options.get('group') |
|
112 |
if groupname is not None: |
|
113 |
try: |
|
114 |
group = Group.objects.get(name=groupname) |
|
115 |
user.groups.add(group) |
|
116 |
except Group.DoesNotExist, e: |
|
117 |
raise CommandError("Group named %s does not exist." % groupname) |
|
118 |
|
|
107 | 119 |
level = options.get('level') |
108 | 120 |
if level is not None: |
109 | 121 |
user.level = int(level) |
b/snf-astakos-app/astakos/im/management/commands/sendactivation.py | ||
---|---|---|
32 | 32 |
# or implied, of GRNET S.A. |
33 | 33 |
|
34 | 34 |
from django.core.management.base import BaseCommand, CommandError |
35 |
from django.db import transaction |
|
36 | 35 |
|
37 | 36 |
from astakos.im.functions import send_verification |
38 | 37 |
|
... | ... | |
57 | 56 |
self.stderr.write(msg) |
58 | 57 |
continue |
59 | 58 |
|
60 |
send_verification(user) |
|
59 |
try: |
|
60 |
send_verification(user) |
|
61 |
except SendMailError, e: |
|
62 |
raise CommandError(e.message) |
|
61 | 63 |
|
62 | 64 |
self.stdout.write("Activated '%s'\n" % (user.email,)) |
b/snf-astakos-app/astakos/im/management/commands/showuser.py | ||
---|---|---|
75 | 75 |
'verified': format_bool(user.is_verified), |
76 | 76 |
'has_credits': format_bool(user.has_credits), |
77 | 77 |
'has_signed_terms': format_bool(user.has_signed_terms), |
78 |
'date_signed_terms': format_date(user.date_signed_terms) |
|
78 |
'date_signed_terms': format_date(user.date_signed_terms), |
|
79 |
'groups': [elem.name for elem in user.groups.all()], |
|
80 |
'third_party_identifier': user.third_party_identifier |
|
79 | 81 |
} |
80 | 82 |
|
81 | 83 |
for key, val in sorted(kv.items()): |
82 |
line = '%s: %s\n' % (key.rjust(17), val)
|
|
84 |
line = '%s: %s\n' % (key.rjust(22), val)
|
|
83 | 85 |
self.stdout.write(line.encode('utf8')) |
b/snf-astakos-app/astakos/im/models.py | ||
---|---|---|
33 | 33 |
|
34 | 34 |
import hashlib |
35 | 35 |
import uuid |
36 |
import logging |
|
36 | 37 |
|
37 | 38 |
from time import asctime |
38 | 39 |
from datetime import datetime, timedelta |
... | ... | |
41 | 42 |
from random import randint |
42 | 43 |
|
43 | 44 |
from django.db import models |
44 |
from django.contrib.auth.models import User, UserManager |
|
45 |
from django.contrib.auth.models import User, UserManager, Group
|
|
45 | 46 |
|
46 | 47 |
from astakos.im.settings import DEFAULT_USER_LEVEL, INVITATIONS_PER_LEVEL, AUTH_TOKEN_DURATION, BILLING_FIELDS, QUEUE_CONNECTION |
47 | 48 |
from astakos.im.queue.userevent import UserEvent |
... | ... | |
49 | 50 |
|
50 | 51 |
QUEUE_CLIENT_ID = 3 # Astakos. |
51 | 52 |
|
53 |
logger = logging.getLogger(__name__) |
|
54 |
|
|
52 | 55 |
class AstakosUser(User): |
53 | 56 |
""" |
54 | 57 |
Extends ``django.contrib.auth.models.User`` by defining additional fields. |
... | ... | |
81 | 84 |
has_signed_terms = models.BooleanField('Agree with the terms?', default=False) |
82 | 85 |
date_signed_terms = models.DateTimeField('Signed terms date', null=True) |
83 | 86 |
|
87 |
__has_signed_terms = False |
|
88 |
__groupnames = [] |
|
89 |
|
|
90 |
def __init__(self, *args, **kwargs): |
|
91 |
super(AstakosUser, self).__init__(*args, **kwargs) |
|
92 |
self.__has_signed_terms = self.has_signed_terms |
|
93 |
if self.id: |
|
94 |
self.__groupnames = [g.name for g in self.groups.all()] |
|
95 |
else: |
|
96 |
self.is_active = False |
|
97 |
|
|
84 | 98 |
@property |
85 | 99 |
def realname(self): |
86 | 100 |
return '%s %s' %(self.first_name, self.last_name) |
... | ... | |
106 | 120 |
if not self.id: |
107 | 121 |
self.date_joined = datetime.now() |
108 | 122 |
self.updated = datetime.now() |
123 |
|
|
124 |
# update date_signed_terms if necessary |
|
125 |
if self.__has_signed_terms != self.has_signed_terms: |
|
126 |
self.date_signed_terms = datetime.now() |
|
127 |
|
|
109 | 128 |
if not self.id: |
110 | 129 |
# set username |
111 | 130 |
while not self.username: |
... | ... | |
114 | 133 |
AstakosUser.objects.get(username = username) |
115 | 134 |
except AstakosUser.DoesNotExist, e: |
116 | 135 |
self.username = username |
117 |
self.is_active = False |
|
118 | 136 |
if not self.provider: |
119 | 137 |
self.provider = 'local' |
120 | 138 |
report_user_event(self) |
121 | 139 |
super(AstakosUser, self).save(**kwargs) |
140 |
|
|
141 |
# set group if does not exist |
|
142 |
groupname = 'shibboleth' if self.provider == 'shibboleth' else 'default' |
|
143 |
if groupname not in self.__groupnames: |
|
144 |
try: |
|
145 |
group = Group.objects.get(name = groupname) |
|
146 |
self.groups.add(group) |
|
147 |
except Group.DoesNotExist, e: |
|
148 |
logger.exception(e) |
|
122 | 149 |
|
123 | 150 |
def renew_token(self): |
124 | 151 |
md5 = hashlib.md5() |
... | ... | |
159 | 186 |
accepted = models.DateTimeField('Acceptance date', null=True, blank=True) |
160 | 187 |
consumed = models.DateTimeField('Consumption date', null=True, blank=True) |
161 | 188 |
|
162 |
def save(self, **kwargs): |
|
189 |
def __init__(self, *args, **kwargs): |
|
190 |
super(Invitation, self).__init__(*args, **kwargs) |
|
163 | 191 |
if not self.id: |
164 | 192 |
self.code = _generate_invitation_code() |
165 |
super(Invitation, self).save(**kwargs) |
|
166 | 193 |
|
167 | 194 |
def consume(self): |
168 | 195 |
self.is_consumed = True |
b/snf-astakos-app/astakos/im/static/im/css/ie7.css | ||
---|---|---|
1 |
.navigation { |
|
2 |
min-width: 660px; |
|
3 |
} |
|
4 |
|
|
5 |
.mainnav.inline { |
|
6 |
position: absolute; |
|
7 |
right: -10px; |
|
8 |
} |
|
9 |
|
|
10 |
.mainnav.inline.subnav { |
|
11 |
position: relative; |
|
12 |
top: 50px; |
|
13 |
} |
b/snf-astakos-app/astakos/im/static/im/css/styles.css | ||
---|---|---|
844 | 844 |
background-color: #3582ac; |
845 | 845 |
} |
846 | 846 |
.rightcol { |
847 |
margin-left: 511px; |
|
847 |
display: inline; |
|
848 |
float: left; |
|
849 |
margin-left: 22px; |
|
848 | 850 |
width: 306px; |
851 |
margin-left: 102px; |
|
849 | 852 |
} |
850 | 853 |
.rightcol.narrow { |
851 |
margin-left: 593px; |
|
854 |
display: inline; |
|
855 |
float: left; |
|
856 |
margin-left: 22px; |
|
852 | 857 |
width: 224px; |
858 |
margin-left: 102px; |
|
853 | 859 |
} |
854 | 860 |
.rightcol input[type=text], .rightcol input[type=password] { |
855 | 861 |
width: 273px; |
... | ... | |
993 | 999 |
} |
994 | 1000 |
form .form-row.submit { |
995 | 1001 |
margin-top: 22px; |
996 |
z-index: 10; |
|
1002 |
} |
|
1003 |
form .form-row.with-checkbox { |
|
1004 |
margin-top: 7px; |
|
997 | 1005 |
} |
998 | 1006 |
form .form-row .extra-link { |
999 | 1007 |
color: #808080; |
... | ... | |
1013 | 1021 |
color: #aaa; |
1014 | 1022 |
} |
1015 | 1023 |
form.innerlabels p { |
1024 |
display: table; |
|
1016 | 1025 |
position: relative; |
1017 | 1026 |
} |
1018 | 1027 |
form textarea, |
... | ... | |
1192 | 1201 |
color: inherit; |
1193 | 1202 |
font-weight: bold; |
1194 | 1203 |
} |
1204 |
form.innerlabels label.checkbox-label { |
|
1205 |
position: relative !important; |
|
1206 |
margin-left: 10px !important; |
|
1207 |
padding-top: 1em !important; |
|
1208 |
top: 11px !important; |
|
1209 |
left: 10px; |
|
1210 |
cursor: pointer; |
|
1211 |
} |
|
1195 | 1212 |
.service-desc { |
1196 | 1213 |
margin-top: 4em; |
1197 | 1214 |
} |
... | ... | |
1481 | 1498 |
margin-bottom: 2em; |
1482 | 1499 |
font-size: 0.8em; |
1483 | 1500 |
} |
1484 |
.initial_hidden {
|
|
1501 |
.initially-hidden {
|
|
1485 | 1502 |
display: none; |
1486 | 1503 |
} |
1487 | 1504 |
/* recaptcha */ |
... | ... | |
1511 | 1528 |
cursor: pointer; |
1512 | 1529 |
margin-top: 5.333333333333333px; |
1513 | 1530 |
} |
1531 |
.textcontent h1, .terms-content h1 { |
|
1532 |
font-size: 1.9em; |
|
1533 |
margin-bottom: 0.2em; |
|
1534 |
margin-top: 1.2em; |
|
1535 |
color: #3582ac; |
|
1536 |
} |
|
1537 |
.textcontent h1:first-child, .terms-content h1:first-child { |
|
1538 |
margin-top: 0; |
|
1539 |
} |
|
1540 |
.textcontent h2, .terms-content h2 { |
|
1541 |
font-size: 1.6em; |
|
1542 |
margin-bottom: 1.1em; |
|
1543 |
margin-top: 1.1em; |
|
1544 |
color: #3b91bf; |
|
1545 |
} |
|
1546 |
.textcontent h2:first-child, .terms-content h2:first-child { |
|
1547 |
margin-top: 0; |
|
1548 |
} |
|
1549 |
.textcontent h3, .terms-content h3 { |
|
1550 |
font-size: 1.3em; |
|
1551 |
margin-bottom: 1em; |
|
1552 |
margin-top: 1em; |
|
1553 |
color: #3b91bf; |
|
1554 |
} |
|
1555 |
.textcontent h3:first-child, .terms-content h3:first-child { |
|
1556 |
margin-top: 0; |
|
1557 |
} |
|
1558 |
.textcontent p, .terms-content p { |
|
1559 |
margin-bottom: 1em; |
|
1560 |
line-height: 1.5em; |
|
1561 |
} |
|
1562 |
.textcontent .date, .terms-content .date { |
|
1563 |
margin: 1em 0; |
|
1564 |
font-size: 0.9em; |
|
1565 |
margin-bottom: 2em; |
|
1566 |
color: #808080; |
|
1567 |
} |
b/snf-astakos-app/astakos/im/static/im/css/styles.less | ||
---|---|---|
258 | 258 |
} |
259 | 259 |
|
260 | 260 |
.rightcol { |
261 |
.offset(6.5);
|
|
262 |
.columns(4);
|
|
261 |
.makeColumn(4);
|
|
262 |
margin-left: 4*@gridGutterWidth + 14;
|
|
263 | 263 |
|
264 | 264 |
&.narrow { |
265 |
.offset(7.5);
|
|
266 |
.columns(3);
|
|
265 |
.makeColumn(3);
|
|
266 |
margin-left: 4*@gridGutterWidth + 14;
|
|
267 | 267 |
} |
268 |
|
|
268 | 269 |
input[type=text], input[type=password] { |
269 | 270 |
width: 3*@gridColumnWidth + 4*@gridGutterWidth + 5; |
270 | 271 |
} |
... | ... | |
428 | 429 |
position: relative; |
429 | 430 |
&.submit { |
430 | 431 |
margin-top: 1.5*@verticalSpacing; |
431 |
z-index: 10; |
|
432 |
} |
|
433 |
|
|
434 |
&.with-checkbox { |
|
435 |
margin-top: 7px; |
|
432 | 436 |
} |
433 | 437 |
|
434 | 438 |
.extra-link { |
... | ... | |
453 | 457 |
} |
454 | 458 |
|
455 | 459 |
&.innerlabels p { |
460 |
display: table; |
|
456 | 461 |
position: relative; |
457 | 462 |
} |
458 | 463 |
|
Also available in: Unified diff