mirror of
https://github.com/element-hq/synapse.git
synced 2024-11-22 17:46:08 +03:00
Fix SSO on workers (#9271)
Fixes #8966. * Factor out build_synapse_client_resource_tree Start a function which will mount resources common to all workers. * Move sso init into build_synapse_client_resource_tree ... so that we don't have to do it for each worker * Fix SSO-login-via-a-worker Expose the SSO login endpoints on workers, like the documentation says. * Update workers config for new endpoints Add documentation for endpoints recently added (#8942, #9017, #9262) * remove submit_token from workers endpoints list this *doesn't* work on workers (yet). * changelog * Add a comment about the odd path for SAML2Resource
This commit is contained in:
parent
f78d07bf00
commit
9c715a5f19
8 changed files with 93 additions and 65 deletions
1
changelog.d/9271.bugfix
Normal file
1
changelog.d/9271.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix single-sign-on when the endpoints are routed to synapse workers.
|
|
@ -225,7 +225,6 @@ expressions:
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/joined_groups$
|
^/_matrix/client/(api/v1|r0|unstable)/joined_groups$
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/publicised_groups$
|
^/_matrix/client/(api/v1|r0|unstable)/publicised_groups$
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/publicised_groups/
|
^/_matrix/client/(api/v1|r0|unstable)/publicised_groups/
|
||||||
^/_synapse/client/password_reset/email/submit_token$
|
|
||||||
|
|
||||||
# Registration/login requests
|
# Registration/login requests
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/login$
|
^/_matrix/client/(api/v1|r0|unstable)/login$
|
||||||
|
@ -256,25 +255,28 @@ Additionally, the following endpoints should be included if Synapse is configure
|
||||||
to use SSO (you only need to include the ones for whichever SSO provider you're
|
to use SSO (you only need to include the ones for whichever SSO provider you're
|
||||||
using):
|
using):
|
||||||
|
|
||||||
|
# for all SSO providers
|
||||||
|
^/_matrix/client/(api/v1|r0|unstable)/login/sso/redirect
|
||||||
|
^/_synapse/client/pick_idp$
|
||||||
|
^/_synapse/client/pick_username
|
||||||
|
^/_synapse/client/sso_register$
|
||||||
|
|
||||||
# OpenID Connect requests.
|
# OpenID Connect requests.
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/login/sso/redirect$
|
|
||||||
^/_synapse/oidc/callback$
|
^/_synapse/oidc/callback$
|
||||||
|
|
||||||
# SAML requests.
|
# SAML requests.
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/login/sso/redirect$
|
|
||||||
^/_matrix/saml2/authn_response$
|
^/_matrix/saml2/authn_response$
|
||||||
|
|
||||||
# CAS requests.
|
# CAS requests.
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/login/(cas|sso)/redirect$
|
|
||||||
^/_matrix/client/(api/v1|r0|unstable)/login/cas/ticket$
|
^/_matrix/client/(api/v1|r0|unstable)/login/cas/ticket$
|
||||||
|
|
||||||
|
Ensure that all SSO logins go to a single process.
|
||||||
|
For multiple workers not handling the SSO endpoints properly, see
|
||||||
|
[#7530](https://github.com/matrix-org/synapse/issues/7530).
|
||||||
|
|
||||||
Note that a HTTP listener with `client` and `federation` resources must be
|
Note that a HTTP listener with `client` and `federation` resources must be
|
||||||
configured in the `worker_listeners` option in the worker config.
|
configured in the `worker_listeners` option in the worker config.
|
||||||
|
|
||||||
Ensure that all SSO logins go to a single process (usually the main process).
|
|
||||||
For multiple workers not handling the SSO endpoints properly, see
|
|
||||||
[#7530](https://github.com/matrix-org/synapse/issues/7530).
|
|
||||||
|
|
||||||
#### Load balancing
|
#### Load balancing
|
||||||
|
|
||||||
It is possible to run multiple instances of this worker app, with incoming requests
|
It is possible to run multiple instances of this worker app, with incoming requests
|
||||||
|
|
|
@ -22,6 +22,7 @@ from typing import Dict, Iterable, Optional, Set
|
||||||
from typing_extensions import ContextManager
|
from typing_extensions import ContextManager
|
||||||
|
|
||||||
from twisted.internet import address
|
from twisted.internet import address
|
||||||
|
from twisted.web.resource import IResource
|
||||||
|
|
||||||
import synapse
|
import synapse
|
||||||
import synapse.events
|
import synapse.events
|
||||||
|
@ -90,9 +91,8 @@ from synapse.replication.tcp.streams import (
|
||||||
ToDeviceStream,
|
ToDeviceStream,
|
||||||
)
|
)
|
||||||
from synapse.rest.admin import register_servlets_for_media_repo
|
from synapse.rest.admin import register_servlets_for_media_repo
|
||||||
from synapse.rest.client.v1 import events, room
|
from synapse.rest.client.v1 import events, login, room
|
||||||
from synapse.rest.client.v1.initial_sync import InitialSyncRestServlet
|
from synapse.rest.client.v1.initial_sync import InitialSyncRestServlet
|
||||||
from synapse.rest.client.v1.login import LoginRestServlet
|
|
||||||
from synapse.rest.client.v1.profile import (
|
from synapse.rest.client.v1.profile import (
|
||||||
ProfileAvatarURLRestServlet,
|
ProfileAvatarURLRestServlet,
|
||||||
ProfileDisplaynameRestServlet,
|
ProfileDisplaynameRestServlet,
|
||||||
|
@ -127,6 +127,7 @@ from synapse.rest.client.v2_alpha.sendtodevice import SendToDeviceRestServlet
|
||||||
from synapse.rest.client.versions import VersionsRestServlet
|
from synapse.rest.client.versions import VersionsRestServlet
|
||||||
from synapse.rest.health import HealthResource
|
from synapse.rest.health import HealthResource
|
||||||
from synapse.rest.key.v2 import KeyApiV2Resource
|
from synapse.rest.key.v2 import KeyApiV2Resource
|
||||||
|
from synapse.rest.synapse.client import build_synapse_client_resource_tree
|
||||||
from synapse.server import HomeServer, cache_in_self
|
from synapse.server import HomeServer, cache_in_self
|
||||||
from synapse.storage.databases.main.censor_events import CensorEventsStore
|
from synapse.storage.databases.main.censor_events import CensorEventsStore
|
||||||
from synapse.storage.databases.main.client_ips import ClientIpWorkerStore
|
from synapse.storage.databases.main.client_ips import ClientIpWorkerStore
|
||||||
|
@ -507,7 +508,7 @@ class GenericWorkerServer(HomeServer):
|
||||||
site_tag = port
|
site_tag = port
|
||||||
|
|
||||||
# We always include a health resource.
|
# We always include a health resource.
|
||||||
resources = {"/health": HealthResource()}
|
resources = {"/health": HealthResource()} # type: Dict[str, IResource]
|
||||||
|
|
||||||
for res in listener_config.http_options.resources:
|
for res in listener_config.http_options.resources:
|
||||||
for name in res.names:
|
for name in res.names:
|
||||||
|
@ -517,7 +518,7 @@ class GenericWorkerServer(HomeServer):
|
||||||
resource = JsonResource(self, canonical_json=False)
|
resource = JsonResource(self, canonical_json=False)
|
||||||
|
|
||||||
RegisterRestServlet(self).register(resource)
|
RegisterRestServlet(self).register(resource)
|
||||||
LoginRestServlet(self).register(resource)
|
login.register_servlets(self, resource)
|
||||||
ThreepidRestServlet(self).register(resource)
|
ThreepidRestServlet(self).register(resource)
|
||||||
DevicesRestServlet(self).register(resource)
|
DevicesRestServlet(self).register(resource)
|
||||||
KeyQueryServlet(self).register(resource)
|
KeyQueryServlet(self).register(resource)
|
||||||
|
@ -557,6 +558,8 @@ class GenericWorkerServer(HomeServer):
|
||||||
groups.register_servlets(self, resource)
|
groups.register_servlets(self, resource)
|
||||||
|
|
||||||
resources.update({CLIENT_API_PREFIX: resource})
|
resources.update({CLIENT_API_PREFIX: resource})
|
||||||
|
|
||||||
|
resources.update(build_synapse_client_resource_tree(self))
|
||||||
elif name == "federation":
|
elif name == "federation":
|
||||||
resources.update({FEDERATION_PREFIX: TransportLayerServer(self)})
|
resources.update({FEDERATION_PREFIX: TransportLayerServer(self)})
|
||||||
elif name == "media":
|
elif name == "media":
|
||||||
|
|
|
@ -60,9 +60,7 @@ from synapse.rest import ClientRestResource
|
||||||
from synapse.rest.admin import AdminRestResource
|
from synapse.rest.admin import AdminRestResource
|
||||||
from synapse.rest.health import HealthResource
|
from synapse.rest.health import HealthResource
|
||||||
from synapse.rest.key.v2 import KeyApiV2Resource
|
from synapse.rest.key.v2 import KeyApiV2Resource
|
||||||
from synapse.rest.synapse.client.pick_idp import PickIdpResource
|
from synapse.rest.synapse.client import build_synapse_client_resource_tree
|
||||||
from synapse.rest.synapse.client.pick_username import pick_username_resource
|
|
||||||
from synapse.rest.synapse.client.sso_register import SsoRegisterResource
|
|
||||||
from synapse.rest.well_known import WellKnownResource
|
from synapse.rest.well_known import WellKnownResource
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.storage import DataStore
|
from synapse.storage import DataStore
|
||||||
|
@ -191,22 +189,10 @@ class SynapseHomeServer(HomeServer):
|
||||||
"/_matrix/client/versions": client_resource,
|
"/_matrix/client/versions": client_resource,
|
||||||
"/.well-known/matrix/client": WellKnownResource(self),
|
"/.well-known/matrix/client": WellKnownResource(self),
|
||||||
"/_synapse/admin": AdminRestResource(self),
|
"/_synapse/admin": AdminRestResource(self),
|
||||||
"/_synapse/client/pick_username": pick_username_resource(self),
|
**build_synapse_client_resource_tree(self),
|
||||||
"/_synapse/client/pick_idp": PickIdpResource(self),
|
|
||||||
"/_synapse/client/sso_register": SsoRegisterResource(self),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.get_config().oidc_enabled:
|
|
||||||
from synapse.rest.oidc import OIDCResource
|
|
||||||
|
|
||||||
resources["/_synapse/oidc"] = OIDCResource(self)
|
|
||||||
|
|
||||||
if self.get_config().saml2_enabled:
|
|
||||||
from synapse.rest.saml2 import SAML2Resource
|
|
||||||
|
|
||||||
resources["/_matrix/saml2"] = SAML2Resource(self)
|
|
||||||
|
|
||||||
if self.get_config().threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
if self.get_config().threepid_behaviour_email == ThreepidBehaviour.LOCAL:
|
||||||
from synapse.rest.synapse.client.password_reset import (
|
from synapse.rest.synapse.client.password_reset import (
|
||||||
PasswordResetSubmitTokenResource,
|
PasswordResetSubmitTokenResource,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright 2020 The Matrix.org Foundation C.I.C.
|
# Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -12,3 +12,50 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Mapping
|
||||||
|
|
||||||
|
from twisted.web.resource import Resource
|
||||||
|
|
||||||
|
from synapse.rest.synapse.client.pick_idp import PickIdpResource
|
||||||
|
from synapse.rest.synapse.client.pick_username import pick_username_resource
|
||||||
|
from synapse.rest.synapse.client.sso_register import SsoRegisterResource
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from synapse.server import HomeServer
|
||||||
|
|
||||||
|
|
||||||
|
def build_synapse_client_resource_tree(hs: "HomeServer") -> Mapping[str, Resource]:
|
||||||
|
"""Builds a resource tree to include synapse-specific client resources
|
||||||
|
|
||||||
|
These are resources which should be loaded on all workers which expose a C-S API:
|
||||||
|
ie, the main process, and any generic workers so configured.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
map from path to Resource.
|
||||||
|
"""
|
||||||
|
resources = {
|
||||||
|
# SSO bits. These are always loaded, whether or not SSO login is actually
|
||||||
|
# enabled (they just won't work very well if it's not)
|
||||||
|
"/_synapse/client/pick_idp": PickIdpResource(hs),
|
||||||
|
"/_synapse/client/pick_username": pick_username_resource(hs),
|
||||||
|
"/_synapse/client/sso_register": SsoRegisterResource(hs),
|
||||||
|
}
|
||||||
|
|
||||||
|
# provider-specific SSO bits. Only load these if they are enabled, since they
|
||||||
|
# rely on optional dependencies.
|
||||||
|
if hs.config.oidc_enabled:
|
||||||
|
from synapse.rest.oidc import OIDCResource
|
||||||
|
|
||||||
|
resources["/_synapse/oidc"] = OIDCResource(hs)
|
||||||
|
|
||||||
|
if hs.config.saml2_enabled:
|
||||||
|
from synapse.rest.saml2 import SAML2Resource
|
||||||
|
|
||||||
|
# This is mounted under '/_matrix' for backwards-compatibility.
|
||||||
|
resources["/_matrix/saml2"] = SAML2Resource(hs)
|
||||||
|
|
||||||
|
return resources
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["build_synapse_client_resource_tree"]
|
||||||
|
|
|
@ -443,6 +443,26 @@ class RegistrationWorkerStore(CacheInvalidationWorkerStore):
|
||||||
|
|
||||||
return await self.db_pool.runInteraction("get_users_by_id_case_insensitive", f)
|
return await self.db_pool.runInteraction("get_users_by_id_case_insensitive", f)
|
||||||
|
|
||||||
|
async def record_user_external_id(
|
||||||
|
self, auth_provider: str, external_id: str, user_id: str
|
||||||
|
) -> None:
|
||||||
|
"""Record a mapping from an external user id to a mxid
|
||||||
|
|
||||||
|
Args:
|
||||||
|
auth_provider: identifier for the remote auth provider
|
||||||
|
external_id: id on that system
|
||||||
|
user_id: complete mxid that it is mapped to
|
||||||
|
"""
|
||||||
|
await self.db_pool.simple_insert(
|
||||||
|
table="user_external_ids",
|
||||||
|
values={
|
||||||
|
"auth_provider": auth_provider,
|
||||||
|
"external_id": external_id,
|
||||||
|
"user_id": user_id,
|
||||||
|
},
|
||||||
|
desc="record_user_external_id",
|
||||||
|
)
|
||||||
|
|
||||||
async def get_user_by_external_id(
|
async def get_user_by_external_id(
|
||||||
self, auth_provider: str, external_id: str
|
self, auth_provider: str, external_id: str
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
|
@ -1371,26 +1391,6 @@ class RegistrationStore(StatsStore, RegistrationBackgroundUpdateStore):
|
||||||
|
|
||||||
self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))
|
self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id,))
|
||||||
|
|
||||||
async def record_user_external_id(
|
|
||||||
self, auth_provider: str, external_id: str, user_id: str
|
|
||||||
) -> None:
|
|
||||||
"""Record a mapping from an external user id to a mxid
|
|
||||||
|
|
||||||
Args:
|
|
||||||
auth_provider: identifier for the remote auth provider
|
|
||||||
external_id: id on that system
|
|
||||||
user_id: complete mxid that it is mapped to
|
|
||||||
"""
|
|
||||||
await self.db_pool.simple_insert(
|
|
||||||
table="user_external_ids",
|
|
||||||
values={
|
|
||||||
"auth_provider": auth_provider,
|
|
||||||
"external_id": external_id,
|
|
||||||
"user_id": user_id,
|
|
||||||
},
|
|
||||||
desc="record_user_external_id",
|
|
||||||
)
|
|
||||||
|
|
||||||
async def user_set_password_hash(
|
async def user_set_password_hash(
|
||||||
self, user_id: str, password_hash: Optional[str]
|
self, user_id: str, password_hash: Optional[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
@ -29,9 +29,7 @@ from synapse.appservice import ApplicationService
|
||||||
from synapse.rest.client.v1 import login, logout
|
from synapse.rest.client.v1 import login, logout
|
||||||
from synapse.rest.client.v2_alpha import devices, register
|
from synapse.rest.client.v2_alpha import devices, register
|
||||||
from synapse.rest.client.v2_alpha.account import WhoamiRestServlet
|
from synapse.rest.client.v2_alpha.account import WhoamiRestServlet
|
||||||
from synapse.rest.synapse.client.pick_idp import PickIdpResource
|
from synapse.rest.synapse.client import build_synapse_client_resource_tree
|
||||||
from synapse.rest.synapse.client.pick_username import pick_username_resource
|
|
||||||
from synapse.rest.synapse.client.sso_register import SsoRegisterResource
|
|
||||||
from synapse.types import create_requester
|
from synapse.types import create_requester
|
||||||
|
|
||||||
from tests import unittest
|
from tests import unittest
|
||||||
|
@ -424,11 +422,8 @@ class MultiSSOTestCase(unittest.HomeserverTestCase):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def create_resource_dict(self) -> Dict[str, Resource]:
|
def create_resource_dict(self) -> Dict[str, Resource]:
|
||||||
from synapse.rest.oidc import OIDCResource
|
|
||||||
|
|
||||||
d = super().create_resource_dict()
|
d = super().create_resource_dict()
|
||||||
d["/_synapse/client/pick_idp"] = PickIdpResource(self.hs)
|
d.update(build_synapse_client_resource_tree(self.hs))
|
||||||
d["/_synapse/oidc"] = OIDCResource(self.hs)
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_get_login_flows(self):
|
def test_get_login_flows(self):
|
||||||
|
@ -1212,12 +1207,8 @@ class UsernamePickerTestCase(HomeserverTestCase):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def create_resource_dict(self) -> Dict[str, Resource]:
|
def create_resource_dict(self) -> Dict[str, Resource]:
|
||||||
from synapse.rest.oidc import OIDCResource
|
|
||||||
|
|
||||||
d = super().create_resource_dict()
|
d = super().create_resource_dict()
|
||||||
d["/_synapse/client/pick_username"] = pick_username_resource(self.hs)
|
d.update(build_synapse_client_resource_tree(self.hs))
|
||||||
d["/_synapse/client/sso_register"] = SsoRegisterResource(self.hs)
|
|
||||||
d["/_synapse/oidc"] = OIDCResource(self.hs)
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_username_picker(self):
|
def test_username_picker(self):
|
||||||
|
|
|
@ -22,7 +22,7 @@ from synapse.api.constants import LoginType
|
||||||
from synapse.handlers.ui_auth.checkers import UserInteractiveAuthChecker
|
from synapse.handlers.ui_auth.checkers import UserInteractiveAuthChecker
|
||||||
from synapse.rest.client.v1 import login
|
from synapse.rest.client.v1 import login
|
||||||
from synapse.rest.client.v2_alpha import auth, devices, register
|
from synapse.rest.client.v2_alpha import auth, devices, register
|
||||||
from synapse.rest.oidc import OIDCResource
|
from synapse.rest.synapse.client import build_synapse_client_resource_tree
|
||||||
from synapse.types import JsonDict, UserID
|
from synapse.types import JsonDict, UserID
|
||||||
|
|
||||||
from tests import unittest
|
from tests import unittest
|
||||||
|
@ -173,9 +173,7 @@ class UIAuthTests(unittest.HomeserverTestCase):
|
||||||
|
|
||||||
def create_resource_dict(self):
|
def create_resource_dict(self):
|
||||||
resource_dict = super().create_resource_dict()
|
resource_dict = super().create_resource_dict()
|
||||||
if HAS_OIDC:
|
resource_dict.update(build_synapse_client_resource_tree(self.hs))
|
||||||
# mount the OIDC resource at /_synapse/oidc
|
|
||||||
resource_dict["/_synapse/oidc"] = OIDCResource(self.hs)
|
|
||||||
return resource_dict
|
return resource_dict
|
||||||
|
|
||||||
def prepare(self, reactor, clock, hs):
|
def prepare(self, reactor, clock, hs):
|
||||||
|
|
Loading…
Reference in a new issue