Compare commits

...

5 Commits

Author SHA1 Message Date
Quentin Gliech
0e5034186b Support for stable scopes in the stable MAS integration 2025-08-04 16:10:22 +02:00
Quentin Gliech
ea84db8458 Merge remote-tracking branch 'origin/develop' into quenting/msc3861-stable 2025-08-04 16:07:38 +02:00
Quentin Gliech
cb9fe38407 Newsfile 2025-06-13 11:43:01 +02:00
Quentin Gliech
9abd4044b9 Support for the stable scopes in MSC3861 delegation 2025-06-13 11:39:08 +02:00
Quentin Gliech
f41e21da54 Support the stable endpoint for MSC2965 2025-06-13 10:25:39 +02:00
6 changed files with 113 additions and 153 deletions

View File

@@ -0,0 +1 @@
Support for the stable endpoint and scopes of [MSC3861](https://github.com/matrix-org/matrix-spec-proposals/pull/3861) & co.

View File

@@ -13,7 +13,7 @@
# #
# #
import logging import logging
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional, Set
from urllib.parse import urlencode from urllib.parse import urlencode
from synapse._pydantic_compat import ( from synapse._pydantic_compat import (
@@ -57,8 +57,10 @@ logger = logging.getLogger(__name__)
# Scope as defined by MSC2967 # Scope as defined by MSC2967
# https://github.com/matrix-org/matrix-spec-proposals/pull/2967 # https://github.com/matrix-org/matrix-spec-proposals/pull/2967
SCOPE_MATRIX_API = "urn:matrix:org.matrix.msc2967.client:api:*" UNSTABLE_SCOPE_MATRIX_API = "urn:matrix:org.matrix.msc2967.client:api:*"
SCOPE_MATRIX_DEVICE_PREFIX = "urn:matrix:org.matrix.msc2967.client:device:" UNSTABLE_SCOPE_MATRIX_DEVICE_PREFIX = "urn:matrix:org.matrix.msc2967.client:device:"
STABLE_SCOPE_MATRIX_API = "urn:matrix:client:api:*"
STABLE_SCOPE_MATRIX_DEVICE_PREFIX = "urn:matrix:client:device:"
class ServerMetadata(BaseModel): class ServerMetadata(BaseModel):
@@ -334,7 +336,10 @@ class MasDelegatedAuth(BaseAuth):
scope = introspection_result.get_scope_set() scope = introspection_result.get_scope_set()
# Determine type of user based on presence of particular scopes # Determine type of user based on presence of particular scopes
if SCOPE_MATRIX_API not in scope: if (
UNSTABLE_SCOPE_MATRIX_API not in scope
and STABLE_SCOPE_MATRIX_API not in scope
):
raise InvalidClientTokenError( raise InvalidClientTokenError(
"Token doesn't grant access to the Matrix C-S API" "Token doesn't grant access to the Matrix C-S API"
) )
@@ -366,11 +371,12 @@ class MasDelegatedAuth(BaseAuth):
# We only allow a single device_id in the scope, so we find them all in the # We only allow a single device_id in the scope, so we find them all in the
# scope list, and raise if there are more than one. The OIDC server should be # scope list, and raise if there are more than one. The OIDC server should be
# the one enforcing valid scopes, so we raise a 500 if we find an invalid scope. # the one enforcing valid scopes, so we raise a 500 if we find an invalid scope.
device_ids = [ device_ids: Set[str] = set()
tok[len(SCOPE_MATRIX_DEVICE_PREFIX) :] for tok in scope:
for tok in scope if tok.startswith(UNSTABLE_SCOPE_MATRIX_DEVICE_PREFIX):
if tok.startswith(SCOPE_MATRIX_DEVICE_PREFIX) device_ids.add(tok[len(UNSTABLE_SCOPE_MATRIX_DEVICE_PREFIX) :])
] elif tok.startswith(STABLE_SCOPE_MATRIX_DEVICE_PREFIX):
device_ids.add(tok[len(STABLE_SCOPE_MATRIX_DEVICE_PREFIX) :])
if len(device_ids) > 1: if len(device_ids) > 1:
raise AuthError( raise AuthError(
@@ -378,7 +384,7 @@ class MasDelegatedAuth(BaseAuth):
"Multiple device IDs in scope", "Multiple device IDs in scope",
) )
device_id = device_ids[0] if device_ids else None device_id = next(iter(device_ids), None)
if device_id is not None: if device_id is not None:
# Sanity check the device_id # Sanity check the device_id

View File

@@ -20,7 +20,7 @@
# #
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set
from urllib.parse import urlencode from urllib.parse import urlencode
from authlib.oauth2 import ClientAuth from authlib.oauth2 import ClientAuth
@@ -34,7 +34,6 @@ from synapse.api.errors import (
AuthError, AuthError,
HttpResponseException, HttpResponseException,
InvalidClientTokenError, InvalidClientTokenError,
OAuthInsufficientScopeError,
SynapseError, SynapseError,
UnrecognizedRequestError, UnrecognizedRequestError,
) )
@@ -63,9 +62,10 @@ logger = logging.getLogger(__name__)
# Scope as defined by MSC2967 # Scope as defined by MSC2967
# https://github.com/matrix-org/matrix-spec-proposals/pull/2967 # https://github.com/matrix-org/matrix-spec-proposals/pull/2967
SCOPE_MATRIX_API = "urn:matrix:org.matrix.msc2967.client:api:*" UNSTABLE_SCOPE_MATRIX_API = "urn:matrix:org.matrix.msc2967.client:api:*"
SCOPE_MATRIX_GUEST = "urn:matrix:org.matrix.msc2967.client:api:guest" UNSTABLE_SCOPE_MATRIX_DEVICE_PREFIX = "urn:matrix:org.matrix.msc2967.client:device:"
SCOPE_MATRIX_DEVICE_PREFIX = "urn:matrix:org.matrix.msc2967.client:device:" STABLE_SCOPE_MATRIX_API = "urn:matrix:client:api:*"
STABLE_SCOPE_MATRIX_DEVICE_PREFIX = "urn:matrix:client:device:"
# Scope which allows access to the Synapse admin API # Scope which allows access to the Synapse admin API
SCOPE_SYNAPSE_ADMIN = "urn:synapse:admin:*" SCOPE_SYNAPSE_ADMIN = "urn:synapse:admin:*"
@@ -444,9 +444,6 @@ class MSC3861DelegatedAuth(BaseAuth):
if not self._is_access_token_the_admin_token(access_token): if not self._is_access_token_the_admin_token(access_token):
await self._record_request(request, requester) await self._record_request(request, requester)
if not allow_guest and requester.is_guest:
raise OAuthInsufficientScopeError([SCOPE_MATRIX_API])
request.requester = requester request.requester = requester
return requester return requester
@@ -528,10 +525,11 @@ class MSC3861DelegatedAuth(BaseAuth):
scope: List[str] = introspection_result.get_scope_list() scope: List[str] = introspection_result.get_scope_list()
# Determine type of user based on presence of particular scopes # Determine type of user based on presence of particular scopes
has_user_scope = SCOPE_MATRIX_API in scope has_user_scope = (
has_guest_scope = SCOPE_MATRIX_GUEST in scope UNSTABLE_SCOPE_MATRIX_API in scope or STABLE_SCOPE_MATRIX_API in scope
)
if not has_user_scope and not has_guest_scope: if not has_user_scope:
raise InvalidClientTokenError("No scope in token granting user rights") raise InvalidClientTokenError("No scope in token granting user rights")
# Match via the sub claim # Match via the sub claim
@@ -579,11 +577,12 @@ class MSC3861DelegatedAuth(BaseAuth):
# We only allow a single device_id in the scope, so we find them all in the # We only allow a single device_id in the scope, so we find them all in the
# scope list, and raise if there are more than one. The OIDC server should be # scope list, and raise if there are more than one. The OIDC server should be
# the one enforcing valid scopes, so we raise a 500 if we find an invalid scope. # the one enforcing valid scopes, so we raise a 500 if we find an invalid scope.
device_ids = [ device_ids: Set[str] = set()
tok[len(SCOPE_MATRIX_DEVICE_PREFIX) :] for tok in scope:
for tok in scope if tok.startswith(UNSTABLE_SCOPE_MATRIX_DEVICE_PREFIX):
if tok.startswith(SCOPE_MATRIX_DEVICE_PREFIX) device_ids.add(tok[len(UNSTABLE_SCOPE_MATRIX_DEVICE_PREFIX) :])
] elif tok.startswith(STABLE_SCOPE_MATRIX_DEVICE_PREFIX):
device_ids.add(tok[len(STABLE_SCOPE_MATRIX_DEVICE_PREFIX) :])
if len(device_ids) > 1: if len(device_ids) > 1:
raise AuthError( raise AuthError(
@@ -591,7 +590,7 @@ class MSC3861DelegatedAuth(BaseAuth):
"Multiple device IDs in scope", "Multiple device IDs in scope",
) )
device_id = device_ids[0] if device_ids else None device_id = next(iter(device_ids), None)
if device_id is not None: if device_id is not None:
# Sanity check the device_id # Sanity check the device_id
@@ -617,5 +616,4 @@ class MSC3861DelegatedAuth(BaseAuth):
user_id=user_id, user_id=user_id,
device_id=device_id, device_id=device_id,
scope=scope, scope=scope,
is_guest=(has_guest_scope and not has_user_scope),
) )

