diff --git a/coderd/exp_chats.go b/coderd/exp_chats.go index c626120371..bd310cc175 100644 --- a/coderd/exp_chats.go +++ b/coderd/exp_chats.go @@ -405,20 +405,15 @@ func (api *API) postChats(rw http.ResponseWriter, r *http.Request) { }) return } - orgMembers, err := api.Database.OrganizationMembers(ctx, database.OrganizationMembersParams{ - OrganizationID: req.OrganizationID, - UserID: apiKey.UserID, - IncludeSystem: false, - GithubUserID: 0, - }) + isMember, err := httpmw.UserAuthorization(ctx).HasOrganizationMembership(req.OrganizationID) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to validate organization membership.", - Detail: err.Error(), + Detail: xerrors.Errorf("check organization membership: %w", err).Error(), }) return } - if len(orgMembers) == 0 { + if !isMember { httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ Message: "You are not a member of the specified organization.", }) diff --git a/coderd/rbac/authz.go b/coderd/rbac/authz.go index 4cd05d2855..78684f35ec 100644 --- a/coderd/rbac/authz.go +++ b/coderd/rbac/authz.go @@ -12,6 +12,7 @@ import ( "time" "github.com/ammario/tlru" + "github.com/google/uuid" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/v1/rego" "github.com/prometheus/client_golang/prometheus" @@ -172,6 +173,25 @@ func (s Subject) SafeRoleNames() []RoleIdentifier { return s.Roles.Names() } +// HasOrganizationMembership reports whether the subject has explicit +// membership in organizationID through an org-scoped role. Site-wide roles +// alone do not count as organization membership. +func (s Subject) HasOrganizationMembership(organizationID uuid.UUID) (bool, error) { + roles, err := s.Roles.Expand() + if err != nil { + return false, xerrors.Errorf("expand user authorization roles: %w", err) + } + + organizationIDString := organizationID.String() + for _, role := range roles { + if _, ok := role.ByOrgID[organizationIDString]; ok { + return true, nil + } + } + + return false, nil +} + type Authorizer interface { // Authorize will authorize the given subject to perform the given action // on the given object. Authorize is pure and deterministic with respect to diff --git a/coderd/workspaceapps/db.go b/coderd/workspaceapps/db.go index da4b8d6ef5..36b11bee1a 100644 --- a/coderd/workspaceapps/db.go +++ b/coderd/workspaceapps/db.go @@ -372,18 +372,16 @@ func (p *DBTokenProvider) authorizeRequest(ctx context.Context, roles *rbac.Subj return false, warnings, nil } - // Check if the user is a member of the same organization as the workspace + // Check if the user is a member of the same organization as the workspace. workspaceOrgID := dbReq.Workspace.OrganizationID - expandedRoles, err := roles.Roles.Expand() + isMember, err := roles.HasOrganizationMembership(workspaceOrgID) if err != nil { - return false, warnings, xerrors.Errorf("expand roles: %w", err) + return false, warnings, xerrors.Errorf("check organization membership: %w", err) } - for _, role := range expandedRoles { - if _, ok := role.ByOrgID[workspaceOrgID.String()]; ok { - return true, []string{}, nil - } + if isMember { + return true, []string{}, nil } - // User is not a member of the workspace's organization + // User is not a member of the workspace's organization. return false, warnings, nil case database.AppSharingLevelPublic: // We don't really care about scopes and stuff if it's public anyways.