Skip to content

Commit 9ede33a

Browse files
committed
Add support for v3 Client-Server API
Related to: - https://matrix.org/blog/2021/11/09/matrix-v-1-1-release - matrix-org/synapse#11318 - spantaleev/matrix-docker-ansible-deploy#1404 The upcoming Synapse v1.48.0 release is likely to expose all these `r0` APIs that we've used till now as `v3` APIs. Both the `r0` and `v3` prefixes lead to the same APIs on the homeserver. matrix-corporal 2.1.5 already properly handles rejecting unknown v-prefixed versions (`v3` included), which patched a potential future security vulnerability (when Synapse v1.48.0 ultimately gets released). This patch adds to it and lets `v3` requests go through and get handled the same way `r0` requests are handled.
1 parent 8af4278 commit 9ede33a

File tree

9 files changed

+43
-38
lines changed

9 files changed

+43
-38
lines changed

corporal/httpgateway/handler/login.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ func (me *loginHandler) RegisterRoutesWithRouter(router *mux.Router) {
4040
// As of this moment (2021-11-15), Synapse (v1.46) does not like trailing-slash login requests.
4141
// Still, we handle both trailing and non-trailing to be on the safe side.
4242

43+
// Requests for an `apiVersion` that we don't support (and don't match below) are rejected via a `denyUnsupportedApiVersionsMiddleware` middleware.
44+
4345
router.Handle(
44-
"/_matrix/client/r0/login{optionalTrailingSlash:[/]?}",
46+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/login{optionalTrailingSlash:[/]?}`,
4547
me.createInterceptorHandler("login", me.loginInterceptor),
4648
).Methods("POST")
4749
}

corporal/httpgateway/handler/policy_checked_routes.go

+16-14
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ func (me *policyCheckedRoutesHandler) RegisterRoutesWithRouter(router *mux.Route
4747
//
4848
// It's important to us that policy-checked routes are matched (with and without a slash),
4949
// so we can guarantee policy checking happens and that we potentially reject requests that need to be rejected.
50-
// Without this, a request for `/_matrix/client/r0/rooms/{roomId}/state/m.room.encryption/` (note the trailing slash)
51-
// does not match our `/_matrix/client/r0/rooms/{roomId}/state/m.room.encryption` policy-checked handler,
50+
// Without this, a request for `/_matrix/client/{apiVersion:(?:r0|v\d+)}/rooms/{roomId}/state/m.room.encryption/` (note the trailing slash)
51+
// does not match our `/_matrix/client/{apiVersion:(?:r0|v\d+)}/rooms/{roomId}/state/m.room.encryption` policy-checked handler,
5252
// slips through and gets happily served by the homserver.
5353
//
5454
// Alternative solutions are:
5555
// 1. Using the mux Router's `StripSlash(true)` setting (but it does weird 301 redirects for POST/PUT requests, so it's not effective)
5656
// 2. Applying a router middleware that modifies the request (stripping trailing slashes) before matching happens.
5757
// See: https://natedenlinger.com/dealing-with-trailing-slashes-on-requesturi-in-go-with-mux/
58-
// The 2nd solution doesn't work as well, because some APIs (`GET /_matrix/client/r0/pushrules/`) require a trailing slash.
58+
// The 2nd solution doesn't work as well, because some APIs (`GET /_matrix/client/{apiVersion:(?:r0|v\d+)}/pushrules/`) require a trailing slash.
5959
// Removing the trailing slash on our side and forwarding the request to the homeserver results in `{"errcode":"M_UNRECOGNIZED","error":"Unrecognized request"}`.
6060
//
6161
// Instead of trying to whitelist routes that require a slash and potentially missing something,
@@ -64,56 +64,58 @@ func (me *policyCheckedRoutesHandler) RegisterRoutesWithRouter(router *mux.Route
6464
// Most of the APIs below will not even be served by the homeserver, as a trailing slash is not tolerated at the homeserver level.
6565
// Still, it's safer if we policy-check them all and not have to worry future homeserver versions handling things differently.
6666

67+
// Requests for an `apiVersion` that we don't support (and don't match below) are rejected via a `denyUnsupportedApiVersionsMiddleware` middleware.
68+
6769
router.HandleFunc(
68-
"/_matrix/client/r0/groups/{communityId}/self/leave{optionalTrailingSlash:[/]?}",
70+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/groups/{communityId}/self/leave{optionalTrailingSlash:[/]?}`,
6971
me.createPolicyCheckingHandler("community.self.leave", policycheck.CheckCommunitySelfLeave, false),
7072
).Methods("PUT")
7173

7274
router.HandleFunc(
73-
"/_matrix/client/r0/rooms/{roomId}/leave{optionalTrailingSlash:[/]?}",
75+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/rooms/{roomId}/leave{optionalTrailingSlash:[/]?}`,
7476
me.createPolicyCheckingHandler("room.leave", policycheck.CheckRoomLeave, false),
7577
).Methods("POST")
7678

7779
// Another way to leave a room is kick yourself out of it. It doesn't require any special permissions.
7880
router.HandleFunc(
79-
"/_matrix/client/r0/rooms/{roomId}/kick{optionalTrailingSlash:[/]?}",
81+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/rooms/{roomId}/kick{optionalTrailingSlash:[/]?}`,
8082
me.createPolicyCheckingHandler("room.kick", policycheck.CheckRoomKick, false),
8183
).Methods("POST")
8284

8385
// Another way to leave a room is to PUT a "membership=leave" into your m.room.member state.
8486
router.HandleFunc(
85-
"/_matrix/client/r0/rooms/{roomId}/state/m.room.member/{memberId}{optionalTrailingSlash:[/]?}",
87+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/rooms/{roomId}/state/m.room.member/{memberId}{optionalTrailingSlash:[/]?}`,
8688
me.createPolicyCheckingHandler("room.member.state.set", policycheck.CheckRoomMembershipStateChange, false),
8789
).Methods("PUT")
8890

8991
// Another way to make a room encrypted is by enabling encryption subsequently.
9092
router.HandleFunc(
91-
"/_matrix/client/r0/rooms/{roomId}/state/m.room.encryption{optionalTrailingSlash:[/]?}",
93+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/rooms/{roomId}/state/m.room.encryption{optionalTrailingSlash:[/]?}`,
9294
me.createPolicyCheckingHandler("room.subsequenly_enabling_encryption", policycheck.CheckRoomEncryptionStateChange, false),
9395
).Methods("PUT")
9496

9597
router.HandleFunc(
96-
"/_matrix/client/r0/createRoom{optionalTrailingSlash:[/]?}",
98+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/createRoom{optionalTrailingSlash:[/]?}`,
9799
me.createPolicyCheckingHandler("room.create", policycheck.CheckRoomCreate, false),
98100
).Methods("POST")
99101

100102
router.HandleFunc(
101-
"/_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}{optionalTrailingSlash:[/]?}",
103+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/rooms/{roomId}/send/{eventType}/{txnId}{optionalTrailingSlash:[/]?}`,
102104
me.createPolicyCheckingHandler("room.send_event", policycheck.CheckRoomSendEvent, false),
103105
).Methods("PUT")
104106

105107
router.HandleFunc(
106-
"/_matrix/client/r0/profile/{targetUserId}/displayname{optionalTrailingSlash:[/]?}",
108+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/profile/{targetUserId}/displayname{optionalTrailingSlash:[/]?}`,
107109
me.createPolicyCheckingHandler("user.set_display_name", policycheck.CheckProfileSetDisplayName, false),
108110
).Methods("PUT")
109111

110112
router.HandleFunc(
111-
"/_matrix/client/r0/profile/{targetUserId}/avatar_url{optionalTrailingSlash:[/]?}",
113+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/profile/{targetUserId}/avatar_url{optionalTrailingSlash:[/]?}`,
112114
me.createPolicyCheckingHandler("user.set_avatar", policycheck.CheckProfileSetAvatarUrl, false),
113115
).Methods("PUT")
114116

115117
router.HandleFunc(
116-
"/_matrix/client/r0/account/deactivate{optionalTrailingSlash:[/]?}",
118+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/account/deactivate{optionalTrailingSlash:[/]?}`,
117119
me.createPolicyCheckingHandler("user.deactivate", policycheck.CheckUserDeactivate, false),
118120
).Methods("POST")
119121

@@ -123,7 +125,7 @@ func (me *policyCheckedRoutesHandler) RegisterRoutesWithRouter(router *mux.Route
123125
//
124126
// We don't want to break the 2nd (access-token-less) flow in some cases (depending on the policy).
125127
router.HandleFunc(
126-
"/_matrix/client/r0/account/password{optionalTrailingSlash:[/]?}",
128+
`/_matrix/client/{apiVersion:(?:r0|v\d+)}/account/password{optionalTrailingSlash:[/]?}`,
127129
me.createPolicyCheckingHandler("user.password", policycheck.CheckUserSetPassword, true),
128130
).Methods("POST")
129131
}

corporal/httpgateway/middleware.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func init() {
2121

2222
supportedApiVersions = []string{
2323
"r0",
24+
"v3",
2425
}
2526
}
2627

@@ -33,7 +34,7 @@ func denyUnsupportedApiVersionsMiddleware(next http.Handler) http.Handler {
3334
return
3435
}
3536

36-
releaseVersion := matches[1] // Something like `r0`
37+
releaseVersion := matches[1] // Something like `r0` or `v3`, etc.
3738

3839
if util.IsStringInArray(releaseVersion, supportedApiVersions) {
3940
// We do support this version and can safely let our gateway

corporal/httpgateway/policycheck/community.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/gorilla/mux"
1010
)
1111

12-
// CheckCommunitySelfLeave is a policy checker for: /_matrix/client/r0/groups/{communityId}/self/leave
12+
// CheckCommunitySelfLeave is a policy checker for: /_matrix/client/{apiVersion:(r0|v3)}/groups/{communityId}/self/leave
1313
func CheckCommunitySelfLeave(r *http.Request, ctx context.Context, policy policy.Policy, checker policy.Checker) PolicyCheckResponse {
1414
userId := ctx.Value("userId").(string)
1515
communityId := mux.Vars(r)["communityId"]

corporal/httpgateway/policycheck/profile.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/gorilla/mux"
1111
)
1212

13-
// CheckProfileSetDisplayName is a policy checker for: /_matrix/client/r0/profile/{targetUserId}/displayname
13+
// CheckProfileSetDisplayName is a policy checker for: /_matrix/client/{apiVersion:(r0|v3)}/profile/{targetUserId}/displayname
1414
func CheckProfileSetDisplayName(r *http.Request, ctx context.Context, policy policy.Policy, checker policy.Checker) PolicyCheckResponse {
1515
userId := ctx.Value("userId").(string)
1616
targetUserId := mux.Vars(r)["targetUserId"]
@@ -63,7 +63,7 @@ func CheckProfileSetDisplayName(r *http.Request, ctx context.Context, policy pol
6363
}
6464
}
6565

66-
// CheckProfileSetAvatarUrl is a policy checker for: /_matrix/client/r0/profile/{targetUserId}/avatar_url
66+
// CheckProfileSetAvatarUrl is a policy checker for: /_matrix/client/{apiVersion:(r0|v3)}/profile/{targetUserId}/avatar_url
6767
func CheckProfileSetAvatarUrl(r *http.Request, ctx context.Context, policy policy.Policy, checker policy.Checker) PolicyCheckResponse {
6868
userId := ctx.Value("userId").(string)
6969
targetUserId := mux.Vars(r)["targetUserId"]

corporal/httpgateway/policycheck/room.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/matrix-org/gomatrix"
1212
)
1313

14-
// CheckRoomCreate is a policy checker for: /_matrix/client/r0/createRoom
14+
// CheckRoomCreate is a policy checker for: /_matrix/client/{apiVersion:(r0|v3)}/createRoom
1515
func CheckRoomCreate(r *http.Request, ctx context.Context, policy policy.Policy, checker policy.Checker) PolicyCheckResponse {
1616
userId := ctx.Value("userId").(string)
1717

@@ -64,7 +64,7 @@ func CheckRoomCreate(r *http.Request, ctx context.Context, policy policy.Policy,
6464
}
6565
}
6666

67-
// CheckRoomEncryptionStateChange is a policy checker for: /_matrix/client/r0/rooms/{roomId}/state/m.room.encryption
67+
// CheckRoomEncryptionStateChange is a policy checker for: /_matrix/client/{apiVersion:(r0|v3)}/rooms/{roomId}/state/m.room.encryption
6868
func CheckRoomEncryptionStateChange(r *http.Request, ctx context.Context, policy policy.Policy, checker policy.Checker) PolicyCheckResponse {
6969
userId := ctx.Value("userId").(string)
7070

@@ -81,7 +81,7 @@ func CheckRoomEncryptionStateChange(r *http.Request, ctx context.Context, policy
8181
}
8282
}
8383

84-
// CheckRoomSendEvent is a policy checker for: /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}
84+
// CheckRoomSendEvent is a policy checker for: /_matrix/client/{apiVersion:(r0|v3)}/rooms/{roomId}/send/{eventType}/{txnId}
8585
func CheckRoomSendEvent(r *http.Request, ctx context.Context, policy policy.Policy, checker policy.Checker) PolicyCheckResponse {
8686
userId := ctx.Value("userId").(string)
8787
eventType := mux.Vars(r)["eventType"]
@@ -100,7 +100,7 @@ func CheckRoomSendEvent(r *http.Request, ctx context.Context, policy policy.Poli
100100
}
101101
}
102102

103-
// CheckRoomLeave is a policy checker for: /_matrix/client/r0/rooms/{roomId}/leave
103+
// CheckRoomLeave is a policy checker for: /_matrix/client/{apiVersion:(r0|v3)}/rooms/{roomId}/leave
104104
func CheckRoomLeave(r *http.Request, ctx context.Context, policy policy.Policy, checker policy.Checker) PolicyCheckResponse {
105105
userId := ctx.Value("userId").(string)
106106
roomId := mux.Vars(r)["roomId"]
@@ -118,7 +118,7 @@ func CheckRoomLeave(r *http.Request, ctx context.Context, policy policy.Policy,
118118
}
119119
}
120120

121-
// CheckRoomMembershipStateChange is a policy checker for: /_matrix/client/r0/rooms/{roomId}/state/m.room.member/{memberId}
121+
// CheckRoomMembershipStateChange is a policy checker for: /_matrix/client/{apiVersion:(r0|v3)}/rooms/{roomId}/state/m.room.member/{memberId}
122122
func CheckRoomMembershipStateChange(r *http.Request, ctx context.Context, policy policy.Policy, checker policy.Checker) PolicyCheckResponse {
123123
userId := ctx.Value("userId").(string)
124124
roomId := mux.Vars(r)["roomId"]
@@ -151,7 +151,7 @@ func CheckRoomMembershipStateChange(r *http.Request, ctx context.Context, policy
151151
}
152152
}
153153

154-
// CheckRoomKick is a policy checker for: /_matrix/client/r0/rooms/{roomId}/kick
154+
// CheckRoomKick is a policy checker for: /_matrix/client/{apiVersion:(r0|v3)}/rooms/{roomId}/kick
155155
func CheckRoomKick(r *http.Request, ctx context.Context, policy policy.Policy, checker policy.Checker) PolicyCheckResponse {
156156
userId := ctx.Value("userId").(string)
157157
roomId := mux.Vars(r)["roomId"]

corporal/httpgateway/policycheck/user.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"net/http"
99
)
1010

11-
// CheckUserDeactivate is a policy checker for: /_matrix/client/r0/account/deactivate
11+
// CheckUserDeactivate is a policy checker for: /_matrix/client/{apiVersion:(r0|v3)}/account/deactivate
1212
func CheckUserDeactivate(r *http.Request, ctx context.Context, policy policy.Policy, checker policy.Checker) PolicyCheckResponse {
1313
userId := ctx.Value("userId").(string)
1414

@@ -28,7 +28,7 @@ func CheckUserDeactivate(r *http.Request, ctx context.Context, policy policy.Pol
2828
}
2929
}
3030

31-
// CheckUserSetPassword is a policy checker for: /_matrix/client/r0/account/password
31+
// CheckUserSetPassword is a policy checker for: /_matrix/client/{apiVersion:(r0|v3)}/account/password
3232
func CheckUserSetPassword(r *http.Request, ctx context.Context, policyObj policy.Policy, checker policy.Checker) PolicyCheckResponse {
3333
userIdOrNil := ctx.Value("userId")
3434
userId, ok := userIdOrNil.(string)

corporal/matrix/constants.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const (
2424
LoginTypePassword = "m.login.password"
2525
LoginTypeToken = "m.login.token"
2626

27-
// See https://matrix.org/docs/spec/client_server/r0.6.1#identifier-types
27+
// See https://spec.matrix.org/v1.1/client-server-api/#identifier-types
2828
LoginIdentifierTypeUser = "m.id.user"
2929
LoginIdentifierTypeThirdParty = "m.id.thirdparty"
3030
LoginIdentifierTypePhone = "m.id.phone"

corporal/matrix/payloads.go

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package matrix
22

3-
// ApiLoginRequestPayload represents is a request payload for: POST /_matrix/client/r0/login
3+
// ApiLoginRequestPayload represents is a request payload for: POST /_matrix/client/{apiVersion:(r0|v3)}/login
44
type ApiLoginRequestPayload struct {
55
// Type is matrix.LoginTypeToken or something else
66
Type string `json:"type"`
@@ -51,33 +51,33 @@ type ApiAdminEntityUser struct {
5151
AvatarURL string `json:"avatar_url"`
5252
}
5353

54-
// ApiWhoAmIResponse is a response as found at: GET /_matrix/client/r0/account/whoami
54+
// ApiWhoAmIResponse is a response as found at: GET /_matrix/client/{apiVersion:(r0|v3)}/account/whoami
5555
type ApiWhoAmIResponse struct {
5656
UserId string `json:"user_id"`
5757
}
5858

59-
// ApiUserProfileResponse is a response as found at: GET /_matrix/client/r0/profile/{userId}
59+
// ApiUserProfileResponse is a response as found at: GET /_matrix/client/{apiVersion:(r0|v3)}/profile/{userId}
6060
type ApiUserProfileResponse struct {
6161
AvatarUrl string `json:"avatar_url"`
6262
DisplayName string `json:"displayname"`
6363
}
6464

65-
// ApiUserProfileDisplayNameRequestPayload is a request payload for: POST /_matrix/client/r0/profile/{userId}/displayname
65+
// ApiUserProfileDisplayNameRequestPayload is a request payload for: POST /_matrix/client/{apiVersion:(r0|v3)}/profile/{userId}/displayname
6666
type ApiUserProfileDisplayNameRequestPayload struct {
6767
DisplayName string `json:"displayname"`
6868
}
6969

70-
// ApiJoinedGroupsResponse is a response as found at: GET /_matrix/client/r0/joined_groups
70+
// ApiJoinedGroupsResponse is a response as found at: GET /_matrix/client/{apiVersion:(r0|v3)}/joined_groups
7171
type ApiJoinedGroupsResponse struct {
7272
GroupIds []string `json:"groups"`
7373
}
7474

75-
// ApiAdminRegisterNonceResponse is a response as found at: GET /_matrix/client/r0/admin/register
75+
// ApiAdminRegisterNonceResponse is a response as found at: GET /_matrix/client/{apiVersion:(r0|v3)}/admin/register
7676
type ApiUserAccountRegisterNonceResponse struct {
7777
Nonce string `json:"nonce"`
7878
}
7979

80-
// ApiUserAccountRegisterRequestPayload is a request payload for: POST /_matrix/client/r0/admin/register
80+
// ApiUserAccountRegisterRequestPayload is a request payload for: POST /_matrix/client/{apiVersion:(r0|v3)}/admin/register
8181
type ApiUserAccountRegisterRequestPayload struct {
8282
Nonce string `json:"nonce"`
8383
Username string `json:"username"`
@@ -87,19 +87,19 @@ type ApiUserAccountRegisterRequestPayload struct {
8787
Admin bool `json:"admin"`
8888
}
8989

90-
// ApiUserAccountRegisterResponse is a response as found at: POST /_matrix/client/r0/admin/register
90+
// ApiUserAccountRegisterResponse is a response as found at: POST /_matrix/client/{apiVersion:(r0|v3)}/admin/register
9191
type ApiUserAccountRegisterResponse struct {
9292
AccessToken string `json:"access_token"`
9393
HomeServer string `json:"home_server"`
9494
UserId string `json:"user_id"`
9595
}
9696

97-
// ApiCommunityInviteResponse is a response as found at: POST /_matrix/client/r0/groups/{communityId}/admin/users/invite/<invitee-id>
97+
// ApiCommunityInviteResponse is a response as found at: POST /_matrix/client/{apiVersion:(r0|v3)}/groups/{communityId}/admin/users/invite/<invitee-id>
9898
type ApiCommunityInviteResponse struct {
9999
State string `json:"state"`
100100
}
101101

102-
// ApiCommunityInvitedUsersResponse is a response as found at: GET /_matrix/client/r0/groups/{communityId}/invited_users
102+
// ApiCommunityInvitedUsersResponse is a response as found at: GET /_matrix/client/{apiVersion:(r0|v3)}/groups/{communityId}/invited_users
103103
type ApiCommunityInvitedUsersResponse struct {
104104
Chunk []ApiEntityCommunityInvitedUser `json:"chunk"`
105105
TotalUserCountEstimate int `json:"total_user_count_estimate"`

0 commit comments

Comments
 (0)