@@ -70,21 +70,66 @@ func (p *Properties) Clear() {
7070 p .Properties = make (map [string ]interface {})
7171}
7272
73- // isValidPropertyValue checks if a value is a valid property type
73+ // IsPropertyValueValid reports whether value is a valid OpenGraph property value.
74+ //
75+ // The BloodHound OpenGraph schema restricts a property value to a single
76+ // primitive (string, number, or boolean) or a homogeneous array of primitives.
77+ // null values, nested objects, arrays of objects, and arrays mixing primitive
78+ // types are not valid.
79+ //
80+ // Source: https://bloodhound.specterops.io/opengraph/developer/nodes
7481func (p * Properties ) IsPropertyValueValid (value interface {}) bool {
7582 if value == nil {
76- return true
83+ return false
84+ }
85+
86+ switch reflect .TypeOf (value ).Kind () {
87+ case reflect .Slice , reflect .Array :
88+ return isHomogeneousPrimitiveSequence (reflect .ValueOf (value ))
89+ default :
90+ return primitiveCategory (value ) != ""
91+ }
92+ }
93+
94+ // primitiveCategory classifies value into one of the OpenGraph primitive
95+ // categories ("string", "number", "boolean"). It returns "" when value is nil
96+ // or not a primitive (e.g. an object, slice, or array).
97+ func primitiveCategory (value interface {}) string {
98+ if value == nil {
99+ return ""
77100 }
78101
79- valueType := reflect .TypeOf (value )
80- switch valueType .Kind () {
81- case reflect .String , reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 ,
102+ switch reflect .TypeOf (value ).Kind () {
103+ case reflect .String :
104+ return "string"
105+ case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 ,
82106 reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 ,
83- reflect .Float32 , reflect .Float64 , reflect .Bool , reflect .Slice :
84- return true
107+ reflect .Float32 , reflect .Float64 :
108+ return "number"
109+ case reflect .Bool :
110+ return "boolean"
85111 default :
86- return false
112+ return ""
113+ }
114+ }
115+
116+ // isHomogeneousPrimitiveSequence reports whether v is a slice or array whose
117+ // elements are all primitives belonging to the same OpenGraph category. An
118+ // empty sequence is considered valid.
119+ func isHomogeneousPrimitiveSequence (v reflect.Value ) bool {
120+ category := ""
121+ for i := 0 ; i < v .Len (); i ++ {
122+ c := primitiveCategory (v .Index (i ).Interface ())
123+ if c == "" {
124+ return false
125+ }
126+ if category == "" {
127+ category = c
128+ } else if c != category {
129+ return false
130+ }
87131 }
132+ return true
88133}
89134
90135// ToDict converts properties to map for JSON serialization
0 commit comments