key-cape/src/internal/authclient/forgejo_current_fixture_test.go
tegwick e2c5a94782
Some checks failed
Authentication acceptance / acceptance (push) Failing after 1m4s
Authentication acceptance / provider-contract (push) Successful in 12s
Build and Publish Container Image / build-and-push (push) Successful in 31s
Bind online human ID tokens to the exact current access grant
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a14bfc-9643-7872-aa12-98326dcf25e7
2026-10-18 12:13:56 +02:00

156 lines
7.1 KiB
Go

//go:build forgejo_current_fixture
package authclient
import (
"bytes"
"context"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"net/http"
"net/http/httptest"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"keycape/internal/domain"
"keycape/internal/server/oidc"
"keycape/internal/server/telemetry"
)
type forgejoFixtureDirectory struct {
mu sync.RWMutex
user domain.User
}
func (d *forgejoFixtureDirectory) LookupUser(context.Context, string) (*domain.User, error) {
d.mu.RLock()
defer d.mu.RUnlock()
user := d.user
user.Groups = append([]string(nil), d.user.Groups...)
return &user, nil
}
func (d *forgejoFixtureDirectory) LookupGroups(context.Context, string) ([]domain.Group, error) {
return nil, nil
}
func (d *forgejoFixtureDirectory) ListUsers(ctx context.Context) ([]domain.User, error) {
user, _ := d.LookupUser(ctx, "")
return []domain.User{*user}, nil
}
func (d *forgejoFixtureDirectory) ValidatePassword(context.Context, string, string) (bool, error) {
return false, nil
}
func TestOnlineActualForgejoCallbackCurrentIssuer(t *testing.T) {
f := onlineProvider(t)
directory := &forgejoFixtureDirectory{user: domain.User{ID: "uid=operator,ou=people,dc=fixture", Username: "operator", Enabled: true, Tenant: "tenant:fixture", Groups: []string{"forge-admins"}}}
client := f.issuer.ClientConfig["current-client"]
client.GrantTypes = []string{"authorization_code"}
client.ServiceSubject = ""
client.Audience = client.ClientID
client.AllowedScopes = []string{"openid", "profile", "groups"}
client.RedirectURIs = []string{"https://forgejo.invalid/callback"}
mux := http.NewServeMux()
server := httptest.NewTLSServer(mux)
t.Cleanup(server.Close)
f.issuer.Users = directory
f.issuer.Issuer = server.URL
f.status.Users = directory
f.status.Issuer = server.URL
userinfo := &oidc.UserinfoHandler{Users: directory, SigningKey: &f.issuer.SigningKey.PublicKey, Issuer: server.URL, Tokens: f.issuer.Tokens, ClientConfig: f.issuer.ClientConfig, Emitter: telemetry.NoopEmitter{}}
var statusCalls, userinfoCalls atomic.Int64
mux.Handle("/token", f.issuer)
mux.HandleFunc("/introspect", func(w http.ResponseWriter, r *http.Request) { statusCalls.Add(1); f.status.Introspect(w, r) })
mux.HandleFunc("/revoke", f.status.Revoke)
mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) { userinfoCalls.Add(1); userinfo.ServeHTTP(w, r) })
keys := oidc.NewKeySet()
keys.AddKey("key-1", &f.issuer.SigningKey.PublicKey)
mux.Handle("/jwks", oidc.NewJWKSHandler(keys))
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"issuer": server.URL, "authorization_endpoint": server.URL + "/authorize", "token_endpoint": server.URL + "/token", "userinfo_endpoint": server.URL + "/userinfo", "jwks_uri": server.URL + "/jwks", "id_token_signing_alg_values_supported": []string{"RS256"}})
})
admission := f.client.admission
admission.Issuer = server.URL
admission.Audience = client.ClientID
if os.WriteFile(admission.CAFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: server.Certificate().Raw}), 0644) != nil {
t.Fatal("fixture CA write failed")
}
var err error
f.client, err = NewOnlineClient(admission)
if err != nil {
t.Fatal("fixture admission failed")
}
der, err := x509.MarshalPKIXPublicKey(&f.issuer.SigningKey.PublicKey)
if err != nil {
t.Fatal("fixture public key failed")
}
publicKey := string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}))
issue := func() Tokens {
verifier := strings.Repeat("v", 43)
digest := sha256.Sum256([]byte(verifier))
code := f.issuer.Sessions.Create(&oidc.PKCESession{ClientID: client.ClientID, Username: directory.user.Username, RedirectURI: client.RedirectURIs[0], PKCEChallengeMethod: "S256", PKCEChallenge: base64.RawURLEncoding.EncodeToString(digest[:]), Scopes: client.AllowedScopes, MFAVerified: true, AuthTime: time.Now().Add(-time.Minute).Truncate(time.Second), ExpiresAt: time.Now().Add(time.Minute)})
form := url.Values{"grant_type": {"authorization_code"}, "client_id": {client.ClientID}, "code": {code}, "code_verifier": {verifier}, "redirect_uri": {client.RedirectURIs[0]}}
r, _ := http.NewRequest(http.MethodPost, server.URL+"/token", strings.NewReader(form.Encode()))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
r.SetBasicAuth(url.QueryEscape(client.ClientID), url.QueryEscape(client.ClientSecret))
response, err := server.Client().Do(r)
if err != nil {
t.Fatal("actual fixture token HTTP failed")
}
defer response.Body.Close()
var tokens Tokens
if response.StatusCode != 200 || json.NewDecoder(response.Body).Decode(&tokens) != nil || tokens.AccessToken == "" || tokens.IDToken == "" {
t.Fatal("actual fixture human PKCE failed")
}
return tokens
}
first, second := issue(), issue()
directoryPath := t.TempDir()
var phases []string
probe := func(tokens Tokens, phase string) {
input := map[string]any{"id_token": tokens.IDToken, "access_token": tokens.AccessToken, "public_key_pem": publicKey, "tenant": directory.user.Tenant, "phase": phase, "current": admission}
raw, _ := json.Marshal(input)
file := filepath.Join(directoryPath, "consumer-input.json")
if os.WriteFile(file, raw, 0600) != nil {
t.Fatal("private fixture input failed")
}
command := exec.Command(os.Getenv("FORGEJO_CURRENT_TEST_BINARY"), "-test.run=^TestAccessEngineLiveFixtureCurrentIssuer$", "-test.v")
command.Env = append(os.Environ(), "FORGEJO_CURRENT_ISSUER_FIXTURE="+file)
output, err := command.CombinedOutput()
for _, private := range []string{first.AccessToken, first.IDToken, second.AccessToken, second.IDToken, client.ClientSecret} {
if bytes.Contains(output, []byte(private)) {
t.Fatal("consumer fixture diagnostic disclosed private input")
}
}
if err != nil {
t.Fatalf("actual consumer phase %s failed: %s", phase, output)
}
phases = append(phases, phase)
}
probe(first, "active")
probe(Tokens{AccessToken: second.AccessToken, IDToken: first.IDToken}, "mixed-pair")
if _, err := f.client.Revoke(context.Background(), first.AccessToken); err != nil {
t.Fatal("actual fixture revoke/independent negative failed")
}
probe(first, "revoked-after-userinfo")
probe(second, "active")
directory.mu.Lock()
directory.user.Groups = []string{"netkingdom-suspended"}
directory.mu.Unlock()
probe(second, "suspended-after-userinfo")
report := map[string]any{"status": "passed", "consumer_processes": len(phases), "phases": phases, "actual_https_human_PKCE": true, "actual_discovery_and_userinfo": true, "actual_introspection_and_revocation": true, "status_calls": statusCalls.Load(), "userinfo_calls": userinfoCalls.Load(), "actual_forgejo_callback_and_generated_client": true, "synthetic_MFA_session_seed_and_PDP_admission": true, "native_acceptance": false, "private_inputs": "fixture memory/tmpfs only"}
raw, _ := json.MarshalIndent(report, "", " ")
if os.WriteFile(os.Getenv("FORGEJO_CURRENT_REPORT"), append(raw, '\n'), 0600) != nil {
t.Fatal("public fixture result failed")
}
}