github.com/rigado/snapd@v2.42.5-go-mod+incompatible/asserts/ifacedecls.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2017 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 asserts 21 22 import ( 23 "errors" 24 "fmt" 25 "reflect" 26 "regexp" 27 "strconv" 28 "strings" 29 ) 30 31 // AttrMatchContext has contextual helpers for evaluating attribute constraints. 32 type AttrMatchContext interface { 33 PlugAttr(arg string) (interface{}, error) 34 SlotAttr(arg string) (interface{}, error) 35 } 36 37 const ( 38 // feature label for $SLOT()/$PLUG()/$MISSING 39 dollarAttrConstraintsFeature = "dollar-attr-constraints" 40 // feature label for on-store/on-brand/on-model 41 deviceScopeConstraintsFeature = "device-scope-constraints" 42 ) 43 44 type attrMatcher interface { 45 match(apath string, v interface{}, ctx AttrMatchContext) error 46 47 feature(flabel string) bool 48 } 49 50 func chain(path, k string) string { 51 if path == "" { 52 return k 53 } 54 return fmt.Sprintf("%s.%s", path, k) 55 } 56 57 type compileContext struct { 58 dotted string 59 hadMap bool 60 wasAlt bool 61 } 62 63 func (cc compileContext) String() string { 64 return cc.dotted 65 } 66 67 func (cc compileContext) keyEntry(k string) compileContext { 68 return compileContext{ 69 dotted: chain(cc.dotted, k), 70 hadMap: true, 71 wasAlt: false, 72 } 73 } 74 75 func (cc compileContext) alt(alt int) compileContext { 76 return compileContext{ 77 dotted: fmt.Sprintf("%s/alt#%d/", cc.dotted, alt+1), 78 hadMap: cc.hadMap, 79 wasAlt: true, 80 } 81 } 82 83 // compileAttrMatcher compiles an attrMatcher derived from constraints, 84 func compileAttrMatcher(cc compileContext, constraints interface{}) (attrMatcher, error) { 85 switch x := constraints.(type) { 86 case map[string]interface{}: 87 return compileMapAttrMatcher(cc, x) 88 case []interface{}: 89 if cc.wasAlt { 90 return nil, fmt.Errorf("cannot nest alternative constraints directly at %q", cc) 91 } 92 return compileAltAttrMatcher(cc, x) 93 case string: 94 if !cc.hadMap { 95 return nil, fmt.Errorf("first level of non alternative constraints must be a set of key-value contraints") 96 } 97 if strings.HasPrefix(x, "$") { 98 if x == "$MISSING" { 99 return missingAttrMatcher{}, nil 100 } 101 return compileEvalAttrMatcher(cc, x) 102 } 103 return compileRegexpAttrMatcher(cc, x) 104 default: 105 return nil, fmt.Errorf("constraint %q must be a key-value map, regexp or a list of alternative constraints: %v", cc, x) 106 } 107 } 108 109 type mapAttrMatcher map[string]attrMatcher 110 111 func compileMapAttrMatcher(cc compileContext, m map[string]interface{}) (attrMatcher, error) { 112 matcher := make(mapAttrMatcher) 113 for k, constraint := range m { 114 matcher1, err := compileAttrMatcher(cc.keyEntry(k), constraint) 115 if err != nil { 116 return nil, err 117 } 118 matcher[k] = matcher1 119 } 120 return matcher, nil 121 } 122 123 func matchEntry(apath, k string, matcher1 attrMatcher, v interface{}, ctx AttrMatchContext) error { 124 apath = chain(apath, k) 125 // every entry matcher expects the attribute to be set except for $MISSING 126 if _, ok := matcher1.(missingAttrMatcher); !ok && v == nil { 127 return fmt.Errorf("attribute %q has constraints but is unset", apath) 128 } 129 if err := matcher1.match(apath, v, ctx); err != nil { 130 return err 131 } 132 return nil 133 } 134 135 func matchList(apath string, matcher attrMatcher, l []interface{}, ctx AttrMatchContext) error { 136 for i, elem := range l { 137 if err := matcher.match(chain(apath, strconv.Itoa(i)), elem, ctx); err != nil { 138 return err 139 } 140 } 141 return nil 142 } 143 144 func (matcher mapAttrMatcher) feature(flabel string) bool { 145 for _, matcher1 := range matcher { 146 if matcher1.feature(flabel) { 147 return true 148 } 149 } 150 return false 151 } 152 153 func (matcher mapAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 154 switch x := v.(type) { 155 case Attrer: 156 // we get Atter from root-level Check (apath is "") 157 for k, matcher1 := range matcher { 158 v, _ := x.Lookup(k) 159 if err := matchEntry("", k, matcher1, v, ctx); err != nil { 160 return err 161 } 162 } 163 case map[string]interface{}: // maps in attributes look like this 164 for k, matcher1 := range matcher { 165 if err := matchEntry(apath, k, matcher1, x[k], ctx); err != nil { 166 return err 167 } 168 } 169 case []interface{}: 170 return matchList(apath, matcher, x, ctx) 171 default: 172 return fmt.Errorf("attribute %q must be a map", apath) 173 } 174 return nil 175 } 176 177 type missingAttrMatcher struct{} 178 179 func (matcher missingAttrMatcher) feature(flabel string) bool { 180 return flabel == dollarAttrConstraintsFeature 181 } 182 183 func (matcher missingAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 184 if v != nil { 185 return fmt.Errorf("attribute %q is constrained to be missing but is set", apath) 186 } 187 return nil 188 } 189 190 type evalAttrMatcher struct { 191 // first iteration supports just $(SLOT|PLUG)(arg) 192 op string 193 arg string 194 } 195 196 var ( 197 validEvalAttrMatcher = regexp.MustCompile(`^\$(SLOT|PLUG)\((.+)\)$`) 198 ) 199 200 func compileEvalAttrMatcher(cc compileContext, s string) (attrMatcher, error) { 201 ops := validEvalAttrMatcher.FindStringSubmatch(s) 202 if len(ops) == 0 { 203 return nil, fmt.Errorf("cannot compile %q constraint %q: not a valid $SLOT()/$PLUG() constraint", cc, s) 204 } 205 return evalAttrMatcher{ 206 op: ops[1], 207 arg: ops[2], 208 }, nil 209 } 210 211 func (matcher evalAttrMatcher) feature(flabel string) bool { 212 return flabel == dollarAttrConstraintsFeature 213 } 214 215 func (matcher evalAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 216 if ctx == nil { 217 return fmt.Errorf("attribute %q cannot be matched without context", apath) 218 } 219 var comp func(string) (interface{}, error) 220 switch matcher.op { 221 case "SLOT": 222 comp = ctx.SlotAttr 223 case "PLUG": 224 comp = ctx.PlugAttr 225 } 226 v1, err := comp(matcher.arg) 227 if err != nil { 228 return fmt.Errorf("attribute %q constraint $%s(%s) cannot be evaluated: %v", apath, matcher.op, matcher.arg, err) 229 } 230 if !reflect.DeepEqual(v, v1) { 231 return fmt.Errorf("attribute %q does not match $%s(%s): %v != %v", apath, matcher.op, matcher.arg, v, v1) 232 } 233 return nil 234 } 235 236 type regexpAttrMatcher struct { 237 *regexp.Regexp 238 } 239 240 func compileRegexpAttrMatcher(cc compileContext, s string) (attrMatcher, error) { 241 rx, err := regexp.Compile("^(" + s + ")$") 242 if err != nil { 243 return nil, fmt.Errorf("cannot compile %q constraint %q: %v", cc, s, err) 244 } 245 return regexpAttrMatcher{rx}, nil 246 } 247 248 func (matcher regexpAttrMatcher) feature(flabel string) bool { 249 return false 250 } 251 252 func (matcher regexpAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 253 var s string 254 switch x := v.(type) { 255 case string: 256 s = x 257 case bool: 258 s = strconv.FormatBool(x) 259 case int64: 260 s = strconv.FormatInt(x, 10) 261 case []interface{}: 262 return matchList(apath, matcher, x, ctx) 263 default: 264 return fmt.Errorf("attribute %q must be a scalar or list", apath) 265 } 266 if !matcher.Regexp.MatchString(s) { 267 return fmt.Errorf("attribute %q value %q does not match %v", apath, s, matcher.Regexp) 268 } 269 return nil 270 271 } 272 273 type altAttrMatcher struct { 274 alts []attrMatcher 275 } 276 277 func compileAltAttrMatcher(cc compileContext, l []interface{}) (attrMatcher, error) { 278 alts := make([]attrMatcher, len(l)) 279 for i, constraint := range l { 280 matcher1, err := compileAttrMatcher(cc.alt(i), constraint) 281 if err != nil { 282 return nil, err 283 } 284 alts[i] = matcher1 285 } 286 return altAttrMatcher{alts}, nil 287 288 } 289 290 func (matcher altAttrMatcher) feature(flabel string) bool { 291 for _, alt := range matcher.alts { 292 if alt.feature(flabel) { 293 return true 294 } 295 } 296 return false 297 } 298 299 func (matcher altAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 300 var firstErr error 301 for _, alt := range matcher.alts { 302 err := alt.match(apath, v, ctx) 303 if err == nil { 304 return nil 305 } 306 if firstErr == nil { 307 firstErr = err 308 } 309 } 310 apathDescr := "" 311 if apath != "" { 312 apathDescr = fmt.Sprintf(" for attribute %q", apath) 313 } 314 return fmt.Errorf("no alternative%s matches: %v", apathDescr, firstErr) 315 } 316 317 // AttributeConstraints implements a set of constraints on the attributes of a slot or plug. 318 type AttributeConstraints struct { 319 matcher attrMatcher 320 } 321 322 func (ac *AttributeConstraints) feature(flabel string) bool { 323 return ac.matcher.feature(flabel) 324 } 325 326 // compileAttributeConstraints checks and compiles a mapping or list 327 // from the assertion format into AttributeConstraints. 328 func compileAttributeConstraints(constraints interface{}) (*AttributeConstraints, error) { 329 matcher, err := compileAttrMatcher(compileContext{}, constraints) 330 if err != nil { 331 return nil, err 332 } 333 return &AttributeConstraints{matcher: matcher}, nil 334 } 335 336 type fixedAttrMatcher struct { 337 result error 338 } 339 340 func (matcher fixedAttrMatcher) feature(flabel string) bool { 341 return false 342 } 343 344 func (matcher fixedAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error { 345 return matcher.result 346 } 347 348 var ( 349 AlwaysMatchAttributes = &AttributeConstraints{matcher: fixedAttrMatcher{nil}} 350 NeverMatchAttributes = &AttributeConstraints{matcher: fixedAttrMatcher{errors.New("not allowed")}} 351 ) 352 353 // Attrer reflects part of the Attrer interface (see interfaces.Attrer). 354 type Attrer interface { 355 Lookup(path string) (interface{}, bool) 356 } 357 358 // Check checks whether attrs don't match the constraints. 359 func (c *AttributeConstraints) Check(attrer Attrer, ctx AttrMatchContext) error { 360 return c.matcher.match("", attrer, ctx) 361 } 362 363 // OnClassicConstraint specifies a constraint based whether the system is classic and optional specific distros' sets. 364 type OnClassicConstraint struct { 365 Classic bool 366 SystemIDs []string 367 } 368 369 // DeviceScopeConstraint specifies a constraints based on which brand 370 // store, brand or model the device belongs to. 371 type DeviceScopeConstraint struct { 372 Store []string 373 Brand []string 374 // Model is a list of precise "<brand>/<model>" constraints 375 Model []string 376 } 377 378 var ( 379 validStoreID = regexp.MustCompile("^[-A-Z0-9a-z_]+$") 380 validBrandSlashModel = regexp.MustCompile("^(" + 381 strings.Trim(validAccountID.String(), "^$") + 382 ")/(" + 383 strings.Trim(validModel.String(), "^$") + 384 ")$") 385 deviceScopeConstraints = map[string]*regexp.Regexp{ 386 "on-store": validStoreID, 387 "on-brand": validAccountID, 388 // on-model constraints are of the form list of 389 // <brand>/<model> strings where <brand> are account 390 // IDs as they appear in the respective model assertion 391 "on-model": validBrandSlashModel, 392 } 393 ) 394 395 func detectDeviceScopeConstraint(cMap map[string]interface{}) bool { 396 // for consistency and simplicity we support all of on-store, 397 // on-brand, and on-model to appear together. The interpretation 398 // layer will AND them as usual 399 for field := range deviceScopeConstraints { 400 if cMap[field] != nil { 401 return true 402 } 403 } 404 return false 405 } 406 407 func compileDeviceScopeConstraint(cMap map[string]interface{}, context string) (constr *DeviceScopeConstraint, err error) { 408 // initial map size of 2: we expect usual cases to have just one of the 409 // constraints or rarely 2 410 deviceConstr := make(map[string][]string, 2) 411 for field, validRegexp := range deviceScopeConstraints { 412 vals, err := checkStringListInMap(cMap, field, fmt.Sprintf("%s in %s", field, context), validRegexp) 413 if err != nil { 414 return nil, err 415 } 416 deviceConstr[field] = vals 417 } 418 419 if len(deviceConstr) == 0 { 420 return nil, fmt.Errorf("internal error: misdetected device scope constraints in %s", context) 421 } 422 return &DeviceScopeConstraint{ 423 Store: deviceConstr["on-store"], 424 Brand: deviceConstr["on-brand"], 425 Model: deviceConstr["on-model"], 426 }, nil 427 } 428 429 // rules 430 431 var ( 432 validSnapType = regexp.MustCompile("^(?:core|kernel|gadget|app)$") 433 validDistro = regexp.MustCompile("^[-0-9a-z._]+$") 434 validSnapID = regexp.MustCompile("^[a-z0-9A-Z]{32}$") // snap-ids look like this 435 validPublisher = regexp.MustCompile("^(?:[a-z0-9A-Z]{32}|[-a-z0-9]{2,28}|\\$[A-Z][A-Z0-9_]*)$") // account ids look like snap-ids or are nice identifiers, support our own special markers $MARKER 436 437 validIDConstraints = map[string]*regexp.Regexp{ 438 "slot-snap-type": validSnapType, 439 "slot-snap-id": validSnapID, 440 "slot-publisher-id": validPublisher, 441 "plug-snap-type": validSnapType, 442 "plug-snap-id": validSnapID, 443 "plug-publisher-id": validPublisher, 444 } 445 ) 446 447 func checkMapOrShortcut(context string, v interface{}) (m map[string]interface{}, invert bool, err error) { 448 switch x := v.(type) { 449 case map[string]interface{}: 450 return x, false, nil 451 case string: 452 switch x { 453 case "true": 454 return nil, false, nil 455 case "false": 456 return nil, true, nil 457 } 458 } 459 return nil, false, errors.New("unexpected type") 460 } 461 462 type constraintsHolder interface { 463 setAttributeConstraints(field string, cstrs *AttributeConstraints) 464 setIDConstraints(field string, cstrs []string) 465 setOnClassicConstraint(onClassic *OnClassicConstraint) 466 setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) 467 } 468 469 func baseCompileConstraints(context string, cDef constraintsDef, target constraintsHolder, attrConstraints, idConstraints []string) error { 470 cMap := cDef.cMap 471 if cMap == nil { 472 fixed := AlwaysMatchAttributes // "true" 473 if cDef.invert { // "false" 474 fixed = NeverMatchAttributes 475 } 476 for _, field := range attrConstraints { 477 target.setAttributeConstraints(field, fixed) 478 } 479 return nil 480 } 481 defaultUsed := 0 482 for _, field := range idConstraints { 483 lst, err := checkStringListInMap(cMap, field, fmt.Sprintf("%s in %s", field, context), validIDConstraints[field]) 484 if err != nil { 485 return err 486 } 487 if lst == nil { 488 defaultUsed++ 489 } 490 target.setIDConstraints(field, lst) 491 } 492 for _, field := range attrConstraints { 493 cstrs := AlwaysMatchAttributes 494 v := cMap[field] 495 if v != nil { 496 var err error 497 cstrs, err = compileAttributeConstraints(cMap[field]) 498 if err != nil { 499 return fmt.Errorf("cannot compile %s in %s: %v", field, context, err) 500 } 501 } else { 502 defaultUsed++ 503 } 504 target.setAttributeConstraints(field, cstrs) 505 } 506 onClassic := cMap["on-classic"] 507 if onClassic == nil { 508 defaultUsed++ 509 } else { 510 var c *OnClassicConstraint 511 switch x := onClassic.(type) { 512 case string: 513 switch x { 514 case "true": 515 c = &OnClassicConstraint{Classic: true} 516 case "false": 517 c = &OnClassicConstraint{Classic: false} 518 } 519 case []interface{}: 520 lst, err := checkStringListInMap(cMap, "on-classic", fmt.Sprintf("on-classic in %s", context), validDistro) 521 if err != nil { 522 return err 523 } 524 c = &OnClassicConstraint{Classic: true, SystemIDs: lst} 525 } 526 if c == nil { 527 return fmt.Errorf("on-classic in %s must be 'true', 'false' or a list of operating system IDs", context) 528 } 529 target.setOnClassicConstraint(c) 530 } 531 if !detectDeviceScopeConstraint(cMap) { 532 defaultUsed++ 533 } else { 534 c, err := compileDeviceScopeConstraint(cMap, context) 535 if err != nil { 536 return err 537 } 538 target.setDeviceScopeConstraint(c) 539 } 540 // checks whether defaults have been used for everything, which is not 541 // well-formed 542 // +1+1 accounts for defaults for missing on-classic plus missing 543 // on-store/on-brand/on-model 544 if defaultUsed == len(attributeConstraints)+len(idConstraints)+1+1 { 545 return fmt.Errorf("%s must specify at least one of %s, %s, on-classic, on-store, on-brand, on-model", context, strings.Join(attrConstraints, ", "), strings.Join(idConstraints, ", ")) 546 } 547 return nil 548 } 549 550 type rule interface { 551 setConstraints(field string, cstrs []constraintsHolder) 552 } 553 554 type constraintsDef struct { 555 cMap map[string]interface{} 556 invert bool 557 } 558 559 type subruleCompiler func(context string, def constraintsDef) (constraintsHolder, error) 560 561 func baseCompileRule(context string, rule interface{}, target rule, subrules []string, compilers map[string]subruleCompiler, defaultOutcome, invertedOutcome map[string]interface{}) error { 562 rMap, invert, err := checkMapOrShortcut(context, rule) 563 if err != nil { 564 return fmt.Errorf("%s must be a map or one of the shortcuts 'true' or 'false'", context) 565 } 566 if rMap == nil { 567 rMap = defaultOutcome // "true" 568 if invert { 569 rMap = invertedOutcome // "false" 570 } 571 } 572 defaultUsed := 0 573 // compile and set subrules 574 for _, subrule := range subrules { 575 v := rMap[subrule] 576 var lst []interface{} 577 alternatives := false 578 switch x := v.(type) { 579 case nil: 580 v = defaultOutcome[subrule] 581 defaultUsed++ 582 case []interface{}: 583 alternatives = true 584 lst = x 585 } 586 if lst == nil { // v is map or a string, checked below 587 lst = []interface{}{v} 588 } 589 compiler := compilers[subrule] 590 if compiler == nil { 591 panic(fmt.Sprintf("no compiler for %s in %s", subrule, context)) 592 } 593 alts := make([]constraintsHolder, len(lst)) 594 for i, alt := range lst { 595 subctxt := fmt.Sprintf("%s in %s", subrule, context) 596 if alternatives { 597 subctxt = fmt.Sprintf("alternative %d of %s", i+1, subctxt) 598 } 599 cMap, invert, err := checkMapOrShortcut(subctxt, alt) 600 if err != nil || (cMap == nil && alternatives) { 601 efmt := "%s must be a map" 602 if !alternatives { 603 efmt = "%s must be a map or one of the shortcuts 'true' or 'false'" 604 } 605 return fmt.Errorf(efmt, subctxt) 606 } 607 608 cstrs, err := compiler(subctxt, constraintsDef{ 609 cMap: cMap, 610 invert: invert, 611 }) 612 if err != nil { 613 return err 614 } 615 alts[i] = cstrs 616 } 617 target.setConstraints(subrule, alts) 618 } 619 if defaultUsed == len(subrules) { 620 return fmt.Errorf("%s must specify at least one of %s", context, strings.Join(subrules, ", ")) 621 } 622 return nil 623 } 624 625 // PlugRule holds the rule of what is allowed, wrt installation and 626 // connection, for a plug of a specific interface for a snap. 627 type PlugRule struct { 628 Interface string 629 630 AllowInstallation []*PlugInstallationConstraints 631 DenyInstallation []*PlugInstallationConstraints 632 633 AllowConnection []*PlugConnectionConstraints 634 DenyConnection []*PlugConnectionConstraints 635 636 AllowAutoConnection []*PlugConnectionConstraints 637 DenyAutoConnection []*PlugConnectionConstraints 638 } 639 640 func (r *PlugRule) feature(flabel string) bool { 641 for _, cs := range [][]*PlugInstallationConstraints{r.AllowInstallation, r.DenyInstallation} { 642 for _, c := range cs { 643 if c.feature(flabel) { 644 return true 645 } 646 } 647 } 648 649 for _, cs := range [][]*PlugConnectionConstraints{r.AllowConnection, r.DenyConnection, r.AllowAutoConnection, r.DenyAutoConnection} { 650 for _, c := range cs { 651 if c.feature(flabel) { 652 return true 653 } 654 } 655 } 656 657 return false 658 } 659 660 func castPlugInstallationConstraints(cstrs []constraintsHolder) (res []*PlugInstallationConstraints) { 661 res = make([]*PlugInstallationConstraints, len(cstrs)) 662 for i, cstr := range cstrs { 663 res[i] = cstr.(*PlugInstallationConstraints) 664 } 665 return res 666 } 667 668 func castPlugConnectionConstraints(cstrs []constraintsHolder) (res []*PlugConnectionConstraints) { 669 res = make([]*PlugConnectionConstraints, len(cstrs)) 670 for i, cstr := range cstrs { 671 res[i] = cstr.(*PlugConnectionConstraints) 672 } 673 return res 674 } 675 676 func (r *PlugRule) setConstraints(field string, cstrs []constraintsHolder) { 677 if len(cstrs) == 0 { 678 panic(fmt.Sprintf("cannot set PlugRule field %q to empty", field)) 679 } 680 switch cstrs[0].(type) { 681 case *PlugInstallationConstraints: 682 switch field { 683 case "allow-installation": 684 r.AllowInstallation = castPlugInstallationConstraints(cstrs) 685 return 686 case "deny-installation": 687 r.DenyInstallation = castPlugInstallationConstraints(cstrs) 688 return 689 } 690 case *PlugConnectionConstraints: 691 switch field { 692 case "allow-connection": 693 r.AllowConnection = castPlugConnectionConstraints(cstrs) 694 return 695 case "deny-connection": 696 r.DenyConnection = castPlugConnectionConstraints(cstrs) 697 return 698 case "allow-auto-connection": 699 r.AllowAutoConnection = castPlugConnectionConstraints(cstrs) 700 return 701 case "deny-auto-connection": 702 r.DenyAutoConnection = castPlugConnectionConstraints(cstrs) 703 return 704 } 705 } 706 panic(fmt.Sprintf("cannot set PlugRule field %q with %T elements", field, cstrs[0])) 707 } 708 709 // PlugInstallationConstraints specifies a set of constraints on an interface plug relevant to the installation of snap. 710 type PlugInstallationConstraints struct { 711 PlugSnapTypes []string 712 713 PlugAttributes *AttributeConstraints 714 715 OnClassic *OnClassicConstraint 716 717 DeviceScope *DeviceScopeConstraint 718 } 719 720 func (c *PlugInstallationConstraints) feature(flabel string) bool { 721 if flabel == deviceScopeConstraintsFeature { 722 return c.DeviceScope != nil 723 } 724 return c.PlugAttributes.feature(flabel) 725 } 726 727 func (c *PlugInstallationConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) { 728 switch field { 729 case "plug-attributes": 730 c.PlugAttributes = cstrs 731 default: 732 panic("unknown PlugInstallationConstraints field " + field) 733 } 734 } 735 736 func (c *PlugInstallationConstraints) setIDConstraints(field string, cstrs []string) { 737 switch field { 738 case "plug-snap-type": 739 c.PlugSnapTypes = cstrs 740 default: 741 panic("unknown PlugInstallationConstraints field " + field) 742 } 743 } 744 745 func (c *PlugInstallationConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) { 746 c.OnClassic = onClassic 747 } 748 749 func (c *PlugInstallationConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { 750 c.DeviceScope = deviceScope 751 } 752 753 func compilePlugInstallationConstraints(context string, cDef constraintsDef) (constraintsHolder, error) { 754 plugInstCstrs := &PlugInstallationConstraints{} 755 err := baseCompileConstraints(context, cDef, plugInstCstrs, []string{"plug-attributes"}, []string{"plug-snap-type"}) 756 if err != nil { 757 return nil, err 758 } 759 return plugInstCstrs, nil 760 } 761 762 // PlugConnectionConstraints specfies a set of constraints on an 763 // interface plug for a snap relevant to its connection or 764 // auto-connection. 765 type PlugConnectionConstraints struct { 766 SlotSnapTypes []string 767 SlotSnapIDs []string 768 SlotPublisherIDs []string 769 770 PlugAttributes *AttributeConstraints 771 SlotAttributes *AttributeConstraints 772 773 OnClassic *OnClassicConstraint 774 775 DeviceScope *DeviceScopeConstraint 776 } 777 778 func (c *PlugConnectionConstraints) feature(flabel string) bool { 779 if flabel == deviceScopeConstraintsFeature { 780 return c.DeviceScope != nil 781 } 782 return c.PlugAttributes.feature(flabel) || c.SlotAttributes.feature(flabel) 783 } 784 785 func (c *PlugConnectionConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) { 786 switch field { 787 case "plug-attributes": 788 c.PlugAttributes = cstrs 789 case "slot-attributes": 790 c.SlotAttributes = cstrs 791 default: 792 panic("unknown PlugConnectionConstraints field " + field) 793 } 794 } 795 796 func (c *PlugConnectionConstraints) setIDConstraints(field string, cstrs []string) { 797 switch field { 798 case "slot-snap-type": 799 c.SlotSnapTypes = cstrs 800 case "slot-snap-id": 801 c.SlotSnapIDs = cstrs 802 case "slot-publisher-id": 803 c.SlotPublisherIDs = cstrs 804 default: 805 panic("unknown PlugConnectionConstraints field " + field) 806 } 807 } 808 809 func (c *PlugConnectionConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) { 810 c.OnClassic = onClassic 811 } 812 813 func (c *PlugConnectionConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { 814 c.DeviceScope = deviceScope 815 } 816 817 var ( 818 attributeConstraints = []string{"plug-attributes", "slot-attributes"} 819 plugIDConstraints = []string{"slot-snap-type", "slot-publisher-id", "slot-snap-id"} 820 ) 821 822 func compilePlugConnectionConstraints(context string, cDef constraintsDef) (constraintsHolder, error) { 823 plugConnCstrs := &PlugConnectionConstraints{} 824 err := baseCompileConstraints(context, cDef, plugConnCstrs, attributeConstraints, plugIDConstraints) 825 if err != nil { 826 return nil, err 827 } 828 return plugConnCstrs, nil 829 } 830 831 var ( 832 defaultOutcome = map[string]interface{}{ 833 "allow-installation": "true", 834 "allow-connection": "true", 835 "allow-auto-connection": "true", 836 "deny-installation": "false", 837 "deny-connection": "false", 838 "deny-auto-connection": "false", 839 } 840 841 invertedOutcome = map[string]interface{}{ 842 "allow-installation": "false", 843 "allow-connection": "false", 844 "allow-auto-connection": "false", 845 "deny-installation": "true", 846 "deny-connection": "true", 847 "deny-auto-connection": "true", 848 } 849 850 ruleSubrules = []string{"allow-installation", "deny-installation", "allow-connection", "deny-connection", "allow-auto-connection", "deny-auto-connection"} 851 ) 852 853 var plugRuleCompilers = map[string]subruleCompiler{ 854 "allow-installation": compilePlugInstallationConstraints, 855 "deny-installation": compilePlugInstallationConstraints, 856 "allow-connection": compilePlugConnectionConstraints, 857 "deny-connection": compilePlugConnectionConstraints, 858 "allow-auto-connection": compilePlugConnectionConstraints, 859 "deny-auto-connection": compilePlugConnectionConstraints, 860 } 861 862 func compilePlugRule(interfaceName string, rule interface{}) (*PlugRule, error) { 863 context := fmt.Sprintf("plug rule for interface %q", interfaceName) 864 plugRule := &PlugRule{ 865 Interface: interfaceName, 866 } 867 err := baseCompileRule(context, rule, plugRule, ruleSubrules, plugRuleCompilers, defaultOutcome, invertedOutcome) 868 if err != nil { 869 return nil, err 870 } 871 return plugRule, nil 872 } 873 874 // SlotRule holds the rule of what is allowed, wrt installation and 875 // connection, for a slot of a specific interface for a snap. 876 type SlotRule struct { 877 Interface string 878 879 AllowInstallation []*SlotInstallationConstraints 880 DenyInstallation []*SlotInstallationConstraints 881 882 AllowConnection []*SlotConnectionConstraints 883 DenyConnection []*SlotConnectionConstraints 884 885 AllowAutoConnection []*SlotConnectionConstraints 886 DenyAutoConnection []*SlotConnectionConstraints 887 } 888 889 func castSlotInstallationConstraints(cstrs []constraintsHolder) (res []*SlotInstallationConstraints) { 890 res = make([]*SlotInstallationConstraints, len(cstrs)) 891 for i, cstr := range cstrs { 892 res[i] = cstr.(*SlotInstallationConstraints) 893 } 894 return res 895 } 896 897 func (r *SlotRule) feature(flabel string) bool { 898 for _, cs := range [][]*SlotInstallationConstraints{r.AllowInstallation, r.DenyInstallation} { 899 for _, c := range cs { 900 if c.feature(flabel) { 901 return true 902 } 903 } 904 } 905 906 for _, cs := range [][]*SlotConnectionConstraints{r.AllowConnection, r.DenyConnection, r.AllowAutoConnection, r.DenyAutoConnection} { 907 for _, c := range cs { 908 if c.feature(flabel) { 909 return true 910 } 911 } 912 } 913 914 return false 915 } 916 917 func castSlotConnectionConstraints(cstrs []constraintsHolder) (res []*SlotConnectionConstraints) { 918 res = make([]*SlotConnectionConstraints, len(cstrs)) 919 for i, cstr := range cstrs { 920 res[i] = cstr.(*SlotConnectionConstraints) 921 } 922 return res 923 } 924 925 func (r *SlotRule) setConstraints(field string, cstrs []constraintsHolder) { 926 if len(cstrs) == 0 { 927 panic(fmt.Sprintf("cannot set SlotRule field %q to empty", field)) 928 } 929 switch cstrs[0].(type) { 930 case *SlotInstallationConstraints: 931 switch field { 932 case "allow-installation": 933 r.AllowInstallation = castSlotInstallationConstraints(cstrs) 934 return 935 case "deny-installation": 936 r.DenyInstallation = castSlotInstallationConstraints(cstrs) 937 return 938 } 939 case *SlotConnectionConstraints: 940 switch field { 941 case "allow-connection": 942 r.AllowConnection = castSlotConnectionConstraints(cstrs) 943 return 944 case "deny-connection": 945 r.DenyConnection = castSlotConnectionConstraints(cstrs) 946 return 947 case "allow-auto-connection": 948 r.AllowAutoConnection = castSlotConnectionConstraints(cstrs) 949 return 950 case "deny-auto-connection": 951 r.DenyAutoConnection = castSlotConnectionConstraints(cstrs) 952 return 953 } 954 } 955 panic(fmt.Sprintf("cannot set SlotRule field %q with %T elements", field, cstrs[0])) 956 } 957 958 // SlotInstallationConstraints specifies a set of constraints on an 959 // interface slot relevant to the installation of snap. 960 type SlotInstallationConstraints struct { 961 SlotSnapTypes []string 962 963 SlotAttributes *AttributeConstraints 964 965 OnClassic *OnClassicConstraint 966 967 DeviceScope *DeviceScopeConstraint 968 } 969 970 func (c *SlotInstallationConstraints) feature(flabel string) bool { 971 if flabel == deviceScopeConstraintsFeature { 972 return c.DeviceScope != nil 973 } 974 return c.SlotAttributes.feature(flabel) 975 } 976 977 func (c *SlotInstallationConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) { 978 switch field { 979 case "slot-attributes": 980 c.SlotAttributes = cstrs 981 default: 982 panic("unknown SlotInstallationConstraints field " + field) 983 } 984 } 985 986 func (c *SlotInstallationConstraints) setIDConstraints(field string, cstrs []string) { 987 switch field { 988 case "slot-snap-type": 989 c.SlotSnapTypes = cstrs 990 default: 991 panic("unknown SlotInstallationConstraints field " + field) 992 } 993 } 994 995 func (c *SlotInstallationConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) { 996 c.OnClassic = onClassic 997 } 998 999 func (c *SlotInstallationConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { 1000 c.DeviceScope = deviceScope 1001 } 1002 1003 func compileSlotInstallationConstraints(context string, cDef constraintsDef) (constraintsHolder, error) { 1004 slotInstCstrs := &SlotInstallationConstraints{} 1005 err := baseCompileConstraints(context, cDef, slotInstCstrs, []string{"slot-attributes"}, []string{"slot-snap-type"}) 1006 if err != nil { 1007 return nil, err 1008 } 1009 return slotInstCstrs, nil 1010 } 1011 1012 // SlotConnectionConstraints specfies a set of constraints on an 1013 // interface slot for a snap relevant to its connection or 1014 // auto-connection. 1015 type SlotConnectionConstraints struct { 1016 PlugSnapTypes []string 1017 PlugSnapIDs []string 1018 PlugPublisherIDs []string 1019 1020 SlotAttributes *AttributeConstraints 1021 PlugAttributes *AttributeConstraints 1022 1023 OnClassic *OnClassicConstraint 1024 1025 DeviceScope *DeviceScopeConstraint 1026 } 1027 1028 func (c *SlotConnectionConstraints) feature(flabel string) bool { 1029 if flabel == deviceScopeConstraintsFeature { 1030 return c.DeviceScope != nil 1031 } 1032 return c.PlugAttributes.feature(flabel) || c.SlotAttributes.feature(flabel) 1033 } 1034 1035 func (c *SlotConnectionConstraints) setAttributeConstraints(field string, cstrs *AttributeConstraints) { 1036 switch field { 1037 case "plug-attributes": 1038 c.PlugAttributes = cstrs 1039 case "slot-attributes": 1040 c.SlotAttributes = cstrs 1041 default: 1042 panic("unknown SlotConnectionConstraints field " + field) 1043 } 1044 } 1045 1046 func (c *SlotConnectionConstraints) setIDConstraints(field string, cstrs []string) { 1047 switch field { 1048 case "plug-snap-type": 1049 c.PlugSnapTypes = cstrs 1050 case "plug-snap-id": 1051 c.PlugSnapIDs = cstrs 1052 case "plug-publisher-id": 1053 c.PlugPublisherIDs = cstrs 1054 default: 1055 panic("unknown SlotConnectionConstraints field " + field) 1056 } 1057 } 1058 1059 var ( 1060 slotIDConstraints = []string{"plug-snap-type", "plug-publisher-id", "plug-snap-id"} 1061 ) 1062 1063 func (c *SlotConnectionConstraints) setOnClassicConstraint(onClassic *OnClassicConstraint) { 1064 c.OnClassic = onClassic 1065 } 1066 1067 func (c *SlotConnectionConstraints) setDeviceScopeConstraint(deviceScope *DeviceScopeConstraint) { 1068 c.DeviceScope = deviceScope 1069 } 1070 1071 func compileSlotConnectionConstraints(context string, cDef constraintsDef) (constraintsHolder, error) { 1072 slotConnCstrs := &SlotConnectionConstraints{} 1073 err := baseCompileConstraints(context, cDef, slotConnCstrs, attributeConstraints, slotIDConstraints) 1074 if err != nil { 1075 return nil, err 1076 } 1077 return slotConnCstrs, nil 1078 } 1079 1080 var slotRuleCompilers = map[string]subruleCompiler{ 1081 "allow-installation": compileSlotInstallationConstraints, 1082 "deny-installation": compileSlotInstallationConstraints, 1083 "allow-connection": compileSlotConnectionConstraints, 1084 "deny-connection": compileSlotConnectionConstraints, 1085 "allow-auto-connection": compileSlotConnectionConstraints, 1086 "deny-auto-connection": compileSlotConnectionConstraints, 1087 } 1088 1089 func compileSlotRule(interfaceName string, rule interface{}) (*SlotRule, error) { 1090 context := fmt.Sprintf("slot rule for interface %q", interfaceName) 1091 slotRule := &SlotRule{ 1092 Interface: interfaceName, 1093 } 1094 err := baseCompileRule(context, rule, slotRule, ruleSubrules, slotRuleCompilers, defaultOutcome, invertedOutcome) 1095 if err != nil { 1096 return nil, err 1097 } 1098 return slotRule, nil 1099 }