4d56716fe8
--- type: change message: |- Give the project a proper root, dehub.dev/src/dehub.git "dehub" was being used as a placeholder up till this point, but now that the project has matured enough to have a home, and people will want to be installing the binary, it's time to give it a proper import root. change_hash: ADTjLNnPARatUjx6HDo7m3O+0KUcWvcMJq1kNrj9++PU credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl6BECkACgkQlcRvpqQRSKyeJBAAocdHTs8AB2u9kZmMcJNf136Cq8KWjglvhuH49Nj9ILkeGOBF7+fIHBRSYxBQm4IIDWsjaQaRRhESJxKrkHUd7BG/z5iVsCfjYTyY7LPgv0oYJKEGbPOF2LDK2fuLLpRlA5v1fwamTdFYxs5j7yxW7weWiiKEgjMGjOiWQCWuqhw8rwOG6vjvYttQ5pekI7GvkdqWe+qODKmIF9xFFuhz7BMsFUAAwLz97W1wZkHA+O1BXGTe9hGwH88RY9IGJZftA6wuGes/SqwWJk+hpb89KV1yR0woMrwz7ITZZeNBboXO7KyxUXrfiLtxeRD4t15tJkdRY0FbIZvNCktviTqjPXOtoVh7c3m3p/LR5vcbgYDxaZKk51DYruovfaRI96Q9CYCK2kVb2PIJ4BJhlsUPzw8AWNVxXFECYZNEZly10hqNh4Xophr/x7PWOmoPoKL2PvXLtOHzk0r4Tw6JqUaC+7/08U/+lhlSKFKLdCj7Xru57qB7gxgd+P3g4rfqjIHqiB4tlViqR5RV7V8Z/goBMJg2uOJXIJGvwEZF9nj+QDgaofImMiQmcLrE6IBCYEELYz274w/6ugSCcdwVIC/n3VHuNY4zJDKN7Q/FgI9/GBS7UoNhTaHF2JmSKR9ErMKpm3PYPZr/VnsaXjfCBO5bVquvWLzfJ9aQ4t+tsRkuAvQ= account: mediocregopher
152 lines
4.3 KiB
Go
152 lines
4.3 KiB
Go
// Package accessctl implements functionality related to allowing or denying
|
|
// actions in a repo based on who is taking what actions.
|
|
package accessctl
|
|
|
|
import (
|
|
"dehub.dev/src/dehub.git/sigcred"
|
|
"errors"
|
|
"fmt"
|
|
|
|
yaml "gopkg.in/yaml.v2"
|
|
)
|
|
|
|
// DefaultAccessControlsStr is the encoded form of the default access control
|
|
// set which is applied to all CommitRequests if no user-supplied ones match.
|
|
//
|
|
// The effect of these AccessControls is to allow all commit types on any branch
|
|
// (with the exception of the main branch, which only allows change commits), as
|
|
// long as the commit has one signature from a configured account.
|
|
var DefaultAccessControlsStr = `
|
|
- action: allow
|
|
filters:
|
|
- type: not
|
|
filter:
|
|
type: branch
|
|
pattern: main
|
|
- type: signature
|
|
any_account: true
|
|
count: 1
|
|
|
|
- action: allow
|
|
filters:
|
|
- type: branch
|
|
pattern: main
|
|
- type: commit_type
|
|
commit_type: change
|
|
- type: signature
|
|
any_account: true
|
|
count: 1
|
|
|
|
- action: deny
|
|
`
|
|
|
|
// DefaultAccessControls is the decoded form of DefaultAccessControlsStr.
|
|
var DefaultAccessControls = func() []AccessControl {
|
|
var acl []AccessControl
|
|
if err := yaml.Unmarshal([]byte(DefaultAccessControlsStr), &acl); err != nil {
|
|
panic(err)
|
|
}
|
|
return acl
|
|
}()
|
|
|
|
// CommitRequest is used to describe a set of interactions which are being
|
|
// requested to be performed.
|
|
type CommitRequest struct {
|
|
// Type describes what type of commit is being requested. Possibilities are
|
|
// determined by the requester.
|
|
Type string
|
|
|
|
// Branch is the name of the branch the interactions are being attempted on.
|
|
// It is required.
|
|
Branch string
|
|
|
|
// Credentials are the Credential objects attached to the commit.
|
|
Credentials []sigcred.Credential
|
|
|
|
// FilesChanged is the set of file paths (relative to the repo root) which
|
|
// have been modified in some way.
|
|
FilesChanged []string
|
|
}
|
|
|
|
// Action describes what action an AccessControl should perform
|
|
// when given a CommitRequest.
|
|
type Action string
|
|
|
|
// Enumerates possible Action values
|
|
const (
|
|
ActionAllow Action = "allow"
|
|
ActionDeny Action = "deny"
|
|
|
|
// ActionNext is used internally when a request does not match an
|
|
// AccessControl's filters. It _could_ be used in the Config as well, but it
|
|
// would be pretty pointless to do so, so we don't talk about it.
|
|
ActionNext Action = "next"
|
|
)
|
|
|
|
// AccessControl describes a set of Filters, and the Actions which should be
|
|
// taken on a CommitRequest if those Filters all match on the CommitRequest.
|
|
type AccessControl struct {
|
|
Action Action `yaml:"action"`
|
|
Filters []Filter `yaml:"filters"`
|
|
}
|
|
|
|
// ActionForCommit returns what Action this AccessControl says to take for a
|
|
// given CommitRequest. It may return ActionNext if the request is not matched
|
|
// by the AccessControl's Filters.
|
|
func (ac AccessControl) ActionForCommit(req CommitRequest) (Action, error) {
|
|
for _, filter := range ac.Filters {
|
|
filterI, err := filter.Interface()
|
|
if err != nil {
|
|
return "", fmt.Errorf("casting %+v to a FilterInterface: %w", filter, err)
|
|
|
|
} else if err := filterI.MatchCommit(req); errors.As(err, new(ErrFilterNoMatch)) {
|
|
return ActionNext, nil
|
|
|
|
} else if err != nil {
|
|
// ignore the error here, if we could get the FilterInterface then
|
|
// we should be able to get the type.
|
|
filterTypeStr, _ := filter.Type()
|
|
return "", fmt.Errorf("matching commit using filter of type %q: %w", filterTypeStr, err)
|
|
}
|
|
}
|
|
return ac.Action, nil
|
|
}
|
|
|
|
// ErrCommitRequestDenied is returned from AssertCanCommit when a particular
|
|
// AccessControl has explicitly disallowed the CommitRequest.
|
|
type ErrCommitRequestDenied struct {
|
|
By AccessControl
|
|
}
|
|
|
|
func (e ErrCommitRequestDenied) Error() string {
|
|
acB, err := yaml.Marshal(e.By)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return fmt.Sprintf("commit matched and denied by this access control:\n%s", string(acB))
|
|
}
|
|
|
|
// AssertCanCommit asserts that the given CommitRequest is allowed by the given
|
|
// AccessControls.
|
|
func AssertCanCommit(acl []AccessControl, req CommitRequest) error {
|
|
acl = append(acl, DefaultAccessControls...)
|
|
for _, ac := range acl {
|
|
action, err := ac.ActionForCommit(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch action {
|
|
case ActionNext:
|
|
continue
|
|
case ActionAllow:
|
|
return nil
|
|
case ActionDeny:
|
|
return ErrCommitRequestDenied{By: ac}
|
|
default:
|
|
return fmt.Errorf("invalid action %q", action)
|
|
}
|
|
}
|
|
|
|
panic("should not be able to get here")
|
|
}
|