Revision 8cb96389

b/docs/astakos-api-guide.rst
310 310
Authenticate
311 311
^^^^^^^^^^^^
312 312

  
313
Fallback call which receives the user token or the user uuid/token and returns
314
back the token as well as information about the token holder and the services
315
he/she can access.
313
Fallback call which receives the user token or the user uuid/token pair and
314
returns back the token as well as information about the token holder and the
315
services he/she can access.
316 316

  
317 317
========================================= =========  ==================
318 318
Uri                                       Method     Description
319 319
========================================= =========  ==================
320
``/astakos/api/tokens/``                  POST       Checks whether the provided token is valid and conforms with the provided uuid (if present) and returns back information about the user
320
``/identity/v2.0/tokens/``                POST       Checks whether the provided token is valid and conforms with the provided uuid (if present) and returns back information about the user
321 321
========================================= =========  ==================
322 322

  
323 323
The input should be json formatted.
......
328 328

  
329 329
    {
330 330
        "auth":{
331
            "passwordCredentials":{
332
                "username":"c18088be-16b1-4263-8180-043c54e22903",
333
                "password":"CDEe2k0T/HdiJWBMMbHyOA=="
331
            "token":{
332
                "id":"CDEe2k0T/HdiJWBMMbHyOA=="
334 333
            },
335 334
            "tenantName":"c18088be-16b1-4263-8180-043c54e22903"
336 335
        }
......
342 341

  
343 342
    {
344 343
        "auth":{
345
            "token":{
346
                "id":"CDEe2k0T/HdiJWBMMbHyOA=="
344
            "passwordCredentials":{
345
                "username":"c18088be-16b1-4263-8180-043c54e22903",
346
                "password":"CDEe2k0T/HdiJWBMMbHyOA=="
347 347
            },
348 348
            "tenantName":"c18088be-16b1-4263-8180-043c54e22903"
349 349
        }
......
359 359

  
360 360
::
361 361

  
362
    {'serviceCatalog': [
363
        {'endpoints': [{'SNF:uiURL': 'https://node1.example.com/ui/',
364
                        'adminURL': 'https://node1.example.com/v1',
365
                        'internalUrl': 'https://node1.example.com/v1',
366
                        'publicURL': 'https://node1.example.com/v1',
367
                        'region': 'cyclades'}],
368
         'name': 'cyclades',
369
         'type': 'compute'},
370
       {'endpoints': [{'SNF:uiURL': 'https://node2.example.com/ui/',
371
                      'adminURL': 'https://node2.example.com/v1',
372
                      'internalUrl': 'https://node2.example.com/v1',
373
                      'publicURL': 'https://node2.example.com/v1',
374
                      'region': 'pithos'}],
375
        'name': 'pithos',
376
        'type': 'storage'}],
377
     'token': {'expires': '2013-06-19T15:23:59.975572+00:00',
378
               'id': 'CDEe2k0T/HdiJWBMMbHyOA==',
379
               'tenant': {'id': 'c18088be-16b1-4263-8180-043c54e22903',
380
                          'name': 'Firstname Lastname'}},
381
     'user': {'id': 'c18088be-16b1-4263-8180-043c54e22903',
382
              'name': 'Firstname Lastname',
383
              'roles': [{'id': 1, 'name': 'default'}],
384
              'roles_links': []}}
385

  
362
    {"access": {
363
        "serviceCatalog": [
364
           {"SNF:uiURL": "https://node2.example.com/ui/",
365
            "endpoints": [{
366
                "publicURL": "https://object-store.example.synnefo.org/pithos/public/v2.0",
367
                "versionId": "v2.0"}],
368
            "endpoints_links": [],
369
            "name": "pithos_public",
370
            "type": "public"},
371
           {"SNF:uiURL": "https://node2.example.com/ui/",
372
            "endpoints": [{
373
                "publicURL": "https://object-store.example.synnefo.org/pithos/object-store/v1",
374
                "versionId": "v1"}],
375
            "endpoints_links": [],
376
            "name": "pithos_object-store",
377
            "type": "object-store"},
378
           {"SNF:uiURL": "https://node2.example.com/ui/",
379
            "endpoints": [{
380
                "publicURL": "https://object-store.example.synnefo.org/pithos/ui",
381
                "versionId": ""}],
382
            "endpoints_links": [],
383
            "name": "pithos_ui",
384
            "type": "pithos_ui"},
385
           {"SNF:uiURL": "http://localhost:8080",
386
            "endpoints": [{
387
                "publicURL": "https://accounts.example.synnefo.org/ui/v1.0",
388
                "versionId": "v1.0"}],
389
            "endpoints_links": [],
390
            "name": "astakos_ui",
391
            "type": "astakos_ui"},
392
           {"SNF:uiURL": "http://localhost:8080",
393
            "endpoints": [{
394
                "publicURL": "https://accounts.example.synnefo.org/account/v1.0",
395
                "versionId": "v1.0"}],
396
            "endpoints_links": [],
397
            "name": "astakos_account",
398
            "type": "account"},
399
           {"SNF:uiURL": "http://localhost:8080",
400
            "endpoints": [{
401
                "publicURL": "https://accounts.example.synnefo.org/identity/v2.0",
402
                "versionId": "v2.0"}],
403
            "endpoints_links": [],
404
            "name": "astakos_keystone",
405
            "type": "identity"}],
406
      "token": {
407
          "expires": "2013-06-19T15:23:59.975572+00:00",
408
           "id": "CDEe2k0T/HdiJWBMMbHyOA==",
409
           "tenant": {"id": "c18088be-16b1-4263-8180-043c54e22903",
410
            "name": "Firstname Lastname"}},
411
      "user": {
412
          "id": "c18088be-16b1-4263-8180-043c54e22903",
413
           "name": "Firstname Lastname",
414
           "roles": [{"id": 1, "name": "default"},
415
           "roles_links": []}}}
386 416

  
387 417
Example xml response:
388 418

  
389 419
::
390 420

  
391 421
    <?xml version="1.0" encoding="UTF-8"?>
422

  
392 423
    <access xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
393 424
        xmlns="http://docs.openstack.org/identity/api/v2.0">
394 425
        <token id="CDEe2k0T/HdiJWBMMbHyOA==" expires="2013-06-19T15:23:59.975572+00:00">
......
400 431
            </roles>
401 432
        </user>
402 433
        <serviceCatalog>
403
            <service type="None" name="cyclades">
404
                    <endpoint region="cyclades"
405
                        publicURL="https://node1.example.com/v1"
406
                        adminURL="https://node1.example.com/v1"
407
                        internalURL="https://node1.example.com/v1"
408
                        SNF:uiURL="https://node1.example.com/ui/">
409
                </endpoint>
434
            <service type="public" name="pithos_public" SNF:uiURL="">
435
                    <endpoint
436
                            versionId="v2.0"
437
                            publicURL="https://object-store.example.synnefo.org/pithos/public/v2.0"
438
            </service>
439
            <service type="object-store" name="pithos_object-store" SNF:uiURL="">
440
                    <endpoint
441
                            versionId="v1"
442
                            publicURL="https://object-store.example.synnefo.org/pithos/object-store/v1"
443
            </service>
444
            <service type="pithos_ui" name="pithos_ui" SNF:uiURL="">
445
                    <endpoint
446
                            versionId=""
447
                            publicURL="https://object-store.example.synnefo.org/pithos/ui"
448
            </service>
449
            <service type="astakos_ui" name="astakos_ui" SNF:uiURL="">
450
                    <endpoint
451
                            versionId="v1.0"
452
                            publicURL="https://accounts.example.synnefo.org/ui/v1.0"
453
            </service>
454
            <service type="account" name="astakos_account" SNF:uiURL="">
455
                    <endpoint
456
                            versionId="v1.0"
457
                            publicURL="https://accounts.example.synnefo.org/account/v1.0"
410 458
            </service>
411
            <service type="None" name="pithos">
412
                <endpoint
413
                        region="pithos"
414
                        publicURL="https://node2.example.com/v1"
415
                        adminURL="https://node2.example.com/v1"
416
                        internalURL="https://node2.example.com/v1"
417
                        SNF:uiURL="https://node2.example.com/ui/">
418
                </endpoint>
459
            <service type="identity" name="astakos_keystone" SNF:uiURL="">
460
                    <endpoint
461
                            versionId="v2.0"
462
                            publicURL="https://accounts.example.synnefo.org/identity/v2.0"
419 463
            </service>
420 464
        </serviceCatalog>
421 465
    </access>
b/snf-astakos-app/astakos/api/tokens.py
32 32
# or implied, of GRNET S.A.
33 33

  
34 34
from urlparse import urlunsplit, urlsplit
35
from collections import defaultdict
35 36

  
36 37
from django.http import urlencode
37 38
from django.views.decorators.csrf import csrf_exempt
......
114 115
        if user.uuid != uuid:
115 116
            raise faults.Unauthorized('Invalid credentials')
116 117

  
117
    access = {}
118
    access['token'] = {'id': user.auth_token,
119
                       'expires': utils.isoformat(user.auth_token_expires),
120
                       'tenant': {'id': user.uuid, 'name': user.realname}}
121
    access['user'] = {'id': user.uuid, 'name': user.realname,
122
                      'roles': list(user.groups.values('id', 'name')),
123
                      'roles_links': []}
124
    access['serviceCatalog'] = []
125
    append = access['serviceCatalog'].append
126
    for s in Service.objects.all().order_by('id'):
127
        append({'name': s.name, 'type': s.type,
128
                'endpoints': [{'adminURL': s.api_url,
129
                               'publicURL': s.api_url,
130
                               'internalURL': s.api_url,
131
                               'SNF:uiURL': s.url,
132
                               'region': s.name}]})
118
    d = defaultdict(dict)
119
    d["access"]["token"] = {
120
        "id": user.auth_token,
121
        "expires": utils.isoformat(user.auth_token_expires),
122
        "tenant": {"id": user.uuid, "name": user.realname}}
123
    d["access"]["user"] = {
124
        "id": user.uuid, 'name': user.realname,
125
        "roles": list(user.groups.values("id", "name")),
126
        "roles_links": []}
127
    d["access"]["serviceCatalog"] = []
128
    append = d["access"]["serviceCatalog"].append
129
    for s in Service.objects.all().order_by("id"):
130
        endpoints = []
131
        for l in [e.data.values('key', 'value') for e in s.endpoints.all()]:
132
            endpoint = dict((d['key'], d['value']) for d in l)
133
            endpoints.append(endpoint)
134
        append({"name": s.name,
135
                "type": s.type,
136
                "SNF:uiURL": s.component.url,
137
                "endpoints": endpoints,
138
                "endpoints_links": []})
133 139

  
134 140
    if request.serialization == 'xml':
135
        return xml_response({'access': access}, 'api/access.xml')
141
        return xml_response({'d': d}, 'api/access.xml')
136 142
    else:
137
        return json_response(access)
143
        return json_response(d)
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
    <token id="{{access.token.id}}" expires="{{access.token.expires}}">
6
        <tenant id="{{access.token.tenant.id}}" name="{{access.token.tenant.name}}" />
5
    <token id="{{d.access.token.id}}" expires="{{d.access.token.expires}}">
6
        <tenant id="{{d.access.token.tenant.id}}" name="{{d.access.token.tenant.name}}" />
7 7
    </token>
8
    <user id="{{access.user.id}}" name="{{access.user.name}}">
8
    <user id="{{d.access.user.id}}" name="{{d.access.user.name}}">
9 9
        <roles>
10
            {% for r in access.user.roles %}
10
            {% for r in d.access.user.roles %}
11 11
                <role id="{{r.id}}" name="{{r.name}}"/>
12 12
            {% endfor %}
13 13
        </roles>
14 14
    </user>
15 15
    <serviceCatalog>
16
    {% for s in access.serviceCatalog %}
17
        <service type="{{s.type}}" name="{{s.name}}">
16
    {% for s in d.access.serviceCatalog %}
17
        <service type="{{s.type}}" name="{{s.name}}" SNF:uiURL="{{s.component.url}}">
18 18
            {% for e in s.endpoints %}
19 19
                <endpoint
20
                    region="{{e.region}}"
21
                    publicURL="{{e.publicURL}}"
22
                    adminURL="{{e.adminURL}}"
23
                    internalURL="{{e.internalURL}}"
24
                    SNF:uiURL="{{e|lookup:'SNF:uiURL'}}"/>
20
                    {% for k, v in e.items %}
21
                        {{k}}="{{v}}"
22
                    {% endfor %}
25 23
            {% endfor %}
26 24
        </service>
27 25
    {% endfor %}
b/snf-astakos-app/astakos/im/tests/api.py
34 34
from astakos.im.tests.common import *
35 35

  
36 36
from django.test import TestCase
37
from django.core.urlresolvers import reverse
38

  
37 39

  
38 40
from urllib import quote
39 41
from urlparse import urlparse, parse_qs
40
from xml.dom import minidom
42
#from xml.dom import minidom
41 43

  
42 44
import json
43 45

  
......
391 393
        backend.activate_user(self.user2)
392 394
        assert self.user2.is_active is True
393 395

  
394
        Service(name='service1', url='http://localhost/service1',
395
                api_url='http://localhost/api/service1',
396
                type='service1').save()
397
        Service(name='service2', url='http://localhost/service2',
398
                api_url='http://localhost/api/service2',
399
                type='service2').save()
400
        Service(name='service3', url='http://localhost/service3',
401
                api_url='http://localhost/api/service3',
402
                type='service3').save()
396
        c1 = Component(name='component1', url='http://localhost/component1')
397
        c1.save()
398
        s1 = Service(component=c1, type='type1', name='service1')
399
        s1.save()
400
        e1 = Endpoint(service=s1)
401
        e1.save()
402
        e1.data.create(key='versionId', value='v1.0')
403
        e1.data.create(key='publicURL', value='http://localhost:8000/s1/v1.0')
404

  
405
        s2 = Service(component=c1, type='type2', name='service2')
406
        s2.save()
407
        e2 = Endpoint(service=s2)
408
        e2.save()
409
        e2.data.create(key='versionId', value='v1.0')
410
        e2.data.create(key='publicURL', value='http://localhost:8000/s2/v1.0')
411

  
412
        c2 = Component(name='component2', url='http://localhost/component2')
413
        c2.save()
414
        s3 = Service(component=c2, type='type3', name='service3')
415
        s3.save()
416
        e3 = Endpoint(service=s3)
417
        e3.save()
418
        e3.data.create(key='versionId', value='v2.0')
419
        e3.data.create(key='publicURL', value='http://localhost:8000/s3/v2.0')
403 420

  
404 421
    def test_authenticate(self):
405 422
        client = Client()
406 423

  
407 424
        # Check not allowed method
408
        url = '/astakos/api/tokens'
425
        url = reverse('astakos.api.tokens.authenticate')
409 426
        r = client.get(url, post_data={})
410 427
        self.assertEqual(r.status_code, 400)
411 428

  
412 429
        # Malformed request
413
        url = '/astakos/api/tokens'
430
        url = reverse('astakos.api.tokens.authenticate')
414 431
        r = client.post(url, post_data={})
415 432
        self.assertEqual(r.status_code, 400)
416 433

  
417 434
        # Check unsupported xml input
418
        url = '/astakos/api/tokens'
435
        url = reverse('astakos.api.tokens.authenticate')
419 436
        post_data = """
420 437
            <?xml version="1.0" encoding="UTF-8"?>
421 438
                <auth xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
......
431 448
                         "Unsupported Content-type: 'application/xml'")
432 449

  
433 450
        # Check malformed request: missing password
434
        url = '/astakos/api/tokens'
451
        url = reverse('astakos.api.tokens.authenticate')
435 452
        post_data = """{"auth":{"passwordCredentials":{"username":"%s"},
436 453
                                "tenantName":"%s"}}""" % (
437 454
            self.user1.uuid, self.user1.uuid)
......
442 459
                         'Malformed request')
443 460

  
444 461
        # Check malformed request: missing username
445
        url = '/astakos/api/tokens'
462
        url = reverse('astakos.api.tokens.authenticate')
446 463
        post_data = """{"auth":{"passwordCredentials":{"password":"%s"},
447 464
                                "tenantName":"%s"}}""" % (
448 465
            self.user1.auth_token, self.user1.uuid)
......
453 470
                         'Malformed request')
454 471

  
455 472
        # Check invalid pass
456
        url = '/astakos/api/tokens'
473
        url = reverse('astakos.api.tokens.authenticate')
457 474
        post_data = """{"auth":{"passwordCredentials":{"username":"%s",
458 475
                                                       "password":"%s"},
459 476
                                "tenantName":"%s"}}""" % (
......
465 482
                         'Invalid token')
466 483

  
467 484
        # Check inconsistent pass
468
        url = '/astakos/api/tokens'
485
        url = reverse('astakos.api.tokens.authenticate')
469 486
        post_data = """{"auth":{"passwordCredentials":{"username":"%s",
470 487
                                                       "password":"%s"},
471 488
                                "tenantName":"%s"}}""" % (
......
477 494
                         'Invalid credentials')
478 495

  
479 496
        # Check invalid json data
480
        url = '/astakos/api/tokens'
497
        url = reverse('astakos.api.tokens.authenticate')
481 498
        r = client.post(url, "not json", content_type='application/json')
482 499
        self.assertEqual(r.status_code, 400)
483 500
        body = json.loads(r.content)
484 501
        self.assertEqual(body['badRequest']['message'], 'Invalid JSON data')
485 502

  
486 503
        # Check auth with token
487
        url = '/astakos/api/tokens'
504
        url = reverse('astakos.api.tokens.authenticate')
488 505
        post_data = """{"auth":{"token": {"id":"%s"},
489 506
                        "tenantName":"%s"}}""" % (
490 507
            self.user1.auth_token, self.user1.uuid)
......
497 514
            self.fail(e)
498 515

  
499 516
        # Check successful json response
500
        url = '/astakos/api/tokens'
517
        url = reverse('astakos.api.tokens.authenticate')
501 518
        post_data = """{"auth":{"passwordCredentials":{"username":"%s",
502 519
                                                       "password":"%s"},
503 520
                                "tenantName":"%s"}}""" % (
......
511 528
            self.fail(e)
512 529

  
513 530
        try:
514
            token = body['token']['id']
515
            user = body['user']['id']
516
            service_catalog = body['serviceCatalog']
531
            token = body['access']['token']['id']
532
            user = body['access']['user']['id']
533
            service_catalog = body['access']['serviceCatalog']
517 534
        except KeyError:
518 535
            self.fail('Invalid response')
519 536

  
......
522 539
        self.assertEqual(len(service_catalog), 3)
523 540

  
524 541
        # Check successful xml response
525
        url = '/astakos/api/tokens'
542
        url = reverse('astakos.api.tokens.authenticate')
526 543
        headers = {'HTTP_ACCEPT': 'application/xml'}
527 544
        post_data = """{"auth":{"passwordCredentials":{"username":"%s",
528 545
                                                       "password":"%s"},

Also available in: Unified diff