180 lines
5.5 KiB
Go
180 lines
5.5 KiB
Go
package receiver
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestGenerateToken_Format(t *testing.T) {
|
|
token := GenerateToken("key", "org", "repo", "wf", "job")
|
|
parts := strings.SplitN(token, ":", 2)
|
|
if len(parts) != 2 {
|
|
t.Fatalf("token should have format 'timestamp:hmac', got %q", token)
|
|
}
|
|
if len(parts[1]) != 64 {
|
|
t.Errorf("HMAC part length = %d, want 64", len(parts[1]))
|
|
}
|
|
}
|
|
|
|
func TestGenerateTokenAt_Deterministic(t *testing.T) {
|
|
ts := time.Unix(1700000000, 0)
|
|
token1 := GenerateTokenAt("key", "org", "repo", "wf", "job", ts)
|
|
token2 := GenerateTokenAt("key", "org", "repo", "wf", "job", ts)
|
|
if token1 != token2 {
|
|
t.Errorf("tokens differ: %q vs %q", token1, token2)
|
|
}
|
|
}
|
|
|
|
func TestGenerateTokenAt_ScopePinning(t *testing.T) {
|
|
ts := time.Unix(1700000000, 0)
|
|
base := GenerateTokenAt("key", "org", "repo", "wf", "job", ts)
|
|
|
|
variants := []struct {
|
|
name string
|
|
org string
|
|
repo string
|
|
wf string
|
|
job string
|
|
}{
|
|
{"different org", "other-org", "repo", "wf", "job"},
|
|
{"different repo", "org", "other-repo", "wf", "job"},
|
|
{"different workflow", "org", "repo", "other-wf", "job"},
|
|
{"different job", "org", "repo", "wf", "other-job"},
|
|
}
|
|
|
|
for _, v := range variants {
|
|
t.Run(v.name, func(t *testing.T) {
|
|
token := GenerateTokenAt("key", v.org, v.repo, v.wf, v.job, ts)
|
|
if token == base {
|
|
t.Errorf("token for %s should differ from base", v.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateTokenAt_DifferentKeys(t *testing.T) {
|
|
ts := time.Unix(1700000000, 0)
|
|
token1 := GenerateTokenAt("key-a", "org", "repo", "wf", "job", ts)
|
|
token2 := GenerateTokenAt("key-b", "org", "repo", "wf", "job", ts)
|
|
if token1 == token2 {
|
|
t.Error("different keys should produce different tokens")
|
|
}
|
|
}
|
|
|
|
func TestGenerateTokenAt_DifferentTimestamps(t *testing.T) {
|
|
ts1 := time.Unix(1700000000, 0)
|
|
ts2 := time.Unix(1700000001, 0)
|
|
token1 := GenerateTokenAt("key", "org", "repo", "wf", "job", ts1)
|
|
token2 := GenerateTokenAt("key", "org", "repo", "wf", "job", ts2)
|
|
if token1 == token2 {
|
|
t.Error("different timestamps should produce different tokens")
|
|
}
|
|
}
|
|
|
|
func TestValidateToken_Correct(t *testing.T) {
|
|
ts := time.Now()
|
|
token := GenerateTokenAt("key", "org", "repo", "wf", "job", ts)
|
|
if !ValidateToken("key", token, "org", "repo", "wf", "job", 5*time.Minute) {
|
|
t.Error("ValidateToken should accept correct token")
|
|
}
|
|
}
|
|
|
|
func TestValidateToken_WrongToken(t *testing.T) {
|
|
if ValidateToken("key", "12345:deadbeef", "org", "repo", "wf", "job", 5*time.Minute) {
|
|
t.Error("ValidateToken should reject wrong token")
|
|
}
|
|
}
|
|
|
|
func TestValidateToken_WrongScope(t *testing.T) {
|
|
ts := time.Now()
|
|
token := GenerateTokenAt("key", "org", "repo", "wf", "job", ts)
|
|
if ValidateToken("key", token, "org", "repo", "wf", "other-job", 5*time.Minute) {
|
|
t.Error("ValidateToken should reject token for different scope")
|
|
}
|
|
}
|
|
|
|
func TestValidateToken_Expired(t *testing.T) {
|
|
ts := time.Now().Add(-10 * time.Minute)
|
|
token := GenerateTokenAt("key", "org", "repo", "wf", "job", ts)
|
|
if ValidateToken("key", token, "org", "repo", "wf", "job", 5*time.Minute) {
|
|
t.Error("ValidateToken should reject expired token")
|
|
}
|
|
}
|
|
|
|
func TestValidateTokenAt_NotExpired(t *testing.T) {
|
|
tokenTime := time.Unix(1700000000, 0)
|
|
token := GenerateTokenAt("key", "org", "repo", "wf", "job", tokenTime)
|
|
|
|
// Validate at 4 minutes later (within 5 minute TTL)
|
|
now := tokenTime.Add(4 * time.Minute)
|
|
if !ValidateTokenAt("key", token, "org", "repo", "wf", "job", 5*time.Minute, now) {
|
|
t.Error("ValidateTokenAt should accept token within TTL")
|
|
}
|
|
}
|
|
|
|
func TestValidateTokenAt_JustExpired(t *testing.T) {
|
|
tokenTime := time.Unix(1700000000, 0)
|
|
token := GenerateTokenAt("key", "org", "repo", "wf", "job", tokenTime)
|
|
|
|
// Validate at 6 minutes later (beyond 5 minute TTL)
|
|
now := tokenTime.Add(6 * time.Minute)
|
|
if ValidateTokenAt("key", token, "org", "repo", "wf", "job", 5*time.Minute, now) {
|
|
t.Error("ValidateTokenAt should reject token beyond TTL")
|
|
}
|
|
}
|
|
|
|
func TestValidateToken_InvalidFormat(t *testing.T) {
|
|
if ValidateToken("key", "no-colon-here", "org", "repo", "wf", "job", 5*time.Minute) {
|
|
t.Error("ValidateToken should reject token without colon")
|
|
}
|
|
if ValidateToken("key", "not-a-number:abc123", "org", "repo", "wf", "job", 5*time.Minute) {
|
|
t.Error("ValidateToken should reject token with invalid timestamp")
|
|
}
|
|
}
|
|
|
|
func TestParseTokenTimestamp(t *testing.T) {
|
|
ts := time.Unix(1700000000, 0)
|
|
token := GenerateTokenAt("key", "org", "repo", "wf", "job", ts)
|
|
|
|
parsed, err := ParseTokenTimestamp(token)
|
|
if err != nil {
|
|
t.Fatalf("ParseTokenTimestamp failed: %v", err)
|
|
}
|
|
if !parsed.Equal(ts) {
|
|
t.Errorf("parsed timestamp = %v, want %v", parsed, ts)
|
|
}
|
|
}
|
|
|
|
func TestParseTokenTimestamp_Invalid(t *testing.T) {
|
|
_, err := ParseTokenTimestamp("no-colon")
|
|
if err == nil {
|
|
t.Error("ParseTokenTimestamp should fail on missing colon")
|
|
}
|
|
|
|
_, err = ParseTokenTimestamp("not-a-number:abc123")
|
|
if err == nil {
|
|
t.Error("ParseTokenTimestamp should fail on invalid timestamp")
|
|
}
|
|
}
|
|
|
|
func TestValidateToken_TamperedTimestamp(t *testing.T) {
|
|
// Generate a valid token
|
|
ts := time.Now()
|
|
token := GenerateTokenAt("key", "org", "repo", "wf", "job", ts)
|
|
|
|
parts := strings.SplitN(token, ":", 2)
|
|
if len(parts) != 2 {
|
|
t.Fatalf("unexpected token format: %q", token)
|
|
}
|
|
hmacPart := parts[1]
|
|
|
|
// Tamper with timestamp (e.g., attacker tries to extend token lifetime)
|
|
tamperedTimestamp := strconv.FormatInt(time.Now().Add(1*time.Hour).Unix(), 10)
|
|
tamperedToken := tamperedTimestamp + ":" + hmacPart
|
|
|
|
if ValidateToken("key", tamperedToken, "org", "repo", "wf", "job", 5*time.Minute) {
|
|
t.Error("ValidateToken should reject token with tampered timestamp")
|
|
}
|
|
}
|