Enhance V3 extensions to provide JSON Home data

The V3 extensions are enhanced to provide JSON Home data for each
of the resources that they provide.

bp json-home

Change-Id: I6466cd583b22b260f8979717fa6ceafcbf252839
This commit is contained in:
Brant Knudson 2014-08-02 22:25:20 -05:00 committed by Dolph Mathews
parent eb03a553b7
commit 9077fdfe11
15 changed files with 295 additions and 30 deletions

View File

@ -41,6 +41,7 @@ class Parameters(object):
"""Relationships for Common parameters."""
DOMAIN_ID = build_v3_parameter_relation('domain_id')
ENDPOINT_ID = build_v3_parameter_relation('endpoint_id')
GROUP_ID = build_v3_parameter_relation('group_id')
PROJECT_ID = build_v3_parameter_relation('project_id')
ROLE_ID = build_v3_parameter_relation('role_id')

View File

@ -12,10 +12,22 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
from keystone.common import json_home
from keystone.common import wsgi
from keystone.contrib.ec2 import controllers
build_resource_relation = functools.partial(
json_home.build_v3_extension_resource_relation, extension_name='OS-EC2',
extension_version='1.0')
build_parameter_relation = functools.partial(
json_home.build_v3_extension_parameter_relation, extension_name='OS-EC2',
extension_version='1.0')
class Ec2Extension(wsgi.ExtensionRouter):
def add_routes(self, mapper):
ec2_controller = controllers.Ec2Controller()
@ -57,16 +69,27 @@ class Ec2ExtensionV3(wsgi.V3ExtensionRouter):
self._add_resource(
mapper, ec2_controller,
path='/ec2tokens',
post_action='authenticate')
post_action='authenticate',
rel=build_resource_relation(resource_name='ec2tokens'))
# crud
self._add_resource(
mapper, ec2_controller,
path='/users/{user_id}/credentials/OS-EC2',
get_action='ec2_list_credentials',
post_action='ec2_create_credential')
post_action='ec2_create_credential',
rel=build_resource_relation(resource_name='user_credentials'),
path_vars={
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, ec2_controller,
path='/users/{user_id}/credentials/OS-EC2/{credential_id}',
get_action='ec2_get_credential',
delete_action='ec2_delete_credential')
delete_action='ec2_delete_credential',
rel=build_resource_relation(resource_name='user_credential'),
path_vars={
'credential_id':
build_parameter_relation(parameter_name='credential_id'),
'user_id': json_home.Parameters.USER_ID,
})

View File

@ -12,10 +12,18 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
from keystone.common import json_home
from keystone.common import wsgi
from keystone.contrib.endpoint_filter import controllers
build_resource_relation = functools.partial(
json_home.build_v3_extension_resource_relation,
extension_name='OS-EP-FILTER', extension_version='1.0')
class EndpointFilterExtension(wsgi.V3ExtensionRouter):
PATH_PREFIX = '/OS-EP-FILTER'
@ -27,14 +35,27 @@ class EndpointFilterExtension(wsgi.V3ExtensionRouter):
self._add_resource(
mapper, endpoint_filter_controller,
path=self.PATH_PREFIX + '/endpoints/{endpoint_id}/projects',
get_action='list_projects_for_endpoint')
get_action='list_projects_for_endpoint',
rel=build_resource_relation(resource_name='endpoint_projects'),
path_vars={
'endpoint_id': json_home.Parameters.ENDPOINT_ID,
})
self._add_resource(
mapper, endpoint_filter_controller,
path=self.PATH_PREFIX + self.PATH_PROJECT_ENDPOINT,
get_head_action='check_endpoint_in_project',
put_action='add_endpoint_to_project',
delete_action='remove_endpoint_from_project')
delete_action='remove_endpoint_from_project',
rel=build_resource_relation(resource_name='project_endpoint'),
path_vars={
'endpoint_id': json_home.Parameters.ENDPOINT_ID,
'project_id': json_home.Parameters.PROJECT_ID,
})
self._add_resource(
mapper, endpoint_filter_controller,
path=self.PATH_PREFIX + '/projects/{project_id}/endpoints',
get_action='list_endpoints_for_project')
get_action='list_endpoints_for_project',
rel=build_resource_relation(resource_name='project_endpoints'),
path_vars={
'project_id': json_home.Parameters.PROJECT_ID,
})

