mirror of
https://github.com/mattermost/mattermost.git
synced 2026-04-13 13:08:56 -04:00
* ci: enable fullyparallel mode for server tests Replace os.Setenv, os.Chdir, and global state mutations with parallel-safe alternatives (t.Setenv, t.Chdir, test hooks) across 37 files. Refactor GetLogRootPath and MM_INSTALL_TYPE to use package-level test hooks instead of environment variables. This enables gotestsum --fullparallel, allowing all test packages to run with maximum parallelism within each shard. Co-authored-by: Claude <claude@anthropic.com> * ci: split fullyparallel from continue-on-error in workflow template - Add new boolean input 'allow-failure' separate from 'fullyparallel' - Change continue-on-error to use allow-failure instead of fullyparallel - Update server-ci.yml to pass allow-failure: true for test coverage job - Allows independent control of parallel execution and failure tolerance Co-authored-by: Claude <claude@anthropic.com> * fix: protect TestOverrideLogRootPath with sync.Mutex for parallel tests - Replace global var TestOverrideLogRootPath with mutex-protected functions - Add SetTestOverrideLogRootPath() and getTestOverrideLogRootPath() functions - Update GetLogRootPath() to use thread-safe getter - Update all test files to use SetTestOverrideLogRootPath() with t.Cleanup() - Fixes race condition when running tests with t.Parallel() Co-authored-by: Claude <claude@anthropic.com> * fix: configure audit settings before server setup in tests - Move ExperimentalAuditSettings from UpdateConfig() to config defaults - Pass audit config via app.Config() option in SetupWithServerOptions() - Fixes audit test setup ordering to configure BEFORE server initialization - Resolves CodeRabbit's audit config timing issue in api4 tests Co-authored-by: Claude <claude@anthropic.com> * fix: implement SetTestOverrideLogRootPath mutex in logger.go The previous commit updated test callers to use SetTestOverrideLogRootPath() but didn't actually create the function in config/logger.go, causing build failures across all CI shards. This commit: - Replaces the exported var TestOverrideLogRootPath with mutex-protected unexported state (testOverrideLogRootPath + testOverrideLogRootMu) - Adds exported SetTestOverrideLogRootPath() setter - Adds unexported getTestOverrideLogRootPath() getter - Updates GetLogRootPath() to use the thread-safe getter - Fixes log_test.go callers that were missed in the previous commit Co-authored-by: Claude <claude@anthropic.com> * fix(test): use SetupConfig for access_control feature flag registration InitAccessControlPolicy() checks FeatureFlags.AttributeBasedAccessControl at route registration time during server startup. Setting the flag via UpdateConfig after Setup() is too late — routes are never registered and API calls return 404. Use SetupConfig() to pass the feature flag in the initial config before server startup, ensuring routes are properly registered. Co-authored-by: Claude <claude@anthropic.com> * fix(test): restore BurnOnRead flag state in TestRevealPost subtest The 'feature not enabled' subtest disables BurnOnRead without restoring it via t.Cleanup. Subsequent subtests inherit the disabled state, which can cause 501 errors when they expect the feature to be available. Add t.Cleanup to restore FeatureFlags.BurnOnRead = true after the subtest completes. Co-authored-by: Claude <claude@anthropic.com> * fix(test): restore EnableSharedChannelsMemberSync flag via t.Cleanup The test disables EnableSharedChannelsMemberSync without restoring it. If the subtest exits early (e.g., require failure), later sibling subtests inherit a disabled flag and become flaky. Add t.Cleanup to restore the flag after the subtest completes. Co-authored-by: Claude <claude@anthropic.com> * Fix test parallelism: use instance-scoped overrides and init-time audit config Replace package-level test globals (TestOverrideInstallType, SetTestOverrideLogRootPath) with fields on PlatformService so each test gets its own instance without process-wide mutation. Fix three audit tests (TestUserLoginAudit, TestLogoutAuditAuthStatus, TestUpdatePasswordAudit) that configured the audit logger after server init — the audit logger only reads config at startup, so pass audit settings via app.Config() at init time instead. Also revert the Go 1.24.13 downgrade and bump mattermost-govet to v2.0.2 for Go 1.25.8 compatibility. * Fix audit unit tests * Fix MMCLOUDURL unit tests * Fixed unit tests using MM_NOTIFY_ADMIN_COOL_OFF_DAYS * Make app migrations idempotent for parallel test safety Change System().Save() to System().SaveOrUpdate() in all migration completion markers. When two parallel tests share a database pool entry, both may race through the check-then-insert migration pattern. Save() causes a duplicate key fatal crash; SaveOrUpdate() makes the second write a harmless no-op. * test: address review feedback on fullyparallel PR - Use SetLogRootPathOverride() setter instead of direct field access in platform/support_packet_test.go and platform/log_test.go (pvev) - Restore TestGetLogRootPath in config/logger_test.go to keep MM_LOG_PATH env var coverage; test uses t.Setenv so it runs serially which is fine (pvev) - Fix misleading comment in config_test.go: code uses t.Setenv, not os.Setenv (jgheithcock) Co-authored-by: Claude <claude@anthropic.com> * fix: add missing os import in post_test.go The os import was dropped during a merge conflict resolution while burn-on-read shared channel tests from master still use os.Setenv. Co-authored-by: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com> Co-authored-by: wiggin77 <wiggin77@warpmail.net> Co-authored-by: Mattermost Build <build@mattermost.com>
2980 lines
103 KiB
Go
2980 lines
103 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||
// See LICENSE.txt for license information.
|
||
|
||
package app
|
||
|
||
import (
|
||
"bytes"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"errors"
|
||
_ "image/jpeg"
|
||
_ "image/png"
|
||
"net/http"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/mock"
|
||
"github.com/stretchr/testify/require"
|
||
|
||
"github.com/mattermost/mattermost/server/public/model"
|
||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||
oauthgitlab "github.com/mattermost/mattermost/server/v8/channels/app/oauthproviders/gitlab"
|
||
"github.com/mattermost/mattermost/server/v8/channels/app/users"
|
||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||
storemocks "github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
||
"github.com/mattermost/mattermost/server/v8/channels/utils/testutils"
|
||
"github.com/mattermost/mattermost/server/v8/einterfaces"
|
||
"github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
|
||
"github.com/mattermost/mattermost/server/v8/platform/services/sharedchannel"
|
||
)
|
||
|
||
// saveTeamState returns a function that restores the team's AllowedDomains field
|
||
// Call the returned function in a defer to ensure cleanup
|
||
func saveTeamState(th *TestHelper) func() {
|
||
originalAllowedDomains := th.BasicTeam.AllowedDomains
|
||
return func() {
|
||
th.BasicTeam.AllowedDomains = originalAllowedDomains
|
||
_, _ = th.App.UpdateTeam(th.BasicTeam)
|
||
}
|
||
}
|
||
|
||
func TestCreateOAuthUser(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.GitLabSettings.Enable = true
|
||
})
|
||
|
||
t.Run("create user successfully", func(t *testing.T) {
|
||
glUser := oauthgitlab.GitLabUser{Id: 42, Username: "o" + model.NewId(), Email: model.NewId() + "@simulator.amazonses.com", Name: "Joram Wilander"}
|
||
js, jsonErr := json.Marshal(glUser)
|
||
require.NoError(t, jsonErr)
|
||
|
||
user, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, bytes.NewReader(js), "", "", nil)
|
||
require.Nil(t, err)
|
||
|
||
require.Equal(t, glUser.Username, user.Username, "usernames didn't match")
|
||
|
||
appErr := th.App.PermanentDeleteUser(th.Context, user)
|
||
require.Nil(t, appErr)
|
||
})
|
||
|
||
t.Run("user exists, update authdata successfully", func(t *testing.T) {
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.Office365Settings.Enable = true
|
||
})
|
||
|
||
dbUser := th.BasicUser
|
||
|
||
// mock oAuth Provider, return data
|
||
mockUser := &model.User{Id: "abcdef", AuthData: model.NewPointer("e7110007-64be-43d8-9840-4a7e9c26b710"), Email: dbUser.Email}
|
||
mockSSOSettings := &model.SSOSettings{}
|
||
providerMock := &mocks.OAuthProvider{}
|
||
providerMock.On("IsSameUser", mock.AnythingOfType("*request.Context"), mock.Anything, mock.Anything).Return(true)
|
||
providerMock.On("GetUserFromJSON", mock.AnythingOfType("*request.Context"), mock.Anything, mock.Anything, mock.Anything).Return(mockUser, nil)
|
||
providerMock.On("GetSSOSettings", mock.AnythingOfType("*request.Context"), mock.Anything, mock.Anything).Return(mockSSOSettings, nil)
|
||
einterfaces.RegisterOAuthProvider(model.ServiceOffice365, providerMock)
|
||
|
||
// Update user to be OAuth, formatting to match Office365 OAuth data
|
||
s, er2 := th.App.Srv().Store().User().UpdateAuthData(dbUser.Id, model.ServiceOffice365, model.NewPointer("e711000764be43d898404a7e9c26b710"), "", false)
|
||
assert.NoError(t, er2)
|
||
assert.Equal(t, dbUser.Id, s)
|
||
|
||
// data passed doesn't matter as return is mocked
|
||
_, err := th.App.CreateOAuthUser(th.Context, model.ServiceOffice365, strings.NewReader("{}"), "", "", nil)
|
||
assert.Nil(t, err)
|
||
u, er := th.App.Srv().Store().User().GetByEmail(dbUser.Email)
|
||
assert.NoError(t, er)
|
||
// make sure authdata is updated
|
||
assert.Equal(t, "e7110007-64be-43d8-9840-4a7e9c26b710", *u.AuthData)
|
||
})
|
||
|
||
t.Run("user creation disabled", func(t *testing.T) {
|
||
*th.App.Config().TeamSettings.EnableUserCreation = false
|
||
_, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, strings.NewReader("{}"), "", "", nil)
|
||
require.NotNil(t, err, "should have failed - user creation disabled")
|
||
})
|
||
}
|
||
|
||
func TestUpdateDefaultProfileImage(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
startTime := model.GetMillis()
|
||
time.Sleep(time.Millisecond)
|
||
|
||
err := th.App.SetDefaultProfileImage(th.Context, &model.User{
|
||
Id: model.NewId(),
|
||
Username: "notvaliduser",
|
||
})
|
||
// It doesn't fail, but it does nothing
|
||
require.Nil(t, err)
|
||
|
||
user := th.BasicUser
|
||
|
||
err = th.App.UpdateDefaultProfileImage(th.Context, user)
|
||
require.Nil(t, err)
|
||
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
assert.Less(t, user.LastPictureUpdate, -startTime, "LastPictureUpdate should be set to -(current time in milliseconds)")
|
||
}
|
||
|
||
func TestAdjustProfileImage(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
_, appErr := th.App.AdjustImage(th.Context, bytes.NewReader([]byte{}))
|
||
require.NotNil(t, appErr)
|
||
|
||
// test image isn't the correct dimensions
|
||
// it should be adjusted
|
||
testjpg, err := testutils.ReadTestFile("testjpg.jpg")
|
||
require.NoError(t, err)
|
||
adjusted, appErr := th.App.AdjustImage(th.Context, bytes.NewReader(testjpg))
|
||
require.Nil(t, appErr)
|
||
assert.True(t, adjusted.Len() > 0)
|
||
assert.NotEqual(t, testjpg, adjusted)
|
||
|
||
// default image should not require adjustment
|
||
user := th.BasicUser
|
||
defaultImg, appErr := th.App.GetDefaultProfileImage(user)
|
||
require.Nil(t, appErr)
|
||
image2, appErr := th.App.AdjustImage(th.Context, bytes.NewReader(defaultImg))
|
||
require.Nil(t, appErr)
|
||
assert.Equal(t, defaultImg, image2.Bytes())
|
||
|
||
t.Run("EXIF orientation is applied for rotated images", func(t *testing.T) {
|
||
// quadrants-orientation-8.png: 128×128 color quadrants with EXIF orientation 8.
|
||
// quadrants-orientation-1.png: same visual content already rotated, EXIF orientation 1.
|
||
rotated, err := testutils.ReadTestFile("exif_samples/quadrants-orientation-8.png")
|
||
require.NoError(t, err)
|
||
normal, err := testutils.ReadTestFile("exif_samples/quadrants-orientation-1.png")
|
||
require.NoError(t, err)
|
||
|
||
rotatedResult, appErr := th.App.AdjustImage(th.Context, bytes.NewReader(rotated))
|
||
require.Nil(t, appErr)
|
||
normalResult, appErr := th.App.AdjustImage(th.Context, bytes.NewReader(normal))
|
||
require.Nil(t, appErr)
|
||
|
||
assert.Equal(t, rotatedResult.Bytes(), normalResult.Bytes(),
|
||
"EXIF-rotated image should produce the same profile picture as the normally-oriented one")
|
||
})
|
||
}
|
||
|
||
func TestUpdateUserToRestrictedDomain(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t)
|
||
|
||
user := th.CreateUser(t)
|
||
defer func() {
|
||
appErr := th.App.PermanentDeleteUser(th.Context, user)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.TeamSettings.RestrictCreationToDomains = "foo.com"
|
||
})
|
||
|
||
_, err := th.App.UpdateUser(th.Context, user, false)
|
||
assert.Nil(t, err)
|
||
|
||
user.Email = "asdf@ghjk.l"
|
||
_, err = th.App.UpdateUser(th.Context, user, false)
|
||
assert.NotNil(t, err)
|
||
|
||
t.Run("Restricted Domains must be ignored for guest users", func(t *testing.T) {
|
||
guest := th.CreateGuest(t)
|
||
defer func() {
|
||
appErr := th.App.PermanentDeleteUser(th.Context, guest)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.TeamSettings.RestrictCreationToDomains = "foo.com"
|
||
})
|
||
|
||
guest.Email = "asdf@bar.com"
|
||
updatedGuest, err := th.App.UpdateUser(th.Context, guest, false)
|
||
require.Nil(t, err)
|
||
require.Equal(t, guest.Email, updatedGuest.Email)
|
||
})
|
||
|
||
t.Run("Guest users should be affected by guest restricted domains", func(t *testing.T) {
|
||
guest := th.CreateGuest(t)
|
||
defer func() {
|
||
appErr := th.App.PermanentDeleteUser(th.Context, guest)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.GuestAccountsSettings.RestrictCreationToDomains = "foo.com"
|
||
})
|
||
|
||
guest.Email = "asdf@bar.com"
|
||
_, err := th.App.UpdateUser(th.Context, guest, false)
|
||
require.NotNil(t, err)
|
||
|
||
guest.Email = "asdf@foo.com"
|
||
updatedGuest, err := th.App.UpdateUser(th.Context, guest, false)
|
||
require.Nil(t, err)
|
||
require.Equal(t, guest.Email, updatedGuest.Email)
|
||
})
|
||
}
|
||
|
||
func TestUpdateUser(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t)
|
||
|
||
user := th.CreateUser(t)
|
||
group := th.CreateGroup(t)
|
||
|
||
t.Run("fails if the username matches a group name", func(t *testing.T) {
|
||
user.Username = *group.Name
|
||
u, err := th.App.UpdateUser(th.Context, user, false)
|
||
require.NotNil(t, err)
|
||
require.Nil(t, u)
|
||
})
|
||
|
||
t.Run("fails if default profile picture is not updated when user has default profile picture and username is changed", func(t *testing.T) {
|
||
user.Username = "updatedUsername"
|
||
iLastPictureUpdate := user.LastPictureUpdate
|
||
require.Equal(t, iLastPictureUpdate, int64(0))
|
||
u, err := th.App.UpdateUser(th.Context, user, false)
|
||
require.Nil(t, err)
|
||
require.NotNil(t, u)
|
||
require.Less(t, u.LastPictureUpdate, iLastPictureUpdate)
|
||
require.Empty(t, u.Password)
|
||
})
|
||
|
||
t.Run("fails if profile picture is updated when user has custom profile picture and username is changed", func(t *testing.T) {
|
||
// Give the user a LastPictureUpdate to mimic having a custom profile picture
|
||
err := th.App.Srv().Store().User().UpdateLastPictureUpdate(user.Id)
|
||
require.NoError(t, err)
|
||
iUser, errGetUser := th.App.GetUser(user.Id)
|
||
require.Nil(t, errGetUser)
|
||
iUser.Username = "updatedUsername"
|
||
iLastPictureUpdate := iUser.LastPictureUpdate
|
||
require.Greater(t, iLastPictureUpdate, int64(0))
|
||
|
||
// Attempt the update, ensure the LastPictureUpdate has not changed
|
||
updatedUser, errUpdateUser := th.App.UpdateUser(th.Context, iUser, false)
|
||
require.Nil(t, errUpdateUser)
|
||
require.NotNil(t, updatedUser)
|
||
require.Equal(t, updatedUser.LastPictureUpdate, iLastPictureUpdate)
|
||
})
|
||
}
|
||
|
||
func TestUpdateUserNilUpdateResult(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := SetupWithStoreMock(t)
|
||
|
||
fakeUserID := model.NewId()
|
||
mockUser := &model.User{
|
||
Id: fakeUserID,
|
||
Username: "testuser",
|
||
Email: "test@example.com",
|
||
}
|
||
|
||
mockUserStore := storemocks.UserStore{}
|
||
mockUserStore.On("Get", mock.Anything, mock.Anything).Return(mockUser, nil)
|
||
// Simulate a store that returns (nil, nil) — no error but no result
|
||
mockUserStore.On("Update", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||
|
||
mockSessionStore := storemocks.SessionStore{}
|
||
mockOAuthStore := storemocks.OAuthStore{}
|
||
|
||
var err error
|
||
th.App.ch.srv.userService, err = users.New(users.ServiceConfig{
|
||
UserStore: &mockUserStore,
|
||
SessionStore: &mockSessionStore,
|
||
OAuthStore: &mockOAuthStore,
|
||
ConfigFn: th.App.ch.srv.platform.Config,
|
||
LicenseFn: th.App.ch.srv.License,
|
||
})
|
||
require.NoError(t, err)
|
||
|
||
updatedUser, appErr := th.App.UpdateUser(th.Context, mockUser, false)
|
||
require.Nil(t, updatedUser, "expected nil user when store returns nil update")
|
||
require.NotNil(t, appErr, "expected error when store returns nil update")
|
||
require.Equal(t, "app.user.update.find.app_error", appErr.Id)
|
||
}
|
||
|
||
func TestUpdateUserMissingFields(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t)
|
||
|
||
user := th.CreateUser(t)
|
||
defer func() {
|
||
appErr := th.App.PermanentDeleteUser(th.Context, user)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
|
||
tests := map[string]struct {
|
||
input *model.User
|
||
expect string
|
||
}{
|
||
"no missing fields": {input: &model.User{Id: user.Id, Username: user.Username, Email: user.Email}, expect: ""},
|
||
"missing id": {input: &model.User{Username: user.Username, Email: user.Email}, expect: "app.user.missing_account.const"},
|
||
"missing username": {input: &model.User{Id: user.Id, Email: user.Email}, expect: "model.user.is_valid.username.app_error"},
|
||
"missing email": {input: &model.User{Id: user.Id, Username: user.Username}, expect: "model.user.is_valid.email.app_error"},
|
||
}
|
||
|
||
for name, tc := range tests {
|
||
t.Run(name, func(t *testing.T) {
|
||
_, err := th.App.UpdateUser(th.Context, tc.input, false)
|
||
|
||
if name == "no missing fields" {
|
||
assert.Nil(t, err)
|
||
} else {
|
||
assert.Equal(t, tc.expect, err.Id)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestCreateUser(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t)
|
||
|
||
t.Run("fails if the username matches a group name", func(t *testing.T) {
|
||
group := th.CreateGroup(t)
|
||
|
||
id := model.NewId()
|
||
user := &model.User{
|
||
Email: "success+" + id + "@simulator.amazonses.com",
|
||
Username: *group.Name,
|
||
Nickname: "nn_" + id,
|
||
Password: model.NewTestPassword(),
|
||
EmailVerified: true,
|
||
}
|
||
|
||
user.Username = *group.Name
|
||
u, err := th.App.CreateUser(th.Context, user)
|
||
require.NotNil(t, err)
|
||
require.Nil(t, u)
|
||
})
|
||
|
||
t.Run("should sanitize user authdata before publishing to plugin hooks", func(t *testing.T) {
|
||
tearDown, _, _ := SetAppEnvironmentWithPlugins(t,
|
||
[]string{
|
||
`
|
||
package main
|
||
|
||
import (
|
||
"github.com/mattermost/mattermost/server/public/plugin"
|
||
"github.com/mattermost/mattermost/server/public/model"
|
||
)
|
||
|
||
type MyPlugin struct {
|
||
plugin.MattermostPlugin
|
||
}
|
||
|
||
func (p *MyPlugin) UserHasBeenCreated(c *plugin.Context, user *model.User) {
|
||
user.Nickname = "sanitized"
|
||
if len(user.Password) > 0 {
|
||
user.Nickname = "not-sanitized"
|
||
}
|
||
p.API.UpdateUser(user)
|
||
}
|
||
|
||
func main() {
|
||
plugin.ClientMain(&MyPlugin{})
|
||
}
|
||
`,
|
||
}, th.App, th.NewPluginAPI)
|
||
defer tearDown()
|
||
|
||
user := &model.User{
|
||
Email: model.NewId() + "success+test@example.com",
|
||
Nickname: "Darth Vader",
|
||
Username: "vader" + model.NewId(),
|
||
Password: model.NewTestPassword(),
|
||
AuthService: "",
|
||
}
|
||
_, err := th.App.CreateUser(th.Context, user)
|
||
require.Nil(t, err)
|
||
|
||
time.Sleep(1 * time.Second)
|
||
|
||
user, err = th.App.GetUser(user.Id)
|
||
require.Nil(t, err)
|
||
require.Equal(t, "sanitized", user.Nickname)
|
||
})
|
||
}
|
||
|
||
func TestUpdateUserActive(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t)
|
||
|
||
user := th.CreateUser(t)
|
||
|
||
EnableUserDeactivation := th.App.Config().TeamSettings.EnableUserDeactivation
|
||
defer func() {
|
||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableUserDeactivation = EnableUserDeactivation })
|
||
}()
|
||
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.TeamSettings.EnableUserDeactivation = true
|
||
})
|
||
err := th.App.UpdateUserActive(th.Context, user.Id, false)
|
||
assert.Nil(t, err)
|
||
}
|
||
|
||
func TestUpdateActiveBotsSideEffect(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
bot, err := th.App.CreateBot(th.Context, &model.Bot{
|
||
Username: "username",
|
||
Description: "a bot",
|
||
OwnerId: th.BasicUser.Id,
|
||
})
|
||
require.Nil(t, err)
|
||
defer func() {
|
||
appErr := th.App.PermanentDeleteBot(th.Context, bot.UserId)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
|
||
// Automatic deactivation disabled
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.ServiceSettings.DisableBotsWhenOwnerIsDeactivated = false
|
||
})
|
||
|
||
_, appErr := th.App.UpdateActive(th.Context, th.BasicUser, false)
|
||
require.Nil(t, appErr)
|
||
|
||
retbot1, err := th.App.GetBot(th.Context, bot.UserId, true)
|
||
require.Nil(t, err)
|
||
require.Zero(t, retbot1.DeleteAt)
|
||
user1, err := th.App.GetUser(bot.UserId)
|
||
require.Nil(t, err)
|
||
require.Zero(t, user1.DeleteAt)
|
||
|
||
_, appErr = th.App.UpdateActive(th.Context, th.BasicUser, true)
|
||
require.Nil(t, appErr)
|
||
|
||
// Automatic deactivation enabled
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.ServiceSettings.DisableBotsWhenOwnerIsDeactivated = true
|
||
})
|
||
|
||
_, appErr = th.App.UpdateActive(th.Context, th.BasicUser, false)
|
||
require.Nil(t, appErr)
|
||
|
||
retbot2, err := th.App.GetBot(th.Context, bot.UserId, true)
|
||
require.Nil(t, err)
|
||
require.NotZero(t, retbot2.DeleteAt)
|
||
user2, err := th.App.GetUser(bot.UserId)
|
||
require.Nil(t, err)
|
||
require.NotZero(t, user2.DeleteAt)
|
||
|
||
_, appErr = th.App.UpdateActive(th.Context, th.BasicUser, true)
|
||
require.Nil(t, appErr)
|
||
}
|
||
|
||
func TestUpdateOAuthUserAttrs(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t)
|
||
|
||
id := model.NewId()
|
||
id2 := model.NewId()
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.GitLabSettings.Enable = true
|
||
})
|
||
gitlabProvider := einterfaces.GetOAuthProvider("gitlab")
|
||
|
||
username := "user" + id
|
||
username2 := "user" + id2
|
||
|
||
email := "user" + id + "@nowhere.com"
|
||
email2 := "user" + id2 + "@nowhere.com"
|
||
|
||
var user, user2 *model.User
|
||
var gitlabUserObj oauthgitlab.GitLabUser
|
||
user, gitlabUserObj = createGitlabUser(t, th.App, th.Context, 1, username, email)
|
||
user2, _ = createGitlabUser(t, th.App, th.Context, 2, username2, email2)
|
||
|
||
t.Run("UpdateUsername", func(t *testing.T) {
|
||
t.Run("NoExistingUserWithSameUsername", func(t *testing.T) {
|
||
gitlabUserObj.Username = "updateduser" + model.NewId()
|
||
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
|
||
data := bytes.NewReader(gitlabUser)
|
||
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
|
||
require.Nil(t, appErr)
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
|
||
require.Equal(t, gitlabUserObj.Username, user.Username, "user's username is not updated")
|
||
})
|
||
|
||
t.Run("ExistinguserWithSameUsername", func(t *testing.T) {
|
||
gitlabUserObj.Username = user2.Username
|
||
|
||
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
|
||
data := bytes.NewReader(gitlabUser)
|
||
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
|
||
require.Nil(t, appErr)
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
|
||
require.NotEqual(t, gitlabUserObj.Username, user.Username, "user's username is updated though there already exists another user with the same username")
|
||
})
|
||
})
|
||
|
||
t.Run("UpdateEmail", func(t *testing.T) {
|
||
t.Run("NoExistingUserWithSameEmail", func(t *testing.T) {
|
||
gitlabUserObj.Email = "newuser" + model.NewId() + "@nowhere.com"
|
||
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
|
||
data := bytes.NewReader(gitlabUser)
|
||
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
|
||
require.Nil(t, appErr)
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
|
||
require.Equal(t, gitlabUserObj.Email, user.Email, "user's email is not updated")
|
||
|
||
require.True(t, user.EmailVerified, "user's email should have been verified")
|
||
})
|
||
|
||
t.Run("ExistingUserWithSameEmail", func(t *testing.T) {
|
||
gitlabUserObj.Email = user2.Email
|
||
|
||
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
|
||
data := bytes.NewReader(gitlabUser)
|
||
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
|
||
require.Nil(t, appErr)
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
|
||
require.NotEqual(t, gitlabUserObj.Email, user.Email, "user's email is updated though there already exists another user with the same email")
|
||
})
|
||
})
|
||
|
||
t.Run("UpdateFirstName", func(t *testing.T) {
|
||
gitlabUserObj.Name = "Updated User"
|
||
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
|
||
data := bytes.NewReader(gitlabUser)
|
||
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
|
||
require.Nil(t, appErr)
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
|
||
require.Equal(t, "Updated", user.FirstName, "user's first name is not updated")
|
||
})
|
||
|
||
t.Run("UpdateLastName", func(t *testing.T) {
|
||
gitlabUserObj.Name = "Updated Lastname"
|
||
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
|
||
data := bytes.NewReader(gitlabUser)
|
||
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
appErr := th.App.UpdateOAuthUserAttrs(th.Context, data, user, gitlabProvider, "gitlab", nil)
|
||
require.Nil(t, appErr)
|
||
user = getUserFromDB(th.App, user.Id, t)
|
||
|
||
require.Equal(t, "Lastname", user.LastName, "user's last name is not updated")
|
||
})
|
||
}
|
||
|
||
func TestCreateUserConflict(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t)
|
||
|
||
user := &model.User{
|
||
Email: "test@localhost",
|
||
Username: model.NewUsername(),
|
||
}
|
||
user, err := th.App.Srv().Store().User().Save(th.Context, user)
|
||
require.NoError(t, err)
|
||
username := user.Username
|
||
|
||
var invErr *store.ErrInvalidInput
|
||
// Same id
|
||
_, err = th.App.Srv().Store().User().Save(th.Context, user)
|
||
require.Error(t, err)
|
||
require.True(t, errors.As(err, &invErr))
|
||
assert.Equal(t, "id", invErr.Field)
|
||
|
||
// Same email
|
||
user = &model.User{
|
||
Email: "test@localhost",
|
||
Username: model.NewUsername(),
|
||
}
|
||
_, err = th.App.Srv().Store().User().Save(th.Context, user)
|
||
require.Error(t, err)
|
||
require.True(t, errors.As(err, &invErr))
|
||
assert.Equal(t, "email", invErr.Field)
|
||
|
||
// Same username
|
||
user = &model.User{
|
||
Email: "test2@localhost",
|
||
Username: username,
|
||
}
|
||
_, err = th.App.Srv().Store().User().Save(th.Context, user)
|
||
require.Error(t, err)
|
||
require.True(t, errors.As(err, &invErr))
|
||
assert.Equal(t, "username", invErr.Field)
|
||
}
|
||
|
||
func TestUpdateUserEmail(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t)
|
||
|
||
user := th.CreateUser(t)
|
||
|
||
t.Run("RequireVerification", func(t *testing.T) {
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.EmailSettings.RequireEmailVerification = true
|
||
})
|
||
|
||
currentEmail := user.Email
|
||
newEmail := th.MakeEmail()
|
||
|
||
user.Email = newEmail
|
||
user2, appErr := th.App.UpdateUser(th.Context, user, false)
|
||
assert.Nil(t, appErr)
|
||
assert.Equal(t, currentEmail, user2.Email)
|
||
assert.True(t, user2.EmailVerified)
|
||
|
||
token, err := th.App.Srv().EmailService.CreateVerifyEmailToken(user2.Id, newEmail)
|
||
assert.NoError(t, err)
|
||
|
||
appErr = th.App.VerifyEmailFromToken(th.Context, token.Token)
|
||
assert.Nil(t, appErr)
|
||
|
||
user2, appErr = th.App.GetUser(user2.Id)
|
||
assert.Nil(t, appErr)
|
||
assert.Equal(t, newEmail, user2.Email)
|
||
assert.True(t, user2.EmailVerified)
|
||
|
||
// Create bot user
|
||
botuser := model.User{
|
||
Email: "botuser@localhost",
|
||
Username: model.NewUsername(),
|
||
IsBot: true,
|
||
}
|
||
_, nErr := th.App.Srv().Store().User().Save(th.Context, &botuser)
|
||
assert.NoError(t, nErr)
|
||
|
||
newBotEmail := th.MakeEmail()
|
||
botuser.Email = newBotEmail
|
||
botuser2, appErr := th.App.UpdateUser(th.Context, &botuser, false)
|
||
assert.Nil(t, appErr)
|
||
assert.Equal(t, botuser2.Email, newBotEmail)
|
||
})
|
||
|
||
t.Run("RequireVerificationAlreadyUsedEmail", func(t *testing.T) {
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.EmailSettings.RequireEmailVerification = true
|
||
})
|
||
|
||
user2 := th.CreateUser(t)
|
||
newEmail := user2.Email
|
||
|
||
user.Email = newEmail
|
||
user3, err := th.App.UpdateUser(th.Context, user, false)
|
||
require.NotNil(t, err)
|
||
assert.Equal(t, err.Id, "app.user.save.email_exists.app_error")
|
||
assert.Nil(t, user3)
|
||
})
|
||
|
||
t.Run("NoVerification", func(t *testing.T) {
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.EmailSettings.RequireEmailVerification = false
|
||
})
|
||
|
||
newEmail := th.MakeEmail()
|
||
|
||
user.Email = newEmail
|
||
user2, err := th.App.UpdateUser(th.Context, user, false)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, newEmail, user2.Email)
|
||
|
||
// Create bot user
|
||
botuser := model.User{
|
||
Email: "botuser@localhost",
|
||
Username: model.NewUsername(),
|
||
IsBot: true,
|
||
}
|
||
_, nErr := th.App.Srv().Store().User().Save(th.Context, &botuser)
|
||
assert.NoError(t, nErr)
|
||
|
||
newBotEmail := th.MakeEmail()
|
||
botuser.Email = newBotEmail
|
||
botuser2, err := th.App.UpdateUser(th.Context, &botuser, false)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, botuser2.Email, newBotEmail)
|
||
})
|
||
|
||
t.Run("NoVerificationAlreadyUsedEmail", func(t *testing.T) {
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.EmailSettings.RequireEmailVerification = false
|
||
})
|
||
|
||
user2 := th.CreateUser(t)
|
||
newEmail := user2.Email
|
||
|
||
user.Email = newEmail
|
||
user3, err := th.App.UpdateUser(th.Context, user, false)
|
||
require.NotNil(t, err)
|
||
assert.Equal(t, err.Id, "app.user.save.email_exists.app_error")
|
||
assert.Nil(t, user3)
|
||
})
|
||
|
||
t.Run("Only the last token works if verification is required", func(t *testing.T) {
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.EmailSettings.RequireEmailVerification = true
|
||
})
|
||
|
||
// we update the email a first time and update. The first
|
||
// token is sent with the email
|
||
user.Email = th.MakeEmail()
|
||
_, appErr := th.App.UpdateUser(th.Context, user, true)
|
||
require.Nil(t, appErr)
|
||
|
||
tokens := []*model.Token{}
|
||
require.Eventually(t, func() bool {
|
||
var err error
|
||
tokens, err = th.App.Srv().Store().Token().GetAllTokensByType(model.TokenTypeVerifyEmail)
|
||
return err == nil && len(tokens) == 1
|
||
}, 100*time.Millisecond, 10*time.Millisecond)
|
||
|
||
firstToken := tokens[0]
|
||
|
||
// without using the first token, we update the email a second
|
||
// time and another token gets sent. The first one should not
|
||
// work anymore and the second should work properly
|
||
user.Email = th.MakeEmail()
|
||
_, appErr = th.App.UpdateUser(th.Context, user, true)
|
||
require.Nil(t, appErr)
|
||
|
||
require.Eventually(t, func() bool {
|
||
var err error
|
||
tokens, err = th.App.Srv().Store().Token().GetAllTokensByType(model.TokenTypeVerifyEmail)
|
||
// We verify the same conditions as the earlier function,
|
||
// but we also need to ensure that this is not the same token
|
||
// as before, which is possible if the token update goroutine
|
||
// hasn't yet run.
|
||
return err == nil && len(tokens) == 1 && tokens[0].Token != firstToken.Token
|
||
}, 100*time.Millisecond, 10*time.Millisecond)
|
||
secondToken := tokens[0]
|
||
|
||
_, err := th.App.Srv().Store().Token().GetByToken(firstToken.Token)
|
||
require.Error(t, err)
|
||
|
||
require.NotNil(t, th.App.VerifyEmailFromToken(th.Context, firstToken.Token))
|
||
require.Nil(t, th.App.VerifyEmailFromToken(th.Context, secondToken.Token))
|
||
require.NotNil(t, th.App.VerifyEmailFromToken(th.Context, firstToken.Token))
|
||
})
|
||
}
|
||
|
||
func getUserFromDB(a *App, id string, t *testing.T) *model.User {
|
||
user, err := a.GetUser(id)
|
||
require.Nil(t, err, "user is not found", err)
|
||
return user
|
||
}
|
||
|
||
func getGitlabUserPayload(gitlabUser oauthgitlab.GitLabUser, t *testing.T) []byte {
|
||
var payload []byte
|
||
var err error
|
||
payload, err = json.Marshal(gitlabUser)
|
||
require.NoError(t, err, "Serialization of gitlab user to json failed", err)
|
||
|
||
return payload
|
||
}
|
||
|
||
func createGitlabUser(t *testing.T, a *App, rctx request.CTX, id int64, username string, email string) (*model.User, oauthgitlab.GitLabUser) {
|
||
gitlabUserObj := oauthgitlab.GitLabUser{Id: id, Username: username, Login: "user1", Email: email, Name: "Test User"}
|
||
gitlabUser := getGitlabUserPayload(gitlabUserObj, t)
|
||
|
||
var user *model.User
|
||
var err *model.AppError
|
||
|
||
user, err = a.CreateOAuthUser(rctx, "gitlab", bytes.NewReader(gitlabUser), "", "", nil)
|
||
require.Nil(t, err, "unable to create the user", err)
|
||
|
||
return user, gitlabUserObj
|
||
}
|
||
|
||
func TestGetUsersByStatus(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t)
|
||
|
||
team := th.CreateTeam(t)
|
||
channel, err := th.App.CreateChannel(th.Context, &model.Channel{
|
||
DisplayName: "dn_" + model.NewId(),
|
||
Name: "name_" + model.NewId(),
|
||
Type: model.ChannelTypeOpen,
|
||
TeamId: team.Id,
|
||
CreatorId: model.NewId(),
|
||
}, false)
|
||
require.Nil(t, err, "failed to create channel: %v", err)
|
||
|
||
createUserWithStatus := func(username string, status string) *model.User {
|
||
id := model.NewId()
|
||
|
||
user, err := th.App.CreateUser(th.Context, &model.User{
|
||
Email: "success+" + id + "@simulator.amazonses.com",
|
||
Username: "un_" + username + "_" + id,
|
||
Nickname: "nn_" + id,
|
||
Password: model.NewTestPassword(),
|
||
})
|
||
require.Nil(t, err, "failed to create user: %v", err)
|
||
|
||
th.LinkUserToTeam(t, user, team)
|
||
th.AddUserToChannel(t, user, channel)
|
||
|
||
th.App.Srv().Platform().SaveAndBroadcastStatus(&model.Status{
|
||
UserId: user.Id,
|
||
Status: status,
|
||
Manual: true,
|
||
})
|
||
|
||
return user
|
||
}
|
||
|
||
// Creating these out of order in case that affects results
|
||
awayUser1 := createUserWithStatus("away1", model.StatusAway)
|
||
awayUser2 := createUserWithStatus("away2", model.StatusAway)
|
||
dndUser1 := createUserWithStatus("dnd1", model.StatusDnd)
|
||
dndUser2 := createUserWithStatus("dnd2", model.StatusDnd)
|
||
offlineUser1 := createUserWithStatus("offline1", model.StatusOffline)
|
||
offlineUser2 := createUserWithStatus("offline2", model.StatusOffline)
|
||
onlineUser1 := createUserWithStatus("online1", model.StatusOnline)
|
||
onlineUser2 := createUserWithStatus("online2", model.StatusOnline)
|
||
|
||
t.Run("sorting by status then alphabetical", func(t *testing.T) {
|
||
usersByStatus, err := th.App.GetUsersInChannelPageByStatus(&model.UserGetOptions{
|
||
InChannelId: channel.Id,
|
||
Page: 0,
|
||
PerPage: 8,
|
||
}, true)
|
||
require.Nil(t, err)
|
||
|
||
expectedUsersByStatus := []*model.User{
|
||
onlineUser1,
|
||
onlineUser2,
|
||
awayUser1,
|
||
awayUser2,
|
||
dndUser1,
|
||
dndUser2,
|
||
offlineUser1,
|
||
offlineUser2,
|
||
}
|
||
|
||
require.Equalf(t, len(expectedUsersByStatus), len(usersByStatus), "received only %v users, expected %v", len(usersByStatus), len(expectedUsersByStatus))
|
||
|
||
for i := range usersByStatus {
|
||
require.Equalf(t, expectedUsersByStatus[i].Id, usersByStatus[i].Id, "received user %v at index %v, expected %v", usersByStatus[i].Username, i, expectedUsersByStatus[i].Username)
|
||
}
|
||
})
|
||
|
||
t.Run("paging", func(t *testing.T) {
|
||
usersByStatus, err := th.App.GetUsersInChannelPageByStatus(&model.UserGetOptions{
|
||
InChannelId: channel.Id,
|
||
Page: 0,
|
||
PerPage: 3,
|
||
}, true)
|
||
require.Nil(t, err)
|
||
|
||
require.Equal(t, 3, len(usersByStatus), "received too many users")
|
||
|
||
require.False(
|
||
t,
|
||
usersByStatus[0].Id != onlineUser1.Id && usersByStatus[1].Id != onlineUser2.Id,
|
||
"expected to receive online users first",
|
||
)
|
||
|
||
require.Equal(t, awayUser1.Id, usersByStatus[2].Id, "expected to receive away users second")
|
||
|
||
usersByStatus, err = th.App.GetUsersInChannelPageByStatus(&model.UserGetOptions{
|
||
InChannelId: channel.Id,
|
||
Page: 1,
|
||
PerPage: 3,
|
||
}, true)
|
||
require.Nil(t, err)
|
||
|
||
require.NotEmpty(t, usersByStatus, "at least some users are expected")
|
||
require.Equal(t, awayUser2.Id, usersByStatus[0].Id, "expected to receive away users second")
|
||
|
||
require.False(
|
||
t,
|
||
usersByStatus[1].Id != dndUser1.Id && usersByStatus[2].Id != dndUser2.Id,
|
||
"expected to receive dnd users third",
|
||
)
|
||
|
||
usersByStatus, err = th.App.GetUsersInChannelPageByStatus(&model.UserGetOptions{
|
||
InChannelId: channel.Id,
|
||
Page: 1,
|
||
PerPage: 4,
|
||
}, true)
|
||
require.Nil(t, err)
|
||
|
||
require.Equal(t, 4, len(usersByStatus), "received too many users")
|
||
|
||
require.False(
|
||
t,
|
||
usersByStatus[0].Id != dndUser1.Id && usersByStatus[1].Id != dndUser2.Id,
|
||
"expected to receive dnd users third",
|
||
)
|
||
|
||
require.False(
|
||
t,
|
||
usersByStatus[2].Id != offlineUser1.Id && usersByStatus[3].Id != offlineUser2.Id,
|
||
"expected to receive offline users last",
|
||
)
|
||
})
|
||
}
|
||
|
||
func TestGetUsersNotInAbacChannel(t *testing.T) {
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
// Set license to EnterpriseAdvanced
|
||
th.App.Srv().SetLicense(model.NewTestLicense("enterprise.advanced"))
|
||
|
||
// Enable ABAC in config
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.AccessControlSettings.EnableAttributeBasedAccessControl = true
|
||
})
|
||
|
||
// Create an ABAC channel
|
||
abacChannel := th.CreatePrivateChannel(t, th.BasicTeam)
|
||
|
||
// Create three test users and add them to the team
|
||
user1 := th.CreateUser(t) // Will have matching attributes for ABAC
|
||
user2 := th.CreateUser(t) // Won't have matching attributes
|
||
user3 := th.CreateUser(t) // Won't have matching attributes
|
||
th.LinkUserToTeam(t, user1, th.BasicTeam)
|
||
th.LinkUserToTeam(t, user2, th.BasicTeam)
|
||
th.LinkUserToTeam(t, user3, th.BasicTeam)
|
||
|
||
// Create a policy with the same ID as the ABAC channel
|
||
channelPolicy := &model.AccessControlPolicy{
|
||
Type: model.AccessControlPolicyTypeChannel,
|
||
ID: abacChannel.Id,
|
||
Name: "Test Channel Policy",
|
||
Revision: 1,
|
||
Version: model.AccessControlPolicyVersionV0_2,
|
||
Rules: []model.AccessControlPolicyRule{
|
||
{
|
||
Actions: []string{"view", "join_channel"},
|
||
Expression: "user.attributes.program == \"test-program\"",
|
||
},
|
||
},
|
||
}
|
||
|
||
// Save the channel policy
|
||
var storeErr error
|
||
channelPolicy, storeErr = th.App.Srv().Store().AccessControlPolicy().Save(th.Context, channelPolicy)
|
||
require.NoError(t, storeErr)
|
||
require.NotNil(t, channelPolicy)
|
||
t.Cleanup(func() {
|
||
dErr := th.App.Srv().Store().AccessControlPolicy().Delete(th.Context, channelPolicy.ID)
|
||
require.NoError(t, dErr)
|
||
})
|
||
|
||
// Mock the AccessControl service
|
||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||
originalAccessControl := th.App.Srv().ch.AccessControl
|
||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||
defer func() {
|
||
th.App.Srv().ch.AccessControl = originalAccessControl
|
||
}()
|
||
|
||
t.Run("Returns users with matching attributes using cursor pagination", func(t *testing.T) {
|
||
// Set up the mock to return user1 when querying for users
|
||
mockAccessControl.On("QueryUsersForResource",
|
||
mock.Anything,
|
||
abacChannel.Id,
|
||
"*",
|
||
mock.MatchedBy(func(opts model.SubjectSearchOptions) bool {
|
||
return opts.TeamID == th.BasicTeam.Id &&
|
||
opts.Limit == 50 &&
|
||
opts.Cursor.TargetID == ""
|
||
})).Return([]*model.User{user1}, int64(1), nil).Once()
|
||
|
||
// Call the new ABAC-specific function with th.Context as first parameter
|
||
users, appErr := th.App.GetUsersNotInAbacChannel(th.Context, th.BasicTeam.Id, abacChannel.Id, false, "", 50, true, nil)
|
||
require.Nil(t, appErr)
|
||
|
||
// Create a map of user IDs for easier lookup
|
||
userMap := make(map[string]bool)
|
||
for _, u := range users {
|
||
userMap[u.Id] = true
|
||
}
|
||
|
||
// Verify only user1 is returned
|
||
assert.True(t, userMap[user1.Id], "User1 should be returned for ABAC channel")
|
||
assert.False(t, userMap[user2.Id], "User2 should not be returned for ABAC channel")
|
||
assert.False(t, userMap[user3.Id], "User3 should not be returned for ABAC channel")
|
||
assert.Len(t, users, 1, "Should return exactly 1 user")
|
||
})
|
||
|
||
t.Run("Works with cursor-based pagination", func(t *testing.T) {
|
||
cursorID := "some-cursor-id"
|
||
|
||
// Set up the mock to return user1 when querying with cursor
|
||
mockAccessControl.On("QueryUsersForResource",
|
||
mock.Anything,
|
||
abacChannel.Id,
|
||
"*",
|
||
mock.MatchedBy(func(opts model.SubjectSearchOptions) bool {
|
||
return opts.TeamID == th.BasicTeam.Id &&
|
||
opts.Limit == 25 &&
|
||
opts.Cursor.TargetID == cursorID
|
||
})).Return([]*model.User{user1}, int64(1), nil).Once()
|
||
|
||
// Call with cursor ID and th.Context as first parameter
|
||
users, appErr := th.App.GetUsersNotInAbacChannel(th.Context, th.BasicTeam.Id, abacChannel.Id, false, cursorID, 25, true, nil)
|
||
require.Nil(t, appErr)
|
||
assert.Len(t, users, 1, "Should return exactly 1 user with cursor pagination")
|
||
})
|
||
|
||
t.Run("Returns error when AccessControl service is unavailable", func(t *testing.T) {
|
||
// Temporarily set AccessControl to nil
|
||
th.App.Srv().ch.AccessControl = nil
|
||
defer func() {
|
||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||
}()
|
||
|
||
// Call should return error with th.Context as first parameter
|
||
users, appErr := th.App.GetUsersNotInAbacChannel(th.Context, th.BasicTeam.Id, abacChannel.Id, false, "", 50, true, nil)
|
||
require.NotNil(t, appErr)
|
||
require.Nil(t, users)
|
||
assert.Equal(t, "api.user.get_users_not_in_abac_channel.access_control_unavailable.app_error", appErr.Id)
|
||
})
|
||
}
|
||
|
||
func TestCreateUserWithInviteId(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
|
||
|
||
t.Run("should create a user", func(t *testing.T) {
|
||
u, err := th.App.CreateUserWithInviteId(th.Context, &user, th.BasicTeam.InviteId, "")
|
||
require.Nil(t, err)
|
||
require.Equal(t, u.Id, user.Id)
|
||
})
|
||
|
||
t.Run("invalid invite id", func(t *testing.T) {
|
||
_, err := th.App.CreateUserWithInviteId(th.Context, &user, "", "")
|
||
require.NotNil(t, err)
|
||
require.Contains(t, err.Id, "app.team.get_by_invite_id")
|
||
})
|
||
|
||
t.Run("invalid domain", func(t *testing.T) {
|
||
restoreTeam := saveTeamState(th)
|
||
defer restoreTeam()
|
||
|
||
th.BasicTeam.AllowedDomains = "mattermost.com"
|
||
_, nErr := th.App.Srv().Store().Team().Update(th.BasicTeam)
|
||
require.NoError(t, nErr)
|
||
_, err := th.App.CreateUserWithInviteId(th.Context, &user, th.BasicTeam.InviteId, "")
|
||
require.NotNil(t, err)
|
||
require.Equal(t, "api.team.invite_members.invalid_email.app_error", err.Id)
|
||
})
|
||
}
|
||
|
||
func TestCreateUserWithToken(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
|
||
|
||
t.Run("invalid token", func(t *testing.T) {
|
||
_, err := th.App.CreateUserWithToken(th.Context, &user, &model.Token{Token: "123"})
|
||
require.NotNil(t, err, "Should fail on unexisting token")
|
||
})
|
||
|
||
t.Run("invalid token type", func(t *testing.T) {
|
||
token := model.NewToken(
|
||
model.TokenTypeVerifyEmail,
|
||
model.MapToJSON(map[string]string{"teamID": th.BasicTeam.Id, "email": user.Email}),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
defer func() {
|
||
appErr := th.App.DeleteToken(token)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
_, err := th.App.CreateUserWithToken(th.Context, &user, token)
|
||
require.NotNil(t, err, "Should fail on bad token type")
|
||
})
|
||
|
||
t.Run("token extra email does not match provided user data email", func(t *testing.T) {
|
||
invitationEmail := "attacker@test.com"
|
||
token := model.NewToken(
|
||
model.TokenTypeTeamInvitation,
|
||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": invitationEmail}),
|
||
)
|
||
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
_, err := th.App.CreateUserWithToken(th.Context, &user, token)
|
||
require.NotNil(t, err)
|
||
})
|
||
|
||
t.Run("expired token", func(t *testing.T) {
|
||
token := model.NewToken(
|
||
model.TokenTypeTeamInvitation,
|
||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": user.Email}),
|
||
)
|
||
token.CreateAt = model.GetMillis() - model.InvitationExpiryTime - 1
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
defer func() {
|
||
appErr := th.App.DeleteToken(token)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
_, err := th.App.CreateUserWithToken(th.Context, &user, token)
|
||
require.NotNil(t, err, "Should fail on expired token")
|
||
})
|
||
|
||
t.Run("invalid team id", func(t *testing.T) {
|
||
token := model.NewToken(
|
||
model.TokenTypeTeamInvitation,
|
||
model.MapToJSON(map[string]string{"teamId": model.NewId(), "email": user.Email}),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
defer func() {
|
||
appErr := th.App.DeleteToken(token)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
_, err := th.App.CreateUserWithToken(th.Context, &user, token)
|
||
require.NotNil(t, err, "Should fail on bad team id")
|
||
})
|
||
|
||
t.Run("valid regular user request", func(t *testing.T) {
|
||
invitationEmail := strings.ToLower(model.NewId()) + "other-email@test.com"
|
||
u := model.User{Email: invitationEmail, Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
|
||
token := model.NewToken(
|
||
model.TokenTypeTeamInvitation,
|
||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": invitationEmail}),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
newUser, err := th.App.CreateUserWithToken(th.Context, &u, token)
|
||
require.Nil(t, err, "Should add user to the team. err=%v", err)
|
||
assert.False(t, newUser.IsGuest())
|
||
require.Equal(t, invitationEmail, newUser.Email, "The user email must be the invitation one")
|
||
|
||
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
|
||
require.Error(t, nErr, "The token must be deleted after be used")
|
||
|
||
members, err := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, newUser.Id)
|
||
require.Nil(t, err)
|
||
assert.Len(t, members, 2)
|
||
})
|
||
|
||
t.Run("valid guest request", func(t *testing.T) {
|
||
invitationEmail := strings.ToLower(model.NewId()) + "other-email@test.com"
|
||
token := model.NewToken(
|
||
model.TokenTypeGuestInvitation,
|
||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": invitationEmail, "channels": th.BasicChannel.Id, "senderId": th.BasicUser.Id}),
|
||
)
|
||
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
guest := model.User{Email: invitationEmail, Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
|
||
newGuest, err := th.App.CreateUserWithToken(th.Context, &guest, token)
|
||
require.Nil(t, err, "Should add user to the team. err=%v", err)
|
||
|
||
assert.True(t, newGuest.IsGuest())
|
||
require.Equal(t, invitationEmail, newGuest.Email, "The user email must be the invitation one")
|
||
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
|
||
require.Error(t, nErr, "The token must be deleted after be used")
|
||
|
||
members, err := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, newGuest.Id)
|
||
require.Nil(t, err)
|
||
require.Len(t, members, 1)
|
||
assert.Equal(t, members[0].ChannelId, th.BasicChannel.Id)
|
||
})
|
||
|
||
t.Run("create guest having email domain restrictions", func(t *testing.T) {
|
||
enableGuestDomainRestrictions := *th.App.Config().GuestAccountsSettings.RestrictCreationToDomains
|
||
defer func() {
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
cfg.GuestAccountsSettings.RestrictCreationToDomains = &enableGuestDomainRestrictions
|
||
})
|
||
}()
|
||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GuestAccountsSettings.RestrictCreationToDomains = "restricted.com" })
|
||
forbiddenInvitationEmail := strings.ToLower(model.NewId()) + "other-email@test.com"
|
||
grantedInvitationEmail := strings.ToLower(model.NewId()) + "other-email@restricted.com"
|
||
forbiddenDomainToken := model.NewToken(
|
||
model.TokenTypeGuestInvitation,
|
||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": forbiddenInvitationEmail, "channels": th.BasicChannel.Id, "senderId": th.BasicUser.Id}),
|
||
)
|
||
grantedDomainToken := model.NewToken(
|
||
model.TokenTypeGuestInvitation,
|
||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": grantedInvitationEmail, "channels": th.BasicChannel.Id, "senderId": th.BasicUser.Id}),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(forbiddenDomainToken))
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(grantedDomainToken))
|
||
guest := model.User{
|
||
Email: forbiddenInvitationEmail,
|
||
Nickname: "Darth Vader",
|
||
Username: "vader" + model.NewId(),
|
||
Password: model.NewTestPassword(),
|
||
AuthService: "",
|
||
}
|
||
newGuest, err := th.App.CreateUserWithToken(th.Context, &guest, forbiddenDomainToken)
|
||
require.NotNil(t, err)
|
||
require.Nil(t, newGuest)
|
||
assert.Equal(t, "api.user.create_user.accepted_domain.app_error", err.Id)
|
||
|
||
guest.Email = grantedInvitationEmail
|
||
newGuest, err = th.App.CreateUserWithToken(th.Context, &guest, grantedDomainToken)
|
||
require.Nil(t, err)
|
||
assert.True(t, newGuest.IsGuest())
|
||
require.Equal(t, grantedInvitationEmail, newGuest.Email)
|
||
_, nErr := th.App.Srv().Store().Token().GetByToken(grantedDomainToken.Token)
|
||
require.Error(t, nErr)
|
||
|
||
members, err := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, newGuest.Id)
|
||
require.Nil(t, err)
|
||
require.Len(t, members, 1)
|
||
assert.Equal(t, members[0].ChannelId, th.BasicChannel.Id)
|
||
})
|
||
|
||
t.Run("create guest having team and system email domain restrictions", func(t *testing.T) {
|
||
restoreTeam := saveTeamState(th)
|
||
defer restoreTeam()
|
||
|
||
th.BasicTeam.AllowedDomains = "restricted-team.com"
|
||
_, err := th.App.UpdateTeam(th.BasicTeam)
|
||
require.Nil(t, err, "Should update the team")
|
||
enableGuestDomainRestrictions := *th.App.Config().TeamSettings.RestrictCreationToDomains
|
||
defer func() {
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
cfg.TeamSettings.RestrictCreationToDomains = &enableGuestDomainRestrictions
|
||
})
|
||
}()
|
||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictCreationToDomains = "restricted.com" })
|
||
invitationEmail := strings.ToLower(model.NewId()) + "other-email@test.com"
|
||
token := model.NewToken(
|
||
model.TokenTypeGuestInvitation,
|
||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id, "email": invitationEmail, "channels": th.BasicChannel.Id, "senderId": th.BasicUser.Id}),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
guest := model.User{
|
||
Email: invitationEmail,
|
||
Nickname: "Darth Vader",
|
||
Username: "vader" + model.NewId(),
|
||
Password: model.NewTestPassword(),
|
||
AuthService: "",
|
||
}
|
||
newGuest, err := th.App.CreateUserWithToken(th.Context, &guest, token)
|
||
require.Nil(t, err)
|
||
assert.True(t, newGuest.IsGuest())
|
||
assert.Equal(t, invitationEmail, newGuest.Email, "The user email must be the invitation one")
|
||
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
|
||
require.Error(t, nErr)
|
||
|
||
members, err := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, newGuest.Id)
|
||
require.Nil(t, err)
|
||
require.Len(t, members, 1)
|
||
assert.Equal(t, members[0].ChannelId, th.BasicChannel.Id)
|
||
})
|
||
|
||
// Tests for Guest Magic Link Invitation token channel assignment
|
||
t.Run("Guest Magic Link Invitation token adds guest to multiple channels", func(t *testing.T) {
|
||
invitationEmail := strings.ToLower(model.NewId()) + "magiclink@test.com"
|
||
channel1 := th.CreateChannel(t, th.BasicTeam)
|
||
channel2 := th.CreateChannel(t, th.BasicTeam)
|
||
|
||
tokenData := map[string]string{
|
||
"teamId": th.BasicTeam.Id,
|
||
"channels": channel1.Id + " " + channel2.Id,
|
||
"email": invitationEmail,
|
||
"guest": "true",
|
||
"senderId": th.BasicUser.Id,
|
||
}
|
||
token := model.NewToken(
|
||
model.TokenTypeGuestMagicLinkInvitation,
|
||
model.MapToJSON(tokenData),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
|
||
guest := model.User{Email: invitationEmail, Nickname: "Magic Link Guest", Username: "magiclinkguest" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
|
||
newGuest, err := th.App.CreateUserWithToken(th.Context, &guest, token)
|
||
require.Nil(t, err, "Should create guest user successfully")
|
||
require.True(t, newGuest.IsGuest())
|
||
|
||
// Verify token was deleted
|
||
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
|
||
require.Error(t, nErr, "Token should be deleted after use")
|
||
|
||
// Verify guest was added to both channels
|
||
members, err := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, newGuest.Id)
|
||
require.Nil(t, err)
|
||
|
||
channelIds := make(map[string]bool)
|
||
for _, member := range members {
|
||
channelIds[member.ChannelId] = true
|
||
}
|
||
assert.True(t, channelIds[channel1.Id], "Guest should be in channel1")
|
||
assert.True(t, channelIds[channel2.Id], "Guest should be in channel2")
|
||
})
|
||
|
||
t.Run("Guest Magic Link invitation token validates channel permissions", func(t *testing.T) {
|
||
invitationEmail := strings.ToLower(model.NewId()) + "magiclink@test.com"
|
||
|
||
// Create channels with different access levels
|
||
publicChannel := th.CreateChannel(t, th.BasicTeam)
|
||
privateChannel := th.CreatePrivateChannel(t, th.BasicTeam)
|
||
|
||
// Create another user and a private channel they own (sender doesn't have access)
|
||
otherUser := th.CreateUser(t)
|
||
th.LinkUserToTeam(t, otherUser, th.BasicTeam)
|
||
restrictedChannel := th.CreatePrivateChannel(t, th.BasicTeam)
|
||
_ = th.RemoveUserFromChannel(t, th.BasicUser, restrictedChannel)
|
||
th.AddUserToChannel(t, otherUser, restrictedChannel)
|
||
|
||
// Token includes all three channels
|
||
tokenData := map[string]string{
|
||
"teamId": th.BasicTeam.Id,
|
||
"channels": publicChannel.Id + " " + privateChannel.Id + " " + restrictedChannel.Id,
|
||
"email": invitationEmail,
|
||
"guest": "true",
|
||
"senderId": th.BasicUser.Id, // Sender has access to public and private, but not restricted
|
||
}
|
||
token := model.NewToken(
|
||
model.TokenTypeGuestMagicLinkInvitation,
|
||
model.MapToJSON(tokenData),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
|
||
guest := model.User{Email: invitationEmail, Nickname: "Magic Link Guest", Username: "magiclinkguest" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
|
||
newGuest, err := th.App.CreateUserWithToken(th.Context, &guest, token)
|
||
require.Nil(t, err)
|
||
|
||
// Verify guest was only added to channels the sender has permissions for
|
||
members, err := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, newGuest.Id)
|
||
require.Nil(t, err)
|
||
|
||
channelIds := make(map[string]bool)
|
||
for _, member := range members {
|
||
channelIds[member.ChannelId] = true
|
||
}
|
||
assert.True(t, channelIds[publicChannel.Id], "Guest should be in public channel")
|
||
assert.True(t, channelIds[privateChannel.Id], "Guest should be in sender's private channel")
|
||
assert.False(t, channelIds[restrictedChannel.Id], "Guest should NOT be in restricted channel")
|
||
})
|
||
|
||
t.Run("Guest Magic Link invitation token doesn't add user to channels if token is TeamInvitation", func(t *testing.T) {
|
||
invitationEmail := strings.ToLower(model.NewId()) + "regular@test.com"
|
||
channel1 := th.CreateChannel(t, th.BasicTeam)
|
||
|
||
// Use regular team invitation token (not guest magic link)
|
||
tokenData := map[string]string{
|
||
"teamId": th.BasicTeam.Id,
|
||
"channels": channel1.Id, // Channels specified but wrong token type
|
||
"email": invitationEmail,
|
||
"senderId": th.BasicUser.Id,
|
||
}
|
||
token := model.NewToken(
|
||
model.TokenTypeTeamInvitation, // Regular team invitation, not guest magic link
|
||
model.MapToJSON(tokenData),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
|
||
regularUser := model.User{Email: invitationEmail, Nickname: "Regular User", Username: "regular" + model.NewId(), Password: model.NewTestPassword(), AuthService: ""}
|
||
newUser, err := th.App.CreateUserWithToken(th.Context, ®ularUser, token)
|
||
require.Nil(t, err)
|
||
require.False(t, newUser.IsGuest())
|
||
|
||
// Regular team invitations with channels should still add to channels (existing behavior)
|
||
members, err := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, newUser.Id)
|
||
require.Nil(t, err)
|
||
|
||
// Should have default channels (Town Square, Off-Topic) + channel1 = 3 channels
|
||
channelIds := make(map[string]bool)
|
||
for _, member := range members {
|
||
channelIds[member.ChannelId] = true
|
||
}
|
||
assert.True(t, channelIds[channel1.Id], "User should be in channel1 even with TeamInvitation token")
|
||
})
|
||
}
|
||
|
||
func TestPermanentDeleteUser(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t).DeleteBots(t)
|
||
|
||
b := []byte("testimage")
|
||
|
||
finfo, err := th.App.DoUploadFile(th.Context, time.Now(), th.BasicTeam.Id, th.BasicChannel.Id, th.BasicUser.Id, "testfile.txt", b, true)
|
||
|
||
require.Nil(t, err, "Unable to upload file. err=%v", err)
|
||
|
||
// upload profile image
|
||
user := th.BasicUser
|
||
|
||
err = th.App.SetDefaultProfileImage(th.Context, user)
|
||
require.Nil(t, err)
|
||
|
||
bot, err := th.App.CreateBot(th.Context, &model.Bot{
|
||
Username: "botname",
|
||
Description: "a bot",
|
||
OwnerId: model.NewId(),
|
||
})
|
||
assert.Nil(t, err)
|
||
|
||
var botCount1 int
|
||
var botCount2 int
|
||
|
||
err1 := th.SQLStore.GetMaster().Get(&botCount1, "SELECT COUNT(*) FROM Bots")
|
||
assert.NoError(t, err1)
|
||
assert.Equal(t, 1, botCount1)
|
||
|
||
// test that bot is deleted from bots table
|
||
retUser1, err := th.App.GetUser(bot.UserId)
|
||
assert.Nil(t, err)
|
||
|
||
err = th.App.PermanentDeleteUser(th.Context, retUser1)
|
||
assert.Nil(t, err)
|
||
|
||
err1 = th.SQLStore.GetMaster().Get(&botCount2, "SELECT COUNT(*) FROM Bots")
|
||
assert.NoError(t, err1)
|
||
assert.Equal(t, 0, botCount2)
|
||
|
||
scheduledPost1 := &model.ScheduledPost{
|
||
Draft: model.Draft{
|
||
ChannelId: th.BasicChannel.Id,
|
||
UserId: th.BasicUser.Id,
|
||
Message: "Scheduled post 1",
|
||
},
|
||
ScheduledAt: model.GetMillis() + 1000000,
|
||
}
|
||
|
||
createdScheduledPost1, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost1, "")
|
||
require.Nil(t, appErr)
|
||
|
||
scheduledPost2 := &model.ScheduledPost{
|
||
Draft: model.Draft{
|
||
ChannelId: th.BasicChannel.Id,
|
||
UserId: th.BasicUser.Id,
|
||
Message: "Scheduled post 2",
|
||
},
|
||
ScheduledAt: model.GetMillis() + 1000000,
|
||
}
|
||
|
||
createdScheduledPost2, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost2, "")
|
||
require.Nil(t, appErr)
|
||
|
||
err = th.App.PermanentDeleteUser(th.Context, th.BasicUser)
|
||
require.Nil(t, err, "Unable to delete user. err=%v", err)
|
||
|
||
res, err := th.App.FileExists(finfo.Path)
|
||
|
||
require.Nil(t, err, "Unable to check whether file exists. err=%v", err)
|
||
|
||
require.False(t, res, "File was not deleted on FS. err=%v", err)
|
||
|
||
finfo, err = th.App.GetFileInfo(th.Context, finfo.Id)
|
||
|
||
require.Nil(t, finfo, "Unable to find finfo. err=%v", err)
|
||
|
||
require.NotNil(t, err, "GetFileInfo after DeleteUser is nil. err=%v", err)
|
||
|
||
// test deletion of profile picture
|
||
exists, err := th.App.FileExists(filepath.Join("users", user.Id))
|
||
require.Nil(t, err, "Unable to stat finfo. err=%v", err)
|
||
require.False(t, exists, "Profile image wasn't deleted. err=%v", err)
|
||
|
||
// verify scheduled posts have been deleted
|
||
fetchedScheduledPost, scheduledPostErr := th.App.Srv().Store().ScheduledPost().Get(createdScheduledPost1.Id)
|
||
require.ErrorIs(t, scheduledPostErr, sql.ErrNoRows)
|
||
require.Nil(t, fetchedScheduledPost)
|
||
|
||
fetchedScheduledPost, scheduledPostErr = th.App.Srv().Store().ScheduledPost().Get(createdScheduledPost2.Id)
|
||
require.ErrorIs(t, scheduledPostErr, sql.ErrNoRows)
|
||
require.Nil(t, fetchedScheduledPost)
|
||
}
|
||
|
||
func TestPasswordRecovery(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
t.Run("password token with same email as during creation", func(t *testing.T) {
|
||
token, err := th.App.CreatePasswordRecoveryToken(th.Context, th.BasicUser.Id, th.BasicUser.Email)
|
||
assert.Nil(t, err)
|
||
|
||
tokenData := struct {
|
||
UserID string
|
||
Email string
|
||
}{}
|
||
|
||
err2 := json.Unmarshal([]byte(token.Extra), &tokenData)
|
||
assert.NoError(t, err2)
|
||
assert.Equal(t, th.BasicUser.Id, tokenData.UserID)
|
||
assert.Equal(t, th.BasicUser.Email, tokenData.Email)
|
||
|
||
err = th.App.ResetPasswordFromToken(th.Context, token.Token, model.NewTestPassword())
|
||
assert.Nil(t, err)
|
||
})
|
||
|
||
t.Run("password token with modified email as during creation", func(t *testing.T) {
|
||
token, err := th.App.CreatePasswordRecoveryToken(th.Context, th.BasicUser.Id, th.BasicUser.Email)
|
||
assert.Nil(t, err)
|
||
|
||
th.App.UpdateConfig(func(c *model.Config) {
|
||
*c.EmailSettings.RequireEmailVerification = false
|
||
})
|
||
|
||
th.BasicUser.Email = th.MakeEmail()
|
||
_, err = th.App.UpdateUser(th.Context, th.BasicUser, false)
|
||
assert.Nil(t, err)
|
||
|
||
err = th.App.ResetPasswordFromToken(th.Context, token.Token, model.NewTestPassword())
|
||
assert.NotNil(t, err)
|
||
})
|
||
|
||
t.Run("non-expired token", func(t *testing.T) {
|
||
token, err := th.App.CreatePasswordRecoveryToken(th.Context, th.BasicUser.Id, th.BasicUser.Email)
|
||
assert.Nil(t, err)
|
||
|
||
err = th.App.resetPasswordFromToken(th.Context, token.Token, model.NewTestPassword(), model.GetMillis())
|
||
assert.Nil(t, err)
|
||
})
|
||
|
||
t.Run("expired token", func(t *testing.T) {
|
||
token, err := th.App.CreatePasswordRecoveryToken(th.Context, th.BasicUser.Id, th.BasicUser.Email)
|
||
assert.Nil(t, err)
|
||
|
||
err = th.App.resetPasswordFromToken(th.Context, token.Token, model.NewTestPassword(), model.GetMillisForTime(time.Now().Add(25*time.Hour)))
|
||
assert.NotNil(t, err)
|
||
})
|
||
}
|
||
|
||
func TestInvalidatePasswordRecoveryTokens(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
t.Run("remove manually added tokens", func(t *testing.T) {
|
||
for range 5 {
|
||
token := model.NewToken(
|
||
model.TokenTypePasswordRecovery,
|
||
model.MapToJSON(map[string]string{"UserId": th.BasicUser.Id, "email": th.BasicUser.Email}),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
}
|
||
tokens, err := th.App.Srv().Store().Token().GetAllTokensByType(model.TokenTypePasswordRecovery)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, 5, len(tokens))
|
||
|
||
appErr := th.App.InvalidatePasswordRecoveryTokensForUser(th.BasicUser.Id)
|
||
assert.Nil(t, appErr)
|
||
|
||
tokens, err = th.App.Srv().Store().Token().GetAllTokensByType(model.TokenTypePasswordRecovery)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, 0, len(tokens))
|
||
})
|
||
|
||
t.Run("add multiple tokens, should only be one valid", func(t *testing.T) {
|
||
_, appErr := th.App.CreatePasswordRecoveryToken(th.Context, th.BasicUser.Id, th.BasicUser.Email)
|
||
assert.Nil(t, appErr)
|
||
|
||
token, appErr := th.App.CreatePasswordRecoveryToken(th.Context, th.BasicUser.Id, th.BasicUser.Email)
|
||
assert.Nil(t, appErr)
|
||
|
||
tokens, err := th.App.Srv().Store().Token().GetAllTokensByType(model.TokenTypePasswordRecovery)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, 1, len(tokens))
|
||
assert.Equal(t, token.Token, tokens[0].Token)
|
||
})
|
||
}
|
||
|
||
func TestPasswordChangeSessionTermination(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
t.Run("user-initiated password change with termination enabled", func(t *testing.T) {
|
||
th.App.UpdateConfig(func(c *model.Config) {
|
||
*c.ServiceSettings.TerminateSessionsOnPasswordChange = true
|
||
})
|
||
|
||
session, err := th.App.CreateSession(th.Context, &model.Session{
|
||
UserId: th.BasicUser2.Id,
|
||
Roles: model.SystemUserRoleId,
|
||
})
|
||
require.Nil(t, err)
|
||
|
||
session2, err := th.App.CreateSession(th.Context, &model.Session{
|
||
UserId: th.BasicUser2.Id,
|
||
Roles: model.SystemUserRoleId,
|
||
})
|
||
require.Nil(t, err)
|
||
|
||
th.Context.Session().UserId = th.BasicUser2.Id
|
||
th.Context.Session().Id = session.Id
|
||
|
||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
|
||
require.Nil(t, err)
|
||
|
||
session, err = th.App.GetSession(session.Token)
|
||
require.Nil(t, err)
|
||
require.False(t, session.IsExpired())
|
||
|
||
session2, err = th.App.GetSession(session2.Token)
|
||
require.NotNil(t, err)
|
||
require.Nil(t, session2)
|
||
|
||
// Cleanup
|
||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
|
||
require.Nil(t, err)
|
||
th.Context.Session().UserId = ""
|
||
th.Context.Session().Id = ""
|
||
})
|
||
|
||
t.Run("user-initiated password change with termination disabled", func(t *testing.T) {
|
||
th.App.UpdateConfig(func(c *model.Config) {
|
||
*c.ServiceSettings.TerminateSessionsOnPasswordChange = false
|
||
})
|
||
|
||
session, err := th.App.CreateSession(th.Context, &model.Session{
|
||
UserId: th.BasicUser2.Id,
|
||
Roles: model.SystemUserRoleId,
|
||
})
|
||
require.Nil(t, err)
|
||
|
||
session2, err := th.App.CreateSession(th.Context, &model.Session{
|
||
UserId: th.BasicUser2.Id,
|
||
Roles: model.SystemUserRoleId,
|
||
})
|
||
require.Nil(t, err)
|
||
|
||
th.Context.Session().UserId = th.BasicUser2.Id
|
||
th.Context.Session().Id = session.Id
|
||
|
||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
|
||
require.Nil(t, err)
|
||
|
||
session, err = th.App.GetSession(session.Token)
|
||
require.Nil(t, err)
|
||
require.False(t, session.IsExpired())
|
||
|
||
session2, err = th.App.GetSession(session2.Token)
|
||
require.Nil(t, err)
|
||
require.False(t, session2.IsExpired())
|
||
|
||
// Cleanup
|
||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
|
||
require.Nil(t, err)
|
||
th.Context.Session().UserId = ""
|
||
th.Context.Session().Id = ""
|
||
})
|
||
|
||
t.Run("admin-initiated password change with termination enabled", func(t *testing.T) {
|
||
th.App.UpdateConfig(func(c *model.Config) {
|
||
*c.ServiceSettings.TerminateSessionsOnPasswordChange = true
|
||
})
|
||
|
||
session, err := th.App.CreateSession(th.Context, &model.Session{
|
||
UserId: th.BasicUser2.Id,
|
||
Roles: model.SystemUserRoleId,
|
||
})
|
||
require.Nil(t, err)
|
||
|
||
session2, err := th.App.CreateSession(th.Context, &model.Session{
|
||
UserId: th.BasicUser2.Id,
|
||
Roles: model.SystemUserRoleId,
|
||
})
|
||
require.Nil(t, err)
|
||
|
||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
|
||
require.Nil(t, err)
|
||
|
||
session, err = th.App.GetSession(session.Token)
|
||
require.NotNil(t, err)
|
||
require.Nil(t, session)
|
||
|
||
session2, err = th.App.GetSession(session2.Token)
|
||
require.NotNil(t, err)
|
||
require.Nil(t, session2)
|
||
|
||
// Cleanup
|
||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
|
||
require.Nil(t, err)
|
||
})
|
||
|
||
t.Run("admin-initiated password change with termination disabled", func(t *testing.T) {
|
||
th.App.UpdateConfig(func(c *model.Config) {
|
||
*c.ServiceSettings.TerminateSessionsOnPasswordChange = false
|
||
})
|
||
|
||
session, err := th.App.CreateSession(th.Context, &model.Session{
|
||
UserId: th.BasicUser2.Id,
|
||
Roles: model.SystemUserRoleId,
|
||
})
|
||
require.Nil(t, err)
|
||
|
||
session2, err := th.App.CreateSession(th.Context, &model.Session{
|
||
UserId: th.BasicUser2.Id,
|
||
Roles: model.SystemUserRoleId,
|
||
})
|
||
require.Nil(t, err)
|
||
|
||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
|
||
require.Nil(t, err)
|
||
|
||
session, err = th.App.GetSession(session.Token)
|
||
require.Nil(t, err)
|
||
require.False(t, session.IsExpired())
|
||
|
||
session2, err = th.App.GetSession(session2.Token)
|
||
require.Nil(t, err)
|
||
require.False(t, session2.IsExpired())
|
||
|
||
// Cleanup
|
||
err = th.App.UpdatePassword(th.Context, th.BasicUser2, model.NewTestPassword())
|
||
require.Nil(t, err)
|
||
})
|
||
}
|
||
|
||
func TestGetViewUsersRestrictions(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
team1 := th.CreateTeam(t)
|
||
team2 := th.CreateTeam(t)
|
||
th.CreateTeam(t) // Another team
|
||
|
||
user1 := th.CreateUser(t)
|
||
|
||
th.LinkUserToTeam(t, user1, team1)
|
||
th.LinkUserToTeam(t, user1, team2)
|
||
|
||
_, appErr := th.App.UpdateTeamMemberRoles(th.Context, team1.Id, user1.Id, "team_user team_admin")
|
||
require.Nil(t, appErr)
|
||
|
||
team1channel1 := th.CreateChannel(t, team1)
|
||
team1channel2 := th.CreateChannel(t, team1)
|
||
th.CreateChannel(t, team1) // Another channel
|
||
team1offtopic, err := th.App.GetChannelByName(th.Context, "off-topic", team1.Id, false)
|
||
require.Nil(t, err)
|
||
team1townsquare, err := th.App.GetChannelByName(th.Context, "town-square", team1.Id, false)
|
||
require.Nil(t, err)
|
||
|
||
team2channel1 := th.CreateChannel(t, team2)
|
||
th.CreateChannel(t, team2) // Another channel
|
||
team2offtopic, err := th.App.GetChannelByName(th.Context, "off-topic", team2.Id, false)
|
||
require.Nil(t, err)
|
||
team2townsquare, err := th.App.GetChannelByName(th.Context, "town-square", team2.Id, false)
|
||
require.Nil(t, err)
|
||
|
||
_, appErr = th.App.AddUserToChannel(th.Context, user1, team1channel1, false)
|
||
require.Nil(t, appErr)
|
||
_, appErr = th.App.AddUserToChannel(th.Context, user1, team1channel2, false)
|
||
require.Nil(t, appErr)
|
||
_, appErr = th.App.AddUserToChannel(th.Context, user1, team2channel1, false)
|
||
require.Nil(t, appErr)
|
||
|
||
addPermission := func(role *model.Role, permission string) *model.AppError {
|
||
newPermissions := append(role.Permissions, permission)
|
||
_, err := th.App.PatchRole(role, &model.RolePatch{Permissions: &newPermissions})
|
||
return err
|
||
}
|
||
|
||
removePermission := func(role *model.Role, permission string) *model.AppError {
|
||
newPermissions := []string{}
|
||
for _, oldPermission := range role.Permissions {
|
||
if permission != oldPermission {
|
||
newPermissions = append(newPermissions, oldPermission)
|
||
}
|
||
}
|
||
_, err := th.App.PatchRole(role, &model.RolePatch{Permissions: &newPermissions})
|
||
return err
|
||
}
|
||
|
||
t.Run("VIEW_MEMBERS permission granted at system level", func(t *testing.T) {
|
||
restrictions, err := th.App.GetViewUsersRestrictions(th.Context, user1.Id)
|
||
require.Nil(t, err)
|
||
|
||
assert.Nil(t, restrictions)
|
||
})
|
||
|
||
t.Run("VIEW_MEMBERS permission granted at team level", func(t *testing.T) {
|
||
systemUserRole, err := th.App.GetRoleByName(th.Context, model.SystemUserRoleId)
|
||
require.Nil(t, err)
|
||
teamUserRole, err := th.App.GetRoleByName(th.Context, model.TeamUserRoleId)
|
||
require.Nil(t, err)
|
||
|
||
require.Nil(t, removePermission(systemUserRole, model.PermissionViewMembers.Id))
|
||
defer func() {
|
||
appErr := addPermission(systemUserRole, model.PermissionViewMembers.Id)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
require.Nil(t, addPermission(teamUserRole, model.PermissionViewMembers.Id))
|
||
defer func() {
|
||
appErr := removePermission(teamUserRole, model.PermissionViewMembers.Id)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
|
||
restrictions, err := th.App.GetViewUsersRestrictions(th.Context, user1.Id)
|
||
require.Nil(t, err)
|
||
|
||
assert.NotNil(t, restrictions)
|
||
assert.NotNil(t, restrictions.Teams)
|
||
assert.NotNil(t, restrictions.Channels)
|
||
assert.ElementsMatch(t, []string{team1townsquare.Id, team1offtopic.Id, team1channel1.Id, team1channel2.Id, team2townsquare.Id, team2offtopic.Id, team2channel1.Id}, restrictions.Channels)
|
||
assert.ElementsMatch(t, []string{team1.Id, team2.Id}, restrictions.Teams)
|
||
})
|
||
|
||
t.Run("VIEW_MEMBERS permission not granted at any level", func(t *testing.T) {
|
||
systemUserRole, err := th.App.GetRoleByName(th.Context, model.SystemUserRoleId)
|
||
require.Nil(t, err)
|
||
require.Nil(t, removePermission(systemUserRole, model.PermissionViewMembers.Id))
|
||
defer func() {
|
||
appErr := addPermission(systemUserRole, model.PermissionViewMembers.Id)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
|
||
restrictions, err := th.App.GetViewUsersRestrictions(th.Context, user1.Id)
|
||
require.Nil(t, err)
|
||
|
||
assert.NotNil(t, restrictions)
|
||
assert.Empty(t, restrictions.Teams)
|
||
assert.NotNil(t, restrictions.Channels)
|
||
assert.ElementsMatch(t, []string{team1townsquare.Id, team1offtopic.Id, team1channel1.Id, team1channel2.Id, team2townsquare.Id, team2offtopic.Id, team2channel1.Id}, restrictions.Channels)
|
||
})
|
||
|
||
t.Run("VIEW_MEMBERS permission for some teams but not for others", func(t *testing.T) {
|
||
systemUserRole, err := th.App.GetRoleByName(th.Context, model.SystemUserRoleId)
|
||
require.Nil(t, err)
|
||
teamAdminRole, err := th.App.GetRoleByName(th.Context, model.TeamAdminRoleId)
|
||
require.Nil(t, err)
|
||
|
||
require.Nil(t, removePermission(systemUserRole, model.PermissionViewMembers.Id))
|
||
defer func() {
|
||
appErr := addPermission(systemUserRole, model.PermissionViewMembers.Id)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
require.Nil(t, addPermission(teamAdminRole, model.PermissionViewMembers.Id))
|
||
defer func() {
|
||
appErr := removePermission(teamAdminRole, model.PermissionViewMembers.Id)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
|
||
restrictions, err := th.App.GetViewUsersRestrictions(th.Context, user1.Id)
|
||
require.Nil(t, err)
|
||
|
||
assert.NotNil(t, restrictions)
|
||
assert.NotNil(t, restrictions.Teams)
|
||
assert.NotNil(t, restrictions.Channels)
|
||
assert.ElementsMatch(t, restrictions.Teams, []string{team1.Id})
|
||
assert.ElementsMatch(t, []string{team1townsquare.Id, team1offtopic.Id, team1channel1.Id, team1channel2.Id, team2townsquare.Id, team2offtopic.Id, team2channel1.Id}, restrictions.Channels)
|
||
})
|
||
}
|
||
|
||
func TestPromoteGuestToUser(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
t.Run("Must fail with regular user", func(t *testing.T) {
|
||
require.Equal(t, "system_user", th.BasicUser.Roles)
|
||
err := th.App.PromoteGuestToUser(th.Context, th.BasicUser, th.BasicUser.Id)
|
||
require.Nil(t, err)
|
||
|
||
user, err := th.App.GetUser(th.BasicUser.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, "system_user", user.Roles)
|
||
})
|
||
|
||
t.Run("Must work with guest user without teams or channels", func(t *testing.T) {
|
||
guest := th.CreateGuest(t)
|
||
require.Equal(t, "system_guest", guest.Roles)
|
||
|
||
err := th.App.PromoteGuestToUser(th.Context, guest, th.BasicUser.Id)
|
||
require.Nil(t, err)
|
||
guest, err = th.App.GetUser(guest.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, "system_user", guest.Roles)
|
||
})
|
||
|
||
t.Run("Must work with guest user with teams but no channels", func(t *testing.T) {
|
||
guest := th.CreateGuest(t)
|
||
require.Equal(t, "system_guest", guest.Roles)
|
||
th.LinkUserToTeam(t, guest, th.BasicTeam)
|
||
teamMember, err := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
|
||
require.Nil(t, err)
|
||
require.True(t, teamMember.SchemeGuest)
|
||
require.False(t, teamMember.SchemeUser)
|
||
|
||
err = th.App.PromoteGuestToUser(th.Context, guest, th.BasicUser.Id)
|
||
require.Nil(t, err)
|
||
guest, err = th.App.GetUser(guest.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, "system_user", guest.Roles)
|
||
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, teamMember.SchemeGuest)
|
||
assert.True(t, teamMember.SchemeUser)
|
||
})
|
||
|
||
t.Run("Must work with guest user with teams and channels", func(t *testing.T) {
|
||
guest := th.CreateGuest(t)
|
||
require.Equal(t, "system_guest", guest.Roles)
|
||
th.LinkUserToTeam(t, guest, th.BasicTeam)
|
||
teamMember, err := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
|
||
require.Nil(t, err)
|
||
require.True(t, teamMember.SchemeGuest)
|
||
require.False(t, teamMember.SchemeUser)
|
||
|
||
channelMember := th.AddUserToChannel(t, guest, th.BasicChannel)
|
||
require.True(t, channelMember.SchemeGuest)
|
||
require.False(t, channelMember.SchemeUser)
|
||
|
||
err = th.App.PromoteGuestToUser(th.Context, guest, th.BasicUser.Id)
|
||
require.Nil(t, err)
|
||
guest, err = th.App.GetUser(guest.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, "system_user", guest.Roles)
|
||
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, teamMember.SchemeGuest)
|
||
assert.True(t, teamMember.SchemeUser)
|
||
_, err = th.App.GetChannelMember(th.Context, th.BasicChannel.Id, guest.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, teamMember.SchemeGuest)
|
||
assert.True(t, teamMember.SchemeUser)
|
||
})
|
||
|
||
t.Run("Must add the default channels", func(t *testing.T) {
|
||
guest := th.CreateGuest(t)
|
||
require.Equal(t, "system_guest", guest.Roles)
|
||
th.LinkUserToTeam(t, guest, th.BasicTeam)
|
||
teamMember, err := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
|
||
require.Nil(t, err)
|
||
require.True(t, teamMember.SchemeGuest)
|
||
require.False(t, teamMember.SchemeUser)
|
||
|
||
channelMember := th.AddUserToChannel(t, guest, th.BasicChannel)
|
||
require.True(t, channelMember.SchemeGuest)
|
||
require.False(t, channelMember.SchemeUser)
|
||
|
||
channelMembers, err := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, guest.Id)
|
||
require.Nil(t, err)
|
||
require.Len(t, channelMembers, 1)
|
||
|
||
err = th.App.PromoteGuestToUser(th.Context, guest, th.BasicUser.Id)
|
||
require.Nil(t, err)
|
||
guest, err = th.App.GetUser(guest.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, "system_user", guest.Roles)
|
||
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, teamMember.SchemeGuest)
|
||
assert.True(t, teamMember.SchemeUser)
|
||
_, err = th.App.GetChannelMember(th.Context, th.BasicChannel.Id, guest.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, teamMember.SchemeGuest)
|
||
assert.True(t, teamMember.SchemeUser)
|
||
|
||
channelMembers, err = th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, guest.Id)
|
||
require.Nil(t, err)
|
||
assert.Len(t, channelMembers, 3)
|
||
})
|
||
|
||
t.Run("Must invalidate channel stats cache when promoting a guest", func(t *testing.T) {
|
||
guest := th.CreateGuest(t)
|
||
require.Equal(t, "system_guest", guest.Roles)
|
||
th.LinkUserToTeam(t, guest, th.BasicTeam)
|
||
teamMember, err := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, guest.Id)
|
||
require.Nil(t, err)
|
||
require.True(t, teamMember.SchemeGuest)
|
||
require.False(t, teamMember.SchemeUser)
|
||
|
||
guestCount, _ := th.App.GetChannelGuestCount(th.Context, th.BasicChannel.Id)
|
||
require.Equal(t, int64(0), guestCount)
|
||
|
||
channelMember := th.AddUserToChannel(t, guest, th.BasicChannel)
|
||
require.True(t, channelMember.SchemeGuest)
|
||
require.False(t, channelMember.SchemeUser)
|
||
|
||
guestCount, _ = th.App.GetChannelGuestCount(th.Context, th.BasicChannel.Id)
|
||
require.Equal(t, int64(1), guestCount)
|
||
|
||
err = th.App.PromoteGuestToUser(th.Context, guest, th.BasicUser.Id)
|
||
require.Nil(t, err)
|
||
|
||
guestCount, _ = th.App.GetChannelGuestCount(th.Context, th.BasicChannel.Id)
|
||
require.Equal(t, int64(0), guestCount)
|
||
})
|
||
}
|
||
|
||
func TestDemoteUserToGuest(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
t.Run("Must invalidate channel stats cache when demoting a user", func(t *testing.T) {
|
||
user := th.CreateUser(t)
|
||
require.Equal(t, "system_user", user.Roles)
|
||
th.LinkUserToTeam(t, user, th.BasicTeam)
|
||
teamMember, err := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
||
require.Nil(t, err)
|
||
require.True(t, teamMember.SchemeUser)
|
||
require.False(t, teamMember.SchemeGuest)
|
||
|
||
guestCount, _ := th.App.GetChannelGuestCount(th.Context, th.BasicChannel.Id)
|
||
require.Equal(t, int64(0), guestCount)
|
||
|
||
channelMember := th.AddUserToChannel(t, user, th.BasicChannel)
|
||
require.True(t, channelMember.SchemeUser)
|
||
require.False(t, channelMember.SchemeGuest)
|
||
|
||
guestCount, _ = th.App.GetChannelGuestCount(th.Context, th.BasicChannel.Id)
|
||
require.Equal(t, int64(0), guestCount)
|
||
|
||
err = th.App.DemoteUserToGuest(th.Context, user)
|
||
require.Nil(t, err)
|
||
|
||
guestCount, _ = th.App.GetChannelGuestCount(th.Context, th.BasicChannel.Id)
|
||
require.Equal(t, int64(1), guestCount)
|
||
})
|
||
|
||
t.Run("Must fail with guest user", func(t *testing.T) {
|
||
guest := th.CreateGuest(t)
|
||
require.Equal(t, "system_guest", guest.Roles)
|
||
err := th.App.DemoteUserToGuest(th.Context, guest)
|
||
require.Nil(t, err)
|
||
|
||
user, err := th.App.GetUser(guest.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, "system_guest", user.Roles)
|
||
})
|
||
|
||
t.Run("Must work with user without teams or channels", func(t *testing.T) {
|
||
user := th.CreateUser(t)
|
||
require.Equal(t, "system_user", user.Roles)
|
||
|
||
err := th.App.DemoteUserToGuest(th.Context, user)
|
||
require.Nil(t, err)
|
||
user, err = th.App.GetUser(user.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, "system_guest", user.Roles)
|
||
})
|
||
|
||
t.Run("Must work with user with teams but no channels", func(t *testing.T) {
|
||
user := th.CreateUser(t)
|
||
require.Equal(t, "system_user", user.Roles)
|
||
th.LinkUserToTeam(t, user, th.BasicTeam)
|
||
teamMember, err := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
||
require.Nil(t, err)
|
||
require.True(t, teamMember.SchemeUser)
|
||
require.False(t, teamMember.SchemeGuest)
|
||
|
||
err = th.App.DemoteUserToGuest(th.Context, user)
|
||
require.Nil(t, err)
|
||
user, err = th.App.GetUser(user.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, "system_guest", user.Roles)
|
||
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, teamMember.SchemeUser)
|
||
assert.True(t, teamMember.SchemeGuest)
|
||
})
|
||
|
||
t.Run("Must work with user with teams and channels", func(t *testing.T) {
|
||
user := th.CreateUser(t)
|
||
require.Equal(t, "system_user", user.Roles)
|
||
th.LinkUserToTeam(t, user, th.BasicTeam)
|
||
teamMember, err := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
||
require.Nil(t, err)
|
||
require.True(t, teamMember.SchemeUser)
|
||
require.False(t, teamMember.SchemeGuest)
|
||
|
||
channelMember := th.AddUserToChannel(t, user, th.BasicChannel)
|
||
require.True(t, channelMember.SchemeUser)
|
||
require.False(t, channelMember.SchemeGuest)
|
||
|
||
err = th.App.DemoteUserToGuest(th.Context, user)
|
||
require.Nil(t, err)
|
||
user, err = th.App.GetUser(user.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, "system_guest", user.Roles)
|
||
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, teamMember.SchemeUser)
|
||
assert.True(t, teamMember.SchemeGuest)
|
||
_, err = th.App.GetChannelMember(th.Context, th.BasicChannel.Id, user.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, teamMember.SchemeUser)
|
||
assert.True(t, teamMember.SchemeGuest)
|
||
})
|
||
|
||
t.Run("Must respect the current channels not removing defaults", func(t *testing.T) {
|
||
user := th.CreateUser(t)
|
||
require.Equal(t, "system_user", user.Roles)
|
||
th.LinkUserToTeam(t, user, th.BasicTeam)
|
||
teamMember, err := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
||
require.Nil(t, err)
|
||
require.True(t, teamMember.SchemeUser)
|
||
require.False(t, teamMember.SchemeGuest)
|
||
|
||
channelMember := th.AddUserToChannel(t, user, th.BasicChannel)
|
||
require.True(t, channelMember.SchemeUser)
|
||
require.False(t, channelMember.SchemeGuest)
|
||
|
||
channelMembers, err := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, user.Id)
|
||
require.Nil(t, err)
|
||
require.Len(t, channelMembers, 3)
|
||
|
||
err = th.App.DemoteUserToGuest(th.Context, user)
|
||
require.Nil(t, err)
|
||
user, err = th.App.GetUser(user.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, "system_guest", user.Roles)
|
||
teamMember, err = th.App.GetTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, teamMember.SchemeUser)
|
||
assert.True(t, teamMember.SchemeGuest)
|
||
_, err = th.App.GetChannelMember(th.Context, th.BasicChannel.Id, user.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, teamMember.SchemeUser)
|
||
assert.True(t, teamMember.SchemeGuest)
|
||
|
||
channelMembers, err = th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, user.Id)
|
||
require.Nil(t, err)
|
||
assert.Len(t, channelMembers, 3)
|
||
})
|
||
|
||
t.Run("Must be removed as team and channel admin", func(t *testing.T) {
|
||
user := th.CreateUser(t)
|
||
require.Equal(t, "system_user", user.Roles)
|
||
|
||
team := th.CreateTeam(t)
|
||
|
||
th.LinkUserToTeam(t, user, team)
|
||
_, appErr := th.App.UpdateTeamMemberRoles(th.Context, team.Id, user.Id, "team_user team_admin")
|
||
require.Nil(t, appErr)
|
||
|
||
teamMember, err := th.App.GetTeamMember(th.Context, team.Id, user.Id)
|
||
require.Nil(t, err)
|
||
require.True(t, teamMember.SchemeUser)
|
||
require.True(t, teamMember.SchemeAdmin)
|
||
require.False(t, teamMember.SchemeGuest)
|
||
|
||
channel := th.CreateChannel(t, team)
|
||
|
||
th.AddUserToChannel(t, user, channel)
|
||
_, appErr = th.App.UpdateChannelMemberSchemeRoles(th.Context, channel.Id, user.Id, false, true, true)
|
||
require.Nil(t, appErr)
|
||
|
||
channelMember, err := th.App.GetChannelMember(th.Context, channel.Id, user.Id)
|
||
assert.Nil(t, err)
|
||
assert.True(t, channelMember.SchemeUser)
|
||
assert.True(t, channelMember.SchemeAdmin)
|
||
assert.False(t, channelMember.SchemeGuest)
|
||
|
||
err = th.App.DemoteUserToGuest(th.Context, user)
|
||
require.Nil(t, err)
|
||
|
||
user, err = th.App.GetUser(user.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, "system_guest", user.Roles)
|
||
|
||
teamMember, err = th.App.GetTeamMember(th.Context, team.Id, user.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, teamMember.SchemeUser)
|
||
assert.False(t, teamMember.SchemeAdmin)
|
||
assert.True(t, teamMember.SchemeGuest)
|
||
|
||
channelMember, err = th.App.GetChannelMember(th.Context, channel.Id, user.Id)
|
||
assert.Nil(t, err)
|
||
assert.False(t, channelMember.SchemeUser)
|
||
assert.False(t, channelMember.SchemeAdmin)
|
||
assert.True(t, channelMember.SchemeGuest)
|
||
})
|
||
}
|
||
|
||
func TestDeactivateGuests(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
guest1 := th.CreateGuest(t)
|
||
guest2 := th.CreateGuest(t)
|
||
user := th.CreateUser(t)
|
||
|
||
err := th.App.DeactivateGuests(th.Context)
|
||
require.Nil(t, err)
|
||
|
||
guest1, err = th.App.GetUser(guest1.Id)
|
||
assert.Nil(t, err)
|
||
assert.NotEqual(t, int64(0), guest1.DeleteAt)
|
||
|
||
guest2, err = th.App.GetUser(guest2.Id)
|
||
assert.Nil(t, err)
|
||
assert.NotEqual(t, int64(0), guest2.DeleteAt)
|
||
|
||
user, err = th.App.GetUser(user.Id)
|
||
assert.Nil(t, err)
|
||
assert.Equal(t, int64(0), user.DeleteAt)
|
||
}
|
||
|
||
func TestUpdateUserRolesWithUser(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
// InitBasic is used to let the first CreateUser call not be
|
||
// a system_admin
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
// Create normal user.
|
||
user := th.CreateUser(t)
|
||
assert.Equal(t, user.Roles, model.SystemUserRoleId)
|
||
|
||
// Upgrade to sysadmin.
|
||
user, err := th.App.UpdateUserRolesWithUser(th.Context, user, model.SystemUserRoleId+" "+model.SystemAdminRoleId, false)
|
||
require.Nil(t, err)
|
||
assert.Equal(t, user.Roles, model.SystemUserRoleId+" "+model.SystemAdminRoleId)
|
||
|
||
// Test bad role.
|
||
_, err = th.App.UpdateUserRolesWithUser(th.Context, user, "does not exist", false)
|
||
require.NotNil(t, err)
|
||
|
||
// Test reset to User role
|
||
user, err = th.App.UpdateUserRolesWithUser(th.Context, user, model.SystemUserRoleId, false)
|
||
require.Nil(t, err)
|
||
assert.Equal(t, user.Roles, model.SystemUserRoleId)
|
||
}
|
||
|
||
func TestUpdateLastAdminUserRolesWithUser(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
// InitBasic is used to let the first CreateUser call not be
|
||
// a system_admin
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
t.Run("Cannot remove if only admin", func(t *testing.T) {
|
||
// Attempt to downgrade sysadmin.
|
||
user, appErr := th.App.UpdateUserRolesWithUser(th.Context, th.SystemAdminUser, model.SystemUserRoleId, false)
|
||
require.NotNil(t, appErr)
|
||
require.Nil(t, user)
|
||
})
|
||
|
||
t.Run("Cannot remove if only non-Bot admin", func(t *testing.T) {
|
||
bot := th.CreateBot(t)
|
||
user, appErr := th.App.UpdateUserRoles(th.Context, bot.UserId, model.SystemUserRoleId+" "+model.SystemAdminRoleId, false)
|
||
require.Nil(t, appErr)
|
||
require.NotNil(t, user)
|
||
|
||
// Attempt to downgrade sysadmin.
|
||
user, appErr = th.App.UpdateUserRolesWithUser(th.Context, th.SystemAdminUser, model.SystemUserRoleId, false)
|
||
require.NotNil(t, appErr)
|
||
require.Nil(t, user)
|
||
})
|
||
|
||
t.Run("Can remove if not only non-Bot admin", func(t *testing.T) {
|
||
systemAdminUser2 := th.CreateUser(t)
|
||
user, appErr := th.App.UpdateUserRoles(th.Context, systemAdminUser2.Id, model.SystemUserRoleId+" "+model.SystemAdminRoleId, false)
|
||
require.Nil(t, appErr)
|
||
require.NotNil(t, user)
|
||
|
||
// Attempt to downgrade sysadmin.
|
||
user, appErr = th.App.UpdateUserRolesWithUser(th.Context, th.SystemAdminUser, model.SystemUserRoleId, false)
|
||
require.Nil(t, appErr)
|
||
require.NotNil(t, user)
|
||
})
|
||
}
|
||
|
||
func TestDeactivateMfa(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
t.Run("MFA is disabled", func(t *testing.T) {
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.ServiceSettings.EnableMultifactorAuthentication = false
|
||
})
|
||
|
||
user := th.BasicUser
|
||
err := th.App.DeactivateMfa(user.Id)
|
||
require.Nil(t, err)
|
||
})
|
||
}
|
||
|
||
func TestPatchUser(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
testUser := th.CreateUser(t)
|
||
defer func() {
|
||
appErr := th.App.PermanentDeleteUser(th.Context, testUser)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
|
||
t.Run("Patch with a username already exists", func(t *testing.T) {
|
||
_, err := th.App.PatchUser(th.Context, testUser.Id, &model.UserPatch{
|
||
Username: model.NewPointer(th.BasicUser.Username),
|
||
}, true)
|
||
|
||
require.NotNil(t, err)
|
||
require.Equal(t, "app.user.save.username_exists.app_error", err.Id)
|
||
})
|
||
|
||
t.Run("Patch with a email already exists", func(t *testing.T) {
|
||
_, err := th.App.PatchUser(th.Context, testUser.Id, &model.UserPatch{
|
||
Email: model.NewPointer(th.BasicUser.Email),
|
||
}, true)
|
||
|
||
require.NotNil(t, err)
|
||
require.Equal(t, "app.user.save.email_exists.app_error", err.Id)
|
||
})
|
||
|
||
t.Run("Patch username with a new username", func(t *testing.T) {
|
||
u, err := th.App.PatchUser(th.Context, testUser.Id, &model.UserPatch{
|
||
Username: model.NewPointer(model.NewUsername()),
|
||
}, true)
|
||
|
||
require.Nil(t, err)
|
||
require.Empty(t, u.Password)
|
||
})
|
||
}
|
||
|
||
func TestUpdateThreadReadForUser(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
t.Run("Ensure thread membership exists before updating read", func(t *testing.T) {
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.ServiceSettings.ThreadAutoFollow = true
|
||
*cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn
|
||
})
|
||
|
||
rootPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, model.CreatePostFlags{})
|
||
require.Nil(t, appErr)
|
||
replyPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{RootId: rootPost.Id, UserId: th.BasicUser2.Id, CreateAt: model.GetMillis(), ChannelId: th.BasicChannel.Id, Message: "hi"}, th.BasicChannel, model.CreatePostFlags{})
|
||
require.Nil(t, appErr)
|
||
threads, appErr := th.App.GetThreadsForUser(th.Context, th.BasicUser.Id, th.BasicTeam.Id, model.GetUserThreadsOpts{})
|
||
require.Nil(t, appErr)
|
||
require.Zero(t, threads.Total)
|
||
|
||
_, appErr = th.App.UpdateThreadReadForUser(th.Context, "currentSessionId", th.BasicUser.Id, th.BasicChannel.TeamId, rootPost.Id, replyPost.CreateAt)
|
||
require.NotNil(t, appErr)
|
||
|
||
_, err := th.App.Srv().Store().Thread().MaintainMembership(th.BasicUser.Id, rootPost.Id, store.ThreadMembershipOpts{Following: true, UpdateFollowing: true})
|
||
require.NoError(t, err)
|
||
|
||
_, appErr = th.App.UpdateThreadReadForUser(th.Context, "currentSessionId", th.BasicUser.Id, th.BasicChannel.TeamId, rootPost.Id, replyPost.CreateAt)
|
||
require.Nil(t, appErr)
|
||
})
|
||
}
|
||
|
||
func TestCreateUserWithInitialPreferences(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
t.Run("successfully create a user with initial tutorial and recommended steps preferences", func(t *testing.T) {
|
||
th.ConfigStore.SetReadOnlyFF(false)
|
||
defer th.ConfigStore.SetReadOnlyFF(true)
|
||
|
||
testUser := th.CreateUser(t)
|
||
defer func() {
|
||
appErr := th.App.PermanentDeleteUser(th.Context, testUser)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
|
||
tutorialStepPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(th.Context, testUser.Id, model.PreferenceCategoryTutorialSteps, testUser.Id)
|
||
require.Nil(t, appErr)
|
||
assert.Equal(t, testUser.Id, tutorialStepPref.Name)
|
||
|
||
recommendedNextStepsPref, appErr := th.App.GetPreferenceByCategoryForUser(th.Context, testUser.Id, model.PreferenceRecommendedNextSteps)
|
||
require.Nil(t, appErr)
|
||
assert.Equal(t, model.PreferenceRecommendedNextSteps, recommendedNextStepsPref[0].Category)
|
||
assert.Equal(t, "hide", recommendedNextStepsPref[0].Name)
|
||
assert.Equal(t, "false", recommendedNextStepsPref[0].Value)
|
||
|
||
gmASdmNoticeViewedPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(th.Context, testUser.Id, model.PreferenceCategorySystemNotice, "GMasDM")
|
||
require.Nil(t, appErr)
|
||
assert.Equal(t, "GMasDM", gmASdmNoticeViewedPref.Name)
|
||
assert.Equal(t, "true", gmASdmNoticeViewedPref.Value)
|
||
})
|
||
|
||
t.Run("successfully create a guest user with initial tutorial and recommended steps preferences", func(t *testing.T) {
|
||
th.Server.platform.SetConfigReadOnlyFF(false)
|
||
defer th.Server.platform.SetConfigReadOnlyFF(true)
|
||
testUser := th.CreateGuest(t)
|
||
defer func() {
|
||
appErr := th.App.PermanentDeleteUser(th.Context, testUser)
|
||
require.Nil(t, appErr)
|
||
}()
|
||
|
||
tutorialStepPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(th.Context, testUser.Id, model.PreferenceCategoryTutorialSteps, testUser.Id)
|
||
require.Nil(t, appErr)
|
||
assert.Equal(t, testUser.Id, tutorialStepPref.Name)
|
||
|
||
recommendedNextStepsPref, appErr := th.App.GetPreferenceByCategoryForUser(th.Context, testUser.Id, model.PreferenceRecommendedNextSteps)
|
||
require.Nil(t, appErr)
|
||
assert.Equal(t, model.PreferenceRecommendedNextSteps, recommendedNextStepsPref[0].Category)
|
||
assert.Equal(t, "hide", recommendedNextStepsPref[0].Name)
|
||
assert.Equal(t, "false", recommendedNextStepsPref[0].Value)
|
||
|
||
gmASdmNoticeViewedPref, appErr := th.App.GetPreferenceByCategoryAndNameForUser(th.Context, testUser.Id, model.PreferenceCategorySystemNotice, "GMasDM")
|
||
require.Nil(t, appErr)
|
||
assert.Equal(t, "GMasDM", gmASdmNoticeViewedPref.Name)
|
||
assert.Equal(t, "true", gmASdmNoticeViewedPref.Value)
|
||
})
|
||
}
|
||
|
||
func TestSendSubscriptionHistoryEvent(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
cloudProduct := &model.Product{
|
||
ID: "prod_test1",
|
||
Name: "name1",
|
||
Description: "description1",
|
||
PricePerSeat: 1000,
|
||
SKU: "sku1",
|
||
PriceID: "price_id1",
|
||
Family: "family1",
|
||
RecurringInterval: "year",
|
||
BillingScheme: "billing_scheme1",
|
||
CrossSellsTo: "prod_test2",
|
||
}
|
||
|
||
subscription := &model.Subscription{
|
||
ID: "MySubscriptionID",
|
||
CustomerID: "MyCustomer",
|
||
ProductID: "SomeProductId",
|
||
AddOns: []string{},
|
||
StartAt: 1000000000,
|
||
EndAt: 2000000000,
|
||
CreateAt: 1000000000,
|
||
Seats: 10,
|
||
DNS: "some.dns.server",
|
||
}
|
||
|
||
subscriptionHistory := &model.SubscriptionHistory{
|
||
ID: "sub_history",
|
||
SubscriptionID: "MySubscriptionID",
|
||
Seats: 10,
|
||
CreateAt: 1000000000,
|
||
}
|
||
|
||
t.Run("Should not create SubscriptionHistoryEvent if the license is not cloud", func(t *testing.T) {
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
th.App.Srv().SetLicense(model.NewTestLicense(""))
|
||
|
||
userID := "123"
|
||
|
||
subscriptionHistoryEvent, err := th.App.SendSubscriptionHistoryEvent(userID)
|
||
require.NoError(t, err)
|
||
require.Nil(t, subscriptionHistoryEvent)
|
||
})
|
||
|
||
t.Run("Should create SubscriptionHistoryEvent if the license is cloud and the product is yearly", func(t *testing.T) {
|
||
th := SetupWithStoreMock(t)
|
||
|
||
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
||
|
||
cloud := mocks.CloudInterface{}
|
||
|
||
// mock the cloud functions
|
||
cloud.Mock.On("GetSubscription", mock.Anything).Return(subscription, nil)
|
||
cloud.Mock.On("GetCloudProduct", mock.Anything, mock.Anything).Return(cloudProduct, nil)
|
||
cloud.Mock.On("CreateOrUpdateSubscriptionHistoryEvent", mock.Anything, mock.Anything).Return(subscriptionHistory, nil)
|
||
|
||
cloudImpl := th.App.Srv().Cloud
|
||
defer func() {
|
||
th.App.Srv().Cloud = cloudImpl
|
||
}()
|
||
th.App.Srv().Cloud = &cloud
|
||
|
||
// Mock to get the user count
|
||
mockStore := th.App.Srv().Store().(*storemocks.Store)
|
||
mockUserStore := storemocks.UserStore{}
|
||
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
|
||
|
||
mockStore.On("User").Return(&mockUserStore)
|
||
|
||
userID := "123"
|
||
|
||
subscriptionHistoryEvent, err := th.App.SendSubscriptionHistoryEvent(userID)
|
||
require.NoError(t, err)
|
||
require.Equal(t, subscription.ID, subscriptionHistoryEvent.SubscriptionID, "subscription ID doesn't match")
|
||
require.Equal(t, 10, subscriptionHistoryEvent.Seats, "Number of seats doesn't match")
|
||
})
|
||
}
|
||
|
||
func TestGetUsersForReporting(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
t.Run("should throw error on invalid date range", func(t *testing.T) {
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
userReports, err := th.App.GetUsersForReporting(&model.UserReportOptions{
|
||
ReportingBaseOptions: model.ReportingBaseOptions{
|
||
SortColumn: "Username",
|
||
PageSize: 50,
|
||
StartAt: 1000,
|
||
EndAt: 500,
|
||
},
|
||
})
|
||
require.NotNil(t, err)
|
||
require.Nil(t, userReports)
|
||
})
|
||
|
||
t.Run("should throw error on bad sort column", func(t *testing.T) {
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
userReports, err := th.App.GetUsersForReporting(&model.UserReportOptions{
|
||
ReportingBaseOptions: model.ReportingBaseOptions{
|
||
SortColumn: "FakeColumn",
|
||
PageSize: 50,
|
||
},
|
||
})
|
||
require.NotNil(t, err)
|
||
require.Nil(t, userReports)
|
||
})
|
||
|
||
t.Run("should return some formatted reporting data", func(t *testing.T) {
|
||
th := SetupWithStoreMock(t)
|
||
|
||
// Mock to get the user count
|
||
mockStore := th.App.Srv().Store().(*storemocks.Store)
|
||
mockUserStore := storemocks.UserStore{}
|
||
mockUserStore.On("GetUserReport",
|
||
mock.Anything,
|
||
mock.Anything,
|
||
mock.Anything,
|
||
mock.Anything,
|
||
mock.Anything,
|
||
mock.Anything,
|
||
mock.Anything,
|
||
mock.Anything,
|
||
mock.Anything,
|
||
mock.Anything,
|
||
mock.Anything,
|
||
mock.Anything,
|
||
).Return([]*model.UserReportQuery{
|
||
{
|
||
User: model.User{
|
||
Id: "some-id",
|
||
CreateAt: 1000,
|
||
FirstName: "Bob",
|
||
LastName: "Bobson",
|
||
LastLogin: 1500,
|
||
},
|
||
},
|
||
}, nil)
|
||
|
||
mockStore.On("User").Return(&mockUserStore)
|
||
|
||
userReports, err := th.App.GetUsersForReporting(&model.UserReportOptions{
|
||
ReportingBaseOptions: model.ReportingBaseOptions{
|
||
SortColumn: "Username",
|
||
PageSize: 50,
|
||
},
|
||
})
|
||
require.Nil(t, err)
|
||
require.NotNil(t, userReports)
|
||
})
|
||
}
|
||
|
||
// Helper functions for remote user testing
|
||
func setupRemoteClusterTest(t *testing.T) (*TestHelper, store.Store) {
|
||
th := setupSharedChannels(t).InitBasic(t)
|
||
|
||
// Enable SharedChannelsDMs feature flag
|
||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.EnableSharedChannelsDMs = true })
|
||
|
||
return th, th.App.Srv().Store()
|
||
}
|
||
|
||
func createTestRemoteCluster(t *testing.T, th *TestHelper, ss store.Store, name, siteURL string, confirmed bool) *model.RemoteCluster {
|
||
cluster := &model.RemoteCluster{
|
||
RemoteId: model.NewId(),
|
||
Name: name,
|
||
SiteURL: siteURL,
|
||
CreateAt: model.GetMillis(),
|
||
LastPingAt: model.GetMillis(),
|
||
Token: model.NewId(),
|
||
CreatorId: th.BasicUser.Id,
|
||
}
|
||
if confirmed {
|
||
cluster.RemoteToken = model.NewId()
|
||
}
|
||
savedCluster, err := ss.RemoteCluster().Save(cluster)
|
||
require.NoError(t, err)
|
||
return savedCluster
|
||
}
|
||
|
||
func createRemoteUser(t *testing.T, th *TestHelper, remoteCluster *model.RemoteCluster) *model.User {
|
||
user := th.CreateUser(t)
|
||
user.RemoteId = &remoteCluster.RemoteId
|
||
updatedUser, appErr := th.App.UpdateUser(th.Context, user, false)
|
||
require.Nil(t, appErr)
|
||
return updatedUser
|
||
}
|
||
|
||
func ensureRemoteClusterConnected(t *testing.T, ss store.Store, cluster *model.RemoteCluster, connected bool) {
|
||
if connected {
|
||
cluster.SiteURL = "https://example.com"
|
||
cluster.RemoteToken = model.NewId()
|
||
cluster.LastPingAt = model.GetMillis()
|
||
} else {
|
||
cluster.SiteURL = model.SiteURLPending + "example.com"
|
||
cluster.RemoteToken = ""
|
||
}
|
||
_, err := ss.RemoteCluster().Update(cluster)
|
||
require.NoError(t, err)
|
||
}
|
||
|
||
// TestRemoteUserDirectChannelCreation tests direct channel creation with remote users
|
||
func TestRemoteUserDirectChannelCreation(t *testing.T) {
|
||
th, ss := setupRemoteClusterTest(t)
|
||
|
||
connectedRC := createTestRemoteCluster(t, th, ss, "connected-cluster", "https://example-connected.com", true)
|
||
|
||
user1 := createRemoteUser(t, th, connectedRC)
|
||
|
||
t.Run("Can create DM with user from connected remote", func(t *testing.T) {
|
||
ensureRemoteClusterConnected(t, ss, connectedRC, true)
|
||
|
||
scs := th.App.Srv().GetSharedChannelSyncService()
|
||
service, ok := scs.(*sharedchannel.Service)
|
||
require.True(t, ok)
|
||
require.True(t, service.IsRemoteClusterDirectlyConnected(connectedRC.RemoteId))
|
||
|
||
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user1.Id)
|
||
assert.NotNil(t, channel)
|
||
assert.Nil(t, appErr)
|
||
assert.Equal(t, model.ChannelTypeDirect, channel.Type)
|
||
})
|
||
}
|
||
|
||
func TestAuthenticateUserForGuestMagicLink(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
// Enable guest accounts for guest magic link
|
||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||
*cfg.GuestAccountsSettings.Enable = true
|
||
})
|
||
|
||
t.Run("valid guest magic link token creates guest user successfully", func(t *testing.T) {
|
||
// Create guest magic link invitation token
|
||
email := strings.ToLower(model.NewId()) + "@example.com"
|
||
channel2 := th.CreateChannel(t, th.BasicTeam)
|
||
tokenData := map[string]string{
|
||
"teamId": th.BasicTeam.Id,
|
||
"channels": th.BasicChannel.Id + " " + channel2.Id,
|
||
"email": email,
|
||
"guest": "true",
|
||
"senderId": th.BasicUser.Id,
|
||
}
|
||
token := model.NewToken(
|
||
model.TokenTypeGuestMagicLinkInvitation,
|
||
model.MapToJSON(tokenData),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
|
||
// Authenticate with guest magic link token
|
||
user, err := th.App.AuthenticateUserForGuestMagicLink(th.Context, token.Token)
|
||
require.Nil(t, err, "Should create guest user successfully")
|
||
require.NotNil(t, user)
|
||
|
||
// Verify user is a guest
|
||
assert.True(t, user.IsGuest(), "User should be a guest")
|
||
assert.Equal(t, email, user.Email)
|
||
assert.True(t, user.EmailVerified, "Email should be verified")
|
||
assert.NotEmpty(t, user.Username, "Username should be generated")
|
||
|
||
// Verify token was deleted (single-use)
|
||
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
|
||
require.Error(t, nErr, "Token should be deleted after use")
|
||
|
||
// Verify user was added to team
|
||
_, teamErr := th.App.GetTeamMember(th.Context, th.BasicTeam.Id, user.Id)
|
||
require.Nil(t, teamErr, "User should be added to team")
|
||
|
||
// Verify user was added to specified channels
|
||
members, chanErr := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, user.Id)
|
||
require.Nil(t, chanErr)
|
||
// Guests are only added to the channels specified in the token (BasicChannel and channel2)
|
||
// They do not automatically get added to Town Square like regular users
|
||
assert.GreaterOrEqual(t, len(members), 2, "User should be in at least 2 channels")
|
||
|
||
// Cleanup
|
||
appErr := th.App.PermanentDeleteUser(th.Context, user)
|
||
require.Nil(t, appErr)
|
||
})
|
||
|
||
t.Run("invalid token returns error", func(t *testing.T) {
|
||
user, err := th.App.AuthenticateUserForGuestMagicLink(th.Context, "invalid-token-123")
|
||
require.NotNil(t, err, "Should fail on invalid token")
|
||
require.Nil(t, user)
|
||
assert.Equal(t, "api.user.guest_magic_link.invalid_token.app_error", err.Id)
|
||
})
|
||
|
||
t.Run("expired token returns error", func(t *testing.T) {
|
||
email := strings.ToLower(model.NewId()) + "@example.com"
|
||
tokenData := map[string]string{
|
||
"teamId": th.BasicTeam.Id,
|
||
"channels": th.BasicChannel.Id,
|
||
"email": email,
|
||
"guest": "true",
|
||
"senderId": th.BasicUser.Id,
|
||
}
|
||
token := model.NewToken(
|
||
model.TokenTypeGuestMagicLinkInvitation,
|
||
model.MapToJSON(tokenData),
|
||
)
|
||
// Set token to be expired (48 hours + 1 millisecond old)
|
||
token.CreateAt = model.GetMillis() - model.InvitationExpiryTime - 1
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
|
||
user, err := th.App.AuthenticateUserForGuestMagicLink(th.Context, token.Token)
|
||
require.NotNil(t, err, "Should fail on expired token")
|
||
require.Nil(t, user)
|
||
assert.Equal(t, "api.user.guest_magic_link.expired_token.app_error", err.Id)
|
||
|
||
// Verify token was deleted
|
||
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
|
||
require.Error(t, nErr, "Expired token should be deleted")
|
||
})
|
||
|
||
t.Run("wrong token type returns error", func(t *testing.T) {
|
||
// Create token with wrong type
|
||
token := model.NewToken(
|
||
model.TokenTypeTeamInvitation, // Wrong type - should be TokenTypeGuestMagicLinkInvitation
|
||
model.MapToJSON(map[string]string{"teamId": th.BasicTeam.Id}),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
defer func() {
|
||
_ = th.App.Srv().Store().Token().Delete(token.Token)
|
||
}()
|
||
|
||
user, err := th.App.AuthenticateUserForGuestMagicLink(th.Context, token.Token)
|
||
require.NotNil(t, err, "Should fail on wrong token type")
|
||
require.Nil(t, user)
|
||
assert.Equal(t, "api.user.guest_magic_link.invalid_token.app_error", err.Id)
|
||
|
||
// Verify token was NOT consumed (wrong type should not delete it)
|
||
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
|
||
require.NoError(t, nErr, "Token with wrong type should still exist")
|
||
})
|
||
|
||
t.Run("user already exists returns generic error", func(t *testing.T) {
|
||
// Use existing user's email
|
||
email := th.BasicUser.Email
|
||
tokenData := map[string]string{
|
||
"teamId": th.BasicTeam.Id,
|
||
"channels": th.BasicChannel.Id,
|
||
"email": email,
|
||
"guest": "true",
|
||
"senderId": th.BasicUser.Id,
|
||
}
|
||
token := model.NewToken(
|
||
model.TokenTypeGuestMagicLinkInvitation,
|
||
model.MapToJSON(tokenData),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
|
||
user, err := th.App.AuthenticateUserForGuestMagicLink(th.Context, token.Token)
|
||
require.NotNil(t, err, "Should fail when user already exists")
|
||
require.Nil(t, user)
|
||
// Returns generic error to prevent user enumeration
|
||
assert.Equal(t, "api.user.guest_magic_link.invalid_token.app_error", err.Id)
|
||
|
||
// Verify token was deleted even on error
|
||
_, nErr := th.App.Srv().Store().Token().GetByToken(token.Token)
|
||
require.Error(t, nErr, "Token should be deleted even when user exists")
|
||
})
|
||
|
||
t.Run("username is generated from email and made unique", func(t *testing.T) {
|
||
// Create a user with a common username
|
||
email1 := "john.doe@example.com"
|
||
tokenData1 := map[string]string{
|
||
"teamId": th.BasicTeam.Id,
|
||
"channels": th.BasicChannel.Id,
|
||
"email": email1,
|
||
"guest": "true",
|
||
"senderId": th.BasicUser.Id,
|
||
}
|
||
token1 := model.NewToken(
|
||
model.TokenTypeGuestMagicLinkInvitation,
|
||
model.MapToJSON(tokenData1),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token1))
|
||
|
||
user1, err1 := th.App.AuthenticateUserForGuestMagicLink(th.Context, token1.Token)
|
||
require.Nil(t, err1)
|
||
require.NotNil(t, user1)
|
||
assert.Contains(t, user1.Username, "john", "Username should be derived from email")
|
||
|
||
// Create another user with similar email - username should be made unique
|
||
email2 := "john.smith@example.com"
|
||
tokenData2 := map[string]string{
|
||
"teamId": th.BasicTeam.Id,
|
||
"channels": th.BasicChannel.Id,
|
||
"email": email2,
|
||
"guest": "true",
|
||
"senderId": th.BasicUser.Id,
|
||
}
|
||
token2 := model.NewToken(
|
||
model.TokenTypeGuestMagicLinkInvitation,
|
||
model.MapToJSON(tokenData2),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token2))
|
||
|
||
user2, err2 := th.App.AuthenticateUserForGuestMagicLink(th.Context, token2.Token)
|
||
require.Nil(t, err2)
|
||
require.NotNil(t, user2)
|
||
|
||
// Usernames should be different (uniqueness enforced)
|
||
assert.NotEqual(t, user1.Username, user2.Username, "Usernames should be unique")
|
||
|
||
// Cleanup
|
||
require.Nil(t, th.App.PermanentDeleteUser(th.Context, user1))
|
||
require.Nil(t, th.App.PermanentDeleteUser(th.Context, user2))
|
||
})
|
||
|
||
t.Run("invalid team id in token returns error", func(t *testing.T) {
|
||
email := strings.ToLower(model.NewId()) + "@example.com"
|
||
tokenData := map[string]string{
|
||
"teamId": model.NewId(), // Non-existent team
|
||
"channels": th.BasicChannel.Id,
|
||
"email": email,
|
||
"guest": "true",
|
||
"senderId": th.BasicUser.Id,
|
||
}
|
||
token := model.NewToken(
|
||
model.TokenTypeGuestMagicLinkInvitation,
|
||
model.MapToJSON(tokenData),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
|
||
user, err := th.App.AuthenticateUserForGuestMagicLink(th.Context, token.Token)
|
||
require.NotNil(t, err, "Should fail on invalid team id")
|
||
require.Nil(t, user)
|
||
|
||
// User should have been created but team join failed
|
||
// Check if user was created
|
||
createdUser, getUserErr := th.App.GetUserByEmail(email)
|
||
if getUserErr == nil {
|
||
// Cleanup if user was created
|
||
require.Nil(t, th.App.PermanentDeleteUser(th.Context, createdUser))
|
||
}
|
||
})
|
||
|
||
t.Run("channels filtered by sender permissions", func(t *testing.T) {
|
||
// Create a private channel
|
||
privateChannel := th.CreatePrivateChannel(t, th.BasicTeam)
|
||
|
||
email := strings.ToLower(model.NewId()) + "@example.com"
|
||
// Include both public and private channel
|
||
tokenData := map[string]string{
|
||
"teamId": th.BasicTeam.Id,
|
||
"channels": th.BasicChannel.Id + " " + privateChannel.Id,
|
||
"email": email,
|
||
"guest": "true",
|
||
"senderId": th.BasicUser.Id, // BasicUser should have access to private channel
|
||
}
|
||
token := model.NewToken(
|
||
model.TokenTypeGuestMagicLinkInvitation,
|
||
model.MapToJSON(tokenData),
|
||
)
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
|
||
user, err := th.App.AuthenticateUserForGuestMagicLink(th.Context, token.Token)
|
||
require.Nil(t, err)
|
||
require.NotNil(t, user)
|
||
|
||
// Verify user was added to both channels
|
||
members, chanErr := th.App.GetChannelMembersForUser(th.Context, th.BasicTeam.Id, user.Id)
|
||
require.Nil(t, chanErr)
|
||
|
||
// Check that user is in the specified channels
|
||
channelIds := make(map[string]bool)
|
||
for _, member := range members {
|
||
channelIds[member.ChannelId] = true
|
||
}
|
||
assert.True(t, channelIds[th.BasicChannel.Id], "User should be in basic channel")
|
||
assert.True(t, channelIds[privateChannel.Id], "User should be in private channel")
|
||
|
||
// Cleanup
|
||
require.Nil(t, th.App.PermanentDeleteUser(th.Context, user))
|
||
})
|
||
}
|
||
|
||
func TestConsumeTokenOnce(t *testing.T) {
|
||
mainHelper.Parallel(t)
|
||
th := Setup(t).InitBasic(t)
|
||
|
||
t.Run("successfully consume valid token", func(t *testing.T) {
|
||
token := model.NewToken(model.TokenTypeOAuth, "extra-data")
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
|
||
consumedToken, appErr := th.App.ConsumeTokenOnce(model.TokenTypeOAuth, token.Token)
|
||
require.Nil(t, appErr)
|
||
require.NotNil(t, consumedToken)
|
||
assert.Equal(t, token.Token, consumedToken.Token)
|
||
assert.Equal(t, model.TokenTypeOAuth, consumedToken.Type)
|
||
assert.Equal(t, "extra-data", consumedToken.Extra)
|
||
|
||
_, err := th.App.Srv().Store().Token().GetByToken(token.Token)
|
||
require.Error(t, err)
|
||
})
|
||
|
||
t.Run("token not found returns 404", func(t *testing.T) {
|
||
nonExistentToken := model.NewRandomString(model.TokenSize)
|
||
|
||
consumedToken, appErr := th.App.ConsumeTokenOnce(model.TokenTypeOAuth, nonExistentToken)
|
||
require.NotNil(t, appErr)
|
||
require.Nil(t, consumedToken)
|
||
assert.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
||
assert.Equal(t, "ConsumeTokenOnce", appErr.Where)
|
||
})
|
||
|
||
t.Run("wrong token type returns not found", func(t *testing.T) {
|
||
token := model.NewToken(model.TokenTypeOAuth, "extra-data")
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
defer func() {
|
||
_ = th.App.Srv().Store().Token().Delete(token.Token)
|
||
}()
|
||
|
||
consumedToken, appErr := th.App.ConsumeTokenOnce(model.TokenTypeSaml, token.Token)
|
||
require.NotNil(t, appErr)
|
||
require.Nil(t, consumedToken)
|
||
assert.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
||
|
||
_, err := th.App.Srv().Store().Token().GetByToken(token.Token)
|
||
require.NoError(t, err)
|
||
})
|
||
|
||
t.Run("token can only be consumed once", func(t *testing.T) {
|
||
token := model.NewToken(model.TokenTypeSSOCodeExchange, "extra-data")
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
|
||
consumedToken1, appErr := th.App.ConsumeTokenOnce(model.TokenTypeSSOCodeExchange, token.Token)
|
||
require.Nil(t, appErr)
|
||
require.NotNil(t, consumedToken1)
|
||
|
||
consumedToken2, appErr := th.App.ConsumeTokenOnce(model.TokenTypeSSOCodeExchange, token.Token)
|
||
require.NotNil(t, appErr)
|
||
require.Nil(t, consumedToken2)
|
||
assert.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
||
})
|
||
|
||
t.Run("empty token string returns not found", func(t *testing.T) {
|
||
consumedToken, appErr := th.App.ConsumeTokenOnce(model.TokenTypeOAuth, "")
|
||
require.NotNil(t, appErr)
|
||
require.Nil(t, consumedToken)
|
||
assert.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
||
})
|
||
|
||
t.Run("empty token type returns not found", func(t *testing.T) {
|
||
token := model.NewToken(model.TokenTypeOAuth, "extra-data")
|
||
require.NoError(t, th.App.Srv().Store().Token().Save(token))
|
||
defer func() {
|
||
_ = th.App.Srv().Store().Token().Delete(token.Token)
|
||
}()
|
||
|
||
consumedToken, appErr := th.App.ConsumeTokenOnce("", token.Token)
|
||
require.NotNil(t, appErr)
|
||
require.Nil(t, consumedToken)
|
||
assert.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
||
})
|
||
}
|