Source code for keystone.common.validation.validators

# 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.
"""Internal implementation of request body validating middleware."""

import re

import jsonschema
from oslo_config import cfg
from oslo_log import log
from oslo_utils import timeutils
from oslo_utils import uuidutils

from keystone import exception
from keystone.i18n import _

CONF = cfg.CONF
LOG = log.getLogger(__name__)


# TODO(rderose): extend schema validation and add this check there
[docs] def validate_password(password): pattern = CONF.security_compliance.password_regex if pattern: if not isinstance(password, str): detail = _("Password must be a string type") raise exception.PasswordValidationError(detail=detail) try: if not re.match(pattern, password): pattern_desc = ( CONF.security_compliance.password_regex_description ) raise exception.PasswordRequirementsValidationError( detail=pattern_desc ) except re.error: msg = ( "Unable to validate password due to invalid regular " "expression - password_regex: %s" ) LOG.error(msg, pattern) detail = _( "Unable to validate password due to invalid configuration" ) raise exception.PasswordValidationError(detail=detail)
_FORMAT_CHECKER = jsonschema.FormatChecker() @_FORMAT_CHECKER.checks("date-time") def _validate_datetime_format(instance: object) -> bool: # format checks constrain to the relevant primitive type # https://github.com/OAI/OpenAPI-Specification/issues/3148 if not isinstance(instance, str): return True try: timeutils.parse_isotime(instance) except ValueError: return False else: return True @_FORMAT_CHECKER.checks("uuid") def _validate_uuid_format(instance: object) -> bool: # format checks constrain to the relevant primitive type # https://github.com/OAI/OpenAPI-Specification/issues/3148 if not isinstance(instance, str): return True return uuidutils.is_uuid_like(instance)
[docs] class SchemaValidator: """Resource reference validator class.""" # Use 2020 Schema consistently in all other OpenStack services validator_org = jsonschema.Draft202012Validator def __init__(self, schema): # NOTE(lbragstad): If at some point in the future we want to extend # our validators to include something specific we need to check for, # we can do it here. Nova's V3 API validators extend the validator to # include `self._validate_minimum` and `self._validate_maximum`. This # would be handy if we needed to check for something the jsonschema # didn't by default. See the Nova V3 validator for details on how this # is done. validators = {} validator_cls = jsonschema.validators.extend( self.validator_org, validators ) self.validator = validator_cls(schema, format_checker=_FORMAT_CHECKER)
[docs] def validate(self, *args, **kwargs): try: self.validator.validate(*args, **kwargs) except jsonschema.ValidationError as ex: # NOTE: For whole OpenStack message consistency, this error # message has been written in a format consistent with WSME. if ex.path: # NOTE(lbragstad): Here we could think about using iter_errors # as a method of providing invalid parameters back to the # user. # TODO(lbragstad): If the value of a field is confidential or # too long, then we should build the masking in here so that # we don't expose sensitive user information in the event it # fails validation. path = '/'.join(map(str, ex.path)) detail = _( "Invalid input for field '%(path)s': %(message)s" ) % {'path': path, 'message': str(ex)} else: detail = str(ex) raise exception.SchemaValidationError(detail=detail)