1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-25 05:23:13 +03:00

Use the v2 (hashed) lookup for identity server queries

Fixes https://github.com/vector-im/riot-web/issues/10556
Implements [MSC2134](https://github.com/matrix-org/matrix-doc/pull/2134) with assistance from [MSC2140](https://github.com/matrix-org/matrix-doc/pull/2140) for auth.

Note: this also changes all identity server requests to use JSON as a request body. URL encoded forms were allowed in v1 but deprecated in favour of JSON. v2 APIs do not allow URL encoded forms.
This commit is contained in:
Travis Ralston
2019-08-21 14:08:46 -06:00
parent 898fa0e41b
commit 8a0ddc43ab
2 changed files with 144 additions and 19 deletions

View File

@@ -1857,6 +1857,93 @@ MatrixBaseApis.prototype.submitMsisdnToken = async function(
} }
}; };
/**
* Gets the V2 hashing information from the identity server. Primarily useful for
* lookups.
* @param identityAccessToken The access token for the identity server.
* @returns {Promise<object>} The hashing information for the identity server.
*/
MatrixBaseApis.prototype.getIdentityHashDetails = function(identityAccessToken) {
return this._http.idServerRequest(
undefined, "GET", "/hash_details",
null, httpApi.PREFIX_IDENTITY_V2, identityAccessToken,
);
};
/**
* Performs a hashed lookup of addresses against the identity server. This is
* only supported on identity servers which have at least the version 2 API.
* @param addressPairs An array of 2 element arrays. The first element of each
* pair is the address, the second is the 3PID medium. Eg: ["email@example.org",
* "email"]
* @param identityAccessToken The access token for the identity server.
* @returns {Promise<{address, mxid}[]>} A collection of address mappings to
* found MXIDs. Results where no user could be found will not be listed.
*/
MatrixBaseApis.prototype.identityHashedLookup = async function(
addressPairs, // [["email@example.org", "email"], ["10005550000", "msisdn"]]
identityAccessToken,
) {
const params = {
// addresses: ["email@example.org", "10005550000"],
// algorithm: "sha256",
// pepper: "abc123"
};
// Get hash information first before trying to do a lookup
const hashes = await this.getIdentityHashDetails();
if (!hashes || !hashes['lookup_pepper'] || !hashes['algorithms']) {
throw new Error("Unsupported identity server: bad response");
}
params['pepper'] = hashes['lookup_pepper'];
const localMapping = {
// hashed identifier => plain text address
// For use in this function's return format
};
// When picking an algorithm, we pick the hashed over no hashes
if (hashes['algorithms'].includes('sha256')) {
// Abuse the olm hashing
const olmutil = new global.Olm.Utility();
params["addresses"] = addressPairs.map(p => {
const hashed = olmutil.sha256(`${p[0]} ${p[1]} ${params['pepper']}`);
localMapping[hashed] = p[0];
return hashed;
});
params["algorithm"] = "sha256";
} else if (hashes['algorithms'].includes('none')) {
params["addresses"] = addressPairs.map(p => {
const unhashed = `${p[0]} ${p[1]}`;
localMapping[unhashed] = p[0];
return unhashed;
});
params["algorithm"] = "none";
} else {
throw new Error("Unsupported identity server: unknown hash algorithm");
}
const response = await this._http.idServerRequest(
undefined, "POST", "/lookup",
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken,
);
if (!response || !response['mappings']) return []; // no results
const foundAddresses = [/* {address: "plain@example.org", mxid} */];
for (const hashed of Object.keys(response['mappings'])) {
const mxid = response['mappings'][hashed];
const plainAddress = localMapping[hashed];
if (!plainAddress) {
throw new Error("Identity server returned more results than expected");
}
foundAddresses.push({address: plainAddress, mxid});
}
return foundAddresses;
};
/** /**
* Looks up the public Matrix ID mapping for a given 3rd party * Looks up the public Matrix ID mapping for a given 3rd party
* identifier from the Identity Server * identifier from the Identity Server
@@ -1878,31 +1965,51 @@ MatrixBaseApis.prototype.lookupThreePid = async function(
callback, callback,
identityAccessToken, identityAccessToken,
) { ) {
const params = { try {
medium: medium, // Note: we're using the V2 API by calling this function, but our
address: address, // function contract requires a V1 response. We therefore have to
// convert it manually.
const response = await this.identityHashedLookup(
[[address, medium]], identityAccessToken,
);
const result = response.find(p => p.address === address);
if (!result) {
// TODO: Fold callback into above call once v1 path below is removed
if (callback) callback(null, {});
return {};
}
const mapping = {
address,
medium,
mxid: result.mxid,
// We can't reasonably fill these parameters:
// not_before
// not_after
// ts
// signatures
}; };
try {
const response = await this._http.idServerRequest(
undefined, "GET", "/lookup",
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken,
);
// TODO: Fold callback into above call once v1 path below is removed // TODO: Fold callback into above call once v1 path below is removed
if (callback) callback(null, response); if (callback) callback(null, mapping);
return response; return mapping;
} catch (err) { } catch (err) {
if (err.cors === "rejected" || err.httpStatus === 404) { if (err.cors === "rejected" || err.httpStatus === 404) {
// Fall back to deprecated v1 API for now // Fall back to deprecated v1 API for now
// TODO: Remove this path once v2 is only supported version // TODO: Remove this path once v2 is only supported version
// See https://github.com/vector-im/riot-web/issues/10443 // See https://github.com/vector-im/riot-web/issues/10443
const params = {
medium: medium,
address: address,
};
logger.warn("IS doesn't support v2, falling back to deprecated v1"); logger.warn("IS doesn't support v2, falling back to deprecated v1");
return await this._http.idServerRequest( return await this._http.idServerRequest(
callback, "GET", "/lookup", callback, "GET", "/lookup",
params, httpApi.PREFIX_IDENTITY_V1, params, httpApi.PREFIX_IDENTITY_V1,
); );
} }
if (callback) callback(err); if (callback) callback(err, undefined);
throw err; throw err;
} }
}; };
@@ -1922,20 +2029,38 @@ MatrixBaseApis.prototype.bulkLookupThreePids = async function(
query, query,
identityAccessToken, identityAccessToken,
) { ) {
const params = {
threepids: query,
};
try { try {
return await this._http.idServerRequest( // Note: we're using the V2 API by calling this function, but our
undefined, "POST", "/bulk_lookup", JSON.stringify(params), // function contract requires a V1 response. We therefore have to
httpApi.PREFIX_IDENTITY_V2, identityAccessToken, // convert it manually.
const response = await this.identityHashedLookup(
// We have to reverse the query order to get [address, medium] pairs
query.map(p => [p[1], p[0]]), identityAccessToken,
); );
const v1results = [];
for (const mapping of response) {
const originalQuery = query.find(p => p[1] === mapping.address);
if (!originalQuery) {
throw new Error("Identity sever returned unexpected results");
}
v1results.push([
originalQuery[0], // medium
mapping.address,
mapping.mxid,
]);
}
return {threepids: v1results};
} catch (err) { } catch (err) {
if (err.cors === "rejected" || err.httpStatus === 404) { if (err.cors === "rejected" || err.httpStatus === 404) {
// Fall back to deprecated v1 API for now // Fall back to deprecated v1 API for now
// TODO: Remove this path once v2 is only supported version // TODO: Remove this path once v2 is only supported version
// See https://github.com/vector-im/riot-web/issues/10443 // See https://github.com/vector-im/riot-web/issues/10443
const params = {
threepids: query,
};
logger.warn("IS doesn't support v2, falling back to deprecated v1"); logger.warn("IS doesn't support v2, falling back to deprecated v1");
return await this._http.idServerRequest( return await this._http.idServerRequest(
undefined, "POST", "/bulk_lookup", JSON.stringify(params), undefined, "POST", "/bulk_lookup", JSON.stringify(params),

View File

@@ -412,7 +412,7 @@ module.exports.MatrixHttpApi.prototype = {
if (method == 'GET') { if (method == 'GET') {
opts.qs = params; opts.qs = params;
} else if (typeof params === "object") { } else if (typeof params === "object") {
opts.form = params; opts.json = params;
} else if (typeof params === "string") { } else if (typeof params === "string") {
// Assume the caller has serialised the body to JSON // Assume the caller has serialised the body to JSON
opts.body = params; opts.body = params;