Merge pull request #4420 from matrix-org/jaywink/openid-listener
New listener resource for the federation API "openid/userinfo" endpointpull/4608/head
commit
b201149c7e
|
@ -0,0 +1 @@
|
|||
Federation OpenID listener resource can now be activated even if federation is disabled
|
|
@ -86,6 +86,16 @@ class FederationReaderServer(HomeServer):
|
|||
resources.update({
|
||||
FEDERATION_PREFIX: TransportLayerServer(self),
|
||||
})
|
||||
if name == "openid" and "federation" not in res["names"]:
|
||||
# Only load the openid resource separately if federation resource
|
||||
# is not specified since federation resource includes openid
|
||||
# resource.
|
||||
resources.update({
|
||||
FEDERATION_PREFIX: TransportLayerServer(
|
||||
self,
|
||||
servlet_groups=["openid"],
|
||||
),
|
||||
})
|
||||
|
||||
root_resource = create_resource_tree(resources, NoResource())
|
||||
|
||||
|
@ -98,7 +108,8 @@ class FederationReaderServer(HomeServer):
|
|||
listener_config,
|
||||
root_resource,
|
||||
self.version_string,
|
||||
)
|
||||
),
|
||||
reactor=self.get_reactor()
|
||||
)
|
||||
|
||||
logger.info("Synapse federation reader now listening on port %d", port)
|
||||
|
|
|
@ -99,6 +99,10 @@ class SynapseHomeServer(HomeServer):
|
|||
resources = {}
|
||||
for res in listener_config["resources"]:
|
||||
for name in res["names"]:
|
||||
if name == "openid" and "federation" in res["names"]:
|
||||
# Skip loading openid resource if federation is defined
|
||||
# since federation resource will include openid
|
||||
continue
|
||||
resources.update(self._configure_named_resource(
|
||||
name, res.get("compress", False),
|
||||
))
|
||||
|
@ -134,6 +138,7 @@ class SynapseHomeServer(HomeServer):
|
|||
self.version_string,
|
||||
),
|
||||
self.tls_server_context_factory,
|
||||
reactor=self.get_reactor(),
|
||||
)
|
||||
|
||||
else:
|
||||
|
@ -146,7 +151,8 @@ class SynapseHomeServer(HomeServer):
|
|||
listener_config,
|
||||
root_resource,
|
||||
self.version_string,
|
||||
)
|
||||
),
|
||||
reactor=self.get_reactor(),
|
||||
)
|
||||
|
||||
def _configure_named_resource(self, name, compress=False):
|
||||
|
@ -193,6 +199,11 @@ class SynapseHomeServer(HomeServer):
|
|||
FEDERATION_PREFIX: TransportLayerServer(self),
|
||||
})
|
||||
|
||||
if name == "openid":
|
||||
resources.update({
|
||||
FEDERATION_PREFIX: TransportLayerServer(self, servlet_groups=["openid"]),
|
||||
})
|
||||
|
||||
if name in ["static", "client"]:
|
||||
resources.update({
|
||||
STATIC_PREFIX: File(
|
||||
|
|
|
@ -335,6 +335,11 @@ class ServerConfig(Config):
|
|||
- names: [federation] # Federation APIs
|
||||
compress: false
|
||||
|
||||
# # If federation is disabled synapse can still expose the open ID endpoint
|
||||
# # to allow integrations to authenticate users
|
||||
# - names: [openid]
|
||||
# compress: false
|
||||
|
||||
# optional list of additional endpoints which can be loaded via
|
||||
# dynamic modules
|
||||
# additional_resources:
|
||||
|
@ -356,6 +361,10 @@ class ServerConfig(Config):
|
|||
compress: true
|
||||
- names: [federation]
|
||||
compress: false
|
||||
# # If federation is disabled synapse can still expose the open ID endpoint
|
||||
# # to allow integrations to authenticate users
|
||||
# - names: [openid]
|
||||
# compress: false
|
||||
|
||||
# Turn on the twisted ssh manhole service on localhost on the given
|
||||
# port.
|
||||
|
@ -480,6 +489,7 @@ KNOWN_RESOURCES = (
|
|||
'keys',
|
||||
'media',
|
||||
'metrics',
|
||||
'openid',
|
||||
'replication',
|
||||
'static',
|
||||
'webclient',
|
||||
|
|
|
@ -43,9 +43,20 @@ logger = logging.getLogger(__name__)
|
|||
class TransportLayerServer(JsonResource):
|
||||
"""Handles incoming federation HTTP requests"""
|
||||
|
||||
def __init__(self, hs):
|
||||
def __init__(self, hs, servlet_groups=None):
|
||||
"""Initialize the TransportLayerServer
|
||||
|
||||
Will by default register all servlets. For custom behaviour, pass in
|
||||
a list of servlet_groups to register.
|
||||
|
||||
Args:
|
||||
hs (synapse.server.HomeServer): homeserver
|
||||
servlet_groups (list[str], optional): List of servlet groups to register.
|
||||
Defaults to ``DEFAULT_SERVLET_GROUPS``.
|
||||
"""
|
||||
self.hs = hs
|
||||
self.clock = hs.get_clock()
|
||||
self.servlet_groups = servlet_groups
|
||||
|
||||
super(TransportLayerServer, self).__init__(hs, canonical_json=False)
|
||||
|
||||
|
@ -67,6 +78,7 @@ class TransportLayerServer(JsonResource):
|
|||
resource=self,
|
||||
ratelimiter=self.ratelimiter,
|
||||
authenticator=self.authenticator,
|
||||
servlet_groups=self.servlet_groups,
|
||||
)
|
||||
|
||||
|
||||
|
@ -1308,10 +1320,12 @@ FEDERATION_SERVLET_CLASSES = (
|
|||
FederationClientKeysClaimServlet,
|
||||
FederationThirdPartyInviteExchangeServlet,
|
||||
On3pidBindServlet,
|
||||
OpenIdUserInfo,
|
||||
FederationVersionServlet,
|
||||
)
|
||||
|
||||
OPENID_SERVLET_CLASSES = (
|
||||
OpenIdUserInfo,
|
||||
)
|
||||
|
||||
ROOM_LIST_CLASSES = (
|
||||
PublicRoomList,
|
||||
|
@ -1350,44 +1364,83 @@ GROUP_ATTESTATION_SERVLET_CLASSES = (
|
|||
FederationGroupsRenewAttestaionServlet,
|
||||
)
|
||||
|
||||
DEFAULT_SERVLET_GROUPS = (
|
||||
"federation",
|
||||
"room_list",
|
||||
"group_server",
|
||||
"group_local",
|
||||
"group_attestation",
|
||||
"openid",
|
||||
)
|
||||
|
||||
def register_servlets(hs, resource, authenticator, ratelimiter):
|
||||
for servletclass in FEDERATION_SERVLET_CLASSES:
|
||||
servletclass(
|
||||
handler=hs.get_federation_server(),
|
||||
authenticator=authenticator,
|
||||
ratelimiter=ratelimiter,
|
||||
server_name=hs.hostname,
|
||||
).register(resource)
|
||||
|
||||
for servletclass in ROOM_LIST_CLASSES:
|
||||
servletclass(
|
||||
handler=hs.get_room_list_handler(),
|
||||
authenticator=authenticator,
|
||||
ratelimiter=ratelimiter,
|
||||
server_name=hs.hostname,
|
||||
).register(resource)
|
||||
def register_servlets(hs, resource, authenticator, ratelimiter, servlet_groups=None):
|
||||
"""Initialize and register servlet classes.
|
||||
|
||||
for servletclass in GROUP_SERVER_SERVLET_CLASSES:
|
||||
servletclass(
|
||||
handler=hs.get_groups_server_handler(),
|
||||
authenticator=authenticator,
|
||||
ratelimiter=ratelimiter,
|
||||
server_name=hs.hostname,
|
||||
).register(resource)
|
||||
Will by default register all servlets. For custom behaviour, pass in
|
||||
a list of servlet_groups to register.
|
||||
|
||||
for servletclass in GROUP_LOCAL_SERVLET_CLASSES:
|
||||
servletclass(
|
||||
handler=hs.get_groups_local_handler(),
|
||||
authenticator=authenticator,
|
||||
ratelimiter=ratelimiter,
|
||||
server_name=hs.hostname,
|
||||
).register(resource)
|
||||
Args:
|
||||
hs (synapse.server.HomeServer): homeserver
|
||||
resource (TransportLayerServer): resource class to register to
|
||||
authenticator (Authenticator): authenticator to use
|
||||
ratelimiter (util.ratelimitutils.FederationRateLimiter): ratelimiter to use
|
||||
servlet_groups (list[str], optional): List of servlet groups to register.
|
||||
Defaults to ``DEFAULT_SERVLET_GROUPS``.
|
||||
"""
|
||||
if not servlet_groups:
|
||||
servlet_groups = DEFAULT_SERVLET_GROUPS
|
||||
|
||||
for servletclass in GROUP_ATTESTATION_SERVLET_CLASSES:
|
||||
servletclass(
|
||||
handler=hs.get_groups_attestation_renewer(),
|
||||
authenticator=authenticator,
|
||||
ratelimiter=ratelimiter,
|
||||
server_name=hs.hostname,
|
||||
).register(resource)
|
||||
if "federation" in servlet_groups:
|
||||
for servletclass in FEDERATION_SERVLET_CLASSES:
|
||||
servletclass(
|
||||
handler=hs.get_federation_server(),
|
||||
authenticator=authenticator,
|
||||
ratelimiter=ratelimiter,
|
||||
server_name=hs.hostname,
|
||||
).register(resource)
|
||||
|
||||
if "openid" in servlet_groups:
|
||||
for servletclass in OPENID_SERVLET_CLASSES:
|
||||
servletclass(
|
||||
handler=hs.get_federation_server(),
|
||||
authenticator=authenticator,
|
||||
ratelimiter=ratelimiter,
|
||||
server_name=hs.hostname,
|
||||
).register(resource)
|
||||
|
||||
if "room_list" in servlet_groups:
|
||||
for servletclass in ROOM_LIST_CLASSES:
|
||||
servletclass(
|
||||
handler=hs.get_room_list_handler(),
|
||||
authenticator=authenticator,
|
||||
ratelimiter=ratelimiter,
|
||||
server_name=hs.hostname,
|
||||
).register(resource)
|
||||
|
||||
if "group_server" in servlet_groups:
|
||||
for servletclass in GROUP_SERVER_SERVLET_CLASSES:
|
||||
servletclass(
|
||||
handler=hs.get_groups_server_handler(),
|
||||
authenticator=authenticator,
|
||||
ratelimiter=ratelimiter,
|
||||
server_name=hs.hostname,
|
||||
).register(resource)
|
||||
|
||||
if "group_local" in servlet_groups:
|
||||
for servletclass in GROUP_LOCAL_SERVLET_CLASSES:
|
||||
servletclass(
|
||||
handler=hs.get_groups_local_handler(),
|
||||
authenticator=authenticator,
|
||||
ratelimiter=ratelimiter,
|
||||
server_name=hs.hostname,
|
||||
).register(resource)
|
||||
|
||||
if "group_attestation" in servlet_groups:
|
||||
for servletclass in GROUP_ATTESTATION_SERVLET_CLASSES:
|
||||
servletclass(
|
||||
handler=hs.get_groups_attestation_renewer(),
|
||||
authenticator=authenticator,
|
||||
ratelimiter=ratelimiter,
|
||||
server_name=hs.hostname,
|
||||
).register(resource)
|
||||
|
|
|
@ -85,7 +85,7 @@ CONDITIONAL_REQUIREMENTS = {
|
|||
|
||||
"saml2": ["pysaml2>=4.5.0"],
|
||||
"url_preview": ["lxml>=3.5.0"],
|
||||
"test": ["mock>=2.0"],
|
||||
"test": ["mock>=2.0", "parameterized"],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ class FrontendProxyTests(HomeserverTestCase):
|
|||
|
||||
def test_listen_http_with_presence_disabled(self):
|
||||
"""
|
||||
When presence is on, the stub servlet will register.
|
||||
When presence is off, the stub servlet will register.
|
||||
"""
|
||||
# Presence is off
|
||||
self.hs.config.use_presence = False
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 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 mock import Mock, patch
|
||||
|
||||
from parameterized import parameterized
|
||||
|
||||
from synapse.app.federation_reader import FederationReaderServer
|
||||
from synapse.app.homeserver import SynapseHomeServer
|
||||
|
||||
from tests.unittest import HomeserverTestCase
|
||||
|
||||
|
||||
class FederationReaderOpenIDListenerTests(HomeserverTestCase):
|
||||
def make_homeserver(self, reactor, clock):
|
||||
hs = self.setup_test_homeserver(
|
||||
http_client=None, homeserverToUse=FederationReaderServer,
|
||||
)
|
||||
return hs
|
||||
|
||||
@parameterized.expand([
|
||||
(["federation"], "auth_fail"),
|
||||
([], "no_resource"),
|
||||
(["openid", "federation"], "auth_fail"),
|
||||
(["openid"], "auth_fail"),
|
||||
])
|
||||
def test_openid_listener(self, names, expectation):
|
||||
"""
|
||||
Test different openid listener configurations.
|
||||
|
||||
401 is success here since it means we hit the handler and auth failed.
|
||||
"""
|
||||
config = {
|
||||
"port": 8080,
|
||||
"bind_addresses": ["0.0.0.0"],
|
||||
"resources": [{"names": names}],
|
||||
}
|
||||
|
||||
# Listen with the config
|
||||
self.hs._listen_http(config)
|
||||
|
||||
# Grab the resource from the site that was told to listen
|
||||
site = self.reactor.tcpServers[0][1]
|
||||
try:
|
||||
self.resource = (
|
||||
site.resource.children[b"_matrix"].children[b"federation"]
|
||||
)
|
||||
except KeyError:
|
||||
if expectation == "no_resource":
|
||||
return
|
||||
raise
|
||||
|
||||
request, channel = self.make_request(
|
||||
"GET",
|
||||
"/_matrix/federation/v1/openid/userinfo",
|
||||
)
|
||||
self.render(request)
|
||||
|
||||
self.assertEqual(channel.code, 401)
|
||||
|
||||
|
||||
@patch("synapse.app.homeserver.KeyApiV2Resource", new=Mock())
|
||||
class SynapseHomeserverOpenIDListenerTests(HomeserverTestCase):
|
||||
def make_homeserver(self, reactor, clock):
|
||||
hs = self.setup_test_homeserver(
|
||||
http_client=None, homeserverToUse=SynapseHomeServer,
|
||||
)
|
||||
return hs
|
||||
|
||||
@parameterized.expand([
|
||||
(["federation"], "auth_fail"),
|
||||
([], "no_resource"),
|
||||
(["openid", "federation"], "auth_fail"),
|
||||
(["openid"], "auth_fail"),
|
||||
])
|
||||
def test_openid_listener(self, names, expectation):
|
||||
"""
|
||||
Test different openid listener configurations.
|
||||
|
||||
401 is success here since it means we hit the handler and auth failed.
|
||||
"""
|
||||
config = {
|
||||
"port": 8080,
|
||||
"bind_addresses": ["0.0.0.0"],
|
||||
"resources": [{"names": names}],
|
||||
}
|
||||
|
||||
# Listen with the config
|
||||
self.hs._listener_http(config, config)
|
||||
|
||||
# Grab the resource from the site that was told to listen
|
||||
site = self.reactor.tcpServers[0][1]
|
||||
try:
|
||||
self.resource = (
|
||||
site.resource.children[b"_matrix"].children[b"federation"]
|
||||
)
|
||||
except KeyError:
|
||||
if expectation == "no_resource":
|
||||
return
|
||||
raise
|
||||
|
||||
request, channel = self.make_request(
|
||||
"GET",
|
||||
"/_matrix/federation/v1/openid/userinfo",
|
||||
)
|
||||
self.render(request)
|
||||
|
||||
self.assertEqual(channel.code, 401)
|
Loading…
Reference in New Issue