Browse Source

Implement an internal 'bind' API (#92)

Richard van der Hoff 5 years ago
parent
commit
972c57a007

+ 19 - 0
README.rst

@@ -56,3 +56,22 @@ Lookup::
 Fetch pubkey key for a server::
 
     curl http://localhost:8090/_matrix/identity/api/v1/pubkey/ed25519:0
+
+Internal bind api
+-----------------
+
+It is possible to enable an internal API which allows identifiers to be bound
+to matrix IDs without any validation. This is open to abuse, so is disabled by
+default, and when it is enabled, is available only on a separate socket which
+is bound to 'localhost' by default.
+
+To enable it, configure the port in the config file. For example::
+
+    [http]
+    internalapi.http.port = 8091
+
+To use it::
+
+    curl -XPOST 'http://localhost:8091/_matrix/identity/internal/bind' -H "Content-Type: application/json" -d '{"address": "matthew@arasphere.net", "medium": "email", "mxid": "@matthew:matrix.org"}'
+
+The response has the same format as ``/_matrix/identity/api/v1/3pid/bind``.

+ 28 - 0
sydent/http/httpserver.py

@@ -22,6 +22,10 @@ import logging
 import twisted.internet.reactor
 import twisted.internet.ssl
 
+from sydent.http.servlets.authenticated_bind_threepid_servlet import (
+    AuthenticatedBindThreePidServlet,
+)
+
 logger = logging.getLogger(__name__)
 
 
@@ -97,6 +101,30 @@ class ClientApiHttpServer:
         twisted.internet.reactor.listenTCP(httpPort, self.factory)
 
 
+class InternalApiHttpServer(object):
+    def __init__(self, sydent):
+        self.sydent = sydent
+
+    def setup(self, interface, port):
+        logger.info("Starting Internal API HTTP server on %s:%d", interface, port)
+        root = Resource()
+
+        matrix = Resource()
+        root.putChild('_matrix', matrix)
+
+        identity = Resource()
+        matrix.putChild('identity', identity)
+
+        internal = Resource()
+        identity.putChild('internal', internal)
+
+        authenticated_bind = AuthenticatedBindThreePidServlet(self.sydent)
+        internal.putChild('bind', authenticated_bind)
+
+        factory = Site(root)
+        twisted.internet.reactor.listenTCP(port, factory)
+
+
 class ReplicationHttpsServer:
     def __init__(self, sydent):
         self.sydent = sydent

+ 45 - 0
sydent/http/servlets/authenticated_bind_threepid_servlet.py

@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2018 New Vector Ltd
+#
+# 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.
+
+from twisted.web.resource import Resource
+
+from sydent.http.servlets import get_args, jsonwrap, send_cors
+
+
+class AuthenticatedBindThreePidServlet(Resource):
+    """A servlet which allows a caller to bind any 3pid they want to an mxid
+
+    It is assumed that authentication happens out of band
+    """
+    def __init__(self, sydent):
+        Resource.__init__(self)
+        self.sydent = sydent
+
+    @jsonwrap
+    def render_POST(self, request):
+        send_cors(request)
+        err, args = get_args(request, ('medium', 'address', 'mxid'))
+        if err:
+            return err
+        return self.sydent.threepidBinder.addBinding(
+            args['medium'], args['address'], args['mxid'],
+        )
+
+    @jsonwrap
+    def render_OPTIONS(self, request):
+        send_cors(request)
+        request.setResponseCode(200)
+        return {}

+ 5 - 2
sydent/http/servlets/threepidbindservlet.py

@@ -16,6 +16,7 @@
 
 from twisted.web.resource import Resource
 
+from sydent.db.valsession import ThreePidValSessionStore
 from sydent.http.servlets import get_args, jsonwrap, send_cors
 from sydent.validators import SessionExpiredException, IncorrectClientSecretException, InvalidSessionIdException,\
     SessionNotValidatedException
@@ -41,8 +42,8 @@ class ThreePidBindServlet(Resource):
                         'error': "No valid session was found matching that sid and client secret"}
 
         try:
-            res = self.sydent.threepidBinder.addBinding(sid, clientSecret, mxid)
-            return res
+            valSessionStore = ThreePidValSessionStore(self.sydent)
+            s = valSessionStore.getValidatedSession(sid, clientSecret)
         except IncorrectClientSecretException:
             return noMatchError
         except SessionExpiredException:
@@ -54,6 +55,8 @@ class ThreePidBindServlet(Resource):
             return {'errcode': 'M_SESSION_NOT_VALIDATED',
                     'error': "This validation session has not yet been completed"}
 
