The hourly release of ADR 0229's brake still ended in the mesh acting alone on a mistake. A consumer now stays active until the same unasked set holds for five passes, waits for a person past three or half of those held, is disabled and marked rather than withdrawn, comes back as it was when asked again, and is deleted only through the provider's delete tool. The backend keeps the mark, so a restart forgets nothing and finds what was withdrawn before.
266 lines
9.5 KiB
Go
266 lines
9.5 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"
|
|
"time"
|
|
)
|
|
|
|
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.Retire(ctx, "mesh_home_grafana", "x", time.Now()); done != "not ours" || f.clients["theirs"]["enabled"] == false {
|
|
t.Fatal(done)
|
|
}
|
|
if err := o.Delete(ctx, "mesh_home_grafana"); err == nil || f.clients["theirs"] == nil {
|
|
t.Fatal("deleted a client the mesh did not make")
|
|
}
|
|
if inv, _ := o.Inventory(ctx); len(inv.Active)+len(inv.Retired) != 0 {
|
|
t.Fatalf("listed a client the mesh did not make: %+v", inv)
|
|
}
|
|
}
|
|
|
|
// Retired is disabled and marked, everything else kept; asked for again it is enabled as it was; only
|
|
// a deletion removes it, and never while enabled (novox/hq ADR 0230).
|
|
func TestARetiredClientIsDisabledKeptAndEnabledAgainAsItWas(t *testing.T) {
|
|
f, o := oidcWorld(t)
|
|
p := grafana("s", nil)
|
|
if _, err := o.Ensure(ctx, p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var id string
|
|
for k := range f.clients {
|
|
id = k
|
|
}
|
|
f.clients[id]["description2"] = "an operator's own field"
|
|
at := time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC)
|
|
if done, err := o.Retire(ctx, "mesh_home_grafana", "the mesh stopped asking for it", at); done != "retired" || err != nil {
|
|
t.Fatal(done, err)
|
|
}
|
|
c := f.clients[id]
|
|
if c["enabled"] != false || c["secret"] != "s" || attributes(c)[MarkRetired] != "2026-10-06T12:00:00Z" ||
|
|
attributes(c)[MarkRetiredWhy] != "the mesh stopped asking for it" || c["description2"] == nil {
|
|
t.Fatalf("%v", c)
|
|
}
|
|
mustHold(t, o, p, false)
|
|
inv, err := o.Inventory(ctx)
|
|
if err != nil || len(inv.Active) != 0 || len(inv.Retired) != 1 || !inv.Retired[0].RetiredAt.Equal(at) ||
|
|
inv.Retired[0].Consumer != "mesh_home_grafana" {
|
|
t.Fatalf("%+v %v", inv, err)
|
|
}
|
|
// Asked for again: enabled, unmarked, the same client.
|
|
if _, err := o.Ensure(ctx, p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c = f.clients[id]
|
|
if c["enabled"] != true || attributes(c)[MarkRetired] != nil || attributes(c)[MarkRetiredWhy] != nil || c["description2"] == nil {
|
|
t.Fatalf("%v", c)
|
|
}
|
|
mustHold(t, o, p, true)
|
|
if inv, _ := o.Inventory(ctx); len(inv.Active) != 1 || len(inv.Retired) != 0 {
|
|
t.Fatalf("%+v", inv)
|
|
}
|
|
// Never deleted while enabled; deleted once retired; absent is no error.
|
|
if err := o.Delete(ctx, "mesh_home_grafana"); err == nil || len(f.clients) != 1 {
|
|
t.Fatal("deleted an enabled client")
|
|
}
|
|
o.Retire(ctx, "mesh_home_grafana", "again", at)
|
|
if err := o.Delete(ctx, "mesh_home_grafana"); err != nil || len(f.clients) != 0 {
|
|
t.Fatal(err, f.clients)
|
|
}
|
|
if err := o.Delete(ctx, "mesh_home_grafana"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if done, err := o.Retire(ctx, "mesh_home_grafana", "x", at); done != "absent" || err != nil {
|
|
t.Fatal(done, err)
|
|
}
|
|
}
|
|
|
|
// A client the mesh made and found disabled without the mark — before ADR 0230 — is listed retired
|
|
// with no date, which the loop adopts.
|
|
func TestAClientFoundDisabledIsListedRetiredWithoutADate(t *testing.T) {
|
|
f, o := oidcWorld(t)
|
|
o.Ensure(ctx, grafana("s", nil))
|
|
for _, c := range f.clients {
|
|
c["enabled"] = false
|
|
}
|
|
inv, err := o.Inventory(ctx)
|
|
if err != nil || len(inv.Retired) != 1 || !inv.Retired[0].RetiredAt.IsZero() {
|
|
t.Fatalf("%+v %v", inv, 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)
|
|
}
|
|
}
|