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 15package validate 16 17import ( 18 "reflect" 19 "regexp" 20 "strings" 21 22 "k8s.io/kube-openapi/pkg/validation/errors" 23 "k8s.io/kube-openapi/pkg/validation/spec" 24 "k8s.io/kube-openapi/pkg/validation/strfmt" 25) 26 27type objectValidator struct { 28 Path string 29 In string 30 MaxProperties *int64 31 MinProperties *int64 32 Required []string 33 Properties map[string]spec.Schema 34 AdditionalProperties *spec.SchemaOrBool 35 PatternProperties map[string]spec.Schema 36 Root interface{} 37 KnownFormats strfmt.Registry 38 Options SchemaValidatorOptions 39} 40 41func (o *objectValidator) SetPath(path string) { 42 o.Path = path 43} 44 45func (o *objectValidator) Applies(source interface{}, kind reflect.Kind) bool { 46 // TODO: this should also work for structs 47 // there is a problem in the type validator where it will be unhappy about null values 48 // so that requires more testing 49 r := reflect.TypeOf(source) == specSchemaType && (kind == reflect.Map || kind == reflect.Struct) 50 debugLog("object validator for %q applies %t for %T (kind: %v)\n", o.Path, r, source, kind) 51 return r 52} 53 54func (o *objectValidator) isProperties() bool { 55 p := strings.Split(o.Path, ".") 56 return len(p) > 1 && p[len(p)-1] == jsonProperties && p[len(p)-2] != jsonProperties 57} 58 59func (o *objectValidator) isDefault() bool { 60 p := strings.Split(o.Path, ".") 61 return len(p) > 1 && p[len(p)-1] == jsonDefault && p[len(p)-2] != jsonDefault 62} 63 64func (o *objectValidator) isExample() bool { 65 p := strings.Split(o.Path, ".") 66 return len(p) > 1 && (p[len(p)-1] == swaggerExample || p[len(p)-1] == swaggerExamples) && p[len(p)-2] != swaggerExample 67} 68 69func (o *objectValidator) Validate(data interface{}) *Result { 70 val := data.(map[string]interface{}) 71 // TODO: guard against nil data 72 numKeys := int64(len(val)) 73 74 if o.MinProperties != nil && numKeys < *o.MinProperties { 75 return errorHelp.sErr(errors.TooFewProperties(o.Path, o.In, *o.MinProperties)) 76 } 77 if o.MaxProperties != nil && numKeys > *o.MaxProperties { 78 return errorHelp.sErr(errors.TooManyProperties(o.Path, o.In, *o.MaxProperties)) 79 } 80 81 res := new(Result) 82 83 // check validity of field names 84 if o.AdditionalProperties != nil && !o.AdditionalProperties.Allows { 85 // Case: additionalProperties: false 86 for k := range val { 87 _, regularProperty := o.Properties[k] 88 matched := false 89 90 for pk := range o.PatternProperties { 91 if matches, _ := regexp.MatchString(pk, k); matches { 92 matched = true 93 break 94 } 95 } 96 97 if !regularProperty && !matched { 98 // Special properties "$schema" and "id" are ignored 99 res.AddErrors(errors.PropertyNotAllowed(o.Path, o.In, k)) 100 } 101 } 102 } else { 103 // Cases: no additionalProperties (implying: true), or additionalProperties: true, or additionalProperties: { <<schema>> } 104 for key, value := range val { 105 _, regularProperty := o.Properties[key] 106 107 // Validates property against "patternProperties" if applicable 108 // BUG(fredbi): succeededOnce is always false 109 110 // NOTE: how about regular properties which do not match patternProperties? 111 matched, succeededOnce, _ := o.validatePatternProperty(key, value, res) 112 113 if !(regularProperty || matched || succeededOnce) { 114 115 // Cases: properties which are not regular properties and have not been matched by the PatternProperties validator 116 if o.AdditionalProperties != nil && o.AdditionalProperties.Schema != nil { 117 // AdditionalProperties as Schema 118 res.Merge(NewSchemaValidator(o.AdditionalProperties.Schema, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value)) 119 } else if regularProperty && !(matched || succeededOnce) { 120 // TODO: this is dead code since regularProperty=false here 121 res.AddErrors(errors.FailedAllPatternProperties(o.Path, o.In, key)) 122 } 123 } 124 } 125 // Valid cases: additionalProperties: true or undefined 126 } 127 128 createdFromDefaults := map[string]bool{} 129 130 // Property types: 131 // - regular Property 132 for pName, pSchema := range o.Properties { 133 rName := pName 134 if o.Path != "" { 135 rName = o.Path + "." + pName 136 } 137 138 // Recursively validates each property against its schema 139 if v, ok := val[pName]; ok { 140 r := NewSchemaValidator(&pSchema, o.Root, rName, o.KnownFormats, o.Options.Options()...).Validate(v) 141 res.Merge(r) 142 } 143 } 144 145 // Check required properties 146 if len(o.Required) > 0 { 147 for _, k := range o.Required { 148 if _, ok := val[k]; !ok && !createdFromDefaults[k] { 149 res.AddErrors(errors.Required(o.Path+"."+k, o.In)) 150 continue 151 } 152 } 153 } 154 155 // Check patternProperties 156 // TODO: it looks like we have done that twice in many cases 157 for key, value := range val { 158 _, regularProperty := o.Properties[key] 159 matched, _ /*succeededOnce*/, patterns := o.validatePatternProperty(key, value, res) 160 if !regularProperty && (matched /*|| succeededOnce*/) { 161 for _, pName := range patterns { 162 if v, ok := o.PatternProperties[pName]; ok { 163 res.Merge(NewSchemaValidator(&v, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...).Validate(value)) 164 } 165 } 166 } 167 } 168 return res 169} 170 171// TODO: succeededOnce is not used anywhere 172func (o *objectValidator) validatePatternProperty(key string, value interface{}, result *Result) (bool, bool, []string) { 173 matched := false 174 succeededOnce := false 175 var patterns []string 176 177 for k, schema := range o.PatternProperties { 178 sch := schema 179 if match, _ := regexp.MatchString(k, key); match { 180 patterns = append(patterns, k) 181 matched = true 182 validator := NewSchemaValidator(&sch, o.Root, o.Path+"."+key, o.KnownFormats, o.Options.Options()...) 183 184 res := validator.Validate(value) 185 result.Merge(res) 186 } 187 } 188 189 // BUG(fredbi): can't get to here. Should remove dead code (commented out). 190 191 //if succeededOnce { 192 // result.Inc() 193 //} 194 195 return matched, succeededOnce, patterns 196} 197