dehub/accessctl/access_control.go
mediocregopher 981fbb8327 Rename trunk branch to main branch
message: |-
  Rename trunk branch to main branch

  The term "trunk" is too close to "branch", imo, and is confusing since its
  inverse term, "thread", isn't related to branch at all. Ultimately I think it's
  best to leave "branch" as a git specific term, and use "main" and "thread" to
  denote the different branch types of dehub.
change_hash: ANitkSWx+QHnxo/d0zg+lrWdeqx1PHqH2GQ2uvyMYZNa
credentials:
- type: pgp_signature
  pub_key_id: 95C46FA6A41148AC
  body: iQIyBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5f/ikACgkQlcRvpqQRSKw04Q/4sX9Ylt8vdkhnT24vRqf7uoO2WsRCs4uHvr3tW5V735o3ReKE4KWT9crIKSSn92vkEaFdNAr8+9M7CMfa3qKLAd0kVbGIDiZ775+4jw/Px+nyORSkl17F5gji3m3kM6rnvqYtg5r1NvxuIeuqNK8TGojtlAsf1hN6inrB6S7MEAgw/wYRMQjV7ohYFkeMMLzaN+Q1YA4LolfnM4JbsIwcFMHC1jxDe6is22lUxR1XqTEzJqpIQLLCtY4Ds6LECqm0cFRlXic1ldcYqHxZvWLks8MdNtkMq0FqwEUtr07IqWUAyWcMsmKkReSbTfqPlizJOZ9J4c+8p5FIkRNl+TuJnfB4oBNANMmEHeeHNY3a5pfeJFOyfzAq7LfOwYBMfWcNvX4kuKfAKhSI55Snb6vK4ChRget3VruOZaFIjfAxWQX5QVcDufh3EJvuCJ1lJKKxdqIHQO95hvJ4cMgz1+pxPnQabEkFlk0Q+qFYS4u1S89y5lWxc5GIRaZX6X43/r74rB027LN9eqsovrJ0MLFHRH3QagS9YxDUsVWnv1Y/8TvUn65QRIsHbmrF1ESYdjycROh1bhTJLwwqrSwQjm5HhquvJ93DzUNgRZDLRXwDGL2raJ7oeNL+6E2fl8ahPntmvdVFWtXsMNK93/IKkW3brxUoBkWt5rzKqE4TRhWjDw==
  account: mediocregopher
2020-03-04 12:14:54 -07:00

155 lines
4.9 KiB
Go

package accessctl
import (
"fmt"
"sort"
"github.com/bmatcuk/doublestar"
)
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.
{
BranchPattern: "main",
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
}
// 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
}
// 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
}
// 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)
} else if ok {
branchAC = accessControls[i]
break
}
}
if !ok {
panic(fmt.Sprintf("no patterns matched branch %q, which shouldn't be possible", interactions.Branch))
}
res.BranchPattern = branchAC.BranchPattern
}
// 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
})
}
return res, nil
}