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