Skip to content

Commit

Permalink
[MM-21688] Use dedicated bot account in Azure (#32)
Browse files Browse the repository at this point in the history
* most things updated for bot account. need to resolve some dependencies like IsAuthorizedAdmin

* add connect and disconnect command tests

* finish PR
* implement IsAuthorizedAdmin, and expose through MSCalendar interface
* ensure non-admins cannot connect the bot account

* lint

* PR feedback:
* reword user error strings
* rename vars
  • Loading branch information
mickmister authored Feb 11, 2020
1 parent c96158e commit 864a161
Show file tree
Hide file tree
Showing 25 changed files with 731 additions and 79 deletions.
6 changes: 6 additions & 0 deletions server/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ func (c *Command) Handle() (string, error) {
handler = c.info
case "connect":
handler = c.connect
case "connect_bot":
handler = c.connectBot
case "disconnect":
handler = c.disconnect
case "disconnect_bot":
handler = c.disconnectBot
case "viewcal":
handler = c.viewCalendar
case "createcal":
Expand Down
23 changes: 23 additions & 0 deletions server/command/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,34 @@ import (
"fmt"

"github.com/mattermost/mattermost-plugin-mscalendar/server/config"
"github.com/pkg/errors"
)

func (c *Command) connect(parameters ...string) (string, error) {
ru, err := c.MSCalendar.GetRemoteUser(c.Args.UserId)
if err == nil {
return fmt.Sprintf("Your Mattermost account is already connected to %s account `%s`. To connect to a different account, first run `/%s disconnect`.", config.ApplicationName, ru.Mail, config.CommandTrigger), nil
}

out := fmt.Sprintf("[Click here to link your %s account.](%s/oauth2/connect)",
config.ApplicationName,
c.Config.PluginURL)
return out, nil
}

func (c *Command) connectBot(parameters ...string) (string, error) {
isAdmin, err := c.MSCalendar.IsAuthorizedAdmin(c.Args.UserId)
if err != nil || !isAdmin {
return "", errors.New("non-admin user attempting to connect bot account")
}

ru, err := c.MSCalendar.GetRemoteUser(c.Config.BotUserID)
if err == nil {
return fmt.Sprintf("The bot account is already connected to %s account `%s`. To connect to a different account, first run `/%s disconnect_bot`.", config.ApplicationName, ru.Mail, config.CommandTrigger), nil
}

out := fmt.Sprintf("[Click here to link the bot's %s account.](%s/oauth2/connect_bot)",
config.ApplicationName,
c.Config.PluginURL)
return out, nil
}
118 changes: 118 additions & 0 deletions server/command/connect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package command

import (
"testing"

"github.com/golang/mock/gomock"
"github.com/mattermost/mattermost-plugin-mscalendar/server/config"
"github.com/mattermost/mattermost-plugin-mscalendar/server/mscalendar"
"github.com/mattermost/mattermost-plugin-mscalendar/server/mscalendar/mock_mscalendar"
"github.com/mattermost/mattermost-plugin-mscalendar/server/remote"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)

func TestConnect(t *testing.T) {
tcs := []struct {
name string
command string
setup func(m mscalendar.MSCalendar)
expectedOutput string
expectedError string
}{
{
name: "user already connected",
command: "connect",
setup: func(m mscalendar.MSCalendar) {
mscal := m.(*mock_mscalendar.MockMSCalendar)
mscal.EXPECT().GetRemoteUser("user_id").Return(&remote.User{Mail: "[email protected]"}, nil).Times(1)
},
expectedOutput: "Your Mattermost account is already connected to Microsoft Calendar account `[email protected]`. To connect to a different account, first run `/mscalendar disconnect`.",
expectedError: "",
},
{
name: "user not connected",
command: "connect",
setup: func(m mscalendar.MSCalendar) {
mscal := m.(*mock_mscalendar.MockMSCalendar)
mscal.EXPECT().GetRemoteUser("user_id").Return(nil, errors.New("remote user not found")).Times(1)
},
expectedOutput: "[Click here to link your Microsoft Calendar account.](http://localhost/oauth2/connect)",
expectedError: "",
},
{
name: "non-admin connecting bot account",
command: "connect_bot",
setup: func(m mscalendar.MSCalendar) {
mscal := m.(*mock_mscalendar.MockMSCalendar)
mscal.EXPECT().IsAuthorizedAdmin("user_id").Return(false, nil).Times(1)
},
expectedOutput: "",
expectedError: "Command /mscalendar connect_bot failed: non-admin user attempting to connect bot account",
},
{
name: "bot user already connected",
command: "connect_bot",
setup: func(m mscalendar.MSCalendar) {
mscal := m.(*mock_mscalendar.MockMSCalendar)
mscal.EXPECT().IsAuthorizedAdmin("user_id").Return(true, nil).Times(1)
mscal.EXPECT().GetRemoteUser("bot_user_id").Return(&remote.User{Mail: "[email protected]"}, nil).Times(1)
},
//The bot account is already connected to %s account `%s`. To connect to a different account, first run `/%s disconnect_bot
expectedOutput: "The bot account is already connected to Microsoft Calendar account `[email protected]`. To connect to a different account, first run `/mscalendar disconnect_bot`.",
expectedError: "",
},
{
name: "bot user not connected",
command: "connect_bot",
setup: func(m mscalendar.MSCalendar) {
mscal := m.(*mock_mscalendar.MockMSCalendar)
mscal.EXPECT().IsAuthorizedAdmin("user_id").Return(true, nil).Times(1)
mscal.EXPECT().GetRemoteUser("bot_user_id").Return(nil, errors.New("remote user not found")).Times(1)
},
expectedOutput: "[Click here to link the bot's Microsoft Calendar account.](http://localhost/oauth2/connect_bot)",
expectedError: "",
},
}

for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

conf := &config.Config{
PluginURL: "http://localhost",
BotUserID: "bot_user_id",
}

mscal := mock_mscalendar.NewMockMSCalendar(ctrl)
command := Command{
Context: &plugin.Context{},
Args: &model.CommandArgs{
Command: "/mscalendar " + tc.command,
UserId: "user_id",
},
ChannelID: "channel_id",
Config: conf,
MSCalendar: mscal,
}

if tc.setup != nil {
tc.setup(mscal)
}

out, err := command.Handle()
if tc.expectedOutput != "" {
require.Equal(t, tc.expectedOutput, out)
}

if tc.expectedError != "" {
require.Equal(t, tc.expectedError, err.Error())
} else {
require.Nil(t, err)
}
})
}
}
29 changes: 29 additions & 0 deletions server/command/disconnect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved.
// See License for license information.

