From 0fc51088175db7092e948d69c0c2f0e88eb5df0f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 31 Oct 2019 11:58:31 +0000 Subject: [PATCH] Add a prompt when interacting with an identity server without terms This adds a prompt whenever we are about to perform some action on a default identity server (from homeserver .well-known or Riot app config) without terms. This allows the user to abort or trust the server (storing it in account data). Fixes https://github.com/vector-im/riot-web/issues/10557 --- src/IdentityAuthClient.js | 54 +++++++++++++++++++- src/components/views/settings/SetIdServer.js | 18 ++----- src/i18n/strings/en_EN.json | 6 ++- src/utils/IdentityServerUtils.js | 22 ++++++++ 4 files changed, 81 insertions(+), 19 deletions(-) diff --git a/src/IdentityAuthClient.js b/src/IdentityAuthClient.js index 7cbad074bf..563e5e0441 100644 --- a/src/IdentityAuthClient.js +++ b/src/IdentityAuthClient.js @@ -17,7 +17,18 @@ limitations under the License. import { createClient, SERVICE_TYPES } from 'matrix-js-sdk'; import MatrixClientPeg from './MatrixClientPeg'; +import Modal from './Modal'; +import sdk from './index'; +import { _t } from './languageHandler'; import { Service, startTermsFlow, TermsNotSignedError } from './Terms'; +import { + doesAccountDataHaveIdentityServer, + doesIdentityServerHaveTerms, + useDefaultIdentityServer, +} from './utils/IdentityServerUtils'; +import { abbreviateUrl } from './utils/UrlUtils'; + +export class AbortedIdentityActionError extends Error {} export default class IdentityAuthClient { /** @@ -89,7 +100,10 @@ export default class IdentityAuthClient { try { await this._checkToken(token); } catch (e) { - if (e instanceof TermsNotSignedError) { + if ( + e instanceof TermsNotSignedError || + e instanceof AbortedIdentityActionError + ) { // Retrying won't help this throw e; } @@ -106,6 +120,8 @@ export default class IdentityAuthClient { } async _checkToken(token) { + const identityServerUrl = this._matrixClient.getIdentityServerUrl(); + try { await this._matrixClient.getIdentityAccount(token); } catch (e) { @@ -113,7 +129,7 @@ export default class IdentityAuthClient { console.log("Identity Server requires new terms to be agreed to"); await startTermsFlow([new Service( SERVICE_TYPES.IS, - this._matrixClient.getIdentityServerUrl(), + identityServerUrl, token, )]); return; @@ -121,6 +137,40 @@ export default class IdentityAuthClient { throw e; } + if ( + !this.tempClient && + !doesAccountDataHaveIdentityServer() && + !await doesIdentityServerHaveTerms(identityServerUrl) + ) { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + const { finished } = Modal.createTrackedDialog('Default identity server terms warning', '', + QuestionDialog, { + title: _t("Identity server has no terms of service"), + description:
+

{_t( + "This action requires accessing the default identity server " + + " to validate an email address or phone number, but the server " + + "does not have any terms of service.", {}, + { + server: () => {abbreviateUrl(identityServerUrl)}, + }, + )}

+

{_t( + "Only continue if you trust the owner of the server.", + )}

+
, + button: _t("Trust"), + }); + const [confirmed] = await finished; + if (confirmed) { + useDefaultIdentityServer(); + } else { + throw new AbortedIdentityActionError( + "User aborted identity server action without terms", + ); + } + } + // We should ensure the token in `localStorage` is cleared // appropriately. We already clear storage on sign out, but we'll need // additional clearing when changing ISes in settings as part of future diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index 7f4a50d391..126cdc9557 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -24,9 +24,8 @@ import Modal from '../../../Modal'; import dis from "../../../dispatcher"; import { getThreepidsWithBindStatus } from '../../../boundThreepids'; import IdentityAuthClient from "../../../IdentityAuthClient"; -import {SERVICE_TYPES} from "matrix-js-sdk"; import {abbreviateUrl, unabbreviateUrl} from "../../../utils/UrlUtils"; -import { getDefaultIdentityServerUrl } from '../../../utils/IdentityServerUtils'; +import { getDefaultIdentityServerUrl, doesIdentityServerHaveTerms } from '../../../utils/IdentityServerUtils'; // We'll wait up to this long when checking for 3PID bindings on the IS. const REACHABILITY_TIMEOUT = 10000; // ms @@ -162,19 +161,8 @@ export default class SetIdServer extends React.Component { let save = true; // Double check that the identity server even has terms of service. - let terms; - try { - terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl); - } catch (e) { - console.error(e); - if (e.cors === "rejected" || e.httpStatus === 404) { - terms = null; - } else { - throw e; - } - } - - if (!terms || !terms["policies"] || Object.keys(terms["policies"]).length <= 0) { + const hasTerms = await doesIdentityServerHaveTerms(fullUrl); + if (!hasTerms) { const [confirmed] = await this._showNoTermsWarning(fullUrl); save = confirmed; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 31602d2dab..69f68b2497 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -99,6 +99,10 @@ "Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s", "Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:", "Unnamed Room": "Unnamed Room", + "Identity server has no terms of service": "Identity server has no terms of service", + "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.", + "Only continue if you trust the owner of the server.": "Only continue if you trust the owner of the server.", + "Trust": "Trust", "Error": "Error", "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", "Dismiss": "Dismiss", @@ -563,9 +567,7 @@ "Change identity server": "Change identity server", "Disconnect from the identity server and connect to instead?": "Disconnect from the identity server and connect to instead?", "Terms of service not accepted or the identity server is invalid.": "Terms of service not accepted or the identity server is invalid.", - "Identity server has no terms of service": "Identity server has no terms of service", "The identity server you have chosen does not have any terms of service.": "The identity server you have chosen does not have any terms of service.", - "Only continue if you trust the owner of the server.": "Only continue if you trust the owner of the server.", "Disconnect identity server": "Disconnect identity server", "Disconnect from the identity server ?": "Disconnect from the identity server ?", "Disconnect": "Disconnect", diff --git a/src/utils/IdentityServerUtils.js b/src/utils/IdentityServerUtils.js index 883bd52149..cf180e3026 100644 --- a/src/utils/IdentityServerUtils.js +++ b/src/utils/IdentityServerUtils.js @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { SERVICE_TYPES } from 'matrix-js-sdk'; import SdkConfig from '../SdkConfig'; import MatrixClientPeg from '../MatrixClientPeg'; @@ -28,3 +29,24 @@ export function useDefaultIdentityServer() { base_url: url, }); } + +export async function doesIdentityServerHaveTerms(fullUrl) { + let terms; + try { + terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl); + } catch (e) { + console.error(e); + if (e.cors === "rejected" || e.httpStatus === 404) { + terms = null; + } else { + throw e; + } + } + + return terms && terms["policies"] && (Object.keys(terms["policies"]).length > 0); +} + +export function doesAccountDataHaveIdentityServer() { + const event = MatrixClientPeg.get().getAccountData("m.identity_server"); + return event && event.getContent() && event.getContent()['base_url']; +}