| 
 | 1 | +package remediation  | 
 | 2 | + | 
 | 3 | +import (  | 
 | 4 | +	"github.com/snyk/cli-extension-os-flows/internal/semver"  | 
 | 5 | +)  | 
 | 6 | + | 
 | 7 | +func FixesToRemediationSummary(findings []Finding) (Summary, error) {  | 
 | 8 | +	pins, err := calculatePins(findings)  | 
 | 9 | +	if err != nil {  | 
 | 10 | +		return Summary{}, err  | 
 | 11 | +	}  | 
 | 12 | +	upgrades, err := calculateUpgrades(findings)  | 
 | 13 | +	if err != nil {  | 
 | 14 | +		return Summary{}, err  | 
 | 15 | +	}  | 
 | 16 | +	return Summary{Pins: pins, Upgrades: upgrades}, err  | 
 | 17 | +}  | 
 | 18 | + | 
 | 19 | +func calculateUpgrades(findings []Finding) ([]Upgrade, error) {  | 
 | 20 | +	var output []Upgrade  | 
 | 21 | +	for _, finding := range findings {  | 
 | 22 | +		switch finding.Fix.(type) {  | 
 | 23 | +		case UpgradeFix:  | 
 | 24 | +			for _, depPath := range finding.DependencyPaths {  | 
 | 25 | +				for _, upath := range finding.Fix.(UpgradeFix).UpgradeAction.UpgradePaths {  | 
 | 26 | +					valid := true  | 
 | 27 | +					for i, uDep := range upath {  | 
 | 28 | +						if uDep.Name != depPath[i+1].Name {  | 
 | 29 | +							valid = false  | 
 | 30 | +						}  | 
 | 31 | +					}  | 
 | 32 | +					if valid {  | 
 | 33 | +						output = append(output, Upgrade{  | 
 | 34 | +							From: depPath[1],  | 
 | 35 | +							To:   upath[0],  | 
 | 36 | +							Fixes: []VulnerabilityInPackage{  | 
 | 37 | +								{  | 
 | 38 | +									VulnerablePackage: finding.Package,  | 
 | 39 | +									Vulnerability:     finding.Vulnerability,  | 
 | 40 | +									IntroducedThrough: finding.DependencyPaths,  | 
 | 41 | +								},  | 
 | 42 | +							},  | 
 | 43 | +						})  | 
 | 44 | +					}  | 
 | 45 | +				}  | 
 | 46 | +			}  | 
 | 47 | +		default:  | 
 | 48 | +			continue  | 
 | 49 | +		}  | 
 | 50 | +	}  | 
 | 51 | +	return output, nil  | 
 | 52 | +}  | 
 | 53 | + | 
 | 54 | +func calculatePins(findings []Finding) ([]Upgrade, error) {  | 
 | 55 | +	var output []Upgrade  | 
 | 56 | +	var pinMap = make(map[string]*[]*Upgrade)  | 
 | 57 | +	for _, finding := range findings {  | 
 | 58 | +		switch finding.Fix.(type) {  | 
 | 59 | +		case PinFix:  | 
 | 60 | +			vulnerablePackage := finding.Package  | 
 | 61 | +			key := finding.Package.Name  | 
 | 62 | + | 
 | 63 | +			// We'll be grouping by package name  | 
 | 64 | +			upgrade, upgradeExists := pinMap[key]  | 
 | 65 | + | 
 | 66 | +			vulnerabilityInPackage := VulnerabilityInPackage{  | 
 | 67 | +				VulnerablePackage: vulnerablePackage,  | 
 | 68 | +				Vulnerability:     finding.Vulnerability,  | 
 | 69 | +				IntroducedThrough: finding.DependencyPaths,  | 
 | 70 | +			}  | 
 | 71 | + | 
 | 72 | +			if upgradeExists {  | 
 | 73 | +				highestVersion, err := getMaxVersion(finding.PackageManager, (*upgrade)[0].To.Version, finding.Fix.(PinFix).PinAction.Package.Version)  | 
 | 74 | +				if err != nil {  | 
 | 75 | +					return nil, err  | 
 | 76 | +				}  | 
 | 77 | + | 
 | 78 | +				// As we process individual pins, we'll get the highest version of a given package across vulns & package version  | 
 | 79 | +				// Goal is to obtain a single pin that fixes as many problems as possible  | 
 | 80 | +				versionAlreadyExists := false  | 
 | 81 | +				for _, u := range *upgrade {  | 
 | 82 | +					u.To.Version = highestVersion  | 
 | 83 | +					if u.From.Version == finding.Package.Version {  | 
 | 84 | +						u.Fixes = append(u.Fixes, vulnerabilityInPackage)  | 
 | 85 | +						versionAlreadyExists = true  | 
 | 86 | +						break  | 
 | 87 | +					}  | 
 | 88 | +				}  | 
 | 89 | +				if !versionAlreadyExists {  | 
 | 90 | +					*upgrade = append(*upgrade, &Upgrade{  | 
 | 91 | +						From:  vulnerablePackage,  | 
 | 92 | +						To:    finding.Fix.(PinFix).PinAction.Package,  | 
 | 93 | +						Fixes: []VulnerabilityInPackage{vulnerabilityInPackage},  | 
 | 94 | +					})  | 
 | 95 | +				}  | 
 | 96 | +			} else {  | 
 | 97 | +				pinMap[key] = &[]*Upgrade{{  | 
 | 98 | +					From:  vulnerablePackage,  | 
 | 99 | +					To:    finding.Fix.(PinFix).PinAction.Package,  | 
 | 100 | +					Fixes: []VulnerabilityInPackage{vulnerabilityInPackage},  | 
 | 101 | +				}}  | 
 | 102 | +			}  | 
 | 103 | + | 
 | 104 | +		default:  | 
 | 105 | +			continue  | 
 | 106 | +		}  | 
 | 107 | +	}  | 
 | 108 | + | 
 | 109 | +	for _, versionGroupedPins := range pinMap {  | 
 | 110 | +		for _, pin := range *versionGroupedPins {  | 
 | 111 | +			output = append(output, *pin)  | 
 | 112 | +		}  | 
 | 113 | +	}  | 
 | 114 | +	return output, nil  | 
 | 115 | +}  | 
 | 116 | + | 
 | 117 | +func getMaxVersion(packageManager PackageManager, v1 string, v2 string) (string, error) {  | 
 | 118 | +	semverResolver, err := semver.GetSemver(string(packageManager))  | 
 | 119 | +	if err != nil {  | 
 | 120 | +		return "", err  | 
 | 121 | +	}  | 
 | 122 | +	var version string  | 
 | 123 | +	compare, err := semverResolver.Compare(v1, v2)  | 
 | 124 | +	if err != nil {  | 
 | 125 | +		return "", err  | 
 | 126 | +	}  | 
 | 127 | +	if compare >= 0 {  | 
 | 128 | +		version = v1  | 
 | 129 | +	} else {  | 
 | 130 | +		version = v2  | 
 | 131 | +	}  | 
 | 132 | +	return version, nil  | 
 | 133 | +}  | 
0 commit comments