View File

@ -12,10 +12,18 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
from keystone.common import json_home
from keystone.common import wsgi
from keystone.contrib.example import controllers
build_resource_relation = functools.partial(
json_home.build_v3_extension_resource_relation,
extension_name='OS-EXAMPLE', extension_version='1.0')
class ExampleRouter(wsgi.V3ExtensionRouter):
PATH_PREFIX = '/OS-EXAMPLE'
@ -26,4 +34,5 @@ class ExampleRouter(wsgi.V3ExtensionRouter):
self._add_resource(
mapper, example_controller,
path=self.PATH_PREFIX + '/example',
get_action='do_something')
get_action='do_something',
rel=build_resource_relation(resource_name='example'))

View File

@ -10,11 +10,27 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
from keystone.common import json_home
from keystone.common import wsgi
from keystone.contrib import federation
from keystone.contrib.federation import controllers
build_resource_relation = functools.partial(
json_home.build_v3_extension_resource_relation,
extension_name='OS-FEDERATION', extension_version='1.0')
build_parameter_relation = functools.partial(
json_home.build_v3_extension_parameter_relation,
extension_name='OS-FEDERATION', extension_version='1.0')
IDP_ID_PARAMETER_RELATION = build_parameter_relation(parameter_name='idp_id')
PROTOCOL_ID_PARAMETER_RELATION = build_parameter_relation(
parameter_name='protocol_id')
class FederationExtension(wsgi.V3ExtensionRouter):
"""API Endpoints for the Federation extension.
@ -74,11 +90,16 @@ class FederationExtension(wsgi.V3ExtensionRouter):
get_action='get_identity_provider',
put_action='create_identity_provider',
patch_action='update_identity_provider',
delete_action='delete_identity_provider')
delete_action='delete_identity_provider',
rel=build_resource_relation(resource_name='identity_provider'),
path_vars={
'idp_id': IDP_ID_PARAMETER_RELATION,
})
self._add_resource(
mapper, idp_controller,
path=self._construct_url('identity_providers'),
get_action='list_identity_providers')
get_action='list_identity_providers',
rel=build_resource_relation(resource_name='identity_providers'))
# Protocol CRUD operations
@ -89,11 +110,22 @@ class FederationExtension(wsgi.V3ExtensionRouter):
get_action='get_protocol',
put_action='create_protocol',
patch_action='update_protocol',
delete_action='delete_protocol')
delete_action='delete_protocol',
rel=build_resource_relation(
resource_name='identity_provider_protocol'),
path_vars={
'idp_id': IDP_ID_PARAMETER_RELATION,
'protocol_id': PROTOCOL_ID_PARAMETER_RELATION,
})
self._add_resource(
mapper, protocol_controller,
path=self._construct_url('identity_providers/{idp_id}/protocols'),
get_action='list_protocols')
get_action='list_protocols',
rel=build_resource_relation(
resource_name='identity_provider_protocols'),
path_vars={
'idp_id': IDP_ID_PARAMETER_RELATION,
})
# Mapping CRUD operations
@ -103,21 +135,35 @@ class FederationExtension(wsgi.V3ExtensionRouter):
get_action='get_mapping',
put_action='create_mapping',
patch_action='update_mapping',
delete_action='delete_mapping')
delete_action='delete_mapping',
rel=build_resource_relation(resource_name='mapping'),
path_vars={
'mapping_id': build_parameter_relation(
parameter_name='mapping_id'),
})
self._add_resource(
mapper, mapping_controller,
path=self._construct_url('mappings'),
get_action='list_mappings')
get_action='list_mappings',
rel=build_resource_relation(resource_name='mappings'))
self._add_resource(
mapper, domain_controller,
path=self._construct_url('domains'),
get_action='list_domains_for_groups')
get_action='list_domains_for_groups',
rel=build_resource_relation(resource_name='domains'))
self._add_resource(
mapper, project_controller,
path=self._construct_url('projects'),
get_action='list_projects_for_groups')
get_action='list_projects_for_groups',
rel=build_resource_relation(resource_name='projects'))
self._add_resource(
mapper, auth_controller,
path=self._construct_url('identity_providers/{identity_provider}/'
'protocols/{protocol}/auth'),
get_post_action='federated_authentication')
get_post_action='federated_authentication',
rel=build_resource_relation(
resource_name='identity_provider_protocol_auth'),
path_vars={
'identity_provider': IDP_ID_PARAMETER_RELATION,
'protocol': PROTOCOL_ID_PARAMETER_RELATION,
})

View File

@ -12,11 +12,26 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
from keystone.common import json_home
from keystone.common import wsgi
from keystone.contrib import oauth1
from keystone.contrib.oauth1 import controllers
build_resource_relation = functools.partial(
json_home.build_v3_extension_resource_relation,
extension_name='OS-OAUTH1', extension_version='1.0')
build_parameter_relation = functools.partial(
json_home.build_v3_extension_parameter_relation,
extension_name='OS-OAUTH1', extension_version='1.0')
ACCESS_TOKEN_ID_PARAMETER_RELATION = build_parameter_relation(
parameter_name='access_token_id')
class OAuth1Extension(wsgi.V3ExtensionRouter):
"""API Endpoints for the OAuth1 extension.
@ -63,45 +78,81 @@ class OAuth1Extension(wsgi.V3ExtensionRouter):
mapper, consumer_controller,
path='/OS-OAUTH1/consumers',
get_action='list_consumers',
post_action='create_consumer')
post_action='create_consumer',
rel=build_resource_relation(resource_name='consumers'))
self._add_resource(
mapper, consumer_controller,
path='/OS-OAUTH1/consumers/{consumer_id}',
get_action='get_consumer',
patch_action='update_consumer',
delete_action='delete_consumer')
delete_action='delete_consumer',
rel=build_resource_relation(resource_name='consumer'),
path_vars={
'consumer_id':
build_parameter_relation(parameter_name='consumer_id'),
})
# user accesss token crud
self._add_resource(
mapper, access_token_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens',
get_action='list_access_tokens')
get_action='list_access_tokens',
rel=build_resource_relation(resource_name='user_access_tokens'),
path_vars={
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, access_token_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}',
get_action='get_access_token',
delete_action='delete_access_token')
delete_action='delete_access_token',
rel=build_resource_relation(resource_name='user_access_token'),
path_vars={
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, access_token_roles_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/'
'roles',
get_action='list_access_token_roles')
get_action='list_access_token_roles',
rel=build_resource_relation(
resource_name='user_access_token_roles'),
path_vars={
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, access_token_roles_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/'
'roles/{role_id}',
get_action='get_access_token_role')
get_action='get_access_token_role',
rel=build_resource_relation(
resource_name='user_access_token_role'),
path_vars={
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
'role_id': json_home.Parameters.ROLE_ID,
'user_id': json_home.Parameters.USER_ID,
})
# oauth flow calls
self._add_resource(
mapper, oauth_controller,
path='/OS-OAUTH1/request_token',
post_action='create_request_token')
post_action='create_request_token',
rel=build_resource_relation(resource_name='request_tokens'))
self._add_resource(
mapper, oauth_controller,
path='/OS-OAUTH1/access_token',
post_action='create_access_token')
post_action='create_access_token',
rel=build_resource_relation(resource_name='access_tokens'))
self._add_resource(
mapper, oauth_controller,
path='/OS-OAUTH1/authorize/{request_token_id}',
put_action='authorize_request_token')
path_vars={
'request_token_id':
build_parameter_relation(parameter_name='request_token_id')
},
put_action='authorize_request_token',
rel=build_resource_relation(
resource_name='authorize_request_token'))

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystone.common import json_home
from keystone.common import wsgi
from keystone.contrib.revoke import controllers
@ -23,4 +24,6 @@ class RevokeExtension(wsgi.V3ExtensionRouter):
self._add_resource(
mapper, revoke_controller,
path=self.PATH_PREFIX + '/events',
get_action='list_revoke_events')
get_action='list_revoke_events',
rel=json_home.build_v3_extension_resource_relation(
'OS-REVOKE', '1.0', 'events'))

