gitee.com/mysnapcore/mysnapd@v0.1.0/polkit/validate/validate.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package validate 21 22 import ( 23 "encoding/xml" 24 "fmt" 25 "io" 26 "strings" 27 ) 28 29 type Element struct { 30 CharData string `xml:",chardata"` 31 UnknownAttrs []xml.Attr `xml:",any,attr"` 32 UnknownChildren []xml.Name `xml:",any"` 33 } 34 35 type policyConfig struct { 36 XMLName xml.Name `xml:"policyconfig"` 37 Element 38 39 Vendor []Element `xml:"vendor"` 40 VendorURL []Element `xml:"vendor_url"` 41 IconName []Element `xml:"icon_name"` 42 43 Actions []action `xml:"action"` 44 } 45 46 type action struct { 47 Element 48 49 ID string `xml:"id,attr"` 50 51 Vendor []Element `xml:"vendor"` 52 VendorURL []Element `xml:"vendor_url"` 53 IconName []Element `xml:"icon_name"` 54 55 Description []message `xml:"description"` 56 Message []message `xml:"message"` 57 Defaults []defaults `xml:"defaults"` 58 Annotate []annotate `xml:"annotate"` 59 } 60 61 type message struct { 62 Element 63 64 GettextDomain string `xml:"gettext-domain,attr"` 65 Language string `xml:"lang,attr"` // to match xml:lang 66 } 67 68 type defaults struct { 69 Element 70 71 AllowAny []Element `xml:"allow_any"` 72 AllowInactive []Element `xml:"allow_inactive"` 73 AllowActive []Element `xml:"allow_active"` 74 } 75 76 type annotate struct { 77 Element 78 79 Key string `xml:"key,attr"` 80 } 81 82 func ValidatePolicy(r io.Reader) (actionIDs []string, err error) { 83 decoder := xml.NewDecoder(r) 84 var config policyConfig 85 if err := decoder.Decode(&config); err != nil { 86 return nil, err 87 } 88 // check for additional data after the root element 89 if err := decoder.Decode(new(interface{})); err != io.EOF { 90 return nil, fmt.Errorf("invalid XML: additional data after root element") 91 } 92 93 return validateConfig(config) 94 } 95 96 func validateConfig(config policyConfig) ([]string, error) { 97 if config.XMLName != (xml.Name{Local: "policyconfig"}) { 98 return nil, fmt.Errorf("root element must be <policyconfig>") 99 } 100 101 if err := validateElement(config.Element, "<policyconfig>", 0); err != nil { 102 return nil, err 103 } 104 105 if err := validateOptionalProperty(config.Vendor, "<vendor>", "<policyconfig>"); err != nil { 106 return nil, err 107 } 108 if err := validateOptionalProperty(config.VendorURL, "<vendor_url>", "<policyconfig>"); err != nil { 109 return nil, err 110 } 111 if err := validateOptionalProperty(config.IconName, "<icon_name>", "<policyconfig>"); err != nil { 112 return nil, err 113 } 114 115 seenIDs := make(map[string]struct{}) 116 for _, a := range config.Actions { 117 if err := validateAction(a, seenIDs); err != nil { 118 return nil, err 119 } 120 } 121 122 actionIDs := make([]string, 0, len(seenIDs)) 123 for id := range seenIDs { 124 actionIDs = append(actionIDs, id) 125 } 126 return actionIDs, nil 127 } 128 129 type validateFlags int 130 131 const ( 132 allowCharData validateFlags = 1 << 1 133 ) 134 135 func validateElement(element Element, name string, flags validateFlags) error { 136 if len(element.UnknownAttrs) != 0 { 137 return fmt.Errorf("%s element contains unexpected attributes", name) 138 } 139 if len(element.UnknownChildren) != 0 { 140 return fmt.Errorf("%s element contains unexpected children", name) 141 } 142 if flags&allowCharData == 0 && len(strings.TrimSpace(element.CharData)) != 0 { 143 return fmt.Errorf("%s element contains unexpected character data", name) 144 } 145 return nil 146 } 147 148 func validateOptionalProperty(prop []Element, name, parent string) error { 149 switch len(prop) { 150 case 0: 151 // nothing 152 case 1: 153 if err := validateElement(prop[0], name, allowCharData); err != nil { 154 return err 155 } 156 if len(strings.TrimSpace(prop[0].CharData)) == 0 { 157 return fmt.Errorf("%s element has no character data", name) 158 } 159 default: 160 return fmt.Errorf("multiple %s elements found under %s", name, parent) 161 } 162 return nil 163 } 164 165 func validateAction(action action, seenIDs map[string]struct{}) error { 166 if err := validateElement(action.Element, "<action>", 0); err != nil { 167 return err 168 } 169 170 if action.ID == "" { 171 return fmt.Errorf("<action> elements must have an ID") 172 } 173 seenIDs[action.ID] = struct{}{} 174 175 if err := validateOptionalProperty(action.Vendor, "<vendor>", "<action>"); err != nil { 176 return err 177 } 178 if err := validateOptionalProperty(action.VendorURL, "<vendor_url>", "<action>"); err != nil { 179 return err 180 } 181 if err := validateOptionalProperty(action.IconName, "<icon_name>", "<action>"); err != nil { 182 return err 183 } 184 185 // There must be at least one description 186 if len(action.Description) == 0 { 187 return fmt.Errorf("<action> element missing <description> child") 188 } 189 for _, d := range action.Description { 190 if err := validateElement(d.Element, "<description>", allowCharData); err != nil { 191 return err 192 } 193 } 194 195 // There must be at least one message 196 if len(action.Message) == 0 { 197 return fmt.Errorf("<action> element missing <message> child") 198 } 199 for _, m := range action.Message { 200 if err := validateElement(m.Element, "<message>", allowCharData); err != nil { 201 return err 202 } 203 } 204 205 // Check defaults 206 if err := validateActionDefaults(action.Defaults); err != nil { 207 return err 208 } 209 210 // Check annotations 211 for _, annotation := range action.Annotate { 212 if err := validateElement(annotation.Element, "<annotate>", allowCharData); err != nil { 213 return err 214 } 215 if len(annotation.Key) == 0 { 216 return fmt.Errorf("<annotate> elements must have a key attribute") 217 } 218 value := strings.TrimSpace(annotation.CharData) 219 if len(value) == 0 { 220 return fmt.Errorf("<annotate> elements must contain character data") 221 } 222 223 // Is this a known annotation? 224 switch annotation.Key { 225 case "org.freedesktop.policykit.imply": 226 // Value contains a space separated list of action IDs 227 for _, id := range strings.Fields(value) { 228 seenIDs[id] = struct{}{} 229 } 230 231 default: 232 return fmt.Errorf("unsupported annotation %q", annotation.Key) 233 } 234 } 235 236 return nil 237 } 238 239 func validateActionDefaults(defaults []defaults) error { 240 switch len(defaults) { 241 case 0: 242 return nil 243 case 1: 244 // nothing 245 default: 246 return fmt.Errorf("<action> element has multiple <defaults> children") 247 } 248 249 d := defaults[0] 250 if err := validateElement(d.Element, "<defaults>", 0); err != nil { 251 return err 252 } 253 if err := validateDefaultAuth(d.AllowAny, "<allow_any>"); err != nil { 254 return err 255 } 256 if err := validateDefaultAuth(d.AllowInactive, "<allow_inactive>"); err != nil { 257 return err 258 } 259 if err := validateDefaultAuth(d.AllowActive, "<allow_active>"); err != nil { 260 return err 261 } 262 263 return nil 264 } 265 266 func validateDefaultAuth(auth []Element, name string) error { 267 switch len(auth) { 268 case 0: 269 // nothing 270 case 1: 271 if err := validateElement(auth[0], name, allowCharData); err != nil { 272 return err 273 } 274 value := strings.TrimSpace(auth[0].CharData) 275 switch value { 276 case "no", "yes", "auth_self", "auth_admin", "auth_self_keep", "auth_admin_keep": 277 // nothing 278 default: 279 return fmt.Errorf("invalid value for %s: %q", name, value) 280 } 281 default: 282 return fmt.Errorf("multiple %s elements found under <defaults>", name) 283 } 284 return nil 285 }