Skip to content

Commit

Permalink
MM-57853: Whitelist Restricted (simplified) (#631)
Browse files Browse the repository at this point in the history
  • Loading branch information
calebroseland authored May 21, 2024
1 parent fd8798e commit 0c1d5d0
Show file tree
Hide file tree
Showing 13 changed files with 589 additions and 131 deletions.
42 changes: 14 additions & 28 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,43 +156,29 @@
"default": 20
},
{
"key": "newUserConnections",
"display_name": "New User Connections",
"type": "dropdown",
"help_text": "When enabled, any user who has not connected will be allowed to connect their account. When set to Rollout, the bot will incrementally send connection invite direct messages to users as they login or become active; as invited users connect, spaces in the invite pool will open up and more invites will be sent out. \nRollout (open) — all users may connect and receive invites. \nRollout (open-restricted) — all users may connect, but only whitelisted users may receive invites.",
"default": "enabled",
"options": [
{
"display_name": "Enabled: allow all users to connect, and do not send connection invites",
"value": "enabled"
},
{
"display_name": "Rollout (open): allow all users to connect, and send connection invites to any user as they login or become active",
"value": "rolloutOpen"
},
{
"display_name": "Rollout (open-restricted): allow all users to connect, and send connection invites to whitelisted users as they login or become active",
"value": "rolloutOpenRestricted"
}
]
},
{
"key": "connectedUsersAllowed",
"display_name": "Max Connected Users",
"type": "number",
"help_text": "The maximum number of users that may connect their MS Teams account. Once connected, users may reconnect at any time if they become disconnected.",
"help_text": "The maximum number of users that may connect their MS Teams account. Once connected, users may reconnect at any time. (Set to 0 to disable new connections.)",
"default": 1000
},
{
"key": "connectedUsersInvitePoolSize",
"display_name": "Rollout Max. Pending Invitations",
"key": "connectedUsersMaxPendingInvites",
"display_name": "Max Pending Invitations",
"type": "number",
"help_text": "Set the maximum number of connection invites that may be pending at a given time during Rollout. As invited users connect, spaces in the invite pool will open up and more invites will be sent out as configured above.",
"default": 10
"help_text": "Invite pool size: the maximum number of connection invites that may be pending at a given time. When specified, connection invite direct messages will be sent to users as they become active, up to the maximum specified here. As invited users connect, spaces in the invite pool will open up and more invites will be sent out. Once invited, users may connect at any time. (Set to 0 or leave empty to disable connection invites.)",
"default": 0
},
{
"key": "connectedUsersRestricted",
"display_name": "New User Connections: Restricted",
"type": "bool",
"help_text": "When true, only whitelisted users may connect their account.",
"default": false
},
{
"key": "inviteWhitelistUpload",
"display_name": "Connection Invites: Whitelist",
"key": "connectedUsersWhitelist",
"display_name": "New User Connections: Whitelist",
"type": "custom",
"help_text": "",
"default": ""
Expand Down
14 changes: 11 additions & 3 deletions server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"strconv"
"strings"
"text/template"
"time"

"github.com/gorilla/mux"
"github.com/mattermost/mattermost-plugin-msteams/server/metrics"
Expand Down Expand Up @@ -417,7 +418,9 @@ func (a *API) notifyConnect(w http.ResponseWriter, r *http.Request) {
return
}

if inviteWasSent, err := a.p.MaybeSendInviteMessage(userID); err != nil {
now := time.Now()

if inviteWasSent, err := a.p.MaybeSendInviteMessage(userID, now); err != nil {
a.p.API.LogWarn("Error in connection invite flow", "user_id", userID, "error", err.Error())
} else if inviteWasSent {
a.p.API.LogInfo("Successfully sent connection invite", "user_id", userID)
Expand Down Expand Up @@ -525,7 +528,7 @@ func (a *API) oauthRedirectHandler(w http.ResponseWriter, r *http.Request) {
}

if !hasRightToConnect {
canOpenlyConnect, openConnectErr := a.p.UserCanOpenlyConnect(mmUserID)
canOpenlyConnect, nAvailable, openConnectErr := a.p.UserCanOpenlyConnect(mmUserID)
if openConnectErr != nil {
a.p.API.LogWarn("Unable to check if user can openly connect", "error", openConnectErr.Error())
http.Error(w, "Something went wrong", http.StatusInternalServerError)
Expand All @@ -536,7 +539,12 @@ func (a *API) oauthRedirectHandler(w http.ResponseWriter, r *http.Request) {
if err = a.p.store.SetUserInfo(mmUserID, msteamsUser.ID, nil); err != nil {
a.p.API.LogWarn("Unable to delete the OAuth token for user", "user_id", mmUserID, "error", err.Error())
}
http.Error(w, "You cannot connect your account because the maximum limit of users allowed to connect has been reached. Please contact your system administrator.", http.StatusBadRequest)

if nAvailable > 0 {
http.Error(w, "You cannot connect your account at this time because an invitation is required. Please contact your system administrator to request an invitation.", http.StatusBadRequest)
} else {
http.Error(w, "You cannot connect your account because the maximum limit of users allowed to connect has been reached. Please contact your system administrator.", http.StatusBadRequest)
}
return
}
}
Expand Down
48 changes: 47 additions & 1 deletion server/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,53 @@ func TestWhitelistDownload(t *testing.T) {
}

func TestNotifyConnect(t *testing.T) {
t.Skip()
th := setupTestHelper(t)
apiURL := th.pluginURL(t, "/notify-connect")
team := th.SetupTeam(t)

sendRequest := func(t *testing.T, user *model.User) *http.Response {
t.Helper()
client1 := th.SetupClient(t, user.Id)

u := apiURL

request, err := http.NewRequest(http.MethodGet, u, nil)
require.NoError(t, err)

request.Header.Set(model.HeaderAuth, client1.AuthType+" "+client1.AuthToken)

response, err := http.DefaultClient.Do(request)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, response.Body.Close())
})

return response
}

t.Run("not authorized", func(t *testing.T) {
th.Reset(t)

request, err := http.NewRequest(http.MethodGet, apiURL, nil)
require.NoError(t, err)

response, err := http.DefaultClient.Do(request)
require.NoError(t, err)

assert.Equal(t, http.StatusUnauthorized, response.StatusCode)
t.Cleanup(func() {
require.NoError(t, response.Body.Close())
})
})

t.Run("notify connect", func(t *testing.T) {
th.Reset(t)

user1 := th.SetupUser(t, team)

response := sendRequest(t, user1)
assert.Equal(t, http.StatusOK, response.StatusCode)
})
}

func TestChoosePrimaryPlatform(t *testing.T) {
Expand Down
17 changes: 17 additions & 0 deletions server/bot_messages_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"testing"
)

func TestSendEphemeralConnectMessage(t *testing.T) {
t.Skip()
}

func TestSendConnectMessage(t *testing.T) {
t.Skip()
}

func TestSendConnectBotMessage(t *testing.T) {
t.Skip()
}
26 changes: 5 additions & 21 deletions server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,13 +492,17 @@ func (p *Plugin) executeConnectCommand(args *model.CommandArgs) (*model.CommandR
}

if !hasRightToConnect {
canOpenlyConnect, openConnectErr := p.UserCanOpenlyConnect(args.UserId)
canOpenlyConnect, nAvailable, openConnectErr := p.UserCanOpenlyConnect(args.UserId)
if openConnectErr != nil {
p.API.LogWarn("Error in checking if the user can openly connect", "user_id", args.UserId, "error", openConnectErr.Error())
return p.cmdError(args, genericErrorMessage)
}

if !canOpenlyConnect {
if nAvailable > 0 {
// spots available, but need to be on whitelist in order to connect
return p.cmdError(args, "You cannot connect your account at this time because an invitation is required. Please contact your system administrator to request an invitation.")
}
return p.cmdError(args, "You cannot connect your account because the maximum limit of users allowed to connect has been reached. Please contact your system administrator.")
}
}
Expand All @@ -516,26 +520,6 @@ func (p *Plugin) executeConnectBotCommand(args *model.CommandArgs) (*model.Comma
return p.cmdError(args, "The bot account is already connected to MS Teams. Please disconnect the bot account first before connecting again.")
}

genericErrorMessage := "Error in trying to connect the bot account, please try again."

hasRightToConnect, err := p.UserHasRightToConnect(p.botUserID)
if err != nil {
p.API.LogWarn("Error in checking if the bot user has the right to connect", "bot_user_id", p.botUserID, "error", err.Error())
return p.cmdError(args, genericErrorMessage)
}

if !hasRightToConnect {
canOpenlyConnect, openConnectErr := p.UserCanOpenlyConnect(p.botUserID)
if openConnectErr != nil {
p.API.LogWarn("Error in checking if the bot user can openly connect", "bot_user_id", p.botUserID, "error", openConnectErr.Error())
return p.cmdError(args, genericErrorMessage)
}

if !canOpenlyConnect {
return p.cmdError(args, "You cannot connect the bot account because the maximum limit of users allowed to connect has been reached.")
}
}

p.SendConnectBotMessage(args.ChannelId, args.UserId)
return &model.CommandResponse{}, nil
}
Expand Down
24 changes: 0 additions & 24 deletions server/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -895,30 +895,6 @@ func TestExecuteConnectBotCommand(t *testing.T) {
assertEphemeralResponse(th, t, args, "The bot account is already connected to MS Teams. Please disconnect the bot account first before connecting again.")
})

