github.com/CycloneDX/sbom-utility@v0.16.0/schema/license_policy_config.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 /* 3 * Licensed to the Apache Software Foundation (ASF) under one or more 4 * contributor license agreements. See the NOTICE file distributed with 5 * this work for additional information regarding copyright ownership. 6 * The ASF licenses this file to You under the Apache License, Version 2.0 7 * (the "License"); you may not use this file except in compliance with 8 * the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package schema 20 21 import ( 22 "encoding/json" 23 "fmt" 24 "os" 25 "regexp" 26 "strings" 27 "sync" 28 29 "github.com/CycloneDX/sbom-utility/common" 30 "github.com/CycloneDX/sbom-utility/resources" 31 "github.com/CycloneDX/sbom-utility/utils" 32 "github.com/jwangsadinata/go-multimap/slicemultimap" 33 ) 34 35 const ( 36 POLICY_ALLOW = "allow" 37 POLICY_DENY = "deny" 38 POLICY_NEEDS_REVIEW = "needs-review" 39 POLICY_UNDEFINED = "UNDEFINED" 40 POLICY_CONFLICT = "CONFLICT" 41 ) 42 43 var VALID_USAGE_POLICIES = []string{POLICY_ALLOW, POLICY_DENY, POLICY_NEEDS_REVIEW} 44 var ALL_USAGE_POLICIES = []string{POLICY_ALLOW, POLICY_DENY, POLICY_NEEDS_REVIEW, POLICY_UNDEFINED, POLICY_CONFLICT} 45 46 type LicensePolicy struct { 47 Id string `json:"id"` 48 Reference string `json:"reference"` 49 IsOsiApproved bool `json:"osi"` 50 IsFsfLibre bool `json:"fsf"` 51 IsDeprecated bool `json:"deprecated"` 52 Family string `json:"family"` 53 Name string `json:"name"` 54 UsagePolicy string `json:"usagePolicy"` 55 Aliases []string `json:"aliases"` 56 Children []string `json:"children"` 57 Notes []string `json:"notes"` 58 Urls []string `json:"urls"` 59 AnnotationRefs []string `json:"annotationRefs"` 60 61 // Alternative field names for --where searches 62 AltUsagePolicy string `json:"usage-policy"` 63 AltAnnotationRefs string `json:"annotations"` 64 AltSPDXId string `json:"spdx-id"` 65 } 66 67 type LicensePolicyConfig struct { 68 PolicyList []LicensePolicy `json:"policies"` 69 Annotations map[string]string `json:"annotations"` 70 defaultPolicyConfigFile string 71 policyConfigFile string 72 loadOnce sync.Once 73 hashOnce sync.Once 74 licenseFamilyNameMap *slicemultimap.MultiMap 75 licenseIdMap *slicemultimap.MultiMap 76 filteredFamilyNameMap *slicemultimap.MultiMap 77 } 78 79 func NewLicensePolicyConfig(configFile string) *LicensePolicyConfig { 80 temp := LicensePolicyConfig{ 81 defaultPolicyConfigFile: configFile, 82 policyConfigFile: configFile, 83 } 84 return &temp 85 } 86 87 func (config *LicensePolicyConfig) Reset() { 88 config.policyConfigFile = config.defaultPolicyConfigFile 89 config.PolicyList = nil 90 config.Annotations = nil 91 if config.licenseFamilyNameMap != nil { 92 config.licenseFamilyNameMap.Clear() 93 } 94 if config.licenseIdMap != nil { 95 config.licenseIdMap.Clear() 96 } 97 if config.filteredFamilyNameMap != nil { 98 config.filteredFamilyNameMap.Clear() 99 } 100 } 101 102 func (config *LicensePolicyConfig) GetFamilyNameMap() (hashmap *slicemultimap.MultiMap, err error) { 103 if config.licenseFamilyNameMap == nil { 104 err = config.hashLicensePolicies() 105 } 106 return config.licenseFamilyNameMap, err 107 } 108 109 func (config *LicensePolicyConfig) GetLicenseIdMap() (hashmap *slicemultimap.MultiMap, err error) { 110 if config.licenseIdMap == nil { 111 err = config.hashLicensePolicies() 112 } 113 return config.licenseIdMap, err 114 } 115 116 func (config *LicensePolicyConfig) GetFilteredFamilyNameMap(whereFilters []common.WhereFilter) (hashmap *slicemultimap.MultiMap, err error) { 117 // NOTE: This call is necessary as this will cause all `licensePolicyConfig.PolicyList` 118 // entries to have alternative field names to be mapped (e.g., `usagePolicy` -> `usage-policy`) 119 config.filteredFamilyNameMap, err = config.GetFamilyNameMap() 120 121 if err != nil { 122 return 123 } 124 125 if len(whereFilters) > 0 { 126 // Always use a new filtered hashmap for each filtered list request 127 config.filteredFamilyNameMap = slicemultimap.New() 128 err = config.filteredHashLicensePolicies(whereFilters) 129 } 130 return config.filteredFamilyNameMap, err 131 } 132 133 func (config *LicensePolicyConfig) LoadHashPolicyConfigurationFile(policyFile string, defaultPolicyFile string) (err error) { 134 // Do not pass a default file, it should fail if custom policy cannot be loaded 135 // Only load the policy config. once 136 config.loadOnce.Do(func() { 137 err = config.innerLoadLicensePolicies(policyFile, defaultPolicyFile) 138 if err != nil { 139 return 140 } 141 142 // Note: the HashLicensePolicies function creates new id and name hashmaps 143 // therefore there is no need to clear them 144 err = config.hashLicensePolicies() 145 }) 146 147 return 148 } 149 150 func (config *LicensePolicyConfig) innerLoadLicensePolicies(policyFile string, defaultPolicyFile string) (err error) { 151 getLogger().Enter(policyFile) 152 defer getLogger().Exit() 153 154 var buffer []byte 155 156 // Always reset the config if a new policy file is loaded 157 config.Reset() 158 159 if policyFile != "" { 160 // locate the license policy file 161 config.policyConfigFile, err = utils.FindVerifyConfigFileAbsPath(getLogger(), policyFile) 162 163 if err != nil { 164 return fmt.Errorf("unable to find license policy file: `%s`", policyFile) 165 } 166 167 // attempt to read in contents of the policy config. 168 getLogger().Infof("Loading license policy file: `%s`...", config.policyConfigFile) 169 buffer, err = os.ReadFile(config.policyConfigFile) 170 if err != nil { 171 return fmt.Errorf("unable to `ReadFile`: `%s`", config.policyConfigFile) 172 } 173 } else { 174 // Attempt to load the default config file from embedded file resources 175 getLogger().Infof("Loading (embedded) default license policy file: `%s`...", defaultPolicyFile) 176 buffer, err = resources.LoadConfigFile(defaultPolicyFile) 177 if err != nil { 178 return fmt.Errorf("unable to read schema config file: `%s` from embedded resources: `%s`", 179 defaultPolicyFile, resources.RESOURCES_CONFIG_DIR) 180 } 181 } 182 183 // NOTE: this cleverly unmarshals into the current config instance this function is associated with 184 errUnmarshal := json.Unmarshal(buffer, config) 185 if errUnmarshal != nil { 186 err = fmt.Errorf("cannot `Unmarshal`: `%s`", config.policyConfigFile) 187 return 188 } 189 190 return 191 } 192 193 func (config *LicensePolicyConfig) hashLicensePolicies() (hashError error) { 194 getLogger().Enter() 195 defer getLogger().Exit() 196 197 config.hashOnce.Do(func() { 198 hashError = config.innerHashLicensePolicies() 199 }) 200 return 201 } 202 203 func (config *LicensePolicyConfig) innerHashLicensePolicies() (err error) { 204 getLogger().Enter() 205 defer getLogger().Exit() 206 207 // Note: we only need test to see if one of the maps has not been allocated 208 // and populated to infer neither has 209 config.licenseFamilyNameMap = slicemultimap.New() 210 config.licenseIdMap = slicemultimap.New() 211 212 for i, policy := range config.PolicyList { 213 214 // Map old JSON key names to new key names (as they appear as titles in report columns) 215 216 // Update the original entries in the []PolicyList stored in the global LicenseComplianceConfig 217 getLogger().Debugf("Mapping: `Id`: `%s` to `spdx-id`: `%s`\n", policy.Id, policy.AltSPDXId) 218 config.PolicyList[i].AltSPDXId = policy.Id 219 getLogger().Debugf("Mapping: `UsagePolicy`: `%s` to `usage-policy`: `%s`\n", policy.Id, policy.AltSPDXId) 220 config.PolicyList[i].AltUsagePolicy = policy.UsagePolicy 221 getLogger().Debugf("Mapping: `AnnotationRefs`: `%s` to `annotations`: `%s`\n", policy.Id, policy.AltSPDXId) 222 config.PolicyList[i].AltAnnotationRefs = strings.Join(policy.AnnotationRefs, ",") 223 224 // Actually hash the policy 225 err = config.hashPolicy(config.PolicyList[i]) 226 if err != nil { 227 err = fmt.Errorf("unable to hash policy: %v", config.PolicyList[i]) 228 return 229 } 230 } 231 return 232 } 233 234 // We will take the raw license policy and make it accessible for fast hash lookup 235 // Multiple hash maps are created understanding that license data in SBOMs can be 236 // based upon SPDX IDs <or> license names <or> license family names 237 // NOTE: we allow for both discrete policies based upon SPDX ID as well as 238 // "family" based policies. This means given hash (lookup) might map to one or more 239 // family policies as well as a discrete one for specific SPDX ID. In such cases, 240 // the policy MUST align (i.e., must not have both "allow" and "deny". Therefore, 241 // when we hash we assure that such a conflict does NOT exist at time of creation. 242 func (config *LicensePolicyConfig) hashPolicy(policy LicensePolicy) (err error) { 243 // ONLY hash valid policy records. 244 if !IsValidPolicyEntry(policy) { 245 // Do not add it to any hash table 246 getLogger().Tracef("WARNING: invalid policy entry (id: `%s`, name: `%s`). Skipping...", policy.Id, policy.Name) 247 return 248 } 249 250 // Only add to "id" hashmap if "Id" value is valid 251 // NOTE: do NOT hash entries with "" (empty) Id values; however, they may represent a "family" entry 252 if policy.Id != "" { 253 getLogger().Debugf("ID Hashmap: Adding policy Id=`%s`, Name=`%s`, Family=`%s`", policy.Id, policy.Name, policy.Family) 254 config.licenseIdMap.Put(policy.Id, policy) 255 } else { 256 getLogger().Debugf("WARNING: Skipping policy with no SPDX ID (empty)...") 257 } 258 259 // Assure we are not adding policy (value) to an existing hash 260 // that represents a policy conflict. 261 values, match := config.licenseFamilyNameMap.Get(policy.Family) 262 263 // If a hashmap entry exists, see if current policy matches those 264 // already added for that key 265 if match { 266 getLogger().Debugf("Family Hashmap: Entries exist for policy Id=`%s`, Name=`%s`, Family=`%s`", policy.Id, policy.Name, policy.Family) 267 consistent := VerifyPoliciesMatch(policy, values) 268 269 if !consistent { 270 err = getLogger().Errorf("Multiple (possibly conflicting) policies declared for ID `%s`,family: `%s`, policy: `%s`", 271 policy.Id, 272 policy.Family, 273 policy.UsagePolicy) 274 return 275 } 276 } 277 278 // NOTE: validation of policy struct (including "family" value) is done above 279 getLogger().Debugf("Family Hashmap: Adding policy Id=`%s`, Name=`%s`, Family=`%s`", policy.Id, policy.Name, policy.Family) 280 281 // Do NOT hash entries with and empty "Family" (name) value 282 if policy.Family != "" { 283 getLogger().Debugf("ID Hashmap: Adding policy Id=`%s`, Name=`%s`, Family=`%s`", policy.Id, policy.Name, policy.Family) 284 config.licenseFamilyNameMap.Put(policy.Family, policy) 285 } else { 286 err = getLogger().Errorf("invalid policy: Family: \"\" (empty)") 287 return 288 } 289 290 if len(policy.Children) > 0 { 291 err = config.hashChildPolicies(policy) 292 if err != nil { 293 return 294 } 295 } 296 297 return 298 } 299 300 func (config *LicensePolicyConfig) hashChildPolicies(policy LicensePolicy) (err error) { 301 302 for _, childId := range policy.Children { 303 // Copy of the parent policy and overwrite its "id" with the child's 304 childPolicy := policy 305 childPolicy.Id = childId 306 // Do NOT copy children as this will break recursion 307 childPolicy.Children = nil 308 // No need to copy Notes and Urls which carry "family" information and links 309 childPolicy.Notes = nil 310 childPolicy.Urls = nil 311 312 err = config.hashPolicy(childPolicy) 313 if err != nil { 314 return 315 } 316 } 317 318 return 319 } 320 321 func (config *LicensePolicyConfig) filteredHashLicensePolicies(whereFilters []common.WhereFilter) (err error) { 322 getLogger().Enter() 323 defer getLogger().Exit(err) 324 325 // NOTE: original []PolicyList includes values for both deprecated and current fields 326 // So that filtered "queries" will work regardless (for backwards compatibility) 327 for _, policy := range config.PolicyList { 328 err = config.filteredHashLicensePolicy(policy, whereFilters) 329 if err != nil { 330 return 331 } 332 } 333 return 334 } 335 336 // Hash a CDX Component and recursively those of any "nested" components 337 // TODO we should WARN if version is not a valid semver (e.g., examples/cyclonedx/BOM/laravel-7.12.0/bom.1.3.json) 338 func (config *LicensePolicyConfig) filteredHashLicensePolicy(policy LicensePolicy, whereFilters []common.WhereFilter) (err error) { 339 var match bool = true 340 var mapPolicy map[string]interface{} 341 342 // See if the policy matches where filters criteria 343 if len(whereFilters) > 0 { 344 mapPolicy, err = utils.MarshalStructToJsonMap(policy) 345 if err != nil { 346 return 347 } 348 349 match, err = whereFilterMatch(mapPolicy, whereFilters) 350 if err != nil { 351 return 352 } 353 } 354 355 // Hash policy if it matched where filters 356 if match { 357 getLogger().Debugf("Matched: Hashing Policy: id: %s, family: %s", policy.Id, policy.Family) 358 config.filteredFamilyNameMap.Put(policy.Family, policy) 359 } 360 361 return 362 } 363 364 func (config *LicensePolicyConfig) FindPolicy(licenseInfo LicenseInfo) (matchedPolicy LicensePolicy, err error) { 365 getLogger().Enter() 366 defer getLogger().Exit() 367 368 // Initialize to empty 369 matchedPolicy = LicensePolicy{} 370 371 switch licenseInfo.LicenseChoiceTypeValue { 372 case LC_TYPE_ID: 373 matchedPolicy.UsagePolicy, matchedPolicy, err = config.FindPolicyBySpdxId(licenseInfo.LicenseChoice.License.Id) 374 if err != nil { 375 return 376 } 377 case LC_TYPE_NAME: 378 matchedPolicy.UsagePolicy, matchedPolicy, err = config.FindPolicyByFamilyName(licenseInfo.LicenseChoice.License.Name) 379 if err != nil { 380 return 381 } 382 case LC_TYPE_EXPRESSION: 383 // Parse expression according to SPDX spec. 384 var expressionTree *CompoundExpression 385 expressionTree, err = ParseExpression(config, licenseInfo.LicenseChoice.Expression) 386 if err != nil { 387 return 388 } 389 getLogger().Debugf("Parsed expression:\n%v", expressionTree) 390 matchedPolicy.UsagePolicy = expressionTree.CompoundUsagePolicy 391 } 392 393 if matchedPolicy.UsagePolicy == "" { 394 matchedPolicy.UsagePolicy = POLICY_UNDEFINED 395 } 396 //return matchedPolicy, err 397 return 398 } 399 400 func (config *LicensePolicyConfig) FindPolicyBySpdxId(id string) (policyValue string, matchedPolicy LicensePolicy, err error) { 401 getLogger().Enter("id:", id) 402 defer getLogger().Exit() 403 404 var matched bool 405 var arrPolicies []interface{} 406 407 // Note: this will cause all policy hashmaps to be initialized (created), if it has not bee 408 licensePolicyIdMap, err := config.GetLicenseIdMap() 409 if err != nil { 410 err = getLogger().Errorf("license policy map error: `%w`", err) 411 return 412 } 413 414 arrPolicies, matched = licensePolicyIdMap.Get(id) 415 getLogger().Tracef("licensePolicyMapById.Get(%s): (%v) matches", id, len(arrPolicies)) 416 417 // There MUST be ONLY one policy per (discrete) license ID 418 if len(arrPolicies) > 1 { 419 err = getLogger().Errorf("Multiple (possibly conflicting) policies declared for SPDX ID=`%s`", id) 420 return 421 } 422 423 if matched { 424 // retrieve the usage policy from the single (first) entry 425 matchedPolicy = arrPolicies[0].(LicensePolicy) 426 policyValue = matchedPolicy.UsagePolicy 427 } else { 428 getLogger().Tracef("No policy match found for SPDX ID=`%s` ", id) 429 policyValue = POLICY_UNDEFINED 430 } 431 432 return 433 } 434 435 // NOTE: for now, we will look for the "family" name encoded in the License.Name field 436 // (until) we can get additional fields/properties added to the CDX LicenseChoice schema 437 func (config *LicensePolicyConfig) FindPolicyByFamilyName(name string) (policyValue string, matchedPolicy LicensePolicy, err error) { 438 getLogger().Enter("name:", name) 439 defer getLogger().Exit() 440 441 var matched bool 442 var key string 443 var arrPolicies []interface{} 444 445 // NOTE: we have found some SBOM authors have placed license expressions 446 // within the "name" field. This prevents us from assigning policy 447 // return 448 if hasLogicalConjunctionOrPreposition(name) { 449 getLogger().Warningf("policy name contains logical conjunctions or preposition: `%s`", name) 450 policyValue = POLICY_UNDEFINED 451 return 452 } 453 454 // Note: this will cause all policy hashmaps to be initialized (created), if it has not been 455 familyNameMap, _ := config.GetFamilyNameMap() 456 457 // See if any of the policy family keys contain the family name 458 matched, key, err = config.searchForLicenseFamilyName(name) 459 if err != nil { 460 return 461 } 462 463 if matched { 464 arrPolicies, _ = familyNameMap.Get(key) 465 466 if len(arrPolicies) == 0 { 467 err = getLogger().Errorf("No policy match found in hashmap for family name key: `%s`", key) 468 return 469 } 470 471 // NOTE: We can use the first policy (of a family) as they are 472 // verified to be consistent when loaded from the policy config. file 473 matchedPolicy = arrPolicies[0].(LicensePolicy) 474 policyValue = matchedPolicy.UsagePolicy 475 476 // If we have more than one license in the same family (name), then 477 // check if there are any "usage policy" conflicts to display in report 478 if len(arrPolicies) > 1 { 479 conflict := policyConflictExists(arrPolicies) 480 if conflict { 481 getLogger().Tracef("Usage policy conflict for license family name=`%s` ", name) 482 policyValue = POLICY_CONFLICT 483 } 484 } 485 } else { 486 getLogger().Tracef("No policy match found for license family name=`%s` ", name) 487 policyValue = POLICY_UNDEFINED 488 } 489 490 return 491 } 492 493 // Loop through all known license family names (in hashMap) to see if any 494 // appear in the CDX License "Name" field 495 func (config *LicensePolicyConfig) searchForLicenseFamilyName(licenseName string) (found bool, familyName string, err error) { 496 getLogger().Enter() 497 defer getLogger().Exit() 498 499 familyNameMap, err := config.GetFamilyNameMap() 500 if err != nil { 501 getLogger().Error(err) 502 return 503 } 504 505 keys := familyNameMap.Keys() 506 507 for _, key := range keys { 508 familyName = key.(string) 509 getLogger().Debugf("Searching for familyName: '%s' in License Name: %s", familyName, licenseName) 510 found = containsFamilyName(licenseName, familyName) 511 512 if found { 513 getLogger().Debugf("Match found: familyName: '%s' in License Name: %s", familyName, licenseName) 514 return 515 } 516 } 517 518 return 519 } 520 521 //------------------------------------------------ 522 // License Policy "helper" functions 523 //------------------------------------------------ 524 525 func IsValidUsagePolicy(usagePolicy string) bool { 526 for _, entry := range VALID_USAGE_POLICIES { 527 if usagePolicy == entry { 528 return true 529 } 530 } 531 return false 532 } 533 534 // NOTE: policy.Id == "" we allow as "valid" as this indicates a potential "family" entry (i.e., group of SPDX IDs) 535 func IsValidPolicyEntry(policy LicensePolicy) bool { 536 537 if policy.Id != "" && !IsValidSpdxId(policy.Id) { 538 getLogger().Warningf("invalid SPDX ID: `%s` (Name=`%s`). Skipping...", policy.Id, policy.Name) 539 return false 540 } 541 542 if strings.TrimSpace(policy.Name) == "" { 543 getLogger().Warningf("invalid Name: `%s` (Id=`%s`).", policy.Name, policy.Id) 544 } 545 546 if !IsValidUsagePolicy(policy.UsagePolicy) { 547 getLogger().Warningf("invalid Usage Policy: `%s` (Id=`%s`, Name=`%s`). Skipping...", policy.UsagePolicy, policy.Id, policy.Name) 548 return false 549 } 550 551 if !IsValidFamilyKey(policy.Family) { 552 getLogger().Warningf("invalid Family: `%s` (Id=`%s`, Name=`%s`). Skipping...", policy.Family, policy.Id, policy.Name) 553 return false 554 } 555 556 if policy.Id == "" { 557 if len(policy.Children) < 1 { 558 getLogger().Debugf("Family (policy): `%s`. Has no children (SPDX IDs) listed.", policy.Family) 559 } 560 // Test to make sure "family" entries (i.e. policy.Id == "") have valid "children" (SPDX IDs) 561 for _, childId := range policy.Children { 562 if !IsValidSpdxId(childId) { 563 getLogger().Warningf("invalid Id: `%s` for Family: `%s`. Skipping...", childId, policy.Family) 564 } 565 } 566 } 567 568 // TODO - make sure policies with valid "Id" do NOT have children as these are 569 // intended to be discrete (non-family-grouped) entries 570 return true 571 } 572 573 // given an array of policies verify their "usage" policy does not represent a conflict 574 func VerifyPoliciesMatch(testPolicy LicensePolicy, policies []interface{}) bool { 575 576 var currentPolicy LicensePolicy 577 testUsagePolicy := testPolicy.UsagePolicy 578 579 for _, current := range policies { 580 currentPolicy = current.(LicensePolicy) 581 getLogger().Debugf("Usage Policy=%s", currentPolicy.UsagePolicy) 582 583 if currentPolicy.UsagePolicy != testUsagePolicy { 584 getLogger().Warningf("Policy (Id: %s, Family: %s, Policy: %s) is in conflict with policies (%s) declared in the same family.", 585 currentPolicy.Id, 586 currentPolicy.Family, 587 currentPolicy.UsagePolicy, 588 testUsagePolicy) 589 } 590 } 591 592 return true 593 } 594 595 // NOTE: caller assumes resp. for checking for empty input array 596 func policyConflictExists(arrPolicies []interface{}) bool { 597 var currentUsagePolicy string 598 var policy LicensePolicy 599 600 // Init. usage policy to first entry in array 601 policy = arrPolicies[0].(LicensePolicy) 602 currentUsagePolicy = policy.UsagePolicy 603 604 // Check every subsequent usage policy in array to identify mismatch (i.e., a conflict) 605 for i := 1; i < len(arrPolicies); i++ { 606 policy = arrPolicies[i].(LicensePolicy) 607 if policy.UsagePolicy != currentUsagePolicy { 608 return true 609 } 610 } 611 return false 612 } 613 614 // Looks for an SPDX family (name) somewhere in the CDX License object "Name" field 615 func containsFamilyName(name string, familyName string) bool { 616 // NOTE: we do not currently normalize as we assume family names 617 // are proper substring of SPDX IDs which are mixed case and 618 // should match exactly as encoded. 619 return strings.Contains(name, familyName) 620 } 621 622 // Supported conjunctions and prepositions 623 const ( 624 AND string = "AND" 625 OR string = "OR" 626 WITH string = "WITH" 627 CONJUNCTION_UNDEFINED string = "" 628 ) 629 630 func hasLogicalConjunctionOrPreposition(value string) bool { 631 632 if strings.Contains(value, AND) || 633 strings.Contains(value, OR) || 634 strings.Contains(value, WITH) { 635 return true 636 } 637 return false 638 } 639 640 //------------------------------------------------ 641 // CDX LicenseChoice "helper" functions 642 //------------------------------------------------ 643 644 // "getter" for compiled regex expression 645 func getRegexForValidSpdxId() (regex *regexp.Regexp, err error) { 646 if spdxIdRegexp == nil { 647 regex, err = regexp.Compile(REGEX_VALID_SPDX_ID) 648 } 649 return 650 } 651 652 func IsValidSpdxId(id string) bool { 653 regex, err := getRegexForValidSpdxId() 654 if err != nil { 655 getLogger().Error(fmt.Errorf("unable to invoke regex. %v", err)) 656 return false 657 } 658 return regex.MatchString(id) 659 } 660 661 func IsValidFamilyKey(key string) bool { 662 var BAD_KEYWORDS = []string{"CONFLICT", "UNKNOWN"} 663 664 // For now, valid family keys are subsets of SPDX IDs 665 // Therefore, pass result from that SPDX ID validation function 666 valid := IsValidSpdxId(key) 667 668 // Test for keywords that we have seen set that clearly are not valid family names 669 // TODO: make keywords configurable 670 for _, keyword := range BAD_KEYWORDS { 671 if strings.Contains(strings.ToLower(key), strings.ToLower(keyword)) { 672 return false 673 } 674 } 675 676 return valid 677 }