From 347ee1d205c95f5fd766e206cc65bfb9782a2623 Mon Sep 17 00:00:00 2001 From: Gabe Van Engel Date: Tue, 28 Aug 2018 08:06:57 -0700 Subject: MM-11327: Restrict Teams by Email (#9142) * Check a team's AllowedDomains setting before adding users to the team. * Updated AddUser tests to validate AllowedDomains restriction. * Updated variable name to match convention. * Removed AllowedDomains from team sanitization. * Update AppError's Where to match the calling function. * Added tests for user matching allowedDomains, and multi domain values of allowedDomains. * Added test to make sure we block users who have a subdomain of a whitelisted domain. * Revert "Removed AllowedDomains from team sanitization." This reverts commit 17c2afea584da40c7d769787ae86408e9700510c. * Update sanitization tests to include dockerhost, now that we enforce AllowedDomains. * Added tests to verify the interplay between the global and per team domain restrictions. * Validate AllowedDomains property against RestrictCreationToDomains before updating a team. * Remove team.AllowedDomains from sanitization. * Add i18n string for the team allowed domains restriction app error. --- api4/team_test.go | 113 ++++++++++++++++------------------- app/team.go | 86 ++++++++++++++++++--------- app/team_test.go | 173 +++++++++++++++++++++++++++++++++++++++++++++++------- i18n/en.json | 8 +++ model/team.go | 6 +- 5 files changed, 273 insertions(+), 113 deletions(-) diff --git a/api4/team_test.go b/api4/team_test.go index fc49b794f..468b9451d 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -96,15 +96,13 @@ func TestCreateTeamSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", } rteam, resp := th.Client.CreateTeam(team) CheckNoError(t, resp) if rteam.Email == "" { t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } }) @@ -114,15 +112,13 @@ func TestCreateTeamSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", } rteam, resp := th.SystemAdminClient.CreateTeam(team) CheckNoError(t, resp) if rteam.Email == "" { t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } }) } @@ -183,7 +179,7 @@ func TestGetTeamSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", }) CheckNoError(t, resp) @@ -197,8 +193,6 @@ func TestGetTeamSanitization(t *testing.T) { CheckNoError(t, resp) if rteam.Email != "" { t.Fatal("should've sanitized email") - } else if rteam.AllowedDomains != "" { - t.Fatal("should've sanitized allowed domains") } }) @@ -207,8 +201,6 @@ func TestGetTeamSanitization(t *testing.T) { CheckNoError(t, resp) if rteam.Email == "" { t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } }) @@ -217,8 +209,6 @@ func TestGetTeamSanitization(t *testing.T) { CheckNoError(t, resp) if rteam.Email == "" { t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } }) } @@ -364,7 +354,7 @@ func TestUpdateTeamSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", }) CheckNoError(t, resp) @@ -375,8 +365,6 @@ func TestUpdateTeamSanitization(t *testing.T) { CheckNoError(t, resp) if rteam.Email == "" { t.Fatal("should not have sanitized email for admin") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } }) @@ -385,8 +373,6 @@ func TestUpdateTeamSanitization(t *testing.T) { CheckNoError(t, resp) if rteam.Email == "" { t.Fatal("should not have sanitized email for admin") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } }) } @@ -463,7 +449,7 @@ func TestPatchTeamSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", }) CheckNoError(t, resp) @@ -474,8 +460,6 @@ func TestPatchTeamSanitization(t *testing.T) { CheckNoError(t, resp) if rteam.Email == "" { t.Fatal("should not have sanitized email for admin") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } }) @@ -484,8 +468,6 @@ func TestPatchTeamSanitization(t *testing.T) { CheckNoError(t, resp) if rteam.Email == "" { t.Fatal("should not have sanitized email for admin") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } }) } @@ -655,7 +637,7 @@ func TestGetAllTeamsSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", AllowOpenInvite: true, }) CheckNoError(t, resp) @@ -664,7 +646,7 @@ func TestGetAllTeamsSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", AllowOpenInvite: true, }) CheckNoError(t, resp) @@ -682,15 +664,11 @@ func TestGetAllTeamsSanitization(t *testing.T) { teamFound = true if rteam.Email == "" { t.Fatal("should not have sanitized email for team admin") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains for team admin") } } else if rteam.Id == team2.Id { team2Found = true if rteam.Email != "" { t.Fatal("should've sanitized email for non-admin") - } else if rteam.AllowedDomains != "" { - t.Fatal("should've sanitized allowed domains for non-admin") } } } @@ -710,8 +688,6 @@ func TestGetAllTeamsSanitization(t *testing.T) { if rteam.Email == "" { t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } } }) @@ -773,7 +749,7 @@ func TestGetTeamByNameSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", }) CheckNoError(t, resp) @@ -787,8 +763,6 @@ func TestGetTeamByNameSanitization(t *testing.T) { CheckNoError(t, resp) if rteam.Email != "" { t.Fatal("should've sanitized email") - } else if rteam.AllowedDomains != "" { - t.Fatal("should've sanitized allowed domains") } }) @@ -797,8 +771,6 @@ func TestGetTeamByNameSanitization(t *testing.T) { CheckNoError(t, resp) if rteam.Email == "" { t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } }) @@ -807,8 +779,6 @@ func TestGetTeamByNameSanitization(t *testing.T) { CheckNoError(t, resp) if rteam.Email == "" { t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } }) } @@ -904,7 +874,7 @@ func TestSearchAllTeamsSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", }) CheckNoError(t, resp) team2, resp := th.Client.CreateTeam(&model.Team{ @@ -912,7 +882,7 @@ func TestSearchAllTeamsSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", }) CheckNoError(t, resp) @@ -955,8 +925,6 @@ func TestSearchAllTeamsSanitization(t *testing.T) { if rteam.Id == team.Id || rteam.Id == team2.Id || rteam.Id == th.BasicTeam.Id { if rteam.Email == "" { t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } } } @@ -968,8 +936,6 @@ func TestSearchAllTeamsSanitization(t *testing.T) { for _, rteam := range rteams { if rteam.Email == "" { t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } } }) @@ -1026,7 +992,7 @@ func TestGetTeamsForUserSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", }) CheckNoError(t, resp) team2, resp := th.Client.CreateTeam(&model.Team{ @@ -1034,7 +1000,7 @@ func TestGetTeamsForUserSanitization(t *testing.T) { Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, - AllowedDomains: "simulator.amazonses.com", + AllowedDomains: "simulator.amazonses.com,dockerhost", }) CheckNoError(t, resp) @@ -1054,8 +1020,6 @@ func TestGetTeamsForUserSanitization(t *testing.T) { if rteam.Email != "" { t.Fatal("should've sanitized email") - } else if rteam.AllowedDomains != "" { - t.Fatal("should've sanitized allowed domains") } } }) @@ -1070,8 +1034,6 @@ func TestGetTeamsForUserSanitization(t *testing.T) { if rteam.Email == "" { t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } } }) @@ -1086,8 +1048,6 @@ func TestGetTeamsForUserSanitization(t *testing.T) { if rteam.Email == "" { t.Fatal("should not have sanitized email") - } else if rteam.AllowedDomains == "" { - t.Fatal("should not have sanitized allowed domains") } } }) @@ -1993,17 +1953,48 @@ func TestInviteUsersToTeam(t *testing.T) { } } - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.RestrictCreationToDomains = "@example.com" }) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.RestrictCreationToDomains = "@global.com,@common.com" }) - err := th.App.InviteNewUsersToTeam(emailList, th.BasicTeam.Id, th.BasicUser.Id) + t.Run("restricted domains", func(t *testing.T) { + err := th.App.InviteNewUsersToTeam(emailList, th.BasicTeam.Id, th.BasicUser.Id) - if err == nil { - t.Fatal("Adding users with non-restricted domains was allowed") - } - if err.Where != "InviteNewUsersToTeam" || err.Id != "api.team.invite_members.invalid_email.app_error" { - t.Log(err) - t.Fatal("Got wrong error message!") - } + if err == nil { + t.Fatal("Adding users with non-restricted domains was allowed") + } + if err.Where != "InviteNewUsersToTeam" || err.Id != "api.team.invite_members.invalid_email.app_error" { + t.Log(err) + t.Fatal("Got wrong error message!") + } + }) + + t.Run("override restricted domains", func(t *testing.T) { + th.BasicTeam.AllowedDomains = "invalid.com,common.com" + if _, err := th.App.UpdateTeam(th.BasicTeam); err == nil { + t.Fatal("Should not update the team") + } + + th.BasicTeam.AllowedDomains = "common.com" + if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { + t.Log(err) + t.Fatal("Should update the team") + } + + if err := th.App.InviteNewUsersToTeam([]string{"test@global.com"}, th.BasicTeam.Id, th.BasicUser.Id); err == nil || err.Where != "InviteNewUsersToTeam" { + t.Log(err) + t.Fatal("Per team restriction should take precedence over the global restriction") + } + + if err := th.App.InviteNewUsersToTeam([]string{"test@common.com"}, th.BasicTeam.Id, th.BasicUser.Id); err != nil { + t.Log(err) + t.Fatal("Failed to invite user which was common between team and global domain restriction") + } + + if err := th.App.InviteNewUsersToTeam([]string{"test@invalid.com"}, th.BasicTeam.Id, th.BasicUser.Id); err == nil { + t.Log(err) + t.Fatal("Should not invite user") + } + + }) } func TestGetTeamInviteInfo(t *testing.T) { diff --git a/app/team.go b/app/team.go index 8d1331823..dd372a99a 100644 --- a/app/team.go +++ b/app/team.go @@ -44,7 +44,7 @@ func (a *App) CreateTeamWithUser(team *model.Team, userId string) (*model.Team, team.Email = user.Email } - if !a.isTeamEmailAllowed(user) { + if !a.isTeamEmailAllowed(user, team) { return nil, model.NewAppError("isTeamEmailAllowed", "api.team.is_team_creation_allowed.domain.app_error", nil, "", http.StatusBadRequest) } @@ -60,35 +60,43 @@ func (a *App) CreateTeamWithUser(team *model.Team, userId string) (*model.Team, return rteam, nil } -func (a *App) isTeamEmailAddressAllowed(email string) bool { - email = strings.ToLower(email) +func (a *App) normalizeDomains(domains string) []string { // commas and @ signs are optional // can be in the form of "@corp.mattermost.com, mattermost.com mattermost.org" -> corp.mattermost.com mattermost.com mattermost.org - domains := strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(strings.Replace(a.Config().TeamSettings.RestrictCreationToDomains, "@", " ", -1), ",", " ", -1)))) + return strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(strings.Replace(domains, "@", " ", -1), ",", " ", -1)))) +} - matched := false - for _, d := range domains { - if strings.HasSuffix(email, "@"+d) { - matched = true - break +func (a *App) isTeamEmailAddressAllowed(email string, allowedDomains string) bool { + email = strings.ToLower(email) + // First check per team allowedDomains, then app wide restrictions + for _, restriction := range []string{allowedDomains, a.Config().TeamSettings.RestrictCreationToDomains} { + domains := a.normalizeDomains(restriction) + if len(domains) <= 0 { + continue + } + matched := false + for _, d := range domains { + if strings.HasSuffix(email, "@"+d) { + matched = true + break + } + } + if !matched { + return false } - } - - if len(a.Config().TeamSettings.RestrictCreationToDomains) > 0 && !matched { - return false } return true } -func (a *App) isTeamEmailAllowed(user *model.User) bool { +func (a *App) isTeamEmailAllowed(user *model.User, team *model.Team) bool { email := strings.ToLower(user.Email) if len(user.AuthService) > 0 && len(*user.AuthData) > 0 { return true } - return a.isTeamEmailAddressAllowed(email) + return a.isTeamEmailAddressAllowed(email, team.AllowedDomains) } func (a *App) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) { @@ -98,6 +106,23 @@ func (a *App) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) { return nil, err } + validDomains := a.normalizeDomains(a.Config().TeamSettings.RestrictCreationToDomains) + if len(validDomains) > 0 { + for _, domain := range a.normalizeDomains(team.AllowedDomains) { + matched := false + for _, d := range validDomains { + if domain == d { + matched = true + break + } + } + if !matched { + err := model.NewAppError("UpdateTeam", "api.team.update_restricted_domains.mismatch.app_error", map[string]interface{}{"Domain": domain}, "", http.StatusBadRequest) + return nil, err + } + } + } + oldTeam.DisplayName = team.DisplayName oldTeam.Description = team.Description oldTeam.InviteId = team.InviteId @@ -430,6 +455,9 @@ func (a *App) joinUserToTeam(team *model.Team, user *model.User) (*model.TeamMem } func (a *App) JoinUserToTeam(team *model.Team, user *model.User, userRequestorId string) *model.AppError { + if !a.isTeamEmailAllowed(user, team) { + return model.NewAppError("JoinUserToTeam", "api.team.join_user_to_team.allowed_domains.app_error", nil, "", http.StatusBadRequest) + } tm, alreadyAdded, err := a.joinUserToTeam(team, user) if err != nil { return err @@ -843,20 +871,6 @@ func (a *App) InviteNewUsersToTeam(emailList []string, teamId, senderId string) return err } - var invalidEmailList []string - - for _, email := range emailList { - if !a.isTeamEmailAddressAllowed(email) { - invalidEmailList = append(invalidEmailList, email) - } - } - - if len(invalidEmailList) > 0 { - s := strings.Join(invalidEmailList, ", ") - err := model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.invalid_email.app_error", map[string]interface{}{"Addresses": s}, "", http.StatusBadRequest) - return err - } - tchan := a.Srv.Store.Team().Get(teamId) uchan := a.Srv.Store.User().Get(senderId) @@ -874,6 +888,20 @@ func (a *App) InviteNewUsersToTeam(emailList []string, teamId, senderId string) user = result.Data.(*model.User) } + var invalidEmailList []string + + for _, email := range emailList { + if !a.isTeamEmailAddressAllowed(email, team.AllowedDomains) { + invalidEmailList = append(invalidEmailList, email) + } + } + + if len(invalidEmailList) > 0 { + s := strings.Join(invalidEmailList, ", ") + err := model.NewAppError("InviteNewUsersToTeam", "api.team.invite_members.invalid_email.app_error", map[string]interface{}{"Addresses": s}, "", http.StatusBadRequest) + return err + } + nameFormat := *a.Config().TeamSettings.TeammateNameDisplay a.SendInviteEmails(team, user.GetDisplayName(nameFormat), user.Id, emailList, a.GetSiteURL()) diff --git a/app/team_test.go b/app/team_test.go index 429e07931..1f2dd5318 100644 --- a/app/team_test.go +++ b/app/team_test.go @@ -73,13 +73,99 @@ func TestAddUserToTeam(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} - ruser, _ := th.App.CreateUser(&user) + t.Run("add user", func(t *testing.T) { + user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser, _ := th.App.CreateUser(&user) + defer th.App.PermanentDeleteUser(&user) - if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err != nil { - t.Log(err) - t.Fatal("Should add user to the team") - } + if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err != nil { + t.Log(err) + t.Fatal("Should add user to the team") + } + }) + + t.Run("allow user by domain", func(t *testing.T) { + th.BasicTeam.AllowedDomains = "example.com" + if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { + t.Log(err) + t.Fatal("Should update the team") + } + + user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser, _ := th.App.CreateUser(&user) + defer th.App.PermanentDeleteUser(&user) + + if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err != nil { + t.Log(err) + t.Fatal("Should have allowed whitelisted user") + } + }) + + t.Run("block user by domain", func(t *testing.T) { + th.BasicTeam.AllowedDomains = "example.com" + if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { + t.Log(err) + t.Fatal("Should update the team") + } + + user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser, _ := th.App.CreateUser(&user) + defer th.App.PermanentDeleteUser(&user) + + if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err == nil || err.Where != "JoinUserToTeam" { + t.Log(err) + t.Fatal("Should not add restricted user") + } + }) + + t.Run("block user with subdomain", func(t *testing.T) { + th.BasicTeam.AllowedDomains = "example.com" + if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { + t.Log(err) + t.Fatal("Should update the team") + } + + user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser, _ := th.App.CreateUser(&user) + defer th.App.PermanentDeleteUser(&user) + + if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err == nil || err.Where != "JoinUserToTeam" { + t.Log(err) + t.Fatal("Should not add restricted user") + } + }) + + t.Run("allow users by multiple domains", func(t *testing.T) { + th.BasicTeam.AllowedDomains = "foo.com, bar.com" + if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { + t.Log(err) + t.Fatal("Should update the team") + } + + user1 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@foo.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser1, _ := th.App.CreateUser(&user1) + user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@bar.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser2, _ := th.App.CreateUser(&user2) + user3 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser3, _ := th.App.CreateUser(&user3) + defer th.App.PermanentDeleteUser(&user1) + defer th.App.PermanentDeleteUser(&user2) + defer th.App.PermanentDeleteUser(&user3) + + if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser1.Id, ""); err != nil { + t.Log(err) + t.Fatal("Should have allowed whitelisted user1") + } + if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser2.Id, ""); err != nil { + t.Log(err) + t.Fatal("Should have allowed whitelisted user2") + } + if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser3.Id, ""); err == nil || err.Where != "JoinUserToTeam" { + t.Log(err) + t.Fatal("Should not have allowed restricted user3") + } + + }) } func TestAddUserToTeamByToken(t *testing.T) { @@ -158,19 +244,62 @@ func TestAddUserToTeamByToken(t *testing.T) { t.Fatal("The token must be deleted after be used") } }) + + t.Run("block user", func(t *testing.T) { + th.BasicTeam.AllowedDomains = "example.com" + if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { + t.Log(err) + t.Fatal("Should update the team") + } + + user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser, _ := th.App.CreateUser(&user) + defer th.App.PermanentDeleteUser(&user) + + token := model.NewToken( + TOKEN_TYPE_TEAM_INVITATION, + model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id}), + ) + <-th.App.Srv.Store.Token().Save(token) + + if _, err := th.App.AddUserToTeamByToken(ruser.Id, token.Token); err == nil || err.Where != "JoinUserToTeam" { + t.Log(err) + t.Fatal("Should not add restricted user") + } + }) } func TestAddUserToTeamByTeamId(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() - user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} - ruser, _ := th.App.CreateUser(&user) + t.Run("add user", func(t *testing.T) { + user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@example.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser, _ := th.App.CreateUser(&user) + + if err := th.App.AddUserToTeamByTeamId(th.BasicTeam.Id, ruser); err != nil { + t.Log(err) + t.Fatal("Should add user to the team") + } + }) + + t.Run("block user", func(t *testing.T) { + th.BasicTeam.AllowedDomains = "example.com" + if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { + t.Log(err) + t.Fatal("Should update the team") + } + + user := model.User{Email: strings.ToLower(model.NewId()) + "test@invalid.com", Nickname: "Darth Vader", Username: "vader" + model.NewId(), Password: "passwd1", AuthService: ""} + ruser, _ := th.App.CreateUser(&user) + defer th.App.PermanentDeleteUser(&user) + + if err := th.App.AddUserToTeamByTeamId(th.BasicTeam.Id, ruser); err == nil || err.Where != "JoinUserToTeam" { + t.Log(err) + t.Fatal("Should not add restricted user") + } + }) - if err := th.App.AddUserToTeamByTeamId(th.BasicTeam.Id, ruser); err != nil { - t.Log(err) - t.Fatal("Should add user to the team") - } } func TestPermanentDeleteTeam(t *testing.T) { @@ -264,7 +393,7 @@ func TestSanitizeTeam(t *testing.T) { } sanitized := th.App.SanitizeTeam(session, copyTeam()) - if sanitized.Email != "" && sanitized.AllowedDomains != "" { + if sanitized.Email != "" { t.Fatal("should've sanitized team") } }) @@ -283,7 +412,7 @@ func TestSanitizeTeam(t *testing.T) { } sanitized := th.App.SanitizeTeam(session, copyTeam()) - if sanitized.Email != "" && sanitized.AllowedDomains != "" { + if sanitized.Email != "" { t.Fatal("should've sanitized team") } }) @@ -302,7 +431,7 @@ func TestSanitizeTeam(t *testing.T) { } sanitized := th.App.SanitizeTeam(session, copyTeam()) - if sanitized.Email == "" && sanitized.AllowedDomains == "" { + if sanitized.Email == "" { t.Fatal("shouldn't have sanitized team") } }) @@ -321,7 +450,7 @@ func TestSanitizeTeam(t *testing.T) { } sanitized := th.App.SanitizeTeam(session, copyTeam()) - if sanitized.Email != "" && sanitized.AllowedDomains != "" { + if sanitized.Email != "" { t.Fatal("should've sanitized team") } }) @@ -340,7 +469,7 @@ func TestSanitizeTeam(t *testing.T) { } sanitized := th.App.SanitizeTeam(session, copyTeam()) - if sanitized.Email == "" && sanitized.AllowedDomains == "" { + if sanitized.Email == "" { t.Fatal("shouldn't have sanitized team") } }) @@ -359,7 +488,7 @@ func TestSanitizeTeam(t *testing.T) { } sanitized := th.App.SanitizeTeam(session, copyTeam()) - if sanitized.Email == "" && sanitized.AllowedDomains == "" { + if sanitized.Email == "" { t.Fatal("shouldn't have sanitized team") } }) @@ -402,11 +531,11 @@ func TestSanitizeTeams(t *testing.T) { sanitized := th.App.SanitizeTeams(session, teams) - if sanitized[0].Email != "" && sanitized[0].AllowedDomains != "" { + if sanitized[0].Email != "" { t.Fatal("should've sanitized first team") } - if sanitized[1].Email == "" && sanitized[1].AllowedDomains == "" { + if sanitized[1].Email == "" { t.Fatal("shouldn't have sanitized second team") } }) @@ -439,11 +568,11 @@ func TestSanitizeTeams(t *testing.T) { sanitized := th.App.SanitizeTeams(session, teams) - if sanitized[0].Email == "" && sanitized[0].AllowedDomains == "" { + if sanitized[0].Email == "" { t.Fatal("shouldn't have sanitized first team") } - if sanitized[1].Email == "" && sanitized[1].AllowedDomains == "" { + if sanitized[1].Email == "" { t.Fatal("shouldn't have sanitized second team") } }) diff --git a/i18n/en.json b/i18n/en.json index a0a5e1dda..f7d687ec9 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1654,6 +1654,10 @@ "id": "api.team.join_team.post_and_forget", "translation": "%v joined the team." }, + { + "id": "api.team.join_user_to_team.allowed_domains.app_error", + "translation": "Email must be from a specific domain (e.g. @example.com). Please ask your team or system administrator for details." + }, { "id": "api.team.leave.left", "translation": "%v left the team." @@ -1730,6 +1734,10 @@ "id": "api.team.update_member_roles.not_a_member", "translation": "Specified user is not a member of specified team." }, + { + "id": "api.team.update_restricted_domains.mismatch.app_error", + "translation": "Restricting team to {{ .Domain }} is not allowed by the system config. Please contact your system administrator." + }, { "id": "api.team.update_team_scheme.license.error", "translation": "Your license does not support updating a team's scheme" diff --git a/model/team.go b/model/team.go index edf9d3a41..530c3fd6a 100644 --- a/model/team.go +++ b/model/team.go @@ -47,6 +47,7 @@ type TeamPatch struct { DisplayName *string `json:"display_name"` Description *string `json:"description"` CompanyName *string `json:"company_name"` + AllowedDomains *string `json:"allowed_domains"` InviteId *string `json:"invite_id"` AllowOpenInvite *bool `json:"allow_open_invite"` } @@ -241,7 +242,6 @@ func CleanTeamName(s string) string { func (o *Team) Sanitize() { o.Email = "" - o.AllowedDomains = "" } func (t *Team) Patch(patch *TeamPatch) { @@ -257,6 +257,10 @@ func (t *Team) Patch(patch *TeamPatch) { t.CompanyName = *patch.CompanyName } + if patch.AllowedDomains != nil { + t.AllowedDomains = *patch.AllowedDomains + } + if patch.InviteId != nil { t.InviteId = *patch.InviteId } -- cgit v1.2.3-1-g7c22