t.Run("not in whitelist, already at limit", func(t *testing.T) {
th.Reset(t)

args := &model.CommandArgs{
UserId: sysadmin1.Id,
ChannelId: model.NewId(),
}

configuration := th.p.configuration.Clone()
configuration.ConnectedUsersAllowed = 0
th.p.setConfiguration(configuration)
defer func() {
configuration := th.p.configuration.Clone()
configuration.ConnectedUsersAllowed = 1000
th.p.setConfiguration(configuration)
}()

commandResponse, appErr := th.p.executeConnectBotCommand(args)
require.Nil(t, appErr)

assertNoCommandResponse(t, commandResponse)
assertEphemeralResponse(th, t, args, "You cannot connect the bot account because the maximum limit of users allowed to connect has been reached.")
})

t.Run("successfully started connection", func(t *testing.T) {
th.Reset(t)

Expand Down
4 changes: 2 additions & 2 deletions server/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ type configuration struct {
MaxSizeForCompleteDownload int `json:"maxSizeForCompleteDownload"`
BufferSizeForFileStreaming int `json:"bufferSizeForFileStreaming"`
ConnectedUsersAllowed int `json:"connectedUsersAllowed"`
NewUserConnections string `json:"newUserConnections"`
ConnectedUsersInvitePoolSize int `json:"connectedUsersInvitePoolSize"`
ConnectedUsersRestricted bool `json:"connectedUsersRestricted"`
ConnectedUsersMaxPendingInvites int `json:"connectedUsersMaxPendingInvites"`
SyntheticUserAuthService string `json:"syntheticUserAuthService"`
SyntheticUserAuthData string `json:"syntheticUserAuthData"`
AutomaticallyPromoteSyntheticUsers bool `json:"automaticallyPromoteSyntheticUsers"`
Expand Down
Loading

0 comments on commit 0c1d5d0

Please sign in to comment.