瀏覽代碼

update invite handlers

Mohammed Nafees 3 年之前
父節點
當前提交
cc80cee20f

+ 2 - 2
api/server/handlers/project/delete_role.go

@@ -36,14 +36,14 @@ func (p *RoleDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	role, err := p.Repo().Project().ReadProjectRole(proj.ID, request.UserID)
+	role, err := p.Repo().Project().ReadLegacyProjectRole(proj.ID, request.UserID)
 
 	if err != nil {
 		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
 		return
 	}
 
-	role, err = p.Repo().Project().DeleteProjectRole(proj.ID, request.UserID)
+	role, err = p.Repo().Project().DeleteLegacyProjectRole(proj.ID, request.UserID)
 
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))

+ 13 - 7
api/server/handlers/project/list_collaborators.go

@@ -38,18 +38,24 @@ func (p *CollaboratorsListHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 	}
 
 	if len(roles) > 0 {
+		userCollaboratorMap := make(map[uint]*types.Collaborator)
+
 		for _, role := range roles {
 			for _, user := range role.Users {
-				res = append(res, &types.Collaborator{
-					ProjectRoleUID: role.UniqueID,
-					UserID:         user.ID,
-					ProjectID:      proj.ID,
-					Email:          user.Email,
-				})
+				if _, ok := userCollaboratorMap[user.ID]; ok {
+					userCollaboratorMap[user.ID].RoleUIDs = append(userCollaboratorMap[user.ID].RoleUIDs, role.UniqueID)
+				} else {
+					userCollaboratorMap[user.ID] = &types.Collaborator{
+						RoleUIDs:  []string{role.UniqueID},
+						UserID:    user.ID,
+						Email:     user.Email,
+						ProjectID: proj.ID,
+					}
+				}
 			}
 		}
 	} else { // legacy operation
-		legacyRoles, err := p.Repo().Project().ListProjectRoles(proj.ID)
+		legacyRoles, err := p.Repo().Project().ListLegacyProjectRoles(proj.ID)
 
 		if err != nil {
 			p.HandleAPIError(w, r, apierrors.NewErrInternal(err))

+ 2 - 2
api/server/handlers/project/update_role.go

@@ -36,7 +36,7 @@ func (p *RoleUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	role, err := p.Repo().Project().ReadProjectRole(proj.ID, request.UserID)
+	role, err := p.Repo().Project().ReadLegacyProjectRole(proj.ID, request.UserID)
 
 	if err != nil {
 		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
@@ -45,7 +45,7 @@ func (p *RoleUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	role.Kind = types.RoleKind(request.Kind)
 
-	role, err = p.Repo().Project().UpdateProjectRole(proj.ID, role)
+	role, err = p.Repo().Project().UpdateLegacyProjectRole(proj.ID, role)
 
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))

+ 89 - 8
api/server/handlers/user/create.go

@@ -1,6 +1,7 @@
 package user
 
 import (
+	"encoding/json"
 	"fmt"
 	"net/http"
 
@@ -11,6 +12,7 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/analytics"
+	"github.com/porter-dev/porter/internal/encryption"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/repository"
 	"golang.org/x/crypto/bcrypt"
@@ -134,20 +136,99 @@ func addUserToDefaultProject(config *config.Config, user *models.User) error {
 				return err
 			}
 
-			// create a new Role with the user as the admin
-			_, err = config.Repo.Project().CreateProjectRole(project, &models.Role{
-				Role: types.Role{
-					UserID:    user.ID,
-					ProjectID: project.ID,
-					Kind:      types.RoleAdmin,
-				},
-			})
+			err = createNewRole(project.ID, types.RoleAdmin, config.Repo.ProjectRole(), config.Repo.Policy())
 
 			if err != nil {
 				return err
 			}
+
+			err = createNewRole(project.ID, types.RoleAdmin, config.Repo.ProjectRole(), config.Repo.Policy())
+
+			if err != nil {
+				return err
+			}
+
+			err = createNewRole(project.ID, types.RoleAdmin, config.Repo.ProjectRole(), config.Repo.Policy())
+
+			if err != nil {
+				return err
+			}
+
+			// attach user to admin role
+			err = config.Repo.ProjectRole().UpdateUsersInProjectRole(project.ID, fmt.Sprintf("%d-%s", project.ID, types.RoleAdmin), []uint{user.ID})
+
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+func createNewRole(
+	projectID uint,
+	kind types.RoleKind,
+	projectRoleRepo repository.ProjectRoleRepository,
+	policyRepo repository.PolicyRepository,
+) error {
+	// for legacy roles - admin, developer, viewer (kinds)
+	// default role name such as <project ID>-<kind> for uniqueness
+	// similarly, create policy for each new default role as <project ID>-<kind>-project-role-policy
+
+	uid, err := encryption.GenerateRandomBytes(16)
+
+	if err != nil {
+		return err
+	}
+
+	var policyBytes []byte
+
+	switch kind {
+	case types.RoleAdmin:
+		policyBytes, err = json.Marshal(types.AdminPolicy)
+
+		if err != nil {
+			return err
+		}
+	case types.RoleDeveloper:
+		policyBytes, err = json.Marshal(types.DeveloperPolicy)
+
+		if err != nil {
+			return err
+		}
+	case types.RoleViewer:
+		policyBytes, err = json.Marshal(types.ViewerPolicy)
+
+		if err != nil {
+			return err
 		}
 	}
 
+	newPolicy, err := policyRepo.CreatePolicy(&models.Policy{
+		UniqueID:    uid,
+		ProjectID:   projectID,
+		Name:        fmt.Sprintf("%s-project-role-policy", kind),
+		PolicyBytes: policyBytes,
+	})
+
+	if err != nil {
+		return err
+	}
+
+	_, err = projectRoleRepo.CreateProjectRole(&models.ProjectRole{
+		UniqueID:  fmt.Sprintf("%d-%s", projectID, kind),
+		ProjectID: projectID,
+		PolicyUID: newPolicy.UniqueID,
+		Name:      string(kind),
+	})
+
+	if err != nil {
+		// delete newly created policy first
+		policyRepo.DeletePolicy(newPolicy)
+
+		return err
+	}
+
 	return nil
 }

+ 12 - 12
api/types/invite.go

@@ -5,21 +5,21 @@ const (
 )
 
 type Invite struct {
-	ID             uint   `json:"id"`
-	Token          string `json:"token"`
-	Expired        bool   `json:"expired"`
-	Email          string `json:"email"`
-	Accepted       bool   `json:"accepted"`
-	Kind           string `json:"kind"`
-	ProjectRoleUID string `json:"project_role_uid"`
+	ID       uint     `json:"id"`
+	Token    string   `json:"token"`
+	Expired  bool     `json:"expired"`
+	Email    string   `json:"email"`
+	Accepted bool     `json:"accepted"`
+	Kind     string   `json:"kind"`
+	RoleUIDs []string `json:"roles"`
 }
 
 type GetInviteResponse Invite
 
 type CreateInviteRequest struct {
-	Email          string `json:"email" form:"required"`
-	Kind           string `json:"kind"`
-	ProjectRoleUID string `json:"project_role_uid"`
+	Email    string   `json:"email" form:"required"`
+	Kind     string   `json:"kind"`
+	RoleUIDs []string `json:"roles"`
 }
 
 type CreateInviteResponse struct {
@@ -29,6 +29,6 @@ type CreateInviteResponse struct {
 type ListInvitesResponse []*Invite
 
 type UpdateInviteRoleRequest struct {
-	Kind           string `json:"kind"`
-	ProjectRoleUID string `json:"project_role_uid"`
+	Kind     string   `json:"kind"`
+	RoleUIDs []string `json:"roles"`
 }

+ 6 - 6
api/types/project.go

@@ -48,12 +48,12 @@ type GetProjectPolicyResponse []*PolicyDocument
 type ListProjectRolesResponse []RoleKind
 
 type Collaborator struct {
-	ID             uint   `json:"id,omitempty"`
-	Kind           string `json:"kind,omitempty"`
-	ProjectRoleUID string `json:"project_role_uid"`
-	UserID         uint   `json:"user_id"`
-	Email          string `json:"email"`
-	ProjectID      uint   `json:"project_id"`
+	ID        uint     `json:"id,omitempty"`
+	Kind      string   `json:"kind,omitempty"`
+	RoleUIDs  []string `json:"roles"`
+	UserID    uint     `json:"user_id"`
+	Email     string   `json:"email"`
+	ProjectID uint     `json:"project_id"`
 }
 
 type ListCollaboratorsResponse []*Collaborator

+ 1 - 1
cmd/migrate/migrate_legacy_rbac/migrate.go

@@ -103,7 +103,7 @@ func MigrateFromLegacyRBAC(db *_gorm.DB, logger *lr.Logger) error {
 
 			for _, legacyRole := range project.Roles {
 				// delete legacy role from project
-				if _, err := projectRepo.DeleteProjectRole(project.ID, legacyRole.UserID); err != nil {
+				if _, err := projectRepo.DeleteLegacyProjectRole(project.ID, legacyRole.UserID); err != nil {
 					return fmt.Errorf("error encountered while deleting legacy project role: %w", err)
 				}
 			}

+ 15 - 11
ee/api/server/handlers/invite/accept.go

@@ -79,15 +79,19 @@ func (c *InviteAcceptHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	if invite.ProjectRoleUID != "" {
-		err := updatePrijectRoleWithUser(c.Repo(), proj.ID, user.ID, invite.ProjectRoleUID)
-
-		if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
-			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such role exists")))
-			return
-		} else if err != nil {
-			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-			return
+	inviteType := invite.ToInviteType()
+
+	if len(inviteType.RoleUIDs) > 0 {
+		for _, roleUID := range inviteType.RoleUIDs {
+			err := updateProjectRoleWithUser(c.Repo(), proj.ID, user.ID, roleUID)
+
+			if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
+				c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such role exists")))
+				return
+			} else if err != nil {
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+				return
+			}
 		}
 	} else { // legacy operation
 		kind := invite.Kind
@@ -96,7 +100,7 @@ func (c *InviteAcceptHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 			kind = models.RoleDeveloper
 		}
 
-		err := updatePrijectRoleWithUser(c.Repo(), proj.ID, user.ID, fmt.Sprintf("%d-%s", proj.ID, kind))
+		err := updateProjectRoleWithUser(c.Repo(), proj.ID, user.ID, fmt.Sprintf("%d-%s", proj.ID, kind))
 
 		if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such role exists")))
@@ -118,7 +122,7 @@ func (c *InviteAcceptHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	http.Redirect(w, r, "/dashboard", 302)
 }
 
-func updatePrijectRoleWithUser(repo repository.Repository, projectID, userID uint, projectRoleUID string) error {
+func updateProjectRoleWithUser(repo repository.Repository, projectID, userID uint, projectRoleUID string) error {
 	role, err := repo.ProjectRole().ReadProjectRole(projectID, projectRoleUID)
 
 	if err != nil {

+ 21 - 12
ee/api/server/handlers/invite/create.go

@@ -7,6 +7,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"strings"
 	"time"
 
 	"github.com/porter-dev/porter/api/server/handlers"
@@ -44,12 +45,20 @@ func (c *InviteCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	if request.ProjectRoleUID != "" {
-		_, err := c.Repo().ProjectRole().ReadProjectRole(project.ID, request.ProjectRoleUID)
-
-		if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
-			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such role exists")))
-			return
+	if len(request.RoleUIDs) == 0 {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+			fmt.Errorf("roles cannot be empty"), http.StatusPreconditionFailed,
+		))
+		return
+	} else {
+		// check for valid project roles
+		for _, role := range request.RoleUIDs {
+			_, err := c.Repo().ProjectRole().ReadProjectRole(project.ID, role)
+
+			if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
+				c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("role not found in project: %s", role)))
+				return
+			}
 		}
 	}
 
@@ -93,11 +102,11 @@ func CreateInviteWithProject(invite *types.CreateInviteRequest, projectID uint)
 	expiry := time.Now().Add(24 * time.Hour)
 
 	return &models.Invite{
-		Email:          invite.Email,
-		Kind:           invite.Kind,
-		Expiry:         &expiry,
-		ProjectID:      projectID,
-		Token:          oauth.CreateRandomState(),
-		ProjectRoleUID: invite.ProjectRoleUID,
+		Email:     invite.Email,
+		Kind:      invite.Kind,
+		Expiry:    &expiry,
+		ProjectID: projectID,
+		Token:     oauth.CreateRandomState(),
+		RoleUIDs:  []byte(strings.Join(invite.RoleUIDs, ",")),
 	}, nil
 }

+ 11 - 9
ee/api/server/handlers/invite/update_role.go

@@ -7,6 +7,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"strings"
 
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
@@ -42,18 +43,21 @@ func (c *InviteUpdateRoleHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 
 	changed := false
 
-	if request.ProjectRoleUID != "" {
-		_, err := c.Repo().ProjectRole().ReadProjectRole(project.ID, request.ProjectRoleUID)
+	if len(request.RoleUIDs) > 0 {
+		// check for valid project roles
+		for _, roleUID := range request.RoleUIDs {
+			_, err := c.Repo().ProjectRole().ReadProjectRole(project.ID, roleUID)
 
-		if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
-			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such role exists")))
-			return
+			if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
+				c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("role not found in project: %s", roleUID)))
+				return
+			}
 		}
 
-		invite.ProjectRoleUID = request.ProjectRoleUID
+		invite.RoleUIDs = []byte(strings.Join(request.RoleUIDs, ","))
 
 		changed = true
-	} else if invite.Kind != "" {
+	} else if invite.Kind != "" { // legacy invite
 		invite.Kind = request.Kind
 
 		changed = true
@@ -64,6 +68,4 @@ func (c *InviteUpdateRoleHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		}
 	}
-
-	w.WriteHeader(http.StatusOK)
 }

+ 12 - 12
internal/models/invite.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"strings"
 	"time"
 
 	"gorm.io/gorm"
@@ -19,27 +20,26 @@ type Invite struct {
 	// Kind is the role kind that this refers to (legacy field)
 	Kind string
 
-	ProjectID      uint
-	UserID         uint
-	ProjectRoleUID string
+	ProjectID uint
+	UserID    uint
+	RoleUIDs  []byte // stored as a byte-array of comma-separated strings of role UIDs
 }
 
 // ToInviteType generates an external Invite to be shared over REST
 func (i *Invite) ToInviteType() *types.Invite {
 	return &types.Invite{
-		ID:             i.Model.ID,
-		Token:          i.Token,
-		Email:          i.Email,
-		Expired:        i.IsExpired(),
-		Accepted:       i.IsAccepted(),
-		Kind:           i.Kind,
-		ProjectRoleUID: i.ProjectRoleUID,
+		ID:       i.Model.ID,
+		Token:    i.Token,
+		Email:    i.Email,
+		Expired:  i.IsExpired(),
+		Accepted: i.IsAccepted(),
+		Kind:     i.Kind,
+		RoleUIDs: strings.Split(string(i.RoleUIDs), ""),
 	}
 }
 
 func (i *Invite) IsExpired() bool {
-	timeLeft := i.Expiry.Sub(time.Now())
-	return timeLeft < 0
+	return time.Until(*i.Expiry) < 0
 }
 
 func (i *Invite) IsAccepted() bool {

+ 6 - 6
internal/repository/gorm/project.go

@@ -26,8 +26,8 @@ func (repo *ProjectRepository) CreateProject(project *models.Project) (*models.P
 	return project, nil
 }
 
-// CreateProjectRole appends a role to the existing array of roles
-func (repo *ProjectRepository) CreateProjectRole(project *models.Project, role *models.Role) (*models.Role, error) {
+// CreateLegacyProjectRole appends a role to the existing array of roles
+func (repo *ProjectRepository) CreateLegacyProjectRole(project *models.Project, role *models.Role) (*models.Role, error) {
 	assoc := repo.db.Model(&project).Association("Roles")
 
 	if assoc.Error != nil {
@@ -50,7 +50,7 @@ func (repo *ProjectRepository) UpdateProject(project *models.Project) (*models.P
 	return project, nil
 }
 
-func (repo *ProjectRepository) UpdateProjectRole(projID uint, role *models.Role) (*models.Role, error) {
+func (repo *ProjectRepository) UpdateLegacyProjectRole(projID uint, role *models.Role) (*models.Role, error) {
 	foundRole := &models.Role{}
 
 	if err := repo.db.Where("project_id = ? AND user_id = ?", projID, role.UserID).First(&foundRole).Error; err != nil {
@@ -78,7 +78,7 @@ func (repo *ProjectRepository) ReadProject(id uint) (*models.Project, error) {
 }
 
 // ReadProject gets a projects specified by a unique id
-func (repo *ProjectRepository) ReadProjectRole(projID, userID uint) (*models.Role, error) {
+func (repo *ProjectRepository) ReadLegacyProjectRole(projID, userID uint) (*models.Role, error) {
 	// find the role
 	role := &models.Role{}
 
@@ -103,7 +103,7 @@ func (repo *ProjectRepository) ListProjectsByUserID(userID uint) ([]*models.Proj
 }
 
 // ReadProject gets a projects specified by a unique id
-func (repo *ProjectRepository) ListProjectRoles(projID uint) ([]models.Role, error) {
+func (repo *ProjectRepository) ListLegacyProjectRoles(projID uint) ([]models.Role, error) {
 	project := &models.Project{}
 
 	if err := repo.db.Preload("Roles").Where("id = ?", projID).First(&project).Error; err != nil {
@@ -121,7 +121,7 @@ func (repo *ProjectRepository) DeleteProject(project *models.Project) (*models.P
 	return project, nil
 }
 
-func (repo *ProjectRepository) DeleteProjectRole(projID, userID uint) (*models.Role, error) {
+func (repo *ProjectRepository) DeleteLegacyProjectRole(projID, userID uint) (*models.Role, error) {
 	// find the role
 	role := &models.Role{}
 

+ 5 - 5
internal/repository/project.go

@@ -10,13 +10,13 @@ type WriteProject func(project *models.Project) (*models.Project, error)
 // ProjectRepository represents the set of queries on the Project model
 type ProjectRepository interface {
 	CreateProject(project *models.Project) (*models.Project, error)
-	CreateProjectRole(project *models.Project, role *models.Role) (*models.Role, error)
+	CreateLegacyProjectRole(project *models.Project, role *models.Role) (*models.Role, error)
 	UpdateProject(project *models.Project) (*models.Project, error)
-	UpdateProjectRole(projID uint, role *models.Role) (*models.Role, error)
+	UpdateLegacyProjectRole(projID uint, role *models.Role) (*models.Role, error)
 	ReadProject(id uint) (*models.Project, error)
-	ReadProjectRole(projID, userID uint) (*models.Role, error)
-	ListProjectRoles(projID uint) ([]models.Role, error)
+	ReadLegacyProjectRole(projID, userID uint) (*models.Role, error)
+	ListLegacyProjectRoles(projID uint) ([]models.Role, error)
 	ListProjectsByUserID(userID uint) ([]*models.Project, error)
 	DeleteProject(project *models.Project) (*models.Project, error)
-	DeleteProjectRole(projID, userID uint) (*models.Role, error)
+	DeleteLegacyProjectRole(projID, userID uint) (*models.Role, error)
 }

+ 5 - 5
internal/repository/test/project.go

@@ -43,7 +43,7 @@ func (repo *ProjectRepository) CreateProject(project *models.Project) (*models.P
 }
 
 // CreateProjectRole appends a role to the existing array of roles
-func (repo *ProjectRepository) CreateProjectRole(project *models.Project, role *models.Role) (*models.Role, error) {
+func (repo *ProjectRepository) CreateLegacyProjectRole(project *models.Project, role *models.Role) (*models.Role, error) {
 	if !repo.canQuery || strings.Contains(repo.failingMethods, CreateProjectRoleMethod) {
 		return nil, errors.New("Cannot write database")
 	}
@@ -65,7 +65,7 @@ func (repo *ProjectRepository) UpdateProject(project *models.Project) (*models.P
 }
 
 // CreateProjectRole appends a role to the existing array of roles
-func (repo *ProjectRepository) UpdateProjectRole(projID uint, role *models.Role) (*models.Role, error) {
+func (repo *ProjectRepository) UpdateLegacyProjectRole(projID uint, role *models.Role) (*models.Role, error) {
 	if !repo.canQuery {
 		return nil, errors.New("Cannot read from database")
 	}
@@ -100,7 +100,7 @@ func (repo *ProjectRepository) UpdateProjectRole(projID uint, role *models.Role)
 }
 
 // ReadProject gets a projects specified by a unique id
-func (repo *ProjectRepository) ReadProjectRole(userID, projID uint) (*models.Role, error) {
+func (repo *ProjectRepository) ReadLegacyProjectRole(userID, projID uint) (*models.Role, error) {
 	if !repo.canQuery {
 		return nil, errors.New("Cannot read from database")
 	}
@@ -156,7 +156,7 @@ func (repo *ProjectRepository) ListProjectsByUserID(userID uint) ([]*models.Proj
 }
 
 // ListProjectRoles returns a list of roles for the project
-func (repo *ProjectRepository) ListProjectRoles(projID uint) ([]models.Role, error) {
+func (repo *ProjectRepository) ListLegacyProjectRoles(projID uint) ([]models.Role, error) {
 	if !repo.canQuery {
 		return nil, errors.New("Cannot read from database")
 	}
@@ -187,7 +187,7 @@ func (repo *ProjectRepository) DeleteProject(project *models.Project) (*models.P
 	return project, nil
 }
 
-func (repo *ProjectRepository) DeleteProjectRole(projID, userID uint) (*models.Role, error) {
+func (repo *ProjectRepository) DeleteLegacyProjectRole(projID, userID uint) (*models.Role, error) {
 	if !repo.canQuery {
 		return nil, errors.New("Cannot write database")
 	}

+ 1 - 1
internal/usage/usage.go

@@ -47,7 +47,7 @@ func GetUsage(opts *GetUsageOpts) (
 	}
 
 	// query for the linked user counts
-	roles, err := opts.Repo.Project().ListProjectRoles(opts.Project.ID)
+	roles, err := opts.Repo.Project().ListLegacyProjectRoles(opts.Project.ID)
 
 	if err != nil {
 		return nil, nil, nil, err