From c370d300410bc47a0e23a93f2fbab457fb70ff8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Tue, 26 Mar 2019 19:30:49 +0100 Subject: [PATCH] MM-14748: Allow to invalidate email invites (#10509) --- api4/team.go | 15 ++++++++++++++ app/team.go | 7 +++++++ i18n/en.json | 8 ++++++++ store/sqlstore/tokens_store.go | 9 +++++++++ store/store.go | 1 + store/storetest/mocks/PostStore.go | 31 +++++++++++++++-------------- store/storetest/mocks/SqlStore.go | 15 ++++++++++++++ store/storetest/mocks/TokenStore.go | 16 +++++++++++++++ 8 files changed, 87 insertions(+), 15 deletions(-) diff --git a/api4/team.go b/api4/team.go index f33baa75aa..409f430065 100644 --- a/api4/team.go +++ b/api4/team.go @@ -55,6 +55,7 @@ func (api *API) InitTeam() { api.BaseRoutes.TeamMember.Handle("/schemeRoles", api.ApiSessionRequired(updateTeamMemberSchemeRoles)).Methods("PUT") api.BaseRoutes.Team.Handle("/import", api.ApiSessionRequired(importTeam)).Methods("POST") api.BaseRoutes.Team.Handle("/invite/email", api.ApiSessionRequired(inviteUsersToTeam)).Methods("POST") + api.BaseRoutes.Teams.Handle("/invites/email", api.ApiSessionRequired(invalidateAllEmailInvites)).Methods("DELETE") api.BaseRoutes.Teams.Handle("/invite/{invite_id:[A-Za-z0-9]+}", api.ApiHandler(getInviteInfo)).Methods("GET") } @@ -789,6 +790,20 @@ func getInviteInfo(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(result))) } +func invalidateAllEmailInvites(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if err := c.App.InvalidateAllEmailInvites(); err != nil { + c.Err = err + return + } + + ReturnStatusOK(w) +} + func getTeamIcon(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireTeamId() if c.Err != nil { diff --git a/app/team.go b/app/team.go index a78599c35d..67293dd0fe 100644 --- a/app/team.go +++ b/app/team.go @@ -1256,3 +1256,10 @@ func (a *App) RemoveTeamIcon(teamId string) *model.AppError { return nil } + +func (a *App) InvalidateAllEmailInvites() *model.AppError { + if result := <-a.Srv.Store.Token().RemoveAllTokensByType(TOKEN_TYPE_TEAM_INVITATION); result.Err != nil { + return model.NewAppError("InvalidateAllEmailInvites", "api.team.invalidate_all_email_invites.app_error", nil, result.Err.Error(), http.StatusBadRequest) + } + return nil +} diff --git a/i18n/en.json b/i18n/en.json index b22664dbe8..04f468be45 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1778,6 +1778,10 @@ "id": "api.team.import_team.unavailable.app_error", "translation": "Malformed request: filesize field is not present." }, + { + "id": "api.team.invalidate_all_email_invites.app_error", + "translation": "Error invalidating email invites." + }, { "id": "api.team.invite_members.disabled.app_error", "translation": "Email invitations are disabled." @@ -6022,6 +6026,10 @@ "id": "store.sql_recover.delete.app_error", "translation": "Unable to delete token" }, + { + "id": "store.sql_recover.remove_all_tokens_by_type.app_error", + "translation": "Unable to remove all the tokens of a type" + }, { "id": "store.sql_recover.get_by_code.app_error", "translation": "Unable to get a token with this code" diff --git a/store/sqlstore/tokens_store.go b/store/sqlstore/tokens_store.go index 6dde1bfbfb..5810d67629 100644 --- a/store/sqlstore/tokens_store.go +++ b/store/sqlstore/tokens_store.go @@ -75,3 +75,12 @@ func (s SqlTokenStore) Cleanup() { mlog.Error("Unable to cleanup token store.") } } + +func (s SqlTokenStore) RemoveAllTokensByType(tokenType string) store.StoreChannel { + return store.Do(func(result *store.StoreResult) { + if _, err := s.GetMaster().Exec("DELETE FROM Tokens WHERE Type = :TokenType", map[string]interface{}{"TokenType": tokenType}); err != nil { + result.Err = model.NewAppError("SqlTokenStore.RemoveAllTokensByType", "store.sql_recover.remove_all_tokens_by_type.app_error", nil, err.Error(), http.StatusInternalServerError) + return + } + }) +} diff --git a/store/store.go b/store/store.go index d395e2eec7..baa0338348 100644 --- a/store/store.go +++ b/store/store.go @@ -443,6 +443,7 @@ type TokenStore interface { Delete(token string) StoreChannel GetByToken(token string) StoreChannel Cleanup() + RemoveAllTokensByType(tokenType string) StoreChannel } type EmojiStore interface { diff --git a/store/storetest/mocks/PostStore.go b/store/storetest/mocks/PostStore.go index 973aff997e..2f169bd96e 100644 --- a/store/storetest/mocks/PostStore.go +++ b/store/storetest/mocks/PostStore.go @@ -98,6 +98,22 @@ func (_m *PostStore) Get(id string) store.StoreChannel { return r0 } +// GetDirectPostParentsForExportAfter provides a mock function with given fields: limit, afterId +func (_m *PostStore) GetDirectPostParentsForExportAfter(limit int, afterId string) store.StoreChannel { + ret := _m.Called(limit, afterId) + + var r0 store.StoreChannel + if rf, ok := ret.Get(0).(func(int, string) store.StoreChannel); ok { + r0 = rf(limit, afterId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.StoreChannel) + } + } + + return r0 +} + // GetEtag provides a mock function with given fields: channelId, allowFromCache func (_m *PostStore) GetEtag(channelId string, allowFromCache bool) store.StoreChannel { ret := _m.Called(channelId, allowFromCache) @@ -470,18 +486,3 @@ func (_m *PostStore) Update(newPost *model.Post, oldPost *model.Post) store.Stor return r0 } - -func (_m *PostStore) GetDirectPostParentsForExportAfter(limit int, afterId string) store.StoreChannel { - ret := _m.Called(limit, afterId) - - var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(int, string) store.StoreChannel); ok { - r0 = rf(limit, afterId) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(store.StoreChannel) - } - } - - return r0 -} diff --git a/store/storetest/mocks/SqlStore.go b/store/storetest/mocks/SqlStore.go index f57138c015..3c7c29c3b1 100644 --- a/store/storetest/mocks/SqlStore.go +++ b/store/storetest/mocks/SqlStore.go @@ -7,6 +7,7 @@ package mocks import gorp "github.com/mattermost/gorp" import mock "github.com/stretchr/testify/mock" +import squirrel "github.com/Masterminds/squirrel" import store "github.com/mattermost/mattermost-server/store" // SqlStore is an autogenerated mock type for the SqlStore type @@ -855,3 +856,17 @@ func (_m *SqlStore) Webhook() store.WebhookStore { return r0 } + +// getQueryBuilder provides a mock function with given fields: +func (_m *SqlStore) getQueryBuilder() squirrel.StatementBuilderType { + ret := _m.Called() + + var r0 squirrel.StatementBuilderType + if rf, ok := ret.Get(0).(func() squirrel.StatementBuilderType); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(squirrel.StatementBuilderType) + } + + return r0 +} diff --git a/store/storetest/mocks/TokenStore.go b/store/storetest/mocks/TokenStore.go index 657aeca490..fb4c62406c 100644 --- a/store/storetest/mocks/TokenStore.go +++ b/store/storetest/mocks/TokenStore.go @@ -50,6 +50,22 @@ func (_m *TokenStore) GetByToken(token string) store.StoreChannel { return r0 } +// RemoveAllTokensByType provides a mock function with given fields: tokenType +func (_m *TokenStore) RemoveAllTokensByType(tokenType string) store.StoreChannel { + ret := _m.Called(tokenType) + + var r0 store.StoreChannel + if rf, ok := ret.Get(0).(func(string) store.StoreChannel); ok { + r0 = rf(tokenType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.StoreChannel) + } + } + + return r0 +} + // Save provides a mock function with given fields: recovery func (_m *TokenStore) Save(recovery *model.Token) store.StoreChannel { ret := _m.Called(recovery)