The original NOTE claimed Dex's kubernetes storage types Hash as []byte, requiring the bcrypt string to be base64-encoded before storage. This was wrong: Dex v2.41 stores and compares the hash field as a plain string. The base64-encoding caused every invite login to fail with 'Invalid credentials' because Dex passed the base64 bytes (starting with 'J' not '$') directly to bcrypt. Static passwords in the configmap always used raw bcrypt strings and worked fine — confirming the dynamic CR encoding was the bug.
102 lines
3.3 KiB
Go
102 lines
3.3 KiB
Go
package dex
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// newTestClient points a PasswordClient at an httptest server, using that
|
|
// server's TLS client so the in-cluster TLS path is exercised without a real CA.
|
|
func newTestClient(srv *httptest.Server) *PasswordClient {
|
|
return newClient(srv.URL, "test-token", srv.Client())
|
|
}
|
|
|
|
func TestCreatePasswordSuccess(t *testing.T) {
|
|
var gotAuth, gotPath, gotMethod string
|
|
var gotBody password
|
|
|
|
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotAuth, gotPath, gotMethod = r.Header.Get("Authorization"), r.URL.Path, r.Method
|
|
b, _ := io.ReadAll(r.Body)
|
|
_ = json.Unmarshal(b, &gotBody)
|
|
w.WriteHeader(http.StatusCreated)
|
|
_, _ = w.Write([]byte(`{"kind":"Password"}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
err := newTestClient(srv).CreatePassword(context.Background(),
|
|
"New.User@Example.com", "$2a$12$abcdefghijklmnopqrstuv", "user-uuid-1")
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, http.MethodPost, gotMethod)
|
|
require.Equal(t, passwordsPath, gotPath)
|
|
require.Equal(t, "Bearer test-token", gotAuth)
|
|
|
|
// Email/username carry the raw address; the CR name is sanitised + lowercased.
|
|
require.Equal(t, "New.User@Example.com", gotBody.Email)
|
|
require.Equal(t, "New.User@Example.com", gotBody.Username)
|
|
require.Equal(t, "user-uuid-1", gotBody.UserID)
|
|
require.Equal(t, "new-dot-user-at-example-dot-com", gotBody.Metadata["name"])
|
|
require.Equal(t, "auth", gotBody.Metadata["namespace"])
|
|
|
|
// Hash is stored as the raw bcrypt string — Dex compares it directly.
|
|
require.Equal(t, "$2a$12$abcdefghijklmnopqrstuv", gotBody.Hash)
|
|
}
|
|
|
|
func TestCreatePasswordConflict(t *testing.T) {
|
|
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusConflict)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
err := newTestClient(srv).CreatePassword(context.Background(), "dup@example.com", "$2a$12$x", "u")
|
|
require.ErrorIs(t, err, ErrPasswordExists)
|
|
}
|
|
|
|
func TestCreatePasswordForbidden(t *testing.T) {
|
|
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusForbidden)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
err := newTestClient(srv).CreatePassword(context.Background(), "x@example.com", "$2a$12$x", "u")
|
|
require.ErrorIs(t, err, ErrForbidden)
|
|
}
|
|
|
|
func TestCreatePasswordUnexpectedStatus(t *testing.T) {
|
|
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = w.Write([]byte("boom"))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
err := newTestClient(srv).CreatePassword(context.Background(), "x@example.com", "$2a$12$x", "u")
|
|
require.Error(t, err)
|
|
require.NotErrorIs(t, err, ErrPasswordExists)
|
|
require.NotErrorIs(t, err, ErrForbidden)
|
|
require.Contains(t, err.Error(), "500")
|
|
}
|
|
|
|
func TestNewPasswordClientNotInCluster(t *testing.T) {
|
|
// In the test environment the SA token mount does not exist.
|
|
_, err := NewPasswordClient()
|
|
require.ErrorIs(t, err, ErrNotInCluster)
|
|
}
|
|
|
|
func TestPasswordName(t *testing.T) {
|
|
cases := map[string]string{
|
|
"Alice@Example.com": "alice-at-example-dot-com",
|
|
"a.b+c@gmail.com": "a-dot-b-c-at-gmail-dot-com",
|
|
"UPPER@DOMAIN.IO": "upper-at-domain-dot-io",
|
|
}
|
|
for in, want := range cases {
|
|
require.Equal(t, want, passwordName(in), in)
|
|
}
|
|
}
|