k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/validation/validate/object_validator.go (about)

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package validate
    16  
    17  import (
    18  	"reflect"
    19  	"regexp"
    20  
    21  	"k8s.io/kube-openapi/pkg/validation/errors"
    22  	"k8s.io/kube-openapi/pkg/validation/spec"
    23  	"k8s.io/kube-openapi/pkg/validation/strfmt"
    24  )
    25  
    26  type objectValidator struct {
    27  	Path                 string
    28  	In                   string
    29  	MaxProperties        *int64
    30  	MinProperties        *int64
    31  	Required             []string
    32  	Properties           map[string]spec.Schema
    33  	AdditionalProperties *spec.SchemaOrBool
    34  	PatternProperties    map[string]spec.Schema
    35  	Root                 interface{}
    36  	KnownFormats         strfmt.Registry
    37  	Options              SchemaValidatorOptions
    38  }
    39  
    40  func (o *objectValidator) SetPath(path string) {
    41  	o.Path = path
    42  }
    43  
    44  func (o *objectValidator) Applies(source interface{}, kind reflect.Kind) bool {
    45  	// TODO: this should also work for structs
    46  	// there is a problem in the type validator where it will be unhappy about null values
    47  	// so that requires more testing
    48  	r := reflect.TypeOf(source) == specSchemaType && (kind == reflect.Map || kind == reflect.Struct)
    49  	debugLog("object validator for %q applies %t for %T (kind: %v)\n", o.Path, r, source, kind)
    50  	return r
    51  }
    52  
    53  func (o *objectValidator) Validate(data interface{}) *Result {
    54  	val := data.(map[string]interface{})
    55  	// TODO: guard against nil data
    56  	numKeys := int64(len(val))
    57  
    58  	res := new(Result)
    59  
    60  	if o.MinProperties != nil && numKeys < *o.MinProperties {
    61  		res.AddErrors(errors.TooFewProperties(o.Path, o.In, *o.MinProperties, numKeys))
    62  	}
    63  	if o.MaxProperties != nil && numKeys > *o.MaxProperties {
    64  		res.AddErrors(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties, numKeys))
    65  	}
    66  
    67  	// check validity of field names
    68  	if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows {
    69  		// Case: additionalProperties: false
    70  		for k := range val {
    71  			_, regularProperty := o.Properties[k]
    72  			matched := false
    73  
    74  			for pk := range o.PatternProperties {
    75  				if matches, _ := regexp.MatchString(pk, k); matches {
    76  					matched = true
    77  					break
    78  				}
    79  			}
    80  
    81  			if !regularProperty && !matched {
    82  				// Special properties "$schema" and "id" are ignored
    83  				res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k))
    84  			}
    85  		}
    86  	} else {
    87  		// Cases: no additionalProperties (implying: true), or additionalProperties: true, or additionalProperties: { <<schema>> }
    88  		for key, value := range val {
    89  			_, regularProperty := o.Properties[key]
    90  
    91  			// Validates property against "patternProperties" if applicable
    92  			// BUG(fredbi): succeededOnce is always false
    93  
    94  			// NOTE: how about regular properties which do not match patternProperties?
    95  			matched, succeededOnce, _ := o.validatePatternProperty(key, value, res)
    96  
    97  			if !(regularProperty || matched || succeededOnce) {
    98  
    99  				// Cases: properties which are not regular properties and have not been matched by the PatternProperties validator
   100  				if o.AdditionalProperties != nil && o.AdditionalProperties.Schema != nil {
   101  					// AdditionalProperties as Schema
   102  					res.Merge(o.Options.NewValidatorForField(key, o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value))
   103  				} else if regularProperty && !(matched || succeededOnce) {
   104  					// TODO: this is dead code since regularProperty=false here
   105  					res.AddErrors(errors.FailedAllPatternProperties(o.Path, o.In, key))
   106  				}
   107  			}
   108  		}
   109  		// Valid cases: additionalProperties: true or undefined
   110  	}
   111  
   112  	createdFromDefaults := map[string]bool{}
   113  
   114  	// Property types:
   115  	// - regular Property
   116  	for pName, pSchema := range o.Properties {
   117  		rName := pName
   118  		if o.Path != "" {
   119  			rName = o.Path + "." + pName
   120  		}
   121  
   122  		// Recursively validates each property against its schema
   123  		if v, ok := val[pName]; ok {
   124  			r := o.Options.NewValidatorForField(pName, &pSchema, o.Root, rName, o.KnownFormats, o.Options.Options()...).Validate(v)
   125  			res.Merge(r)
   126  		}
   127  	}
   128  
   129  	// Check required properties
   130  	if len(o.Required) > 0 {
   131  		for _, k := range o.Required {
   132  			if _, ok := val[k]; !ok && !createdFromDefaults[k] {
   133  				res.AddErrors(errors.Required(o.Path+"."+k, o.In))
   134  				continue
   135  			}
   136  		}
   137  	}
   138  
   139  	// Check patternProperties
   140  	// TODO: it looks like we have done that twice in many cases
   141  	for key, value := range val {
   142  		_, regularProperty := o.Properties[key]
   143  		matched, _ /*succeededOnce*/, patterns := o.validatePatternProperty(key, value, res)
   144  		if !regularProperty && (matched /*|| succeededOnce*/) {
   145  			for _, pName := range patterns {
   146  				if v, ok := o.PatternProperties[pName]; ok {
   147  					res.Merge(o.Options.NewValidatorForField(key, &v, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value))
   148  				}
   149  			}
   150  		}
   151  	}
   152  	return res
   153  }
   154  
   155  // TODO: succeededOnce is not used anywhere
   156  func (o *objectValidator) validatePatternProperty(key string, value interface{}, result *Result) (bool, bool, []string) {
   157  	matched := false
   158  	succeededOnce := false
   159  	var patterns []string
   160  
   161  	for k, schema := range o.PatternProperties {
   162  		sch := schema
   163  		if match, _ := regexp.MatchString(k, key); match {
   164  			patterns = append(patterns, k)
   165  			matched = true
   166  			validator := o.Options.NewValidatorForField(key, &sch, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...)
   167  
   168  			res := validator.Validate(value)
   169  			result.Merge(res)
   170  		}
   171  	}
   172  
   173  	// BUG(fredbi): can't get to here. Should remove dead code (commented out).
   174  
   175  	//if succeededOnce {
   176  	//	result.Inc()
   177  	//}
   178  
   179  	return matched, succeededOnce, patterns
   180  }