View File

@ -26,6 +26,7 @@ import hashlib
import hmac
from keystone.common import extension
from keystone.common import json_home
from keystone.common import utils
from keystone.common import wsgi
from keystone.contrib.ec2 import controllers
@ -56,7 +57,9 @@ class S3Extension(wsgi.V3ExtensionRouter):
self._add_resource(
mapper, controller,
path='/s3tokens',
post_action='authenticate')
post_action='authenticate',
rel=json_home.build_v3_extension_resource_relation(
's3tokens', '1.0', 's3tokens'))
class S3Controller(controllers.Ec2Controller):

View File

@ -10,10 +10,18 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
from keystone.common import json_home
from keystone.common import wsgi
from keystone.contrib.simple_cert import controllers
build_resource_relation = functools.partial(
json_home.build_v3_extension_resource_relation,
extension_name='OS-SIMPLE-CERT', extension_version='1.0')
class SimpleCertExtension(wsgi.V3ExtensionRouter):
PREFIX = 'OS-SIMPLE-CERT'
@ -24,8 +32,10 @@ class SimpleCertExtension(wsgi.V3ExtensionRouter):
self._add_resource(
mapper, controller,
path='/%s/ca' % self.PREFIX,
get_action='get_ca_certificate')
get_action='get_ca_certificate',
rel=build_resource_relation(resource_name='ca_certificate'))
self._add_resource(
mapper, controller,
path='/%s/certificates' % self.PREFIX,
get_action='list_certificates')
get_action='list_certificates',
rel=build_resource_relation(resource_name='certificates'))

