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.
200 lines
7.0 KiB
Go
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)
|
|
}
|
|
}
|