Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43714047be |
@@ -1,6 +1,11 @@
|
||||
package allowlist
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.d-ma.be/mathias/gitea-mcp/internal/gitea"
|
||||
)
|
||||
|
||||
type Allowlist struct {
|
||||
owners map[string]struct{}
|
||||
@@ -14,10 +19,17 @@ func New(owners []string) *Allowlist {
|
||||
return &Allowlist{owners: m}
|
||||
}
|
||||
|
||||
func (a *Allowlist) Check(owner string) error {
|
||||
// Check gates owner access to the static list — except for a caller
|
||||
// authenticated with their own Gitea PAT (pass-through, gitea-mcp#59), whose
|
||||
// access Gitea's own permission model already gates more precisely than a
|
||||
// coarse owner name list ever could.
|
||||
func (a *Allowlist) Check(ctx context.Context, owner string) error {
|
||||
if owner == "" {
|
||||
return fmt.Errorf("owner required")
|
||||
}
|
||||
if _, ok := gitea.TokenFromContext(ctx); ok {
|
||||
return nil
|
||||
}
|
||||
if _, ok := a.owners[owner]; !ok {
|
||||
return fmt.Errorf("owner %q not in allowlist", owner)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
package allowlist_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.d-ma.be/mathias/gitea-mcp/internal/allowlist"
|
||||
"git.d-ma.be/mathias/gitea-mcp/internal/gitea"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAllowlistCheck(t *testing.T) {
|
||||
a := allowlist.New([]string{"mathias", "acme"})
|
||||
assert.NoError(t, a.Check("mathias"))
|
||||
assert.NoError(t, a.Check("acme"))
|
||||
assert.Error(t, a.Check("evil"))
|
||||
assert.Error(t, a.Check(""))
|
||||
ctx := context.Background()
|
||||
assert.NoError(t, a.Check(ctx, "mathias"))
|
||||
assert.NoError(t, a.Check(ctx, "acme"))
|
||||
assert.Error(t, a.Check(ctx, "evil"))
|
||||
assert.Error(t, a.Check(ctx, ""))
|
||||
}
|
||||
|
||||
// A caller authenticated with their own Gitea PAT (pass-through, gitea-mcp#59)
|
||||
// is gated by Gitea's own permission model, not the MCP's static owner list —
|
||||
// otherwise a legitimate second user could never touch their own repos.
|
||||
func TestAllowlistCheckTrustsPassthroughAuthenticatedCaller(t *testing.T) {
|
||||
a := allowlist.New([]string{"mathias"})
|
||||
ctx := gitea.WithToken(context.Background(), "someone-elses-pat")
|
||||
assert.NoError(t, a.Check(ctx, "someone-else"))
|
||||
}
|
||||
|
||||
// Empty owner is a structural input error, not an authz question — still
|
||||
// rejected even on the pass-through path.
|
||||
func TestAllowlistCheckStillRejectsEmptyOwnerOnPassthrough(t *testing.T) {
|
||||
a := allowlist.New([]string{"mathias"})
|
||||
ctx := gitea.WithToken(context.Background(), "someone-elses-pat")
|
||||
assert.Error(t, a.Check(ctx, ""))
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (t *BranchDelete) Call(ctx context.Context, raw json.RawMessage) (json.RawM
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Branch == "" {
|
||||
|
||||
@@ -47,7 +47,7 @@ func (t *BranchList) Call(ctx context.Context, raw json.RawMessage) (json.RawMes
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ func (t *BranchProtectionGet) Call(ctx context.Context, raw json.RawMessage) (js
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ func (t *CodeSearch) Call(ctx context.Context, raw json.RawMessage) (json.RawMes
|
||||
if args.Q == "" {
|
||||
return nil, fmt.Errorf("q is required: %w", gitea.ErrValidation)
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Page < 1 {
|
||||
|
||||
@@ -111,7 +111,7 @@ func (t *CreateProjectFromTemplate) Call(ctx context.Context, raw json.RawMessag
|
||||
}
|
||||
|
||||
// Allowlist check first.
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ func (t *DirList) Call(ctx context.Context, raw json.RawMessage) (json.RawMessag
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ func (t *FileDelete) Call(ctx context.Context, raw json.RawMessage) (json.RawMes
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Sha == "" {
|
||||
|
||||
@@ -51,7 +51,7 @@ func (t *FileRead) Call(ctx context.Context, raw json.RawMessage) (json.RawMessa
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ func (t *FileWriteBranch) Call(ctx context.Context, raw json.RawMessage) (json.R
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Branch == "" {
|
||||
|
||||
@@ -45,7 +45,7 @@ func (t *IssueClose) Call(ctx context.Context, raw json.RawMessage) (json.RawMes
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iss, err := t.c.SetIssueState(ctx, args.Owner, args.Repo, args.Number, "closed")
|
||||
|
||||
@@ -50,7 +50,7 @@ func (t *IssueComment) Call(ctx context.Context, raw json.RawMessage) (json.RawM
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Number < 1 {
|
||||
|
||||
@@ -56,7 +56,7 @@ func (t *IssueCreate) Call(ctx context.Context, raw json.RawMessage) (json.RawMe
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Title == "" {
|
||||
|
||||
@@ -53,7 +53,7 @@ func (t *IssueEdit) Call(ctx context.Context, raw json.RawMessage) (json.RawMess
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Number < 1 {
|
||||
|
||||
@@ -43,7 +43,7 @@ func (t *IssueGet) Call(ctx context.Context, raw json.RawMessage) (json.RawMessa
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iss, err := t.c.GetIssue(ctx, args.Owner, args.Repo, args.Number)
|
||||
|
||||
@@ -50,7 +50,7 @@ func (t *IssueLabel) Call(ctx context.Context, raw json.RawMessage) (json.RawMes
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Number < 1 {
|
||||
|
||||
@@ -53,7 +53,7 @@ func (t *IssueList) Call(ctx context.Context, raw json.RawMessage) (json.RawMess
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.State == "" {
|
||||
|
||||
@@ -45,7 +45,7 @@ func (t *IssueListComments) Call(ctx context.Context, raw json.RawMessage) (json
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
comments, err := t.c.ListIssueComments(ctx, args.Owner, args.Repo, args.Number)
|
||||
|
||||
@@ -45,7 +45,7 @@ func (t *IssueReopen) Call(ctx context.Context, raw json.RawMessage) (json.RawMe
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iss, err := t.c.SetIssueState(ctx, args.Owner, args.Repo, args.Number, "open")
|
||||
|
||||
@@ -43,7 +43,7 @@ func (t *LabelList) Call(ctx context.Context, raw json.RawMessage) (json.RawMess
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
labels, err := t.c.ListLabels(ctx, args.Owner, args.Repo)
|
||||
|
||||
@@ -50,7 +50,7 @@ func (t *PRComment) Call(ctx context.Context, raw json.RawMessage) (json.RawMess
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Number < 1 {
|
||||
|
||||
@@ -56,7 +56,7 @@ func (t *PRCreate) Call(ctx context.Context, raw json.RawMessage) (json.RawMessa
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Title == "" {
|
||||
|
||||
@@ -63,7 +63,7 @@ func (t *PRFilesDiff) Call(ctx context.Context, raw json.RawMessage) (json.RawMe
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Number < 1 {
|
||||
|
||||
@@ -44,7 +44,7 @@ func (t *PRGet) Call(ctx context.Context, raw json.RawMessage) (json.RawMessage,
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Number < 1 {
|
||||
|
||||
@@ -51,7 +51,7 @@ func (t *PRList) Call(ctx context.Context, raw json.RawMessage) (json.RawMessage
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := args.State
|
||||
|
||||
@@ -52,7 +52,7 @@ func (t *PRMerge) Call(ctx context.Context, raw json.RawMessage) (json.RawMessag
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Number < 1 {
|
||||
|
||||
@@ -55,7 +55,7 @@ func (t *ReleaseCreate) Call(ctx context.Context, raw json.RawMessage) (json.Raw
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rel, err := t.c.CreateRelease(ctx, args.Owner, args.Repo, gitea.CreateReleaseArgs{
|
||||
|
||||
@@ -53,7 +53,7 @@ func (t *RepoCreate) Call(ctx context.Context, raw json.RawMessage) (json.RawMes
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createArgs := gitea.CreateRepoArgs{
|
||||
|
||||
@@ -46,7 +46,7 @@ func (t *RepoDelete) Call(ctx context.Context, raw json.RawMessage) (json.RawMes
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Confirm != args.Repo {
|
||||
|
||||
@@ -38,7 +38,7 @@ func (t *RepoGet) Call(ctx context.Context, raw json.RawMessage) (json.RawMessag
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := t.c.GetRepo(ctx, args.Owner, args.Repo)
|
||||
|
||||
@@ -45,7 +45,7 @@ func (t *RepoList) Call(ctx context.Context, raw json.RawMessage) (json.RawMessa
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args.Limit = capLimit(args.Limit, 30)
|
||||
|
||||
@@ -96,7 +96,7 @@ func (t *RepoMirrorPush) Call(ctx context.Context, raw json.RawMessage) (json.Ra
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch args.Action {
|
||||
|
||||
@@ -53,7 +53,7 @@ func (t *RepoSearch) Call(ctx context.Context, raw json.RawMessage) (json.RawMes
|
||||
return nil, fmt.Errorf("q is required: %w", gitea.ErrValidation)
|
||||
}
|
||||
if args.Owner != "" {
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func (t *RepoSearch) Call(ctx context.Context, raw json.RawMessage) (json.RawMes
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if t.a.Check(parts[0]) == nil {
|
||||
if t.a.Check(ctx, parts[0]) == nil {
|
||||
filtered = append(filtered, r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (t *RepoStatus) Call(ctx context.Context, raw json.RawMessage) (json.RawMes
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ func (t *RepoTopicsUpdate) Call(ctx context.Context, raw json.RawMessage) (json.
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.c.UpdateTopics(ctx, args.Owner, args.Repo, args.Topics); err != nil {
|
||||
|
||||
@@ -45,7 +45,7 @@ func (t *RepoTree) Call(ctx context.Context, raw json.RawMessage) (json.RawMessa
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tree, err := t.c.GetTree(ctx, args.Owner, args.Repo, args.Ref, true)
|
||||
|
||||
@@ -60,7 +60,7 @@ func (t *RepoUpdate) Call(ctx context.Context, raw json.RawMessage) (json.RawMes
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ func (t *TagCreate) Call(ctx context.Context, raw json.RawMessage) (json.RawMess
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Tag == "" {
|
||||
|
||||
@@ -145,7 +145,7 @@ func (t *TBDShip) Call(ctx context.Context, raw json.RawMessage) (json.RawMessag
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Path == "" || args.Content == "" || args.Message == "" {
|
||||
|
||||
@@ -57,7 +57,7 @@ func (t *WorkflowRunList) Call(ctx context.Context, raw json.RawMessage) (json.R
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args.Limit = capLimit(args.Limit, 10)
|
||||
|
||||
@@ -47,7 +47,7 @@ func (t *WorkflowRunStatus) Call(ctx context.Context, raw json.RawMessage) (json
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.RunID < 1 {
|
||||
|
||||
@@ -62,7 +62,7 @@ func (t *WorkflowRunTrigger) Call(ctx context.Context, raw json.RawMessage) (jso
|
||||
if err := parseArgs(raw, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.a.Check(args.Owner); err != nil {
|
||||
if err := t.a.Check(ctx, args.Owner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if args.Workflow == "" {
|
||||
|
||||
Reference in New Issue
Block a user