package command

import "github.com/pkg/errors"

func (c *Command) disconnect(parameters ...string) (string, error) {
err := c.MSCalendar.DisconnectUser(c.Args.UserId)
if err != nil {
return "", err
}

return "Successfully disconnected your account", nil
}

func (c *Command) disconnectBot(parameters ...string) (string, error) {
isAdmin, err := c.MSCalendar.IsAuthorizedAdmin(c.Args.UserId)
if err != nil || !isAdmin {
return "", errors.New("non-admin user attempting to disconnect bot account")
}

err = c.MSCalendar.DisconnectUser(c.Config.BotUserID)
if err != nil {
return "", err
}

return "Successfully disconnected bot user", nil
}
116 changes: 116 additions & 0 deletions server/command/disconnect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package command

import (
"testing"

"github.com/golang/mock/gomock"
"github.com/mattermost/mattermost-plugin-mscalendar/server/config"
"github.com/mattermost/mattermost-plugin-mscalendar/server/mscalendar"
"github.com/mattermost/mattermost-plugin-mscalendar/server/mscalendar/mock_mscalendar"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/plugin"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)

func TestDisconnect(t *testing.T) {
tcs := []struct {
name string
command string
setup func(mscalendar.MSCalendar)
expectedOutput string
expectedError string
}{
{
name: "disconnect failed",
command: "disconnect",
setup: func(m mscalendar.MSCalendar) {
mscal := m.(*mock_mscalendar.MockMSCalendar)
mscal.EXPECT().DisconnectUser("user_id").Return(errors.New("Some error")).Times(1)
},
expectedOutput: "",
expectedError: "Command /mscalendar disconnect failed: Some error",
},
{
name: "disconnect successful",
command: "disconnect",
setup: func(m mscalendar.MSCalendar) {
mscal := m.(*mock_mscalendar.MockMSCalendar)
mscal.EXPECT().DisconnectUser("user_id").Return(nil).Times(1)
},
expectedOutput: "Successfully disconnected your account",
expectedError: "",
},
{
name: "non-admin disconnecting bot account",
command: "disconnect_bot",
setup: func(m mscalendar.MSCalendar) {
mscal := m.(*mock_mscalendar.MockMSCalendar)
mscal.EXPECT().IsAuthorizedAdmin("user_id").Return(false, nil).Times(1)
},
expectedOutput: "",
expectedError: "Command /mscalendar disconnect_bot failed: non-admin user attempting to disconnect bot account",
},
{
name: "bot disconnect failed",
command: "disconnect_bot",
setup: func(m mscalendar.MSCalendar) {
mscal := m.(*mock_mscalendar.MockMSCalendar)
mscal.EXPECT().IsAuthorizedAdmin("user_id").Return(true, nil).Times(1)
mscal.EXPECT().DisconnectUser("bot_user_id").Return(errors.New("Some error")).Times(1)
},
expectedOutput: "",
expectedError: "Command /mscalendar disconnect_bot failed: Some error",
},
{
name: "bot disconnect successful",
command: "disconnect_bot",
setup: func(m mscalendar.MSCalendar) {
mscal := m.(*mock_mscalendar.MockMSCalendar)
mscal.EXPECT().IsAuthorizedAdmin("user_id").Return(true, nil).Times(1)
mscal.EXPECT().DisconnectUser("bot_user_id").Return(nil).Times(1)
},
expectedOutput: "Successfully disconnected bot user",
expectedError: "",
},
}

for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

conf := &config.Config{
PluginURL: "http://localhost",
BotUserID: "bot_user_id",
}

mscal := mock_mscalendar.NewMockMSCalendar(ctrl)
command := Command{
Context: &plugin.Context{},
Args: &model.CommandArgs{
Command: "/mscalendar " + tc.command,
UserId: "user_id",
},
ChannelID: "channel_id",
Config: conf,
MSCalendar: mscal,
}

if tc.setup != nil {
tc.setup(mscal)
}

out, err := command.Handle()
if tc.expectedOutput != "" {
require.Equal(t, tc.expectedOutput, out)
}

if tc.expectedError != "" {
require.Equal(t, tc.expectedError, err.Error())
} else {
require.Nil(t, err)
}
})
}
}
25 changes: 21 additions & 4 deletions server/mscalendar/availability.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,25 @@ func (m *mscalendar) SyncStatusAll() (string, error) {
return "", err
}

