github.com/google/osv-scalibr@v0.4.1/guidedremediation/internal/resolution/resolve.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package resolution provides dependency graph resolution and vulnerability findings
    16  // for guided remediation.
    17  package resolution
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"slices"
    24  
    25  	"deps.dev/util/resolve"
    26  	"deps.dev/util/resolve/dep"
    27  	mavenresolve "deps.dev/util/resolve/maven"
    28  	npmresolve "deps.dev/util/resolve/npm"
    29  	pypiresolve "deps.dev/util/resolve/pypi"
    30  	client "github.com/google/osv-scalibr/clients/resolution"
    31  	"github.com/google/osv-scalibr/guidedremediation/internal/manifest"
    32  	"github.com/google/osv-scalibr/guidedremediation/internal/manifest/maven"
    33  	"github.com/google/osv-scalibr/guidedremediation/options"
    34  	"github.com/google/osv-scalibr/internal/mavenutil"
    35  )
    36  
    37  // Resolve resolves the dependencies in the manifest using the provided client.
    38  func Resolve(ctx context.Context, c resolve.Client, m manifest.Manifest, opts options.ResolutionOptions) (*resolve.Graph, error) {
    39  	// Insert the manifest dependency into the client
    40  	cl := client.NewOverrideClient(c)
    41  	cl.AddVersion(m.Root(), m.Requirements())
    42  	for _, lm := range m.LocalManifests() {
    43  		cl.AddVersion(lm.Root(), lm.Requirements())
    44  	}
    45  
    46  	var r resolve.Resolver
    47  	sys := m.System()
    48  	switch sys {
    49  	case resolve.Maven:
    50  		r = mavenresolve.NewResolver(cl)
    51  	case resolve.NPM:
    52  		r = npmresolve.NewResolver(cl)
    53  	case resolve.PyPI:
    54  		r = pypiresolve.NewResolver(cl)
    55  	case resolve.UnknownSystem:
    56  		fallthrough
    57  	default:
    58  		return nil, fmt.Errorf("no resolver for ecosystem %v", sys)
    59  	}
    60  
    61  	graph, err := r.Resolve(ctx, m.Root().VersionKey)
    62  	if err != nil {
    63  		return nil, fmt.Errorf("error resolving manifest dependencies: %w", err)
    64  	}
    65  
    66  	if graph.Error != "" {
    67  		return nil, fmt.Errorf("manifest resolved with error: %s", graph.Error)
    68  	}
    69  
    70  	return resolvePostProcess(ctx, cl, m, opts, graph)
    71  }
    72  
    73  func resolvePostProcess(ctx context.Context, cl resolve.Client, m manifest.Manifest, opts options.ResolutionOptions, graph *resolve.Graph) (*resolve.Graph, error) {
    74  	if m.System() == resolve.Maven && opts.MavenManagement {
    75  		// Add a node & edge for each dependency in dependencyManagement that doesn't already appear in the resolved graph
    76  		manifestSpecific, ok := m.EcosystemSpecific().(maven.ManifestSpecific)
    77  		if !ok {
    78  			return graph, errors.New("invalid maven ManifestSpecific data")
    79  		}
    80  
    81  		// Search through OriginalRequirements management dependencies in this pom only (not parents).
    82  		for _, req := range manifestSpecific.OriginalRequirements {
    83  			if req.Origin != mavenutil.OriginManagement {
    84  				// TODO(#463): also check management in activated profiles and dependencies in inactive profiles.
    85  				continue
    86  			}
    87  
    88  			// Unique identifier for this package.
    89  			reqKey := MakeRequirementKey(resolve.RequirementVersion{
    90  				VersionKey: resolve.VersionKey{
    91  					PackageKey: resolve.PackageKey{
    92  						System: resolve.Maven,
    93  						Name:   req.Name(),
    94  					},
    95  					VersionType: resolve.Requirement,
    96  					Version:     string(req.Version),
    97  				},
    98  				Type: resolve.MavenDepType(req.Dependency, req.Origin),
    99  			})
   100  
   101  			// Find the current version of the dependencyManagement dependency, after property interpolation & changes from remediation.
   102  			requirements := m.Requirements()
   103  			idx := slices.IndexFunc(requirements, func(rv resolve.RequirementVersion) bool {
   104  				if origin, _ := rv.Type.GetAttr(dep.MavenDependencyOrigin); origin != mavenutil.OriginManagement {
   105  					return false
   106  				}
   107  
   108  				return reqKey == MakeRequirementKey(rv)
   109  			})
   110  
   111  			if idx == -1 {
   112  				// Ideally, this would be an error, but there a few known instances where this lookup fails:
   113  				// 1. The artifact name contain a property (properties aren't substituted in OriginalRequirements, but are in Manifest.Requirements)
   114  				// 2. Missing properties (due to e.g. un-activated profiles) cause the dependency to be invalid, and therefore excluded from Manifest.Requirements.
   115  				// Ignore these dependencies in these cases so that we can still remediation vulns in the other packages.
   116  				continue
   117  			}
   118  
   119  			rv := requirements[idx]
   120  
   121  			// See if the package is already in the resolved graph.
   122  			// Check the edges so we can make sure the ArtifactTypes and Classifiers match.
   123  			if !slices.ContainsFunc(graph.Edges, func(e resolve.Edge) bool {
   124  				return reqKey == MakeRequirementKey(resolve.RequirementVersion{
   125  					VersionKey: graph.Nodes[e.To].Version,
   126  					Type:       e.Type,
   127  				})
   128  			}) {
   129  				// Management dependency not in graph - create the node.
   130  				// Find the version the management requirement would resolve to.
   131  				// First assume it's a soft requirement.
   132  				vk := rv.VersionKey
   133  				vk.VersionType = resolve.Concrete
   134  				if _, err := cl.Version(ctx, vk); err != nil {
   135  					// Not a soft requirement - try find a match.
   136  					vk.VersionType = resolve.Requirement
   137  					vks, err := cl.MatchingVersions(ctx, vk)
   138  					if err != nil || len(vks) == 0 {
   139  						err = graph.AddError(0, vk, fmt.Sprintf("could not find a version that satisfies requirement %s for package %s", vk.Version, vk.Name))
   140  						if err != nil {
   141  							return nil, err
   142  						}
   143  
   144  						continue
   145  					}
   146  					vk = vks[len(vks)-1].VersionKey
   147  				}
   148  				// Add the node & and edge from the root.
   149  				nID := graph.AddNode(vk)
   150  				if err := graph.AddEdge(0, nID, rv.Version, rv.Type.Clone()); err != nil {
   151  					return nil, err
   152  				}
   153  			}
   154  		}
   155  	}
   156  
   157  	return graph, nil
   158  }