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  }