mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
An opt-out flag exists as an escape hatch closes https://linear.app/codercom/issue/PLAT-343/automatically-reset-user-link-for-affected-users-when-idp-provider
168 lines
5.3 KiB
Go
168 lines
5.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"cdr.dev/slog/v3"
|
|
"cdr.dev/slog/v3/sloggers/slogtest"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbgen"
|
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/testutil"
|
|
"github.com/coder/serpent"
|
|
)
|
|
|
|
func fakeOIDCIssuerDiscovery(t *testing.T, issuer string) *httptest.Server {
|
|
t.Helper()
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/.well-known/openid-configuration" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]string{
|
|
"issuer": issuer,
|
|
})
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
return srv
|
|
}
|
|
|
|
func newDeploymentValues(t *testing.T, issuerURL string, autoRepair bool) *codersdk.DeploymentValues {
|
|
t.Helper()
|
|
vals := &codersdk.DeploymentValues{}
|
|
require.NoError(t, vals.OIDC.IssuerURL.Set(issuerURL))
|
|
vals.OIDC.AutoRepairLinks = serpent.Bool(autoRepair)
|
|
return vals
|
|
}
|
|
|
|
func TestOIDCAuthLinks(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("RepairsMismatchedLinks", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
logger := testutil.Logger(t)
|
|
|
|
const expectedIssuer = "https://accounts.google.com"
|
|
oidcSrv := fakeOIDCIssuerDiscovery(t, expectedIssuer)
|
|
|
|
db, _ := dbtestutil.NewDB(t)
|
|
|
|
// Correctly linked user.
|
|
correctUser := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: correctUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: expectedIssuer + "||sub-correct",
|
|
})
|
|
|
|
// Mismatched user.
|
|
mismatchedUser := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: mismatchedUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: "https://old-issuer.example.com||sub-mismatched",
|
|
})
|
|
|
|
vals := newDeploymentValues(t, oidcSrv.URL, true)
|
|
err := oidcAuthLinks(ctx, logger, oidcSrv.Client(), vals, db)
|
|
require.NoError(t, err)
|
|
|
|
// Mismatched link should be reset.
|
|
link, err := db.GetUserLinkByUserIDLoginType(ctx, database.GetUserLinkByUserIDLoginTypeParams{
|
|
UserID: mismatchedUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "", link.LinkedID)
|
|
|
|
// Correct link should be untouched.
|
|
link, err = db.GetUserLinkByUserIDLoginType(ctx, database.GetUserLinkByUserIDLoginTypeParams{
|
|
UserID: correctUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedIssuer+"||sub-correct", link.LinkedID)
|
|
})
|
|
|
|
t.Run("AutoRepairDisabledSkipsReset", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
logger := testutil.Logger(t)
|
|
|
|
const expectedIssuer = "https://accounts.google.com"
|
|
oidcSrv := fakeOIDCIssuerDiscovery(t, expectedIssuer)
|
|
|
|
db, _ := dbtestutil.NewDB(t)
|
|
|
|
mismatchedUser := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: mismatchedUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: "https://old-issuer.example.com||sub-mismatched",
|
|
})
|
|
|
|
vals := newDeploymentValues(t, oidcSrv.URL, false)
|
|
err := oidcAuthLinks(ctx, logger, oidcSrv.Client(), vals, db)
|
|
require.NoError(t, err)
|
|
|
|
// Link must not be modified when auto-repair is off.
|
|
link, err := db.GetUserLinkByUserIDLoginType(ctx, database.GetUserLinkByUserIDLoginTypeParams{
|
|
UserID: mismatchedUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "https://old-issuer.example.com||sub-mismatched", link.LinkedID)
|
|
})
|
|
|
|
t.Run("DiscoveryFailureNonFatal", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
// oidcAuthLinks logs errors but does not return them.
|
|
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
|
|
|
// Serve 500 so discovery fails.
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
|
|
db, _ := dbtestutil.NewDB(t)
|
|
|
|
vals := newDeploymentValues(t, srv.URL, true)
|
|
err := oidcAuthLinks(ctx, logger, srv.Client(), vals, db)
|
|
require.NoError(t, err, "discovery failure must not be fatal")
|
|
})
|
|
|
|
t.Run("AnalyzeFailureNonFatal", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
// oidcAuthLinks logs errors but does not return them.
|
|
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
|
|
|
const expectedIssuer = "https://accounts.google.com"
|
|
oidcSrv := fakeOIDCIssuerDiscovery(t, expectedIssuer)
|
|
|
|
// Use a closed DB so the analysis query fails.
|
|
connectionURL, err := dbtestutil.Open(t)
|
|
require.NoError(t, err)
|
|
sqlDB, err := sql.Open("postgres", connectionURL)
|
|
require.NoError(t, err)
|
|
db := database.New(sqlDB)
|
|
// Close the underlying connection so the query fails.
|
|
sqlDB.Close()
|
|
|
|
vals := newDeploymentValues(t, oidcSrv.URL, true)
|
|
err = oidcAuthLinks(ctx, logger, oidcSrv.Client(), vals, db)
|
|
require.NoError(t, err, "analysis failure must not be fatal")
|
|
})
|
|
}
|