dehub/accessctl/access_control.go
mediocregopher b01fe1524a Completely refactor naming of everything, in light of new SPEC
---
type: change
description: |-
  Completely refactor naming of everything, in light of new SPEC

  Writing the SPEC shed some light on just how weakly a lot of concepts, like
  "commit", had been defined, and prompted the delineation of a lot of things
  along specific lines (commit vs payload, repo vs project). This commit makes the
  code reflect the SPEC much better in quite a few ways:

  * Repo is now Project
  * Commit is now Payload
  * GitCommit is now just Commit
  * Hash is now Fingerprint
  * A lot of minor fields got renamed
  * All the XXXInterface types are now just XXX, and their old XXX type is now
    XXXUnion.

  More than likely there's still some comments and variable names that have
  slipped passed, but overall I feel like I got most of the changes.
fingerprint: AKkDC5BKhKbfXzZQ/F4KquHeMgVvcNxgLmkZFz/nP/tY
credentials:
- type: pgp_signature
  pub_key_id: 95C46FA6A41148AC
  body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl6l7aYACgkQlcRvpqQRSKxFrA//VQ+f8B6pwGS3ORB4VVBnHvvJTGZvAYTvB0fHuHJx2EreR4FwjhaNakk5ClkwbO7WFMq++2OV4xIkvzwswLdbXZF0IHx3wScQM59v4vIkR4V9Lj5p1aGGhQna52uIKugF2gTqKdU4tqYzmBjDND/c2XDwCN5CwTwwnAHXUSSsHxviiPUYPWV5wzFP7uyRW0ZeK8Isv7QECKRXlsDjcSJa+g+jc091FG/jG9Dkai8fbDbW8YXj7W3ALaXgXWEBJMrgQxZcJJRjgCvLY72FIIrUBquu3FepiyzMtZ0yaIvi4NmGCsYqIv00NcMvMtD7iwhOCZn10Sku4wvaKJ8YBMRduhqC99fnr/ZDW0/HvTNcL7GKx11GjwtmzkJgwsHFPy3zX+kMdF4m3WgtoeI0GwEsBXXZE2C49yAk3Mb/3puegl3a1PPMvOabTzo7Xm6xpWkI6gISChI7My71H3EuKZWhkb+IubPmMvJJXIdVxHnsHPz2dl/BZXLgpfVdEgQa2qWeXtYI4NNm37pLl3gv92V4kka+Kr4gfdoq8mJ7aqvc9was35baJbHg4+fEVJG2Wj+2AQU+ncx3nAFzgYyMxwo9K8VuC4QdfRF4ImyxTnWkuokEn9H6JRrbkBDKIELj6vzdPmsjOUEQ4nsYX66/zSibFD7UvhQmdXFs8Gp8/Qq6g4M=
  account: mediocregopher
2020-04-26 14:23:10 -06:00

156 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: deny
filters:
- type: commit_attributes
non_fast_forward: true
- action: allow
filters:
- type: branch
pattern: main
- type: payload_type
payload_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 credentials attached to the commit.
Credentials []sigcred.CredentialUnion
// FilesChanged is the set of file paths (relative to the repo root) which
// have been modified in some way.
FilesChanged []string
// NonFastForward should be set to true if the branch HEAD and this commit
// are not directly related (i.e. neither is a direct ancestor of the
// other).
NonFastForward bool
}
// 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 []FilterUnion `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 _, filterUn := range ac.Filters {
if err := filterUn.Filter().MatchCommit(req); errors.As(err, new(ErrFilterNoMatch)) {
return ActionNext, nil
} else if err != nil {
return "", fmt.Errorf("matching commit using filter of type %q: %w", filterUn.Type(), 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")
}