326de2afc6
--- type: change message: |- Fully implement credential commits The actual commit objects and related refactoring had already been done, this commit takes the next step of implementing the access control changes, tests for verification, and refactoring of the dehub command to support multiple commit message types (as well as a small fix to dcmd). change_hash: AJyuAR0koVoe+uPBisa5qXsbW8YhlgOKNhnvy9uv7hQ8 credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5tVzoACgkQlcRvpqQRSKyznw//b9lWd4V4G81cFwGAxZtJ3JiFpspYdtTAUUcLi9nogGsDmqkkSQxLdmBCT99QtaenKsxpad+9sXhkZpgWF/AyCX9pN6TTlMKuRcDXeoMUjeKjRpRhCHN0Lt8Sz80NDPYIa81r9cH0o1987GirgGmDEkYNDAFPDdGNDcCad/LLnG+ONwOl9WEM1q5O4etUPurTywlBiELDjHxeLzqgxCo8fMaMejW6mxnMDV6DIHiX6INWZAAG66HPVetmq6EVl9bnFgZmgKNzqKzFVZJRdGQNbhR/WzlOh0HPyJGwCveZPM5Zjd/dpfQUYEGGprVKc0G0YVNU2Hcz6O7hqafGGxWpCFW6zKrNmBRaW2u2zjVJD4ukmWn9gFuKJKhs0kyawRTbHNIX+gonYv9lDFO3cZ5qcsJbSAYSHrCav121z0GsQDoFJMJDQnP0syEEbAaxdQe7Bd7bmOM3SpCOLJLF1+X7Srrq5//u6fiFDxQ82Ylo3hG/r7/QT/vSipUCglx4POq33+z8VEHGhVfl4dgSU6OgIV/S7evKC7EiS/jh/xywU44RHpxFhwS3hthHxZqgRIHTm65DqGYWWZds2Hkr29TTRajuf0t4MxqY2MrLAhNJUc6OmrVN+lWMmm/z1FEhfrOvZ8v7mOSqTKwkvbsZzk5mpeo2RrLdNnnWvTCy87FpA48= account: mediocregopher
203 lines
6.5 KiB
Go
203 lines
6.5 KiB
Go
package accessctl
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/bmatcuk/doublestar"
|
|
)
|
|
|
|
var (
|
|
// DefaultSignatureCondition represents the Condition which is applied for
|
|
// default access controls. It requires a single signature credential from
|
|
// any account defined in the Config.
|
|
DefaultSignatureCondition = Condition{
|
|
Signature: &ConditionSignature{
|
|
AnyAccount: true,
|
|
Count: "1",
|
|
},
|
|
}
|
|
|
|
// 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: DefaultSignatureCondition,
|
|
}
|
|
|
|
// DefaultCredentialAccessControl represents the CredentialAccessControl
|
|
// which is applied when a BranchAccessControl does not have a defined
|
|
// CredentialAccessControl.
|
|
DefaultCredentialAccessControl = CredentialAccessControl{
|
|
Condition: DefaultSignatureCondition,
|
|
}
|
|
|
|
// 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{
|
|
{
|
|
BranchPattern: "main",
|
|
CredentialAccessControl: &CredentialAccessControl{
|
|
Condition: Condition{Never: new(ConditionNever)},
|
|
},
|
|
},
|
|
{
|
|
BranchPattern: "**",
|
|
},
|
|
}
|
|
)
|
|
|
|
// any account can do anything, except main branch can only get change commits
|
|
|
|
// 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,omitempty"`
|
|
CredentialAccessControl *CredentialAccessControl `yaml:"credential_access_control,omitempty"`
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// CredentialAccessControl represents an access control object being defined in
|
|
// the Config for the purpose of controlling who is able to create credential
|
|
// commits.
|
|
type CredentialAccessControl struct {
|
|
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
|
|
|
|
// CredentialAdded indicates a credential commit is being added to the
|
|
// Branch.
|
|
CredentialAdded bool
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// MatchedCredentialAccessControl contains information about a
|
|
// CredentialAccessControl which was matched in Match.
|
|
type MatchedCredentialAccessControl struct {
|
|
CredentialAccessControl CredentialAccessControl
|
|
}
|
|
|
|
// 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
|
|
|
|
// CredentialAccessControls indicates which CredentialAccessControl object
|
|
// matched for a credential commit. Will be nil if CredentialAdded was
|
|
// false.
|
|
CredentialAccessControl *MatchedCredentialAccessControl
|
|
}
|
|
|
|
// 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("matching branch %q to branch_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("matching path %q to file_path_pattern %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
|
|
})
|
|
}
|
|
|
|
// Handle CredentialAccessControl, if applicable
|
|
if interactions.CredentialAdded {
|
|
credAC := branchAC.CredentialAccessControl
|
|
if credAC == nil {
|
|
credAC = &DefaultCredentialAccessControl
|
|
}
|
|
|
|
res.CredentialAccessControl = &MatchedCredentialAccessControl{
|
|
CredentialAccessControl: *credAC,
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|