a01f2b1512
--- type: change message: implement the ability for users without an account to still submit accredited commits change_hash: AIcRB2u380KAM345cCdexq3RzhDeEuDNT9gwcGDIj8xp credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl6STloACgkQlcRvpqQRSKwMvg//c0JOUTlXNpDA8VSSUdPwHPrUJix223eMOi+SzP4RlCkEh8bHT24D3P1bpMacjgNpcmHGshAwtZkboTbvHNnqVu7HvOM2Vb/lrtkod/0sD4NsO2+GjJBNJmBtv9rEEz9wBGKKhjgZc5+h3d7UlmWHiLO++v2RnjEYd13Fj4/fOHnpAWrXVodtSEFVxUGup980Ug3uvC/vc8+a9w5llafqBMAnastQ/DyulPQeMTE2lxfyGvK1EhQSxbSokO61rgNssPFyfsmheA7T1FIrOzkXqNhIFsfRB4dAOBhhtoqRFEoJ755jK4XSlE5Y8klFuRVIfdnyBlrbia+Pc8u9KMoIBk0hDP+niFqUn9lEZS5D6X7PW/8DsaHa0rlHic9IB7nE563Fm1QVd5GSj7t3/0vPBetxdmXshLuTWtq+gSEJBH2DlC7AHw6gZkSr0w4d2HJlDivfP/cWuyrp0PrOAnEuKCRpnD+EBV5+wa+QlYYIAuTLMwF+/aT/G1VCtCSFkE5JWzZVw6J2oVq25deLpe1TMQ8dHevSlPx/UcofZasO7uFHLc3xDyC8ceK+pGuvRA2SSOIGo7+qR1xh2EhmQ2RZO1AN0NB4NYHQixYqWERen8SDe1jsSy6ercKTE5T/jJeHVPIOm1nutdP+D5gjQGU0JzcNoG/luJv02MWoD7J7RWU= account: mediocregopher
153 lines
4.4 KiB
Go
153 lines
4.4 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 (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"dehub.dev/src/dehub.git/sigcred"
|
|
|
|
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")
|
|
}
|