User Directory support in REST Backend

This commit is contained in:
Maxime Dor
2017-10-01 18:13:01 +02:00
parent c702a34aab
commit d0aac5ac52
5 changed files with 264 additions and 2 deletions

View File

@@ -91,9 +91,13 @@ Content-type: JSON UTF-8
#### Request Body #### Request Body
``` ```
{ {
"by": "<search type>",
"search_term": "doe" "search_term": "doe"
} }
``` ```
`by` can be:
- `name`
- `threepid`
#### Response Body: #### Response Body:
If users found: If users found:
@@ -102,8 +106,8 @@ If users found:
"limited": false, "limited": false,
"results": [ "results": [
{ {
"display_name": "John Doe",
"avatar_url": "http://domain.tld/path/to/avatar.png", "avatar_url": "http://domain.tld/path/to/avatar.png",
"display_name": "John Doe",
"user_id": "UserIdLocalpart" "user_id": "UserIdLocalpart"
}, },
{ {

View File

@@ -0,0 +1,82 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.rest;
import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.rest.RestBackendConfig;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.directory.IDirectoryProvider;
import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.util.RestClientUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class RestDirectoryProvider extends RestProvider implements IDirectoryProvider {
private MatrixConfig mxCfg;
public RestDirectoryProvider(RestBackendConfig cfg, MatrixConfig mxCfg) {
super(cfg);
this.mxCfg = mxCfg;
}
@Override
public boolean isEnabled() {
return cfg.isEnabled() && StringUtils.isNotBlank(cfg.getEndpoints().getDirectory());
}
private UserDirectorySearchResult search(String by, String query) {
UserDirectorySearchRequest request = new UserDirectorySearchRequest(query);
request.setBy(by);
try (CloseableHttpResponse httpResponse = client.execute(RestClientUtils.post(cfg.getEndpoints().getDirectory(), request))) {
int status = httpResponse.getStatusLine().getStatusCode();
if (status < 200 || status >= 300) {
throw new InternalServerError("REST backend: Error: " + IOUtils.toString(httpResponse.getEntity().getContent(), StandardCharsets.UTF_8));
}
UserDirectorySearchResult response = parser.parse(httpResponse, UserDirectorySearchResult.class);
for (UserDirectorySearchResult.Result result : response.getResults()) {
result.setUserId(new MatrixID(result.getUserId(), mxCfg.getDomain()).getId());
}
return response;
} catch (IOException e) {
throw new InternalServerError("REST backend: I/O error: " + e.getMessage());
}
}
@Override
public UserDirectorySearchResult searchByDisplayName(String query) {
return search("name", query);
}
@Override
public UserDirectorySearchResult searchBy3pid(String query) {
return search("threepid", query);
}
}

View File

@@ -22,12 +22,21 @@ package io.kamax.mxisd.controller.directory.v1.io;
public class UserDirectorySearchRequest { public class UserDirectorySearchRequest {
private String by;
private String searchTerm; private String searchTerm;
public UserDirectorySearchRequest(String searchTerm) { public UserDirectorySearchRequest(String searchTerm) {
setSearchTerm(searchTerm); setSearchTerm(searchTerm);
} }
public String getBy() {
return by;
}
public void setBy(String by) {
this.by = by;
}
public String getSearchTerm() { public String getSearchTerm() {
return searchTerm; return searchTerm;
} }

View File

@@ -0,0 +1,167 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.rest;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.rest.RestBackendConfig;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class RestDirectoryProviderTest {
@Rule
public WireMockRule wireMockRule = new WireMockRule(65000);
private RestDirectoryProvider p;
private String domain = "example.org";
private String endpoint = "/directory/search";
private String byNameSearch = "doe";
private String byNameAvatar = "http://domain.tld/path/to/avatar.png";
private String byNameDisplay = "John Doe";
private String byNameId = "john.doe";
private String byNameRequest = "{\"by\":\"name\",\"search_term\":\"" + byNameSearch + "\"}";
private String byNameResponse = "{\"limited\":false,\"results\":[{\"avatar_url\":\"" + byNameAvatar +
"\",\"display_name\":\"" + byNameDisplay + "\",\"user_id\":\"" + byNameId + "\"}]}";
private String byNameEmptyResponse = "{\"limited\":false,\"results\":[]}";
private String byThreepidSearch = "jane";
private String byThreepidAvatar = "http://domain.tld/path/to/avatar.png";
private String byThreepidDisplay = "John Doe";
private String byThreepidId = "john.doe";
private String byThreepidRequest = "{\"by\":\"threepid\",\"search_term\":\"" + byThreepidSearch + "\"}";
private String byThreepidResponse = "{\"limited\":false,\"results\":[{\"avatar_url\":\"" + byThreepidAvatar +
"\",\"display_name\":\"" + byThreepidDisplay + "\",\"user_id\":\"" + byThreepidId + "\"}]}";
private String byThreepidEmptyResponse = "{\"limited\":false,\"results\":[]}";
@Before
public void before() {
MatrixConfig mxCfg = new MatrixConfig();
mxCfg.setDomain(domain);
mxCfg.build();
RestBackendConfig cfg = new RestBackendConfig();
cfg.setEnabled(true);
cfg.setHost("http://localhost:65000");
cfg.getEndpoints().setDirectory(endpoint);
cfg.build();
p = new RestDirectoryProvider(cfg, mxCfg);
}
@Test
public void byNameFound() {
stubFor(post(urlEqualTo(endpoint))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(byNameResponse)
)
);
UserDirectorySearchResult result = p.searchByDisplayName(byNameSearch);
assertTrue(!result.isLimited());
assertTrue(result.getResults().size() == 1);
UserDirectorySearchResult.Result entry = result.getResults().get(0);
assertNotNull(entry);
assertTrue(StringUtils.equals(byNameAvatar, entry.getAvatarUrl()));
assertTrue(StringUtils.equals(byNameDisplay, entry.getDisplayName()));
assertTrue(StringUtils.equals(new MatrixID(byNameId, domain).getId(), entry.getUserId()));
verify(postRequestedFor(urlMatching(endpoint))
.withHeader("Content-Type", containing("application/json"))
.withRequestBody(equalTo(byNameRequest))
);
}
@Test
public void byNameNotFound() {
stubFor(post(urlEqualTo(endpoint))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(byNameEmptyResponse)
)
);
UserDirectorySearchResult result = p.searchByDisplayName(byNameSearch);
assertTrue(!result.isLimited());
assertTrue(result.getResults().isEmpty());
verify(postRequestedFor(urlMatching(endpoint))
.withHeader("Content-Type", containing("application/json"))
.withRequestBody(equalTo(byNameRequest))
);
}
@Test
public void byThreepidFound() {
stubFor(post(urlEqualTo(endpoint))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(new String(byThreepidResponse.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8))
)
);
UserDirectorySearchResult result = p.searchBy3pid(byThreepidSearch);
assertTrue(!result.isLimited());
assertTrue(result.getResults().size() == 1);
UserDirectorySearchResult.Result entry = result.getResults().get(0);
assertNotNull(entry);
assertTrue(StringUtils.equals(byThreepidAvatar, entry.getAvatarUrl()));
assertTrue(StringUtils.equals(byThreepidDisplay, entry.getDisplayName()));
assertTrue(StringUtils.equals(new MatrixID(byThreepidId, domain).getId(), entry.getUserId()));
verify(postRequestedFor(urlMatching(endpoint))
.withHeader("Content-Type", containing("application/json"))
.withRequestBody(equalTo(byThreepidRequest))
);
}
@Test
public void byThreepidNotFound() {
stubFor(post(urlEqualTo(endpoint))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(byThreepidEmptyResponse)
)
);
UserDirectorySearchResult result = p.searchBy3pid(byThreepidSearch);
assertTrue(!result.isLimited());
assertTrue(result.getResults().isEmpty());
verify(postRequestedFor(urlMatching(endpoint))
.withHeader("Content-Type", containing("application/json"))
.withRequestBody(equalTo(byThreepidRequest))
);
}
}

View File

@@ -53,7 +53,7 @@ public class RestThreePidProviderTest {
cfg.setEnabled(true); cfg.setEnabled(true);
cfg.setHost("http://localhost:65000"); cfg.setHost("http://localhost:65000");
cfg.getEndpoints().getIdentity().setSingle(lookupSinglePath); cfg.getEndpoints().getIdentity().setSingle(lookupSinglePath);
cfg.getEndpoints().getIdentity().setBulk("/lookup/bulk"); cfg.getEndpoints().getIdentity().setBulk(lookupBulkPath);
cfg.build(); cfg.build();
p = new RestThreePidProvider(cfg, mxCfg); p = new RestThreePidProvider(cfg, mxCfg);