View File

@@ -76,11 +76,17 @@ class AuthMetadataServlet(RestServlet):
Advertises the OAuth 2.0 server metadata for the homeserver. Advertises the OAuth 2.0 server metadata for the homeserver.
""" """
PATTERNS = client_patterns( PATTERNS = [
"/org.matrix.msc2965/auth_metadata$", *client_patterns(
unstable=True, "/auth_metadata$",
releases=(), releases=("v1",),
) ),
*client_patterns(
"/org.matrix.msc2965/auth_metadata$",
unstable=True,
releases=(),
),
]
def __init__(self, hs: "HomeServer"): def __init__(self, hs: "HomeServer"):
super().__init__() super().__init__()

View File

@@ -25,11 +25,11 @@ import time
from http import HTTPStatus from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
from io import BytesIO from io import BytesIO
from typing import Any, Coroutine, Dict, Generator, Optional, TypeVar, Union from typing import Any, ClassVar, Coroutine, Dict, Generator, Optional, TypeVar, Union
from unittest.mock import ANY, AsyncMock, Mock from unittest.mock import ANY, AsyncMock, Mock
from urllib.parse import parse_qs from urllib.parse import parse_qs
from parameterized import parameterized_class from parameterized.parameterized import parameterized_class
from signedjson.key import ( from signedjson.key import (
encode_verify_key_base64, encode_verify_key_base64,
generate_signing_key, generate_signing_key,
@@ -46,7 +46,6 @@ from synapse.api.errors import (
Codes, Codes,
HttpResponseException, HttpResponseException,
InvalidClientTokenError, InvalidClientTokenError,
OAuthInsufficientScopeError,
SynapseError, SynapseError,
) )
from synapse.appservice import ApplicationService from synapse.appservice import ApplicationService
@@ -78,11 +77,7 @@ JWKS_URI = ISSUER + ".well-known/jwks.json"
INTROSPECTION_ENDPOINT = ISSUER + "introspect" INTROSPECTION_ENDPOINT = ISSUER + "introspect"
SYNAPSE_ADMIN_SCOPE = "urn:synapse:admin:*" SYNAPSE_ADMIN_SCOPE = "urn:synapse:admin:*"
MATRIX_USER_SCOPE = "urn:matrix:org.matrix.msc2967.client:api:*"
MATRIX_GUEST_SCOPE = "urn:matrix:org.matrix.msc2967.client:api:guest"
MATRIX_DEVICE_SCOPE_PREFIX = "urn:matrix:org.matrix.msc2967.client:device:"
DEVICE = "AABBCCDD" DEVICE = "AABBCCDD"
MATRIX_DEVICE_SCOPE = MATRIX_DEVICE_SCOPE_PREFIX + DEVICE
SUBJECT = "abc-def-ghi" SUBJECT = "abc-def-ghi"
USERNAME = "test-user" USERNAME = "test-user"
USER_ID = "@" + USERNAME + ":" + SERVER_NAME USER_ID = "@" + USERNAME + ":" + SERVER_NAME
@@ -112,7 +107,24 @@ async def get_json(url: str) -> JsonDict:
@skip_unless(HAS_AUTHLIB, "requires authlib") @skip_unless(HAS_AUTHLIB, "requires authlib")
@parameterized_class(
("device_scope_prefix", "api_scope"),
[
("urn:matrix:client:device:", "urn:matrix:client:api:*"),
(
"urn:matrix:org.matrix.msc2967.client:device:",
"urn:matrix:org.matrix.msc2967.client:api:*",
),
],
)
class MSC3861OAuthDelegation(HomeserverTestCase): class MSC3861OAuthDelegation(HomeserverTestCase):
device_scope_prefix: ClassVar[str]
api_scope: ClassVar[str]
@property
def device_scope(self) -> str:
return self.device_scope_prefix + DEVICE
servlets = [ servlets = [
account.register_servlets, account.register_servlets,
keys.register_servlets, keys.register_servlets,
@@ -212,7 +224,7 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
"""The handler should return a 500 when no subject is present.""" """The handler should return a 500 when no subject is present."""
self._set_introspection_returnvalue( self._set_introspection_returnvalue(
{"active": True, "scope": " ".join([MATRIX_USER_SCOPE])} {"active": True, "scope": " ".join([self.api_scope])},
) )
request = Mock(args={}) request = Mock(args={})
@@ -235,7 +247,7 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
{ {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join([MATRIX_DEVICE_SCOPE]), "scope": " ".join([self.device_scope]),
} }
) )
request = Mock(args={}) request = Mock(args={})
@@ -282,7 +294,7 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
{ {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join([SYNAPSE_ADMIN_SCOPE, MATRIX_USER_SCOPE]), "scope": " ".join([SYNAPSE_ADMIN_SCOPE, self.api_scope]),
"username": USERNAME, "username": USERNAME,
} }
) )
@@ -312,9 +324,7 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
{ {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join( "scope": " ".join([SYNAPSE_ADMIN_SCOPE, self.api_scope]),
[SYNAPSE_ADMIN_SCOPE, MATRIX_USER_SCOPE, MATRIX_GUEST_SCOPE]
),
"username": USERNAME, "username": USERNAME,
} }
) )
@@ -344,7 +354,7 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
{ {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join([MATRIX_USER_SCOPE]), "scope": " ".join([self.api_scope]),
"username": USERNAME, "username": USERNAME,
} }
) )
@@ -374,7 +384,7 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
{ {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join([MATRIX_USER_SCOPE, MATRIX_DEVICE_SCOPE]), "scope": " ".join([self.api_scope, self.device_scope]),
"username": USERNAME, "username": USERNAME,
} }
) )
@@ -404,7 +414,7 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
{ {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join([MATRIX_USER_SCOPE]), "scope": " ".join([self.api_scope]),
"device_id": DEVICE, "device_id": DEVICE,
"username": USERNAME, "username": USERNAME,
} }
@@ -444,9 +454,9 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join( "scope": " ".join(
[ [
MATRIX_USER_SCOPE, self.api_scope,
f"{MATRIX_DEVICE_SCOPE_PREFIX}AABBCC", f"{self.device_scope_prefix}AABBCC",
f"{MATRIX_DEVICE_SCOPE_PREFIX}DDEEFF", f"{self.device_scope_prefix}DDEEFF",
] ]
), ),
"username": USERNAME, "username": USERNAME,
@@ -457,68 +467,6 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
request.requestHeaders.getRawHeaders = mock_getRawHeaders() request.requestHeaders.getRawHeaders = mock_getRawHeaders()
self.get_failure(self.auth.get_user_by_req(request), AuthError) self.get_failure(self.auth.get_user_by_req(request), AuthError)
def test_active_guest_not_allowed(self) -> None:
"""The handler should return an insufficient scope error."""
self._set_introspection_returnvalue(
{
"active": True,
"sub": SUBJECT,
"scope": " ".join([MATRIX_GUEST_SCOPE, MATRIX_DEVICE_SCOPE]),
"username": USERNAME,
}
)
request = Mock(args={})
request.args[b"access_token"] = [b"mockAccessToken"]
request.requestHeaders.getRawHeaders = mock_getRawHeaders()
error = self.get_failure(
self.auth.get_user_by_req(request), OAuthInsufficientScopeError
)
self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
self._rust_client.post.assert_called_once_with(
url=INTROSPECTION_ENDPOINT,
response_limit=ANY,
request_body=ANY,
headers=ANY,
)
self._assertParams()
self.assertEqual(
getattr(error.value, "headers", {})["WWW-Authenticate"],
'Bearer error="insufficient_scope", scope="urn:matrix:org.matrix.msc2967.client:api:*"',
)
def test_active_guest_allowed(self) -> None:
"""The handler should return a requester with guest user rights and a device ID."""
self._set_introspection_returnvalue(
{
"active": True,
"sub": SUBJECT,
"scope": " ".join([MATRIX_GUEST_SCOPE, MATRIX_DEVICE_SCOPE]),
"username": USERNAME,
}
)
request = Mock(args={})
request.args[b"access_token"] = [b"mockAccessToken"]
request.requestHeaders.getRawHeaders = mock_getRawHeaders()
requester = self.get_success(
self.auth.get_user_by_req(request, allow_guest=True)
)
self.http_client.get_json.assert_called_once_with(WELL_KNOWN)
self._rust_client.post.assert_called_once_with(
url=INTROSPECTION_ENDPOINT,
response_limit=ANY,
request_body=ANY,
headers=ANY,
)
self._assertParams()
self.assertEqual(requester.user.to_string(), "@%s:%s" % (USERNAME, SERVER_NAME))
self.assertEqual(requester.is_guest, True)
self.assertEqual(
get_awaitable_result(self.auth.is_server_admin(requester)), False
)
self.assertEqual(requester.device_id, DEVICE)
def test_unavailable_introspection_endpoint(self) -> None: def test_unavailable_introspection_endpoint(self) -> None:
"""The handler should return an internal server error.""" """The handler should return an internal server error."""
request = Mock(args={}) request = Mock(args={})
@@ -562,8 +510,8 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join( "scope": " ".join(
[ [
MATRIX_USER_SCOPE, self.api_scope,
f"{MATRIX_DEVICE_SCOPE_PREFIX}AABBCC", f"{self.device_scope_prefix}AABBCC",
] ]
), ),
"username": USERNAME, "username": USERNAME,
@@ -611,7 +559,7 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
{ {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join([MATRIX_USER_SCOPE, MATRIX_DEVICE_SCOPE]), "scope": " ".join([self.api_scope, self.device_scope]),
"username": USERNAME, "username": USERNAME,
} }
) )
@@ -676,7 +624,7 @@ class MSC3861OAuthDelegation(HomeserverTestCase):
return json.dumps( return json.dumps(
{ {
"active": True, "active": True,
"scope": MATRIX_USER_SCOPE, "scope": self.api_scope,
"sub": SUBJECT, "sub": SUBJECT,
"username": USERNAME, "username": USERNAME,
}, },
@@ -842,8 +790,24 @@ class FakeMasServer(HTTPServer):
T = TypeVar("T") T = TypeVar("T")
@parameterized_class(
("device_scope_prefix", "api_scope"),
[
("urn:matrix:client:device:", "urn:matrix:client:api:*"),
(
"urn:matrix:org.matrix.msc2967.client:device:",
"urn:matrix:org.matrix.msc2967.client:api:*",
),
],
)
class MasAuthDelegation(HomeserverTestCase): class MasAuthDelegation(HomeserverTestCase):
server: FakeMasServer server: FakeMasServer
device_scope_prefix: ClassVar[str]
api_scope: ClassVar[str]
@property
def device_scope(self) -> str:
return self.device_scope_prefix + DEVICE
def till_deferred_has_result( def till_deferred_has_result(
self, self,
@@ -914,12 +878,7 @@ class MasAuthDelegation(HomeserverTestCase):
self.server.introspection_response = { self.server.introspection_response = {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join( "scope": " ".join([self.api_scope, self.device_scope]),
[
MATRIX_USER_SCOPE,
f"{MATRIX_DEVICE_SCOPE_PREFIX}{DEVICE}",
]
),
"username": USERNAME, "username": USERNAME,
"expires_in": 60, "expires_in": 60,
} }
@@ -943,12 +902,7 @@ class MasAuthDelegation(HomeserverTestCase):
self.server.introspection_response = { self.server.introspection_response = {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join( "scope": " ".join([self.api_scope, self.device_scope]),
[
MATRIX_USER_SCOPE,
f"{MATRIX_DEVICE_SCOPE_PREFIX}{DEVICE}",
]
),
"username": USERNAME, "username": USERNAME,
} }
@@ -971,12 +925,7 @@ class MasAuthDelegation(HomeserverTestCase):
self.server.introspection_response = { self.server.introspection_response = {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join( "scope": " ".join([self.api_scope, f"{self.device_scope_prefix}ABCDEF"]),
[
MATRIX_USER_SCOPE,
f"{MATRIX_DEVICE_SCOPE_PREFIX}ABCDEF",
]
),
"username": USERNAME, "username": USERNAME,
"expires_in": 60, "expires_in": 60,
} }
@@ -993,7 +942,7 @@ class MasAuthDelegation(HomeserverTestCase):
self.server.introspection_response = { self.server.introspection_response = {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join([MATRIX_USER_SCOPE]), "scope": " ".join([self.api_scope]),
"username": "inexistent_user", "username": "inexistent_user",
"expires_in": 60, "expires_in": 60,
} }
@@ -1039,7 +988,7 @@ class MasAuthDelegation(HomeserverTestCase):
self.server.introspection_response = { self.server.introspection_response = {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": MATRIX_USER_SCOPE, "scope": self.api_scope,
"username": USERNAME, "username": USERNAME,
"expires_in": 60, "expires_in": 60,
"device_id": DEVICE, "device_id": DEVICE,
@@ -1057,7 +1006,7 @@ class MasAuthDelegation(HomeserverTestCase):
self.server.introspection_response = { self.server.introspection_response = {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join([SYNAPSE_ADMIN_SCOPE, MATRIX_USER_SCOPE]), "scope": " ".join([SYNAPSE_ADMIN_SCOPE, self.api_scope]),
"username": USERNAME, "username": USERNAME,
"expires_in": 60, "expires_in": 60,
} }
@@ -1079,12 +1028,7 @@ class MasAuthDelegation(HomeserverTestCase):
self.server.introspection_response = { self.server.introspection_response = {
"active": True, "active": True,
"sub": SUBJECT, "sub": SUBJECT,
"scope": " ".join( "scope": " ".join([self.api_scope, self.device_scope]),
[
MATRIX_USER_SCOPE,
f"{MATRIX_DEVICE_SCOPE_PREFIX}{DEVICE}",
]
),
"username": USERNAME, "username": USERNAME,
"expires_in": 60, "expires_in": 60,
} }

View File

@@ -18,8 +18,11 @@
# [This file includes modifications made by New Vector Limited] # [This file includes modifications made by New Vector Limited]
# #
from http import HTTPStatus from http import HTTPStatus
from typing import ClassVar
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
from parameterized import parameterized_class
from synapse.rest.client import auth_metadata from synapse.rest.client import auth_metadata
from tests.unittest import HomeserverTestCase, override_config, skip_unless from tests.unittest import HomeserverTestCase, override_config, skip_unless
@@ -85,17 +88,22 @@ class AuthIssuerTestCase(HomeserverTestCase):
req_mock.assert_not_called() req_mock.assert_not_called()
@parameterized_class(
("endpoint",),
[
("/_matrix/client/unstable/org.matrix.msc2965/auth_metadata",),
("/_matrix/client/v1/auth_metadata",),
],
)
class AuthMetadataTestCase(HomeserverTestCase): class AuthMetadataTestCase(HomeserverTestCase):
endpoint: ClassVar[str]
servlets = [ servlets = [
auth_metadata.register_servlets, auth_metadata.register_servlets,
] ]
def test_returns_404_when_msc3861_disabled(self) -> None: def test_returns_404_when_msc3861_disabled(self) -> None:
# Make an unauthenticated request for the discovery info. # Make an unauthenticated request for the discovery info.
channel = self.make_request( channel = self.make_request("GET", self.endpoint)
"GET",
"/_matrix/client/unstable/org.matrix.msc2965/auth_metadata",
)
self.assertEqual(channel.code, HTTPStatus.NOT_FOUND) self.assertEqual(channel.code, HTTPStatus.NOT_FOUND)
@skip_unless(HAS_AUTHLIB, "requires authlib") @skip_unless(HAS_AUTHLIB, "requires authlib")
@@ -124,10 +132,7 @@ class AuthMetadataTestCase(HomeserverTestCase):
) )
self.hs.get_proxied_http_client().get_json = req_mock # type: ignore[method-assign] self.hs.get_proxied_http_client().get_json = req_mock # type: ignore[method-assign]
channel = self.make_request( channel = self.make_request("GET", self.endpoint)
"GET",
"/_matrix/client/unstable/org.matrix.msc2965/auth_metadata",
)
self.assertEqual(channel.code, HTTPStatus.OK) self.assertEqual(channel.code, HTTPStatus.OK)
self.assertEqual( self.assertEqual(