Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(api): same checks for project and workflow permissions #6092

Merged
merged 3 commits into from
Feb 18, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions engine/api/api_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ func isGroupAdmin(ctx context.Context, g *sdk.Group) bool {
if c == nil {
return false
}
member := g.IsMember(c.GetGroupIDs())
admin := g.IsAdmin(*c.AuthentifiedUser)
log.Debug(ctx, "api.isGroupAdmin> member:%t admin:%t", member, admin)
return member && admin && c.Worker == nil
return group.IsConsumerGroupAdmin(g, c)
}

func isGroupMember(ctx context.Context, g *sdk.Group) bool {
Expand Down
4 changes: 2 additions & 2 deletions engine/api/event/publish_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,11 @@ func PublishUpdateProjectPermission(ctx context.Context, p *sdk.Project, gp sdk.
}

// PublishDeleteProjectPermission publishes an event on deleting a group permission on the project
func PublishDeleteProjectPermission(ctx context.Context, p *sdk.Project, gp sdk.GroupPermission) {
func PublishDeleteProjectPermission(ctx context.Context, p *sdk.Project, gp sdk.GroupPermission, u sdk.Identifiable) {
e := sdk.EventProjectPermissionDelete{
Permission: gp,
}
PublishProjectEvent(ctx, e, p.Key, nil)
PublishProjectEvent(ctx, e, p.Key, u)
}

// PublishAddProjectKey publishes an event on adding a project key
Expand Down
2 changes: 1 addition & 1 deletion engine/api/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func (api *API) deleteGroupHandler() service.Handler {

// Send project permission changes
for _, pg := range projPerms {
event.PublishDeleteProjectPermission(ctx, &pg.Project, sdk.GroupPermission{Group: *g})
event.PublishDeleteProjectPermission(ctx, &pg.Project, sdk.GroupPermission{Group: *g}, getAPIConsumer(ctx))
}

return service.WriteJSON(w, nil, http.StatusOK)
Expand Down
8 changes: 8 additions & 0 deletions engine/api/group/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,11 @@ func CheckUserInDefaultGroup(ctx context.Context, db gorpmapper.SqlExecutorWithT

return nil
}

// For given consumer check that it is group admin, member should be loaded
// on group and worker should be loaded on consumer if exists
func IsConsumerGroupAdmin(g *sdk.Group, c *sdk.AuthConsumer) bool {
member := g.IsMember(c.GetGroupIDs())
admin := g.IsAdmin(*c.AuthentifiedUser)
return member && admin && c.Worker == nil
}
69 changes: 69 additions & 0 deletions engine/api/group/group_permission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package group

import (
"context"

"github.com/go-gorp/gorp"

"github.com/ovh/cds/sdk"
)

func CheckGroupPermission(ctx context.Context, db gorp.SqlExecutor, projectGroups sdk.GroupPermissions, gp *sdk.GroupPermission, consumer *sdk.AuthConsumer) error {
if gp.Group.ID == 0 {
g, err := LoadByName(ctx, db, gp.Group.Name)
if err != nil {
return err
}
gp.Group = *g
}
if err := LoadOptions.WithMembers(ctx, db, &gp.Group); err != nil {
return err
}

if IsDefaultGroupID(gp.Group.ID) && gp.Permission > sdk.PermissionRead {
return sdk.NewErrorFrom(sdk.ErrDefaultGroupPermission, "only read permission is allowed to default group")
}

if err := CheckGroupPermissionOrganizationMatch(ctx, db, projectGroups, &gp.Group, gp.Permission); err != nil {
return err
}

if !IsConsumerGroupAdmin(&gp.Group, consumer) && gp.Permission > sdk.PermissionRead {
return sdk.WithStack(sdk.ErrInvalidGroupAdmin)
}

return nil
}

func CheckProjectOrganizationMatch(ctx context.Context, db gorp.SqlExecutor, proj *sdk.Project, grp *sdk.Group, role int) error {
if err := LoadGroupsIntoProject(ctx, db, proj); err != nil {
return err
}
return CheckGroupPermissionOrganizationMatch(ctx, db, proj.ProjectGroups, grp, role)
}

func CheckGroupPermissionOrganizationMatch(ctx context.Context, db gorp.SqlExecutor, projectGroups sdk.GroupPermissions, grp *sdk.Group, role int) error {
if role == sdk.PermissionRead {
return nil
}

projectOrganization, err := projectGroups.ComputeOrganization()
if err != nil {
return sdk.NewError(sdk.ErrForbidden, err)
}
if projectOrganization == "" {
return nil
}

if err := LoadOptions.WithOrganization(ctx, db, grp); err != nil {
return err
}
if grp.Organization != projectOrganization {
if grp.Organization == "" {
return sdk.NewErrorFrom(sdk.ErrForbidden, "given group without organization don't match project organization %q", projectOrganization)
}
return sdk.NewErrorFrom(sdk.ErrForbidden, "given group with organization %q don't match project organization %q", grp.Organization, projectOrganization)
}

return nil
}
97 changes: 55 additions & 42 deletions engine/api/group/workflow_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/lib/pq"

"github.com/ovh/cds/engine/api/database/gorpmapping"
"github.com/ovh/cds/engine/gorpmapper"
"github.com/ovh/cds/sdk"
)

Expand All @@ -27,7 +28,7 @@ func LoadRoleGroupInWorkflow(db gorp.SqlExecutor, workflowID, groupID int64) (in
}

// AddWorkflowGroup Add permission on the given workflow for the given group
func AddWorkflowGroup(ctx context.Context, db gorp.SqlExecutor, w *sdk.Workflow, gp sdk.GroupPermission) error {
func AddWorkflowGroup(ctx context.Context, db gorpmapper.SqlExecutorWithTx, w *sdk.Workflow, gp sdk.GroupPermission) error {
link, err := LoadLinkGroupProjectForGroupIDAndProjectID(ctx, db, gp.Group.ID, w.ProjectID)
if err != nil {
if sdk.ErrorIs(err, sdk.ErrNotFound) {
Expand All @@ -48,24 +49,20 @@ func AddWorkflowGroup(ctx context.Context, db gorp.SqlExecutor, w *sdk.Workflow,
}

// UpdateWorkflowGroup update group permission for the given group on the current workflow
func UpdateWorkflowGroup(ctx context.Context, db gorp.SqlExecutor, w *sdk.Workflow, gp sdk.GroupPermission) error {
func UpdateWorkflowGroup(ctx context.Context, db gorpmapper.SqlExecutorWithTx, w *sdk.Workflow, gp sdk.GroupPermission) error {
link, err := LoadLinkGroupProjectForGroupIDAndProjectID(ctx, db, gp.Group.ID, w.ProjectID)
if err != nil {
if sdk.ErrorIs(err, sdk.ErrNotFound) {
return sdk.WithStack(sdk.ErrGroupNotFoundInProject)
}
return sdk.WrapError(err, "cannot load role for group %d in project %d", gp.Group.ID, w.ProjectID)
}
if link.Role == sdk.PermissionReadWriteExecute && gp.Permission < link.Role {
return sdk.WithStack(sdk.ErrWorkflowPermInsufficient)
}

query := `
UPDATE workflow_perm
SET role = $1
FROM project_group
WHERE project_group.id = workflow_perm.project_group_id
AND workflow_perm.workflow_id = $2
AND project_group.group_id = $3
`
if _, err := db.Exec(query, gp.Permission, w.ID, gp.Group.ID); err != nil {
query := "UPDATE workflow_perm SET role = $3 WHERE project_group_id = $1 AND workflow_id = $2"
if _, err := db.Exec(query, link.ID, w.ID, gp.Permission); err != nil {
return sdk.WithStack(err)
}

Expand All @@ -76,32 +73,40 @@ func UpdateWorkflowGroup(ctx context.Context, db gorp.SqlExecutor, w *sdk.Workfl
}
}

ok, err := checkAtLeastOneGroupWithWriteRoleOnWorkflow(db, w.ID)
if err != nil {
if err := checkAtLeastOneRWXRoleOnWorkflow(db, w.ID); err != nil {
return err
}
if !ok {
return sdk.WithStack(sdk.ErrLastGroupWithWriteRole)
}

return nil
}

// UpsertAllWorkflowGroups upsert all groups in a workflow
func UpsertAllWorkflowGroups(db gorp.SqlExecutor, w *sdk.Workflow, gps []sdk.GroupPermission) error {
func UpsertAllWorkflowGroups(ctx context.Context, db gorpmapper.SqlExecutorWithTx, w *sdk.Workflow, gps []sdk.GroupPermission) error {
query := "DELETE FROM workflow_perm WHERE workflow_id = $1"
if _, err := db.Exec(query, w.ID); err != nil {
return sdk.WrapError(err, "unable to remove group permissions for workflow %d", w.ID)
}

for _, gp := range gps {
link, err := LoadLinkGroupProjectForGroupIDAndProjectID(ctx, db, gp.Group.ID, w.ProjectID)
if err != nil {
if sdk.ErrorIs(err, sdk.ErrNotFound) {
return sdk.WithStack(sdk.ErrGroupNotFoundInProject)
}
return sdk.WrapError(err, "cannot load role for group %d in project %d", gp.Group.ID, w.ProjectID)
}
if link.Role == sdk.PermissionReadWriteExecute && gp.Permission < link.Role {
return sdk.WithStack(sdk.ErrWorkflowPermInsufficient)
}

if err := UpsertWorkflowGroup(db, w.ProjectID, w.ID, gp); err != nil {
return err
}
}

ok, err := checkAtLeastOneGroupWithWriteRoleOnWorkflow(db, w.ID)
if err != nil {
if err := checkAtLeastOneRWXRoleOnWorkflow(db, w.ID); err != nil {
return err
}
if !ok {
return sdk.WithStack(sdk.ErrLastGroupWithWriteRole)
}

return nil
}
Expand Down Expand Up @@ -134,36 +139,25 @@ func DeleteWorkflowGroup(db gorp.SqlExecutor, w *sdk.Workflow, groupID int64, in
return sdk.WithStack(err)
}

ok, err := checkAtLeastOneGroupWithWriteRoleOnWorkflow(db, w.ID)
if err != nil {
if err := checkAtLeastOneRWXRoleOnWorkflow(db, w.ID); err != nil {
return err
}
if !ok {
return sdk.WithStack(sdk.ErrLastGroupWithWriteRole)
}

w.Groups = append(w.Groups[:index], w.Groups[index+1:]...)
return nil
}

// DeleteAllWorkflowGroups removes all group permission for the given workflow.
func DeleteAllWorkflowGroups(db gorp.SqlExecutor, workflowID int64) error {
query := `
DELETE FROM workflow_perm
WHERE workflow_id = $1
`
if _, err := db.Exec(query, workflowID); err != nil {
return sdk.WrapError(err, "unable to remove group permissions for workflow %d", workflowID)
}
return nil
}

func checkAtLeastOneGroupWithWriteRoleOnWorkflow(db gorp.SqlExecutor, wID int64) (bool, error) {
func checkAtLeastOneRWXRoleOnWorkflow(db gorp.SqlExecutor, wID int64) error {
query := `select count(project_group_id) from workflow_perm where workflow_id = $1 and role = $2`
nb, err := db.SelectInt(query, wID, 7)
nb, err := db.SelectInt(query, wID, sdk.PermissionReadWriteExecute)
if err != nil {
return false, sdk.WithStack(err)
return sdk.WithStack(err)
}
if nb == 0 {
return sdk.WithStack(sdk.ErrLastGroupWithWriteRole)
}
return nb > 0, err
return nil
}

type LinkWorkflowGroupPermission struct {
Expand Down Expand Up @@ -251,3 +245,22 @@ func LoadWorkflowGroups(db gorp.SqlExecutor, workflowID int64) ([]sdk.GroupPermi
}
return wgs, nil
}

func CheckWorkflowGroups(ctx context.Context, db gorp.SqlExecutor, proj *sdk.Project, w *sdk.Workflow, consumer *sdk.AuthConsumer) error {
if err := LoadGroupsIntoProject(ctx, db, proj); err != nil {
return err
}
for i := range w.Groups {
if err := CheckGroupPermission(ctx, db, proj.ProjectGroups, &w.Groups[i], consumer); err != nil {
return err
}
}
for _, n := range w.WorkflowData.Array() {
for i := range n.Groups {
if err := CheckGroupPermission(ctx, db, proj.ProjectGroups, &n.Groups[i], consumer); err != nil {
return err
}
}
}
return nil
}
Loading