Files
mesh-catalog/modules/keycloak/cmd/keycloak-provider/fake_test.go
T
jochen 77fb1ecfb2 keycloak: port to Go and repair an admin that refuses the mesh's secret
Twice the identity provider's admin kept an older password than the one the
mesh minted (an adopted, then a moved database), and the provisioner failed
every consumer until it was repaired by hand (hq issue 179). The module now
checks the admin's login and repairs a refusal itself through the server's
bootstrap command, verifies, brakes a failed repair and announces it, and
stops asking the server while refused. Ported to Go to change it.
2026-10-06 00:13:42 +02:00

170 lines
4.3 KiB
Go

package main
// A fake Keycloak: the token endpoint, accepting one password that a test may change, and the admin
// routes this module touches on one realm's clients, answering with the status codes and shapes
// Keycloak gives.
import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
)
var ctx = context.Background()
type fakeKeycloak struct {
mu sync.Mutex
srv *httptest.Server
realm string
password string // what the admin's login accepts
down bool // answer 503, as a starting server does
logins int
clients map[string]Rep
calls []string
}
func newFakeKeycloak(t *testing.T, realm, password string) *fakeKeycloak {
f := &fakeKeycloak{realm: realm, password: password, clients: map[string]Rep{}}
f.srv = httptest.NewServer(http.HandlerFunc(f.serve))
t.Cleanup(f.srv.Close)
return f
}
func uuid() string {
b := make([]byte, 16)
_, _ = rand.Read(b)
return hex.EncodeToString(b)
}
func send(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if v != nil {
_ = json.NewEncoder(w).Encode(v)
}
}
func (f *fakeKeycloak) set(fn func()) {
f.mu.Lock()
defer f.mu.Unlock()
fn()
}
func (f *fakeKeycloak) serve(w http.ResponseWriter, r *http.Request) {
f.mu.Lock()
defer f.mu.Unlock()
f.calls = append(f.calls, r.Method+" "+r.URL.Path)
if f.down {
send(w, 503, nil)
return
}
if r.URL.Path == "/realms/master/protocol/openid-connect/token" {
_ = r.ParseForm()
f.logins++
if r.PostForm.Get("password") != f.password {
send(w, 401, map[string]string{"error": "invalid_grant", "error_description": "Invalid user credentials"})
return
}
send(w, 200, map[string]any{"access_token": "t", "expires_in": 300})
return
}
base := "/admin/realms/" + f.realm + "/clients"
if !strings.HasPrefix(r.URL.Path, base) {
send(w, 404, map[string]string{"error": "Realm not found."})
return
}
var rest []string
for _, p := range strings.Split(strings.TrimPrefix(r.URL.Path, base), "/") {
if p != "" {
rest = append(rest, p)
}
}
body := func() Rep {
raw, _ := io.ReadAll(r.Body)
var v Rep
_ = json.Unmarshal(raw, &v)
return v
}
if len(rest) == 0 && r.Method == "GET" {
want := r.URL.Query().Get("clientId")
out := []Rep{}
for _, c := range f.clients {
if want == "" || c["clientId"] == want {
out = append(out, c)
}
}
send(w, 200, out)
return
}
if len(rest) == 0 && r.Method == "POST" {
rep := body()
for _, c := range f.clients {
if c["clientId"] == rep["clientId"] {
send(w, 409, map[string]string{"errorMessage": "exists"})
return
}
}
id := uuid()
mappers := []any{}
if list, ok := rep["protocolMappers"].([]any); ok {
for _, m := range list {
mm := m.(map[string]any)
mm["id"] = uuid()
mappers = append(mappers, mm)
}
}
rep["id"], rep["protocolMappers"] = id, mappers
f.clients[id] = rep
send(w, 201, nil)
return
}
c := f.clients[rest[0]]
if c == nil {
send(w, 404, map[string]string{"error": "Could not find client"})
return
}
switch {
case len(rest) == 1 && r.Method == "PUT":
// Keycloak ignores protocolMappers on a client update: they have their own endpoints.
rep := body()
rep["id"], rep["protocolMappers"] = c["id"], c["protocolMappers"]
f.clients[rest[0]] = rep
send(w, 204, nil)
case len(rest) == 1 && r.Method == "DELETE":
delete(f.clients, rest[0])
send(w, 204, nil)
case rest[1] == "client-secret" && r.Method == "GET":
send(w, 200, map[string]any{"type": "secret", "value": c["secret"]})
case rest[1] == "protocol-mappers" && r.Method == "GET":
send(w, 200, c["protocolMappers"])
case rest[1] == "protocol-mappers" && r.Method == "POST":
m := body()
m["id"] = uuid()
list, _ := c["protocolMappers"].([]any)
c["protocolMappers"] = append(list, m)
send(w, 201, nil)
case rest[1] == "protocol-mappers" && r.Method == "PUT":
m := body()
list, _ := c["protocolMappers"].([]any)
for i, x := range list {
if x.(map[string]any)["id"] == rest[4] {
list[i] = m
}
}
send(w, 204, nil)
default:
send(w, 405, nil)
}
}
func (f *fakeKeycloak) client(password string) *Client {
return NewClient(f.srv.URL, "admin", func() (string, error) { return password, nil }, "master")
}