ソースを参照

Merge branch 'nafees/rbac-improvements' into nico/rbac-crud-operations

Mohammed Nafees 3 年 前
コミット
e5a5cfeed6

+ 9 - 1
api/server/handlers/project/create.go

@@ -147,7 +147,7 @@ func createDefaultProjectRoles(projectID, userID uint, repo repository.Repositor
 			return err
 		}
 
-		_, err = repo.ProjectRole().CreateProjectRole(&models.ProjectRole{
+		role, err := repo.ProjectRole().CreateProjectRole(&models.ProjectRole{
 			UniqueID:  fmt.Sprintf("%d-%s", projectID, kind),
 			ProjectID: projectID,
 			PolicyUID: policy.UniqueID,
@@ -157,6 +157,14 @@ func createDefaultProjectRoles(projectID, userID uint, repo repository.Repositor
 		if err != nil {
 			return err
 		}
+
+		if kind == types.RoleAdmin {
+			err := repo.ProjectRole().UpdateUsersInProjectRole(projectID, role.UniqueID, []uint{userID})
+
+			if err != nil {
+				return err
+			}
+		}
 	}
 
 	return nil

+ 44 - 23
api/server/handlers/project/list_collaborators.go

@@ -1,9 +1,10 @@
 package project
 
 import (
-	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"net/http"
 
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/config"
@@ -27,39 +28,59 @@ func NewCollaboratorsListHandler(
 func (p *CollaboratorsListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
-	roles, err := p.Repo().Project().ListProjectRoles(proj.ID)
+	var res types.ListCollaboratorsResponse
+
+	roles, err := p.Repo().ProjectRole().ListProjectRoles(proj.ID)
 
 	if err != nil {
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	roleMap := make(map[uint]*models.Role)
-	idArr := make([]uint, 0)
+	if len(roles) > 0 {
+		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,
+				})
+			}
+		}
+	} else { // legacy operation
+		legacyRoles, err := p.Repo().Project().ListProjectRoles(proj.ID)
 
-	for _, role := range roles {
-		roleCp := role
-		roleMap[role.UserID] = &roleCp
-		idArr = append(idArr, role.UserID)
-	}
+		if err != nil {
+			p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
 
-	users, err := p.Repo().User().ListUsersByIDs(idArr)
+		roleMap := make(map[uint]*models.Role)
+		idArr := make([]uint, 0)
 
-	if err != nil {
-		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
+		for _, role := range legacyRoles {
+			roleCp := role
+			roleMap[role.UserID] = &roleCp
+			idArr = append(idArr, role.UserID)
+		}
+
+		users, err := p.Repo().User().ListUsersByIDs(idArr)
 
-	var res types.ListCollaboratorsResponse = make([]*types.Collaborator, 0)
+		if err != nil {
+			p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
 
-	for _, user := range users {
-		res = append(res, &types.Collaborator{
-			ID:        roleMap[user.ID].ID,
-			Kind:      string(roleMap[user.ID].Kind),
-			UserID:    roleMap[user.ID].UserID,
-			Email:     user.Email,
-			ProjectID: roleMap[user.ID].ProjectID,
-		})
+		for _, user := range users {
+			res = append(res, &types.Collaborator{
+				ID:        roleMap[user.ID].ID,
+				Kind:      string(roleMap[user.ID].Kind),
+				UserID:    roleMap[user.ID].UserID,
+				Email:     user.Email,
+				ProjectID: roleMap[user.ID].ProjectID,
+			})
+		}
 	}
 
 	p.WriteResult(w, r, res)

+ 9 - 7
api/server/handlers/project_role/create.go

@@ -99,15 +99,17 @@ func (c *CreateProjectRoleHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	err = c.Repo().ProjectRole().UpdateUsersInProjectRole(project.ID, role.UniqueID, request.Users)
+	if len(request.Users) > 0 {
+		err = c.Repo().ProjectRole().UpdateUsersInProjectRole(project.ID, role.UniqueID, request.Users)
 
-	if err != nil {
-		// we need to delete the policy and project role we just created
-		c.Repo().Policy().DeletePolicy(policy)
-		c.Repo().ProjectRole().DeleteProjectRole(role)
+		if err != nil {
+			// we need to delete the policy and project role we just created
+			c.Repo().Policy().DeletePolicy(policy)
+			c.Repo().ProjectRole().DeleteProjectRole(role)
 
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
 	}
 
 	w.WriteHeader(http.StatusCreated)

+ 12 - 9
api/types/invite.go

@@ -5,19 +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"`
+	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"`
 }
 
 type GetInviteResponse Invite
 
 type CreateInviteRequest struct {
-	Email string `json:"email,required"`
-	Kind  string `json:"kind,required"`
+	Email          string `json:"email" form:"required"`
+	Kind           string `json:"kind"`
+	ProjectRoleUID string `json:"project_role_uid"`
 }
 
 type CreateInviteResponse struct {
@@ -27,5 +29,6 @@ type CreateInviteResponse struct {
 type ListInvitesResponse []*Invite
 
 type UpdateInviteRoleRequest struct {
-	Kind string `json:"kind,required"`
+	Kind           string `json:"kind"`
+	ProjectRoleUID string `json:"project_role_uid"`
 }

+ 6 - 5
api/types/project.go

@@ -48,11 +48,12 @@ type GetProjectPolicyResponse []*PolicyDocument
 type ListProjectRolesResponse []RoleKind
 
 type Collaborator struct {
-	ID        uint   `json:"id"`
-	Kind      string `json:"kind"`
-	UserID    uint   `json:"user_id"`
-	Email     string `json:"email"`
-	ProjectID uint   `json:"project_id"`
+	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"`
 }
 
 type ListCollaboratorsResponse []*Collaborator

+ 56 - 14
ee/api/server/handlers/invite/accept.go

@@ -15,6 +15,7 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
 	"gorm.io/gorm"
 )
 
@@ -78,23 +79,32 @@ func (c *InviteAcceptHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	kind := invite.Kind
+	if invite.ProjectRoleUID != "" {
+		err := updatePrijectRoleWithUser(c.Repo(), proj.ID, user.ID, invite.ProjectRoleUID)
 
-	if kind == "" {
-		kind = models.RoleDeveloper
-	}
+		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
 
-	role := &models.Role{
-		Role: types.Role{
-			UserID:    user.ID,
-			ProjectID: proj.ID,
-			Kind:      types.RoleKind(kind),
-		},
-	}
+		if kind == "" {
+			kind = models.RoleDeveloper
+		}
 
-	if role, err = c.Repo().Project().CreateProjectRole(proj, role); err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
+		err := updatePrijectRoleWithUser(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")))
+			return
+		} else if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
 	}
 
 	// update the invite
@@ -107,3 +117,35 @@ 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 {
+	role, err := repo.ProjectRole().ReadProjectRole(projectID, projectRoleUID)
+
+	if err != nil {
+		return err
+	}
+
+	userAlreadyInRole := false
+	var userIDs []uint
+
+	for _, u := range role.Users {
+		if u.ID == userID {
+			userAlreadyInRole = true
+			break
+		}
+
+		userIDs = append(userIDs, u.ID)
+	}
+
+	if !userAlreadyInRole {
+		userIDs = append(userIDs, userID)
+
+		err := repo.ProjectRole().UpdateUsersInProjectRole(projectID, role.UniqueID, userIDs)
+
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 18 - 7
ee/api/server/handlers/invite/create.go

@@ -1,8 +1,10 @@
+//go:build ee
 // +build ee
 
 package invite
 
 import (
+	"errors"
 	"fmt"
 	"net/http"
 	"time"
@@ -15,6 +17,7 @@ import (
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/notifier"
 	"github.com/porter-dev/porter/internal/oauth"
+	"gorm.io/gorm"
 )
 
 type InviteCreateHandler struct {
@@ -41,6 +44,15 @@ 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
+		}
+	}
+
 	// create invite model
 	invite, err := CreateInviteWithProject(request, project.ID)
 
@@ -57,8 +69,6 @@ func (c *InviteCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	// app.Logger.Info().Msgf("New invite created: %d", invite.ID)
-
 	if err := c.Config().UserNotifier.SendProjectInviteEmail(
 		&notifier.SendProjectInviteEmailOpts{
 			InviteeEmail:      request.Email,
@@ -83,10 +93,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(),
+		Email:          invite.Email,
+		Kind:           invite.Kind,
+		Expiry:         &expiry,
+		ProjectID:      projectID,
+		Token:          oauth.CreateRandomState(),
+		ProjectRoleUID: invite.ProjectRoleUID,
 	}, nil
 }

+ 2 - 1
ee/api/server/handlers/invite/list.go

@@ -1,3 +1,4 @@
+//go:build ee
 // +build ee
 
 package invite
@@ -36,7 +37,7 @@ func (c *InvitesListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	var res types.ListInvitesResponse = make([]*types.Invite, 0)
+	var res types.ListInvitesResponse
 
 	for _, invite := range invites {
 		res = append(res, invite.ToInviteType())

+ 27 - 3
ee/api/server/handlers/invite/update_role.go

@@ -1,8 +1,11 @@
+//go:build ee
 // +build ee
 
 package invite
 
 import (
+	"errors"
+	"fmt"
 	"net/http"
 
 	"github.com/porter-dev/porter/api/server/handlers"
@@ -11,6 +14,7 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
 )
 
 type InviteUpdateRoleHandler struct {
@@ -28,6 +32,7 @@ func NewInviteUpdateRoleHandler(
 
 func (c *InviteUpdateRoleHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	invite, _ := r.Context().Value(types.InviteScope).(*models.Invite)
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
 	request := &types.UpdateInviteRoleRequest{}
 
@@ -35,10 +40,29 @@ func (c *InviteUpdateRoleHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
 		return
 	}
 
-	invite.Kind = request.Kind
+	changed := false
 
-	if _, err := c.Repo().Invite().UpdateInvite(invite); err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+	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
+		}
+
+		invite.ProjectRoleUID = request.ProjectRoleUID
+
+		changed = true
+	} else if invite.Kind != "" {
+		invite.Kind = request.Kind
+
+		changed = true
+	}
+
+	if changed {
+		if _, err := c.Repo().Invite().UpdateInvite(invite); err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		}
 	}
 
 	w.WriteHeader(http.StatusOK)

+ 11 - 9
internal/models/invite.go

@@ -16,22 +16,24 @@ type Invite struct {
 	Expiry *time.Time
 	Email  string
 
-	// Kind is the role kind that this refers to
+	// Kind is the role kind that this refers to (legacy field)
 	Kind string
 
-	ProjectID uint
-	UserID    uint
+	ProjectID      uint
+	UserID         uint
+	ProjectRoleUID string
 }
 
 // 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,
+		ID:             i.Model.ID,
+		Token:          i.Token,
+		Email:          i.Email,
+		Expired:        i.IsExpired(),
+		Accepted:       i.IsAccepted(),
+		Kind:           i.Kind,
+		ProjectRoleUID: i.ProjectRoleUID,
 	}
 }
 

+ 2 - 2
internal/repository/gorm/project_role.go

@@ -28,7 +28,7 @@ func (repo *ProjectRoleRepository) CreateProjectRole(role *models.ProjectRole) (
 func (repo *ProjectRoleRepository) ReadProjectRole(projectID uint, roleUID string) (*models.ProjectRole, error) {
 	role := &models.ProjectRole{}
 
-	if err := repo.db.Where("project_id = ? AND unique_id = ?", projectID, roleUID).First(role).Error; err != nil {
+	if err := repo.db.Preload("Users").Where("project_id = ? AND unique_id = ?", projectID, roleUID).First(role).Error; err != nil {
 		return nil, err
 	}
 
@@ -48,7 +48,7 @@ func (repo *ProjectRoleRepository) ListProjectRoles(projectID uint) ([]*models.P
 func (repo *ProjectRoleRepository) ListAllRolesForUser(projectID, userID uint) ([]*models.ProjectRole, error) {
 	projectRoles := []*models.ProjectRole{}
 
-	if err := repo.db.Where("project_id = ?", userID).Find(&projectRoles).Error; err != nil {
+	if err := repo.db.Where("project_id = ?", projectID).Find(&projectRoles).Error; err != nil {
 		return nil, err
 	}