1f422511d5
--- type: change message: |- completely refactor accessctl (again) This time it's using an actual access control list system, rather than whatever it was doing before. The new system uses a Filter type, rather than Condition, to decide which acl element should have its action (allow or deny) applied. This makes testing way easier, since all the different matching conditions are now individual filters, and so are tested individually. change_hash: AFgN0hormIlO0VWkLKnAdSDZeVRbh0Wj8LLXOMVQEK+L credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5yoi8ACgkQlcRvpqQRSKwo/g//QkSA80APnPtwcibNeaoUwduH1Xim8pmi5JScKGsOypYkE0Iy+fO3fRNz4r7Tm6qNn7O04fDsiXlxPojxn7+NDFCQArVgoJk3jVTRDBW7LpahWJsYPP1SBjGtaR9o0bOpclXblTMIcteTkLM94AeASqLaEY8StO+5PX/82AkFRQ8E6m9R2HCmgwbBhqwWp8936x8ekMFbSSi0TMIyV4rpd0wj4mjvjjBwa3ArGmH/ynwabPCFuuqMT6996N1zoDn5EqZA5jGrf+Q7rxsI6t1bOnLmg9NGMQYRaZLAVZrp5P6G5XR3et4Gz/2AphAEgYJM3yLbEjZW6Daa77CgTNHXde7gCaWqyfcKlVGPi29/O+2IXhpjwxHwGpsBgEdX9227zapL+jwSAOdUVj8n6C8I8BGqpT7rTwA53yxlbSwXlkttvAn/lGT5X4lK74YfkzMXMEBZKzsb/dQEPyP2Y+AG6z2D4Bs/4szsCiUXF9aG2Yx1o45lVXTTdPUNLIsnhBjM7usbQRg8i5kC+OC9AVCi8E+lf0/Qgp0cUb6QLH47bHvDTH7UluY1bgSLZy+Zjaisvl3a0aK/UspywWN/fFgOrz2cDw232n8IC+Zi4LSKm7dXDRFbC1JNzrwAPP1ifboOrltwKroOsDNaVGhX8ABahNjmrUO4JgE7gvX+zxXb+/I= account: mediocregopher
98 lines
2.7 KiB
Go
98 lines
2.7 KiB
Go
package accessctl
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// FilterSignature represents the configuration of a Filter which requires one
|
|
// or more signature credentials to be present on a commit.
|
|
//
|
|
// Either AccountIDs or AnyAccount must be filled in.
|
|
type FilterSignature struct {
|
|
AccountIDs []string `yaml:"account_ids,omitempty"`
|
|
AnyAccount bool `yaml:"any_account,omitempty"`
|
|
Count string `yaml:"count"`
|
|
}
|
|
|
|
var _ FilterInterface = FilterSignature{}
|
|
|
|
func (f FilterSignature) targetNum() (int, error) {
|
|
if !strings.HasSuffix(f.Count, "%") {
|
|
return strconv.Atoi(f.Count)
|
|
} else if f.AnyAccount {
|
|
return 0, errors.New("cannot use AnyAccount and a percent Count together")
|
|
}
|
|
|
|
percentStr := strings.TrimRight(f.Count, "%")
|
|
percent, err := strconv.ParseFloat(percentStr, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("could not parse Count as percent %q: %w", f.Count, err)
|
|
}
|
|
target := float64(len(f.AccountIDs)) * percent / 100
|
|
target = math.Ceil(target)
|
|
return int(target), nil
|
|
}
|
|
|
|
// ErrFilterSignatureUnsatisfied is returned from FilterSignature's
|
|
// Match method when the filter has not been satisfied.
|
|
type ErrFilterSignatureUnsatisfied struct {
|
|
TargetNumAccounts, NumAccounts int
|
|
}
|
|
|
|
func (err ErrFilterSignatureUnsatisfied) Error() string {
|
|
return fmt.Sprintf("not enough valid signature credentials, filter requires %d but only had %d",
|
|
err.TargetNumAccounts, err.NumAccounts)
|
|
}
|
|
|
|
// MatchCommit returns true if the CommitRequest contains a sufficient number of
|
|
// signature Credentials.
|
|
func (f FilterSignature) MatchCommit(req CommitRequest) error {
|
|
targetN, err := f.targetNum()
|
|
if err != nil {
|
|
return fmt.Errorf("computing target number of accounts: %w", err)
|
|
}
|
|
|
|
credAccountIDs := map[string]struct{}{}
|
|
for _, cred := range req.Credentials {
|
|
// TODO support other kinds of signatures
|
|
if cred.PGPSignature == nil {
|
|
continue
|
|
}
|
|
credAccountIDs[cred.AccountID] = struct{}{}
|
|
}
|
|
|
|
var n int
|
|
if f.AnyAccount {
|
|
// TODO this doesn't actually check that the accounts are defined in the
|
|
// Config. It works for now as long as the Credentials are valid, since
|
|
// only an Account defined in the Config could create a valid
|
|
// Credential, but once that's not the case this will need to be
|
|
// revisited.
|
|
n = len(credAccountIDs)
|
|
} else {
|
|
targetAccountIDs := map[string]struct{}{}
|
|
for _, accountID := range f.AccountIDs {
|
|
targetAccountIDs[accountID] = struct{}{}
|
|
}
|
|
for accountID := range targetAccountIDs {
|
|
if _, ok := credAccountIDs[accountID]; ok {
|
|
n++
|
|
}
|
|
}
|
|
}
|
|
|
|
if n >= targetN {
|
|
return nil
|
|
}
|
|
return ErrFilterNoMatch{
|
|
Err: ErrFilterSignatureUnsatisfied{
|
|
NumAccounts: n,
|
|
TargetNumAccounts: targetN,
|
|
},
|
|
}
|
|
}
|