From fb61fd790bebccfb65545eb993cd21509acee096 Mon Sep 17 00:00:00 2001 From: Bruno Date: Fri, 6 May 2022 18:08:52 +0000 Subject: [PATCH 1/2] Return DisplayName and Name in the roles endpoint --- coderd/rbac/builtin.go | 15 ++++++++++----- coderd/rbac/role.go | 5 +++-- coderd/roles.go | 7 +++++-- coderd/roles_test.go | 26 +++++++++++++------------- coderd/users.go | 8 ++++---- codersdk/roles.go | 29 +++++++++++++++++++++++++---- 6 files changed, 60 insertions(+), 30 deletions(-) diff --git a/coderd/rbac/builtin.go b/coderd/rbac/builtin.go index 26ec0a1afa1cc..ade2f79f6f8d7 100644 --- a/coderd/rbac/builtin.go +++ b/coderd/rbac/builtin.go @@ -51,7 +51,8 @@ var ( // admin grants all actions to all resources. admin: func(_ string) Role { return Role{ - Name: admin, + Name: admin, + DisplayName: "Admin", Site: permissions(map[Object][]Action{ ResourceWildcard: {WildcardSymbol}, }), @@ -61,7 +62,8 @@ var ( // member grants all actions to all resources owned by the user member: func(_ string) Role { return Role{ - Name: member, + Name: member, + DisplayName: "Member", User: permissions(map[Object][]Action{ ResourceWildcard: {WildcardSymbol}, }), @@ -73,7 +75,8 @@ var ( // TODO: Finish the auditor as we add resources. auditor: func(_ string) Role { return Role{ - Name: "auditor", + Name: "auditor", + DisplayName: "Auditor", Site: permissions(map[Object][]Action{ // Should be able to read all template details, even in orgs they // are not in. @@ -86,7 +89,8 @@ var ( // organization scope. orgAdmin: func(organizationID string) Role { return Role{ - Name: roleName(orgAdmin, organizationID), + Name: roleName(orgAdmin, organizationID), + DisplayName: "Organization Admin", Org: map[string][]Permission{ organizationID: { { @@ -104,7 +108,8 @@ var ( // in an organization. orgMember: func(organizationID string) Role { return Role{ - Name: roleName(orgMember, organizationID), + Name: roleName(orgMember, organizationID), + DisplayName: "Organization Member", Org: map[string][]Permission{ organizationID: {}, }, diff --git a/coderd/rbac/role.go b/coderd/rbac/role.go index a083b2a11ac80..c236bf4a3cf59 100644 --- a/coderd/rbac/role.go +++ b/coderd/rbac/role.go @@ -17,8 +17,9 @@ type Permission struct { // Users of this package should instead **only** use the role names, and // this package will expand the role names into their json payloads. type Role struct { - Name string `json:"name"` - Site []Permission `json:"site"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Site []Permission `json:"site"` // Org is a map of orgid to permissions. We represent orgid as a string. // We scope the organizations in the role so we can easily combine all the // roles. diff --git a/coderd/roles.go b/coderd/roles.go index 64bd7353b4293..ccd7f0c85a39e 100644 --- a/coderd/roles.go +++ b/coderd/roles.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/coder/coder/coderd/httpmw" + "github.com/coder/coder/codersdk" "github.com/coder/coder/coderd/httpapi" "github.com/coder/coder/coderd/rbac" @@ -13,7 +14,8 @@ import ( func (*api) assignableSiteRoles(rw http.ResponseWriter, _ *http.Request) { // TODO: @emyrk in the future, allow granular subsets of roles to be returned based on the // role of the user. - roles := rbac.SiteRoles() + roleNames := rbac.SiteRoles() + roles := codersdk.RolesFromName(roleNames) httpapi.Write(rw, http.StatusOK, roles) } @@ -22,6 +24,7 @@ func (*api) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) { // TODO: @emyrk in the future, allow granular subsets of roles to be returned based on the // role of the user. organization := httpmw.OrganizationParam(r) - roles := rbac.OrganizationRoles(organization.ID) + roleNames := rbac.OrganizationRoles(organization.ID) + roles := codersdk.RolesFromName(roleNames) httpapi.Write(rw, http.StatusOK, roles) } diff --git a/coderd/roles_test.go b/coderd/roles_test.go index 523439883e33d..850785ea72c25 100644 --- a/coderd/roles_test.go +++ b/coderd/roles_test.go @@ -45,13 +45,13 @@ func TestListRoles(t *testing.T) { testCases := []struct { Name string Client *codersdk.Client - APICall func() ([]string, error) - ExpectedRoles []string + APICall func() ([]codersdk.Role, error) + ExpectedRoles []codersdk.Role AuthorizedError string }{ { Name: "MemberListSite", - APICall: func() ([]string, error) { + APICall: func() ([]codersdk.Role, error) { x, err := member.ListSiteRoles(ctx) return x, err }, @@ -59,14 +59,14 @@ func TestListRoles(t *testing.T) { }, { Name: "OrgMemberListOrg", - APICall: func() ([]string, error) { + APICall: func() ([]codersdk.Role, error) { return member.ListOrganizationRoles(ctx, admin.OrganizationID) }, AuthorizedError: unauth, }, { Name: "NonOrgMemberListOrg", - APICall: func() ([]string, error) { + APICall: func() ([]codersdk.Role, error) { return member.ListOrganizationRoles(ctx, otherOrg.ID) }, AuthorizedError: notMember, @@ -74,21 +74,21 @@ func TestListRoles(t *testing.T) { // Org admin { Name: "OrgAdminListSite", - APICall: func() ([]string, error) { + APICall: func() ([]codersdk.Role, error) { return orgAdmin.ListSiteRoles(ctx) }, AuthorizedError: unauth, }, { Name: "OrgAdminListOrg", - APICall: func() ([]string, error) { + APICall: func() ([]codersdk.Role, error) { return orgAdmin.ListOrganizationRoles(ctx, admin.OrganizationID) }, - ExpectedRoles: rbac.OrganizationRoles(admin.OrganizationID), + ExpectedRoles: codersdk.RolesFromName(rbac.OrganizationRoles(admin.OrganizationID)), }, { Name: "OrgAdminListOtherOrg", - APICall: func() ([]string, error) { + APICall: func() ([]codersdk.Role, error) { return orgAdmin.ListOrganizationRoles(ctx, otherOrg.ID) }, AuthorizedError: notMember, @@ -96,17 +96,17 @@ func TestListRoles(t *testing.T) { // Admin { Name: "AdminListSite", - APICall: func() ([]string, error) { + APICall: func() ([]codersdk.Role, error) { return client.ListSiteRoles(ctx) }, - ExpectedRoles: rbac.SiteRoles(), + ExpectedRoles: codersdk.RolesFromName(rbac.SiteRoles()), }, { Name: "AdminListOrg", - APICall: func() ([]string, error) { + APICall: func() ([]codersdk.Role, error) { return client.ListOrganizationRoles(ctx, admin.OrganizationID) }, - ExpectedRoles: rbac.OrganizationRoles(admin.OrganizationID), + ExpectedRoles: codersdk.RolesFromName(rbac.OrganizationRoles(admin.OrganizationID)), }, } diff --git a/coderd/users.go b/coderd/users.go index 108ad0813cb49..7841198558f5b 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -361,10 +361,10 @@ func (api *api) putUserSuspend(rw http.ResponseWriter, r *http.Request) { } func (api *api) putUserPassword(rw http.ResponseWriter, r *http.Request) { - var ( - user = httpmw.UserParam(r) - params codersdk.UpdateUserPasswordRequest - ) + var ( + user = httpmw.UserParam(r) + params codersdk.UpdateUserPasswordRequest + ) if !httpapi.Read(rw, r, ¶ms) { return } diff --git a/codersdk/roles.go b/codersdk/roles.go index d7d6d0fe2b8bc..f7877aba32baf 100644 --- a/codersdk/roles.go +++ b/codersdk/roles.go @@ -6,12 +6,18 @@ import ( "fmt" "net/http" + "github.com/coder/coder/coderd/rbac" "github.com/google/uuid" ) +type Role struct { + Name string `json:"name"` + DisplayName string `json:"display_name"` +} + // ListSiteRoles lists all available site wide roles. // This is not user specific. -func (c *Client) ListSiteRoles(ctx context.Context) ([]string, error) { +func (c *Client) ListSiteRoles(ctx context.Context) ([]Role, error) { res, err := c.request(ctx, http.MethodGet, "/api/v2/users/roles", nil) if err != nil { return nil, err @@ -20,13 +26,13 @@ func (c *Client) ListSiteRoles(ctx context.Context) ([]string, error) { if res.StatusCode != http.StatusOK { return nil, readBodyAsError(res) } - var roles []string + var roles []Role return roles, json.NewDecoder(res.Body).Decode(&roles) } // ListOrganizationRoles lists all available roles for a given organization. // This is not user specific. -func (c *Client) ListOrganizationRoles(ctx context.Context, org uuid.UUID) ([]string, error) { +func (c *Client) ListOrganizationRoles(ctx context.Context, org uuid.UUID) ([]Role, error) { res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/members/roles/", org.String()), nil) if err != nil { return nil, err @@ -35,6 +41,21 @@ func (c *Client) ListOrganizationRoles(ctx context.Context, org uuid.UUID) ([]st if res.StatusCode != http.StatusOK { return nil, readBodyAsError(res) } - var roles []string + var roles []Role return roles, json.NewDecoder(res.Body).Decode(&roles) } + +func RolesFromName(roleNames []string) []Role { + roles := make([]Role, 0, len(roleNames)) + for _, roleName := range roleNames { + role, err := rbac.RoleByName(roleName) + if err != nil { + continue + } + roles = append(roles, Role{ + Name: role.Name, + DisplayName: role.DisplayName, + }) + } + return roles +} From 85d6f69344ae923ace900caddc89de6ef266ea02 Mon Sep 17 00:00:00 2001 From: Bruno Date: Fri, 6 May 2022 19:09:27 +0000 Subject: [PATCH 2/2] Return the Role struct instead of only the name --- coderd/rbac/builtin.go | 14 +++++++------- coderd/rbac/builtin_test.go | 16 ++++++++++++++-- coderd/roles.go | 10 ++++------ coderd/roles_test.go | 6 +++--- codersdk/roles.go | 16 ++++++---------- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/coderd/rbac/builtin.go b/coderd/rbac/builtin.go index ade2f79f6f8d7..6a85cfe3256a2 100644 --- a/coderd/rbac/builtin.go +++ b/coderd/rbac/builtin.go @@ -156,11 +156,11 @@ func IsOrgRole(roleName string) (string, bool) { // // This should be a list in a database, but until then we build // the list from the builtins. -func OrganizationRoles(organizationID uuid.UUID) []string { - var roles []string +func OrganizationRoles(organizationID uuid.UUID) []Role { + var roles []Role for _, roleF := range builtInRoles { - role := roleF(organizationID.String()).Name - _, scope, err := roleSplit(role) + role := roleF(organizationID.String()) + _, scope, err := roleSplit(role.Name) if err != nil { // This should never happen continue @@ -177,8 +177,8 @@ func OrganizationRoles(organizationID uuid.UUID) []string { // // This should be a list in a database, but until then we build // the list from the builtins. -func SiteRoles() []string { - var roles []string +func SiteRoles() []Role { + var roles []Role for _, roleF := range builtInRoles { role := roleF("random") _, scope, err := roleSplit(role.Name) @@ -187,7 +187,7 @@ func SiteRoles() []string { continue } if scope == "" { - roles = append(roles, role.Name) + roles = append(roles, role) } } return roles diff --git a/coderd/rbac/builtin_test.go b/coderd/rbac/builtin_test.go index d8b937f78ac53..72cde1eea40a6 100644 --- a/coderd/rbac/builtin_test.go +++ b/coderd/rbac/builtin_test.go @@ -65,6 +65,12 @@ func TestIsOrgRole(t *testing.T) { func TestListRoles(t *testing.T) { t.Parallel() + siteRoles := rbac.SiteRoles() + siteRoleNames := make([]string, 0, len(siteRoles)) + for _, role := range siteRoles { + siteRoleNames = append(siteRoleNames, role.Name) + } + // If this test is ever failing, just update the list to the roles // expected from the builtin set. require.ElementsMatch(t, []string{ @@ -72,12 +78,18 @@ func TestListRoles(t *testing.T) { "member", "auditor", }, - rbac.SiteRoles()) + siteRoleNames) orgID := uuid.New() + orgRoles := rbac.OrganizationRoles(orgID) + orgRoleNames := make([]string, 0, len(orgRoles)) + for _, role := range orgRoles { + orgRoleNames = append(orgRoleNames, role.Name) + } + require.ElementsMatch(t, []string{ fmt.Sprintf("organization-admin:%s", orgID.String()), fmt.Sprintf("organization-member:%s", orgID.String()), }, - rbac.OrganizationRoles(orgID)) + orgRoleNames) } diff --git a/coderd/roles.go b/coderd/roles.go index ccd7f0c85a39e..470d726252809 100644 --- a/coderd/roles.go +++ b/coderd/roles.go @@ -14,9 +14,8 @@ import ( func (*api) assignableSiteRoles(rw http.ResponseWriter, _ *http.Request) { // TODO: @emyrk in the future, allow granular subsets of roles to be returned based on the // role of the user. - roleNames := rbac.SiteRoles() - roles := codersdk.RolesFromName(roleNames) - httpapi.Write(rw, http.StatusOK, roles) + roles := rbac.SiteRoles() + httpapi.Write(rw, http.StatusOK, codersdk.ConvertRoles(roles)) } // assignableSiteRoles returns all site wide roles that can be assigned. @@ -24,7 +23,6 @@ func (*api) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) { // TODO: @emyrk in the future, allow granular subsets of roles to be returned based on the // role of the user. organization := httpmw.OrganizationParam(r) - roleNames := rbac.OrganizationRoles(organization.ID) - roles := codersdk.RolesFromName(roleNames) - httpapi.Write(rw, http.StatusOK, roles) + roles := rbac.OrganizationRoles(organization.ID) + httpapi.Write(rw, http.StatusOK, codersdk.ConvertRoles(roles)) } diff --git a/coderd/roles_test.go b/coderd/roles_test.go index 850785ea72c25..6254ffa73d1d1 100644 --- a/coderd/roles_test.go +++ b/coderd/roles_test.go @@ -84,7 +84,7 @@ func TestListRoles(t *testing.T) { APICall: func() ([]codersdk.Role, error) { return orgAdmin.ListOrganizationRoles(ctx, admin.OrganizationID) }, - ExpectedRoles: codersdk.RolesFromName(rbac.OrganizationRoles(admin.OrganizationID)), + ExpectedRoles: codersdk.ConvertRoles(rbac.OrganizationRoles(admin.OrganizationID)), }, { Name: "OrgAdminListOtherOrg", @@ -99,14 +99,14 @@ func TestListRoles(t *testing.T) { APICall: func() ([]codersdk.Role, error) { return client.ListSiteRoles(ctx) }, - ExpectedRoles: codersdk.RolesFromName(rbac.SiteRoles()), + ExpectedRoles: codersdk.ConvertRoles(rbac.SiteRoles()), }, { Name: "AdminListOrg", APICall: func() ([]codersdk.Role, error) { return client.ListOrganizationRoles(ctx, admin.OrganizationID) }, - ExpectedRoles: codersdk.RolesFromName(rbac.OrganizationRoles(admin.OrganizationID)), + ExpectedRoles: codersdk.ConvertRoles(rbac.OrganizationRoles(admin.OrganizationID)), }, } diff --git a/codersdk/roles.go b/codersdk/roles.go index f7877aba32baf..cc2234505fa2d 100644 --- a/codersdk/roles.go +++ b/codersdk/roles.go @@ -45,17 +45,13 @@ func (c *Client) ListOrganizationRoles(ctx context.Context, org uuid.UUID) ([]Ro return roles, json.NewDecoder(res.Body).Decode(&roles) } -func RolesFromName(roleNames []string) []Role { - roles := make([]Role, 0, len(roleNames)) - for _, roleName := range roleNames { - role, err := rbac.RoleByName(roleName) - if err != nil { - continue - } - roles = append(roles, Role{ - Name: role.Name, +func ConvertRoles(roles []rbac.Role) []Role { + converted := make([]Role, 0, len(roles)) + for _, role := range roles { + converted = append(converted, Role{ DisplayName: role.DisplayName, + Name: role.Name, }) } - return roles + return converted }