Files
mesh-catalog/modules/keycloak/cmd/keycloak-provider/oidc_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

200 lines
7.0 KiB
Go

package main
// What holds keycloak to the `oidc-client` provision: one confidential client per consumer, under
// the id and secret the mesh gave, redirecting only to the consumer's own callback under the names
// the mesh composed; made once and brought back on every apply; and a client the mesh did not make —
// same id or not — never adopted, changed or deleted. Ported from test/oidc.test.ts.
import (
"encoding/json"
"reflect"
"strings"
"testing"
)
func oidcWorld(t *testing.T) (*fakeKeycloak, OidcClients) {
f := newFakeKeycloak(t, "Novox", "pw")
return f, OidcClients{KC: f.client("pw"), Realm: "Novox"}
}
// grafana is a dashboard on the home server, as the mesh hands it to the provisioner.
func grafana(secret string, extra map[string]any) Provision {
values := map[string]any{"label": "grafana", "endpoint": "web", "port": 20010.0, "callback": "/login/generic_oauth",
"name": "grafana.example.org", "internal-name": "grafana.home.internal"}
for k, v := range extra {
values[k] = v
}
return Provision{As: "mesh_home_grafana", Password: secret, Consumer: "home", Values: values}
}
func only(t *testing.T, f *fakeKeycloak, clientID string) Rep {
t.Helper()
var found []Rep
for _, c := range f.clients {
if c["clientId"] == clientID {
found = append(found, c)
}
}
if len(found) != 1 {
t.Fatalf("exactly one client %s, found %d", clientID, len(found))
}
return found[0]
}
func mustHold(t *testing.T, o OidcClients, p Provision, want bool) {
t.Helper()
held, err := o.Holds(ctx, p)
if err != nil || held != want {
t.Fatalf("holds = %v, %v; want %v", held, err, want)
}
}
func TestTheRealmIsReadOutOfTheIssuer(t *testing.T) {
for issuer, want := range map[string]string{
"https://id.example.org/realms/Novox": "Novox",
"https://id.example.org/realms/Novox/": "Novox",
"http://127.0.0.1:18500/realms/master": "master",
} {
if got, err := RealmOf(issuer); err != nil || got != want {
t.Errorf("%s: %q %v", issuer, got, err)
}
}
if _, err := RealmOf("https://id.example.org"); err == nil || !strings.Contains(err.Error(), "realms") {
t.Error(err)
}
if _, err := RealmOf("keycloak"); err == nil || !strings.Contains(err.Error(), "not a URL") {
t.Error(err)
}
}
func TestTheRedirectIsTheCallbackUnderEveryComposedName(t *testing.T) {
root, redirects, err := RedirectsOf(grafana("s", nil).Values)
if err != nil || root != "https://grafana.example.org" || !reflect.DeepEqual(redirects,
[]string{"https://grafana.example.org/login/generic_oauth", "https://grafana.home.internal/login/generic_oauth"}) {
t.Fatal(root, redirects, err)
}
if _, r, _ := RedirectsOf(map[string]any{"callback": "/cb", "internal-name": "x.home.internal"}); !reflect.DeepEqual(r, []string{"https://x.home.internal/cb"}) {
t.Fatal(r)
}
for _, values := range []map[string]any{{"name": "g"}, {"name": "g", "callback": "login"}} {
if _, _, err := RedirectsOf(values); err == nil || !strings.Contains(err.Error(), "callback") {
t.Error(err)
}
}
if _, _, err := RedirectsOf(map[string]any{"callback": "/cb"}); err == nil || !strings.Contains(err.Error(), "label") {
t.Error(err)
}
}
func TestAConsumerIsGivenOneConfidentialClient(t *testing.T) {
f, o := oidcWorld(t)
if done, err := o.Ensure(ctx, grafana("s3cret", nil)); err != nil || done != "created" {
t.Fatal(done, err)
}
c := only(t, f, "mesh_home_grafana")
for k, v := range map[string]any{"publicClient": false, "clientAuthenticatorType": "client-secret", "secret": "s3cret",
"enabled": true, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "implicitFlowEnabled": false} {
if c[k] != v {
t.Errorf("%s = %v, want %v", k, c[k], v)
}
}
if attributes(c)[Mark] != "true" || len(c["protocolMappers"].([]any)) != 1 {
t.Fatal(c)
}
mustHold(t, o, grafana("s3cret", nil), true)
}
func TestApplyingTheSameGrantAgainMakesNoSecondClient(t *testing.T) {
f, o := oidcWorld(t)
o.Ensure(ctx, grafana("s", nil))
for i := 0; i < 2; i++ {
if done, err := o.Ensure(ctx, grafana("s", nil)); err != nil || done != "updated" {
t.Fatal(done, err)
}
}
if n := len(only(t, f, "mesh_home_grafana")["protocolMappers"].([]any)); n != 1 {
t.Fatalf("the roles mapper was added %d times", n)
}
}
func TestANewSecretIsAppliedInPlaceAndWhatTheMeshDoesNotOwnSurvives(t *testing.T) {
f, o := oidcWorld(t)
o.Ensure(ctx, grafana("s", nil))
c := only(t, f, "mesh_home_grafana")
id := c["id"]
c["consentRequired"] = true
attributes(c)["post.logout.redirect.uris"] = "+"
mustHold(t, o, grafana("rotated", nil), false)
if _, err := o.Ensure(ctx, grafana("rotated", map[string]any{"name": "dash.example.org"})); err != nil {
t.Fatal(err)
}
c = only(t, f, "mesh_home_grafana")
if c["id"] != id || c["secret"] != "rotated" || c["rootUrl"] != "https://dash.example.org" ||
c["consentRequired"] != true || attributes(c)["post.logout.redirect.uris"] != "+" || attributes(c)[Mark] != "true" {
raw, _ := json.Marshal(c)
t.Fatal(string(raw))
}
mustHold(t, o, grafana("rotated", map[string]any{"name": "dash.example.org"}), true)
}
func TestAClientEditedBehindTheMeshsBackIsNotHeldAndIsMadeWhole(t *testing.T) {
f, o := oidcWorld(t)
o.Ensure(ctx, grafana("s", nil))
only(t, f, "mesh_home_grafana")["redirectUris"] = []any{"*"}
mustHold(t, o, grafana("s", nil), false)
o.Ensure(ctx, grafana("s", nil))
mustHold(t, o, grafana("s", nil), true)
only(t, f, "mesh_home_grafana")["protocolMappers"] = []any{}
mustHold(t, o, grafana("s", nil), false)
o.Ensure(ctx, grafana("s", nil))
mustHold(t, o, grafana("s", nil), true)
f.clients = map[string]Rep{}
mustHold(t, o, grafana("s", nil), false)
}
func TestAClientTheMeshDidNotMakeIsRefusedAndLeftAlone(t *testing.T) {
f, o := oidcWorld(t)
f.clients["theirs"] = Rep{"id": "theirs", "clientId": "mesh_home_grafana", "secret": "their-secret", "redirectUris": []any{"*"}}
before, _ := json.Marshal(f.clients["theirs"])
from := len(f.calls)
if _, err := o.Ensure(ctx, grafana("s", nil)); err == nil || !strings.Contains(err.Error(), "did not make") {
t.Fatal(err)
}
after, _ := json.Marshal(f.clients["theirs"])
if string(before) != string(after) {
t.Fatal("changed")
}
for _, c := range f.calls[from:] {
if !strings.HasPrefix(c, "GET") && !strings.HasPrefix(c, "POST /realms/master") {
t.Fatalf("wrote: %v", f.calls[from:])
}
}
mustHold(t, o, grafana("s", nil), false)
if done, _ := o.Remove(ctx, "mesh_home_grafana"); done != "not ours" || f.clients["theirs"] == nil {
t.Fatal(done)
}
}
func TestAWithdrawnClientIsRemovedAndAnAbsentOneIsNoError(t *testing.T) {
f, o := oidcWorld(t)
o.Ensure(ctx, grafana("s", nil))
if done, err := o.Remove(ctx, "mesh_home_grafana"); done != "removed" || err != nil || len(f.clients) != 0 {
t.Fatal(done, err)
}
if done, err := o.Remove(ctx, "mesh_home_grafana"); done != "absent" || err != nil {
t.Fatal(done, err)
}
}
func TestAContributionWithNoCallbackMakesNoClient(t *testing.T) {
f, o := oidcWorld(t)
p := grafana("s", nil)
p.Values = map[string]any{"name": "grafana.example.org"}
if _, err := o.Ensure(ctx, p); err == nil || !strings.Contains(err.Error(), "callback") || len(f.clients) != 0 {
t.Fatal(err)
}
}