keystone/keystone/tests/test_validation.py

613 lines
28 KiB
Python

# -*- coding: utf-8 -*-
# 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.
import uuid
import testtools
from keystone.assignment import schema as assignment_schema
from keystone.common import validation
from keystone.common.validation import parameter_types
from keystone.common.validation import validators
from keystone import exception
from keystone.policy import schema as policy_schema
"""Example model to validate create requests against. Assume that this is
the only backend for the create and validate schemas. This is just an
example to show how a backend can be used to construct a schema. In
Keystone, schemas are built according to the Identity API and the backends
available in Keystone. This example does not mean that all schema in
Keystone were strictly based of the SQL backends.
class Entity(sql.ModelBase):
__tablename__ = 'entity'
attributes = ['id', 'name', 'domain_id', 'description']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(255), nullable=False)
description = sql.Column(sql.Text(), nullable=True)
enabled = sql.Column(sql.Boolean, default=True, nullable=False)
url = sql.Column(sql.String(225), nullable=True)
email = sql.Column(sql.String(64), nullable=True)
"""
# Test schema to validate create requests against
_entity_properties = {
'name': parameter_types.name,
'description': validation.nullable(parameter_types.description),
'enabled': parameter_types.boolean,
'url': validation.nullable(parameter_types.url),
'email': validation.nullable(parameter_types.email),
'id_string': validation.nullable(parameter_types.id_string)
}
entity_create = {
'type': 'object',
'properties': _entity_properties,
'required': ['name'],
'additionalProperties': True,
}
entity_update = {
'type': 'object',
'properties': _entity_properties,
'minProperties': 1,
'additionalProperties': True,
}
_VALID_ENABLED_FORMATS = [True, False]
_INVALID_ENABLED_FORMATS = ['some string', 1, 0, 'True', 'False']
class EntityValidationTestCase(testtools.TestCase):
def setUp(self):
super(EntityValidationTestCase, self).setUp()
self.resource_name = 'some resource name'
self.description = 'Some valid description'
self.valid_enabled = True
self.valid_url = 'http://example.com'
self.valid_email = 'joe@example.com'
self.create_schema_validator = validators.SchemaValidator(
entity_create)
self.update_schema_validator = validators.SchemaValidator(
entity_update)
def test_create_entity_with_all_valid_parameters_validates(self):
"""Validate all parameter values against test schema."""
request_to_validate = {'name': self.resource_name,
'description': self.description,
'enabled': self.valid_enabled,
'url': self.valid_url,
'email': self.valid_email}
self.create_schema_validator.validate(request_to_validate)
def test_create_entity_with_only_required_valid_parameters_validates(self):
"""Validate correct for only parameters values against test schema."""
request_to_validate = {'name': self.resource_name}
self.create_schema_validator.validate(request_to_validate)
def test_create_entity_with_name_too_long_raises_exception(self):
"""Validate long names.
Validate that an exception is raised when validating a string of 255+
characters passed in as a name.
"""
invalid_name = 'a' * 256
request_to_validate = {'name': invalid_name}
self.assertRaises(exception.SchemaValidationError,
self.create_schema_validator.validate,
request_to_validate)
def test_create_entity_with_name_too_short_raises_exception(self):
"""Validate short names.
Test that an exception is raised when passing a string of length
zero as a name parameter.
"""
request_to_validate = {'name': ''}
self.assertRaises(exception.SchemaValidationError,
self.create_schema_validator.validate,
request_to_validate)
def test_create_entity_with_unicode_name_validates(self):
"""Test that we successfully validate a unicode string."""
request_to_validate = {'name': u'αβγδ'}
self.create_schema_validator.validate(request_to_validate)
def test_create_entity_with_invalid_enabled_format_raises_exception(self):
"""Validate invalid enabled formats.
Test that an exception is raised when passing invalid boolean-like
values as `enabled`.
"""
for format in _INVALID_ENABLED_FORMATS:
request_to_validate = {'name': self.resource_name,
'enabled': format}
self.assertRaises(exception.SchemaValidationError,
self.create_schema_validator.validate,
request_to_validate)
def test_create_entity_with_valid_enabled_formats_validates(self):
"""Validate valid enabled formats.
Test that we have successful validation on boolean values for
`enabled`.
"""
for valid_enabled in _VALID_ENABLED_FORMATS:
request_to_validate = {'name': self.resource_name,
'enabled': valid_enabled}
# Make sure validation doesn't raise a validation exception
self.create_schema_validator.validate(request_to_validate)
def test_create_entity_with_valid_urls_validates(self):
"""Test that proper urls are successfully validated."""
valid_urls = ['https://169.254.0.1', 'https://example.com',
'https://EXAMPLE.com', 'https://127.0.0.1:35357',
'https://localhost']
for valid_url in valid_urls:
request_to_validate = {'name': self.resource_name,
'url': valid_url}
self.create_schema_validator.validate(request_to_validate)
def test_create_entity_with_invalid_urls_fails(self):
"""Test that an exception is raised when validating improper urls."""
invalid_urls = ['http//something.com',
'https//something.com',
'https://9.9.9']
for invalid_url in invalid_urls:
request_to_validate = {'name': self.resource_name,
'url': invalid_url}
self.assertRaises(exception.SchemaValidationError,
self.create_schema_validator.validate,
request_to_validate)
def test_create_entity_with_valid_email_validates(self):
"""Validate email address
Test that we successfully validate properly formatted email
addresses.
"""
request_to_validate = {'name': self.resource_name,
'email': self.valid_email}
self.create_schema_validator.validate(request_to_validate)
def test_create_entity_with_invalid_email_fails(self):
"""Validate invalid email address.
Test that an exception is raised when validating improperly
formatted email addresses.
"""
request_to_validate = {'name': self.resource_name,
'email': 'some invalid email value'}
self.assertRaises(exception.SchemaValidationError,
self.create_schema_validator.validate,
request_to_validate)
def test_create_entity_with_valid_id_strings(self):
"""Validate acceptable id strings."""
valid_id_strings = [str(uuid.uuid4()), uuid.uuid4().hex, 'default']
for valid_id in valid_id_strings:
request_to_validate = {'name': self.resource_name,
'id_string': valid_id}
self.create_schema_validator.validate(request_to_validate)
def test_create_entity_with_invalid_id_strings(self):
"""Exception raised when using invalid id strings."""
long_string = 'A' * 65
invalid_id_strings = ['', long_string, 'this,should,fail']
for invalid_id in invalid_id_strings:
request_to_validate = {'name': self.resource_name,
'id_string': invalid_id}
self.assertRaises(exception.SchemaValidationError,
self.create_schema_validator.validate,
request_to_validate)
def test_create_entity_with_null_id_string(self):
"""Validate that None is an acceptable optional string type."""
request_to_validate = {'name': self.resource_name,
'id_string': None}
self.create_schema_validator.validate(request_to_validate)
def test_create_entity_with_null_string_succeeds(self):
"""Exception raised when passing None on required id strings."""
request_to_validate = {'name': self.resource_name,
'id_string': None}
self.create_schema_validator.validate(request_to_validate)
def test_update_entity_with_no_parameters_fails(self):
"""At least one parameter needs to be present for an update."""
request_to_validate = {}
self.assertRaises(exception.SchemaValidationError,
self.update_schema_validator.validate,
request_to_validate)
def test_update_entity_with_all_parameters_valid_validates(self):
"""Simulate updating an entity by ID."""
request_to_validate = {'name': self.resource_name,
'description': self.description,
'enabled': self.valid_enabled,
'url': self.valid_url,
'email': self.valid_email}
self.update_schema_validator.validate(request_to_validate)
def test_update_entity_with_a_valid_required_parameter_validates(self):
"""Succeed if a valid required parameter is provided."""
request_to_validate = {'name': self.resource_name}
self.update_schema_validator.validate(request_to_validate)
def test_update_entity_with_invalid_required_parameter_fails(self):
"""Fail if a provided required parameter is invalid."""
request_to_validate = {'name': 'a' * 256}
self.assertRaises(exception.SchemaValidationError,
self.update_schema_validator.validate,
request_to_validate)
def test_update_entity_with_a_null_optional_parameter_validates(self):
"""Optional parameters can be null to removed the value."""
request_to_validate = {'email': None}
self.update_schema_validator.validate(request_to_validate)
def test_update_entity_with_a_required_null_parameter_fails(self):
"""The `name` parameter can't be null."""
request_to_validate = {'name': None}
self.assertRaises(exception.SchemaValidationError,
self.update_schema_validator.validate,
request_to_validate)
def test_update_entity_with_a_valid_optional_parameter_validates(self):
"""Succeeds with only a single valid optional parameter."""
request_to_validate = {'email': self.valid_email}
self.update_schema_validator.validate(request_to_validate)
def test_update_entity_with_invalid_optional_parameter_fails(self):
"""Fails when an optional parameter is invalid."""
request_to_validate = {'email': 0}
self.assertRaises(exception.SchemaValidationError,
self.update_schema_validator.validate,
request_to_validate)
class ProjectValidationTestCase(testtools.TestCase):
"""Test for V3 Project API validation."""
def setUp(self):
super(ProjectValidationTestCase, self).setUp()
self.project_name = 'My Project'
create = assignment_schema.project_create
update = assignment_schema.project_update
self.create_project_validator = validators.SchemaValidator(create)
self.update_project_validator = validators.SchemaValidator(update)
def test_validate_project_request(self):
"""Test that we validate a project with `name` in request."""
request_to_validate = {'name': self.project_name}
self.create_project_validator.validate(request_to_validate)
def test_validate_project_request_without_name_fails(self):
"""Validate project request fails without name."""
request_to_validate = {'enabled': True}
self.assertRaises(exception.SchemaValidationError,
self.create_project_validator.validate,
request_to_validate)
def test_validate_project_request_with_enabled(self):
"""Validate `enabled` as boolean-like values for projects."""
for valid_enabled in _VALID_ENABLED_FORMATS:
request_to_validate = {'name': self.project_name,
'enabled': valid_enabled}
self.create_project_validator.validate(request_to_validate)
def test_validate_project_request_with_invalid_enabled_fails(self):
"""Exception is raised when `enabled` isn't a boolean-like value."""
for invalid_enabled in _INVALID_ENABLED_FORMATS:
request_to_validate = {'name': self.project_name,
'enabled': invalid_enabled}
self.assertRaises(exception.SchemaValidationError,
self.create_project_validator.validate,
request_to_validate)
def test_validate_project_request_with_valid_description(self):
"""Test that we validate `description` in create project requests."""
request_to_validate = {'name': self.project_name,
'description': 'My Project'}
self.create_project_validator.validate(request_to_validate)
def test_validate_project_request_with_invalid_description_fails(self):
"""Exception is raised when `description` as a non-string value."""
request_to_validate = {'name': self.project_name,
'description': False}
self.assertRaises(exception.SchemaValidationError,
self.create_project_validator.validate,
request_to_validate)
def test_validate_project_request_with_name_too_long(self):
"""Exception is raised when `name` is too long."""
long_project_name = 'a' * 65
request_to_validate = {'name': long_project_name}
self.assertRaises(exception.SchemaValidationError,
self.create_project_validator.validate,
request_to_validate)
def test_validate_project_request_with_name_too_short(self):
"""Exception raised when `name` is too short."""
request_to_validate = {'name': ''}
self.assertRaises(exception.SchemaValidationError,
self.create_project_validator.validate,
request_to_validate)
def test_validate_project_update_request(self):
"""Test that we validate a project update request."""
request_to_validate = {'domain_id': uuid.uuid4().hex}
self.update_project_validator.validate(request_to_validate)
def test_validate_project_update_request_with_no_parameters_fails(self):
"""Exception is raised when updating project without parameters."""
request_to_validate = {}
self.assertRaises(exception.SchemaValidationError,
self.update_project_validator.validate,
request_to_validate)
def test_validate_project_update_request_with_name_too_long_fails(self):
"""Exception raised when updating a project with `name` too long."""
long_project_name = 'a' * 65
request_to_validate = {'name': long_project_name}
self.assertRaises(exception.SchemaValidationError,
self.update_project_validator.validate,
request_to_validate)
def test_validate_project_update_request_with_name_too_short_fails(self):
"""Exception raised when updating a project with `name` too short."""
request_to_validate = {'name': ''}
self.assertRaises(exception.SchemaValidationError,
self.update_project_validator.validate,
request_to_validate)
def test_validate_project_update_request_with_null_domain_id_fails(self):
request_to_validate = {'domain_id': None}
self.assertRaises(exception.SchemaValidationError,
self.update_project_validator.validate,
request_to_validate)
class DomainValidationTestCase(testtools.TestCase):
"""Test for V3 Domain API validation."""
def setUp(self):
super(DomainValidationTestCase, self).setUp()
self.domain_name = 'My Domain'
create = assignment_schema.domain_create
update = assignment_schema.domain_update
self.create_domain_validator = validators.SchemaValidator(create)
self.update_domain_validator = validators.SchemaValidator(update)
def test_validate_domain_request(self):
"""Make sure we successfully validate a create domain request."""
request_to_validate = {'name': self.domain_name}
self.create_domain_validator.validate(request_to_validate)
def test_validate_domain_request_without_name_fails(self):
"""Make sure we raise an exception when `name` isn't included."""
request_to_validate = {'enabled': True}
self.assertRaises(exception.SchemaValidationError,
self.create_domain_validator.validate,
request_to_validate)
def test_validate_domain_request_with_enabled(self):
"""Validate `enabled` as boolean-like values for domains."""
for valid_enabled in _VALID_ENABLED_FORMATS:
request_to_validate = {'name': self.domain_name,
'enabled': valid_enabled}
self.create_domain_validator.validate(request_to_validate)
def test_validate_domain_request_with_invalid_enabled_fails(self):
"""Exception is raised when `enabled` isn't a boolean-like value."""
for invalid_enabled in _INVALID_ENABLED_FORMATS:
request_to_validate = {'name': self.domain_name,
'enabled': invalid_enabled}
self.assertRaises(exception.SchemaValidationError,
self.create_domain_validator.validate,
request_to_validate)
def test_validate_domain_request_with_valid_description(self):
"""Test that we validate `description` in create domain requests."""
request_to_validate = {'name': self.domain_name,
'description': 'My Domain'}
self.create_domain_validator.validate(request_to_validate)
def test_validate_domain_request_with_invalid_description_fails(self):
"""Exception is raised when `description` is a non-string value."""
request_to_validate = {'name': self.domain_name,
'description': False}
self.assertRaises(exception.SchemaValidationError,
self.create_domain_validator.validate,
request_to_validate)
def test_validate_domain_request_with_name_too_long(self):
"""Exception is raised when `name` is too long."""
long_domain_name = 'a' * 65
request_to_validate = {'name': long_domain_name}
self.assertRaises(exception.SchemaValidationError,
self.create_domain_validator.validate,
request_to_validate)
def test_validate_domain_request_with_name_too_short(self):
"""Exception raised when `name` is too short."""
request_to_validate = {'name': ''}
self.assertRaises(exception.SchemaValidationError,
self.create_domain_validator.validate,
request_to_validate)
def test_validate_domain_update_request(self):
"""Test that we validate a domain update request."""
request_to_validate = {'domain_id': uuid.uuid4().hex}
self.update_domain_validator.validate(request_to_validate)
def test_validate_domain_update_request_with_no_parameters_fails(self):
"""Exception is raised when updating a domain without parameters."""
request_to_validate = {}
self.assertRaises(exception.SchemaValidationError,
self.update_domain_validator.validate,
request_to_validate)
def test_validate_domain_update_request_with_name_too_long_fails(self):
"""Exception raised when updating a domain with `name` too long."""
long_domain_name = 'a' * 65
request_to_validate = {'name': long_domain_name}
self.assertRaises(exception.SchemaValidationError,
self.update_domain_validator.validate,
request_to_validate)
def test_validate_domain_update_request_with_name_too_short_fails(self):
"""Exception raised when updating a domain with `name` too short."""
request_to_validate = {'name': ''}
self.assertRaises(exception.SchemaValidationError,
self.update_domain_validator.validate,
request_to_validate)
class RoleValidationTestCase(testtools.TestCase):
"""Test for V3 Role API validation."""
def setUp(self):
super(RoleValidationTestCase, self).setUp()
self.role_name = 'My Role'
create = assignment_schema.role_create
update = assignment_schema.role_update
self.create_role_validator = validators.SchemaValidator(create)
self.update_role_validator = validators.SchemaValidator(update)
def test_validate_role_request(self):
"""Test we can successfully validate a create role request."""
request_to_validate = {'name': self.role_name}
self.create_role_validator.validate(request_to_validate)
def test_validate_role_create_without_name_raises_exception(self):
"""Test that we raise an exception when `name` isn't included."""
request_to_validate = {'enabled': True}
self.assertRaises(exception.SchemaValidationError,
self.create_role_validator.validate,
request_to_validate)
def test_validate_role_create_when_name_is_not_string_fails(self):
"""Exception is raised on role create with a non-string `name`."""
request_to_validate = {'name': True}
self.assertRaises(exception.SchemaValidationError,
self.create_role_validator.validate,
request_to_validate)
request_to_validate = {'name': 24}
self.assertRaises(exception.SchemaValidationError,
self.create_role_validator.validate,
request_to_validate)
def test_validate_role_update_request(self):
"""Test that we validate a role update request."""
request_to_validate = {'name': 'My New Role'}
self.update_role_validator.validate(request_to_validate)
def test_validate_role_update_fails_with_invalid_name_fails(self):
"""Exception when validating an update request with invalid `name`."""
request_to_validate = {'name': True}
self.assertRaises(exception.SchemaValidationError,
self.update_role_validator.validate,
request_to_validate)
request_to_validate = {'name': 24}
self.assertRaises(exception.SchemaValidationError,
self.update_role_validator.validate,
request_to_validate)
class PolicyValidationTestCase(testtools.TestCase):
"""Test for V3 Policy API validation."""
def setUp(self):
super(PolicyValidationTestCase, self).setUp()
create = policy_schema.policy_create
update = policy_schema.policy_update
self.create_policy_validator = validators.SchemaValidator(create)
self.update_policy_validator = validators.SchemaValidator(update)
def test_validate_policy_succeeds(self):
"""Test that we validate a create policy request."""
request_to_validate = {'blob': 'some blob information',
'type': 'application/json'}
self.create_policy_validator.validate(request_to_validate)
def test_validate_policy_without_blob_fails(self):
"""Exception raised without `blob` in request."""
request_to_validate = {'type': 'application/json'}
self.assertRaises(exception.SchemaValidationError,
self.create_policy_validator.validate,
request_to_validate)
def test_validate_policy_without_type_fails(self):
"""Exception raised without `type` in request."""
request_to_validate = {'blob': 'some blob information'}
self.assertRaises(exception.SchemaValidationError,
self.create_policy_validator.validate,
request_to_validate)
def test_validate_policy_create_with_extra_parameters_succeeds(self):
"""Validate policy create with extra parameters."""
request_to_validate = {'blob': 'some blob information',
'type': 'application/json',
'extra': 'some extra stuff'}
self.create_policy_validator.validate(request_to_validate)
def test_validate_policy_create_with_invalid_type_fails(self):
"""Exception raised when `blob` and `type` are boolean."""
for prop in ['blob', 'type']:
request_to_validate = {prop: False}
self.assertRaises(exception.SchemaValidationError,
self.create_policy_validator.validate,
request_to_validate)
def test_validate_policy_update_without_parameters_fails(self):
"""Exception raised when updating policy without parameters."""
request_to_validate = {}
self.assertRaises(exception.SchemaValidationError,
self.update_policy_validator.validate,
request_to_validate)
def test_validate_policy_update_with_extra_parameters_succeeds(self):
"""Validate policy update request with extra parameters."""
request_to_validate = {'blob': 'some blob information',
'type': 'application/json',
'extra': 'some extra stuff'}
self.update_policy_validator.validate(request_to_validate)
def test_validate_policy_update_succeeds(self):
"""Test that we validate a policy update request."""
request_to_validate = {'blob': 'some blob information',
'type': 'application/json'}
self.update_policy_validator.validate(request_to_validate)
def test_validate_policy_update_with_invalid_type_fails(self):
"""Exception raised when invalid `type` on policy update."""
for prop in ['blob', 'type']:
request_to_validate = {prop: False}
self.assertRaises(exception.SchemaValidationError,
self.update_policy_validator.validate,
request_to_validate)