fix: filter out bots for sharing, @mention-ing, and Person property (#3762)
* fix: filter out bots for sharing and @mention-ing * feat: add `?exclude_bots` to `getTeamUsers` API * chore: `make swagger` * chore: `make generate` * fix: plugin store test function implementation Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
parent
01eb95a6d2
commit
15e13fcdac
15 changed files with 85 additions and 30 deletions
2
Makefile
2
Makefile
|
@ -100,7 +100,7 @@ server-linux-package-docker:
|
|||
rm -rf package
|
||||
|
||||
generate: ## Install and run code generators.
|
||||
cd server; go get github.com/golang/mock/mockgen
|
||||
cd server; go install github.com/golang/mock/mockgen@v1.6.0
|
||||
cd server; go generate ./...
|
||||
|
||||
server-lint: templates-archive ## Run linters on server code.
|
||||
|
|
|
@ -198,6 +198,11 @@ func (a *API) handleGetTeamUsers(w http.ResponseWriter, r *http.Request) {
|
|||
// description: string to filter users list
|
||||
// required: false
|
||||
// type: string
|
||||
// - name: exclude_bots
|
||||
// in: query
|
||||
// description: exclude bot users
|
||||
// required: false
|
||||
// type: boolean
|
||||
// security:
|
||||
// - BearerAuth: []
|
||||
// responses:
|
||||
|
@ -217,6 +222,7 @@ func (a *API) handleGetTeamUsers(w http.ResponseWriter, r *http.Request) {
|
|||
userID := getUserID(r)
|
||||
query := r.URL.Query()
|
||||
searchQuery := query.Get("search")
|
||||
excludeBots := r.URL.Query().Get("exclude_bots") == True
|
||||
|
||||
if !a.permissions.HasPermissionToTeam(userID, teamID, model.PermissionViewTeam) {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusForbidden, "Access denied to team", PermissionError{"access denied to team"})
|
||||
|
@ -236,7 +242,7 @@ func (a *API) handleGetTeamUsers(w http.ResponseWriter, r *http.Request) {
|
|||
asGuestUser = userID
|
||||
}
|
||||
|
||||
users, err := a.app.SearchTeamUsers(teamID, searchQuery, asGuestUser)
|
||||
users, err := a.app.SearchTeamUsers(teamID, searchQuery, asGuestUser, excludeBots)
|
||||
if err != nil {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "searchQuery="+searchQuery, err)
|
||||
return
|
||||
|
|
|
@ -9,8 +9,8 @@ func (a *App) GetTeamUsers(teamID string, asGuestID string) ([]*model.User, erro
|
|||
return a.store.GetUsersByTeam(teamID, asGuestID)
|
||||
}
|
||||
|
||||
func (a *App) SearchTeamUsers(teamID string, searchQuery string, asGuestID string) ([]*model.User, error) {
|
||||
return a.store.SearchUsersByTeam(teamID, searchQuery, asGuestID)
|
||||
func (a *App) SearchTeamUsers(teamID string, searchQuery string, asGuestID string, excludeBots bool) ([]*model.User, error) {
|
||||
return a.store.SearchUsersByTeam(teamID, searchQuery, asGuestID, excludeBots)
|
||||
}
|
||||
|
||||
func (a *App) UpdateUserConfig(userID string, patch model.UserPropPatch) ([]mmModel.Preference, error) {
|
||||
|
|
|
@ -219,7 +219,7 @@ func (s *PluginTestStore) GetUsersByTeam(teamID string, asGuestID string) ([]*mo
|
|||
return nil, errTestStore
|
||||
}
|
||||
|
||||
func (s *PluginTestStore) SearchUsersByTeam(teamID string, searchQuery string, asGuestID string) ([]*model.User, error) {
|
||||
func (s *PluginTestStore) SearchUsersByTeam(teamID string, searchQuery string, asGuestID string, excludeBots bool) ([]*model.User, error) {
|
||||
users := []*model.User{}
|
||||
teamUsers, err := s.GetUsersByTeam(teamID, asGuestID)
|
||||
if err != nil {
|
||||
|
@ -227,6 +227,9 @@ func (s *PluginTestStore) SearchUsersByTeam(teamID string, searchQuery string, a
|
|||
}
|
||||
|
||||
for _, user := range teamUsers {
|
||||
if excludeBots && user.IsBot {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(user.Username, searchQuery) {
|
||||
users = append(users, user)
|
||||
}
|
||||
|
|
|
@ -351,7 +351,7 @@ func (s *MattermostAuthLayer) GetUsersList(userIDs []string) ([]*model.User, err
|
|||
return users, nil
|
||||
}
|
||||
|
||||
func (s *MattermostAuthLayer) SearchUsersByTeam(teamID string, searchQuery string, asGuestID string) ([]*model.User, error) {
|
||||
func (s *MattermostAuthLayer) SearchUsersByTeam(teamID string, searchQuery string, asGuestID string, excludeBots bool) ([]*model.User, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select("u.id", "u.username", "u.email", "u.nickname", "u.firstname", "u.lastname", "u.props", "u.CreateAt as create_at", "u.UpdateAt as update_at",
|
||||
"u.DeleteAt as delete_at", "b.UserId IS NOT NULL AS is_bot, u.roles = 'system_guest' as is_guest").
|
||||
|
@ -367,6 +367,11 @@ func (s *MattermostAuthLayer) SearchUsersByTeam(teamID string, searchQuery strin
|
|||
OrderBy("u.username").
|
||||
Limit(10)
|
||||
|
||||
if excludeBots {
|
||||
query = query.
|
||||
Where(sq.Eq{"b.UserId IS NOT NULL": false})
|
||||
}
|
||||
|
||||
if asGuestID == "" {
|
||||
query = query.
|
||||
Join("TeamMembers as tm ON tm.UserID = u.id").
|
||||
|
|
|
@ -1413,18 +1413,18 @@ func (mr *MockStoreMockRecorder) SearchUserChannels(arg0, arg1, arg2 interface{}
|
|||
}
|
||||
|
||||
// SearchUsersByTeam mocks base method.
|
||||
func (m *MockStore) SearchUsersByTeam(arg0, arg1, arg2 string) ([]*model.User, error) {
|
||||
func (m *MockStore) SearchUsersByTeam(arg0, arg1, arg2 string, arg3 bool) ([]*model.User, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SearchUsersByTeam", arg0, arg1, arg2)
|
||||
ret := m.ctrl.Call(m, "SearchUsersByTeam", arg0, arg1, arg2, arg3)
|
||||
ret0, _ := ret[0].([]*model.User)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SearchUsersByTeam indicates an expected call of SearchUsersByTeam.
|
||||
func (mr *MockStoreMockRecorder) SearchUsersByTeam(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
func (mr *MockStoreMockRecorder) SearchUsersByTeam(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchUsersByTeam", reflect.TypeOf((*MockStore)(nil).SearchUsersByTeam), arg0, arg1, arg2)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchUsersByTeam", reflect.TypeOf((*MockStore)(nil).SearchUsersByTeam), arg0, arg1, arg2, arg3)
|
||||
}
|
||||
|
||||
// SendMessage mocks base method.
|
||||
|
|
|
@ -786,8 +786,8 @@ func (s *SQLStore) SearchUserChannels(teamID string, userID string, query string
|
|||
|
||||
}
|
||||
|
||||
func (s *SQLStore) SearchUsersByTeam(teamID string, searchQuery string, asGuestID string) ([]*model.User, error) {
|
||||
return s.searchUsersByTeam(s.db, teamID, searchQuery, asGuestID)
|
||||
func (s *SQLStore) SearchUsersByTeam(teamID string, searchQuery string, asGuestID string, excludeBots bool) ([]*model.User, error) {
|
||||
return s.searchUsersByTeam(s.db, teamID, searchQuery, asGuestID, excludeBots)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@ func (s *SQLStore) getUsersByTeam(db sq.BaseRunner, _ string, _ string) ([]*mode
|
|||
return s.getUsersByCondition(db, nil, 0)
|
||||
}
|
||||
|
||||
func (s *SQLStore) searchUsersByTeam(db sq.BaseRunner, _ string, searchQuery string, _ string) ([]*model.User, error) {
|
||||
func (s *SQLStore) searchUsersByTeam(db sq.BaseRunner, _ string, searchQuery string, _ string, _ bool) ([]*model.User, error) {
|
||||
return s.getUsersByCondition(db, &sq.Like{"username": "%" + searchQuery + "%"}, 10)
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ type Store interface {
|
|||
UpdateUserPassword(username, password string) error
|
||||
UpdateUserPasswordByID(userID, password string) error
|
||||
GetUsersByTeam(teamID string, asGuestID string) ([]*model.User, error)
|
||||
SearchUsersByTeam(teamID string, searchQuery string, asGuestID string) ([]*model.User, error)
|
||||
SearchUsersByTeam(teamID string, searchQuery string, asGuestID string, excludeBots bool) ([]*model.User, error)
|
||||
PatchUserProps(userID string, patch model.UserPropPatch) error
|
||||
GetUserPreferences(userID string) (mmModel.Preferences, error)
|
||||
|
||||
|
|
48
server/swagger/docs/html/index.html
generated
48
server/swagger/docs/html/index.html
generated
|
@ -14909,7 +14909,7 @@ Team ID
|
|||
<pre class="prettyprint"><code class="language-bsh">curl -X GET \
|
||||
-H "Authorization: [[apiKey]]" \
|
||||
-H "Accept: application/json" \
|
||||
"http://localhost/api/v2/teams/{teamID}/users?search=search_example"
|
||||
"http://localhost/api/v2/teams/{teamID}/users?search=search_example&exclude_bots=true"
|
||||
</code></pre>
|
||||
</div>
|
||||
<div class="tab-pane" id="examples-Default-getTeamUsers-0-java">
|
||||
|
@ -14935,9 +14935,10 @@ public class DefaultApiExample {
|
|||
DefaultApi apiInstance = new DefaultApi();
|
||||
String teamID = teamID_example; // String | Team ID
|
||||
String search = search_example; // String | string to filter users list
|
||||
Boolean excludeBots = true; // Boolean | exclude bot users
|
||||
|
||||
try {
|
||||
array[Object] result = apiInstance.getTeamUsers(teamID, search);
|
||||
array[Object] result = apiInstance.getTeamUsers(teamID, search, excludeBots);
|
||||
System.out.println(result);
|
||||
} catch (ApiException e) {
|
||||
System.err.println("Exception when calling DefaultApi#getTeamUsers");
|
||||
|
@ -14956,9 +14957,10 @@ public class DefaultApiExample {
|
|||
DefaultApi apiInstance = new DefaultApi();
|
||||
String teamID = teamID_example; // String | Team ID
|
||||
String search = search_example; // String | string to filter users list
|
||||
Boolean excludeBots = true; // Boolean | exclude bot users
|
||||
|
||||
try {
|
||||
array[Object] result = apiInstance.getTeamUsers(teamID, search);
|
||||
array[Object] result = apiInstance.getTeamUsers(teamID, search, excludeBots);
|
||||
System.out.println(result);
|
||||
} catch (ApiException e) {
|
||||
System.err.println("Exception when calling DefaultApi#getTeamUsers");
|
||||
|
@ -14984,9 +14986,11 @@ public class DefaultApiExample {
|
|||
DefaultApi *apiInstance = [[DefaultApi alloc] init];
|
||||
String *teamID = teamID_example; // Team ID (default to null)
|
||||
String *search = search_example; // string to filter users list (optional) (default to null)
|
||||
Boolean *excludeBots = true; // exclude bot users (optional) (default to null)
|
||||
|
||||
[apiInstance getTeamUsersWith:teamID
|
||||
search:search
|
||||
excludeBots:excludeBots
|
||||
completionHandler: ^(array[Object] output, NSError* error) {
|
||||
if (output) {
|
||||
NSLog(@"%@", output);
|
||||
|
@ -15012,7 +15016,8 @@ BearerAuth.apiKey = "YOUR API KEY";
|
|||
var api = new FocalboardServer.DefaultApi()
|
||||
var teamID = teamID_example; // {String} Team ID
|
||||
var opts = {
|
||||
'search': search_example // {String} string to filter users list
|
||||
'search': search_example, // {String} string to filter users list
|
||||
'excludeBots': true // {Boolean} exclude bot users
|
||||
};
|
||||
|
||||
var callback = function(error, data, response) {
|
||||
|
@ -15051,9 +15056,10 @@ namespace Example
|
|||
var apiInstance = new DefaultApi();
|
||||
var teamID = teamID_example; // String | Team ID (default to null)
|
||||
var search = search_example; // String | string to filter users list (optional) (default to null)
|
||||
var excludeBots = true; // Boolean | exclude bot users (optional) (default to null)
|
||||
|
||||
try {
|
||||
array[Object] result = apiInstance.getTeamUsers(teamID, search);
|
||||
array[Object] result = apiInstance.getTeamUsers(teamID, search, excludeBots);
|
||||
Debug.WriteLine(result);
|
||||
} catch (Exception e) {
|
||||
Debug.Print("Exception when calling DefaultApi.getTeamUsers: " + e.Message );
|
||||
|
@ -15077,9 +15083,10 @@ OpenAPITools\Client\Configuration::getDefaultConfiguration()->setApiKey('Authori
|
|||
$api_instance = new OpenAPITools\Client\Api\DefaultApi();
|
||||
$teamID = teamID_example; // String | Team ID
|
||||
$search = search_example; // String | string to filter users list
|
||||
$excludeBots = true; // Boolean | exclude bot users
|
||||
|
||||
try {
|
||||
$result = $api_instance->getTeamUsers($teamID, $search);
|
||||
$result = $api_instance->getTeamUsers($teamID, $search, $excludeBots);
|
||||
print_r($result);
|
||||
} catch (Exception $e) {
|
||||
echo 'Exception when calling DefaultApi->getTeamUsers: ', $e->getMessage(), PHP_EOL;
|
||||
|
@ -15101,9 +15108,10 @@ $WWW::OPenAPIClient::Configuration::api_key->{'Authorization'} = 'YOUR_API_KEY';
|
|||
my $api_instance = WWW::OPenAPIClient::DefaultApi->new();
|
||||
my $teamID = teamID_example; # String | Team ID
|
||||
my $search = search_example; # String | string to filter users list
|
||||
my $excludeBots = true; # Boolean | exclude bot users
|
||||
|
||||
eval {
|
||||
my $result = $api_instance->getTeamUsers(teamID => $teamID, search => $search);
|
||||
my $result = $api_instance->getTeamUsers(teamID => $teamID, search => $search, excludeBots => $excludeBots);
|
||||
print Dumper($result);
|
||||
};
|
||||
if ($@) {
|
||||
|
@ -15127,9 +15135,10 @@ openapi_client.configuration.api_key['Authorization'] = 'YOUR_API_KEY'
|
|||
api_instance = openapi_client.DefaultApi()
|
||||
teamID = teamID_example # String | Team ID (default to null)
|
||||
search = search_example # String | string to filter users list (optional) (default to null)
|
||||
excludeBots = true # Boolean | exclude bot users (optional) (default to null)
|
||||
|
||||
try:
|
||||
api_response = api_instance.get_team_users(teamID, search=search)
|
||||
api_response = api_instance.get_team_users(teamID, search=search, excludeBots=excludeBots)
|
||||
pprint(api_response)
|
||||
except ApiException as e:
|
||||
print("Exception when calling DefaultApi->getTeamUsers: %s\n" % e)</code></pre>
|
||||
|
@ -15141,9 +15150,10 @@ except ApiException as e:
|
|||
pub fn main() {
|
||||
let teamID = teamID_example; // String
|
||||
let search = search_example; // String
|
||||
let excludeBots = true; // Boolean
|
||||
|
||||
let mut context = DefaultApi::Context::default();
|
||||
let result = client.getTeamUsers(teamID, search, &context).wait();
|
||||
let result = client.getTeamUsers(teamID, search, excludeBots, &context).wait();
|
||||
|
||||
println!("{:?}", result);
|
||||
}
|
||||
|
@ -15216,6 +15226,26 @@ string to filter users list
|
|||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr><td style="width:150px;">exclude_bots</td>
|
||||
<td>
|
||||
|
||||
|
||||
<div id="d2e199_getTeamUsers_excludeBots">
|
||||
<div class="json-schema-view">
|
||||
<div class="primitive">
|
||||
<span class="type">
|
||||
Boolean
|
||||
</span>
|
||||
|
||||
<div class="inner description marked">
|
||||
exclude bot users
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
|
4
server/swagger/swagger.yml
generated
4
server/swagger/swagger.yml
generated
|
@ -1637,6 +1637,10 @@ paths:
|
|||
in: query
|
||||
name: search
|
||||
type: string
|
||||
- description: exclude bot users
|
||||
in: query
|
||||
name: exclude_bots
|
||||
type: boolean
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
|
|
@ -74,7 +74,8 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
|
|||
let users: Array<IUser>
|
||||
|
||||
if (!me?.is_guest && (allowAddUsers || (board && board.type === BoardTypeOpen))) {
|
||||
users = await octoClient.searchTeamUsers(term)
|
||||
const excludeBots = true
|
||||
users = await octoClient.searchTeamUsers(term, excludeBots)
|
||||
} else {
|
||||
users = boardUsers
|
||||
.filter(user => {
|
||||
|
|
|
@ -364,7 +364,8 @@ export default function ShareBoardDialog(props: Props): JSX.Element {
|
|||
loadOptions={async (inputValue: string) => {
|
||||
const result = []
|
||||
if (Utils.isFocalboardPlugin()) {
|
||||
const users = await client.searchTeamUsers(inputValue)
|
||||
const excludeBots = true
|
||||
const users = await client.searchTeamUsers(inputValue, excludeBots)
|
||||
if (users) {
|
||||
result.push({label: intl.formatMessage({id: 'shareBoard.members-select-group', defaultMessage: 'Members'}), options: users || []})
|
||||
}
|
||||
|
|
|
@ -629,8 +629,10 @@ class OctoClient {
|
|||
return this.getJson<Array<Team>>(response, [])
|
||||
}
|
||||
|
||||
async getTeamUsers(): Promise<IUser[]> {
|
||||
const path = this.teamPath() + '/users'
|
||||
async getTeamUsers(excludeBots?: boolean): Promise<IUser[]> {
|
||||
let path = this.teamPath() + '/users'
|
||||
if (excludeBots)
|
||||
path += '?exclude_bots=true'
|
||||
const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})
|
||||
if (response.status !== 200) {
|
||||
return []
|
||||
|
@ -638,8 +640,10 @@ class OctoClient {
|
|||
return (await this.getJson(response, [])) as IUser[]
|
||||
}
|
||||
|
||||
async searchTeamUsers(searchQuery: string): Promise<IUser[]> {
|
||||
const path = this.teamPath() + `/users?search=${searchQuery}`
|
||||
async searchTeamUsers(searchQuery: string, excludeBots?: boolean): Promise<IUser[]> {
|
||||
let path = this.teamPath() + `/users?search=${searchQuery}`
|
||||
if (excludeBots)
|
||||
path += '&exclude_bots=true'
|
||||
const response = await fetch(this.getBaseURL() + path, {headers: this.headers()})
|
||||
if (response.status !== 200) {
|
||||
return []
|
||||
|
|
|
@ -127,7 +127,8 @@ const Person = (props: PropertyProps): JSX.Element => {
|
|||
if (!allowAddUsers) {
|
||||
return boardUsers.filter((u) => u.username.toLowerCase().includes(value.toLowerCase()))
|
||||
}
|
||||
const allUsers = await client.searchTeamUsers(value)
|
||||
const excludeBots = true
|
||||
const allUsers = await client.searchTeamUsers(value, excludeBots)
|
||||
const usersInsideBoard: IUser[] = []
|
||||
const usersOutsideBoard: IUser[] = []
|
||||
for (const u of allUsers) {
|
||||
|
|
Loading…
Reference in a new issue