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:
parent
543707bbac
commit
892cbf5025
|
@ -102,8 +102,6 @@ class OAuth1(core.Oauth1DriverV8):
|
||||||
|
|
||||||
def create_consumer(self, consumer):
|
def create_consumer(self, consumer):
|
||||||
consumer['secret'] = uuid.uuid4().hex
|
consumer['secret'] = uuid.uuid4().hex
|
||||||
if not consumer.get('description'):
|
|
||||||
consumer['description'] = None
|
|
||||||
session = sql.get_session()
|
session = sql.get_session()
|
||||||
with session.begin():
|
with session.begin():
|
||||||
consumer_ref = Consumer.from_dict(consumer)
|
consumer_ref = Consumer.from_dict(consumer)
|
||||||
|
|
|
@ -21,11 +21,13 @@ from oslo_utils import timeutils
|
||||||
from keystone.common import controller
|
from keystone.common import controller
|
||||||
from keystone.common import dependency
|
from keystone.common import dependency
|
||||||
from keystone.common import utils
|
from keystone.common import utils
|
||||||
|
from keystone.common import validation
|
||||||
from keystone.common import wsgi
|
from keystone.common import wsgi
|
||||||
from keystone import exception
|
from keystone import exception
|
||||||
from keystone.i18n import _
|
from keystone.i18n import _
|
||||||
from keystone import notifications
|
from keystone import notifications
|
||||||
from keystone.oauth1 import core as oauth1
|
from keystone.oauth1 import core as oauth1
|
||||||
|
from keystone.oauth1 import schema
|
||||||
from keystone.oauth1 import validator
|
from keystone.oauth1 import validator
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,6 +58,7 @@ class ConsumerCrudV3(controller.V3Controller):
|
||||||
return controller.V3Controller.base_url(context, path=path)
|
return controller.V3Controller.base_url(context, path=path)
|
||||||
|
|
||||||
@controller.protected()
|
@controller.protected()
|
||||||
|
@validation.validated(schema.consumer_create, 'consumer')
|
||||||
def create_consumer(self, context, consumer):
|
def create_consumer(self, context, consumer):
|
||||||
ref = self._assign_unique_id(self._normalize_dict(consumer))
|
ref = self._assign_unique_id(self._normalize_dict(consumer))
|
||||||
initiator = notifications._get_request_audit_info(context)
|
initiator = notifications._get_request_audit_info(context)
|
||||||
|
@ -63,10 +66,10 @@ class ConsumerCrudV3(controller.V3Controller):
|
||||||
return ConsumerCrudV3.wrap_member(context, consumer_ref)
|
return ConsumerCrudV3.wrap_member(context, consumer_ref)
|
||||||
|
|
||||||
@controller.protected()
|
@controller.protected()
|
||||||
|
@validation.validated(schema.consumer_update, 'consumer')
|
||||||
def update_consumer(self, context, consumer_id, consumer):
|
def update_consumer(self, context, consumer_id, consumer):
|
||||||
self._require_matching_id(consumer_id, consumer)
|
self._require_matching_id(consumer_id, consumer)
|
||||||
ref = self._normalize_dict(consumer)
|
ref = self._normalize_dict(consumer)
|
||||||
self._validate_consumer_ref(ref)
|
|
||||||
initiator = notifications._get_request_audit_info(context)
|
initiator = notifications._get_request_audit_info(context)
|
||||||
ref = self.oauth_api.update_consumer(consumer_id, ref, initiator)
|
ref = self.oauth_api.update_consumer(consumer_id, ref, initiator)
|
||||||
return ConsumerCrudV3.wrap_member(context, ref)
|
return ConsumerCrudV3.wrap_member(context, ref)
|
||||||
|
@ -90,11 +93,6 @@ class ConsumerCrudV3(controller.V3Controller):
|
||||||
initiator = notifications._get_request_audit_info(context)
|
initiator = notifications._get_request_audit_info(context)
|
||||||
self.oauth_api.delete_consumer(consumer_id, initiator)
|
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')
|
@dependency.requires('oauth_api')
|
||||||
class AccessTokenCrudV3(controller.V3Controller):
|
class AccessTokenCrudV3(controller.V3Controller):
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ from keystone.credential import schema as credential_schema
|
||||||
from keystone import exception
|
from keystone import exception
|
||||||
from keystone.federation import schema as federation_schema
|
from keystone.federation import schema as federation_schema
|
||||||
from keystone.identity import schema as identity_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.policy import schema as policy_schema
|
||||||
from keystone.resource import schema as resource_schema
|
from keystone.resource import schema as resource_schema
|
||||||
from keystone.tests import unit
|
from keystone.tests import unit
|
||||||
|
@ -83,6 +84,8 @@ _VALID_ENABLED_FORMATS = [True, False]
|
||||||
|
|
||||||
_INVALID_ENABLED_FORMATS = ['some string', 1, 0, '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',
|
_VALID_URLS = ['https://example.com', 'http://EXAMPLE.com/v3',
|
||||||
'http://localhost', 'http://127.0.0.1:5000',
|
'http://localhost', 'http://127.0.0.1:5000',
|
||||||
'http://1.1.1.1', 'http://255.255.255.255',
|
'http://1.1.1.1', 'http://255.255.255.255',
|
||||||
|
@ -2038,3 +2041,56 @@ class FederationProtocolValidationTestCase(unit.BaseTestCase):
|
||||||
self.assertRaises(exception.SchemaValidationError,
|
self.assertRaises(exception.SchemaValidationError,
|
||||||
self.protocol_validator.validate,
|
self.protocol_validator.validate,
|
||||||
request_to_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)
|
||||||
|
|
Loading…
Reference in New Issue