Co-authored-by: Quentin Gliech <quenting@element.io>pull/16737/head
@@ -0,0 +1 @@ | |||||
Update the implementation of [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965) (OIDC Provider discovery). |
@@ -22,6 +22,7 @@ from synapse.rest.client import ( | |||||
account_validity, | account_validity, | ||||
appservice_ping, | appservice_ping, | ||||
auth, | auth, | ||||
auth_issuer, | |||||
capabilities, | capabilities, | ||||
devices, | devices, | ||||
directory, | directory, | ||||
@@ -148,3 +149,4 @@ class ClientRestResource(JsonResource): | |||||
mutual_rooms.register_servlets(hs, client_resource) | mutual_rooms.register_servlets(hs, client_resource) | ||||
login_token_request.register_servlets(hs, client_resource) | login_token_request.register_servlets(hs, client_resource) | ||||
rendezvous.register_servlets(hs, client_resource) | rendezvous.register_servlets(hs, client_resource) | ||||
auth_issuer.register_servlets(hs, client_resource) |
@@ -0,0 +1,63 @@ | |||||
# Copyright 2023 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 logging | |||||
import typing | |||||
from typing import Tuple | |||||
from synapse.api.errors import Codes, SynapseError | |||||
from synapse.http.server import HttpServer | |||||
from synapse.http.servlet import RestServlet | |||||
from synapse.http.site import SynapseRequest | |||||
from synapse.rest.client._base import client_patterns | |||||
from synapse.types import JsonDict | |||||
if typing.TYPE_CHECKING: | |||||
from synapse.server import HomeServer | |||||
logger = logging.getLogger(__name__) | |||||
class AuthIssuerServlet(RestServlet): | |||||
""" | |||||
Advertises what OpenID Connect issuer clients should use to authorise users. | |||||
""" | |||||
PATTERNS = client_patterns( | |||||
"/org.matrix.msc2965/auth_issuer$", | |||||
unstable=True, | |||||
releases=(), | |||||
) | |||||
def __init__(self, hs: "HomeServer"): | |||||
super().__init__() | |||||
self._config = hs.config | |||||
async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: | |||||
if self._config.experimental.msc3861.enabled: | |||||
return 200, {"issuer": self._config.experimental.msc3861.issuer} | |||||
else: | |||||
# Wouldn't expect this to be reached: the servelet shouldn't have been | |||||
# registered. Still, fail gracefully if we are registered for some reason. | |||||
raise SynapseError( | |||||
404, | |||||
"OIDC discovery has not been configured on this homeserver", | |||||
Codes.NOT_FOUND, | |||||
) | |||||
def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: | |||||
# We use the MSC3861 values as they are used by multiple MSCs | |||||
if hs.config.experimental.msc3861.enabled: | |||||
AuthIssuerServlet(hs).register(http_server) |
@@ -0,0 +1,59 @@ | |||||
# Copyright 2023 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. | |||||
from http import HTTPStatus | |||||
from synapse.rest.client import auth_issuer | |||||
from tests.unittest import HomeserverTestCase, override_config, skip_unless | |||||
from tests.utils import HAS_AUTHLIB | |||||
ISSUER = "https://account.example.com/" | |||||
class AuthIssuerTestCase(HomeserverTestCase): | |||||
servlets = [ | |||||
auth_issuer.register_servlets, | |||||
] | |||||
def test_returns_404_when_msc3861_disabled(self) -> None: | |||||
# Make an unauthenticated request for the discovery info. | |||||
channel = self.make_request( | |||||
"GET", | |||||
"/_matrix/client/unstable/org.matrix.msc2965/auth_issuer", | |||||
) | |||||
self.assertEqual(channel.code, HTTPStatus.NOT_FOUND) | |||||
@skip_unless(HAS_AUTHLIB, "requires authlib") | |||||
@override_config( | |||||
{ | |||||
"disable_registration": True, | |||||
"experimental_features": { | |||||
"msc3861": { | |||||
"enabled": True, | |||||
"issuer": ISSUER, | |||||
"client_id": "David Lister", | |||||
"client_auth_method": "client_secret_post", | |||||
"client_secret": "Who shot Mister Burns?", | |||||
} | |||||
}, | |||||
} | |||||
) | |||||
def test_returns_issuer_when_oidc_enabled(self) -> None: | |||||
# Make an unauthenticated request for the discovery info. | |||||
channel = self.make_request( | |||||
"GET", | |||||
"/_matrix/client/unstable/org.matrix.msc2965/auth_issuer", | |||||
) | |||||
self.assertEqual(channel.code, HTTPStatus.OK) | |||||
self.assertEqual(channel.json_body, {"issuer": ISSUER}) |