Skip to content

Commit 38e5730

Browse files
authored
refactor(api): same checks for project and workflow permissions (#6092)
1 parent 8200e78 commit 38e5730

25 files changed

+880
-469
lines changed

engine/api/api_helper.go

+1-4
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@ func isGroupAdmin(ctx context.Context, g *sdk.Group) bool {
2121
if c == nil {
2222
return false
2323
}
24-
member := g.IsMember(c.GetGroupIDs())
25-
admin := g.IsAdmin(*c.AuthentifiedUser)
26-
log.Debug(ctx, "api.isGroupAdmin> member:%t admin:%t", member, admin)
27-
return member && admin && c.Worker == nil
24+
return group.IsConsumerGroupAdmin(g, c)
2825
}
2926

3027
func isGroupMember(ctx context.Context, g *sdk.Group) bool {

engine/api/event/publish_project.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,11 @@ func PublishUpdateProjectPermission(ctx context.Context, p *sdk.Project, gp sdk.
110110
}
111111

112112
// PublishDeleteProjectPermission publishes an event on deleting a group permission on the project
113-
func PublishDeleteProjectPermission(ctx context.Context, p *sdk.Project, gp sdk.GroupPermission) {
113+
func PublishDeleteProjectPermission(ctx context.Context, p *sdk.Project, gp sdk.GroupPermission, u sdk.Identifiable) {
114114
e := sdk.EventProjectPermissionDelete{
115115
Permission: gp,
116116
}
117-
PublishProjectEvent(ctx, e, p.Key, nil)
117+
PublishProjectEvent(ctx, e, p.Key, u)
118118
}
119119

120120
// PublishAddProjectKey publishes an event on adding a project key

engine/api/group.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ func (api *API) deleteGroupHandler() service.Handler {
213213

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

219219
return service.WriteJSON(w, nil, http.StatusOK)

engine/api/group/group.go

+8
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,11 @@ func CheckUserInDefaultGroup(ctx context.Context, db gorpmapper.SqlExecutorWithT
2929

3030
return nil
3131
}
32+
33+
// For given consumer check that it is group admin, member should be loaded
34+
// on group and worker should be loaded on consumer if exists
35+
func IsConsumerGroupAdmin(g *sdk.Group, c *sdk.AuthConsumer) bool {
36+
member := g.IsMember(c.GetGroupIDs())
37+
admin := g.IsAdmin(*c.AuthentifiedUser)
38+
return member && admin && c.Worker == nil
39+
}

engine/api/group/group_permission.go

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package group
2+
3+
import (
4+
"context"
5+
6+
"github.com/go-gorp/gorp"
7+
8+
"github.com/ovh/cds/sdk"
9+
)
10+
11+
func CheckGroupPermission(ctx context.Context, db gorp.SqlExecutor, projectGroups sdk.GroupPermissions, gp *sdk.GroupPermission, consumer *sdk.AuthConsumer) error {
12+
if gp.Group.ID == 0 {
13+
g, err := LoadByName(ctx, db, gp.Group.Name)
14+
if err != nil {
15+
return err
16+
}
17+
gp.Group = *g
18+
}
19+
if err := LoadOptions.WithMembers(ctx, db, &gp.Group); err != nil {
20+
return err
21+
}
22+
23+
if IsDefaultGroupID(gp.Group.ID) && gp.Permission > sdk.PermissionRead {
24+
return sdk.NewErrorFrom(sdk.ErrDefaultGroupPermission, "only read permission is allowed to default group")
25+
}
26+
27+
if err := CheckGroupPermissionOrganizationMatch(ctx, db, projectGroups, &gp.Group, gp.Permission); err != nil {
28+
return err
29+
}
30+
31+
if !IsConsumerGroupAdmin(&gp.Group, consumer) && gp.Permission > sdk.PermissionRead {
32+
return sdk.WithStack(sdk.ErrInvalidGroupAdmin)
33+
}
34+
35+
return nil
36+
}
37+
38+
func CheckProjectOrganizationMatch(ctx context.Context, db gorp.SqlExecutor, proj *sdk.Project, grp *sdk.Group, role int) error {
39+
if err := LoadGroupsIntoProject(ctx, db, proj); err != nil {
40+
return err
41+
}
42+
return CheckGroupPermissionOrganizationMatch(ctx, db, proj.ProjectGroups, grp, role)
43+
}
44+
45+
func CheckGroupPermissionOrganizationMatch(ctx context.Context, db gorp.SqlExecutor, projectGroups sdk.GroupPermissions, grp *sdk.Group, role int) error {
46+
if role == sdk.PermissionRead {
47+
return nil
48+
}
49+
50+
projectOrganization, err := projectGroups.ComputeOrganization()
51+
if err != nil {
52+
return sdk.NewError(sdk.ErrForbidden, err)
53+
}
54+
if projectOrganization == "" {
55+
return nil
56+
}
57+
58+
if err := LoadOptions.WithOrganization(ctx, db, grp); err != nil {
59+
return err
60+
}
61+
if grp.Organization != projectOrganization {
62+
if grp.Organization == "" {
63+
return sdk.NewErrorFrom(sdk.ErrForbidden, "given group without organization don't match project organization %q", projectOrganization)
64+
}
65+
return sdk.NewErrorFrom(sdk.ErrForbidden, "given group with organization %q don't match project organization %q", grp.Organization, projectOrganization)
66+
}
67+
68+
return nil
69+
}

engine/api/group/workflow_group.go

+55-42
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/lib/pq"
1010

1111
"github.com/ovh/cds/engine/api/database/gorpmapping"
12+
"github.com/ovh/cds/engine/gorpmapper"
1213
"github.com/ovh/cds/sdk"
1314
)
1415

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

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

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

60-
query := `
61-
UPDATE workflow_perm
62-
SET role = $1
63-
FROM project_group
64-
WHERE project_group.id = workflow_perm.project_group_id
65-
AND workflow_perm.workflow_id = $2
66-
AND project_group.group_id = $3
67-
`
68-
if _, err := db.Exec(query, gp.Permission, w.ID, gp.Group.ID); err != nil {
64+
query := "UPDATE workflow_perm SET role = $3 WHERE project_group_id = $1 AND workflow_id = $2"
65+
if _, err := db.Exec(query, link.ID, w.ID, gp.Permission); err != nil {
6966
return sdk.WithStack(err)
7067
}
7168

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

79-
ok, err := checkAtLeastOneGroupWithWriteRoleOnWorkflow(db, w.ID)
80-
if err != nil {
76+
if err := checkAtLeastOneRWXRoleOnWorkflow(db, w.ID); err != nil {
8177
return err
8278
}
83-
if !ok {
84-
return sdk.WithStack(sdk.ErrLastGroupWithWriteRole)
85-
}
8679

8780
return nil
8881
}
8982

9083
// UpsertAllWorkflowGroups upsert all groups in a workflow
91-
func UpsertAllWorkflowGroups(db gorp.SqlExecutor, w *sdk.Workflow, gps []sdk.GroupPermission) error {
84+
func UpsertAllWorkflowGroups(ctx context.Context, db gorpmapper.SqlExecutorWithTx, w *sdk.Workflow, gps []sdk.GroupPermission) error {
85+
query := "DELETE FROM workflow_perm WHERE workflow_id = $1"
86+
if _, err := db.Exec(query, w.ID); err != nil {
87+
return sdk.WrapError(err, "unable to remove group permissions for workflow %d", w.ID)
88+
}
89+
9290
for _, gp := range gps {
91+
link, err := LoadLinkGroupProjectForGroupIDAndProjectID(ctx, db, gp.Group.ID, w.ProjectID)
92+
if err != nil {
93+
if sdk.ErrorIs(err, sdk.ErrNotFound) {
94+
return sdk.WithStack(sdk.ErrGroupNotFoundInProject)
95+
}
96+
return sdk.WrapError(err, "cannot load role for group %d in project %d", gp.Group.ID, w.ProjectID)
97+
}
98+
if link.Role == sdk.PermissionReadWriteExecute && gp.Permission < link.Role {
99+
return sdk.WithStack(sdk.ErrWorkflowPermInsufficient)
100+
}
101+
93102
if err := UpsertWorkflowGroup(db, w.ProjectID, w.ID, gp); err != nil {
94103
return err
95104
}
96105
}
97106

98-
ok, err := checkAtLeastOneGroupWithWriteRoleOnWorkflow(db, w.ID)
99-
if err != nil {
107+
if err := checkAtLeastOneRWXRoleOnWorkflow(db, w.ID); err != nil {
100108
return err
101109
}
102-
if !ok {
103-
return sdk.WithStack(sdk.ErrLastGroupWithWriteRole)
104-
}
105110

106111
return nil
107112
}
@@ -134,36 +139,25 @@ func DeleteWorkflowGroup(db gorp.SqlExecutor, w *sdk.Workflow, groupID int64, in
134139
return sdk.WithStack(err)
135140
}
136141

137-
ok, err := checkAtLeastOneGroupWithWriteRoleOnWorkflow(db, w.ID)
138-
if err != nil {
142+
if err := checkAtLeastOneRWXRoleOnWorkflow(db, w.ID); err != nil {
139143
return err
140144
}
141-
if !ok {
142-
return sdk.WithStack(sdk.ErrLastGroupWithWriteRole)
143-
}
145+
144146
w.Groups = append(w.Groups[:index], w.Groups[index+1:]...)
145-
return nil
146-
}
147147

148-
// DeleteAllWorkflowGroups removes all group permission for the given workflow.
149-
func DeleteAllWorkflowGroups(db gorp.SqlExecutor, workflowID int64) error {
150-
query := `
151-
DELETE FROM workflow_perm
152-
WHERE workflow_id = $1
153-
`
154-
if _, err := db.Exec(query, workflowID); err != nil {
155-
return sdk.WrapError(err, "unable to remove group permissions for workflow %d", workflowID)
156-
}
157148
return nil
158149
}
159150

160-
func checkAtLeastOneGroupWithWriteRoleOnWorkflow(db gorp.SqlExecutor, wID int64) (bool, error) {
151+
func checkAtLeastOneRWXRoleOnWorkflow(db gorp.SqlExecutor, wID int64) error {
161152
query := `select count(project_group_id) from workflow_perm where workflow_id = $1 and role = $2`
162-
nb, err := db.SelectInt(query, wID, 7)
153+
nb, err := db.SelectInt(query, wID, sdk.PermissionReadWriteExecute)
163154
if err != nil {
164-
return false, sdk.WithStack(err)
155+
return sdk.WithStack(err)
156+
}
157+
if nb == 0 {
158+
return sdk.WithStack(sdk.ErrLastGroupWithWriteRole)
165159
}
166-
return nb > 0, err
160+
return nil
167161
}
168162

169163
type LinkWorkflowGroupPermission struct {
@@ -251,3 +245,22 @@ func LoadWorkflowGroups(db gorp.SqlExecutor, workflowID int64) ([]sdk.GroupPermi
251245
}
252246
return wgs, nil
253247
}
248+
249+
func CheckWorkflowGroups(ctx context.Context, db gorp.SqlExecutor, proj *sdk.Project, w *sdk.Workflow, consumer *sdk.AuthConsumer) error {
250+
if err := LoadGroupsIntoProject(ctx, db, proj); err != nil {
251+
return err
252+
}
253+
for i := range w.Groups {
254+
if err := CheckGroupPermission(ctx, db, proj.ProjectGroups, &w.Groups[i], consumer); err != nil {
255+
return err
256+
}
257+
}
258+
for _, n := range w.WorkflowData.Array() {
259+
for i := range n.Groups {
260+
if err := CheckGroupPermission(ctx, db, proj.ProjectGroups, &n.Groups[i], consumer); err != nil {
261+
return err
262+
}
263+
}
264+
}
265+
return nil
266+
}

0 commit comments

Comments
 (0)