mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
fix(site): use RBAC-filtered organizations for embedded metadata (#27110)
This commit is contained in:
+4
-3
@@ -436,9 +436,10 @@ func (h *Handler) renderHTMLWithState(r *http.Request, filePath string, state ht
|
||||
return err
|
||||
})
|
||||
eg.Go(func() error {
|
||||
orgs, err := h.opts.Database.GetOrganizationsByUserID(ctx, database.GetOrganizationsByUserIDParams{
|
||||
UserID: apiKey.UserID,
|
||||
})
|
||||
// Match the /api/v2/organizations endpoint: RBAC-filtered and
|
||||
// excluding deleted organizations. Membership-scoped queries would
|
||||
// hide organizations that roles like owner can view.
|
||||
orgs, err := h.opts.Database.GetOrganizations(ctx, database.GetOrganizationsParams{})
|
||||
if err == nil {
|
||||
userOrgs = orgs
|
||||
}
|
||||
|
||||
@@ -28,8 +28,10 @@ import (
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/appearance"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
@@ -349,6 +351,98 @@ func TestInjectionFailureProducesCleanHTML(t *testing.T) {
|
||||
assert.Equal(t, "<html></html>", body)
|
||||
}
|
||||
|
||||
func TestOrganizationsMetadata(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// GIVEN: a site handler backed by an authz-wrapped database,
|
||||
// matching production wiring, and a template that renders only
|
||||
// the organizations metadata.
|
||||
siteFS := fstest.MapFS{
|
||||
"index.html": &fstest.MapFile{
|
||||
Data: []byte("{{ .Organizations }}"),
|
||||
},
|
||||
}
|
||||
rawDB, _ := dbtestutil.NewDB(t)
|
||||
db := dbauthz.New(
|
||||
rawDB,
|
||||
rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry()),
|
||||
testutil.Logger(t),
|
||||
coderdtest.AccessControlStorePointer(),
|
||||
)
|
||||
handler, err := site.New(&site.Options{
|
||||
Telemetry: telemetry.NewNoop(),
|
||||
Database: db,
|
||||
SiteFS: siteFS,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
// GIVEN: an org with members, another org, and a soft-deleted org.
|
||||
memberOrg := dbgen.Organization(t, rawDB, database.Organization{})
|
||||
otherOrg := dbgen.Organization(t, rawDB, database.Organization{})
|
||||
deletedOrg := dbgen.Organization(t, rawDB, database.Organization{})
|
||||
err = rawDB.UpdateOrganizationDeletedByID(ctx, database.UpdateOrganizationDeletedByIDParams{
|
||||
ID: deletedOrg.ID,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
fetchOrgIDs := func(t *testing.T, token string) []uuid.UUID {
|
||||
t.Helper()
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
r.Header.Set(codersdk.SessionTokenHeader, token)
|
||||
rw := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rw, r)
|
||||
require.Equal(t, http.StatusOK, rw.Code)
|
||||
var orgs []codersdk.Organization
|
||||
err := json.Unmarshal([]byte(html.UnescapeString(rw.Body.String())), &orgs)
|
||||
require.NoError(t, err)
|
||||
ids := make([]uuid.UUID, 0, len(orgs))
|
||||
for _, org := range orgs {
|
||||
ids = append(ids, org.ID)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// WHEN: an owner who is a member of only one org loads the page.
|
||||
owner := dbgen.User(t, rawDB, database.User{
|
||||
RBACRoles: []string{codersdk.RoleOwner},
|
||||
})
|
||||
dbgen.OrganizationMember(t, rawDB, database.OrganizationMember{
|
||||
OrganizationID: memberOrg.ID,
|
||||
UserID: owner.ID,
|
||||
})
|
||||
_, ownerToken := dbgen.APIKey(t, rawDB, database.APIKey{
|
||||
UserID: owner.ID,
|
||||
ExpiresAt: time.Now().Add(time.Hour),
|
||||
})
|
||||
|
||||
// THEN: the metadata includes every non-deleted org, not just the
|
||||
// orgs the owner is a member of.
|
||||
ownerOrgIDs := fetchOrgIDs(t, ownerToken)
|
||||
assert.Contains(t, ownerOrgIDs, memberOrg.ID)
|
||||
assert.Contains(t, ownerOrgIDs, otherOrg.ID)
|
||||
assert.NotContains(t, ownerOrgIDs, deletedOrg.ID)
|
||||
|
||||
// WHEN: a regular member of a single org loads the page.
|
||||
member := dbgen.User(t, rawDB, database.User{})
|
||||
dbgen.OrganizationMember(t, rawDB, database.OrganizationMember{
|
||||
OrganizationID: memberOrg.ID,
|
||||
UserID: member.ID,
|
||||
})
|
||||
_, memberToken := dbgen.APIKey(t, rawDB, database.APIKey{
|
||||
UserID: member.ID,
|
||||
ExpiresAt: time.Now().Add(time.Hour),
|
||||
})
|
||||
|
||||
// THEN: the metadata only includes orgs the member can read.
|
||||
memberOrgIDs := fetchOrgIDs(t, memberToken)
|
||||
assert.Contains(t, memberOrgIDs, memberOrg.ID)
|
||||
assert.NotContains(t, memberOrgIDs, otherOrg.ID)
|
||||
assert.NotContains(t, memberOrgIDs, deletedOrg.ID)
|
||||
}
|
||||
|
||||
func TestCaching(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user