github.com/jfrog/frogbot@v1.1.1-0.20231221090046-821a26f50338/packagehandlers/pythonpackagehandler.go (about) 1 package packagehandlers 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/jfrog/frogbot/utils" 7 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 8 "os" 9 "path/filepath" 10 "regexp" 11 "strings" 12 ) 13 14 const ( 15 16 // Package names are case-insensitive with this prefix 17 PythonPackageRegexPrefix = "(?i)" 18 // Match all possible operators and versions syntax 19 PythonPackageRegexSuffix = "\\s*(([\\=\\<\\>\\~]=)|([\\>\\<]))\\s*(\\.|\\d)*(\\d|(\\.\\*))(\\,\\s*(([\\=\\<\\>\\~]=)|([\\>\\<])).*\\s*(\\.|\\d)*(\\d|(\\.\\*)))?" 20 ) 21 22 // PythonPackageHandler Handles all the python package mangers as they share behavior 23 type PythonPackageHandler struct { 24 pipRequirementsFile string 25 CommonPackageHandler 26 } 27 28 func (py *PythonPackageHandler) UpdateDependency(vulnDetails *utils.VulnerabilityDetails) error { 29 if vulnDetails.IsDirectDependency { 30 return py.updateDirectDependency(vulnDetails) 31 } 32 33 return &utils.ErrUnsupportedFix{ 34 PackageName: vulnDetails.ImpactedDependencyName, 35 FixedVersion: vulnDetails.SuggestedFixedVersion, 36 ErrorType: utils.IndirectDependencyFixNotSupported, 37 } 38 } 39 40 func (py *PythonPackageHandler) updateDirectDependency(vulnDetails *utils.VulnerabilityDetails) (err error) { 41 switch vulnDetails.Technology { 42 case coreutils.Poetry: 43 return py.handlePoetry(vulnDetails) 44 case coreutils.Pip: 45 return py.handlePip(vulnDetails) 46 case coreutils.Pipenv: 47 return py.CommonPackageHandler.UpdateDependency(vulnDetails, vulnDetails.Technology.GetPackageInstallationCommand()) 48 default: 49 return errors.New("unknown python package manger: " + vulnDetails.Technology.GetPackageType()) 50 } 51 } 52 53 func (py *PythonPackageHandler) handlePoetry(vulnDetails *utils.VulnerabilityDetails) (err error) { 54 // Install the desired fixed version 55 if err = py.CommonPackageHandler.UpdateDependency(vulnDetails, vulnDetails.Technology.GetPackageInstallationCommand()); err != nil { 56 return 57 } 58 // Update Poetry lock file as well 59 return runPackageMangerCommand(coreutils.Poetry.GetExecCommandName(), coreutils.Poetry.String(), []string{"update"}) 60 } 61 62 func (py *PythonPackageHandler) handlePip(vulnDetails *utils.VulnerabilityDetails) (err error) { 63 var fixedFile string 64 // This function assumes that the version of the dependencies is statically pinned in the requirements file or inside the 'install_requires' array in the setup.py file 65 fixedPackage := vulnDetails.ImpactedDependencyName + "==" + vulnDetails.SuggestedFixedVersion 66 if py.pipRequirementsFile == "" { 67 py.pipRequirementsFile = "setup.py" 68 } 69 wd, err := os.Getwd() 70 if err != nil { 71 return 72 } 73 fullPath := filepath.Join(wd, py.pipRequirementsFile) 74 if !strings.HasPrefix(filepath.Clean(fullPath), wd) { 75 return errors.New("wrong requirements file input") 76 } 77 data, err := os.ReadFile(filepath.Clean(py.pipRequirementsFile)) 78 if err != nil { 79 return errors.New("an error occurred while attempting to read the requirements file:\n" + err.Error()) 80 } 81 currentFile := string(data) 82 83 // Check both original and lowered package name and replace to only one lowered result 84 // This regex will match the impactedPackage with it's pinned version e.py. PyJWT==1.7.1 85 re := regexp.MustCompile(PythonPackageRegexPrefix + "(" + vulnDetails.ImpactedDependencyName + "|" + strings.ToLower(vulnDetails.ImpactedDependencyName) + ")" + PythonPackageRegexSuffix) 86 if packageToReplace := re.FindString(currentFile); packageToReplace != "" { 87 fixedFile = strings.Replace(currentFile, packageToReplace, strings.ToLower(fixedPackage), 1) 88 } 89 if fixedFile == "" { 90 return fmt.Errorf("impacted package %s not found, fix failed", vulnDetails.ImpactedDependencyName) 91 } 92 if err = os.WriteFile(py.pipRequirementsFile, []byte(fixedFile), 0600); err != nil { 93 err = fmt.Errorf("an error occured while writing the fixed version of %s to the requirements file:\n%s", vulnDetails.SuggestedFixedVersion, err.Error()) 94 } 95 return 96 }