View File

@ -537,3 +537,17 @@ class EndpointFilterTokenRequestTestCase(TestExtensionCase):
endpoints = r.result['token']['catalog'][0]['endpoints']
endpoint_ids = [ep['id'] for ep in endpoints]
self.assertEqual([self.endpoint_id], endpoint_ids)
class JsonHomeTests(TestExtensionCase, test_v3.JsonHomeTestMixin):
JSON_HOME_DATA = {
'http://docs.openstack.org/api/openstack-identity/3/ext/OS-EP-FILTER/'
'1.0/rel/endpoint_projects': {
'href-template': '/OS-EP-FILTER/endpoints/{endpoint_id}/projects',
'href-vars': {
'endpoint_id':
'http://docs.openstack.org/api/openstack-identity/3/param/'
'endpoint_id',
},
},
}

View File

@ -27,6 +27,7 @@ from keystone.common import serializer
from keystone import config
from keystone import exception
from keystone import middleware
from keystone.openstack.common import jsonutils
from keystone.policy.backends import rules
from keystone import tests
from keystone.tests.ksfixtures import database
@ -1246,3 +1247,27 @@ class AuthContextMiddlewareTestCase(RestfulTestCase):
middleware.AuthContextMiddleware(application).process_request(req)
self.assertDictEqual(req.environ.get(authorization.AUTH_CONTEXT_ENV),
{})
class JsonHomeTestMixin(object):
"""JSON Home test
Mixin this class to provide a test for the JSON-Home response for an
extension.
The base class must set JSON_HOME_DATA to a dict of relationship URLs
(rels) to the JSON-Home data for the relationship. The rels and associated
data must be in the response.
"""
def test_get_json_home(self):
resp = self.get('/', convert=False,
headers={'Accept': 'application/json-home'})
self.assertThat(resp.headers['Content-Type'],
matchers.Equals('application/json-home'))
resp_data = jsonutils.loads(resp.body)
# Check that the example relationships are present.
for rel in self.JSON_HOME_DATA:
self.assertThat(resp_data['resources'][rel],
matchers.Equals(self.JSON_HOME_DATA[rel]))

View File

@ -1571,3 +1571,16 @@ class FederatedTokenTests(FederationTests):
assertion = getattr(mapping_fixtures, variant)
context['environment'].update(assertion)
context['query_string'] = []
class JsonHomeTests(FederationTests, test_v3.JsonHomeTestMixin):
JSON_HOME_DATA = {
'http://docs.openstack.org/api/openstack-identity/3/ext/OS-FEDERATION/'
'1.0/rel/identity_provider': {
'href-template': '/OS-FEDERATION/identity_providers/{idp_id}',
'href-vars': {
'idp_id': 'http://docs.openstack.org/api/openstack-identity/3/'
'ext/OS-FEDERATION/1.0/param/idp_id'
},
},
}

