github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/store/searchlayer/user_layer.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package searchlayer
     5  
     6  import (
     7  	"context"
     8  	"strings"
     9  
    10  	"github.com/pkg/errors"
    11  
    12  	"github.com/mattermost/mattermost-server/v5/mlog"
    13  	"github.com/mattermost/mattermost-server/v5/model"
    14  	"github.com/mattermost/mattermost-server/v5/services/searchengine"
    15  	"github.com/mattermost/mattermost-server/v5/store"
    16  )
    17  
    18  type SearchUserStore struct {
    19  	store.UserStore
    20  	rootStore *SearchStore
    21  }
    22  
    23  func (s *SearchUserStore) deleteUserIndex(user *model.User) {
    24  	for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
    25  		if engine.IsIndexingEnabled() {
    26  			runIndexFn(engine, func(engineCopy searchengine.SearchEngineInterface) {
    27  				if err := engineCopy.DeleteUser(user); err != nil {
    28  					mlog.Error("Encountered error deleting user", mlog.String("user_id", user.Id), mlog.String("search_engine", engineCopy.GetName()), mlog.Err(err))
    29  					return
    30  				}
    31  				mlog.Debug("Removed user from the index in search engine", mlog.String("search_engine", engineCopy.GetName()), mlog.String("user_id", user.Id))
    32  			})
    33  		}
    34  	}
    35  }
    36  
    37  func (s *SearchUserStore) Search(teamId, term string, options *model.UserSearchOptions) ([]*model.User, error) {
    38  	for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
    39  		if engine.IsSearchEnabled() {
    40  			listOfAllowedChannels, nErr := s.getListOfAllowedChannelsForTeam(teamId, options.ViewRestrictions)
    41  			if nErr != nil {
    42  				mlog.Warn("Encountered error on Search.", mlog.String("search_engine", engine.GetName()), mlog.Err(nErr))
    43  				continue
    44  			}
    45  
    46  			if listOfAllowedChannels != nil && len(listOfAllowedChannels) == 0 {
    47  				return []*model.User{}, nil
    48  			}
    49  
    50  			sanitizedTerm := sanitizeSearchTerm(term)
    51  
    52  			usersIds, err := engine.SearchUsersInTeam(teamId, listOfAllowedChannels, sanitizedTerm, options)
    53  			if err != nil {
    54  				mlog.Warn("Encountered error on Search", mlog.String("search_engine", engine.GetName()), mlog.Err(err))
    55  				continue
    56  			}
    57  
    58  			users, nErr := s.UserStore.GetProfileByIds(context.Background(), usersIds, nil, false)
    59  			if nErr != nil {
    60  				mlog.Warn("Encountered error on Search", mlog.String("search_engine", engine.GetName()), mlog.Err(nErr))
    61  				continue
    62  			}
    63  
    64  			mlog.Debug("Using the first available search engine", mlog.String("search_engine", engine.GetName()))
    65  			return users, nil
    66  		}
    67  	}
    68  
    69  	mlog.Debug("Using database search because no other search engine is available")
    70  
    71  	return s.UserStore.Search(teamId, term, options)
    72  }
    73  
    74  func (s *SearchUserStore) Update(user *model.User, trustedUpdateData bool) (*model.UserUpdate, error) {
    75  	userUpdate, err := s.UserStore.Update(user, trustedUpdateData)
    76  
    77  	if err == nil {
    78  		s.rootStore.indexUser(userUpdate.New)
    79  	}
    80  	return userUpdate, err
    81  }
    82  
    83  func (s *SearchUserStore) Save(user *model.User) (*model.User, error) {
    84  	nuser, err := s.UserStore.Save(user)
    85  
    86  	if err == nil {
    87  		s.rootStore.indexUser(nuser)
    88  	}
    89  	return nuser, err
    90  }
    91  
    92  func (s *SearchUserStore) PermanentDelete(userId string) error {
    93  	user, userErr := s.UserStore.Get(context.Background(), userId)
    94  	if userErr != nil {
    95  		mlog.Warn("Encountered error deleting user", mlog.String("user_id", userId), mlog.Err(userErr))
    96  	}
    97  	err := s.UserStore.PermanentDelete(userId)
    98  	if err == nil && userErr == nil {
    99  		s.deleteUserIndex(user)
   100  	}
   101  	return err
   102  }
   103  
   104  func (s *SearchUserStore) autocompleteUsersInChannelByEngine(engine searchengine.SearchEngineInterface, teamId, channelId, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, error) {
   105  	var err *model.AppError
   106  	uchanIds := []string{}
   107  	nuchanIds := []string{}
   108  	sanitizedTerm := sanitizeSearchTerm(term)
   109  	if channelId != "" && options.ListOfAllowedChannels != nil && !strings.Contains(strings.Join(options.ListOfAllowedChannels, "."), channelId) {
   110  		nuchanIds, err = engine.SearchUsersInTeam(teamId, options.ListOfAllowedChannels, sanitizedTerm, options)
   111  	} else {
   112  		uchanIds, nuchanIds, err = engine.SearchUsersInChannel(teamId, channelId, options.ListOfAllowedChannels, sanitizedTerm, options)
   113  	}
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	uchan := make(chan store.StoreResult, 1)
   119  	go func() {
   120  		users, nErr := s.UserStore.GetProfileByIds(context.Background(), uchanIds, nil, false)
   121  		uchan <- store.StoreResult{Data: users, NErr: nErr}
   122  		close(uchan)
   123  	}()
   124  
   125  	nuchan := make(chan store.StoreResult, 1)
   126  	go func() {
   127  		users, nErr := s.UserStore.GetProfileByIds(context.Background(), nuchanIds, nil, false)
   128  		nuchan <- store.StoreResult{Data: users, NErr: nErr}
   129  		close(nuchan)
   130  	}()
   131  
   132  	autocomplete := &model.UserAutocompleteInChannel{}
   133  
   134  	result := <-uchan
   135  	if result.NErr != nil {
   136  		return nil, errors.Wrap(result.NErr, "failed to get user profiles by ids")
   137  	}
   138  	inUsers := result.Data.([]*model.User)
   139  	autocomplete.InChannel = inUsers
   140  
   141  	result = <-nuchan
   142  	if result.NErr != nil {
   143  		return nil, errors.Wrap(result.NErr, "failed to get user profiles by ids")
   144  	}
   145  	outUsers := result.Data.([]*model.User)
   146  	autocomplete.OutOfChannel = outUsers
   147  
   148  	return autocomplete, nil
   149  }
   150  
   151  // getListOfAllowedChannelsForTeam return the list of allowed channels to search user based on the
   152  //	next scenarios:
   153  //		- If there isn't view restrictions (team or channel) and no team id to filter them, then all
   154  //		  channels are allowed (nil return)
   155  //	   	- If we receive a team Id and either we don't have view restrictions or the provided team id is included in the
   156  //		  list of restricted teams, then we return all the team channels
   157  //		- If we don't receive team id or the provided team id is not in the list of allowed teams to search of and we
   158  //		  don't have channel restrictions then we return an empty result because we cannot get channels
   159  //		- If we receive channels restrictions we get:
   160  //			- If we don't have team id, we get those restricted channels (guest accounts and quick search)
   161  //			- If we have a team id then we only return those restricted channels that belongs to that team
   162  func (s *SearchUserStore) getListOfAllowedChannelsForTeam(teamId string, viewRestrictions *model.ViewUsersRestrictions) ([]string, error) {
   163  	var listOfAllowedChannels []string
   164  	if viewRestrictions == nil && teamId == "" {
   165  		// nil return without error means all channels are allowed
   166  		return nil, nil
   167  	}
   168  
   169  	if teamId != "" && (viewRestrictions == nil || strings.Contains(strings.Join(viewRestrictions.Teams, "."), teamId)) {
   170  		channels, err := s.rootStore.Channel().GetTeamChannels(teamId)
   171  		if err != nil {
   172  			return nil, errors.Wrap(err, "failed to get team channels")
   173  		}
   174  		for _, channel := range *channels {
   175  			listOfAllowedChannels = append(listOfAllowedChannels, channel.Id)
   176  		}
   177  		return listOfAllowedChannels, nil
   178  	}
   179  
   180  	if len(viewRestrictions.Channels) > 0 {
   181  		channels, err := s.rootStore.Channel().GetChannelsByIds(viewRestrictions.Channels, false)
   182  		if err != nil {
   183  			return nil, errors.Wrap(err, "failed to get channels by ids")
   184  		}
   185  		for _, c := range channels {
   186  			if teamId == "" || (teamId != "" && c.TeamId == teamId) {
   187  				listOfAllowedChannels = append(listOfAllowedChannels, c.Id)
   188  			}
   189  		}
   190  		return listOfAllowedChannels, nil
   191  	}
   192  
   193  	return []string{}, nil
   194  }
   195  
   196  func (s *SearchUserStore) AutocompleteUsersInChannel(teamId, channelId, term string, options *model.UserSearchOptions) (*model.UserAutocompleteInChannel, error) {
   197  	for _, engine := range s.rootStore.searchEngine.GetActiveEngines() {
   198  		if engine.IsAutocompletionEnabled() {
   199  			listOfAllowedChannels, nErr := s.getListOfAllowedChannelsForTeam(teamId, options.ViewRestrictions)
   200  			if nErr != nil {
   201  				mlog.Warn("Encountered error on AutocompleteUsersInChannel.", mlog.String("search_engine", engine.GetName()), mlog.Err(nErr))
   202  				continue
   203  			}
   204  			if listOfAllowedChannels != nil && len(listOfAllowedChannels) == 0 {
   205  				return &model.UserAutocompleteInChannel{}, nil
   206  			}
   207  			options.ListOfAllowedChannels = listOfAllowedChannels
   208  			autocomplete, nErr := s.autocompleteUsersInChannelByEngine(engine, teamId, channelId, term, options)
   209  			if nErr != nil {
   210  				mlog.Warn("Encountered error on AutocompleteUsersInChannel.", mlog.String("search_engine", engine.GetName()), mlog.Err(nErr))
   211  				continue
   212  			}
   213  			mlog.Debug("Using the first available search engine", mlog.String("search_engine", engine.GetName()))
   214  			return autocomplete, nil
   215  		}
   216  	}
   217  
   218  	mlog.Debug("Using database search because no other search engine is available")
   219  	return s.UserStore.AutocompleteUsersInChannel(teamId, channelId, term, options)
   220  }