Revision 5ce3ce4f
b/snf-astakos-app/astakos/__init__.py | ||
---|---|---|
1 | 1 |
# Copyright (c) Django Software Foundation and individual contributors. |
2 | 2 |
# All rights reserved. |
3 |
#
|
|
3 |
# |
|
4 | 4 |
# Redistribution and use in source and binary forms, with or without modification, |
5 | 5 |
# are permitted provided that the following conditions are met: |
6 |
#
|
|
7 |
# 1. Redistributions of source code must retain the above copyright notice,
|
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above copyright notice, |
|
8 | 8 |
# this list of conditions and the following disclaimer. |
9 |
#
|
|
10 |
# 2. Redistributions in binary form must reproduce the above copyright
|
|
9 |
# |
|
10 |
# 2. Redistributions in binary form must reproduce the above copyright |
|
11 | 11 |
# notice, this list of conditions and the following disclaimer in the |
12 | 12 |
# documentation and/or other materials provided with the distribution. |
13 |
#
|
|
13 |
# |
|
14 | 14 |
# 3. Neither the name of Django nor the names of its contributors may be used |
15 | 15 |
# to endorse or promote products derived from this software without |
16 | 16 |
# specific prior written permission. |
17 |
#
|
|
17 |
# |
|
18 | 18 |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
19 | 19 |
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
20 | 20 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
... | ... | |
28 | 28 |
|
29 | 29 |
VERSION = (0, 3, 0, 'alpha', 0) |
30 | 30 |
|
31 |
|
|
31 | 32 |
def get_version(): |
32 | 33 |
version = '%s.%s' % (VERSION[0], VERSION[1]) |
33 | 34 |
if VERSION[2]: |
b/snf-astakos-app/astakos/im/activation_backends.py | ||
---|---|---|
47 | 47 |
|
48 | 48 |
logger = logging.getLogger(__name__) |
49 | 49 |
|
50 |
|
|
50 | 51 |
def get_backend(request): |
51 | 52 |
""" |
52 | 53 |
Returns an instance of an activation backend, |
... | ... | |
59 | 60 |
""" |
60 | 61 |
module = 'astakos.im.activation_backends' |
61 | 62 |
prefix = 'Invitations' if INVITATIONS_ENABLED else 'Simple' |
62 |
backend_class_name = '%sBackend' %prefix |
|
63 |
backend_class_name = '%sBackend' % prefix
|
|
63 | 64 |
try: |
64 | 65 |
mod = import_module(module) |
65 | 66 |
except ImportError, e: |
66 |
raise ImproperlyConfigured('Error loading activation backend %s: "%s"' % (module, e)) |
|
67 |
raise ImproperlyConfigured( |
|
68 |
'Error loading activation backend %s: "%s"' % (module, e)) |
|
67 | 69 |
try: |
68 | 70 |
backend_class = getattr(mod, backend_class_name) |
69 | 71 |
except AttributeError: |
70 | 72 |
raise ImproperlyConfigured('Module "%s" does not define a activation backend named "%s"' % (module, backend_class_name)) |
71 | 73 |
return backend_class(request) |
72 | 74 |
|
75 |
|
|
73 | 76 |
class ActivationBackend(object): |
74 | 77 |
def __init__(self, request): |
75 | 78 |
self.request = request |
76 |
|
|
79 |
|
|
77 | 80 |
def _is_preaccepted(self, user): |
78 | 81 |
# return True if user email matches specific patterns |
79 | 82 |
for pattern in RE_USER_EMAIL_PATTERNS: |
80 | 83 |
if re.match(pattern, user.email): |
81 | 84 |
return True |
82 | 85 |
return False |
83 |
|
|
86 |
|
|
84 | 87 |
def get_signup_form(self, provider='local', instance=None): |
85 | 88 |
""" |
86 | 89 |
Returns a form instance of the relevant class |
87 | 90 |
""" |
88 | 91 |
main = provider.capitalize() if provider == 'local' else 'ThirdParty' |
89 |
suffix = 'UserCreationForm'
|
|
92 |
suffix = 'UserCreationForm' |
|
90 | 93 |
formclass = '%s%s' % (main, suffix) |
91 | 94 |
request = self.request |
92 | 95 |
initial_data = None |
... | ... | |
94 | 97 |
if provider == request.POST.get('provider', ''): |
95 | 98 |
initial_data = request.POST |
96 | 99 |
return globals()[formclass](initial_data, instance=instance, request=request) |
97 |
|
|
98 |
def handle_activation(self, user, \
|
|
99 |
activation_template_name='im/activation_email.txt', \
|
|
100 |
greeting_template_name='im/welcome_email.txt', \
|
|
101 |
admin_email_template_name='im/account_notification.txt', \
|
|
100 |
|
|
101 |
def handle_activation(self, user, |
|
102 |
activation_template_name='im/activation_email.txt', |
|
103 |
greeting_template_name='im/welcome_email.txt', |
|
104 |
admin_email_template_name='im/account_notification.txt', |
|
102 | 105 |
switch_accounts_email_template_name='im/switch_accounts_email.txt'): |
103 | 106 |
""" |
104 | 107 |
If the user is already active returns immediately. |
... | ... | |
117 | 120 |
if user.conflicting_email(): |
118 | 121 |
send_verification(user, switch_accounts_email_template_name) |
119 | 122 |
return SwitchAccountsVerificationSent(user.email) |
120 |
|
|
123 |
|
|
121 | 124 |
if self._is_preaccepted(user): |
122 | 125 |
if user.email_verified: |
123 | 126 |
activate(user, greeting_template_name) |
... | ... | |
128 | 131 |
else: |
129 | 132 |
send_admin_notification( |
130 | 133 |
template_name=admin_email_template_name, |
131 |
dictionary={'user':user, 'group_creation':True},
|
|
134 |
dictionary={'user': user, 'group_creation': True},
|
|
132 | 135 |
subject='%s alpha2 testing account notification' % SITENAME |
133 | 136 |
) |
134 | 137 |
return NotificationSent() |
... | ... | |
136 | 139 |
logger.exception(e) |
137 | 140 |
raise e |
138 | 141 |
|
142 |
|
|
139 | 143 |
class InvitationsBackend(ActivationBackend): |
140 | 144 |
""" |
141 | 145 |
A activation backend which implements the following workflow: a user |
... | ... | |
148 | 152 |
def get_signup_form(self, provider='local', instance=None): |
149 | 153 |
""" |
150 | 154 |
Returns a form instance of the relevant class |
151 |
|
|
155 |
|
|
152 | 156 |
raises Invitation.DoesNotExist and ValueError if invitation is consumed |
153 | 157 |
or invitation username is reserved. |
154 | 158 |
""" |
... | ... | |
157 | 161 |
initial_data = self.get_signup_initial_data(provider) |
158 | 162 |
prefix = 'Invited' if invitation else '' |
159 | 163 |
main = provider.capitalize() |
160 |
suffix = 'UserCreationForm'
|
|
164 |
suffix = 'UserCreationForm' |
|
161 | 165 |
formclass = '%s%s%s' % (prefix, main, suffix) |
162 | 166 |
return globals()[formclass](initial_data, instance=instance, request=self.request) |
163 | 167 |
|
... | ... | |
174 | 178 |
if invitation: |
175 | 179 |
# create a tmp user with the invitation realname |
176 | 180 |
# to extract first and last name |
177 |
u = AstakosUser(realname = invitation.realname)
|
|
178 |
initial_data = {'email':invitation.username, |
|
179 |
'inviter':invitation.inviter.realname, |
|
180 |
'first_name':u.first_name, |
|
181 |
'last_name':u.last_name, |
|
182 |
'provider':provider} |
|
181 |
u = AstakosUser(realname=invitation.realname)
|
|
182 |
initial_data = {'email': invitation.username,
|
|
183 |
'inviter': invitation.inviter.realname,
|
|
184 |
'first_name': u.first_name,
|
|
185 |
'last_name': u.last_name,
|
|
186 |
'provider': provider}
|
|
183 | 187 |
else: |
184 | 188 |
if provider == request.POST.get('provider', ''): |
185 | 189 |
initial_data = request.POST |
... | ... | |
200 | 204 |
return True |
201 | 205 |
return False |
202 | 206 |
|
207 |
|
|
203 | 208 |
class SimpleBackend(ActivationBackend): |
204 | 209 |
""" |
205 | 210 |
A activation backend which implements the following workflow: a user |
... | ... | |
213 | 218 |
return False |
214 | 219 |
return True |
215 | 220 |
|
221 |
|
|
216 | 222 |
class ActivationResult(object): |
217 | 223 |
def __init__(self, message): |
218 | 224 |
self.message = message |
219 | 225 |
|
226 |
|
|
220 | 227 |
class VerificationSent(ActivationResult): |
221 | 228 |
def __init__(self): |
222 | 229 |
message = _('Verification sent.') |
223 | 230 |
super(VerificationSent, self).__init__(message) |
224 | 231 |
|
232 |
|
|
225 | 233 |
class SwitchAccountsVerificationSent(ActivationResult): |
226 | 234 |
def __init__(self, email): |
227 | 235 |
message = _('This email is already associated with another \ |
... | ... | |
230 | 238 |
to %s. Otherwise just ignore it.' % email) |
231 | 239 |
super(SwitchAccountsVerificationSent, self).__init__(message) |
232 | 240 |
|
241 |
|
|
233 | 242 |
class NotificationSent(ActivationResult): |
234 | 243 |
def __init__(self): |
235 | 244 |
message = _('Your request for an account was successfully received and is now pending \ |
... | ... | |
237 | 246 |
your interest in ~okeanos! The GRNET team.') |
238 | 247 |
super(NotificationSent, self).__init__(message) |
239 | 248 |
|
249 |
|
|
240 | 250 |
class RegistationCompleted(ActivationResult): |
241 | 251 |
def __init__(self): |
242 | 252 |
message = _('Registration completed. You can now login.') |
243 |
super(RegistationCompleted, self).__init__(message) |
|
253 |
super(RegistationCompleted, self).__init__(message) |
b/snf-astakos-app/astakos/im/api/__init__.py | ||
---|---|---|
51 | 51 |
|
52 | 52 |
absolute = lambda request, url: request.build_absolute_uri(url) |
53 | 53 |
|
54 |
|
|
54 | 55 |
def render_fault(request, fault): |
55 | 56 |
if isinstance(fault, InternalServerError) and settings.DEBUG: |
56 | 57 |
fault.details = format_exc(fault) |
... | ... | |
63 | 64 |
response['Content-Length'] = len(response.content) |
64 | 65 |
return response |
65 | 66 |
|
67 |
|
|
66 | 68 |
def api_method(http_method=None): |
67 | 69 |
"""Decorator function for views that implement an API method.""" |
68 | 70 |
def decorator(func): |
... | ... | |
82 | 84 |
return wrapper |
83 | 85 |
return decorator |
84 | 86 |
|
87 |
|
|
85 | 88 |
def _get_user_by_username(user_id): |
86 | 89 |
try: |
87 |
user = AstakosUser.objects.get(username = user_id)
|
|
90 |
user = AstakosUser.objects.get(username=user_id)
|
|
88 | 91 |
except AstakosUser.DoesNotExist: |
89 | 92 |
raise ItemNotFound('Invalid userid') |
90 | 93 |
else: |
91 | 94 |
response = HttpResponse() |
92 |
response.status=200
|
|
93 |
user_info = {'id':user.id, |
|
94 |
'username':user.username, |
|
95 |
'email':[user.email], |
|
96 |
'name':user.realname, |
|
97 |
'auth_token_created':user.auth_token_created.strftime(format), |
|
98 |
'auth_token_expires':user.auth_token_expires.strftime(format), |
|
99 |
'has_credits':user.has_credits, |
|
100 |
'enabled':user.is_active, |
|
101 |
'groups':[g.name for g in user.groups.all()]} |
|
95 |
response.status = 200
|
|
96 |
user_info = {'id': user.id,
|
|
97 |
'username': user.username,
|
|
98 |
'email': [user.email],
|
|
99 |
'name': user.realname,
|
|
100 |
'auth_token_created': user.auth_token_created.strftime(format),
|
|
101 |
'auth_token_expires': user.auth_token_expires.strftime(format),
|
|
102 |
'has_credits': user.has_credits,
|
|
103 |
'enabled': user.is_active,
|
|
104 |
'groups': [g.name for g in user.groups.all()]}
|
|
102 | 105 |
response.content = json.dumps(user_info) |
103 | 106 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
104 | 107 |
response['Content-Length'] = len(response.content) |
105 | 108 |
return response |
106 | 109 |
|
110 |
|
|
107 | 111 |
def _get_user_by_email(email): |
108 | 112 |
if not email: |
109 | 113 |
raise BadRequest('Email missing') |
110 | 114 |
try: |
111 |
user = AstakosUser.objects.get(email = email)
|
|
115 |
user = AstakosUser.objects.get(email=email)
|
|
112 | 116 |
except AstakosUser.DoesNotExist: |
113 | 117 |
raise ItemNotFound('Invalid email') |
114 |
|
|
118 |
|
|
115 | 119 |
if not user.is_active: |
116 | 120 |
raise ItemNotFound('Inactive user') |
117 | 121 |
else: |
118 | 122 |
response = HttpResponse() |
119 |
response.status=200
|
|
120 |
user_info = {'id':user.id, |
|
121 |
'username':user.username, |
|
122 |
'email':[user.email], |
|
123 |
'enabled':user.is_active, |
|
124 |
'name':user.realname, |
|
125 |
'auth_token_created':user.auth_token_created.strftime(format), |
|
126 |
'auth_token_expires':user.auth_token_expires.strftime(format), |
|
127 |
'has_credits':user.has_credits, |
|
128 |
'groups':[g.name for g in user.groups.all()], |
|
129 |
'user_permissions':[p.codename for p in user.user_permissions.all()]} |
|
123 |
response.status = 200
|
|
124 |
user_info = {'id': user.id,
|
|
125 |
'username': user.username,
|
|
126 |
'email': [user.email],
|
|
127 |
'enabled': user.is_active,
|
|
128 |
'name': user.realname,
|
|
129 |
'auth_token_created': user.auth_token_created.strftime(format),
|
|
130 |
'auth_token_expires': user.auth_token_expires.strftime(format),
|
|
131 |
'has_credits': user.has_credits,
|
|
132 |
'groups': [g.name for g in user.groups.all()],
|
|
133 |
'user_permissions': [p.codename for p in user.user_permissions.all()]}
|
|
130 | 134 |
response.content = json.dumps(user_info) |
131 | 135 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
132 | 136 |
response['Content-Length'] = len(response.content) |
133 | 137 |
return response |
134 | 138 |
|
139 |
|
|
135 | 140 |
@api_method(http_method='GET') |
136 | 141 |
def get_services(request): |
137 | 142 |
callback = request.GET.get('callback', None) |
138 | 143 |
services = Service.objects.all() |
139 |
data = tuple({'id':s.pk, 'name':s.name, 'url':s.url, 'icon':s.icon} for s in services) |
|
144 |
data = tuple({'id': s.pk, 'name': s.name, 'url': s.url, 'icon': |
|
145 |
s.icon} for s in services) |
|
140 | 146 |
data = json.dumps(data) |
141 | 147 |
mimetype = 'application/json' |
142 | 148 |
|
... | ... | |
146 | 152 |
|
147 | 153 |
return HttpResponse(content=data, mimetype=mimetype) |
148 | 154 |
|
155 |
|
|
149 | 156 |
@api_method() |
150 | 157 |
def get_menu(request, with_extra_links=False, with_signout=True): |
151 | 158 |
user = request.user |
... | ... | |
159 | 166 |
pass |
160 | 167 |
if not isinstance(user, AstakosUser): |
161 | 168 |
index_url = reverse('index') |
162 |
l = [{ 'url': absolute(request, index_url), 'name': "Sign in"}]
|
|
169 |
l = [{'url': absolute(request, index_url), 'name': "Sign in"}] |
|
163 | 170 |
else: |
164 | 171 |
l = [] |
165 | 172 |
append = l.append |
... | ... | |
216 | 223 |
), |
217 | 224 |
item( |
218 | 225 |
url=absolute(request, |
219 |
reverse('group_create_list') |
|
220 |
), |
|
226 |
reverse('group_create_list')
|
|
227 |
),
|
|
221 | 228 |
name="Create" |
222 | 229 |
), |
223 | 230 |
item( |
... | ... | |
246 | 253 |
name="Sign out" |
247 | 254 |
) |
248 | 255 |
) |
249 |
|
|
256 |
|
|
250 | 257 |
callback = request.GET.get('callback', None) |
251 | 258 |
data = json.dumps(tuple(l)) |
252 | 259 |
mimetype = 'application/json' |
... | ... | |
257 | 264 |
|
258 | 265 |
return HttpResponse(content=data, mimetype=mimetype) |
259 | 266 |
|
267 |
|
|
260 | 268 |
class MenuItem(dict): |
261 | 269 |
current_path = '' |
262 |
|
|
270 |
|
|
263 | 271 |
def __init__(self, *args, **kwargs): |
264 | 272 |
super(MenuItem, self).__init__(*args, **kwargs) |
265 | 273 |
if kwargs.get('url') or kwargs.get('submenu'): |
266 | 274 |
self.__set_is_active__() |
267 |
|
|
275 |
|
|
268 | 276 |
def __setitem__(self, key, value): |
269 | 277 |
super(MenuItem, self).__setitem__(key, value) |
270 | 278 |
if key in ('url', 'submenu'): |
271 | 279 |
self.__set_is_active__() |
272 |
|
|
280 |
|
|
273 | 281 |
def __set_is_active__(self): |
274 | 282 |
if self.get('is_active'): |
275 | 283 |
return |
... | ... | |
285 | 293 |
self.__setitem__('is_active', True) |
286 | 294 |
except StopIteration: |
287 | 295 |
return |
288 |
|
|
296 |
|
|
289 | 297 |
def __setattribute__(self, name, value): |
290 | 298 |
super(MenuItem, self).__setattribute__(name, value) |
291 | 299 |
if name == 'current_path': |
292 |
self.__set_is_active__() |
|
300 |
self.__set_is_active__() |
b/snf-astakos-app/astakos/im/api/admin.py | ||
---|---|---|
39 | 39 |
from django.http import HttpResponse |
40 | 40 |
from django.utils import simplejson as json |
41 | 41 |
|
42 |
from astakos.im.api.faults import (Fault, Unauthorized, InternalServerError, BadRequest, |
|
42 |
from astakos.im.api.faults import ( |
|
43 |
Fault, Unauthorized, InternalServerError, BadRequest, |
|
43 | 44 |
Forbidden) |
44 | 45 |
from astakos.im.api import render_fault, _get_user_by_email, _get_user_by_username |
45 | 46 |
from astakos.im.models import AstakosUser |
... | ... | |
48 | 49 |
logger = logging.getLogger(__name__) |
49 | 50 |
format = ('%a, %d %b %Y %H:%M:%S GMT') |
50 | 51 |
|
52 |
|
|
51 | 53 |
def api_method(http_method=None, token_required=False, perms=None): |
52 | 54 |
"""Decorator function for views that implement an API method.""" |
53 | 55 |
if not perms: |
... | ... | |
81 | 83 |
return wrapper |
82 | 84 |
return decorator |
83 | 85 |
|
86 |
|
|
84 | 87 |
@api_method(http_method='GET', token_required=True) |
85 | 88 |
def authenticate_old(request, user=None): |
86 | 89 |
# Normal Response Codes: 204 |
... | ... | |
102 | 105 |
raise Unauthorized('Pending approval terms') |
103 | 106 |
|
104 | 107 |
response = HttpResponse() |
105 |
response.status=204
|
|
106 |
user_info = {'username':user.username, |
|
107 |
'uniq':user.email, |
|
108 |
'auth_token':user.auth_token, |
|
109 |
'auth_token_created':user.auth_token_created.isoformat(), |
|
110 |
'auth_token_expires':user.auth_token_expires.isoformat(), |
|
111 |
'has_credits':user.has_credits, |
|
112 |
'has_signed_terms':user.signed_terms, |
|
113 |
'groups':[g.name for g in user.groups.all()]} |
|
108 |
response.status = 204
|
|
109 |
user_info = {'username': user.username,
|
|
110 |
'uniq': user.email,
|
|
111 |
'auth_token': user.auth_token,
|
|
112 |
'auth_token_created': user.auth_token_created.isoformat(),
|
|
113 |
'auth_token_expires': user.auth_token_expires.isoformat(),
|
|
114 |
'has_credits': user.has_credits,
|
|
115 |
'has_signed_terms': user.signed_terms,
|
|
116 |
'groups': [g.name for g in user.groups.all()]}
|
|
114 | 117 |
response.content = json.dumps(user_info) |
115 | 118 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
116 | 119 |
response['Content-Length'] = len(response.content) |
117 | 120 |
return response |
118 | 121 |
|
122 |
|
|
119 | 123 |
@api_method(http_method='GET', token_required=True) |
120 | 124 |
def authenticate(request, user=None): |
121 | 125 |
# Normal Response Codes: 204 |
... | ... | |
137 | 141 |
raise Unauthorized('Pending approval terms') |
138 | 142 |
|
139 | 143 |
response = HttpResponse() |
140 |
response.status=204
|
|
141 |
user_info = {'userid':user.username, |
|
142 |
'email':[user.email], |
|
143 |
'name':user.realname, |
|
144 |
'auth_token':user.auth_token, |
|
145 |
'auth_token_created':epoch(user.auth_token_created), |
|
146 |
'auth_token_expires':epoch(user.auth_token_expires), |
|
147 |
'has_credits':user.has_credits, |
|
148 |
'is_active':user.is_active, |
|
149 |
'groups':[g.name for g in user.groups.all()]} |
|
144 |
response.status = 204
|
|
145 |
user_info = {'userid': user.username,
|
|
146 |
'email': [user.email],
|
|
147 |
'name': user.realname,
|
|
148 |
'auth_token': user.auth_token,
|
|
149 |
'auth_token_created': epoch(user.auth_token_created),
|
|
150 |
'auth_token_expires': epoch(user.auth_token_expires),
|
|
151 |
'has_credits': user.has_credits,
|
|
152 |
'is_active': user.is_active,
|
|
153 |
'groups': [g.name for g in user.groups.all()]}
|
|
150 | 154 |
response.content = json.dumps(user_info) |
151 | 155 |
response['Content-Type'] = 'application/json; charset=UTF-8' |
152 | 156 |
response['Content-Length'] = len(response.content) |
153 | 157 |
return response |
154 | 158 |
|
159 |
|
|
155 | 160 |
@api_method(http_method='GET', token_required=True, perms=['im.can_access_userinfo']) |
156 | 161 |
def get_user_by_email(request, user=None): |
157 | 162 |
# Normal Response Codes: 200 |
... | ... | |
163 | 168 |
email = request.GET.get('name') |
164 | 169 |
return _get_user_by_email(email) |
165 | 170 |
|
171 |
|
|
166 | 172 |
@api_method(http_method='GET', token_required=True, perms=['im.can_access_userinfo']) |
167 | 173 |
def get_user_by_username(request, user_id, user=None): |
168 | 174 |
# Normal Response Codes: 200 |
b/snf-astakos-app/astakos/im/api/faults.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 |
32 | 32 |
# or implied, of GRNET S.A. |
33 | 33 |
|
34 |
|
|
34 | 35 |
def camelCase(s): |
35 | 36 |
return s[0].lower() + s[1:] |
36 | 37 |
|
38 |
|
|
37 | 39 |
class Fault(Exception): |
38 | 40 |
def __init__(self, message='', details='', name=''): |
39 | 41 |
Exception.__init__(self, message, details, name) |
... | ... | |
41 | 43 |
self.details = details |
42 | 44 |
self.name = name or camelCase(self.__class__.__name__) |
43 | 45 |
|
46 |
|
|
44 | 47 |
class BadRequest(Fault): |
45 | 48 |
code = 400 |
46 | 49 |
|
50 |
|
|
47 | 51 |
class Unauthorized(Fault): |
48 | 52 |
code = 401 |
49 | 53 |
|
54 |
|
|
50 | 55 |
class InternalServerError(Fault): |
51 | 56 |
code = 500 |
52 | 57 |
|
58 |
|
|
53 | 59 |
class Forbidden(Fault): |
54 | 60 |
code = 403 |
55 | 61 |
|
62 |
|
|
56 | 63 |
class ItemNotFound(Fault): |
57 |
code = 404 |
|
64 |
code = 404 |
b/snf-astakos-app/astakos/im/api/service.py | ||
---|---|---|
47 | 47 |
|
48 | 48 |
logger = logging.getLogger(__name__) |
49 | 49 |
|
50 |
|
|
50 | 51 |
def api_method(http_method=None, token_required=False): |
51 | 52 |
"""Decorator function for views that implement an API method.""" |
52 | 53 |
def decorator(func): |
... | ... | |
61 | 62 |
raise Unauthorized('Access denied') |
62 | 63 |
try: |
63 | 64 |
service = Service.objects.get(auth_token=x_auth_token) |
64 |
|
|
65 |
|
|
65 | 66 |
# Check if the token has expired. |
66 | 67 |
if (time() - mktime(service.auth_token_expires.timetuple())) > 0: |
67 | 68 |
raise Unauthorized('Authentication expired') |
... | ... | |
78 | 79 |
return wrapper |
79 | 80 |
return decorator |
80 | 81 |
|
82 |
|
|
81 | 83 |
@api_method(http_method='GET', token_required=True) |
82 | 84 |
def get_user_by_email(request, user=None): |
83 | 85 |
# Normal Response Codes: 200 |
... | ... | |
89 | 91 |
email = request.GET.get('name') |
90 | 92 |
return _get_user_by_email(email) |
91 | 93 |
|
94 |
|
|
92 | 95 |
@api_method(http_method='GET', token_required=True) |
93 | 96 |
def get_user_by_username(request, user_id, user=None): |
94 | 97 |
# Normal Response Codes: 200 |
... | ... | |
99 | 102 |
# itemNotFound (404) |
100 | 103 |
return _get_user_by_username(user_id) |
101 | 104 |
|
105 |
|
|
102 | 106 |
@csrf_exempt |
103 | 107 |
@api_method(http_method='POST', token_required=True) |
104 | 108 |
def send_feedback(request, email_template_name='im/feedback_mail.txt'): |
... | ... | |
109 | 113 |
auth_token = request.POST.get('auth', '') |
110 | 114 |
if not auth_token: |
111 | 115 |
raise BadRequest('Missing user authentication') |
112 |
|
|
113 |
user = None
|
|
116 |
|
|
117 |
user = None |
|
114 | 118 |
try: |
115 | 119 |
user = AstakosUser.objects.get(auth_token=auth_token) |
116 | 120 |
except: |
117 | 121 |
pass |
118 |
|
|
122 |
|
|
119 | 123 |
if not user: |
120 | 124 |
raise BadRequest('Invalid user authentication') |
121 |
|
|
125 |
|
|
122 | 126 |
form = FeedbackForm(request.POST) |
123 | 127 |
if not form.is_valid(): |
124 | 128 |
raise BadRequest('Invalid data') |
125 |
|
|
129 |
|
|
126 | 130 |
msg = form.cleaned_data['feedback_msg'] |
127 | 131 |
data = form.cleaned_data['feedback_data'] |
128 | 132 |
send_feedback_func(msg, data, user, email_template_name) |
129 | 133 |
response = HttpResponse(status=200) |
130 | 134 |
response['Content-Length'] = len(response.content) |
131 |
return response |
|
135 |
return response |
b/snf-astakos-app/astakos/im/auth_backends.py | ||
---|---|---|
36 | 36 |
|
37 | 37 |
from astakos.im.models import AstakosUser |
38 | 38 |
|
39 |
|
|
39 | 40 |
class TokenBackend(ModelBackend): |
40 | 41 |
""" |
41 | 42 |
AuthenticationBackend used to authenticate using token instead |
... | ... | |
54 | 55 |
except AstakosUser.DoesNotExist: |
55 | 56 |
return None |
56 | 57 |
|
58 |
|
|
57 | 59 |
class EmailBackend(ModelBackend): |
58 | 60 |
""" |
59 | 61 |
If the ``username`` parameter is actually an email uses email to authenticate |
60 | 62 |
the user else tries the username. |
61 |
|
|
63 |
|
|
62 | 64 |
Used from ``astakos.im.forms.LoginForm`` to authenticate. |
63 | 65 |
""" |
64 | 66 |
def authenticate(self, username=None, password=None): |
... | ... | |
77 | 79 |
return None |
78 | 80 |
if user.check_password(password): |
79 | 81 |
return user |
80 |
|
|
82 |
|
|
81 | 83 |
def get_user(self, user_id): |
82 | 84 |
try: |
83 | 85 |
return AstakosUser.objects.get(pk=user_id) |
84 | 86 |
except AstakosUser.DoesNotExist: |
85 |
return None |
|
87 |
return None |
b/snf-astakos-app/astakos/im/context_processors.py | ||
---|---|---|
32 | 32 |
# or implied, of GRNET S.A. |
33 | 33 |
|
34 | 34 |
from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, IM_STATIC_URL, \ |
35 |
LOGIN_MESSAGES, SIGNUP_MESSAGES, PROFILE_MESSAGES, \
|
|
36 |
GLOBAL_MESSAGES, PROFILE_EXTRA_LINKS
|
|
35 |
LOGIN_MESSAGES, SIGNUP_MESSAGES, PROFILE_MESSAGES, \ |
|
36 |
GLOBAL_MESSAGES, PROFILE_EXTRA_LINKS |
|
37 | 37 |
from astakos.im.api import get_menu |
38 | 38 |
from astakos.im.util import get_query |
39 | 39 |
from astakos.im.models import GroupKind |
40 | 40 |
|
41 | 41 |
from django.utils import simplejson as json |
42 | 42 |
|
43 |
|
|
43 | 44 |
def im_modules(request): |
44 | 45 |
return {'im_modules': IM_MODULES} |
45 | 46 |
|
47 |
|
|
46 | 48 |
def next(request): |
47 |
return {'next' : get_query(request).get('next', '')} |
|
49 |
return {'next': get_query(request).get('next', '')} |
|
50 |
|
|
48 | 51 |
|
49 | 52 |
def code(request): |
50 |
return {'code' : request.GET.get('code', '')} |
|
53 |
return {'code': request.GET.get('code', '')} |
|
54 |
|
|
51 | 55 |
|
52 | 56 |
def invitations(request): |
53 |
return {'invitations_enabled' :INVITATIONS_ENABLED} |
|
57 |
return {'invitations_enabled': INVITATIONS_ENABLED} |
|
58 |
|
|
54 | 59 |
|
55 | 60 |
def media(request): |
56 |
return {'IM_STATIC_URL' : IM_STATIC_URL} |
|
61 |
return {'IM_STATIC_URL': IM_STATIC_URL} |
|
62 |
|
|
57 | 63 |
|
58 | 64 |
def custom_messages(request): |
59 |
EXTRA_MESSAGES_SET = bool(GLOBAL_MESSAGES or SIGNUP_MESSAGES or \
|
|
60 |
LOGIN_MESSAGES or PROFILE_MESSAGES) |
|
65 |
EXTRA_MESSAGES_SET = bool(GLOBAL_MESSAGES or SIGNUP_MESSAGES or |
|
66 |
LOGIN_MESSAGES or PROFILE_MESSAGES)
|
|
61 | 67 |
return { |
62 |
'GLOBAL_MESSAGES' : GLOBAL_MESSAGES, |
|
63 |
'SIGNUP_MESSAGES' : SIGNUP_MESSAGES, |
|
64 |
'LOGIN_MESSAGES' : LOGIN_MESSAGES, |
|
65 |
'PROFILE_MESSAGES' : PROFILE_MESSAGES, |
|
66 |
'PROFILE_EXTRA_LINKS' : PROFILE_EXTRA_LINKS, |
|
67 |
'EXTRA_MESSAGES_SET' : EXTRA_MESSAGES_SET |
|
68 |
} |
|
68 |
'GLOBAL_MESSAGES': GLOBAL_MESSAGES, |
|
69 |
'SIGNUP_MESSAGES': SIGNUP_MESSAGES, |
|
70 |
'LOGIN_MESSAGES': LOGIN_MESSAGES, |
|
71 |
'PROFILE_MESSAGES': PROFILE_MESSAGES, |
|
72 |
'PROFILE_EXTRA_LINKS': PROFILE_EXTRA_LINKS, |
|
73 |
'EXTRA_MESSAGES_SET': EXTRA_MESSAGES_SET |
|
74 |
} |
|
75 |
|
|
69 | 76 |
|
70 | 77 |
def menu(request): |
71 | 78 |
try: |
... | ... | |
74 | 81 |
except Exception, e: |
75 | 82 |
return {} |
76 | 83 |
else: |
77 |
return {'menu':menu_items} |
|
84 |
return {'menu': menu_items} |
|
85 |
|
|
78 | 86 |
|
79 | 87 |
def group_kinds(request): |
80 |
return {'group_kinds':GroupKind.objects.exclude( |
|
88 |
return {'group_kinds': GroupKind.objects.exclude(
|
|
81 | 89 |
name='default' |
82 |
).values_list('name', flat=True) |
|
83 |
} |
|
90 |
).values_list('name', flat=True) |
|
91 |
} |
b/snf-astakos-app/astakos/im/endpoints/aquarium/client.py | ||
---|---|---|
37 | 37 |
|
38 | 38 |
from astakos.im.settings import AQUARIUM_URL |
39 | 39 |
|
40 |
|
|
40 | 41 |
class AquariumClient(): |
41 | 42 |
def get_billing(self, user, start, end): |
42 | 43 |
if not AQUARIUM_URL: |
43 | 44 |
return |
44 |
|
|
45 |
|
|
45 | 46 |
url = AQUARIUM_URL.rstrip('/') |
46 | 47 |
url = '%s/%s/bill/%d/%d' % (url, user, start, end) |
47 | 48 |
r = requests.get(url) |
... | ... | |
49 | 50 |
return r.status_code, json.loads(r.text) |
50 | 51 |
except ValueError: |
51 | 52 |
pass |
52 |
return r.status_code, None |
|
53 |
return r.status_code, None |
b/snf-astakos-app/astakos/im/endpoints/aquarium/consumer.py | ||
---|---|---|
40 | 40 |
|
41 | 41 |
from astakos.im.models import AstakosUser |
42 | 42 |
|
43 |
|
|
43 | 44 |
def on_creditevent(msg): |
44 | 45 |
""" |
45 | 46 |
Queue handler for updating AstakosUser has_credits |
... | ... | |
51 | 52 |
user.has_credits = has_credits |
52 | 53 |
user.save() |
53 | 54 |
except BaseException, e: |
54 |
logger.exception(e) |
|
55 |
logger.exception(e) |
b/snf-astakos-app/astakos/im/endpoints/aquarium/producer.py | ||
---|---|---|
40 | 40 |
|
41 | 41 |
if QUEUE_CONNECTION: |
42 | 42 |
from synnefo.lib.queue import (exchange_connect, exchange_send, |
43 |
exchange_close, UserEvent, Receipt |
|
44 |
) |
|
43 |
exchange_close, UserEvent, Receipt
|
|
44 |
)
|
|
45 | 45 |
|
46 |
QUEUE_CLIENT_ID = '3' # Astakos. |
|
46 |
QUEUE_CLIENT_ID = '3' # Astakos.
|
|
47 | 47 |
INSTANCE_ID = '1' |
48 | 48 |
RESOURCE = 'addcredits' |
49 | 49 |
DEFAULT_CREDITS = 1000 |
50 | 50 |
|
51 | 51 |
logging.basicConfig(format='%(asctime)s [%(levelname)s] %(name)s %(message)s', |
52 |
datefmt='%Y-%m-%d %H:%M:%S' |
|
53 |
) |
|
52 |
datefmt='%Y-%m-%d %H:%M:%S'
|
|
53 |
)
|
|
54 | 54 |
logger = logging.getLogger('endpoint.aquarium') |
55 | 55 |
|
56 |
|
|
56 | 57 |
def wrapper(func): |
57 | 58 |
@wraps(func) |
58 | 59 |
def wrapper(*args, **kwargs): |
59 | 60 |
if not QUEUE_CONNECTION: |
60 | 61 |
return |
61 |
|
|
62 |
|
|
62 | 63 |
try: |
63 | 64 |
body, key = func(*args, **kwargs) |
64 | 65 |
conn = exchange_connect(QUEUE_CONNECTION) |
... | ... | |
71 | 72 |
logger.exception(e) |
72 | 73 |
return wrapper |
73 | 74 |
|
75 |
|
|
74 | 76 |
@wrapper |
75 | 77 |
def report_user_event(user, create=False): |
76 | 78 |
eventType = 'create' if not create else 'modify' |
77 | 79 |
body = UserEvent(QUEUE_CLIENT_ID, user.email, user.is_active, eventType, {} |
78 |
).format() |
|
80 |
).format()
|
|
79 | 81 |
routing_key = '%s.user' |
80 | 82 |
return body, routing_key |
81 | 83 |
|
84 |
|
|
82 | 85 |
@wrapper |
83 | 86 |
def report_user_credits_event(user): |
84 | 87 |
body = Receipt(QUEUE_CLIENT_ID, user.email, INSTANCE_ID, RESOURCE, |
85 |
DEFAULT_CREDITS, details={} |
|
86 |
).format() |
|
88 |
DEFAULT_CREDITS, details={}
|
|
89 |
).format()
|
|
87 | 90 |
routing_key = '%s.resource' |
88 | 91 |
return body, routing_key |
89 | 92 |
|
93 |
|
|
90 | 94 |
def report_credits_event(): |
91 | 95 |
# better approach? |
92 | 96 |
from astakos.im.models import AstakosUser |
b/snf-astakos-app/astakos/im/endpoints/quotaholder.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 |
... | ... | |
45 | 45 |
|
46 | 46 |
logger = logging.getLogger(__name__) |
47 | 47 |
|
48 |
|
|
48 | 49 |
def register_users(users, client=None): |
49 | 50 |
if not users: |
50 | 51 |
return |
51 |
|
|
52 |
|
|
52 | 53 |
if not QUOTA_HOLDER_URL: |
53 | 54 |
return |
54 |
|
|
55 |
|
|
55 | 56 |
c = client or QuotaholderHTTP(QUOTA_HOLDER_URL) |
56 | 57 |
data = [] |
57 | 58 |
append = data.append |
... | ... | |
60 | 61 |
entity = user.email |
61 | 62 |
except AttributeError: |
62 | 63 |
continue |
63 |
else:
|
|
64 |
else: |
|
64 | 65 |
args = entity, owner, key, ownerkey = ( |
65 | 66 |
entity, 'system', ENTITY_KEY, '' |
66 | 67 |
) |
67 | 68 |
append(args) |
68 |
|
|
69 |
|
|
69 | 70 |
if not data: |
70 | 71 |
return |
71 |
|
|
72 |
|
|
72 | 73 |
rejected = c.create_entity( |
73 | 74 |
context={}, |
74 | 75 |
create_entity=data, |
75 | 76 |
) |
76 | 77 |
msg = _('Create entities: %s - Rejected: %s' % (data, rejected,)) |
77 | 78 |
logger.log(LOGGING_LEVEL, msg) |
78 |
|
|
79 |
|
|
79 | 80 |
created = filter(lambda u: unicode(u.email) not in rejected, users) |
80 | 81 |
send_quota(created, c) |
81 | 82 |
return rejected |
82 | 83 |
|
84 |
|
|
83 | 85 |
def send_quota(users, client=None): |
84 | 86 |
if not users: |
85 | 87 |
return |
86 |
|
|
88 |
|
|
87 | 89 |
if not QUOTA_HOLDER_URL: |
88 | 90 |
return |
89 |
|
|
91 |
|
|
90 | 92 |
c = client or QuotaholderHTTP(QUOTA_HOLDER_URL) |
91 | 93 |
data = [] |
92 | 94 |
append = data.append |
... | ... | |
97 | 99 |
continue |
98 | 100 |
else: |
99 | 101 |
for resource, limit in user.quota.iteritems(): |
100 |
args = entity, resource, key, quantity, capacity, import_limit , \
|
|
101 |
export_limit, flags =( |
|
102 |
args = entity, resource, key, quantity, capacity, import_limit, \ |
|
103 |
export_limit, flags = (
|
|
102 | 104 |
entity, resource, ENTITY_KEY, '0', str(limit), 0, 0, 0 |
103 |
) |
|
105 |
)
|
|
104 | 106 |
append(args) |
105 |
|
|
107 |
|
|
106 | 108 |
if not data: |
107 | 109 |
return |
108 |
|
|
110 |
|
|
109 | 111 |
rejected = c.set_quota(context={}, set_quota=data) |
110 | 112 |
msg = _('Set quota: %s - Rejected: %s' % (data, rejected,)) |
111 | 113 |
logger.log(LOGGING_LEVEL, msg) |
112 | 114 |
return rejected |
113 | 115 |
|
116 |
|
|
114 | 117 |
def get_quota(users, client=None): |
115 | 118 |
if not users: |
116 | 119 |
return |
117 |
|
|
120 |
|
|
118 | 121 |
if not QUOTA_HOLDER_URL: |
119 | 122 |
return |
120 |
|
|
123 |
|
|
121 | 124 |
c = client or QuotaholderHTTP(QUOTA_HOLDER_URL) |
122 | 125 |
data = [] |
123 | 126 |
append = data.append |
... | ... | |
130 | 133 |
for r in user.quota.keys(): |
131 | 134 |
args = entity, resource, key = entity, r, ENTITY_KEY |
132 | 135 |
append(args) |
133 |
|
|
136 |
|
|
134 | 137 |
if not data: |
135 | 138 |
return |
136 |
|
|
139 |
|
|
137 | 140 |
r = c.get_quota(context={}, get_quota=data) |
138 | 141 |
msg = _('Get quota: %s' % data) |
139 | 142 |
logger.log(LOGGING_LEVEL, msg) |
140 |
return r |
|
143 |
return r |
b/snf-astakos-app/astakos/im/forms.py | ||
---|---|---|
35 | 35 |
from django import forms |
36 | 36 |
from django.utils.translation import ugettext as _ |
37 | 37 |
from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm, |
38 |
PasswordResetForm, PasswordChangeForm |
|
39 |
) |
|
38 |
PasswordResetForm, PasswordChangeForm
|
|
39 |
)
|
|
40 | 40 |
from django.core.mail import send_mail |
41 | 41 |
from django.contrib.auth.tokens import default_token_generator |
42 | 42 |
from django.template import Context, loader |
... | ... | |
47 | 47 |
from django.forms.extras.widgets import SelectDateWidget |
48 | 48 |
from django.conf import settings |
49 | 49 |
|
50 |
from astakos.im.models import (AstakosUser, EmailChange, AstakosGroup, Invitation, |
|
50 |
from astakos.im.models import ( |
|
51 |
AstakosUser, EmailChange, AstakosGroup, Invitation, |
|
51 | 52 |
Membership, GroupKind, get_latest_terms |
52 | 53 |
) |
53 | 54 |
from astakos.im.settings import (INVITATIONS_PER_LEVEL, BASEURL, SITENAME, |
54 |
RECAPTCHA_PRIVATE_KEY, RECAPTCHA_ENABLED, DEFAULT_CONTACT_EMAIL, |
|
55 |
LOGGING_LEVEL |
|
56 |
) |
|
55 |
RECAPTCHA_PRIVATE_KEY, RECAPTCHA_ENABLED, DEFAULT_CONTACT_EMAIL,
|
|
56 |
LOGGING_LEVEL
|
|
57 |
)
|
|
57 | 58 |
from astakos.im.widgets import DummyWidget, RecaptchaWidget |
58 | 59 |
from astakos.im.functions import send_change_email |
59 | 60 |
|
... | ... | |
66 | 67 |
|
67 | 68 |
logger = logging.getLogger(__name__) |
68 | 69 |
|
70 |
|
|
69 | 71 |
class LocalUserCreationForm(UserCreationForm): |
70 | 72 |
""" |
71 | 73 |
Extends the built in UserCreationForm in several ways: |
... | ... | |
75 | 77 |
* User created is not active. |
76 | 78 |
""" |
77 | 79 |
recaptcha_challenge_field = forms.CharField(widget=DummyWidget) |
78 |
recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='') |
|
80 |
recaptcha_response_field = forms.CharField( |
|
81 |
widget=RecaptchaWidget, label='') |
|
79 | 82 |
|
80 | 83 |
class Meta: |
81 | 84 |
model = AstakosUser |
82 |
fields = ("email", "first_name", "last_name", "has_signed_terms", "has_signed_terms") |
|
85 |
fields = ("email", "first_name", "last_name", |
|
86 |
"has_signed_terms", "has_signed_terms") |
|
83 | 87 |
|
84 | 88 |
def __init__(self, *args, **kwargs): |
85 | 89 |
""" |
... | ... | |
90 | 94 |
kwargs.pop('request') |
91 | 95 |
self.ip = request.META.get('REMOTE_ADDR', |
92 | 96 |
request.META.get('HTTP_X_REAL_IP', None)) |
93 |
|
|
97 |
|
|
94 | 98 |
super(LocalUserCreationForm, self).__init__(*args, **kwargs) |
95 | 99 |
self.fields.keyOrder = ['email', 'first_name', 'last_name', |
96 | 100 |
'password1', 'password2'] |
97 | 101 |
|
98 | 102 |
if RECAPTCHA_ENABLED: |
99 | 103 |
self.fields.keyOrder.extend(['recaptcha_challenge_field', |
100 |
'recaptcha_response_field',]) |
|
104 |
'recaptcha_response_field', ])
|
|
101 | 105 |
if get_latest_terms(): |
102 | 106 |
self.fields.keyOrder.append('has_signed_terms') |
103 |
|
|
107 |
|
|
104 | 108 |
if 'has_signed_terms' in self.fields: |
105 | 109 |
# Overriding field label since we need to apply a link |
106 | 110 |
# to the terms within the label |
107 | 111 |
terms_link_html = '<a href="%s" target="_blank">%s</a>' \ |
108 |
% (reverse('latest_terms'), _("the terms"))
|
|
112 |
% (reverse('latest_terms'), _("the terms")) |
|
109 | 113 |
self.fields['has_signed_terms'].label = \ |
110 |
mark_safe("I agree with %s" % terms_link_html)
|
|
114 |
mark_safe("I agree with %s" % terms_link_html) |
|
111 | 115 |
|
112 | 116 |
def clean_email(self): |
113 | 117 |
email = self.cleaned_data['email'] |
... | ... | |
138 | 142 |
rrf = self.cleaned_data['recaptcha_response_field'] |
139 | 143 |
check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip) |
140 | 144 |
if not check.is_valid: |
141 |
raise forms.ValidationError(_('You have not entered the correct words')) |
|
145 |
raise forms.ValidationError( |
|
146 |
_('You have not entered the correct words')) |
|
142 | 147 |
|
143 | 148 |
def save(self, commit=True): |
144 | 149 |
""" |
... | ... | |
152 | 157 |
logger.log(LOGGING_LEVEL, 'Created user %s' % user.email) |
153 | 158 |
return user |
154 | 159 |
|
160 |
|
|
155 | 161 |
class InvitedLocalUserCreationForm(LocalUserCreationForm): |
156 | 162 |
""" |
157 | 163 |
Extends the LocalUserCreationForm: email is readonly. |
... | ... | |
170 | 176 |
ro = ('email', 'username',) |
171 | 177 |
for f in ro: |
172 | 178 |
self.fields[f].widget.attrs['readonly'] = True |
173 |
|
|
174 | 179 |
|
175 | 180 |
def save(self, commit=True): |
176 | 181 |
user = super(InvitedLocalUserCreationForm, self).save(commit=False) |
... | ... | |
182 | 187 |
user.save() |
183 | 188 |
return user |
184 | 189 |
|
190 |
|
|
185 | 191 |
class ThirdPartyUserCreationForm(forms.ModelForm): |
186 | 192 |
class Meta: |
187 | 193 |
model = AstakosUser |
188 |
fields = ("email", "first_name", "last_name", "third_party_identifier", "has_signed_terms") |
|
189 |
|
|
194 |
fields = ("email", "first_name", "last_name", |
|
195 |
"third_party_identifier", "has_signed_terms") |
|
196 |
|
|
190 | 197 |
def __init__(self, *args, **kwargs): |
191 | 198 |
""" |
192 | 199 |
Changes the order of fields, and removes the username field. |
... | ... | |
195 | 202 |
if self.request: |
196 | 203 |
kwargs.pop('request') |
197 | 204 |
super(ThirdPartyUserCreationForm, self).__init__(*args, **kwargs) |
198 |
self.fields.keyOrder = ['email', 'first_name', 'last_name', 'third_party_identifier'] |
|
205 |
self.fields.keyOrder = ['email', 'first_name', 'last_name', |
|
206 |
'third_party_identifier'] |
|
199 | 207 |
if get_latest_terms(): |
200 | 208 |
self.fields.keyOrder.append('has_signed_terms') |
201 | 209 |
#set readonly form fields |
202 | 210 |
ro = ["third_party_identifier"] |
203 | 211 |
for f in ro: |
204 | 212 |
self.fields[f].widget.attrs['readonly'] = True |
205 |
|
|
213 |
|
|
206 | 214 |
if 'has_signed_terms' in self.fields: |
207 | 215 |
# Overriding field label since we need to apply a link |
208 | 216 |
# to the terms within the label |
209 | 217 |
terms_link_html = '<a href="%s" target="_blank">%s</a>' \ |
210 |
% (reverse('latest_terms'), _("the terms"))
|
|
218 |
% (reverse('latest_terms'), _("the terms")) |
|
211 | 219 |
self.fields['has_signed_terms'].label = \ |
212 |
mark_safe("I agree with %s" % terms_link_html)
|
|
213 |
|
|
220 |
mark_safe("I agree with %s" % terms_link_html) |
|
221 |
|
|
214 | 222 |
def clean_email(self): |
215 | 223 |
email = self.cleaned_data['email'] |
216 | 224 |
if not email: |
217 | 225 |
raise forms.ValidationError(_("This field is required")) |
218 | 226 |
return email |
219 |
|
|
227 |
|
|
220 | 228 |
def clean_has_signed_terms(self): |
221 | 229 |
has_signed_terms = self.cleaned_data['has_signed_terms'] |
222 | 230 |
if not has_signed_terms: |
223 | 231 |
raise forms.ValidationError(_('You have to agree with the terms')) |
224 | 232 |
return has_signed_terms |
225 |
|
|
233 |
|
|
226 | 234 |
def save(self, commit=True): |
227 | 235 |
user = super(ThirdPartyUserCreationForm, self).save(commit=False) |
228 | 236 |
user.set_unusable_password() |
... | ... | |
233 | 241 |
logger.log(LOGGING_LEVEL, 'Created user %s' % user.email) |
234 | 242 |
return user |
235 | 243 |
|
244 |
|
|
236 | 245 |
class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm): |
237 | 246 |
""" |
238 | 247 |
Extends the ThirdPartyUserCreationForm: email is readonly. |
... | ... | |
241 | 250 |
""" |
242 | 251 |
Changes the order of fields, and removes the username field. |
243 | 252 |
""" |
244 |
super(InvitedThirdPartyUserCreationForm, self).__init__(*args, **kwargs) |
|
253 |
super( |
|
254 |
InvitedThirdPartyUserCreationForm, self).__init__(*args, **kwargs) |
|
245 | 255 |
|
246 | 256 |
#set readonly form fields |
247 | 257 |
ro = ('email',) |
248 | 258 |
for f in ro: |
249 | 259 |
self.fields[f].widget.attrs['readonly'] = True |
250 |
|
|
260 |
|
|
251 | 261 |
def save(self, commit=True): |
252 |
user = super(InvitedThirdPartyUserCreationForm, self).save(commit=False) |
|
262 |
user = super( |
|
263 |
InvitedThirdPartyUserCreationForm, self).save(commit=False) |
|
253 | 264 |
level = user.invitation.inviter.level + 1 |
254 | 265 |
user.level = level |
255 | 266 |
user.invitations = INVITATIONS_PER_LEVEL.get(level, 0) |
... | ... | |
258 | 269 |
user.save() |
259 | 270 |
return user |
260 | 271 |
|
272 |
|
|
261 | 273 |
class ShibbolethUserCreationForm(ThirdPartyUserCreationForm): |
262 |
additional_email = forms.CharField(widget=forms.HiddenInput(), label='', required = False) |
|
263 |
|
|
274 |
additional_email = forms.CharField( |
|
275 |
widget=forms.HiddenInput(), label='', required=False) |
|
276 |
|
|
264 | 277 |
def __init__(self, *args, **kwargs): |
265 | 278 |
super(ShibbolethUserCreationForm, self).__init__(*args, **kwargs) |
266 | 279 |
self.fields.keyOrder.append('additional_email') |
267 | 280 |
# copy email value to additional_mail in case user will change it |
268 | 281 |
name = 'email' |
269 | 282 |
field = self.fields[name] |
270 |
self.initial['additional_email'] = self.initial.get(name, field.initial) |
|
271 |
|
|
283 |
self.initial['additional_email'] = self.initial.get(name, |
|
284 |
field.initial) |
|
285 |
|
|
272 | 286 |
def clean_email(self): |
273 | 287 |
email = self.cleaned_data['email'] |
274 |
for user in AstakosUser.objects.filter(email = email):
|
|
288 |
for user in AstakosUser.objects.filter(email=email):
|
|
275 | 289 |
if user.provider == 'shibboleth': |
276 | 290 |
raise forms.ValidationError(_("This email is already associated with another shibboleth account.")) |
277 | 291 |
elif not user.is_active: |
... | ... | |
280 | 294 |
super(ShibbolethUserCreationForm, self).clean_email() |
281 | 295 |
return email |
282 | 296 |
|
297 |
|
|
283 | 298 |
class InvitedShibbolethUserCreationForm(ShibbolethUserCreationForm, InvitedThirdPartyUserCreationForm): |
284 | 299 |
pass |
285 |
|
|
300 |
|
|
301 |
|
|
286 | 302 |
class LoginForm(AuthenticationForm): |
287 | 303 |
username = forms.EmailField(label=_("Email")) |
288 | 304 |
recaptcha_challenge_field = forms.CharField(widget=DummyWidget) |
289 |
recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='') |
|
290 |
|
|
305 |
recaptcha_response_field = forms.CharField( |
|
306 |
widget=RecaptchaWidget, label='') |
|
307 |
|
|
291 | 308 |
def __init__(self, *args, **kwargs): |
292 | 309 |
was_limited = kwargs.get('was_limited', False) |
293 | 310 |
request = kwargs.get('request', None) |
294 | 311 |
if request: |
295 | 312 |
self.ip = request.META.get('REMOTE_ADDR', |
296 | 313 |
request.META.get('HTTP_X_REAL_IP', None)) |
297 |
|
|
314 |
|
|
298 | 315 |
t = ('request', 'was_limited') |
299 | 316 |
for elem in t: |
300 | 317 |
if elem in kwargs.keys(): |
301 | 318 |
kwargs.pop(elem) |
302 | 319 |
super(LoginForm, self).__init__(*args, **kwargs) |
303 |
|
|
320 |
|
|
304 | 321 |
self.fields.keyOrder = ['username', 'password'] |
305 | 322 |
if was_limited and RECAPTCHA_ENABLED: |
306 | 323 |
self.fields.keyOrder.extend(['recaptcha_challenge_field', |
307 |
'recaptcha_response_field',]) |
|
308 |
|
|
324 |
'recaptcha_response_field', ])
|
|
325 |
|
|
309 | 326 |
def clean_recaptcha_response_field(self): |
310 | 327 |
if 'recaptcha_challenge_field' in self.cleaned_data: |
311 | 328 |
self.validate_captcha() |
... | ... | |
321 | 338 |
rrf = self.cleaned_data['recaptcha_response_field'] |
322 | 339 |
check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip) |
323 | 340 |
if not check.is_valid: |
324 |
raise forms.ValidationError(_('You have not entered the correct words')) |
|
325 |
|
|
341 |
raise forms.ValidationError( |
|
342 |
_('You have not entered the correct words')) |
|
343 |
|
|
326 | 344 |
def clean(self): |
327 | 345 |
super(LoginForm, self).clean() |
328 | 346 |
if self.user_cache and self.user_cache.provider not in ('local', ''): |
329 | 347 |
raise forms.ValidationError(_('Local login is not the current authentication method for this account.')) |
330 | 348 |
return self.cleaned_data |
331 | 349 |
|
350 |
|
|
332 | 351 |
class ProfileForm(forms.ModelForm): |
333 | 352 |
""" |
334 | 353 |
Subclass of ``ModelForm`` for permiting user to edit his/her profile. |
... | ... | |
341 | 360 |
|
342 | 361 |
class Meta: |
343 | 362 |
model = AstakosUser |
344 |
fields = ('email', 'first_name', 'last_name', 'auth_token', 'auth_token_expires') |
|
363 |
fields = ('email', 'first_name', 'last_name', 'auth_token', |
|
364 |
'auth_token_expires') |
|
345 | 365 |
|
346 | 366 |
def __init__(self, *args, **kwargs): |
347 | 367 |
super(ProfileForm, self).__init__(*args, **kwargs) |
... | ... | |
360 | 380 |
user.save() |
361 | 381 |
return user |
362 | 382 |
|
383 |
|
|
363 | 384 |
class FeedbackForm(forms.Form): |
364 | 385 |
""" |
365 | 386 |
Form for writing feedback. |
... | ... | |
368 | 389 |
feedback_data = forms.CharField(widget=forms.HiddenInput(), label='', |
369 | 390 |
required=False) |
370 | 391 |
|
392 |
|
|
371 | 393 |
class SendInvitationForm(forms.Form): |
372 | 394 |
""" |
373 | 395 |
Form for sending an invitations |
374 | 396 |
""" |
375 | 397 |
|
376 |
email = forms.EmailField(required = True, label = 'Email address') |
|
377 |
first_name = forms.EmailField(label = 'First name') |
|
378 |
last_name = forms.EmailField(label = 'Last name') |
|
398 |
email = forms.EmailField(required=True, label='Email address') |
|
399 |
first_name = forms.EmailField(label='First name') |
|
400 |
last_name = forms.EmailField(label='Last name') |
|
401 |
|
|
379 | 402 |
|
380 | 403 |
class ExtendedPasswordResetForm(PasswordResetForm): |
381 | 404 |
""" |
... | ... | |
390 | 413 |
try: |
391 | 414 |
user = AstakosUser.objects.get(email=email, is_active=True) |
392 | 415 |
if not user.has_usable_password(): |
393 |
raise forms.ValidationError(_("This account has not a usable password.")) |
|
416 |
raise forms.ValidationError( |
|
417 |
_("This account has not a usable password.")) |
|
394 | 418 |
except AstakosUser.DoesNotExist: |
395 | 419 |
raise forms.ValidationError(_('That e-mail address doesn\'t have an associated user account. Are you sure you\'ve registered?')) |
396 | 420 |
return email |
397 |
|
|
398 |
def save(self, domain_override=None, email_template_name='registration/password_reset_email.html', |
|
399 |
use_https=False, token_generator=default_token_generator, request=None): |
|
421 |
|
|
422 |
def save( |
|
423 |
self, domain_override=None, email_template_name='registration/password_reset_email.html', |
|
424 |
use_https=False, token_generator=default_token_generator, request=None): |
|
400 | 425 |
""" |
401 | 426 |
Generates a one-use only link for resetting password and sends to the user. |
402 | 427 |
""" |
403 | 428 |
for user in self.users_cache: |
404 | 429 |
url = reverse('django.contrib.auth.views.password_reset_confirm', |
405 |
kwargs={'uidb36':int_to_base36(user.id),
|
|
406 |
'token':token_generator.make_token(user)
|
|
407 |
} |
|
408 |
) |
|
430 |
kwargs={'uidb36': int_to_base36(user.id),
|
|
431 |
'token': token_generator.make_token(user)
|
|
432 |
}
|
|
433 |
)
|
|
409 | 434 |
url = urljoin(BASEURL, url) |
410 | 435 |
t = loader.get_template(email_template_name) |
411 | 436 |
c = { |
... | ... | |
418 | 443 |
} |
419 | 444 |
from_email = settings.SERVER_EMAIL |
420 | 445 |
send_mail(_("Password reset on %s alpha2 testing") % SITENAME, |
421 |
t.render(Context(c)), from_email, [user.email]) |
|
446 |
t.render(Context(c)), from_email, [user.email]) |
|
447 |
|
|
422 | 448 |
|
423 | 449 |
class EmailChangeForm(forms.ModelForm): |
424 | 450 |
class Meta: |
425 | 451 |
model = EmailChange |
426 | 452 |
fields = ('new_email_address',) |
427 |
|
|
453 |
|
|
428 | 454 |
def clean_new_email_address(self): |
429 | 455 |
addr = self.cleaned_data['new_email_address'] |
430 | 456 |
if AstakosUser.objects.filter(email__iexact=addr): |
431 | 457 |
raise forms.ValidationError(_(u'This email address is already in use. Please supply a different email address.')) |
432 | 458 |
return addr |
433 |
|
|
459 |
|
|
434 | 460 |
def save(self, email_template_name, request, commit=True): |
435 | 461 |
ec = super(EmailChangeForm, self).save(commit=False) |
436 | 462 |
ec.user = request.user |
437 |
activation_key = hashlib.sha1(str(random()) + smart_str(ec.new_email_address)) |
|
438 |
ec.activation_key=activation_key.hexdigest() |
|
463 |
activation_key = hashlib.sha1( |
|
464 |
str(random()) + smart_str(ec.new_email_address)) |
|
465 |
ec.activation_key = activation_key.hexdigest() |
|
439 | 466 |
if commit: |
440 | 467 |
ec.save() |
441 | 468 |
send_change_email(ec, request, email_template_name=email_template_name) |
442 | 469 |
|
470 |
|
|
443 | 471 |
class SignApprovalTermsForm(forms.ModelForm): |
444 | 472 |
class Meta: |
445 | 473 |
model = AstakosUser |
... | ... | |
454 | 482 |
raise forms.ValidationError(_('You have to agree with the terms')) |
455 | 483 |
return has_signed_terms |
456 | 484 |
|
485 |
|
|
457 | 486 |
class InvitationForm(forms.ModelForm): |
458 | 487 |
username = forms.EmailField(label=_("Email")) |
459 |
|
|
488 |
|
|
460 | 489 |
def __init__(self, *args, **kwargs): |
461 | 490 |
super(InvitationForm, self).__init__(*args, **kwargs) |
462 |
|
|
491 |
|
|
463 | 492 |
class Meta: |
464 | 493 |
model = Invitation |
465 | 494 |
fields = ('username', 'realname') |
466 |
|
|
495 |
|
|
467 | 496 |
def clean_username(self): |
468 | 497 |
username = self.cleaned_data['username'] |
469 | 498 |
try: |
470 |
Invitation.objects.get(username = username) |
|
471 |
raise forms.ValidationError(_('There is already invitation for this email.')) |
|
499 |
Invitation.objects.get(username=username) |
|
500 |
raise forms.ValidationError( |
|
501 |
_('There is already invitation for this email.')) |
|
472 | 502 |
except Invitation.DoesNotExist: |
473 | 503 |
pass |
474 | 504 |
return username |
475 | 505 |
|
506 |
|
|
476 | 507 |
class ExtendedPasswordChangeForm(PasswordChangeForm): |
477 | 508 |
""" |
478 | 509 |
Extends PasswordChangeForm by enabling user |
479 | 510 |
to optionally renew also the token. |
480 | 511 |
""" |
481 | 512 |
renew = forms.BooleanField(label='Renew token', required=False) |
482 |
|
|
513 |
|
|
483 | 514 |
def __init__(self, user, *args, **kwargs): |
484 | 515 |
super(ExtendedPasswordChangeForm, self).__init__(user, *args, **kwargs) |
485 |
|
|
516 |
|
|
486 | 517 |
def save(self, commit=True): |
487 | 518 |
user = super(ExtendedPasswordChangeForm, self).save(commit=False) |
488 | 519 |
if self.cleaned_data.get('renew'): |
... | ... | |
491 | 522 |
user.save() |
492 | 523 |
return user |
493 | 524 |
|
525 |
|
|
494 | 526 |
class AstakosGroupCreationForm(forms.ModelForm): |
495 | 527 |
# issue_date = forms.DateField(widget=SelectDateWidget()) |
496 | 528 |
# expiration_date = forms.DateField(widget=SelectDateWidget()) |
... | ... | |
508 | 540 |
|
509 | 541 |
class Meta: |
510 | 542 |
model = AstakosGroup |
511 |
|
|
543 |
|
|
512 | 544 |
def __init__(self, *args, **kwargs): |
513 | 545 |
try: |
Also available in: Unified diff