+        res = self.sydent.threepidBinder.addBinding(s.medium, s.address, mxid)
+        return res
 
     @jsonwrap
     def render_OPTIONS(self, request):

+ 14 - 1
sydent/sydent.py

@@ -26,7 +26,10 @@ from twisted.python import log
 from db.sqlitedb import SqliteDatabase
 
 from http.httpcommon import SslComponents
-from http.httpserver import ClientApiHttpServer, ReplicationHttpsServer
+from http.httpserver import (
+    ClientApiHttpServer, ReplicationHttpsServer,
+    InternalApiHttpServer,
+)
 from http.httpsclient import ReplicationHttpsClient
 from http.servlets.blindlysignstuffservlet import BlindlySignStuffServlet
 from http.servlets.pubkeyservlets import EphemeralPubkeyIsValidServlet, PubkeyIsValidServlet
@@ -65,6 +68,7 @@ CONFIG_DEFAULTS = {
     },
     'http': {
         'clientapi.http.port': '8090',
+        'internalapi.http.port': '',
         'replication.https.certfile': '',
         'replication.https.cacert': '', # This should only be used for testing
         'replication.https.port': '4434',
@@ -186,6 +190,15 @@ class Sydent:
         self.replicationHttpsServer.setup()
         self.pusher.setup()
 
+        internalport = self.cfg.get('http', 'internalapi.http.port')
+        if internalport:
+            try:
+                interface = self.cfg.get('http', 'internalapi.http.bind_address')
+            except ConfigParser.NoOptionError:
+                interface = '::1'
+            self.internalApiHttpServer = InternalApiHttpServer(self)
+            self.internalApiHttpServer.setup(interface, int(internalport))
+
         if self.pidfile:
             with open(self.pidfile, 'w') as pidfile:
                 pidfile.write(str(os.getpid()) + "\n")

+ 15 - 9
sydent/threepid/bind.py

@@ -22,7 +22,6 @@ import random
 import signedjson.sign
 from sydent.db.invite_tokens import JoinTokenStore
 
-from sydent.db.valsession import ThreePidValSessionStore
 from sydent.db.threepid_associations import LocalAssociationStore
 
 from sydent.util import time_msec
@@ -41,6 +40,7 @@ from twisted.web.http_headers import Headers
 
 logger = logging.getLogger(__name__)
 
+
 class ThreepidBinder:
     # the lifetime of a 3pid association
     THREEPID_ASSOCIATION_LIFETIME_MS = 100 * 365 * 24 * 60 * 60 * 1000
@@ -48,23 +48,29 @@ class ThreepidBinder:
     def __init__(self, sydent):
         self.sydent = sydent
 
-    def addBinding(self, valSessionId, clientSecret, mxid):
-        valSessionStore = ThreePidValSessionStore(self.sydent)
-        localAssocStore = LocalAssociationStore(self.sydent)
+    def addBinding(self, medium, address, mxid):
+        """Binds the given 3pid to the given mxid.
 
-        s = valSessionStore.getValidatedSession(valSessionId, clientSecret)
+        It's assumed that we have somehow validated that the given user owns
+        the given 3pid
+
+        Args:
+            medium (str): the type of 3pid
+            address (str): the 3pid
+            mxid (str): the mxid to bind it to
+        """
+        localAssocStore = LocalAssociationStore(self.sydent)
 
         createdAt = time_msec()
         expires = createdAt + ThreepidBinder.THREEPID_ASSOCIATION_LIFETIME_MS
-
-        assoc = ThreepidAssociation(s.medium, s.address, mxid, createdAt, createdAt, expires)
+        assoc = ThreepidAssociation(medium, address, mxid, createdAt, createdAt, expires)
 
         localAssocStore.addOrUpdateAssociation(assoc)
 
         self.sydent.pusher.doLocalPush()
 
         joinTokenStore = JoinTokenStore(self.sydent)
-        pendingJoinTokens = joinTokenStore.getTokens(s.medium, s.address)
+        pendingJoinTokens = joinTokenStore.getTokens(medium, address)
         invites = []
         for token in pendingJoinTokens:
             token["mxid"] = mxid
@@ -76,7 +82,7 @@ class ThreepidBinder:
             invites.append(token)
         if invites:
             assoc.extra_fields["invites"] = invites
-            joinTokenStore.markTokensAsSent(s.medium, s.address)
+            joinTokenStore.markTokensAsSent(medium, address)
 
         assocSigner = AssociationSigner(self.sydent)
         sgassoc = assocSigner.signedThreePidAssociation(assoc)