Revision d29f0371
b/docs/astakos-api-guide.rst | ||
---|---|---|
314 | 314 |
Fallback call which receives the user token or the user uuid/token pair and |
315 | 315 |
returns back the token as well as information about the token holder and the |
316 | 316 |
services he/she can access. |
317 |
If not request body is provided (the request content length is missing or |
|
318 |
equals to 0) the response contains only non authentication protected |
|
319 |
information (the service catalog). |
|
317 | 320 |
|
318 | 321 |
========================================= ========= ================== |
319 | 322 |
Uri Method Description |
... | ... | |
361 | 364 |
:: |
362 | 365 |
|
363 | 366 |
{"access": { |
367 |
"token": { |
|
368 |
"expires": "2013-06-19T15:23:59.975572+00:00", |
|
369 |
"id": "CDEe2k0T/HdiJWBMMbHyOA==", |
|
370 |
"tenant": { |
|
371 |
"id": "c18088be-16b1-4263-8180-043c54e22903", |
|
372 |
"name": "Firstname Lastname" |
|
373 |
} |
|
374 |
}, |
|
364 | 375 |
"serviceCatalog": [ |
365 |
{"SNF:uiURL": "https://node2.example.com/ui/", |
|
366 |
"endpoints": [{ |
|
367 |
"publicURL": "https://object-store.example.synnefo.org/pithos/public/v2.0", |
|
368 |
"versionId": "v2.0"}], |
|
369 |
"endpoints_links": [], |
|
370 |
"name": "pithos_public", |
|
371 |
"type": "public"}, |
|
372 |
{"SNF:uiURL": "https://node2.example.com/ui/", |
|
373 |
"endpoints": [{ |
|
374 |
"publicURL": "https://object-store.example.synnefo.org/pithos/object-store/v1", |
|
375 |
"versionId": "v1"}], |
|
376 |
"endpoints_links": [], |
|
377 |
"name": "pithos_object-store", |
|
378 |
"type": "object-store"}, |
|
379 |
{"SNF:uiURL": "https://node2.example.com/ui/", |
|
380 |
"endpoints": [{ |
|
381 |
"publicURL": "https://object-store.example.synnefo.org/pithos/ui", |
|
382 |
"versionId": ""}], |
|
383 |
"endpoints_links": [], |
|
384 |
"name": "pithos_ui", |
|
385 |
"type": "pithos_ui"}, |
|
386 |
{"SNF:uiURL": "http://localhost:8080", |
|
387 |
"endpoints": [{ |
|
388 |
"publicURL": "https://accounts.example.synnefo.org/ui/v1.0", |
|
389 |
"versionId": "v1.0"}], |
|
390 |
"endpoints_links": [], |
|
391 |
"name": "astakos_ui", |
|
392 |
"type": "astakos_ui"}, |
|
393 |
{"SNF:uiURL": "http://localhost:8080", |
|
394 |
"endpoints": [{ |
|
395 |
"publicURL": "https://accounts.example.synnefo.org/account/v1.0", |
|
396 |
"versionId": "v1.0"}], |
|
397 |
"endpoints_links": [], |
|
398 |
"name": "astakos_account", |
|
399 |
"type": "account"}, |
|
400 |
{"SNF:uiURL": "http://localhost:8080", |
|
401 |
"endpoints": [{ |
|
402 |
"publicURL": "https://accounts.example.synnefo.org/identity/v2.0", |
|
403 |
"versionId": "v2.0"}], |
|
404 |
"endpoints_links": [], |
|
405 |
"name": "astakos_identity", |
|
406 |
"type": "identity"}], |
|
407 |
"token": { |
|
408 |
"expires": "2013-06-19T15:23:59.975572+00:00", |
|
409 |
"id": "CDEe2k0T/HdiJWBMMbHyOA", |
|
410 |
"tenant": {"id": "c18088be-16b1-4263-8180-043c54e22903", |
|
411 |
"name": "Firstname Lastname"}}, |
|
412 |
"user": { |
|
413 |
"id": "c18088be-16b1-4263-8180-043c54e22903", |
|
414 |
"name": "Firstname Lastname", |
|
415 |
"roles": [{"id": 1, "name": "default"}, |
|
416 |
"roles_links": []}}} |
|
376 |
{"endpoints_links": [], |
|
377 |
"endpoints": [{ |
|
378 |
"SNF:uiURL": "https://accounts.example.synnefo.org/ui", |
|
379 |
"versionId": "v1.0", |
|
380 |
"publicURL": "https://accounts.example.synnefo.org/account/v1.0"}], |
|
381 |
"type": "account", |
|
382 |
"name": "astakos_account"}, |
|
383 |
{"endpoints_links": [], |
|
384 |
"endpoints": [{ |
|
385 |
"SNF:uiURL": "https://accounts.example.synnefo.org/ui", |
|
386 |
"versionId": "v2.0", |
|
387 |
"publicURL": "https://accounts.example.synnefo.org/account/v2.0"}], |
|
388 |
"type": "identity", |
|
389 |
"name": "astakos_identity"}, |
|
390 |
{"endpoints_links": [], |
|
391 |
"endpoints": [{ |
|
392 |
"SNF:uiURL": "https://cyclades.example.synnefo.org/ui", |
|
393 |
"versionId": "v2.0", |
|
394 |
"publicURL": "https://cyclades.example.synnefo.org/cyclades/compute/v2.0"}], |
|
395 |
"type": "compute", |
|
396 |
"name": "cyclades_compute"}, |
|
397 |
{"endpoints_links": [], |
|
398 |
"endpoints": [{ |
|
399 |
"SNF:uiURL": "https://cyclades.example.synnefo.org/ui", |
|
400 |
"versionId": "v1.0", |
|
401 |
"publicURL": "https://cyclades.example.synnefo.org/cyclades/vmapi/v1.0"}], |
|
402 |
"type": "cyclades_vmapi", |
|
403 |
"name": "cyclades_vmapi"}, |
|
404 |
{"endpoints_links": [], |
|
405 |
"endpoints": [{ |
|
406 |
"SNF:uiURL": "https://cyclades.example.synnefo.org/ui", |
|
407 |
"versionId": "v1.0", |
|
408 |
"publicURL": "https://cyclades.example.synnefo.org/cyclades/image/v1.0"}], |
|
409 |
"type": "image", |
|
410 |
"name": "cyclades_plankton"}, |
|
411 |
{"endpoints_links": [], |
|
412 |
"endpoints": [{ |
|
413 |
"SNF:uiURL": "https://object-store.example.synnefo.org/ui", |
|
414 |
"versionId": "v2.0", |
|
415 |
"publicURL": "https://object-store.example.synnefo.org/pithos/public/v2.0"}], |
|
416 |
"type": "public", |
|
417 |
"name": "pithos_public"}, |
|
418 |
{"endpoints_links": [], |
|
419 |
"endpoints": [{ |
|
420 |
"SNF:uiURL": "https://object-store.example.synnefo.org/ui", |
|
421 |
"versionId": "v1", |
|
422 |
"publicURL": "https://object-store.example.synnefo.org/pithos/object-store/v1"}], |
|
423 |
"type": "object-store", |
|
424 |
"name": "pithos_object-store"}], |
|
425 |
"user": { |
|
426 |
"roles_links": [], |
|
427 |
"id": "c18088be-16b1-4263-8180-043c54e22903", |
|
428 |
"roles": [{"id": 1, "name": "default"}], |
|
429 |
"name": "Firstname Lastname"}}} |
|
417 | 430 |
|
418 | 431 |
Example xml response: |
419 | 432 |
|
... | ... | |
423 | 436 |
|
424 | 437 |
<access xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
425 | 438 |
xmlns="http://docs.openstack.org/identity/api/v2.0"> |
426 |
<token id="CDEe2k0T/HdiJWBMMbHyOA" expires="2013-06-19T15:23:59.975572+00:00"> |
|
439 |
<token id="CDEe2k0T/HdiJWBMMbHyOA==" expires="2013-06-19T15:23:59.975572+00:00">
|
|
427 | 440 |
<tenant id="c18088be-16b1-4263-8180-043c54e22903" name="Firstname Lastname" /> |
428 | 441 |
</token> |
429 | 442 |
<user id="c18088be-16b1-4263-8180-043c54e22903" name="Firstname Lastname"> |
... | ... | |
432 | 445 |
</roles> |
433 | 446 |
</user> |
434 | 447 |
<serviceCatalog> |
435 |
<service type="public" name="pithos_public" SNF:uiURL=""> |
|
436 |
<endpoint |
|
437 |
versionId="v2.0" |
|
438 |
publicURL="https://object-store.example.synnefo.org/pithos/public/v2.0" |
|
448 |
<service type="account" name="astakos_account"> |
|
449 |
<endpoint SNF:uiURL="https://accounts.example.synnefo.org/ui" versionId="v1.0" publicURL="https://accounts.example.synnefo.org/account/v1.0" /> |
|
439 | 450 |
</service> |
440 |
<service type="object-store" name="pithos_object-store" SNF:uiURL=""> |
|
441 |
<endpoint |
|
442 |
versionId="v1" |
|
443 |
publicURL="https://object-store.example.synnefo.org/pithos/object-store/v1" |
|
451 |
<service type="identity" name="astakos_identity"> |
|
452 |
<endpoint SNF:uiURL="https://accounts.example.synnefo.org/ui" versionId="v2.0" publicURL="https://accounts.example.synnefo.org/account/v2.0" /> |
|
444 | 453 |
</service> |
445 |
<service type="pithos_ui" name="pithos_ui" SNF:uiURL=""> |
|
446 |
<endpoint |
|
447 |
versionId="" |
|
448 |
publicURL="https://object-store.example.synnefo.org/pithos/ui" |
|
454 |
<service type="compute" name="cyclades_compute"> |
|
455 |
<endpoint SNF:uiURL="https://cyclades.example.synnefo.org/ui" versionId="v2.0" publicURL="https://cyclades.example.synnefo.org/cyclades/compute/v2.0" /> |
|
449 | 456 |
</service> |
450 |
<service type="astakos_ui" name="astakos_ui" SNF:uiURL=""> |
|
451 |
<endpoint |
|
452 |
versionId="v1.0" |
|
453 |
publicURL="https://accounts.example.synnefo.org/ui/v1.0" |
|
457 |
<service type="cyclades_vmapi" name="cyclades_vmapi"> |
|
458 |
<endpoint SNF:uiURL="https://cyclades.example.synnefo.org/ui" versionId="v1.0" publicURL="https://cyclades.example.synnefo.org/cyclades/vmapi/v1.0" /> |
|
454 | 459 |
</service> |
455 |
<service type="account" name="astakos_account" SNF:uiURL=""> |
|
456 |
<endpoint |
|
457 |
versionId="v1.0" |
|
458 |
publicURL="https://accounts.example.synnefo.org/account/v1.0" |
|
460 |
<service type="image" name="cyclades_plankton"> |
|
461 |
<endpoint SNF:uiURL="https://cyclades.example.synnefo.org/ui" versionId="v1.0" publicURL="https://cyclades.example.synnefo.org/cyclades/image/v1.0" /> |
|
459 | 462 |
</service> |
460 |
<service type="identity" name="astakos_identity" SNF:uiURL=""> |
|
461 |
<endpoint |
|
462 |
versionId="v2.0" |
|
463 |
publicURL="https://accounts.example.synnefo.org/identity/v2.0" |
|
463 |
<service type="public" name="pithos_public"> |
|
464 |
<endpoint SNF:uiURL="https://object-store.example.synnefo.org/ui" versionId="v2.0" publicURL="https://object-store.example.synnefo.org/pithos/public/v2.0" /> |
|
464 | 465 |
</service> |
466 |
<service type="object-store" name="pithos_object-store"> |
|
467 |
<endpoint SNF:uiURL="https://object-store.example.synnefo.org/ui" versionId="v1" publicURL="https://object-store.example.synnefo.org/pithos/object-store/v1" /> </service> |
|
465 | 468 |
</serviceCatalog> |
466 | 469 |
</access> |
467 | 470 |
|
b/snf-astakos-app/astakos/api/tokens.py | ||
---|---|---|
38 | 38 |
from snf_django.lib.api import faults, utils, api_method |
39 | 39 |
|
40 | 40 |
from astakos.im.models import Service, AstakosUser |
41 |
from .util import json_response, xml_response, validate_user |
|
41 |
from .util import json_response, xml_response, validate_user,\ |
|
42 |
get_content_length |
|
42 | 43 |
|
43 | 44 |
import logging |
44 | 45 |
logger = logging.getLogger(__name__) |
... | ... | |
48 | 49 |
@api_method(http_method="POST", token_required=False, user_required=False, |
49 | 50 |
logger=logger) |
50 | 51 |
def authenticate(request): |
51 |
req = utils.get_request_dict(request) |
|
52 |
content_length = get_content_length(request) |
|
53 |
public_mode = True if not content_length else False |
|
52 | 54 |
|
53 |
uuid = None |
|
54 |
try: |
|
55 |
token_id = req['auth']['token']['id'] |
|
56 |
except KeyError: |
|
55 |
d = defaultdict(dict) |
|
56 |
if not public_mode: |
|
57 |
req = utils.get_request_dict(request) |
|
58 |
|
|
59 |
uuid = None |
|
57 | 60 |
try: |
58 |
token_id = req['auth']['passwordCredentials']['password'] |
|
59 |
uuid = req['auth']['passwordCredentials']['username'] |
|
61 |
token_id = req['auth']['token']['id'] |
|
60 | 62 |
except KeyError: |
63 |
try: |
|
64 |
token_id = req['auth']['passwordCredentials']['password'] |
|
65 |
uuid = req['auth']['passwordCredentials']['username'] |
|
66 |
except KeyError: |
|
67 |
raise faults.BadRequest('Malformed request') |
|
68 |
|
|
69 |
if token_id is None: |
|
61 | 70 |
raise faults.BadRequest('Malformed request') |
62 | 71 |
|
63 |
if token_id is None: |
|
64 |
raise faults.BadRequest('Malformed request') |
|
72 |
try: |
|
73 |
user = AstakosUser.objects.get(auth_token=token_id) |
|
74 |
except AstakosUser.DoesNotExist: |
|
75 |
raise faults.Unauthorized('Invalid token') |
|
65 | 76 |
|
66 |
try: |
|
67 |
user = AstakosUser.objects.get(auth_token=token_id) |
|
68 |
except AstakosUser.DoesNotExist: |
|
69 |
raise faults.Unauthorized('Invalid token') |
|
77 |
validate_user(user) |
|
70 | 78 |
|
71 |
validate_user(user) |
|
79 |
if uuid is not None: |
|
80 |
if user.uuid != uuid: |
|
81 |
raise faults.Unauthorized('Invalid credentials') |
|
72 | 82 |
|
73 |
if uuid is not None: |
|
74 |
if user.uuid != uuid: |
|
75 |
raise faults.Unauthorized('Invalid credentials') |
|
83 |
d["access"]["token"] = { |
|
84 |
"id": user.auth_token, |
|
85 |
"expires": utils.isoformat(user.auth_token_expires), |
|
86 |
"tenant": {"id": user.uuid, "name": user.realname}} |
|
87 |
d["access"]["user"] = { |
|
88 |
"id": user.uuid, 'name': user.realname, |
|
89 |
"roles": list(user.groups.values("id", "name")), |
|
90 |
"roles_links": []} |
|
76 | 91 |
|
77 |
d = defaultdict(dict) |
|
78 |
d["access"]["token"] = { |
|
79 |
"id": user.auth_token, |
|
80 |
"expires": utils.isoformat(user.auth_token_expires), |
|
81 |
"tenant": {"id": user.uuid, "name": user.realname}} |
|
82 |
d["access"]["user"] = { |
|
83 |
"id": user.uuid, 'name': user.realname, |
|
84 |
"roles": list(user.groups.values("id", "name")), |
|
85 |
"roles_links": []} |
|
86 | 92 |
d["access"]["serviceCatalog"] = [] |
87 | 93 |
append = d["access"]["serviceCatalog"].append |
88 | 94 |
for s in Service.objects.all().order_by("id"): |
b/snf-astakos-app/astakos/api/util.py | ||
---|---|---|
206 | 206 |
return |
207 | 207 |
d[new] = d[old] |
208 | 208 |
del(d[old]) |
209 |
|
|
210 |
|
|
211 |
def get_int_parameter(p): |
|
212 |
if p is not None: |
|
213 |
try: |
|
214 |
p = int(p) |
|
215 |
except ValueError: |
|
216 |
return None |
|
217 |
if p < 0: |
|
218 |
return None |
|
219 |
return p |
|
220 |
|
|
221 |
|
|
222 |
def get_content_length(request): |
|
223 |
content_length = get_int_parameter(request.META.get('CONTENT_LENGTH')) |
|
224 |
if content_length is None: |
|
225 |
raise faults.LengthRequired('Missing or invalid Content-Length header') |
|
226 |
return content_length |
b/snf-astakos-app/astakos/im/templates/api/access.xml | ||
---|---|---|
2 | 2 |
{% load filters %} |
3 | 3 |
<access xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
4 | 4 |
xmlns="http://docs.openstack.org/identity/api/v2.0"> |
5 |
{% if d.access.token %} |
|
5 | 6 |
<token id="{{d.access.token.id}}" expires="{{d.access.token.expires}}"> |
6 | 7 |
<tenant id="{{d.access.token.tenant.id}}" name="{{d.access.token.tenant.name}}" /> |
7 | 8 |
</token> |
9 |
{% endif %} |
|
10 |
{% if d.access.user %} |
|
8 | 11 |
<user id="{{d.access.user.id}}" name="{{d.access.user.name}}"> |
9 | 12 |
<roles> |
10 | 13 |
{% for r in d.access.user.roles %} |
... | ... | |
12 | 15 |
{% endfor %} |
13 | 16 |
</roles> |
14 | 17 |
</user> |
18 |
{% endif %} |
|
15 | 19 |
<serviceCatalog> |
16 | 20 |
{% for s in d.access.serviceCatalog %} |
17 |
<service type="{{s.type}}" name="{{s.name}}" SNF:uiURL="{{s.component.url}}">
|
|
21 |
<service type="{{s.type}}" name="{{s.name}}"> |
|
18 | 22 |
{% for e in s.endpoints %} |
19 | 23 |
<endpoint {% for k, v in e.items %} {{k}}="{{v}}" {% endfor %} /> |
20 | 24 |
{% endfor %} |
b/snf-astakos-app/astakos/im/tests/api.py | ||
---|---|---|
426 | 426 |
r = client.get(url, post_data={}) |
427 | 427 |
self.assertEqual(r.status_code, 400) |
428 | 428 |
|
429 |
# Malformed request |
|
430 |
url = reverse('astakos.api.tokens.authenticate') |
|
431 |
r = client.post(url, post_data={}) |
|
432 |
self.assertEqual(r.status_code, 400) |
|
429 |
# check public mode |
|
430 |
r = client.post(url, CONTENT_LENGTH=0) |
|
431 |
self.assertEqual(r.status_code, 200) |
|
432 |
self.assertTrue(r['Content-Type'].startswith('application/json')) |
|
433 |
try: |
|
434 |
body = json.loads(r.content) |
|
435 |
except Exception, e: |
|
436 |
self.fail(e) |
|
437 |
self.assertTrue('token' not in body.get('access')) |
|
438 |
self.assertTrue('user' not in body.get('access')) |
|
439 |
self.assertTrue('serviceCatalog' in body.get('access')) |
|
433 | 440 |
|
434 | 441 |
# Check unsupported xml input |
435 | 442 |
url = reverse('astakos.api.tokens.authenticate') |
... | ... | |
553 | 560 |
# body = minidom.parseString(r.content) |
554 | 561 |
# except Exception, e: |
555 | 562 |
# self.fail(e) |
563 |
|
|
564 |
# test public mode: json response |
Also available in: Unified diff