Add schema for OAuth1 consumer API

Add schema validation on create/update of OAuth1 consumer.

This patch also remove the unnecessary code after schema
validation enforcement.

- Remove the check of `secret` in request body for update
consumer API. It is covered by this schema definition.
 'not': {
     'required': ['secret']
 }

- Remove the check of `description` to consistent with other
entities, such as `user`, `group`. It is covered by the following
schema definition.
 'description': validation.nullable(parameter_types.description)

Partially implements: bp schema-validation-extent

Change-Id: I4d7e6188e8120aa4bcb4a27a22a34d7b395d5f49
This commit is contained in:
Dave Chen 2016-01-13 16:53:10 +08:00
parent 543707bbac
commit 892cbf5025
4 changed files with 94 additions and 8 deletions

View File

@ -102,8 +102,6 @@ class OAuth1(core.Oauth1DriverV8):
def create_consumer(self, consumer):
consumer['secret'] = uuid.uuid4().hex
if not consumer.get('description'):
consumer['description'] = None
session = sql.get_session()
with session.begin():
consumer_ref = Consumer.from_dict(consumer)

View File

@ -21,11 +21,13 @@ from oslo_utils import timeutils
from keystone.common import controller
from keystone.common import dependency
from keystone.common import utils
from keystone.common import validation
from keystone.common import wsgi
from keystone import exception
from keystone.i18n import _
from keystone import notifications
from keystone.oauth1 import core as oauth1
from keystone.oauth1 import schema
from keystone.oauth1 import validator
@ -56,6 +58,7 @@ class ConsumerCrudV3(controller.V3Controller):
return controller.V3Controller.base_url(context, path=path)
@controller.protected()
@validation.validated(schema.consumer_create, 'consumer')
def create_consumer(self, context, consumer):
ref = self._assign_unique_id(self._normalize_dict(consumer))
initiator = notifications._get_request_audit_info(context)
@ -63,10 +66,10 @@ class ConsumerCrudV3(controller.V3Controller):
return ConsumerCrudV3.wrap_member(context, consumer_ref)
@controller.protected()
@validation.validated(schema.consumer_update, 'consumer')
def update_consumer(self, context, consumer_id, consumer):
self._require_matching_id(consumer_id, consumer)
ref = self._normalize_dict(consumer)
self._validate_consumer_ref(ref)
initiator = notifications._get_request_audit_info(context)
ref = self.oauth_api.update_consumer(consumer_id, ref, initiator)
return ConsumerCrudV3.wrap_member(context, ref)
@ -90,11 +93,6 @@ class ConsumerCrudV3(controller.V3Controller):
initiator = notifications._get_request_audit_info(context)
self.oauth_api.delete_consumer(consumer_id, initiator)
def _validate_consumer_ref(self, consumer):
if 'secret' in consumer:
msg = _('Cannot change consumer secret')
raise exception.ValidationError(message=msg)
@dependency.requires('oauth_api')
class AccessTokenCrudV3(controller.V3Controller):

34
keystone/oauth1/schema.py Normal file
View File

@ -0,0 +1,34 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from keystone.common import validation
from keystone.common.validation import parameter_types
_consumer_properties = {
'description': validation.nullable(parameter_types.description)
}
consumer_create = {
'type': 'object',
'properties': _consumer_properties,
'additionalProperties': True
}
consumer_update = {
'type': 'object',
'properties': _consumer_properties,
'not': {
'required': ['secret']
},
'minProperties': 1,
'additionalProperties': True
}

View File

@ -25,6 +25,7 @@ from keystone.credential import schema as credential_schema
from keystone import exception
from keystone.federation import schema as federation_schema
from keystone.identity import schema as identity_schema
from keystone.oauth1 import schema as oauth1_schema
from keystone.policy import schema as policy_schema
from keystone.resource import schema as resource_schema
from keystone.tests import unit
@ -83,6 +84,8 @@ _VALID_ENABLED_FORMATS = [True, False]
_INVALID_ENABLED_FORMATS = ['some string', 1, 0, 'True', 'False']
_INVALID_DESC_FORMATS = [False, 1, 2.0]
_VALID_URLS = ['https://example.com', 'http://EXAMPLE.com/v3',
'http://localhost', 'http://127.0.0.1:5000',
'http://1.1.1.1', 'http://255.255.255.255',
@ -2038,3 +2041,56 @@ class FederationProtocolValidationTestCase(unit.BaseTestCase):
self.assertRaises(exception.SchemaValidationError,
self.protocol_validator.validate,
request_to_validate)
class OAuth1ValidationTestCase(unit.BaseTestCase):
"""Test for V3 Identity OAuth1 API validation."""
def setUp(self):
super(OAuth1ValidationTestCase, self).setUp()
create = oauth1_schema.consumer_create
update = oauth1_schema.consumer_update
self.create_consumer_validator = validators.SchemaValidator(create)
self.update_consumer_validator = validators.SchemaValidator(update)
def test_validate_consumer_request_succeeds(self):
"""Test that we validate a consumer request successfully."""
request_to_validate = {'description': uuid.uuid4().hex,
'name': uuid.uuid4().hex}
self.create_consumer_validator.validate(request_to_validate)
self.update_consumer_validator.validate(request_to_validate)
def test_validate_consumer_request_with_no_parameters(self):
"""Test that schema validation with empty request body."""
request_to_validate = {}
self.create_consumer_validator.validate(request_to_validate)
# At least one property should be given.
self.assertRaises(exception.SchemaValidationError,
self.update_consumer_validator.validate,
request_to_validate)
def test_validate_consumer_request_with_invalid_description_fails(self):
"""Exception is raised when `description` as a non-string value."""
for invalid_desc in _INVALID_DESC_FORMATS:
request_to_validate = {'description': invalid_desc}
self.assertRaises(exception.SchemaValidationError,
self.create_consumer_validator.validate,
request_to_validate)
self.assertRaises(exception.SchemaValidationError,
self.update_consumer_validator.validate,
request_to_validate)
def test_validate_update_consumer_request_fails_with_secret(self):
"""Exception raised when secret is given."""
request_to_validate = {'secret': uuid.uuid4().hex}
self.assertRaises(exception.SchemaValidationError,
self.update_consumer_validator.validate,
request_to_validate)
def test_validate_consumer_request_with_none_desc(self):
"""Test that schema validation with None desc."""
request_to_validate = {'description': None}
self.create_consumer_validator.validate(request_to_validate)
self.update_consumer_validator.validate(request_to_validate)