Revision 1ecb12b5
b/.gitignore | ||
---|---|---|
34 | 34 |
*.iml |
35 | 35 |
*.graffle |
36 | 36 |
snf-stats-app/synnefo_stats/version.py |
37 |
snf-astakos-client/astakosclient/version.py |
|
37 |
astakosclient/astakosclient/version.py |
|
38 |
snf-django-lib/snf_django/version.py |
b/astakosclient/COPYRIGHT | ||
---|---|---|
1 |
Copyright (C) 2012, 2013 GRNET S.A. All rights reserved. |
|
2 |
|
|
3 |
Redistribution and use in source and binary forms, with or |
|
4 |
without modification, are permitted provided that the following |
|
5 |
conditions are met: |
|
6 |
|
|
7 |
1. Redistributions of source code must retain the above |
|
8 |
copyright notice, this list of conditions and the following |
|
9 |
disclaimer. |
|
10 |
|
|
11 |
2. Redistributions in binary form must reproduce the above |
|
12 |
copyright notice, this list of conditions and the following |
|
13 |
disclaimer in the documentation and/or other materials |
|
14 |
provided with the distribution. |
|
15 |
|
|
16 |
THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
POSSIBILITY OF SUCH DAMAGE. |
|
28 |
|
|
29 |
The views and conclusions contained in the software and |
|
30 |
documentation are those of the authors and should not be |
|
31 |
interpreted as representing official policies, either expressed |
|
32 |
or implied, of GRNET S.A. |
b/astakosclient/MANIFEST.in | ||
---|---|---|
1 |
include distribute_setup.py |
|
2 |
include README Changelog |
b/astakosclient/README | ||
---|---|---|
1 |
astakos-client |
|
2 |
============== |
|
3 |
|
|
4 |
Python client for the Astakos, the Identity Management Service of |
|
5 |
synnefo(http://www.synnefo.org) |
|
6 |
|
|
7 |
Feedback |
|
8 |
======== |
|
9 |
|
|
10 |
For any comments or suggestions for improvement, please contact |
|
11 |
the synnefo development team <synnefo-devel@googlegroups.com> |
b/astakosclient/astakosclient/__init__.py | ||
---|---|---|
1 |
# Copyright (C) 2012, 2013 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or |
|
4 |
# without modification, are permitted provided that the following |
|
5 |
# conditions are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above |
|
8 |
# copyright notice, this list of conditions and the following |
|
9 |
# disclaimer. |
|
10 |
# |
|
11 |
# 2. Redistributions in binary form must reproduce the above |
|
12 |
# copyright notice, this list of conditions and the following |
|
13 |
# disclaimer in the documentation and/or other materials |
|
14 |
# provided with the distribution. |
|
15 |
# |
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
27 |
# POSSIBILITY OF SUCH DAMAGE. |
|
28 |
# |
|
29 |
# The views and conclusions contained in the software and |
|
30 |
# documentation are those of the authors and should not be |
|
31 |
# interpreted as representing official policies, either expressed |
|
32 |
# or implied, of GRNET S.A. |
|
33 |
|
|
34 |
import logging |
|
35 |
import urlparse |
|
36 |
import urllib |
|
37 |
import hashlib |
|
38 |
from copy import copy |
|
39 |
|
|
40 |
import simplejson |
|
41 |
from astakosclient.utils import retry, scheme_to_class |
|
42 |
from astakosclient.errors import \ |
|
43 |
AstakosClientException, Unauthorized, BadRequest, NotFound, Forbidden, \ |
|
44 |
NoUserName, NoUUID |
|
45 |
|
|
46 |
|
|
47 |
# -------------------------------------------------------------------- |
|
48 |
# Astakos Client Class |
|
49 |
|
|
50 |
def get_token_from_cookie(request, cookie_name): |
|
51 |
"""Extract token from the cookie name provided |
|
52 |
|
|
53 |
Cookie should be in the same form as astakos |
|
54 |
service sets its cookie contents: |
|
55 |
<user_uniq>|<user_token> |
|
56 |
|
|
57 |
""" |
|
58 |
try: |
|
59 |
cookie_content = urllib.unquote(request.COOKIE.get(cookie_name, None)) |
|
60 |
return cookie_content.split("|")[1] |
|
61 |
except: |
|
62 |
return None |
|
63 |
|
|
64 |
|
|
65 |
class AstakosClient(): |
|
66 |
"""AstakosClient Class Implementation""" |
|
67 |
|
|
68 |
# ---------------------------------- |
|
69 |
def __init__(self, astakos_url, retry=0, |
|
70 |
use_pool=False, pool_size=8, logger=None): |
|
71 |
"""Intialize AstakosClient Class |
|
72 |
|
|
73 |
Keyword arguments: |
|
74 |
astakos_url -- i.e https://accounts.example.com (string) |
|
75 |
use_pool -- use objpool for http requests (boolean) |
|
76 |
retry -- how many time to retry (integer) |
|
77 |
logger -- pass a different logger |
|
78 |
|
|
79 |
""" |
|
80 |
if logger is None: |
|
81 |
logging.basicConfig( |
|
82 |
format='%(asctime)s [%(levelname)s] %(name)s %(message)s', |
|
83 |
datefmt='%Y-%m-%d %H:%M:%S', |
|
84 |
level=logging.INFO) |
|
85 |
logger = logging.getLogger("astakosclient") |
|
86 |
logger.debug("Intialize AstakosClient: astakos_url = %s, " |
|
87 |
"use_pool = %s" % (astakos_url, use_pool)) |
|
88 |
|
|
89 |
if not astakos_url: |
|
90 |
m = "Astakos url not given" |
|
91 |
logger.error(m) |
|
92 |
raise ValueError(m) |
|
93 |
|
|
94 |
# Check for supported scheme |
|
95 |
p = urlparse.urlparse(astakos_url) |
|
96 |
conn_class = scheme_to_class(p.scheme, use_pool, pool_size) |
|
97 |
if conn_class is None: |
|
98 |
m = "Unsupported scheme: %s" % p.scheme |
|
99 |
logger.error(m) |
|
100 |
raise ValueError(m) |
|
101 |
|
|
102 |
# Save astakos_url etc. in our class |
|
103 |
self.retry = retry |
|
104 |
self.logger = logger |
|
105 |
self.netloc = p.netloc |
|
106 |
self.scheme = p.scheme |
|
107 |
self.conn_class = conn_class |
|
108 |
|
|
109 |
# ---------------------------------- |
|
110 |
@retry |
|
111 |
def _call_astakos(self, token, request_path, |
|
112 |
headers=None, body=None, method="GET"): |
|
113 |
"""Make the actual call to Astakos Service""" |
|
114 |
hashed_token = hashlib.sha1() |
|
115 |
hashed_token.update(token) |
|
116 |
self.logger.debug( |
|
117 |
"Make a %s request to %s using token %s " |
|
118 |
"with headers %s and body %s" |
|
119 |
% (method, request_path, hashed_token.hexdigest(), headers, body)) |
|
120 |
|
|
121 |
# Check Input |
|
122 |
if not token: |
|
123 |
m = "Token not given" |
|
124 |
self.logger.error(m) |
|
125 |
raise ValueError(m) |
|
126 |
if headers is None: |
|
127 |
headers = {} |
|
128 |
if body is None: |
|
129 |
body = {} |
|
130 |
if request_path[0] != '/': |
|
131 |
request_path = "/" + request_path |
|
132 |
|
|
133 |
# Build request's header and body |
|
134 |
kwargs = {} |
|
135 |
kwargs['headers'] = copy(headers) |
|
136 |
kwargs['headers']['X-Auth-Token'] = token |
|
137 |
if body: |
|
138 |
kwargs['body'] = copy(body) |
|
139 |
kwargs['headers'].setdefault( |
|
140 |
'content-type', 'application/octet-stream') |
|
141 |
kwargs['headers'].setdefault('content-length', |
|
142 |
len(body) if body else 0) |
|
143 |
|
|
144 |
try: |
|
145 |
# Get the connection object |
|
146 |
with self.conn_class(self.netloc) as conn: |
|
147 |
# Send request |
|
148 |
(data, status) = \ |
|
149 |
_do_request(conn, method, request_path, **kwargs) |
|
150 |
except Exception as err: |
|
151 |
self.logger.error("Failed to send request: %s" % repr(err)) |
|
152 |
raise AstakosClientException(str(err)) |
|
153 |
|
|
154 |
# Return |
|
155 |
self.logger.debug("Request returned with status %s" % status) |
|
156 |
if status == 400: |
|
157 |
raise BadRequest(data) |
|
158 |
elif status == 401: |
|
159 |
raise Unauthorized(data) |
|
160 |
elif status == 403: |
|
161 |
raise Forbidden(data) |
|
162 |
elif status == 404: |
|
163 |
raise NotFound(data) |
|
164 |
elif status < 200 or status >= 300: |
|
165 |
raise AstakosClientException(data, status) |
|
166 |
return simplejson.loads(unicode(data)) |
|
167 |
|
|
168 |
# ------------------------ |
|
169 |
def get_user_info(self, token, usage=False): |
|
170 |
"""Authenticate user and get user's info as a dictionary |
|
171 |
|
|
172 |
Keyword arguments: |
|
173 |
token -- user's token (string) |
|
174 |
usage -- return usage information for user (boolean) |
|
175 |
|
|
176 |
In case of success return user information (json parsed format). |
|
177 |
Otherwise raise an AstakosClientException. |
|
178 |
|
|
179 |
""" |
|
180 |
# Send request |
|
181 |
auth_path = "/im/authenticate" |
|
182 |
if usage: |
|
183 |
auth_path += "?usage=1" |
|
184 |
return self._call_astakos(token, auth_path) |
|
185 |
|
|
186 |
# ---------------------------------- |
|
187 |
def _uuid_catalog(self, token, uuids, req_path): |
|
188 |
req_headers = {'content-type': 'application/json'} |
|
189 |
req_body = simplejson.dumps({'uuids': uuids}) |
|
190 |
data = self._call_astakos( |
|
191 |
token, req_path, req_headers, req_body, "POST") |
|
192 |
if "uuid_catalog" in data: |
|
193 |
return data.get("uuid_catalog") |
|
194 |
else: |
|
195 |
m = "_uuid_catalog request returned %s. No uuid_catalog found" \ |
|
196 |
% data |
|
197 |
self.logger.error(m) |
|
198 |
raise AstakosClientException(m) |
|
199 |
|
|
200 |
def get_usernames(self, token, uuids): |
|
201 |
"""Return a uuid_catalog dictionary for the given uuids |
|
202 |
|
|
203 |
Keyword arguments: |
|
204 |
token -- user's token (string) |
|
205 |
uuids -- list of user ids (list of strings) |
|
206 |
|
|
207 |
The returned uuid_catalog is a dictionary with uuids as |
|
208 |
keys and the corresponding user names as values |
|
209 |
|
|
210 |
""" |
|
211 |
req_path = "/user_catalogs" |
|
212 |
return self._uuid_catalog(token, uuids, req_path) |
|
213 |
|
|
214 |
def get_username(self, token, uuid): |
|
215 |
"""Return the user name of a uuid (see get_usernames)""" |
|
216 |
if not uuid: |
|
217 |
m = "No uuid was given" |
|
218 |
self.logger.error(m) |
|
219 |
raise ValueError(m) |
|
220 |
uuid_dict = self.get_usernames(token, [uuid]) |
|
221 |
if uuid in uuid_dict: |
|
222 |
return uuid_dict.get(uuid) |
|
223 |
else: |
|
224 |
raise NoUserName(uuid) |
|
225 |
|
|
226 |
def service_get_usernames(self, token, uuids): |
|
227 |
"""Return a uuid_catalog dict using a service's token""" |
|
228 |
req_path = "/service/api/user_catalogs" |
|
229 |
return self._uuid_catalog(token, uuids, req_path) |
|
230 |
|
|
231 |
def service_get_username(self, token, uuid): |
|
232 |
"""Return the displayName of a uuid using a service's token""" |
|
233 |
if not uuid: |
|
234 |
m = "No uuid was given" |
|
235 |
self.logger.error(m) |
|
236 |
raise ValueError(m) |
|
237 |
uuid_dict = self.service_get_usernames(token, [uuid]) |
|
238 |
if uuid in uuid_dict: |
|
239 |
return uuid_dict.get(uuid) |
|
240 |
else: |
|
241 |
raise NoUserName(uuid) |
|
242 |
|
|
243 |
# ---------------------------------- |
|
244 |
def _displayname_catalog(self, token, display_names, req_path): |
|
245 |
req_headers = {'content-type': 'application/json'} |
|
246 |
req_body = simplejson.dumps({'displaynames': display_names}) |
|
247 |
data = self._call_astakos( |
|
248 |
token, req_path, req_headers, req_body, "POST") |
|
249 |
if "displayname_catalog" in data: |
|
250 |
return data.get("displayname_catalog") |
|
251 |
else: |
|
252 |
m = "_displayname_catalog request returned %s. " \ |
|
253 |
"No displayname_catalog found" % data |
|
254 |
self.logger.error(m) |
|
255 |
raise AstakosClientException(m) |
|
256 |
|
|
257 |
def get_uuids(self, token, display_names): |
|
258 |
"""Return a displayname_catalog for the given names |
|
259 |
|
|
260 |
Keyword arguments: |
|
261 |
token -- user's token (string) |
|
262 |
display_names -- list of user names (list of strings) |
|
263 |
|
|
264 |
The returned displayname_catalog is a dictionary with |
|
265 |
the names as keys and the corresponding uuids as values |
|
266 |
|
|
267 |
""" |
|
268 |
req_path = "/user_catalogs" |
|
269 |
return self._displayname_catalog(token, display_names, req_path) |
|
270 |
|
|
271 |
def get_uuid(self, token, display_name): |
|
272 |
"""Return the uuid of a name (see getUUIDs)""" |
|
273 |
if not display_name: |
|
274 |
m = "No display_name was given" |
|
275 |
self.logger.error(m) |
|
276 |
raise ValueError(m) |
|
277 |
name_dict = self.get_uuids(token, [display_name]) |
|
278 |
if display_name in name_dict: |
|
279 |
return name_dict.get(display_name) |
|
280 |
else: |
|
281 |
raise NoUUID(display_name) |
|
282 |
|
|
283 |
def service_get_uuids(self, token, display_names): |
|
284 |
"""Return a display_name catalog using a service's token""" |
|
285 |
req_path = "/service/api/user_catalogs" |
|
286 |
return self._displayname_catalog(token, display_names, req_path) |
|
287 |
|
|
288 |
def service_get_uuid(self, token, display_name): |
|
289 |
"""Return the uuid of a name using a service's token""" |
|
290 |
if not display_name: |
|
291 |
m = "No display_name was given" |
|
292 |
self.logger.error(m) |
|
293 |
raise ValueError(m) |
|
294 |
name_dict = self.service_get_uuids(token, [display_name]) |
|
295 |
if display_name in name_dict: |
|
296 |
return name_dict.get(display_name) |
|
297 |
else: |
|
298 |
raise NoUUID(display_name) |
|
299 |
|
|
300 |
# ---------------------------------- |
|
301 |
def get_services(self): |
|
302 |
"""Return a list of dicts with the registered services""" |
|
303 |
return self._call_astakos("dummy token", "/im/get_services") |
|
304 |
|
|
305 |
|
|
306 |
# -------------------------------------------------------------------- |
|
307 |
# Private functions |
|
308 |
# We want _doRequest to be a distinct function |
|
309 |
# so that we can replace it during unit tests. |
|
310 |
def _do_request(conn, method, url, **kwargs): |
|
311 |
"""The actual request. This function can easily be mocked""" |
|
312 |
conn.request(method, url, **kwargs) |
|
313 |
response = conn.getresponse() |
|
314 |
length = response.getheader('content-length', None) |
|
315 |
data = response.read(length) |
|
316 |
status = int(response.status) |
|
317 |
return (data, status) |
b/astakosclient/astakosclient/errors.py | ||
---|---|---|
1 |
# Copyright (C) 2012, 2013 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 |
|
|
35 |
class AstakosClientException(Exception): |
|
36 |
def __init__(self, message, status=0): |
|
37 |
self.message = message |
|
38 |
self.status = status |
|
39 |
|
|
40 |
def __str__(self): |
|
41 |
return repr(self.message) |
|
42 |
|
|
43 |
|
|
44 |
class BadRequest(AstakosClientException): |
|
45 |
def __init__(self, message): |
|
46 |
"""400 Bad Request""" |
|
47 |
super(BadRequest, self).__init__(message, 400) |
|
48 |
|
|
49 |
|
|
50 |
class Unauthorized(AstakosClientException): |
|
51 |
def __init__(self, message): |
|
52 |
"""401 Invalid X-Auth-Token""" |
|
53 |
super(Unauthorized, self).__init__(message, 401) |
|
54 |
|
|
55 |
|
|
56 |
class Forbidden(AstakosClientException): |
|
57 |
def __init__(self, message): |
|
58 |
"""403 Forbidden""" |
|
59 |
super(Forbidden, self).__init__(message, 403) |
|
60 |
|
|
61 |
|
|
62 |
class NotFound(AstakosClientException): |
|
63 |
def __init__(self, message): |
|
64 |
"""404 Not Found""" |
|
65 |
super(NotFound, self).__init__(message, 404) |
|
66 |
|
|
67 |
|
|
68 |
class NoUserName(AstakosClientException): |
|
69 |
def __init__(self, uuid): |
|
70 |
"""No display name for the given uuid""" |
|
71 |
message = "No display name for the given uuid: %s" % uuid |
|
72 |
super(NoUserName, self).__init__(message) |
|
73 |
|
|
74 |
|
|
75 |
class NoUUID(AstakosClientException): |
|
76 |
def __init__(self, display_name): |
|
77 |
"""No uuid for the given display name""" |
|
78 |
message = "No uuid for the given display name: %s" % display_name |
|
79 |
super(NoUUID, self).__init__(message) |
b/astakosclient/astakosclient/tests.py | ||
---|---|---|
1 |
#!/usr/bin/env python |
|
2 |
# |
|
3 |
# Copyright (C) 2012, 2013 GRNET S.A. All rights reserved. |
|
4 |
# |
|
5 |
# Redistribution and use in source and binary forms, with or |
|
6 |
# without modification, are permitted provided that the following |
|
7 |
# conditions are met: |
|
8 |
# |
|
9 |
# 1. Redistributions of source code must retain the above |
|
10 |
# copyright notice, this list of conditions and the following |
|
11 |
# disclaimer. |
|
12 |
# |
|
13 |
# 2. Redistributions in binary form must reproduce the above |
|
14 |
# copyright notice, this list of conditions and the following |
|
15 |
# disclaimer in the documentation and/or other materials |
|
16 |
# provided with the distribution. |
|
17 |
# |
|
18 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS |
|
19 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
20 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
21 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR |
|
22 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
23 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
24 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
25 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
|
26 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
27 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|
28 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
29 |
# POSSIBILITY OF SUCH DAMAGE. |
|
30 |
# |
|
31 |
# The views and conclusions contained in the software and |
|
32 |
# documentation are those of the authors and should not be |
|
33 |
# interpreted as representing official policies, either expressed |
|
34 |
# or implied, of GRNET S.A. |
|
35 |
|
|
36 |
"""Unit Tests for the astakos-client module |
|
37 |
|
|
38 |
Provides unit tests for the code implementing |
|
39 |
the astakos client library |
|
40 |
|
|
41 |
""" |
|
42 |
|
|
43 |
import sys |
|
44 |
import socket |
|
45 |
import simplejson |
|
46 |
|
|
47 |
import astakosclient |
|
48 |
from astakosclient import AstakosClient |
|
49 |
from astakosclient.errors import \ |
|
50 |
AstakosClientException, Unauthorized, BadRequest, NotFound, \ |
|
51 |
NoUserName, NoUUID |
|
52 |
|
|
53 |
# Use backported unittest functionality if Python < 2.7 |
|
54 |
try: |
|
55 |
import unittest2 as unittest |
|
56 |
except ImportError: |
|
57 |
if sys.version_info < (2, 7): |
|
58 |
raise Exception("The unittest2 package is required for Python < 2.7") |
|
59 |
import unittest |
|
60 |
|
|
61 |
|
|
62 |
# -------------------------------------------------------------------- |
|
63 |
# Helper functions |
|
64 |
|
|
65 |
# ---------------------------- |
|
66 |
# This functions will be used as mocked requests |
|
67 |
def _request_offline(conn, method, url, **kwargs): |
|
68 |
"""This request behaves as we were offline""" |
|
69 |
raise socket.gaierror |
|
70 |
|
|
71 |
|
|
72 |
def _request_status_302(conn, method, url, **kwargs): |
|
73 |
"""This request returns 302""" |
|
74 |
status = 302 |
|
75 |
data = '<html>\r\n<head><title>302 Found</title></head>\r\n' \ |
|
76 |
'<body bgcolor="white">\r\n<center><h1>302 Found</h1></center>\r\n' \ |
|
77 |
'<hr><center>nginx/0.7.67</center>\r\n</body>\r\n</html>\r\n' |
|
78 |
return (data, status) |
|
79 |
|
|
80 |
|
|
81 |
def _request_status_404(conn, method, url, **kwargs): |
|
82 |
"""This request returns 404""" |
|
83 |
status = 404 |
|
84 |
data = '<html><head><title>404 Not Found</title></head>' \ |
|
85 |
'<body><h1>Not Found</h1><p>The requested URL /foo was ' \ |
|
86 |
'not found on this server.</p><hr><address>Apache Server ' \ |
|
87 |
'at example.com Port 80</address></body></html>' |
|
88 |
return (data, status) |
|
89 |
|
|
90 |
|
|
91 |
def _request_status_401(conn, method, url, **kwargs): |
|
92 |
"""This request returns 401""" |
|
93 |
status = 401 |
|
94 |
data = "Invalid X-Auth-Token\n" |
|
95 |
return (data, status) |
|
96 |
|
|
97 |
|
|
98 |
def _request_status_400(conn, method, url, **kwargs): |
|
99 |
"""This request returns 400""" |
|
100 |
status = 400 |
|
101 |
data = "Method not allowed.\n" |
|
102 |
return (data, status) |
|
103 |
|
|
104 |
|
|
105 |
def _request_ok(conn, method, url, **kwargs): |
|
106 |
"""This request behaves like original Astakos does""" |
|
107 |
if url[0:16] == "/im/authenticate": |
|
108 |
return _req_authenticate(conn, method, url, **kwargs) |
|
109 |
elif url[0:14] == "/user_catalogs": |
|
110 |
return _req_catalogs(conn, method, url, **kwargs) |
|
111 |
else: |
|
112 |
return _request_status_404(conn, method, url, **kwargs) |
|
113 |
|
|
114 |
|
|
115 |
def _req_authenticate(conn, method, url, **kwargs): |
|
116 |
"""Check if user exists and return his profile""" |
|
117 |
global user_1, user_2 |
|
118 |
|
|
119 |
# Check input |
|
120 |
if conn.__class__.__name__ != "HTTPSConnection": |
|
121 |
return _request_status_302(conn, method, url, **kwargs) |
|
122 |
|
|
123 |
if method != "GET": |
|
124 |
return _request_status_400(conn, method, url, **kwargs) |
|
125 |
|
|
126 |
token = kwargs['headers']['X-Auth-Token'] |
|
127 |
if token == token_1: |
|
128 |
user = dict(user_1) |
|
129 |
elif token == token_2: |
|
130 |
user = dict(user_2) |
|
131 |
else: |
|
132 |
# No user found |
|
133 |
return _request_status_401(conn, method, url, **kwargs) |
|
134 |
|
|
135 |
# Return |
|
136 |
if "usage=1" not in url: |
|
137 |
# Strip `usage' key from `user' |
|
138 |
del user['usage'] |
|
139 |
return (simplejson.dumps(user), 200) |
|
140 |
|
|
141 |
|
|
142 |
def _req_catalogs(conn, method, url, **kwargs): |
|
143 |
"""Return user catalogs""" |
|
144 |
global token_1, token_2, user_1, user_2 |
|
145 |
|
|
146 |
# Check input |
|
147 |
if conn.__class__.__name__ != "HTTPSConnection": |
|
148 |
return _request_status_302(conn, method, url, **kwargs) |
|
149 |
|
|
150 |
if method != "POST": |
|
151 |
return _request_status_400(conn, method, url, **kwargs) |
|
152 |
|
|
153 |
token = kwargs['headers']['X-Auth-Token'] |
|
154 |
if token != token_1 and token != token_2: |
|
155 |
return _request_status_401(conn, method, url, **kwargs) |
|
156 |
|
|
157 |
# Return |
|
158 |
body = simplejson.loads(kwargs['body']) |
|
159 |
if 'uuids' in body: |
|
160 |
# Return uuid_catalog |
|
161 |
uuids = body['uuids'] |
|
162 |
catalogs = {} |
|
163 |
if user_1['uuid'] in uuids: |
|
164 |
catalogs[user_1['uuid']] = user_1['username'] |
|
165 |
if user_2['uuid'] in uuids: |
|
166 |
catalogs[user_2['uuid']] = user_2['username'] |
|
167 |
return_catalog = {"displayname_catalog": {}, "uuid_catalog": catalogs} |
|
168 |
elif 'displaynames' in body: |
|
169 |
# Return displayname_catalog |
|
170 |
names = body['displaynames'] |
|
171 |
catalogs = {} |
|
172 |
if user_1['username'] in names: |
|
173 |
catalogs[user_1['username']] = user_1['uuid'] |
|
174 |
if user_2['username'] in names: |
|
175 |
catalogs[user_2['username']] = user_2['uuid'] |
|
176 |
return_catalog = {"displayname_catalog": catalogs, "uuid_catalog": {}} |
|
177 |
else: |
|
178 |
return_catalog = {"displayname_catalog": {}, "uuid_catalog": {}} |
|
179 |
return (simplejson.dumps(return_catalog), 200) |
|
180 |
|
|
181 |
|
|
182 |
# ---------------------------- |
|
183 |
# Mock the actual _doRequest |
|
184 |
def _mock_request(new_requests): |
|
185 |
"""Mock the actual request |
|
186 |
|
|
187 |
Given a list of requests to use (in rotation), |
|
188 |
replace the original _doRequest function with |
|
189 |
a new one |
|
190 |
|
|
191 |
""" |
|
192 |
def _mock(conn, method, url, **kwargs): |
|
193 |
# Get first request |
|
194 |
request = _mock.requests[0] |
|
195 |
# Rotate requests |
|
196 |
_mock.requests = _mock.requests[1:] + _mock.requests[:1] |
|
197 |
# Use first request |
|
198 |
return request(conn, method, url, **kwargs) |
|
199 |
|
|
200 |
_mock.requests = new_requests |
|
201 |
# Replace `_doRequest' with our `_mock' |
|
202 |
astakosclient._do_request = _mock |
|
203 |
|
|
204 |
|
|
205 |
# ---------------------------- |
|
206 |
# Local users |
|
207 |
token_1 = "skzleaFlBl+fasFdaf24sx==" |
|
208 |
user_1 = \ |
|
209 |
{"username": "user1@example.com", |
|
210 |
"auth_token_created": 1359386939000, |
|
211 |
"name": "Example User One", |
|
212 |
"email": ["user1@example.com"], |
|
213 |
"auth_token_expires": 1361978939000, |
|
214 |
"id": 108, |
|
215 |
"uuid": "73917abc-abcd-477e-a1f1-1763abcdefab", |
|
216 |
"usage": [ |
|
217 |
{"currValue": 42949672960, |
|
218 |
"display_name": "System Disk", |
|
219 |
"name": "cyclades.disk"}, |
|
220 |
{"currValue": 4, |
|
221 |
"display_name": "CPU", |
|
222 |
"name": "cyclades.cpu"}, |
|
223 |
{"currValue": 4294967296, |
|
224 |
"display_name": "RAM", |
|
225 |
"name": "cyclades.ram"}, |
|
226 |
{"currValue": 3, |
|
227 |
"display_name": "VM", |
|
228 |
"name": "cyclades.vm"}, |
|
229 |
{"currValue": 0, |
|
230 |
"display_name": "private network", |
|
231 |
"name": "cyclades.network.private"}, |
|
232 |
{"currValue": 152, |
|
233 |
"display_name": "Storage Space", |
|
234 |
"name": "pithos+.diskspace"}]} |
|
235 |
|
|
236 |
token_2 = "fasdfDSFdf98923DF+sdfk==" |
|
237 |
user_2 = \ |
|
238 |
{"username": "user2@example.com", |
|
239 |
"auth_token_created": 1358386938997, |
|
240 |
"name": "Example User Two", |
|
241 |
"email": ["user1@example.com"], |
|
242 |
"auth_token_expires": 1461998939000, |
|
243 |
"id": 109, |
|
244 |
"uuid": "73917bca-1234-5678-a1f1-1763abcdefab", |
|
245 |
"usage": [ |
|
246 |
{"currValue": 68719476736, |
|
247 |
"display_name": "System Disk", |
|
248 |
"name": "cyclades.disk"}, |
|
249 |
{"currValue": 1, |
|
250 |
"display_name": "CPU", |
|
251 |
"name": "cyclades.cpu"}, |
|
252 |
{"currValue": 1073741824, |
|
253 |
"display_name": "RAM", |
|
254 |
"name": "cyclades.ram"}, |
|
255 |
{"currValue": 2, |
|
256 |
"display_name": "VM", |
|
257 |
"name": "cyclades.vm"}, |
|
258 |
{"currValue": 1, |
|
259 |
"display_name": "private network", |
|
260 |
"name": "cyclades.network.private"}, |
|
261 |
{"currValue": 2341634510, |
|
262 |
"display_name": "Storage Space", |
|
263 |
"name": "pithos+.diskspace"}]} |
|
264 |
|
|
265 |
|
|
266 |
# -------------------------------------------------------------------- |
|
267 |
# The actual tests |
|
268 |
|
|
269 |
class TestCallAstakos(unittest.TestCase): |
|
270 |
"""Test cases for function _callAstakos""" |
|
271 |
|
|
272 |
# ---------------------------------- |
|
273 |
# Test the response we get if we don't have internet access |
|
274 |
def _offline(self, pool): |
|
275 |
global token_1 |
|
276 |
_mock_request([_request_offline]) |
|
277 |
try: |
|
278 |
client = AstakosClient("https://example.com", use_pool=pool) |
|
279 |
client._call_astakos(token_1, "/im/authenticate") |
|
280 |
except AstakosClientException: |
|
281 |
pass |
|
282 |
else: |
|
283 |
self.fail("Should have raised AstakosClientException") |
|
284 |
|
|
285 |
def test_offline(self): |
|
286 |
"""Test _offline without pool""" |
|
287 |
self._offline(False) |
|
288 |
|
|
289 |
def test_offline_pool(self): |
|
290 |
"""Test _offline using pool""" |
|
291 |
self._offline(True) |
|
292 |
|
|
293 |
# ---------------------------------- |
|
294 |
# Test the response we get if we send invalid token |
|
295 |
def _invalid_token(self, pool): |
|
296 |
token = "skaksaFlBl+fasFdaf24sx==" |
|
297 |
_mock_request([_request_ok]) |
|
298 |
try: |
|
299 |
client = AstakosClient("https://example.com", use_pool=pool) |
|
300 |
client._call_astakos(token, "/im/authenticate") |
|
301 |
except Unauthorized: |
|
302 |
pass |
|
303 |
except Exception: |
|
304 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
|
305 |
else: |
|
306 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
|
307 |
|
|
308 |
def test_invalid_token(self): |
|
309 |
"""Test _invalid_token without pool""" |
|
310 |
self._invalid_token(False) |
|
311 |
|
|
312 |
def test_invalid_token_pool(self): |
|
313 |
"""Test _invalid_token using pool""" |
|
314 |
self._invalid_token(True) |
|
315 |
|
|
316 |
# ---------------------------------- |
|
317 |
# Test the response we get if we send invalid url |
|
318 |
def _invalid_url(self, pool): |
|
319 |
global token_1 |
|
320 |
_mock_request([_request_ok]) |
|
321 |
try: |
|
322 |
client = AstakosClient("https://example.com", use_pool=pool) |
|
323 |
client._call_astakos(token_1, "/im/misspelled") |
|
324 |
except NotFound: |
|
325 |
pass |
|
326 |
except Exception: |
|
327 |
self.fail("Should have returned 404 (Not Found)") |
|
328 |
else: |
|
329 |
self.fail("Should have returned 404 (Not Found)") |
|
330 |
|
|
331 |
def test_invalid_url(self): |
|
332 |
"""Test _invalid_url without pool""" |
|
333 |
self._invalid_url(False) |
|
334 |
|
|
335 |
def test_invalid_url_pool(self): |
|
336 |
"""Test _invalid_url using pool""" |
|
337 |
self._invalid_url(True) |
|
338 |
|
|
339 |
# ---------------------------------- |
|
340 |
# Test the response we get if we use an unsupported scheme |
|
341 |
def _unsupported_scheme(self, pool): |
|
342 |
global token_1 |
|
343 |
_mock_request([_request_ok]) |
|
344 |
try: |
|
345 |
client = AstakosClient("ftp://example.com", use_pool=pool) |
|
346 |
client._call_astakos(token_1, "/im/authenticate") |
|
347 |
except ValueError: |
|
348 |
pass |
|
349 |
except Exception: |
|
350 |
self.fail("Should have raise ValueError Exception") |
|
351 |
else: |
|
352 |
self.fail("Should have raise ValueError Exception") |
|
353 |
|
|
354 |
def test_unsupported_scheme(self): |
|
355 |
"""Test _unsupported_scheme without pool""" |
|
356 |
self._unsupported_scheme(False) |
|
357 |
|
|
358 |
def test_unsupported_scheme_pool(self): |
|
359 |
"""Test _unsupported_scheme using pool""" |
|
360 |
self._unsupported_scheme(True) |
|
361 |
|
|
362 |
# ---------------------------------- |
|
363 |
# Test the response we get if we use http instead of https |
|
364 |
def _http_scheme(self, pool): |
|
365 |
global token_1 |
|
366 |
_mock_request([_request_ok]) |
|
367 |
try: |
|
368 |
client = AstakosClient("http://example.com", use_pool=pool) |
|
369 |
client._call_astakos(token_1, "/im/authenticate") |
|
370 |
except AstakosClientException as err: |
|
371 |
if err.status != 302: |
|
372 |
self.fail("Should have returned 302 (Found)") |
|
373 |
else: |
|
374 |
self.fail("Should have returned 302 (Found)") |
|
375 |
|
|
376 |
def test_http_scheme(self): |
|
377 |
"""Test _http_scheme without pool""" |
|
378 |
self._http_scheme(False) |
|
379 |
|
|
380 |
def test_http_scheme_pool(self): |
|
381 |
"""Test _http_scheme using pool""" |
|
382 |
self._http_scheme(True) |
|
383 |
|
|
384 |
# ---------------------------------- |
|
385 |
# Test the response we get if we use authenticate with POST |
|
386 |
def _post_authenticate(self, pool): |
|
387 |
global token_1 |
|
388 |
_mock_request([_request_ok]) |
|
389 |
try: |
|
390 |
client = AstakosClient("https://example.com", use_pool=pool) |
|
391 |
client._call_astakos(token_1, "/im/authenticate", method="POST") |
|
392 |
except BadRequest: |
|
393 |
pass |
|
394 |
except Exception: |
|
395 |
self.fail("Should have returned 400 (Method not allowed)") |
|
396 |
else: |
|
397 |
self.fail("Should have returned 400 (Method not allowed)") |
|
398 |
|
|
399 |
def test_post_authenticate(self): |
|
400 |
"""Test _post_authenticate without pool""" |
|
401 |
self._post_authenticate(False) |
|
402 |
|
|
403 |
def test_post_authenticate_pool(self): |
|
404 |
"""Test _post_authenticate using pool""" |
|
405 |
self._post_authenticate(True) |
|
406 |
|
|
407 |
# ---------------------------------- |
|
408 |
# Test the response if we request user_catalogs with GET |
|
409 |
def _get_user_catalogs(self, pool): |
|
410 |
global token_1 |
|
411 |
_mock_request([_request_ok]) |
|
412 |
try: |
|
413 |
client = AstakosClient("https://example.com", use_pool=pool) |
|
414 |
client._call_astakos(token_1, "/user_catalogs") |
|
415 |
except BadRequest: |
|
416 |
pass |
|
417 |
except Exception: |
|
418 |
self.fail("Should have returned 400 (Method not allowed)") |
|
419 |
else: |
|
420 |
self.fail("Should have returned 400 (Method not allowed)") |
|
421 |
|
|
422 |
def test_get_user_catalogs(self): |
|
423 |
"""Test _get_user_catalogs without pool""" |
|
424 |
self._get_user_catalogs(False) |
|
425 |
|
|
426 |
def test_get_user_catalogs_pool(self): |
|
427 |
"""Test _get_user_catalogs using pool""" |
|
428 |
self._get_user_catalogs(True) |
|
429 |
|
|
430 |
|
|
431 |
class TestAuthenticate(unittest.TestCase): |
|
432 |
"""Test cases for function getUserInfo""" |
|
433 |
|
|
434 |
# ---------------------------------- |
|
435 |
# Test the response we get if we don't have internet access |
|
436 |
def test_offline(self): |
|
437 |
"""Test offline after 3 retries""" |
|
438 |
global token_1 |
|
439 |
_mock_request([_request_offline]) |
|
440 |
try: |
|
441 |
client = AstakosClient("https://example.com", retry=3) |
|
442 |
client.get_user_info(token_1) |
|
443 |
except AstakosClientException: |
|
444 |
pass |
|
445 |
else: |
|
446 |
self.fail("Should have raised AstakosClientException exception") |
|
447 |
|
|
448 |
# ---------------------------------- |
|
449 |
# Test the response we get for invalid token |
|
450 |
def _invalid_token(self, pool): |
|
451 |
token = "skaksaFlBl+fasFdaf24sx==" |
|
452 |
_mock_request([_request_ok]) |
|
453 |
try: |
|
454 |
client = AstakosClient("https://example.com", use_pool=pool) |
|
455 |
client.get_user_info(token) |
|
456 |
except Unauthorized: |
|
457 |
pass |
|
458 |
except Exception: |
|
459 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
|
460 |
else: |
|
461 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
|
462 |
|
|
463 |
def test_invalid_token(self): |
|
464 |
"""Test _invalid_token without pool""" |
|
465 |
self._invalid_token(False) |
|
466 |
|
|
467 |
def test_invalid_token_pool(self): |
|
468 |
"""Test _invalid_token using pool""" |
|
469 |
self._invalid_token(True) |
|
470 |
|
|
471 |
#- --------------------------------- |
|
472 |
# Test response for user 1 |
|
473 |
def _auth_user(self, token, user_info, usage, pool): |
|
474 |
_mock_request([_request_ok]) |
|
475 |
try: |
|
476 |
client = AstakosClient("https://example.com", use_pool=pool) |
|
477 |
auth_info = client.get_user_info(token, usage=usage) |
|
478 |
except: |
|
479 |
self.fail("Shouldn't raise an Exception") |
|
480 |
self.assertEqual(user_info, auth_info) |
|
481 |
|
|
482 |
def test_auth_user_one(self): |
|
483 |
"""Test _auth_user for User 1 without pool, without usage""" |
|
484 |
global token_1, user_1 |
|
485 |
user_info = dict(user_1) |
|
486 |
del user_info['usage'] |
|
487 |
self._auth_user(token_1, user_info, False, False) |
|
488 |
|
|
489 |
def test_auth_user_one_usage(self): |
|
490 |
"""Test _auth_user for User 1 without pool, with usage""" |
|
491 |
global token_1, user_1 |
|
492 |
self._auth_user(token_1, user_1, True, False) |
|
493 |
|
|
494 |
def test_auth_user_one_usage_pool(self): |
|
495 |
"""Test _auth_user for User 1 using pool, with usage""" |
|
496 |
global token_1, user_1 |
|
497 |
self._auth_user(token_1, user_1, True, True) |
|
498 |
|
|
499 |
def test_auth_user_two(self): |
|
500 |
"""Test _auth_user for User 2 without pool, without usage""" |
|
501 |
global token_2, user_2 |
|
502 |
user_info = dict(user_2) |
|
503 |
del user_info['usage'] |
|
504 |
self._auth_user(token_2, user_info, False, False) |
|
505 |
|
|
506 |
def test_auth_user_two_usage(self): |
|
507 |
"""Test _auth_user for User 2 without pool, with usage""" |
|
508 |
global token_2, user_2 |
|
509 |
self._auth_user(token_2, user_2, True, False) |
|
510 |
|
|
511 |
def test_auth_user_two_usage_pool(self): |
|
512 |
"""Test _auth_user for User 2 using pool, with usage""" |
|
513 |
global token_2, user_2 |
|
514 |
self._auth_user(token_2, user_2, True, True) |
|
515 |
|
|
516 |
# ---------------------------------- |
|
517 |
# Test retry functionality |
|
518 |
def test_offline_retry(self): |
|
519 |
"""Test retry functionality for getUserInfo""" |
|
520 |
global token_1, user_1 |
|
521 |
_mock_request([_request_offline, _request_offline, _request_ok]) |
|
522 |
try: |
|
523 |
client = AstakosClient("https://example.com", retry=2) |
|
524 |
auth_info = client.get_user_info(token_1, usage=True) |
|
525 |
except: |
|
526 |
self.fail("Shouldn't raise an Exception") |
|
527 |
self.assertEqual(user_1, auth_info) |
|
528 |
|
|
529 |
|
|
530 |
class TestDisplayNames(unittest.TestCase): |
|
531 |
"""Test cases for functions getDisplayNames/getDisplayName""" |
|
532 |
|
|
533 |
# ---------------------------------- |
|
534 |
# Test the response we get for invalid token |
|
535 |
def test_invalid_token(self): |
|
536 |
"""Test the response we get for invalid token (without pool)""" |
|
537 |
global user_1 |
|
538 |
token = "skaksaFlBl+fasFdaf24sx==" |
|
539 |
_mock_request([_request_ok]) |
|
540 |
try: |
|
541 |
client = AstakosClient("https://example.com") |
|
542 |
client.get_usernames(token, [user_1['uuid']]) |
|
543 |
except Unauthorized: |
|
544 |
pass |
|
545 |
except Exception: |
|
546 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
|
547 |
else: |
|
548 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
|
549 |
|
|
550 |
# ---------------------------------- |
|
551 |
# Get Info for both users |
|
552 |
def test_usernames(self): |
|
553 |
"""Test get_usernames with both users""" |
|
554 |
global token_1, user_1, user_2 |
|
555 |
_mock_request([_request_ok]) |
|
556 |
try: |
|
557 |
client = AstakosClient("https://example.com") |
|
558 |
catalog = client.get_usernames( |
|
559 |
token_1, [user_1['uuid'], user_2['uuid']]) |
|
560 |
except: |
|
561 |
self.fail("Shouldn't raise an Exception") |
|
562 |
self.assertEqual(catalog[user_1['uuid']], user_1['username']) |
|
563 |
self.assertEqual(catalog[user_2['uuid']], user_2['username']) |
|
564 |
|
|
565 |
# ---------------------------------- |
|
566 |
# Get info for user 1 |
|
567 |
def test_username_user_one(self): |
|
568 |
"""Test get_username for User One""" |
|
569 |
global token_2, user_1 |
|
570 |
_mock_request([_request_offline, _request_ok]) |
|
571 |
try: |
|
572 |
client = AstakosClient( |
|
573 |
"https://example.com", use_pool=True, retry=2) |
|
574 |
info = client.get_username(token_2, user_1['uuid']) |
|
575 |
except: |
|
576 |
self.fail("Shouldn't raise an Exception") |
|
577 |
self.assertEqual(info, user_1['username']) |
|
578 |
|
|
579 |
# ---------------------------------- |
|
580 |
# Get info with wrong uuid |
|
581 |
def test_no_username(self): |
|
582 |
global token_1 |
|
583 |
_mock_request([_request_ok]) |
|
584 |
try: |
|
585 |
client = AstakosClient("https://example.com") |
|
586 |
client.get_username(token_1, "1234") |
|
587 |
except NoUserName: |
|
588 |
pass |
|
589 |
except: |
|
590 |
self.fail("Should have raised NoDisplayName exception") |
|
591 |
else: |
|
592 |
self.fail("Should have raised NoDisplayName exception") |
|
593 |
|
|
594 |
|
|
595 |
class TestGetUUIDs(unittest.TestCase): |
|
596 |
"""Test cases for functions getUUIDs/getUUID""" |
|
597 |
|
|
598 |
# ---------------------------------- |
|
599 |
# Test the response we get for invalid token |
|
600 |
def test_invalid_token(self): |
|
601 |
"""Test the response we get for invalid token (using pool)""" |
|
602 |
global user_1 |
|
603 |
token = "skaksaFlBl+fasFdaf24sx==" |
|
604 |
_mock_request([_request_ok]) |
|
605 |
try: |
|
606 |
client = AstakosClient("https://example.com") |
|
607 |
client.get_uuids(token, [user_1['username']]) |
|
608 |
except Unauthorized: |
|
609 |
pass |
|
610 |
except Exception: |
|
611 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
|
612 |
else: |
|
613 |
self.fail("Should have returned 401 (Invalid X-Auth-Token)") |
|
614 |
|
|
615 |
# ---------------------------------- |
|
616 |
# Get info for both users |
|
617 |
def test_uuids(self): |
|
618 |
"""Test get_uuids with both users""" |
|
619 |
global token_1, user_1, user_2 |
|
620 |
_mock_request([_request_ok]) |
|
621 |
try: |
|
622 |
client = AstakosClient("https://example.com") |
|
623 |
catalog = client.get_uuids( |
|
624 |
token_1, [user_1['username'], user_2['username']]) |
|
625 |
except: |
|
626 |
self.fail("Shouldn't raise an Exception") |
|
627 |
self.assertEqual(catalog[user_1['username']], user_1['uuid']) |
|
628 |
self.assertEqual(catalog[user_2['username']], user_2['uuid']) |
|
629 |
|
|
630 |
# ---------------------------------- |
|
631 |
# Get uuid for user 2 |
|
632 |
def test_get_uuid_user_two(self): |
|
633 |
"""Test get_uuid for User Two""" |
|
634 |
global token_1, user_2 |
|
635 |
_mock_request([_request_offline, _request_ok]) |
|
636 |
try: |
|
637 |
client = AstakosClient("https://example.com", retry=1) |
|
638 |
info = client.get_uuid(token_2, user_1['username']) |
|
639 |
except: |
|
640 |
self.fail("Shouldn't raise an Exception") |
|
641 |
self.assertEqual(info, user_1['uuid']) |
|
642 |
|
|
643 |
# ---------------------------------- |
|
644 |
# Get uuid with wrong username |
|
645 |
def test_no_uuid(self): |
|
646 |
global token_1 |
|
647 |
_mock_request([_request_ok]) |
|
648 |
try: |
|
649 |
client = AstakosClient("https://example.com") |
|
650 |
client.get_uuid(token_1, "1234") |
|
651 |
except NoUUID: |
|
652 |
pass |
|
653 |
except: |
|
654 |
self.fail("Should have raised NoUUID exception") |
|
655 |
else: |
|
656 |
self.fail("Should have raised NoUUID exception") |
|
657 |
|
|
658 |
|
|
659 |
# ---------------------------- |
|
660 |
# Run tests |
|
661 |
if __name__ == "__main__": |
|
662 |
unittest.main() |
b/astakosclient/astakosclient/utils.py | ||
---|---|---|
1 |
# Copyright (C) 2012, 2013 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 httplib import HTTPConnection, HTTPSConnection |
|
35 |
from contextlib import closing |
|
36 |
|
|
37 |
from objpool.http import PooledHTTPConnection |
|
38 |
from astakosclient.errors import AstakosClientException |
|
39 |
|
|
40 |
|
|
41 |
def retry(func): |
|
42 |
def decorator(self, *args, **kwargs): |
|
43 |
attemps = 0 |
|
44 |
while True: |
|
45 |
try: |
|
46 |
return func(self, *args, **kwargs) |
|
47 |
except AstakosClientException as err: |
|
48 |
is_last_attempt = attemps == self.retry |
|
49 |
if is_last_attempt: |
|
50 |
raise err |
|
51 |
if err.status == 401 or err.status == 404: |
|
52 |
# In case of Unauthorized response |
|
53 |
# or Not Found return immediately |
|
54 |
raise err |
|
55 |
self.logger.info("AstakosClient request failed..retrying") |
|
56 |
attemps += 1 |
|
57 |
return decorator |
|
58 |
|
|
59 |
|
|
60 |
def scheme_to_class(scheme, use_pool, pool_size): |
|
61 |
"""Return the appropriate conn class for given scheme""" |
|
62 |
def _objpool(netloc): |
|
63 |
return PooledHTTPConnection( |
|
64 |
netloc=netloc, scheme=scheme, pool_size=pool_size) |
|
65 |
|
|
66 |
def _http_connection(netloc): |
|
67 |
return closing(HTTPConnection(netloc)) |
|
68 |
|
|
69 |
def _https_connection(netloc): |
|
70 |
return closing(HTTPSConnection(netloc)) |
|
71 |
|
|
72 |
if scheme == "http": |
|
73 |
if use_pool: |
|
74 |
return _objpool |
|
75 |
else: |
|
76 |
return _http_connection |
|
77 |
elif scheme == "https": |
|
78 |
if use_pool: |
|
79 |
return _objpool |
|
80 |
else: |
|
81 |
return _https_connection |
|
82 |
else: |
|
83 |
return None |
b/astakosclient/distribute_setup.py | ||
---|---|---|
1 |
#!python |
|
2 |
"""Bootstrap distribute installation |
|
3 |
|
|
4 |
If you want to use setuptools in your package's setup.py, just include this |
|
5 |
file in the same directory with it, and add this to the top of your setup.py:: |
|
6 |
|
|
7 |
from distribute_setup import use_setuptools |
|
8 |
use_setuptools() |
|
9 |
|
|
10 |
If you want to require a specific version of setuptools, set a download |
|
11 |
mirror, or use an alternate download directory, you can do so by supplying |
|
12 |
the appropriate options to ``use_setuptools()``. |
|
13 |
|
|
14 |
This file can also be run as a script to install or upgrade setuptools. |
|
15 |
""" |
|
16 |
import os |
|
17 |
import sys |
|
18 |
import time |
|
19 |
import fnmatch |
|
20 |
import tempfile |
|
21 |
import tarfile |
|
22 |
from distutils import log |
|
23 |
|
|
24 |
try: |
|
25 |
from site import USER_SITE |
|
26 |
except ImportError: |
|
27 |
USER_SITE = None |
|
28 |
|
|
29 |
try: |
|
30 |
import subprocess |
|
31 |
|
|
32 |
def _python_cmd(*args): |
|
33 |
args = (sys.executable,) + args |
|
34 |
return subprocess.call(args) == 0 |
|
35 |
|
|
36 |
except ImportError: |
|
37 |
# will be used for python 2.3 |
|
38 |
def _python_cmd(*args): |
|
39 |
args = (sys.executable,) + args |
|
40 |
# quoting arguments if windows |
|
41 |
if sys.platform == 'win32': |
|
42 |
def quote(arg): |
|
43 |
if ' ' in arg: |
|
44 |
return '"%s"' % arg |
|
45 |
return arg |
|
46 |
args = [quote(arg) for arg in args] |
|
47 |
return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 |
|
48 |
|
|
49 |
DEFAULT_VERSION = "0.6.10" |
|
50 |
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" |
|
51 |
SETUPTOOLS_FAKED_VERSION = "0.6c11" |
|
52 |
|
|
53 |
SETUPTOOLS_PKG_INFO = """\ |
|
54 |
Metadata-Version: 1.0 |
|
55 |
Name: setuptools |
|
56 |
Version: %s |
|
57 |
Summary: xxxx |
|
58 |
Home-page: xxx |
|
59 |
Author: xxx |
|
60 |
Author-email: xxx |
|
61 |
License: xxx |
|
62 |
Description: xxx |
|
63 |
""" % SETUPTOOLS_FAKED_VERSION |
|
64 |
|
|
65 |
|
|
66 |
def _install(tarball): |
|
67 |
# extracting the tarball |
|
68 |
tmpdir = tempfile.mkdtemp() |
|
69 |
log.warn('Extracting in %s', tmpdir) |
|
70 |
old_wd = os.getcwd() |
|
71 |
try: |
|
72 |
os.chdir(tmpdir) |
|
73 |
tar = tarfile.open(tarball) |
|
74 |
_extractall(tar) |
|
75 |
tar.close() |
|
76 |
|
|
77 |
# going in the directory |
|
78 |
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) |
|
79 |
os.chdir(subdir) |
|
80 |
log.warn('Now working in %s', subdir) |
|
81 |
|
|
82 |
# installing |
|
83 |
log.warn('Installing Distribute') |
|
84 |
if not _python_cmd('setup.py', 'install'): |
|
85 |
log.warn('Something went wrong during the installation.') |
|
86 |
log.warn('See the error message above.') |
|
87 |
finally: |
|
88 |
os.chdir(old_wd) |
|
89 |
|
|
90 |
|
|
91 |
def _build_egg(egg, tarball, to_dir): |
|
92 |
# extracting the tarball |
|
93 |
tmpdir = tempfile.mkdtemp() |
|
94 |
log.warn('Extracting in %s', tmpdir) |
|
95 |
old_wd = os.getcwd() |
|
96 |
try: |
|
97 |
os.chdir(tmpdir) |
|
98 |
tar = tarfile.open(tarball) |
|
99 |
_extractall(tar) |
|
100 |
tar.close() |
|
101 |
|
|
102 |
# going in the directory |
|
103 |
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) |
|
104 |
os.chdir(subdir) |
|
105 |
log.warn('Now working in %s', subdir) |
|
106 |
|
|
107 |
# building an egg |
|
108 |
log.warn('Building a Distribute egg in %s', to_dir) |
|
109 |
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) |
|
110 |
|
|
111 |
finally: |
|
112 |
os.chdir(old_wd) |
|
113 |
# returning the result |
|
114 |
log.warn(egg) |
|
115 |
if not os.path.exists(egg): |
|
116 |
raise IOError('Could not build the egg.') |
|
117 |
|
|
118 |
|
|
119 |
def _do_download(version, download_base, to_dir, download_delay): |
|
120 |
egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' |
|
121 |
% (version, sys.version_info[0], sys.version_info[1])) |
|
122 |
if not os.path.exists(egg): |
|
123 |
tarball = download_setuptools(version, download_base, |
|
124 |
to_dir, download_delay) |
|
125 |
_build_egg(egg, tarball, to_dir) |
|
126 |
sys.path.insert(0, egg) |
|
127 |
import setuptools |
|
128 |
setuptools.bootstrap_install_from = egg |
|
129 |
|
|
130 |
|
|
131 |
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, |
|
132 |
to_dir=os.curdir, download_delay=15, no_fake=True): |
|
133 |
# making sure we use the absolute path |
|
134 |
to_dir = os.path.abspath(to_dir) |
|
135 |
was_imported = 'pkg_resources' in sys.modules or \ |
|
136 |
'setuptools' in sys.modules |
|
137 |
try: |
|
138 |
try: |
|
139 |
import pkg_resources |
|
140 |
if not hasattr(pkg_resources, '_distribute'): |
|
141 |
if not no_fake: |
|
142 |
_fake_setuptools() |
|
143 |
raise ImportError |
|
144 |
except ImportError: |
|
145 |
return _do_download(version, download_base, to_dir, download_delay) |
|
146 |
try: |
|
147 |
pkg_resources.require("distribute>="+version) |
|
148 |
return |
|
149 |
except pkg_resources.VersionConflict: |
|
150 |
e = sys.exc_info()[1] |
|
151 |
if was_imported: |
|
152 |
sys.stderr.write( |
|
153 |
"The required version of distribute (>=%s) is not available,\n" |
|
154 |
"and can't be installed while this script is running. Please\n" |
|
155 |
"install a more recent version first, using\n" |
|
156 |
"'easy_install -U distribute'." |
|
157 |
"\n\n(Currently using %r)\n" % (version, e.args[0])) |
|
158 |
sys.exit(2) |
|
159 |
else: |
|
160 |
del pkg_resources, sys.modules['pkg_resources'] # reload ok |
|
161 |
return _do_download(version, download_base, to_dir, |
|
162 |
download_delay) |
|
163 |
except pkg_resources.DistributionNotFound: |
|
164 |
return _do_download(version, download_base, to_dir, |
|
165 |
download_delay) |
|
166 |
finally: |
|
167 |
if not no_fake: |
|
168 |
_create_fake_setuptools_pkg_info(to_dir) |
|
169 |
|
|
170 |
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, |
|
171 |
to_dir=os.curdir, delay=15): |
|
172 |
"""Download distribute from a specified location and return its filename |
|
173 |
|
|
174 |
`version` should be a valid distribute version number that is available |
|
175 |
as an egg for download under the `download_base` URL (which should end |
|
176 |
with a '/'). `to_dir` is the directory where the egg will be downloaded. |
|
177 |
`delay` is the number of seconds to pause before an actual download |
|
178 |
attempt. |
|
179 |
""" |
|
180 |
# making sure we use the absolute path |
|
181 |
to_dir = os.path.abspath(to_dir) |
|
182 |
try: |
|
183 |
from urllib.request import urlopen |
|
184 |
except ImportError: |
|
185 |
from urllib2 import urlopen |
|
186 |
tgz_name = "distribute-%s.tar.gz" % version |
|
187 |
url = download_base + tgz_name |
|
188 |
saveto = os.path.join(to_dir, tgz_name) |
|
189 |
src = dst = None |
|
190 |
if not os.path.exists(saveto): # Avoid repeated downloads |
|
191 |
try: |
|
192 |
log.warn("Downloading %s", url) |
|
193 |
src = urlopen(url) |
|
194 |
# Read/write all in one block, so we don't create a corrupt file |
|
195 |
# if the download is interrupted. |
|
196 |
data = src.read() |
|
197 |
dst = open(saveto, "wb") |
|
198 |
dst.write(data) |
|
199 |
finally: |
|
200 |
if src: |
|
201 |
src.close() |
|
202 |
if dst: |
|
203 |
dst.close() |
|
204 |
return os.path.realpath(saveto) |
|
205 |
|
|
206 |
def _no_sandbox(function): |
|
207 |
def __no_sandbox(*args, **kw): |
|
208 |
try: |
|
209 |
from setuptools.sandbox import DirectorySandbox |
|
210 |
if not hasattr(DirectorySandbox, '_old'): |
|
211 |
def violation(*args): |
|
212 |
pass |
|
213 |
DirectorySandbox._old = DirectorySandbox._violation |
|
214 |
DirectorySandbox._violation = violation |
|
215 |
patched = True |
|
216 |
else: |
|
217 |
patched = False |
|
218 |
except ImportError: |
|
219 |
patched = False |
|
220 |
|
|
221 |
try: |
|
222 |
return function(*args, **kw) |
|
223 |
finally: |
|
224 |
if patched: |
|
225 |
DirectorySandbox._violation = DirectorySandbox._old |
|
226 |
del DirectorySandbox._old |
|
227 |
|
|
228 |
return __no_sandbox |
|
229 |
|
|
230 |
def _patch_file(path, content): |
|
231 |
"""Will backup the file then patch it""" |
|
232 |
existing_content = open(path).read() |
|
233 |
if existing_content == content: |
|
234 |
# already patched |
|
235 |
log.warn('Already patched.') |
|
236 |
return False |
|
237 |
log.warn('Patching...') |
|
238 |
_rename_path(path) |
|
239 |
f = open(path, 'w') |
|
240 |
try: |
|
241 |
f.write(content) |
|
242 |
finally: |
|
243 |
f.close() |
|
244 |
return True |
|
245 |
|
|
246 |
_patch_file = _no_sandbox(_patch_file) |
|
247 |
|
Also available in: Unified diff