mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
306 lines
9.4 KiB
Go
306 lines
9.4 KiB
Go
package authlink_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/coderd/authlink"
|
|
"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/testutil"
|
|
)
|
|
|
|
func TestAnalyzeOIDCLinks(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("MixedIssuers", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
db, _ := dbtestutil.NewDB(t)
|
|
const expectedIssuer = "https://accounts.google.com"
|
|
|
|
// 3 users linked to the expected issuer.
|
|
for i := 0; i < 3; i++ {
|
|
user := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: user.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: expectedIssuer + "||sub-" + user.ID.String(),
|
|
})
|
|
}
|
|
|
|
// 2 users linked to an old issuer.
|
|
for i := 0; i < 2; i++ {
|
|
user := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: user.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: "https://old-issuer.example.com||sub-" + user.ID.String(),
|
|
})
|
|
}
|
|
|
|
// 1 user linked to another old issuer.
|
|
user := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: user.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: "https://staging.example.com||sub-" + user.ID.String(),
|
|
})
|
|
|
|
// 1 unlinked user (empty linked_id).
|
|
unlinkedUser := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: unlinkedUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: "",
|
|
})
|
|
|
|
analysis, err := authlink.AnalyzeOIDCLinks(ctx, db, expectedIssuer)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 7, analysis.Total)
|
|
require.Equal(t, 3, analysis.CorrectIssuer)
|
|
require.Equal(t, 1, analysis.Unlinked)
|
|
require.Equal(t, 3, analysis.MismatchedTotal())
|
|
require.Equal(t, 2, analysis.MismatchedCounts["https://old-issuer.example.com"])
|
|
require.Equal(t, 1, analysis.MismatchedCounts["https://staging.example.com"])
|
|
})
|
|
|
|
t.Run("NoOIDCUsers", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
db, _ := dbtestutil.NewDB(t)
|
|
|
|
analysis, err := authlink.AnalyzeOIDCLinks(ctx, db, "https://issuer.example.com")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, analysis.Total)
|
|
require.Equal(t, 0, analysis.CorrectIssuer)
|
|
require.Equal(t, 0, analysis.Unlinked)
|
|
require.Equal(t, 0, analysis.MismatchedTotal())
|
|
})
|
|
|
|
t.Run("AllCorrect", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
db, _ := dbtestutil.NewDB(t)
|
|
const expectedIssuer = "https://accounts.google.com"
|
|
|
|
for i := 0; i < 3; i++ {
|
|
user := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: user.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: expectedIssuer + "||sub-" + user.ID.String(),
|
|
})
|
|
}
|
|
|
|
analysis, err := authlink.AnalyzeOIDCLinks(ctx, db, expectedIssuer)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 3, analysis.Total)
|
|
require.Equal(t, 3, analysis.CorrectIssuer)
|
|
require.Equal(t, 0, analysis.Unlinked)
|
|
require.Equal(t, 0, analysis.MismatchedTotal())
|
|
})
|
|
|
|
t.Run("DeletedUsersExcluded", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
db, _ := dbtestutil.NewDB(t)
|
|
const expectedIssuer = "https://accounts.google.com"
|
|
|
|
// Active user with mismatched issuer.
|
|
activeUser := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: activeUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: "https://old-issuer.example.com||sub-active",
|
|
})
|
|
|
|
// Create user and link first, then soft-delete the user.
|
|
// The DB trigger prevents inserting links for already-deleted users.
|
|
deletedUser := dbgen.User(t, db, database.User{
|
|
LoginType: database.LoginTypeOIDC,
|
|
})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: deletedUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: "https://old-issuer.example.com||sub-deleted",
|
|
})
|
|
require.NoError(t, db.UpdateUserDeletedByID(ctx, deletedUser.ID))
|
|
|
|
analysis, err := authlink.AnalyzeOIDCLinks(ctx, db, expectedIssuer)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, analysis.Total)
|
|
require.Equal(t, 1, analysis.MismatchedTotal())
|
|
})
|
|
|
|
t.Run("NonOIDCLinksExcluded", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
db, _ := dbtestutil.NewDB(t)
|
|
const expectedIssuer = "https://accounts.google.com"
|
|
|
|
// GitHub user link should not be counted.
|
|
ghUser := dbgen.User(t, db, database.User{LoginType: database.LoginTypeGithub})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: ghUser.ID,
|
|
LoginType: database.LoginTypeGithub,
|
|
LinkedID: "github||12345",
|
|
})
|
|
|
|
analysis, err := authlink.AnalyzeOIDCLinks(ctx, db, expectedIssuer)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, analysis.Total)
|
|
})
|
|
}
|
|
|
|
func TestResetMismatchedOIDCLinks(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("ResetsOnlyMismatched", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
db, _ := dbtestutil.NewDB(t)
|
|
const expectedIssuer = "https://accounts.google.com"
|
|
|
|
// Correctly linked user.
|
|
correctUser := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
correctLink := 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",
|
|
})
|
|
|
|
// Unlinked user (empty linked_id).
|
|
unlinkedUser := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: unlinkedUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: "",
|
|
})
|
|
|
|
count, err := authlink.ResetMismatchedOIDCLinks(ctx, db, expectedIssuer)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 1, count)
|
|
|
|
// Verify the correct link is unchanged.
|
|
link, err := db.GetUserLinkByUserIDLoginType(ctx, database.GetUserLinkByUserIDLoginTypeParams{
|
|
UserID: correctUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, correctLink.LinkedID, link.LinkedID)
|
|
|
|
// Verify the mismatched link was reset.
|
|
link, err = db.GetUserLinkByUserIDLoginType(ctx, database.GetUserLinkByUserIDLoginTypeParams{
|
|
UserID: mismatchedUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "", link.LinkedID)
|
|
|
|
// Verify the unlinked user is still unlinked.
|
|
link, err = db.GetUserLinkByUserIDLoginType(ctx, database.GetUserLinkByUserIDLoginTypeParams{
|
|
UserID: unlinkedUser.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "", link.LinkedID)
|
|
})
|
|
|
|
t.Run("NothingToReset", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
db, _ := dbtestutil.NewDB(t)
|
|
const expectedIssuer = "https://accounts.google.com"
|
|
|
|
user := dbgen.User(t, db, database.User{LoginType: database.LoginTypeOIDC})
|
|
dbgen.UserLink(t, db, database.UserLink{
|
|
UserID: user.ID,
|
|
LoginType: database.LoginTypeOIDC,
|
|
LinkedID: expectedIssuer + "||sub-correct",
|
|
})
|
|
|
|
count, err := authlink.ResetMismatchedOIDCLinks(ctx, db, expectedIssuer)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, count)
|
|
})
|
|
}
|
|
|
|
func TestResolveIssuer(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("Success", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
const expectedIssuer = "https://accounts.google.com"
|
|
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": expectedIssuer,
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
issuer, err := authlink.ResolveIssuer(ctx, srv.Client(), srv.URL)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedIssuer, issuer)
|
|
})
|
|
|
|
t.Run("EmptyIssuer", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]string{
|
|
"issuer": "",
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
_, err := authlink.ResolveIssuer(ctx, srv.Client(), srv.URL)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "empty issuer")
|
|
})
|
|
|
|
t.Run("HTTPError", func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
_, err := authlink.ResolveIssuer(ctx, srv.Client(), srv.URL)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "HTTP 500")
|
|
})
|
|
}
|