Skip to content

Commit

Permalink
Added mvn repository support (#22)
Browse files Browse the repository at this point in the history
* Added mvn repository support

* Removed dead code and added MVN option to the readme

* Added change to changelog

Co-authored-by: Rene Schilperoort <rene.schilperoort@greenvalley.nl>
  • Loading branch information
rlschilperoort and Rene Schilperoort committed Feb 25, 2021
1 parent e56947e commit cdd385a
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,10 @@
- Changed
- Fixed a bug where the pip requirements.txt parser processes a 'tilde equals' sign.

- v0.4
- New
- MVN (Maven) support

- v0.3
- New
- PHP (composer) support
Expand Down
16 changes: 14 additions & 2 deletions README.md
@@ -1,7 +1,7 @@
# Confused

A tool for checking for lingering free namespaces for private package names referenced in dependency configuration
for Python (pypi) `requirements.txt`, JavaScript (npm) `package.json` or PHP (composer) `composer.json`.
for Python (pypi) `requirements.txt`, JavaScript (npm) `package.json`, PHP (composer) `composer.json` or MVN (maven) `pom.xml`.

## What is this all about?

Expand Down Expand Up @@ -46,7 +46,7 @@ Usage:
Usage of ./confused:
-l string
Package repository system. Possible values: "pip", "npm", "composer" (default "npm")
Package repository system. Possible values: "pip", "npm", "composer", "mvn" (default "npm")
-s string
Comma-separated list of known-secure namespaces. Supports wildcards
-v Verbose output
Expand Down Expand Up @@ -79,3 +79,15 @@ Issues found, the following packages are not available in public package reposit
Issues found, the following packages are not available in public package repositories:
[!] internal_package1
```


### Maven (mvn)
```
./confused -l mvn pom.xml
Issues found, the following packages are not available in public package repositories:
[!] internal
[!] internal/package1
[!] internal/_package2
```
4 changes: 3 additions & 1 deletion main.go
Expand Up @@ -21,7 +21,7 @@ func main() {
verbose := false
filename := ""
safespaces := ""
flag.StringVar(&lang, "l", "npm", "Package repository system. Possible values: \"pip\", \"npm\", \"composer\"")
flag.StringVar(&lang, "l", "npm", "Package repository system. Possible values: \"pip\", \"npm\", \"composer\", \"mvn\"")
flag.StringVar(&safespaces, "s", "", "Comma-separated list of known-secure namespaces. Supports wildcards")
flag.BoolVar(&verbose, "v", false, "Verbose output")
flag.Parse()
Expand All @@ -40,6 +40,8 @@ func main() {
resolver = NewNPMLookup(verbose)
} else if lang == "composer" {
resolver = NewComposerLookup(verbose)
} else if lang == "mvn" {
resolver = NewMVNLookup(verbose)
} else {
fmt.Printf("Unknown package repository system: %s\n", lang)
os.Exit(1)
Expand Down
120 changes: 120 additions & 0 deletions mvn.go
@@ -0,0 +1,120 @@
package main

import (
"encoding/json"
"encoding/xml"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"time"
)

// NPMLookup represents a collection of npm packages to be tested for dependency confusion.
type MVNLookup struct {
Packages []MVNPackage
Verbose bool
}

type MVNPackage struct {
Group string
Artifact string
Version string
}

// NewNPMLookup constructs an `MVNLookup` struct and returns it.
func NewMVNLookup(verbose bool) PackageResolver {
return &MVNLookup{Packages: []MVNPackage{}, Verbose: verbose}
}

// ReadPackagesFromFile reads package information from an npm package.json file
//
// Returns any errors encountered
func (n *MVNLookup) ReadPackagesFromFile(filename string) error {
rawfile, err := ioutil.ReadFile(filename)
if err != nil {
return err
}

fmt.Print("Checking: filename: " + filename + "\n")

var project MavenProject
if err := xml.Unmarshal([]byte(rawfile), &project); err != nil {
log.Fatalf("unable to unmarshal pom file. Reason: %s\n", err)
}

for _, dep := range project.Dependencies {
n.Packages = append(n.Packages, MVNPackage{dep.GroupId, dep.ArtifactId, dep.Version})
}

for _, dep := range project.Build.Plugins {
n.Packages = append(n.Packages, MVNPackage{dep.GroupId, dep.ArtifactId, dep.Version})
}

for _, build := range project.Profiles {
for _, dep := range build.Build.Plugins {
n.Packages = append(n.Packages, MVNPackage{dep.GroupId, dep.ArtifactId, dep.Version})
}
}

return nil
}

// PackagesNotInPublic determines if an npm package does not exist in the public npm package repository.
//
// Returns a slice of strings with any npm packages not in the public npm package repository
func (n *MVNLookup) PackagesNotInPublic() []string {
notavail := []string{}
for _, pkg := range n.Packages {
if !n.isAvailableInPublic(pkg, 0) {
notavail = append(notavail, pkg.Group + "/" + pkg.Artifact)
}
}
return notavail
}

// isAvailableInPublic determines if an npm package exists in the public npm package repository.
//
// Returns true if the package exists in the public npm package repository.
func (n *MVNLookup) isAvailableInPublic(pkg MVNPackage, retry int) bool {
if retry > 3 {
fmt.Printf(" [W] Maximum number of retries exhausted for package: %s\n", pkg.Group)
return false
}
if pkg.Group == "" {
return true
}

group := strings.Replace(pkg.Group, ".", "/",-1)
if n.Verbose {
fmt.Print("Checking: https://repo1.maven.org/maven2/"+group+"/ ")
}
resp, err := http.Get("https://repo1.maven.org/maven2/"+group+"/")
if err != nil {
fmt.Printf(" [W] Error when trying to request https://repo1.maven.org/maven2/"+group+"/ : %s\n", err)
return false
}
defer resp.Body.Close()
if n.Verbose {
fmt.Printf("%s\n", resp.Status)
}
if resp.StatusCode == http.StatusOK {
npmResp := NpmResponse{}
body, _ := ioutil.ReadAll(resp.Body)
_ = json.Unmarshal(body, &npmResp)
if npmResp.NotAvailable() {
if n.Verbose {
fmt.Printf("[W] Package %s was found, but all its versions are unpublished, making anyone able to takeover the namespace.\n", pkg.Group)
}
return false
}
return true
} else if resp.StatusCode == 429 {
fmt.Printf(" [!] Server responded with 429 (Too many requests), throttling and retrying...\n")
time.Sleep(10 * time.Second)
retry = retry + 1
n.isAvailableInPublic(pkg, retry)
}
return false
}
139 changes: 139 additions & 0 deletions mvnparser.go
@@ -0,0 +1,139 @@
//
// https://raw.githubusercontent.com/creekorful/mvnparser/master/parser.go
//
// MIT License
//
// Copyright (c) 2019 Aloïs Micard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package main

import (
"encoding/xml"
"io"
)

// Represent a POM file
type MavenProject struct {
XMLName xml.Name `xml:"project"`
ModelVersion string `xml:"modelVersion"`
Parent Parent `xml:"parent"`
GroupId string `xml:"groupId"`
ArtifactId string `xml:"artifactId"`
Version string `xml:"version"`
Packaging string `xml:"packaging"`
Name string `xml:"name"`
Repositories []Repository `xml:"repositories>repository"`
Properties Properties `xml:"properties"`
DependencyManagement DependencyManagement `xml:"dependencyManagement"`
Dependencies []Dependency `xml:"dependencies>dependency"`
Profiles []Profile `xml:"profiles"`
Build Build `xml:"build"`
PluginRepositories []PluginRepository `xml:"pluginRepositories>pluginRepository"`
Modules []string `xml:"modules>module"`
}

// Represent the parent of the project
type Parent struct {
GroupId string `xml:"groupId"`
ArtifactId string `xml:"artifactId"`
Version string `xml:"version"`
}

// Represent a dependency of the project
type Dependency struct {
XMLName xml.Name `xml:"dependency"`
GroupId string `xml:"groupId"`
ArtifactId string `xml:"artifactId"`
Version string `xml:"version"`
Classifier string `xml:"classifier"`
Type string `xml:"type"`
Scope string `xml:"scope"`
Exclusions []Exclusion `xml:"exclusions>exclusion"`
}

// Represent an exclusion
type Exclusion struct {
XMLName xml.Name `xml:"exclusion"`
GroupId string `xml:"groupId"`
ArtifactId string `xml:"artifactId"`
}

type DependencyManagement struct {
Dependencies []Dependency `xml:"dependencies>dependency"`
}

// Represent a repository
type Repository struct {
Id string `xml:"id"`
Name string `xml:"name"`
Url string `xml:"url"`
}

type Profile struct {
Id string `xml:"id"`
Build Build `xml:"build"`
}

type Build struct {
// todo: final name ?
Plugins []Plugin `xml:"plugins>plugin"`
}

type Plugin struct {
XMLName xml.Name `xml:"plugin"`
GroupId string `xml:"groupId"`
ArtifactId string `xml:"artifactId"`
Version string `xml:"version"`
//todo something like: Configuration map[string]string `xml:"configuration"`
// todo executions
}

// Represent a pluginRepository
type PluginRepository struct {
Id string `xml:"id"`
Name string `xml:"name"`
Url string `xml:"url"`
}

// Represent Properties
type Properties map[string]string

func (p *Properties) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*p = map[string]string{}
for {
key := ""
value := ""
token, err := d.Token()
if err == io.EOF {
break
}
switch tokenType := token.(type) {
case xml.StartElement:
key = tokenType.Name.Local
err := d.DecodeElement(&value, &start)
if err != nil {
return err
}
(*p)[key] = value
}
}
return nil
}

0 comments on commit cdd385a

Please sign in to comment.