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 "fmt" 19 "strings" 20 21 "github.com/go-openapi/spec" 22) 23 24// defaultValidator validates default values in a spec. 25// According to Swagger spec, default values MUST validate their schema. 26type defaultValidator struct { 27 SpecValidator *SpecValidator 28 visitedSchemas map[string]bool 29} 30 31// resetVisited resets the internal state of visited schemas 32func (d *defaultValidator) resetVisited() { 33 d.visitedSchemas = map[string]bool{} 34} 35 36func isVisited(path string, visitedSchemas map[string]bool) bool { 37 found := visitedSchemas[path] 38 if !found { 39 // search for overlapping paths 40 frags := strings.Split(path, ".") 41 if len(frags) < 2 { 42 // shortcut exit on smaller paths 43 return found 44 } 45 last := len(frags) - 1 46 var currentFragStr, parent string 47 for i := range frags { 48 if i == 0 { 49 currentFragStr = frags[last] 50 } else { 51 currentFragStr = strings.Join([]string{frags[last-i], currentFragStr}, ".") 52 } 53 if i < last { 54 parent = strings.Join(frags[0:last-i], ".") 55 } else { 56 parent = "" 57 } 58 if strings.HasSuffix(parent, currentFragStr) { 59 found = true 60 break 61 } 62 } 63 } 64 return found 65} 66 67// beingVisited asserts a schema is being visited 68func (d *defaultValidator) beingVisited(path string) { 69 d.visitedSchemas[path] = true 70} 71 72// isVisited tells if a path has already been visited 73func (d *defaultValidator) isVisited(path string) bool { 74 return isVisited(path, d.visitedSchemas) 75} 76 77// Validate validates the default values declared in the swagger spec 78func (d *defaultValidator) Validate() (errs *Result) { 79 errs = new(Result) 80 if d == nil || d.SpecValidator == nil { 81 return errs 82 } 83 d.resetVisited() 84 errs.Merge(d.validateDefaultValueValidAgainstSchema()) // error - 85 return errs 86} 87 88func (d *defaultValidator) validateDefaultValueValidAgainstSchema() *Result { 89 // every default value that is specified must validate against the schema for that property 90 // headers, items, parameters, schema 91 92 res := new(Result) 93 s := d.SpecValidator 94 95 for method, pathItem := range s.analyzer.Operations() { 96 for path, op := range pathItem { 97 // parameters 98 for _, param := range paramHelp.safeExpandedParamsFor(path, method, op.ID, res, s) { 99 if param.Default != nil && param.Required { 100 res.AddWarnings(requiredHasDefaultMsg(param.Name, param.In)) 101 } 102 103 // reset explored schemas to get depth-first recursive-proof exploration 104 d.resetVisited() 105 106 // Check simple parameters first 107 // default values provided must validate against their inline definition (no explicit schema) 108 if param.Default != nil && param.Schema == nil { 109 // check param default value is valid 110 red := NewParamValidator(¶m, s.KnownFormats).Validate(param.Default) //#nosec 111 if red.HasErrorsOrWarnings() { 112 res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In)) 113 res.Merge(red) 114 } 115 } 116 117 // Recursively follows Items and Schemas 118 if param.Items != nil { 119 red := d.validateDefaultValueItemsAgainstSchema(param.Name, param.In, ¶m, param.Items) //#nosec 120 if red.HasErrorsOrWarnings() { 121 res.AddErrors(defaultValueItemsDoesNotValidateMsg(param.Name, param.In)) 122 res.Merge(red) 123 } 124 } 125 126 if param.Schema != nil { 127 // Validate default value against schema 128 red := d.validateDefaultValueSchemaAgainstSchema(param.Name, param.In, param.Schema) 129 if red.HasErrorsOrWarnings() { 130 res.AddErrors(defaultValueDoesNotValidateMsg(param.Name, param.In)) 131 res.Merge(red) 132 } 133 } 134 } 135 136 if op.Responses != nil { 137 if op.Responses.Default != nil { 138 // Same constraint on default Response 139 res.Merge(d.validateDefaultInResponse(op.Responses.Default, jsonDefault, path, 0, op.ID)) 140 } 141 // Same constraint on regular Responses 142 if op.Responses.StatusCodeResponses != nil { // Safeguard 143 for code, r := range op.Responses.StatusCodeResponses { 144 res.Merge(d.validateDefaultInResponse(&r, "response", path, code, op.ID)) //#nosec 145 } 146 } 147 } else if op.ID != "" { 148 // Empty op.ID means there is no meaningful operation: no need to report a specific message 149 res.AddErrors(noValidResponseMsg(op.ID)) 150 } 151 } 152 } 153 if s.spec.Spec().Definitions != nil { // Safeguard 154 // reset explored schemas to get depth-first recursive-proof exploration 155 d.resetVisited() 156 for nm, sch := range s.spec.Spec().Definitions { 157 res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("definitions.%s", nm), "body", &sch)) //#nosec 158 } 159 } 160 return res 161} 162 163func (d *defaultValidator) validateDefaultInResponse(resp *spec.Response, responseType, path string, responseCode int, operationID string) *Result { 164 s := d.SpecValidator 165 166 response, res := responseHelp.expandResponseRef(resp, path, s) 167 if !res.IsValid() { 168 return res 169 } 170 171 responseName, responseCodeAsStr := responseHelp.responseMsgVariants(responseType, responseCode) 172 173 // nolint: dupl 174 if response.Headers != nil { // Safeguard 175 for nm, h := range response.Headers { 176 // reset explored schemas to get depth-first recursive-proof exploration 177 d.resetVisited() 178 179 if h.Default != nil { 180 red := NewHeaderValidator(nm, &h, s.KnownFormats).Validate(h.Default) //#nosec 181 if red.HasErrorsOrWarnings() { 182 res.AddErrors(defaultValueHeaderDoesNotValidateMsg(operationID, nm, responseName)) 183 res.Merge(red) 184 } 185 } 186 187 // Headers have inline definition, like params 188 if h.Items != nil { 189 red := d.validateDefaultValueItemsAgainstSchema(nm, "header", &h, h.Items) //#nosec 190 if red.HasErrorsOrWarnings() { 191 res.AddErrors(defaultValueHeaderItemsDoesNotValidateMsg(operationID, nm, responseName)) 192 res.Merge(red) 193 } 194 } 195 196 if _, err := compileRegexp(h.Pattern); err != nil { 197 res.AddErrors(invalidPatternInHeaderMsg(operationID, nm, responseName, h.Pattern, err)) 198 } 199 200 // Headers don't have schema 201 } 202 } 203 if response.Schema != nil { 204 // reset explored schemas to get depth-first recursive-proof exploration 205 d.resetVisited() 206 207 red := d.validateDefaultValueSchemaAgainstSchema(responseCodeAsStr, "response", response.Schema) 208 if red.HasErrorsOrWarnings() { 209 // Additional message to make sure the context of the error is not lost 210 res.AddErrors(defaultValueInDoesNotValidateMsg(operationID, responseName)) 211 res.Merge(red) 212 } 213 } 214 return res 215} 216 217func (d *defaultValidator) validateDefaultValueSchemaAgainstSchema(path, in string, schema *spec.Schema) *Result { 218 if schema == nil || d.isVisited(path) { 219 // Avoids recursing if we are already done with that check 220 return nil 221 } 222 d.beingVisited(path) 223 res := new(Result) 224 s := d.SpecValidator 225 226 if schema.Default != nil { 227 res.Merge(NewSchemaValidator(schema, s.spec.Spec(), path+".default", s.KnownFormats, SwaggerSchema(true)).Validate(schema.Default)) 228 } 229 if schema.Items != nil { 230 if schema.Items.Schema != nil { 231 res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+".items.default", in, schema.Items.Schema)) 232 } 233 // Multiple schemas in items 234 if schema.Items.Schemas != nil { // Safeguard 235 for i, sch := range schema.Items.Schemas { 236 res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.items[%d].default", path, i), in, &sch)) //#nosec 237 } 238 } 239 } 240 if _, err := compileRegexp(schema.Pattern); err != nil { 241 res.AddErrors(invalidPatternInMsg(path, in, schema.Pattern)) 242 } 243 if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil { 244 // NOTE: we keep validating values, even though additionalItems is not supported by Swagger 2.0 (and 3.0 as well) 245 res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalItems", path), in, schema.AdditionalItems.Schema)) 246 } 247 for propName, prop := range schema.Properties { 248 res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec 249 } 250 for propName, prop := range schema.PatternProperties { 251 res.Merge(d.validateDefaultValueSchemaAgainstSchema(path+"."+propName, in, &prop)) //#nosec 252 } 253 if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { 254 res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.additionalProperties", path), in, schema.AdditionalProperties.Schema)) 255 } 256 if schema.AllOf != nil { 257 for i, aoSch := range schema.AllOf { 258 res.Merge(d.validateDefaultValueSchemaAgainstSchema(fmt.Sprintf("%s.allOf[%d]", path, i), in, &aoSch)) //#nosec 259 } 260 } 261 return res 262} 263 264// TODO: Temporary duplicated code. Need to refactor with examples 265// nolint: dupl 266func (d *defaultValidator) validateDefaultValueItemsAgainstSchema(path, in string, root interface{}, items *spec.Items) *Result { 267 res := new(Result) 268 s := d.SpecValidator 269 if items != nil { 270 if items.Default != nil { 271 res.Merge(newItemsValidator(path, in, items, root, s.KnownFormats).Validate(0, items.Default)) 272 } 273 if items.Items != nil { 274 res.Merge(d.validateDefaultValueItemsAgainstSchema(path+"[0].default", in, root, items.Items)) 275 } 276 if _, err := compileRegexp(items.Pattern); err != nil { 277 res.AddErrors(invalidPatternInMsg(path, in, items.Pattern)) 278 } 279 } 280 return res 281} 282