View File

@ -711,3 +711,12 @@ class MaliciousOAuth1Tests(OAuth1Tests):
url, headers, body = self._get_oauth_token(self.consumer,
self.access_token)
self.post(url, headers=headers, body=body, expected_status=401)
class JsonHomeTests(OAuth1Tests, test_v3.JsonHomeTestMixin):
JSON_HOME_DATA = {
'http://docs.openstack.org/api/openstack-identity/3/ext/OS-OAUTH1/1.0/'
'rel/consumers': {
'href': '/OS-OAUTH1/consumers',
},
}

View File

@ -29,10 +29,17 @@ def _future_time_string():
@dependency.requires('revoke_api')
class OSRevokeTests(test_v3.RestfulTestCase):
class OSRevokeTests(test_v3.RestfulTestCase, test_v3.JsonHomeTestMixin):
EXTENSION_NAME = 'revoke'
EXTENSION_TO_ADD = 'revoke_extension'
JSON_HOME_DATA = {
'http://docs.openstack.org/api/openstack-identity/3/ext/OS-REVOKE/1.0/'
'rel/events': {
'href': '/OS-REVOKE/events',
},
}
def test_get_empty_list(self):
resp = self.get('/OS-REVOKE/events')
self.assertEqual([], resp.json_body['events'])

View File

@ -104,9 +104,17 @@ VERSIONS_RESPONSE = {
}
}
_build_ec2tokens_relation = functools.partial(
json_home.build_v3_extension_resource_relation, extension_name='OS-EC2',
extension_version='1.0')
REVOCATIONS_RELATION = json_home.build_v3_extension_resource_relation(
'OS-PKI', '1.0', 'revocations')
_build_simple_cert_relation = functools.partial(
json_home.build_v3_extension_resource_relation,
extension_name='OS-SIMPLE-CERT', extension_version='1.0')
_build_trust_relation = functools.partial(
json_home.build_v3_extension_resource_relation, extension_name='OS-TRUST',
extension_version='1.0')
@ -165,8 +173,27 @@ V3_JSON_HOME_RESOURCES_INHERIT_DISABLED = {
json_home.build_v3_parameter_relation('endpoint_id'), }},
json_home.build_v3_resource_relation('endpoints'): {
'href': '/endpoints'},
_build_ec2tokens_relation(resource_name='ec2tokens'): {
'href': '/ec2tokens'},
_build_ec2tokens_relation(resource_name='user_credential'): {
'href-template': '/users/{user_id}/credentials/OS-EC2/{credential_id}',
'href-vars': {
'credential_id': json_home.build_v3_extension_parameter_relation(
'OS-EC2', '1.0', 'credential_id'),
'user_id': json_home.Parameters.USER_ID, }},
_build_ec2tokens_relation(resource_name='user_credentials'): {
'href-template': '/users/{user_id}/credentials/OS-EC2',
'href-vars': {
'user_id': json_home.Parameters.USER_ID, }},
REVOCATIONS_RELATION: {
'href': '/auth/tokens/OS-PKI/revoked'},
'http://docs.openstack.org/api/openstack-identity/3/ext/OS-REVOKE/1.0/rel/'
'events': {
'href': '/OS-REVOKE/events'},
_build_simple_cert_relation(resource_name='ca_certificate'): {
'href': '/OS-SIMPLE-CERT/ca'},
_build_simple_cert_relation(resource_name='certificates'): {
'href': '/OS-SIMPLE-CERT/certificates'},
_build_trust_relation(resource_name='trust'):
{
'href-template': '/OS-TRUST/trusts/{trust_id}',
@ -181,6 +208,9 @@ V3_JSON_HOME_RESOURCES_INHERIT_DISABLED = {
'href-vars': {'trust_id': TRUST_ID_PARAMETER_RELATION, }},
_build_trust_relation(resource_name='trusts'): {
'href': '/OS-TRUST/trusts'},
'http://docs.openstack.org/api/openstack-identity/3/ext/s3tokens/1.0/rel/'
's3tokens': {
'href': '/s3tokens'},
json_home.build_v3_resource_relation('group'): {
'href-template': '/groups/{group_id}',
'href-vars': {