~ubuntu-branches/debian/sid/keystone/sid

« back to all changes in this revision

Viewing changes to keystone/token/controllers.py

  • Committer: Package Import Robot
  • Author(s): Thomas Goirand
  • Date: 2013-05-10 10:22:18 UTC
  • mfrom: (1.2.1) (26.1.4 experimental)
  • Revision ID: package-import@ubuntu.com-20130510102218-7hph1420gz5jsyr7
Tags: 2013.1.1-2
* Uploading to unstable.
* New upstream release:
  - Fixes CVE-2013-2059: Keystone tokens not immediately invalidated when
  user is deleted [OSSA 2013-011] (Closes: #707598).
* Also installs httpd/keystone.py.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import json
 
2
import subprocess
 
3
import uuid
 
4
 
 
5
from keystone.common import cms
 
6
from keystone.common import controller
 
7
from keystone.common import dependency
 
8
from keystone.common import logging
 
9
from keystone.common import utils
 
10
from keystone import config
 
11
from keystone import exception
 
12
from keystone.openstack.common import timeutils
 
13
from keystone.token import core
 
14
 
 
15
CONF = config.CONF
 
16
LOG = logging.getLogger(__name__)
 
17
DEFAULT_DOMAIN_ID = CONF.identity.default_domain_id
 
18
 
 
19
 
 
20
class ExternalAuthNotApplicable(Exception):
 
21
    """External authentication is not applicable"""
 
22
    pass
 
23
 
 
24
 
 
25
@dependency.requires('catalog_api', 'trust_api', 'token_api')
 
26
class Auth(controller.V2Controller):
 
27
    def ca_cert(self, context, auth=None):
 
28
        ca_file = open(CONF.signing.ca_certs, 'r')
 
29
        data = ca_file.read()
 
30
        ca_file.close()
 
31
        return data
 
32
 
 
33
    def signing_cert(self, context, auth=None):
 
34
        cert_file = open(CONF.signing.certfile, 'r')
 
35
        data = cert_file.read()
 
36
        cert_file.close()
 
37
        return data
 
38
 
 
39
    def authenticate(self, context, auth=None):
 
40
        """Authenticate credentials and return a token.
 
41
 
 
42
        Accept auth as a dict that looks like::
 
43
 
 
44
            {
 
45
                "auth":{
 
46
                    "passwordCredentials":{
 
47
                        "username":"test_user",
 
48
                        "password":"mypass"
 
49
                    },
 
50
                    "tenantName":"customer-x"
 
51
                }
 
52
            }
 
53
 
 
54
        In this case, tenant is optional, if not provided the token will be
 
55
        considered "unscoped" and can later be used to get a scoped token.
 
56
 
 
57
        Alternatively, this call accepts auth with only a token and tenant
 
58
        that will return a token that is scoped to that tenant.
 
59
        """
 
60
 
 
61
        if auth is None:
 
62
            raise exception.ValidationError(attribute='auth',
 
63
                                            target='request body')
 
64
 
 
65
        auth_token_data = None
 
66
 
 
67
        if "token" in auth:
 
68
            # Try to authenticate using a token
 
69
            auth_info = self._authenticate_token(
 
70
                context, auth)
 
71
        else:
 
72
            # Try external authentication
 
73
            try:
 
74
                auth_info = self._authenticate_external(
 
75
                    context, auth)
 
76
            except ExternalAuthNotApplicable:
 
77
                # Try local authentication
 
78
                auth_info = self._authenticate_local(
 
79
                    context, auth)
 
80
 
 
81
        user_ref, tenant_ref, metadata_ref, expiry = auth_info
 
82
        core.validate_auth_info(self, context, user_ref, tenant_ref)
 
83
        trust_id = metadata_ref.get('trust_id')
 
84
        user_ref = self._filter_domain_id(user_ref)
 
85
        if tenant_ref:
 
86
            tenant_ref = self._filter_domain_id(tenant_ref)
 
87
        auth_token_data = self._get_auth_token_data(user_ref,
 
88
                                                    tenant_ref,
 
89
                                                    metadata_ref,
 
90
                                                    expiry)
 
91
 
 
92
        if tenant_ref:
 
93
            catalog_ref = self.catalog_api.get_catalog(
 
94
                context=context,
 
95
                user_id=user_ref['id'],
 
96
                tenant_id=tenant_ref['id'],
 
97
                metadata=metadata_ref)
 
98
        else:
 
99
            catalog_ref = {}
 
100
 
 
101
        auth_token_data['id'] = 'placeholder'
 
102
 
 
103
        roles_ref = []
 
104
        for role_id in metadata_ref.get('roles', []):
 
105
            role_ref = self.identity_api.get_role(context, role_id)
 
106
            roles_ref.append(dict(name=role_ref['name']))
 
107
 
 
108
        token_data = Auth.format_token(auth_token_data, roles_ref)
 
109
 
 
110
        service_catalog = Auth.format_catalog(catalog_ref)
 
111
        token_data['access']['serviceCatalog'] = service_catalog
 
112
 
 
113
        if CONF.signing.token_format == 'UUID':
 
114
            token_id = uuid.uuid4().hex
 
115
        elif CONF.signing.token_format == 'PKI':
 
116
            try:
 
117
                token_id = cms.cms_sign_token(json.dumps(token_data),
 
118
                                              CONF.signing.certfile,
 
119
                                              CONF.signing.keyfile)
 
120
            except subprocess.CalledProcessError:
 
121
                raise exception.UnexpectedError(_(
 
122
                    'Unable to sign token.'))
 
123
        else:
 
124
            raise exception.UnexpectedError(_(
 
125
                'Invalid value for token_format: %s.'
 
126
                '  Allowed values are PKI or UUID.') %
 
127
                CONF.signing.token_format)
 
128
        try:
 
129
            self.token_api.create_token(
 
130
                context, token_id, dict(key=token_id,
 
131
                                        id=token_id,
 
132
                                        expires=auth_token_data['expires'],
 
133
                                        user=user_ref,
 
134
                                        tenant=tenant_ref,
 
135
                                        metadata=metadata_ref,
 
136
                                        trust_id=trust_id))
 
137
        except Exception as e:
 
138
            # an identical token may have been created already.
 
139
            # if so, return the token_data as it is also identical
 
140
            try:
 
141
                self.token_api.get_token(context=context,
 
142
                                         token_id=token_id)
 
143
            except exception.TokenNotFound:
 
144
                raise e
 
145
 
 
146
        token_data['access']['token']['id'] = token_id
 
147
 
 
148
        return token_data
 
149
 
 
150
    def _authenticate_token(self, context, auth):
 
151
        """Try to authenticate using an already existing token.
 
152
 
 
153
        Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
 
154
        """
 
155
        if 'token' not in auth:
 
156
            raise exception.ValidationError(
 
157
                attribute='token', target='auth')
 
158
 
 
159
        if "id" not in auth['token']:
 
160
            raise exception.ValidationError(
 
161
                attribute="id", target="token")
 
162
 
 
163
        old_token = auth['token']['id']
 
164
        if len(old_token) > CONF.max_token_size:
 
165
            raise exception.ValidationSizeError(attribute='token',
 
166
                                                size=CONF.max_token_size)
 
167
 
 
168
        try:
 
169
            old_token_ref = self.token_api.get_token(context=context,
 
170
                                                     token_id=old_token)
 
171
        except exception.NotFound as e:
 
172
            raise exception.Unauthorized(e)
 
173
 
 
174
        #A trust token cannot be used to get another token
 
175
        if 'trust' in old_token_ref:
 
176
            raise exception.Forbidden()
 
177
        if 'trust_id' in old_token_ref['metadata']:
 
178
            raise exception.Forbidden()
 
179
 
 
180
        user_ref = old_token_ref['user']
 
181
        user_id = user_ref['id']
 
182
        if not CONF.trust.enabled and 'trust_id' in auth:
 
183
            raise exception.Forbidden('Trusts are disabled.')
 
184
        elif CONF.trust.enabled and 'trust_id' in auth:
 
185
            trust_ref = self.trust_api.get_trust(context, auth['trust_id'])
 
186
            if trust_ref is None:
 
187
                raise exception.Forbidden()
 
188
            if user_id != trust_ref['trustee_user_id']:
 
189
                raise exception.Forbidden()
 
190
            if ('expires' in trust_ref) and (trust_ref['expires']):
 
191
                expiry = trust_ref['expires']
 
192
                if expiry < timeutils.parse_isotime(timeutils.isotime()):
 
193
                    raise exception.Forbidden()()
 
194
            user_id = trust_ref['trustor_user_id']
 
195
            trustor_user_ref = (self.identity_api.get_user(
 
196
                                context=context,
 
197
                                user_id=trust_ref['trustor_user_id']))
 
198
            if not trustor_user_ref['enabled']:
 
199
                raise exception.Forbidden()()
 
200
            trustee_user_ref = self.identity_api.get_user(
 
201
                context, trust_ref['trustee_user_id'])
 
202
            if not trustee_user_ref['enabled']:
 
203
                raise exception.Forbidden()()
 
204
            if trust_ref['impersonation'] == 'True':
 
205
                current_user_ref = trustor_user_ref
 
206
            else:
 
207
                current_user_ref = trustee_user_ref
 
208
 
 
209
        else:
 
210
            current_user_ref = self.identity_api.get_user(context=context,
 
211
                                                          user_id=user_id)
 
212
 
 
213
        tenant_id = self._get_project_id_from_auth(context, auth)
 
214
 
 
215
        tenant_ref = self._get_project_ref(context, user_id, tenant_id)
 
216
        metadata_ref = self._get_metadata_ref(context, user_id, tenant_id)
 
217
 
 
218
        # TODO (henry-nash) If no tenant was specified, instead check
 
219
        # for a domain and find any related user/group roles
 
220
 
 
221
        self._append_roles(metadata_ref,
 
222
                           self._get_group_metadata_ref(
 
223
                               context, user_id, tenant_id))
 
224
 
 
225
        expiry = old_token_ref['expires']
 
226
        if CONF.trust.enabled and 'trust_id' in auth:
 
227
            trust_id = auth['trust_id']
 
228
            trust_roles = []
 
229
            for role in trust_ref['roles']:
 
230
                if not 'roles' in metadata_ref:
 
231
                    raise exception.Forbidden()()
 
232
                if role['id'] in metadata_ref['roles']:
 
233
                    trust_roles.append(role['id'])
 
234
                else:
 
235
                    raise exception.Forbidden()
 
236
            if 'expiry' in trust_ref and trust_ref['expiry']:
 
237
                trust_expiry = timeutils.parse_isotime(trust_ref['expiry'])
 
238
                if trust_expiry < expiry:
 
239
                    expiry = trust_expiry
 
240
            metadata_ref['roles'] = trust_roles
 
241
            metadata_ref['trustee_user_id'] = trust_ref['trustee_user_id']
 
242
            metadata_ref['trust_id'] = trust_id
 
243
 
 
244
        auth_token_data = self._get_auth_token_data(current_user_ref,
 
245
                                                    tenant_ref,
 
246
                                                    metadata_ref,
 
247
                                                    expiry)
 
248
        return (current_user_ref, tenant_ref, metadata_ref, expiry)
 
249
 
 
250
    def _authenticate_local(self, context, auth):
 
251
        """Try to authenticate against the identity backend.
 
252
 
 
253
        Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
 
254
        """
 
255
        if 'passwordCredentials' not in auth:
 
256
            raise exception.ValidationError(
 
257
                attribute='passwordCredentials', target='auth')
 
258
 
 
259
        if "password" not in auth['passwordCredentials']:
 
260
            raise exception.ValidationError(
 
261
                attribute='password', target='passwordCredentials')
 
262
 
 
263
        password = auth['passwordCredentials']['password']
 
264
        max_pw_size = utils.MAX_PASSWORD_LENGTH
 
265
        if password and len(password) > max_pw_size:
 
266
            raise exception.ValidationSizeError(attribute='password',
 
267
                                                size=max_pw_size)
 
268
 
 
269
        if ("userId" not in auth['passwordCredentials'] and
 
270
                "username" not in auth['passwordCredentials']):
 
271
            raise exception.ValidationError(
 
272
                attribute='username or userId',
 
273
                target='passwordCredentials')
 
274
 
 
275
        user_id = auth['passwordCredentials'].get('userId', None)
 
276
        if user_id and len(user_id) > CONF.max_param_size:
 
277
            raise exception.ValidationSizeError(attribute='userId',
 
278
                                                size=CONF.max_param_size)
 
279
 
 
280
        username = auth['passwordCredentials'].get('username', '')
 
281
        if len(username) > CONF.max_param_size:
 
282
            raise exception.ValidationSizeError(attribute='username',
 
283
                                                size=CONF.max_param_size)
 
284
 
 
285
        if username:
 
286
            try:
 
287
                user_ref = self.identity_api.get_user_by_name(
 
288
                    context=context, user_name=username,
 
289
                    domain_id=DEFAULT_DOMAIN_ID)
 
290
                user_id = user_ref['id']
 
291
            except exception.UserNotFound as e:
 
292
                raise exception.Unauthorized(e)
 
293
 
 
294
        tenant_id = self._get_project_id_from_auth(context, auth)
 
295
 
 
296
        try:
 
297
            auth_info = self.identity_api.authenticate(
 
298
                context=context,
 
299
                user_id=user_id,
 
300
                password=password,
 
301
                tenant_id=tenant_id)
 
302
        except AssertionError as e:
 
303
            raise exception.Unauthorized(e)
 
304
        (user_ref, tenant_ref, metadata_ref) = auth_info
 
305
 
 
306
        # By now we will have authorized and if a tenant/project was
 
307
        # specified, we will have obtained its metadata.  In this case
 
308
        # we just need to add in any group roles.
 
309
        #
 
310
        # TODO (henry-nash) If no tenant was specified, instead check
 
311
        # for a domain and find any related user/group roles
 
312
 
 
313
        self._append_roles(metadata_ref,
 
314
                           self._get_group_metadata_ref(
 
315
                               context, user_id, tenant_id))
 
316
 
 
317
        expiry = core.default_expire_time()
 
318
        return (user_ref, tenant_ref, metadata_ref, expiry)
 
319
 
 
320
    def _authenticate_external(self, context, auth):
 
321
        """Try to authenticate an external user via REMOTE_USER variable.
 
322
 
 
323
        Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
 
324
        """
 
325
        if 'REMOTE_USER' not in context:
 
326
            raise ExternalAuthNotApplicable()
 
327
 
 
328
        username = context['REMOTE_USER']
 
329
        try:
 
330
            user_ref = self.identity_api.get_user_by_name(
 
331
                context=context, user_name=username,
 
332
                domain_id=DEFAULT_DOMAIN_ID)
 
333
            user_id = user_ref['id']
 
334
        except exception.UserNotFound as e:
 
335
            raise exception.Unauthorized(e)
 
336
 
 
337
        tenant_id = self._get_project_id_from_auth(context, auth)
 
338
 
 
339
        tenant_ref = self._get_project_ref(context, user_id, tenant_id)
 
340
        metadata_ref = self._get_metadata_ref(context, user_id, tenant_id)
 
341
 
 
342
        # TODO (henry-nash) If no tenant was specified, instead check
 
343
        # for a domain and find any related user/group roles
 
344
 
 
345
        self._append_roles(metadata_ref,
 
346
                           self._get_group_metadata_ref(
 
347
                               context, user_id, tenant_id))
 
348
 
 
349
        expiry = core.default_expire_time()
 
350
        return (user_ref, tenant_ref, metadata_ref, expiry)
 
351
 
 
352
    def _get_auth_token_data(self, user, tenant, metadata, expiry):
 
353
        return dict(dict(user=user,
 
354
                         tenant=tenant,
 
355
                         metadata=metadata,
 
356
                         expires=expiry))
 
357
 
 
358
    def _get_project_id_from_auth(self, context, auth):
 
359
        """Extract tenant information from auth dict.
 
360
 
 
361
        Returns a valid tenant_id if it exists, or None if not specified.
 
362
        """
 
363
        tenant_id = auth.get('tenantId', None)
 
364
        if tenant_id and len(tenant_id) > CONF.max_param_size:
 
365
            raise exception.ValidationSizeError(attribute='tenantId',
 
366
                                                size=CONF.max_param_size)
 
367
 
 
368
        tenant_name = auth.get('tenantName', None)
 
369
        if tenant_name and len(tenant_name) > CONF.max_param_size:
 
370
            raise exception.ValidationSizeError(attribute='tenantName',
 
371
                                                size=CONF.max_param_size)
 
372
 
 
373
        if tenant_name:
 
374
            try:
 
375
                tenant_ref = self.identity_api.get_project_by_name(
 
376
                    context=context, tenant_name=tenant_name,
 
377
                    domain_id=DEFAULT_DOMAIN_ID)
 
378
                tenant_id = tenant_ref['id']
 
379
            except exception.ProjectNotFound as e:
 
380
                raise exception.Unauthorized(e)
 
381
        return tenant_id
 
382
 
 
383
    def _get_domain_id_from_auth(self, context, auth):
 
384
        """Extract domain information from v3 auth dict.
 
385
 
 
386
        Returns a valid domain_id if it exists, or None if not specified.
 
387
        """
 
388
        # FIXME(henry-nash): This is a placeholder that needs to be
 
389
        # only called in the v3 context, and the auth.get calls
 
390
        # converted to the v3 format
 
391
        domain_id = auth.get('domainId', None)
 
392
        domain_name = auth.get('domainName', None)
 
393
        if domain_name:
 
394
            try:
 
395
                domain_ref = self.identity_api._get_domain_by_name(
 
396
                    context=context, domain_name=domain_name)
 
397
                domain_id = domain_ref['id']
 
398
            except exception.DomainNotFound as e:
 
399
                raise exception.Unauthorized(e)
 
400
        return domain_id
 
401
 
 
402
    def _get_project_ref(self, context, user_id, tenant_id):
 
403
        """Returns the tenant_ref for the user's tenant"""
 
404
        tenant_ref = None
 
405
        if tenant_id:
 
406
            tenants = self.identity_api.get_projects_for_user(context, user_id)
 
407
            if tenant_id not in tenants:
 
408
                msg = 'User %s is unauthorized for tenant %s' % (
 
409
                    user_id, tenant_id)
 
410
                LOG.warning(msg)
 
411
                raise exception.Unauthorized(msg)
 
412
 
 
413
            try:
 
414
                tenant_ref = self.identity_api.get_project(context=context,
 
415
                                                           tenant_id=tenant_id)
 
416
            except exception.ProjectNotFound as e:
 
417
                exception.Unauthorized(e)
 
418
        return tenant_ref
 
419
 
 
420
    def _get_metadata_ref(self, context, user_id=None, tenant_id=None,
 
421
                          domain_id=None, group_id=None):
 
422
        """Returns metadata_ref for a user or group in a tenant or domain"""
 
423
 
 
424
        metadata_ref = {}
 
425
        if (user_id or group_id) and (tenant_id or domain_id):
 
426
            try:
 
427
                metadata_ref = self.identity_api.get_metadata(
 
428
                    context=context, user_id=user_id, tenant_id=tenant_id,
 
429
                    domain_id=domain_id, group_id=group_id)
 
430
            except exception.MetadataNotFound:
 
431
                pass
 
432
        return metadata_ref
 
433
 
 
434
    def _get_group_metadata_ref(self, context, user_id,
 
435
                                tenant_id=None, domain_id=None):
 
436
        """Return any metadata for this project/domain due to group grants"""
 
437
        group_refs = self.identity_api.list_groups_for_user(context=context,
 
438
                                                            user_id=user_id)
 
439
        metadata_ref = {}
 
440
        for x in group_refs:
 
441
            metadata_ref.update(self._get_metadata_ref(context,
 
442
                                                       group_id=x['id'],
 
443
                                                       tenant_id=tenant_id,
 
444
                                                       domain_id=domain_id))
 
445
        return metadata_ref
 
446
 
 
447
    def _append_roles(self, metadata, additional_metadata):
 
448
        """
 
449
        Update the roles in metadata to be the union of the roles from
 
450
        both of the passed metadatas
 
451
        """
 
452
 
 
453
        first = set(metadata.get('roles', []))
 
454
        second = set(additional_metadata.get('roles', []))
 
455
        metadata['roles'] = list(first.union(second))
 
456
 
 
457
    def _get_token_ref(self, context, token_id, belongs_to=None):
 
458
        """Returns a token if a valid one exists.
 
459
 
 
460
        Optionally, limited to a token owned by a specific tenant.
 
461
 
 
462
        """
 
463
        # TODO(termie): this stuff should probably be moved to middleware
 
464
        self.assert_admin(context)
 
465
        data = self.token_api.get_token(context=context,
 
466
                                        token_id=token_id)
 
467
        if belongs_to:
 
468
            if data.get('tenant') is None:
 
469
                raise exception.Unauthorized(
 
470
                    _('Token does not belong to specified tenant.'))
 
471
            if data['tenant'].get('id') != belongs_to:
 
472
                raise exception.Unauthorized(
 
473
                    _('Token does not belong to specified tenant.'))
 
474
        return data
 
475
 
 
476
    def _assert_default_domain(self, context, token_ref):
 
477
        """ Make sure we are operating on default domain only. """
 
478
        if token_ref.get('token_data'):
 
479
            # this is a V3 token
 
480
            msg = _('Non-default domain is not supported')
 
481
            # user in a non-default is prohibited
 
482
            if (token_ref['token_data']['token']['user']['domain']['id'] !=
 
483
                    DEFAULT_DOMAIN_ID):
 
484
                raise exception.Unauthorized(msg)
 
485
            # domain scoping is prohibited
 
486
            if token_ref['token_data']['token'].get('domain'):
 
487
                raise exception.Unauthorized(
 
488
                    _('Domain scoped token is not supported'))
 
489
            # project in non-default domain is prohibited
 
490
            if token_ref['token_data']['token'].get('project'):
 
491
                project = token_ref['token_data']['token']['project']
 
492
                project_domain_id = project['domain']['id']
 
493
                # scoped to project in non-default domain is prohibited
 
494
                if project_domain_id != DEFAULT_DOMAIN_ID:
 
495
                    raise exception.Unauthorized(msg)
 
496
            # if token is scoped to trust, both trustor and trustee must
 
497
            # be in the default domain. Furthermore, the delegated project
 
498
            # must also be in the default domain
 
499
            metadata_ref = token_ref['metadata']
 
500
            if CONF.trust.enabled and 'trust_id' in metadata_ref:
 
501
                trust_ref = self.trust_api.get_trust(context,
 
502
                                                     metadata_ref['trust_id'])
 
503
                trustee_user_ref = self.identity_api.get_user(
 
504
                    context, trust_ref['trustee_user_id'])
 
505
                if trustee_user_ref['domain_id'] != DEFAULT_DOMAIN_ID:
 
506
                    raise exception.Unauthorized(msg)
 
507
                trustor_user_ref = self.identity_api.get_user(
 
508
                    context, trust_ref['trustor_user_id'])
 
509
                if trustor_user_ref['domain_id'] != DEFAULT_DOMAIN_ID:
 
510
                    raise exception.Unauthorized(msg)
 
511
                project_ref = self.identity_api.get_project(
 
512
                    context, trust_ref['project_id'])
 
513
                if project_ref['domain_id'] != DEFAULT_DOMAIN_ID:
 
514
                    raise exception.Unauthorized(msg)
 
515
 
 
516
    # admin only
 
517
    def validate_token_head(self, context, token_id):
 
518
        """Check that a token is valid.
 
519
 
 
520
        Optionally, also ensure that it is owned by a specific tenant.
 
521
 
 
522
        Identical to ``validate_token``, except does not return a response.
 
523
 
 
524
        """
 
525
        belongs_to = context['query_string'].get('belongsTo')
 
526
        token_ref = self._get_token_ref(context, token_id, belongs_to)
 
527
        assert token_ref
 
528
        self._assert_default_domain(context, token_ref)
 
529
 
 
530
    # admin only
 
531
    def validate_token(self, context, token_id):
 
532
        """Check that a token is valid.
 
533
 
 
534
        Optionally, also ensure that it is owned by a specific tenant.
 
535
 
 
536
        Returns metadata about the token along any associated roles.
 
537
 
 
538
        """
 
539
        belongs_to = context['query_string'].get('belongsTo')
 
540
        token_ref = self._get_token_ref(context, token_id, belongs_to)
 
541
        self._assert_default_domain(context, token_ref)
 
542
 
 
543
        # TODO(termie): optimize this call at some point and put it into the
 
544
        #               the return for metadata
 
545
        # fill out the roles in the metadata
 
546
        metadata_ref = token_ref['metadata']
 
547
        roles_ref = []
 
548
        for role_id in metadata_ref.get('roles', []):
 
549
            roles_ref.append(self.identity_api.get_role(context, role_id))
 
550
 
 
551
        # Get a service catalog if possible
 
552
        # This is needed for on-behalf-of requests
 
553
        catalog_ref = None
 
554
        if token_ref.get('tenant'):
 
555
            catalog_ref = self.catalog_api.get_catalog(
 
556
                context=context,
 
557
                user_id=token_ref['user']['id'],
 
558
                tenant_id=token_ref['tenant']['id'],
 
559
                metadata=metadata_ref)
 
560
        return Auth.format_token(token_ref, roles_ref, catalog_ref)
 
561
 
 
562
    def delete_token(self, context, token_id):
 
563
        """Delete a token, effectively invalidating it for authz."""
 
564
        # TODO(termie): this stuff should probably be moved to middleware
 
565
        self.assert_admin(context)
 
566
        self.token_api.delete_token(context=context, token_id=token_id)
 
567
 
 
568
    def revocation_list(self, context, auth=None):
 
569
        self.assert_admin(context)
 
570
        tokens = self.token_api.list_revoked_tokens(context)
 
571
 
 
572
        for t in tokens:
 
573
            expires = t['expires']
 
574
            if not (expires and isinstance(expires, unicode)):
 
575
                    t['expires'] = timeutils.isotime(expires)
 
576
        data = {'revoked': tokens}
 
577
        json_data = json.dumps(data)
 
578
        signed_text = cms.cms_sign_text(json_data,
 
579
                                        CONF.signing.certfile,
 
580
                                        CONF.signing.keyfile)
 
581
 
 
582
        return {'signed': signed_text}
 
583
 
 
584
    def endpoints(self, context, token_id):
 
585
        """Return a list of endpoints available to the token."""
 
586
        self.assert_admin(context)
 
587
 
 
588
        token_ref = self._get_token_ref(context, token_id)
 
589
 
 
590
        catalog_ref = None
 
591
        if token_ref.get('tenant'):
 
592
            catalog_ref = self.catalog_api.get_catalog(
 
593
                context=context,
 
594
                user_id=token_ref['user']['id'],
 
595
                tenant_id=token_ref['tenant']['id'],
 
596
                metadata=token_ref['metadata'])
 
597
 
 
598
        return Auth.format_endpoint_list(catalog_ref)
 
599
 
 
600
    @classmethod
 
601
    def format_authenticate(cls, token_ref, roles_ref, catalog_ref):
 
602
        o = Auth.format_token(token_ref, roles_ref)
 
603
        o['access']['serviceCatalog'] = Auth.format_catalog(catalog_ref)
 
604
        return o
 
605
 
 
606
    @classmethod
 
607
    def format_token(cls, token_ref, roles_ref, catalog_ref=None):
 
608
        user_ref = token_ref['user']
 
609
        metadata_ref = token_ref['metadata']
 
610
        expires = token_ref['expires']
 
611
        if expires is not None:
 
612
            if not isinstance(expires, unicode):
 
613
                expires = timeutils.isotime(expires)
 
614
        o = {'access': {'token': {'id': token_ref['id'],
 
615
                                  'expires': expires,
 
616
                                  'issued_at': timeutils.strtime()
 
617
                                  },
 
618
                        'user': {'id': user_ref['id'],
 
619
                                 'name': user_ref['name'],
 
620
                                 'username': user_ref['name'],
 
621
                                 'roles': roles_ref,
 
622
                                 'roles_links': metadata_ref.get('roles_links',
 
623
                                                                 [])
 
624
                                 }
 
625
                        }
 
626
             }
 
627
        if 'tenant' in token_ref and token_ref['tenant']:
 
628
            token_ref['tenant']['enabled'] = True
 
629
            o['access']['token']['tenant'] = token_ref['tenant']
 
630
        if catalog_ref is not None:
 
631
            o['access']['serviceCatalog'] = Auth.format_catalog(catalog_ref)
 
632
        if metadata_ref:
 
633
            if 'is_admin' in metadata_ref:
 
634
                o['access']['metadata'] = {'is_admin':
 
635
                                           metadata_ref['is_admin']}
 
636
            else:
 
637
                o['access']['metadata'] = {'is_admin': 0}
 
638
        if 'roles' in metadata_ref:
 
639
            o['access']['metadata']['roles'] = metadata_ref['roles']
 
640
        if CONF.trust.enabled and 'trust_id' in metadata_ref:
 
641
            o['access']['trust'] = {'trustee_user_id':
 
642
                                    metadata_ref['trustee_user_id'],
 
643
                                    'id': metadata_ref['trust_id']
 
644
                                    }
 
645
        return o
 
646
 
 
647
    @classmethod
 
648
    def format_catalog(cls, catalog_ref):
 
649
        """Munge catalogs from internal to output format
 
650
        Internal catalogs look like:
 
651
 
 
652
        {$REGION: {
 
653
            {$SERVICE: {
 
654
                $key1: $value1,
 
655
                ...
 
656
                }
 
657
            }
 
658
        }
 
659
 
 
660
        The legacy api wants them to look like
 
661
 
 
662
        [{'name': $SERVICE[name],
 
663
          'type': $SERVICE,
 
664
          'endpoints': [{
 
665
              'tenantId': $tenant_id,
 
666
              ...
 
667
              'region': $REGION,
 
668
              }],
 
669
          'endpoints_links': [],
 
670
         }]
 
671
 
 
672
        """
 
673
        if not catalog_ref:
 
674
            return []
 
675
 
 
676
        services = {}
 
677
        for region, region_ref in catalog_ref.iteritems():
 
678
            for service, service_ref in region_ref.iteritems():
 
679
                new_service_ref = services.get(service, {})
 
680
                new_service_ref['name'] = service_ref.pop('name')
 
681
                new_service_ref['type'] = service
 
682
                new_service_ref['endpoints_links'] = []
 
683
                service_ref['region'] = region
 
684
 
 
685
                endpoints_ref = new_service_ref.get('endpoints', [])
 
686
                endpoints_ref.append(service_ref)
 
687
 
 
688
                new_service_ref['endpoints'] = endpoints_ref
 
689
                services[service] = new_service_ref
 
690
 
 
691
        return services.values()
 
692
 
 
693
    @classmethod
 
694
    def format_endpoint_list(cls, catalog_ref):
 
695
        """Formats a list of endpoints according to Identity API v2.
 
696
 
 
697
        The v2.0 API wants an endpoint list to look like::
 
698
 
 
699
            {
 
700
                'endpoints': [
 
701
                    {
 
702
                        'id': $endpoint_id,
 
703
                        'name': $SERVICE[name],
 
704
                        'type': $SERVICE,
 
705
                        'tenantId': $tenant_id,
 
706
                        'region': $REGION,
 
707
                    }
 
708
                ],
 
709
                'endpoints_links': [],
 
710
            }
 
711
 
 
712
        """
 
713
        if not catalog_ref:
 
714
            return {}
 
715
 
 
716
        endpoints = []
 
717
        for region_name, region_ref in catalog_ref.iteritems():
 
718
            for service_type, service_ref in region_ref.iteritems():
 
719
                endpoints.append({
 
720
                    'id': service_ref.get('id'),
 
721
                    'name': service_ref.get('name'),
 
722
                    'type': service_type,
 
723
                    'region': region_name,
 
724
                    'publicURL': service_ref.get('publicURL'),
 
725
                    'internalURL': service_ref.get('internalURL'),
 
726
                    'adminURL': service_ref.get('adminURL'),
 
727
                })
 
728
 
 
729
        return {'endpoints': endpoints, 'endpoints_links': []}