Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Use the v2 lookup API for 3PID invites #5897

Merged
merged 14 commits into from
Aug 28, 2019
1 change: 1 addition & 0 deletions changelog.d/5897.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Switch to the v2 lookup API for 3PID invites.
14 changes: 14 additions & 0 deletions synapse/handlers/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"""Utilities for interacting with Identity Servers"""

import logging
from enum import Enum

from canonicaljson import json

Expand Down Expand Up @@ -282,3 +283,16 @@ def requestMsisdnToken(
except HttpResponseException as e:
logger.info("Proxied requestToken failed: %r", e)
raise e.to_synapse_error()


class LookupAlgorithm(Enum):
"""
Supported hashing algorithms when performing a 3PID lookup.

SHA256 - Hashing an (address, medium, pepper) combo with sha256, then url-safe base64
encoding
NONE - Not performing any hashing. Simply sending an (address, medium) combo in plaintext
"""

SHA256 = "sha256"
NONE = "none"
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
68 changes: 58 additions & 10 deletions synapse/handlers/room_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
from synapse import types
from synapse.api.constants import EventTypes, Membership
from synapse.api.errors import AuthError, Codes, HttpResponseException, SynapseError
from synapse.handlers.identity import LookupAlgorithm
from synapse.types import RoomID, UserID
from synapse.util.async_helpers import Linearizer
from synapse.util.distributor import user_joined_room, user_left_room
from synapse.util.hash import sha256_and_url_safe_base64

from ._base import BaseHandler

Expand Down Expand Up @@ -697,22 +699,68 @@ def _lookup_3pid(self, id_server, medium, address):
raise SynapseError(
403, "Looking up third-party identifiers is denied from this server"
)

# Check what hashing details are supported by this identity server
try:
data = yield self.simple_http_client.get_json(
"%s%s/_matrix/identity/api/v1/lookup" % (id_server_scheme, id_server),
{"medium": medium, "address": address},
hash_details = yield self.simple_http_client.get_json(
"%s%s/_matrix/identity/v2/hash_details" % (id_server_scheme, id_server)
)
supported_lookup_algorithms = hash_details["algorithms"]
lookup_pepper = hash_details["lookup_pepper"]
except (HttpResponseException, ValueError) as e:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why catch ValueError?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A ValueError is returned by SimpleHttpClient.get_json if the response was not JSON, which may be the case on a 404 error? I'm not sure if it prioritizes a 404 error code over receiving non-JSON from the server, but even a non-JSON 200 error is unusable.

Though perhaps it could be argued that we should just fail the request in that case versus falling back to v1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, that's annoying.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added comments explaining the reason for catching these exception types.

logger.warn("Error when looking up hashing details: %s" % (e,))
return None

if "mxid" in data:
if "signatures" not in data:
raise AuthError(401, "No signatures on 3pid binding")
yield self._verify_any_signature(data, id_server)
return data["mxid"]
# Check if none of the supported lookup algorithms are present
if not any(
i in supported_lookup_algorithms
for i in [LookupAlgorithm.SHA256, LookupAlgorithm.NONE]
):
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
logger.warn(
"No supported lookup algorithms found for %s%s"
% (id_server_scheme, id_server)
)

except IOError as e:
logger.warn("Error from identity server lookup: %s" % (e,))
return None

if LookupAlgorithm.SHA256 in supported_lookup_algorithms:
# Perform a hashed lookup
lookup_algorithm = LookupAlgorithm.SHA256

# Hash address, medium and the pepper with sha256
to_hash = "%s %s %s" % (address, medium, lookup_pepper)
lookup_value = sha256_and_url_safe_base64(to_hash)

elif LookupAlgorithm.NONE in supported_lookup_algorithms:
# Perform a non-hashed lookup
lookup_algorithm = LookupAlgorithm.NONE

# Combine together plaintext address and medium
lookup_value = "%s %s" % (address, medium)

anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
try:
lookup_results = yield self.simple_http_client.post_json_get_json(
"%s%s/_matrix/identity/v2/lookup" % (id_server_scheme, id_server),
{
"addresses": [lookup_value],
"algorithm": lookup_algorithm,
"pepper": lookup_pepper,
},
)
except (HttpResponseException, ValueError) as e:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ValueError?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the same reason as #5897 (comment)

logger.warn("Error when performing a 3pid lookup: %s" % (e,))
return None

# Check for a mapping from what we looked up to an MXID
if "mappings" not in lookup_results or not isinstance(
lookup_results["mappings"], dict
):
logger.debug("No results from 3pid lookup")
return None

# Return the MXID if it's available, or None otherwise
return lookup_results["mappings"].get(lookup_value)

@defer.inlineCallbacks
def _verify_any_signature(self, data, server_hostname):
if server_hostname not in data["signatures"]:
Expand Down
33 changes: 33 additions & 0 deletions synapse/util/hash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-

# Copyright 2019 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import hashlib

import unpaddedbase64


def sha256_and_url_safe_base64(input_text):
"""SHA256 hash an input string, encode the digest as url-safe base64, and
return

:param input_text: string to hash
:type input_text: str

:returns a sha256 hashed and url-safe base64 encoded digest
:rtype: str
"""
digest = hashlib.sha256(input_text.encode()).digest()
return unpaddedbase64.encode_base64(digest, urlsafe=True)