mmIDs := userIndex.GetMattermostUserIDs()
return m.syncStatusUsers(mmIDs)
allIDs := userIndex.GetMattermostUserIDs()
filteredIDs := []string{}
for _, id := range allIDs {
if id != m.Config.BotUserID {
filteredIDs = append(filteredIDs, id)
}
}
return m.syncStatusUsers(filteredIDs)
}

func (m *mscalendar) syncStatusUsers(mattermostUserIDs []string) (string, error) {
err := m.Filter(
withClient,
withUserExpanded(m.actingUser),
)
if err != nil {
return "", err
}

fullUserIndex, err := m.Store.LoadUserIndex()
if err != nil {
if err.Error() == "not found" {
Expand All @@ -66,7 +80,7 @@ func (m *mscalendar) syncStatusUsers(mattermostUserIDs []string) (string, error)
scheduleIDs = append(scheduleIDs, u.Email)
}

schedules, err := m.GetAvailabilities(filteredUsers[0].RemoteID, scheduleIDs)
schedules, err := m.GetAvailabilities(m.actingUser.Remote.ID, scheduleIDs)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -111,7 +125,10 @@ func (m *mscalendar) setUserStatuses(filteredUsers store.UserIndex, schedules []
}

func (m *mscalendar) GetAvailabilities(remoteUserID string, scheduleIDs []string) ([]*remote.ScheduleInformation, error) {
client := m.MakeSuperuserClient()
client, err := m.MakeSuperuserClient()
if err != nil {
return nil, err
}

start := remote.NewDateTime(time.Now().UTC(), "UTC")
end := remote.NewDateTime(time.Now().UTC().Add(availabilityTimeWindowSize*time.Minute), "UTC")
Expand Down
Loading

0 comments on commit 864a161

Please sign in to comment.