2020-02-15 22:13:50 +00:00
|
|
|
package accessctl
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2020-02-29 20:02:25 +00:00
|
|
|
"sort"
|
2020-02-15 22:13:50 +00:00
|
|
|
|
|
|
|
"github.com/bmatcuk/doublestar"
|
|
|
|
)
|
|
|
|
|
2020-02-29 20:02:25 +00:00
|
|
|
var (
|
|
|
|
// DefaultChangeAccessControl represents the ChangeAccessControl which is
|
|
|
|
// applied when a changed file's path does not match any defined patterns
|
|
|
|
// within a BranchAccessControl.
|
|
|
|
DefaultChangeAccessControl = ChangeAccessControl{
|
|
|
|
FilePathPattern: "**",
|
|
|
|
Condition: Condition{
|
|
|
|
Signature: &ConditionSignature{
|
|
|
|
AnyAccount: true,
|
|
|
|
Count: "1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultBranchAccessControls represents the BranchAccessControls which are
|
|
|
|
// applied when the name of a branch being interacted with does not match
|
|
|
|
// any defined patterns within the Config.
|
|
|
|
DefaultBranchAccessControls = []BranchAccessControl{
|
|
|
|
// These are currently the same, but they will differ once things like
|
|
|
|
// comments start being implemented.
|
|
|
|
{
|
2020-03-04 19:14:54 +00:00
|
|
|
BranchPattern: "main",
|
2020-02-29 20:02:25 +00:00
|
|
|
ChangeAccessControls: []ChangeAccessControl{DefaultChangeAccessControl},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
BranchPattern: "**",
|
|
|
|
ChangeAccessControls: []ChangeAccessControl{DefaultChangeAccessControl},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// BranchAccessControl represents an access control object defined for the
|
|
|
|
// purpose of controlling who is able to perform what interactions with a
|
|
|
|
// branch.
|
|
|
|
type BranchAccessControl struct {
|
|
|
|
BranchPattern string `yaml:"branch_pattern"`
|
|
|
|
ChangeAccessControls []ChangeAccessControl `yaml:"change_access_controls"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChangeAccessControl represents an access control object being defined in the
|
|
|
|
// Config for the purpose of controlling who is able to change which files.
|
|
|
|
type ChangeAccessControl struct {
|
|
|
|
FilePathPattern string `yaml:"file_path_pattern"`
|
|
|
|
Condition Condition `yaml:"condition"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// MatchInteractions is used as an input to Match to describe all
|
|
|
|
// interactions which are being attempted on a particular Branch.
|
|
|
|
type MatchInteractions struct {
|
|
|
|
// Branch is the name of the branch the interactions are being attempted on.
|
|
|
|
// It is required.
|
|
|
|
Branch string
|
|
|
|
|
|
|
|
// FilePathsChanged is the set of file paths (relative to the repo root)
|
|
|
|
// which have been modified in some way.
|
|
|
|
FilePathsChanged []string
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 20:02:25 +00:00
|
|
|
// MatchedChangeAccessControl contains information about a ChangeAccessControl
|
|
|
|
// which was matched in Match
|
|
|
|
type MatchedChangeAccessControl struct {
|
|
|
|
ChangeAccessControl ChangeAccessControl
|
|
|
|
|
|
|
|
// FilePaths contains all FilePaths to which this access control was found
|
|
|
|
// to be applicable.
|
|
|
|
FilePaths []string
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 20:02:25 +00:00
|
|
|
// MatchResult is the result returned from the Match method.
|
|
|
|
type MatchResult struct {
|
|
|
|
// BranchPattern indicates the BranchPattern field of the
|
|
|
|
// BranchAccessControl object which matched the inputs.
|
|
|
|
BranchPattern string
|
|
|
|
|
|
|
|
// ChangeAccessControls indicates which ChangeAccessControl objects matched
|
|
|
|
// the files being changed.
|
|
|
|
ChangeAccessControls []MatchedChangeAccessControl
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 20:02:25 +00:00
|
|
|
// Match takes in a set of access controls and a set of interactions taking
|
|
|
|
// place, and returns a MatchResult describing the access controls which should
|
|
|
|
// be applied to the interactions.
|
|
|
|
func Match(accessControls []BranchAccessControl, interactions MatchInteractions) (MatchResult, error) {
|
|
|
|
var res MatchResult
|
|
|
|
|
|
|
|
accessControls = append(accessControls, DefaultBranchAccessControls...)
|
|
|
|
|
|
|
|
// find the applicable BranchAccessControl
|
|
|
|
var branchAC BranchAccessControl
|
|
|
|
{
|
|
|
|
var ok bool
|
|
|
|
var err error
|
|
|
|
for i := range accessControls {
|
|
|
|
ok, err = doublestar.Match(accessControls[i].BranchPattern, interactions.Branch)
|
|
|
|
if err != nil {
|
|
|
|
return res, fmt.Errorf("error matching branch %q to pattern %q: %w",
|
|
|
|
accessControls[i].BranchPattern, interactions.Branch, err)
|
2020-02-15 22:13:50 +00:00
|
|
|
} else if ok {
|
2020-02-29 20:02:25 +00:00
|
|
|
branchAC = accessControls[i]
|
2020-02-15 22:13:50 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-02-29 20:02:25 +00:00
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("no patterns matched branch %q, which shouldn't be possible", interactions.Branch))
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|
2020-02-29 20:02:25 +00:00
|
|
|
res.BranchPattern = branchAC.BranchPattern
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 20:02:25 +00:00
|
|
|
// determine ChangeAccessControl for each path in FilesChanged
|
|
|
|
{
|
|
|
|
changeACs := append(branchAC.ChangeAccessControls, DefaultChangeAccessControl)
|
|
|
|
acToPaths := map[ChangeAccessControl][]string{}
|
|
|
|
for _, path := range interactions.FilePathsChanged {
|
|
|
|
var ok bool
|
|
|
|
var err error
|
|
|
|
for _, ac := range changeACs {
|
|
|
|
if ok, err = doublestar.PathMatch(ac.FilePathPattern, path); err != nil {
|
|
|
|
return res, fmt.Errorf("error matching path %q to patterrn %q: %w",
|
|
|
|
path, ac.FilePathPattern, err)
|
|
|
|
} else if ok {
|
|
|
|
acToPaths[ac] = append(acToPaths[ac], path)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("no patterns matched change path %q, which shouldn't be possible", path))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for ac, paths := range acToPaths {
|
|
|
|
res.ChangeAccessControls = append(res.ChangeAccessControls, MatchedChangeAccessControl{
|
|
|
|
ChangeAccessControl: ac,
|
|
|
|
FilePaths: paths,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort result for determinancy
|
|
|
|
sort.Slice(res.ChangeAccessControls, func(i, j int) bool {
|
|
|
|
pi := res.ChangeAccessControls[i].ChangeAccessControl.FilePathPattern
|
|
|
|
pj := res.ChangeAccessControls[j].ChangeAccessControl.FilePathPattern
|
|
|
|
return pi < pj
|
|
|
|
})
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|
2020-02-29 20:02:25 +00:00
|
|
|
|
|
|
|
return res, nil
|
2020-02-15 22:13:50 +00:00
|
|
|
}
|