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

[MI-3629] Added cluster mutexes and solved the problem of race conditions for whitelist #351

Merged
merged 27 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
86dfdc0
[MI-3606] Added the feature of limiting the no. of connected users al…
manojmalik20 Oct 10, 2023
736bf30
[MI-3606] Fixed failing lint errors
manojmalik20 Oct 10, 2023
85bdbd3
[MI-3606] Added unit tests and fixed a bug
manojmalik20 Oct 11, 2023
9dc6c06
[MI-3606] Fixed the failing unit tests in CI
manojmalik20 Oct 11, 2023
fe0d6c1
Merge branch 'main' of github.com:mattermost/mattermost-plugin-msteam…
manojmalik20 Oct 12, 2023
a5c0dc8
[MI-3606] Fixed the bug of using no. of connected users instead of wh…
manojmalik20 Oct 13, 2023
8e52c0c
Merge branch 'main' of github.com:mattermost/mattermost-plugin-msteam…
manojmalik20 Oct 13, 2023
420b7e7
[MI-3629] Added table level locks and solved the problem of race cond…
manojmalik20 Oct 16, 2023
9b918b0
[MI-3629] Added separate handling of locking and unlocking tables for…
manojmalik20 Oct 17, 2023
e9de039
[MI-3606] Review fixes
manojmalik20 Oct 17, 2023
5aa5cc9
[MI-3629] Fixed failing unit tests and added new tests for new store …
manojmalik20 Oct 17, 2023
c8d2f36
Merge branch 'MI-3606' of github.com:mattermost/mattermost-plugin-mst…
manojmalik20 Oct 17, 2023
e554160
[MI-3606] Review fixes: Modified an error message
manojmalik20 Oct 18, 2023
d069766
Merge branch 'MI-3606' of github.com:mattermost/mattermost-plugin-mst…
manojmalik20 Oct 18, 2023
da62777
[MI-3629] Fixed the race condition in unit tests
manojmalik20 Oct 18, 2023
1c96885
[MI-3629] Fixed the race condition in unit tests by using wait group
manojmalik20 Oct 18, 2023
6f2f4df
Merge branch 'main' of github.com:mattermost/mattermost-plugin-msteam…
manojmalik20 Oct 18, 2023
720d4d4
[MI-3629] Fixed the failing unit tests by using channels
manojmalik20 Oct 19, 2023
36f5461
[MI-3629] Fixed the iint errors
manojmalik20 Oct 20, 2023
8049793
[MI-3629] Review fixes
manojmalik20 Oct 23, 2023
ac2912d
[MI-3629] Added additional check for postgres error string and modifi…
manojmalik20 Oct 24, 2023
4d89c3e
[MI-3629] Replaced the use of table level locking with using cluster …
manojmalik20 Oct 25, 2023
8e0400e
[MI-3629] Added the logic of locking and unlocking the whitelist mute…
manojmalik20 Oct 25, 2023
78894e5
Merge branch 'main' of github.com:mattermost/mattermost-plugin-msteam…
manojmalik20 Oct 25, 2023
ada94c6
[MI-3629] Review fixes: Removed unnecessary code and optimised StoreU…
manojmalik20 Oct 25, 2023
b37c57a
Merge branch 'main' of github.com:mattermost/mattermost-plugin-msteam…
manojmalik20 Oct 25, 2023
17fc74b
[MI-3629] Review fixes
manojmalik20 Oct 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ func (a *API) needsConnect(w http.ResponseWriter, r *http.Request) {
if a.p.getConfiguration().EnabledTeams == "" {
response["needsConnect"] = true
} else {
enabledTeams := strings.Fields(a.p.getConfiguration().EnabledTeams)
enabledTeams := strings.Split(a.p.getConfiguration().EnabledTeams, ",")

teams, _ := a.p.API.GetTeamsForUser(userID)
for _, enabledTeam := range enabledTeams {
Expand Down Expand Up @@ -399,7 +399,45 @@ func (a *API) oauthRedirectHandler(w http.ResponseWriter, r *http.Request) {
return
}

if err = a.p.store.StoreUserInWhitelist(mmUserID); err != nil {
tx, err := a.p.store.BeginTx()
if err != nil {
a.p.API.LogError("Unable to begin transaction", "error", err.Error())
http.Error(w, "Something went wrong", http.StatusInternalServerError)
return
}

if err = a.p.store.LockWhitelist(tx); err != nil {
manojmalik20 marked this conversation as resolved.
Show resolved Hide resolved
a.p.API.LogError("Unable to lock whitelist", "error", err.Error())
http.Error(w, "Something went wrong", http.StatusInternalServerError)
return
}

defer func() {
if err = a.p.store.UnlockWhitelist(tx); err != nil {
a.p.API.LogError("Unable to unlock whitelist", "error", err.Error())
}

if err = a.p.store.CommitTx(tx); err != nil {
jespino marked this conversation as resolved.
Show resolved Hide resolved
a.p.API.LogError("Unable to commit transaction", "error", err.Error())
}
}()

whitelistSize, err := a.p.store.GetSizeOfWhitelist(tx)
if err != nil {
a.p.API.LogError("Unable to get whitelist size", "error", err.Error())
http.Error(w, "Something went wrong", http.StatusInternalServerError)
return
}

if whitelistSize >= a.p.getConfiguration().ConnectedUsersAllowed {
if err = a.p.store.SetUserInfo(mmUserID, msteamsUser.ID, nil); err != nil {
a.p.API.LogError("Unable to delete the OAuth token for user", "UserID", 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)
return
}

if err := a.p.store.StoreUserInWhitelist(tx, mmUserID); err != nil {
if !strings.Contains(err.Error(), "Duplicate entry") {
manojmalik20 marked this conversation as resolved.
Show resolved Hide resolved
a.p.API.LogError("Unable to store the user in whitelist", "UserID", mmUserID, "Error", err.Error())
if err = a.p.store.SetUserInfo(mmUserID, msteamsUser.ID, nil); err != nil {
Expand Down
4 changes: 2 additions & 2 deletions server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ func (p *Plugin) executeConnectCommand(args *model.CommandArgs) (*model.CommandR
}

if !presentInWhitelist {
whitelistSize, err := p.store.GetSizeOfWhitelist()
whitelistSize, err := p.store.GetSizeOfWhitelist(nil)
if err != nil {
p.API.LogError("Error in getting the size of whitelist", "Error", err.Error())
return p.cmdError(args.UserId, args.ChannelId, genericErrorMessage)
Expand Down Expand Up @@ -484,7 +484,7 @@ func (p *Plugin) executeConnectBotCommand(args *model.CommandArgs) (*model.Comma
}

if !presentInWhitelist {
whitelistSize, err := p.store.GetSizeOfWhitelist()
whitelistSize, err := p.store.GetSizeOfWhitelist(nil)
if err != nil {
p.API.LogError("Error in getting the size of whitelist", "Error", err.Error())
return p.cmdError(args.UserId, args.ChannelId, genericErrorMessage)
Expand Down
8 changes: 4 additions & 4 deletions server/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,7 @@ func TestExecuteConnectCommand(t *testing.T) {
setupStore: func(s *mockStore.Store) {
s.On("GetTokenForMattermostUser", testutils.GetUserID()).Return(nil, nil).Once()
s.On("IsUserPresentInWhitelist", testutils.GetUserID()).Return(false, nil).Once()
s.On("GetSizeOfWhitelist").Return(0, errors.New("unable to get size of whitelist")).Once()
s.On("GetSizeOfWhitelist", (*sql.Tx)(nil)).Return(0, errors.New("unable to get size of whitelist")).Once()
},
},
{
Expand All @@ -870,7 +870,7 @@ func TestExecuteConnectCommand(t *testing.T) {
setupStore: func(s *mockStore.Store) {
s.On("GetTokenForMattermostUser", testutils.GetUserID()).Return(nil, nil).Once()
s.On("IsUserPresentInWhitelist", testutils.GetUserID()).Return(false, nil).Once()
s.On("GetSizeOfWhitelist").Return(0, nil).Once()
s.On("GetSizeOfWhitelist", (*sql.Tx)(nil)).Return(0, nil).Once()
},
},
{
Expand Down Expand Up @@ -985,7 +985,7 @@ func TestExecuteConnectBotCommand(t *testing.T) {
setupStore: func(s *mockStore.Store) {
s.On("GetTokenForMattermostUser", p.userID).Return(nil, nil).Once()
s.On("IsUserPresentInWhitelist", p.userID).Return(false, nil).Once()
s.On("GetSizeOfWhitelist").Return(0, errors.New("unable to get size of whitelist")).Once()
s.On("GetSizeOfWhitelist", (*sql.Tx)(nil)).Return(0, errors.New("unable to get size of whitelist")).Once()
},
},
{
Expand All @@ -1001,7 +1001,7 @@ func TestExecuteConnectBotCommand(t *testing.T) {
setupStore: func(s *mockStore.Store) {
s.On("GetTokenForMattermostUser", p.userID).Return(nil, nil).Once()
s.On("IsUserPresentInWhitelist", p.userID).Return(false, nil).Once()
s.On("GetSizeOfWhitelist").Return(0, nil).Once()
s.On("GetSizeOfWhitelist", (*sql.Tx)(nil)).Return(0, nil).Once()
},
},
{
Expand Down
59 changes: 50 additions & 9 deletions server/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"reflect"
"strings"

"github.com/pkg/errors"
)
Expand Down Expand Up @@ -37,6 +38,53 @@ type configuration struct {
ConnectedUsersAllowed int `json:"connectedUsersAllowed"`
}

func (c *configuration) ProcessConfiguration() {
c.TenantID = strings.TrimSpace(c.TenantID)
c.ClientID = strings.TrimSpace(c.ClientID)
c.ClientSecret = strings.TrimSpace(c.ClientSecret)
c.EncryptionKey = strings.TrimSpace(c.EncryptionKey)
c.WebhookSecret = strings.TrimSpace(c.WebhookSecret)
c.EnabledTeams = strings.TrimSpace(c.EnabledTeams)
}

func (p *Plugin) validateConfiguration(configuration *configuration) error {
configuration.ProcessConfiguration()
if configuration.TenantID == "" {
return errors.New("tenant ID should not be empty")
}
if configuration.ClientID == "" {
return errors.New("client ID should not be empty")
}
if configuration.ClientSecret == "" {
return errors.New("client secret should not be empty")
}
if configuration.EncryptionKey == "" {
return errors.New("encryption key should not be empty")
}
if configuration.WebhookSecret == "" {
return errors.New("webhook secret should not be empty")
}
if configuration.MaxSizeForCompleteDownload < 0 {
return errors.New("max size for complete single download should not be negative")
}
if configuration.BufferSizeForFileStreaming <= 0 {
return errors.New("buffer size for file streaming should be greater than zero")
}

if p.store != nil {
whitelistSize, err := p.store.GetSizeOfWhitelist(nil)
if err != nil {
return errors.New("failed to get the size of whitelist from the DB")
}

if configuration.ConnectedUsersAllowed < whitelistSize {
return errors.New("failed to save configuration, no. of connected users allowed should be greater than or equal to the current size of the whitelist")
}
}

return nil
}

// Clone shallow copies the configuration. Your implementation may require a deep copy if
// your configuration has reference types.
func (c *configuration) Clone() *configuration {
Expand Down Expand Up @@ -108,15 +156,8 @@ func (p *Plugin) OnConfigurationChange() error {
return errors.Wrap(err, "failed to load plugin configuration")
}

if p.store != nil {
whitelistSize, err := p.store.GetSizeOfWhitelist()
if err != nil {
return errors.New("failed to get the size of whitelist from the DB")
}

if configuration.ConnectedUsersAllowed < whitelistSize {
return errors.New("failed to save configuration, no. of connected users allowed should be greater than the current size of the whitelist")
}
if err := p.validateConfiguration(configuration); err != nil {
return err
}

p.setConfiguration(configuration)
Expand Down
9 changes: 2 additions & 7 deletions server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,13 +344,8 @@ func (p *Plugin) OnActivate() error {
}
}

whitelistSize, err := p.store.GetSizeOfWhitelist()
if err != nil {
return errors.New("failed to get the size of whitelist from the DB")
}

if p.getConfiguration().ConnectedUsersAllowed < whitelistSize {
return errors.New("failed to save configuration, no. of connected users allowed should be greater than the current size of the whitelist")
if err := p.validateConfiguration(p.getConfiguration()); err != nil {
return err
}
ayusht2810 marked this conversation as resolved.
Show resolved Hide resolved

go func() {
Expand Down
52 changes: 40 additions & 12 deletions server/store/mocks/Store.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading