20613afb26
Add a Vault/OpenBao secrets engine that mints ephemeral, scoped Gitea access tokens on demand. The engine holds a single seeded Gitea site-admin Basic-Auth credential and, per role, mints a fresh per-user token via the admin API, bound to a Vault lease and deleted from Gitea on revocation. Gitea requires Basic Auth for token management (token auth is rejected), and reqSelfOrAdmin lets a site admin manage any user's tokens, which is the mechanism this relies on. Gitea tokens never expire server-side, so the Vault lease is the sole expiry mechanism. - add backend wiring, config (+ rotate-root), roles, creds paths - add the gitea client (Basic Auth create/delete token, admin password change) - add scope validation against Gitea's access-token scope set - add unit tests (fake Gitea API) and a Vault+OpenBao e2e harness - add Makefile, nfpm RPM packaging, and Woodpecker build/test/release pipelines Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
54 lines
1.5 KiB
Go
54 lines
1.5 KiB
Go
package gitea
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestNormalizeScopes(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in []string
|
|
want []string
|
|
wantErr bool
|
|
}{
|
|
{"single", []string{"read:repository"}, []string{"read:repository"}, false},
|
|
{"trim+case", []string{" Write:Issue "}, []string{"write:issue"}, false},
|
|
{"dedupe", []string{"read:user", "read:user", "write:user"}, []string{"read:user", "write:user"}, false},
|
|
{"all", []string{"all"}, []string{"all"}, false},
|
|
{"public-only", []string{"public-only", "read:repository"}, []string{"public-only", "read:repository"}, false},
|
|
{"blank-only", []string{"", " "}, nil, true},
|
|
{"empty", nil, nil, true},
|
|
{"invalid", []string{"read:repository", "sudo"}, nil, true},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
got, err := normalizeScopes(c.in)
|
|
if c.wantErr {
|
|
if err == nil {
|
|
t.Fatalf("expected error, got %v", got)
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if strings.Join(got, ",") != strings.Join(c.want, ",") {
|
|
t.Errorf("normalizeScopes(%v) = %v, want %v", c.in, got, c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKnownScopesSorted(t *testing.T) {
|
|
scopes := knownScopes()
|
|
if len(scopes) != len(validScopes) {
|
|
t.Fatalf("knownScopes len = %d, want %d", len(scopes), len(validScopes))
|
|
}
|
|
for i := 1; i < len(scopes); i++ {
|
|
if scopes[i-1] > scopes[i] {
|
|
t.Errorf("knownScopes not sorted at %d: %q > %q", i, scopes[i-1], scopes[i])
|
|
}
|
|
}
|
|
}
|