Skip to content

Commit 54a9c46

Browse files
[GH-326] Added subscription flag for filtering out confidential issues, and disallow guests from creating subscriptions (#376)
* [MI-3045]: Added checks for confidential issues at the time of making subscriptions (#37) * [MI-3045]:Added checks for confidential issues * [MI-3045]:Added unit test cases * [MI-3045]:Fixed self review comments * [MI-3045]:Fixed unit test cases * [MI-3045]:Fixed lint errors * [MI-3045]:Fixed review comments * [MI-3045]:Fixed context deadline error * [MI-3045]:Fixed review comments * [MI-3045]:Fixed statements * [MI-3060]:Fixed review comments of PR #376 on gitlab plugin (#38) * [MI-3060]:Fixed review comments of PR #376 on gitlab plugin * [MI-3060]:Fixed test cases * [MI-3060]:Updated the msg * [MI-3060]:Fixed the test cases * [MM-326]:fixed test cases * [MM-326]:Fixed CI error * [MM-326]:Fixed review comments * [MM-326]:Fixed review comments --------- Co-authored-by: ayusht2810 <ayush.thakur@brightscout.com>
1 parent 789a404 commit 54a9c46

File tree

9 files changed

+108
-23
lines changed

9 files changed

+108
-23
lines changed

server/command.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const commandHelp = `* |/gitlab connect| - Connect your Mattermost account to yo
2424
* |/gitlab subscriptions add owner[/repo] [features]| - Subscribe the current channel to receive notifications about opened merge requests and issues for a group or repository
2525
* |features| is a comma-delimited list of one or more the following:
2626
* issues - includes new and closed issues
27+
* confidential_issues - includes new and closed confidential issues
2728
* jobs - includes jobs status updates
2829
* merges - includes new and closed merge requests
2930
* pushes - includes pushes
@@ -578,6 +579,7 @@ func (p *Plugin) subscriptionsListCommand(channelID string) string {
578579
if err != nil {
579580
return err.Error()
580581
}
582+
581583
if len(subs) == 0 {
582584
txt = "Currently there are no subscriptions in this channel"
583585
} else {
@@ -614,6 +616,12 @@ func (p *Plugin) subscriptionsAddCommand(ctx context.Context, info *gitlab.UserI
614616
return err.Error()
615617
}
616618

619+
if hasPermission := p.permissionToProject(ctx, info.UserID, namespace, project); !hasPermission {
620+
msg := "You don't have the permissions to create subscriptions for this project."
621+
p.client.Log.Warn(msg)
622+
return msg
623+
}
624+
617625
updatedSubscriptions, subscribeErr := p.Subscribe(info, namespace, project, channelID, features)
618626
if subscribeErr != nil {
619627
p.client.Log.Warn(
@@ -807,7 +815,7 @@ func getAutocompleteData(config *configuration) *model.AutocompleteData {
807815

808816
subscriptionsAdd := model.NewAutocompleteData(commandAdd, "owner[/repo] [features]", "Subscribe the current channel to receive notifications from a project")
809817
subscriptionsAdd.AddTextArgument("Project path: includes user or group name with optional slash project name", "owner[/repo]", "")
810-
subscriptionsAdd.AddTextArgument("comma-delimited list of features to subscribe to: issues, merges, pushes, issue_comments, merge_request_comments, pipeline, tag, pull_reviews, label:<labelName>", "[features] (optional)", `/[^,-\s]+(,[^,-\s]+)*/`)
818+
subscriptionsAdd.AddTextArgument("comma-delimited list of features to subscribe to: issues, confidential_issues, merges, pushes, issue_comments, merge_request_comments, pipeline, tag, pull_reviews, label:<labelName>", "[features] (optional)", `/[^,-\s]+(,[^,-\s]+)*/`)
811819
subscriptions.AddCommand(subscriptionsAdd)
812820

813821
subscriptionsDelete := model.NewAutocompleteData(commandDelete, "owner[/repo]", "Unsubscribe the current channel from a repository")

server/command_test.go

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
56
"testing"
67

78
"github.com/golang/mock/gomock"
@@ -11,6 +12,7 @@ import (
1112
"github.com/pkg/errors"
1213
"github.com/stretchr/testify/assert"
1314
"github.com/stretchr/testify/mock"
15+
"github.com/stretchr/testify/require"
1416
gitLabAPI "github.com/xanzy/go-gitlab"
1517

1618
"github.com/mattermost/mattermost-plugin-gitlab/server/gitlab"
@@ -23,7 +25,9 @@ type subscribeCommandTest struct {
2325
want string
2426
webhookInfo []*gitlab.WebhookInfo
2527
mattermostURL string
28+
noAccess bool
2629
projectHookErr error
30+
getProjectErr error
2731
mockGitlab bool
2832
}
2933

@@ -37,6 +41,25 @@ var subscribeCommandTests = []subscribeCommandTest{
3741
parameters: []string{"list"},
3842
want: "Currently there are no subscriptions in this channel",
3943
},
44+
{
45+
testName: "No Repository permissions",
46+
parameters: []string{"add", "group/project"},
47+
mockGitlab: true,
48+
want: "You don't have the permissions to create subscriptions for this project.",
49+
webhookInfo: []*gitlab.WebhookInfo{{URL: "example.com/somewebhookURL"}},
50+
noAccess: true,
51+
mattermostURL: "example.com",
52+
getProjectErr: errors.New("unable to get project"),
53+
},
54+
{
55+
testName: "Guest permissions only",
56+
parameters: []string{"add", "group/project"},
57+
mockGitlab: true,
58+
want: "You don't have the permissions to create subscriptions for this project.",
59+
webhookInfo: []*gitlab.WebhookInfo{{URL: "example.com/somewebhookURL"}},
60+
noAccess: true,
61+
mattermostURL: "example.com",
62+
},
4063
{
4164
testName: "Hook Found",
4265
parameters: []string{"add", "group/project"},
@@ -78,9 +101,11 @@ func TestSubscribeCommand(t *testing.T) {
78101
mockCtrl := gomock.NewController(t)
79102

80103
channelID := "12345"
81-
userInfo := &gitlab.UserInfo{}
104+
userInfo := &gitlab.UserInfo{
105+
UserID: "user_id",
106+
}
82107

83-
p := getTestPlugin(t, mockCtrl, test.webhookInfo, test.mattermostURL, test.projectHookErr, test.mockGitlab)
108+
p := getTestPlugin(t, mockCtrl, test.webhookInfo, test.mattermostURL, test.projectHookErr, test.getProjectErr, test.mockGitlab, test.noAccess)
84109
subscribeMessage := p.subscribeCommand(context.Background(), test.parameters, channelID, &configuration{}, userInfo)
85110

86111
assert.Equal(t, test.want, subscribeMessage, "Subscribe command message should be the same.")
@@ -224,15 +249,34 @@ func TestListWebhookCommand(t *testing.T) {
224249
}
225250
}
226251

227-
func getTestPlugin(t *testing.T, mockCtrl *gomock.Controller, hooks []*gitlab.WebhookInfo, mattermostURL string, projectHookErr error, mockGitlab bool) *Plugin {
252+
func getTestPlugin(t *testing.T, mockCtrl *gomock.Controller, hooks []*gitlab.WebhookInfo, mattermostURL string, projectHookErr error, getProjectErr error, mockGitlab, noAccess bool) *Plugin {
228253
p := new(Plugin)
229254

255+
accessLevel := gitLabAPI.OwnerPermission
256+
if noAccess {
257+
accessLevel = gitLabAPI.GuestPermissions
258+
}
259+
230260
mockedClient := mocks.NewMockGitlab(mockCtrl)
231261
if mockGitlab {
232262
mockedClient.EXPECT().ResolveNamespaceAndProject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return("group", "project", nil)
233-
mockedClient.EXPECT().GetProjectHooks(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(hooks, projectHookErr)
234-
if projectHookErr == nil {
235-
mockedClient.EXPECT().GetGroupHooks(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(hooks, projectHookErr)
263+
if getProjectErr != nil {
264+
mockedClient.EXPECT().GetProject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, getProjectErr)
265+
} else {
266+
mockedClient.EXPECT().GetProject(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&gitLabAPI.Project{
267+
Permissions: &gitLabAPI.Permissions{
268+
ProjectAccess: &gitLabAPI.ProjectAccess{
269+
AccessLevel: accessLevel,
270+
},
271+
},
272+
}, nil)
273+
}
274+
275+
if !noAccess {
276+
mockedClient.EXPECT().GetProjectHooks(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(hooks, projectHookErr)
277+
if projectHookErr == nil {
278+
mockedClient.EXPECT().GetGroupHooks(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(hooks, projectHookErr)
279+
}
236280
}
237281
}
238282

@@ -247,15 +291,32 @@ func getTestPlugin(t *testing.T, mockCtrl *gomock.Controller, hooks []*gitlab.We
247291
EncryptionKey: testEncryptionKey,
248292
}
249293

294+
info := gitlab.UserInfo{
295+
UserID: "user_id",
296+
GitlabUsername: "gitlab_username",
297+
}
298+
299+
jsonInfo, err := json.Marshal(info)
300+
require.NoError(t, err)
301+
250302
var subVal []byte
251303

252304
api := &plugintest.API{}
253305
api.On("GetConfig", mock.Anything).Return(conf)
254-
api.On("KVGet", "_usertoken").Return([]byte(encryptedToken), nil)
255-
api.On("KVGet", mock.Anything).Return(subVal, nil)
306+
api.On("KVGet", "user_id_usertoken").Return([]byte(encryptedToken), nil)
307+
api.On("KVGet", "user_id_userinfo").Return(subVal, nil).Once()
308+
api.On("KVGet", "user_id_gitlabtoken").Return(jsonInfo, nil).Once()
309+
api.On("KVGet", "subscriptions").Return(subVal, nil)
310+
256311
api.On("KVSet", mock.Anything, mock.Anything).Return(nil)
257312
api.On("KVSetWithOptions", mock.AnythingOfType("string"), mock.Anything, mock.AnythingOfType("model.PluginKVSetOptions")).Return(true, nil)
258313
api.On("PublishWebSocketEvent", mock.Anything, mock.Anything, mock.Anything).Return(nil)
314+
api.On("LogWarn",
315+
mock.AnythingOfTypeArgument("string"),
316+
mock.AnythingOfTypeArgument("string"),
317+
mock.AnythingOfTypeArgument("string"),
318+
mock.AnythingOfTypeArgument("string"),
319+
mock.AnythingOfTypeArgument("string"))
259320

260321
p.SetAPI(api)
261322
p.client = pluginapi.NewClient(api, p.Driver)

server/subscription/subscription.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ var allFeatures = map[string]bool{
1616
"pipeline": true,
1717
"tag": true,
1818
"pull_reviews": true,
19-
// "label:": true,//particular case for label:XXX
19+
"confidential_issues": true,
20+
// "label:": true,//particular case for label:XXX
2021
}
2122

2223
type Subscription struct {
@@ -63,6 +64,10 @@ func (s *Subscription) Issues() bool {
6364
return strings.Contains(s.Features, "issues")
6465
}
6566

67+
func (s *Subscription) ConfidentialIssues() bool {
68+
return strings.Contains(s.Features, "confidential_issues")
69+
}
70+
6671
func (s *Subscription) Pushes() bool {
6772
return strings.Contains(s.Features, "pushes")
6873
}

server/webhook.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
7373
return
7474
}
7575

76-
event, err := gitlabLib.ParseWebhook(gitlabLib.WebhookEventType(r), body)
76+
eventType := gitlabLib.WebhookEventType(r)
77+
event, err := gitlabLib.ParseWebhook(eventType, body)
7778
if err != nil {
7879
p.client.Log.Debug("Can't parse webhook", "err", err.Error(), "header", r.Header.Get("X-Gitlab-Event"), "event", string(body))
7980
http.Error(w, "Unable to handle request", http.StatusBadRequest)
@@ -99,7 +100,7 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
99100
repoPrivate = event.Project.Visibility == gitlabLib.PrivateVisibility
100101
pathWithNamespace = event.Project.PathWithNamespace
101102
fromUser = event.User.Username
102-
handlers, errHandler = p.WebhookHandler.HandleIssue(ctx, event)
103+
handlers, errHandler = p.WebhookHandler.HandleIssue(ctx, event, eventType)
103104
case *gitlabLib.IssueCommentEvent:
104105
repoPrivate = event.Project.Visibility == gitlabLib.PrivateVisibility
105106
pathWithNamespace = event.Project.PathWithNamespace
@@ -224,10 +225,16 @@ func (p *Plugin) permissionToProject(ctx context.Context, userID, namespace, pro
224225
})
225226
if result == nil || err != nil {
226227
if err != nil {
227-
p.client.Log.Warn("can't get project in webhook", "err", err.Error(), "project", namespace+"/"+project)
228+
p.client.Log.Warn("Can't get project in webhook", "err", err.Error(), "project", namespace+"/"+project)
228229
}
229230
return false
230231
}
232+
233+
// Check for guest level permissions
234+
if result.Permissions.ProjectAccess == nil || result.Permissions.ProjectAccess.AccessLevel == gitlabLib.GuestPermissions {
235+
return false
236+
}
237+
231238
return true
232239
}
233240

server/webhook/issue.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import (
77
"github.com/xanzy/go-gitlab"
88
)
99

10-
func (w *webhook) HandleIssue(ctx context.Context, event *gitlab.IssueEvent) ([]*HandleWebhook, error) {
10+
func (w *webhook) HandleIssue(ctx context.Context, event *gitlab.IssueEvent, eventType gitlab.EventType) ([]*HandleWebhook, error) {
1111
handlers, err := w.handleDMIssue(event)
1212
if err != nil {
1313
return nil, err
1414
}
15-
handlers2, err := w.handleChannelIssue(ctx, event)
15+
handlers2, err := w.handleChannelIssue(ctx, event, eventType)
1616
if err != nil {
1717
return nil, err
1818
}
@@ -65,7 +65,7 @@ func (w *webhook) handleDMIssue(event *gitlab.IssueEvent) ([]*HandleWebhook, err
6565
return []*HandleWebhook{}, nil
6666
}
6767

68-
func (w *webhook) handleChannelIssue(ctx context.Context, event *gitlab.IssueEvent) ([]*HandleWebhook, error) {
68+
func (w *webhook) handleChannelIssue(ctx context.Context, event *gitlab.IssueEvent, eventType gitlab.EventType) ([]*HandleWebhook, error) {
6969
issue := event.ObjectAttributes
7070
senderGitlabUsername := event.User.Username
7171
repo := event.Project
@@ -98,6 +98,10 @@ func (w *webhook) handleChannelIssue(ctx context.Context, event *gitlab.IssueEve
9898
continue
9999
}
100100

101+
if eventType == gitlab.EventConfidentialIssue && !sub.ConfidentialIssues() {
102+
continue
103+
}
104+
101105
if sub.Label() != "" && !containsLabel(event.Labels, sub.Label()) {
102106
continue
103107
}

server/webhook/issue_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func TestIssueWebhook(t *testing.T) {
111111
if err := json.Unmarshal([]byte(test.fixture), issueEvent); err != nil {
112112
assert.Fail(t, "can't unmarshal fixture")
113113
}
114-
res, err := w.HandleIssue(context.Background(), issueEvent)
114+
res, err := w.HandleIssue(context.Background(), issueEvent, gitlab.EventTypeIssue)
115115
assert.Empty(t, err)
116116
assert.Equal(t, len(test.res), len(res))
117117
for index := range res {

server/webhook/webhook.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type HandleWebhook struct {
3737
}
3838

3939
type Webhook interface {
40-
HandleIssue(ctx context.Context, event *gitlab.IssueEvent) ([]*HandleWebhook, error)
40+
HandleIssue(ctx context.Context, event *gitlab.IssueEvent, eventType gitlab.EventType) ([]*HandleWebhook, error)
4141
HandleMergeRequest(ctx context.Context, event *gitlab.MergeEvent) ([]*HandleWebhook, error)
4242
HandleIssueComment(ctx context.Context, event *gitlab.IssueCommentEvent) ([]*HandleWebhook, error)
4343
HandleMergeRequestComment(ctx context.Context, event *gitlab.MergeCommentEvent) ([]*HandleWebhook, error)

server/webhook_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818

1919
type fakeWebhookHandler struct{}
2020

21-
func (fakeWebhookHandler) HandleIssue(_ context.Context, _ *gitlabLib.IssueEvent) ([]*webhook.HandleWebhook, error) {
21+
func (fakeWebhookHandler) HandleIssue(_ context.Context, _ *gitlabLib.IssueEvent, _ gitlabLib.EventType) ([]*webhook.HandleWebhook, error) {
2222
return []*webhook.HandleWebhook{{
2323
Message: "hello",
2424
From: "test",

webapp/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)