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

DEVPROD-3065: Update directive to handle SetLastRevision permission #7689

Merged
merged 9 commits into from
Apr 5, 2024
58 changes: 56 additions & 2 deletions graphql/directive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ func setupPermissions(t *testing.T) {

func TestRequireDistroAccess(t *testing.T) {
setupPermissions(t)
require.NoError(t, db.Clear(user.Collection),
"unable to clear user collection")
require.NoError(t, db.ClearCollections(model.ProjectRefCollection, user.Collection),
"unable to clear user or project ref collection")
dbUser := &user.DBUser{
Id: apiUser,
Settings: user.UserSettings{
Expand Down Expand Up @@ -401,6 +401,13 @@ func TestRequireProjectAdmin(t *testing.T) {
ctx = gimlet.AttachUser(ctx, usr)
require.NotNil(t, ctx)

projectRef := model.ProjectRef{
Id: "project_id",
Identifier: "project_identifier",
}
err = projectRef.Insert()
require.NoError(t, err)

// superuser should always be successful, no matter the resolver
err = usr.AddRole("superuser")
require.NoError(t, err)
Expand Down Expand Up @@ -479,6 +486,53 @@ func TestRequireProjectAdmin(t *testing.T) {
require.NoError(t, err)
require.Nil(t, res)
require.Equal(t, 4, callCount)

// SetLastRevision - successful
operationContext = &graphql.OperationContext{
OperationName: SetLastRevisionMutation,
}
ctx = graphql.WithOperationContext(ctx, operationContext)
obj = map[string]interface{}{
"opts": map[string]interface{}{
"projectIdentifier": "project_identifier",
},
}
res, err = config.Directives.RequireProjectAdmin(ctx, obj, next)
require.NoError(t, err)
require.Nil(t, res)
require.Equal(t, 5, callCount)

// SetLastRevision - project not found
operationContext = &graphql.OperationContext{
OperationName: SetLastRevisionMutation,
}
ctx = graphql.WithOperationContext(ctx, operationContext)
obj = map[string]interface{}{
"opts": map[string]interface{}{
"projectIdentifier": "project_whatever",
},
}
res, err = config.Directives.RequireProjectAdmin(ctx, obj, next)
require.EqualError(t, err, "input: project 'project_whatever' not found")
require.Nil(t, res)
require.Equal(t, 5, callCount)

// SetLastRevision - permission denied
operationContext = &graphql.OperationContext{
OperationName: SetLastRevisionMutation,
}
ctx = graphql.WithOperationContext(ctx, operationContext)
obj = map[string]interface{}{
"opts": map[string]interface{}{
"projectIdentifier": "project_identifier",
},
}
require.NoError(t, usr.RemoveRole("admin_project"))
res, err = config.Directives.RequireProjectAdmin(ctx, obj, next)
require.EqualError(t, err, "input: user testuser does not have permission to access the SetLastRevision resolver")
require.Nil(t, res)
require.Equal(t, 5, callCount)

}

func setupUser(t *testing.T) (*user.DBUser, error) {
Expand Down
57 changes: 38 additions & 19 deletions graphql/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ import (

"github.com/99designs/gqlgen/graphql"
"github.com/evergreen-ci/evergreen"
"github.com/evergreen-ci/evergreen/model"
"github.com/evergreen-ci/evergreen/rest/data"
restModel "github.com/evergreen-ci/evergreen/rest/model"
"github.com/evergreen-ci/gimlet"
"github.com/evergreen-ci/utility"
)

const (
CreateProjectMutation = "CreateProject"
CopyProjectMutation = "CopyProject"
DeleteProjectMutation = "DeleteProject"
CreateProjectMutation = "CreateProject"
CopyProjectMutation = "CopyProject"
DeleteProjectMutation = "DeleteProject"
SetLastRevisionMutation = "SetLastRevision"
)

type Resolver struct {
Expand Down Expand Up @@ -87,11 +89,6 @@ func New(apiURL string) Config {
return next(ctx)
}

// Check for admin permissions for each of the resolvers.
args, isStringMap := obj.(map[string]interface{})
if !isStringMap {
return nil, ResourceNotFound.Send(ctx, "Project not specified")
}
operationContext := graphql.GetOperationContext(ctx).OperationName

if operationContext == CreateProjectMutation {
Expand All @@ -104,17 +101,26 @@ func New(apiURL string) Config {
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(request) can we move the block for CreateProjectMutation above the declaration of the args variable, since it doesn't use the args variable?


getPermissionOpts := func(projectId string) gimlet.PermissionOpts {
return gimlet.PermissionOpts{
Resource: projectId,
ResourceType: evergreen.ProjectResourceType,
Permission: evergreen.PermissionProjectSettings,
RequiredLevel: evergreen.ProjectSettingsEdit.Value,
}
}

args, isStringMap := obj.(map[string]interface{})
if !isStringMap {
return nil, ResourceNotFound.Send(ctx, "Project not specified")
}

if operationContext == CopyProjectMutation {
projectIdToCopy, ok := args["project"].(map[string]interface{})["projectIdToCopy"].(string)
if !ok {
return nil, InternalServerError.Send(ctx, "finding projectIdToCopy for copy project operation")
}
opts := gimlet.PermissionOpts{
Resource: projectIdToCopy,
ResourceType: evergreen.ProjectResourceType,
Permission: evergreen.PermissionProjectSettings,
RequiredLevel: evergreen.ProjectSettingsEdit.Value,
}
opts := getPermissionOpts(projectIdToCopy)
if user.HasPermission(opts) {
return next(ctx)
}
Expand All @@ -125,12 +131,25 @@ func New(apiURL string) Config {
if !ok {
return nil, InternalServerError.Send(ctx, "finding projectId for delete project operation")
}
opts := gimlet.PermissionOpts{
Resource: projectId,
ResourceType: evergreen.ProjectResourceType,
Permission: evergreen.PermissionProjectSettings,
RequiredLevel: evergreen.ProjectSettingsEdit.Value,
opts := getPermissionOpts(projectId)
if user.HasPermission(opts) {
return next(ctx)
}
}

if operationContext == SetLastRevisionMutation {
projectIdentifier, ok := args["opts"].(map[string]interface{})["projectIdentifier"].(string)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just retrieve the project ID directly from the request or is that not possible?

Copy link
Contributor Author

@SupaJoon SupaJoon Apr 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you show me which data structure you're referring to? "opts" refers to the mutation parameter where the directive was applied.

if !ok {
return nil, InternalServerError.Send(ctx, "finding projectIdentifier for set last revision operation")
}
project, err := model.FindBranchProjectRef(projectIdentifier)
if err != nil {
return nil, InternalServerError.Send(ctx, fmt.Sprintf("finding project '%s': %s", projectIdentifier, err.Error()))
}
if project == nil {
return nil, ResourceNotFound.Send(ctx, fmt.Sprintf("project '%s' not found", projectIdentifier))
}
opts := getPermissionOpts(project.Id)
if user.HasPermission(opts) {
return next(ctx)
}
Expand Down
Loading