Skip to content
This repository was archived by the owner on Nov 25, 2024. It is now read-only.

Commit 0f6b81f

Browse files
authored
Modernize appservice paths and authentication (#3316)
This brings Dendrite's appservice spec support up to v1.4, from the previous level of pre-release-spec support only (even r0.1.0 wasn't supported for pushing transactions 🙃). There are config options to revert to the old behavior, but the default is v1.4+ only. [Synapse also does that](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#use_appservice_legacy_authorization) mautrix bridges will drop support for legacy paths and authentication soon (and possibly also require matrix v1.4 to be advertised, but I might add some workaround to not require that for dendrite) Signed-off-by: Tulir Asokan <[email protected]>
1 parent a3a18fb commit 0f6b81f

File tree

5 files changed

+95
-39
lines changed

5 files changed

+95
-39
lines changed

appservice/api/query.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,17 @@ type UserIDExistsResponse struct {
8282
}
8383

8484
const (
85-
ASProtocolPath = "/_matrix/app/unstable/thirdparty/protocol/"
86-
ASUserPath = "/_matrix/app/unstable/thirdparty/user"
87-
ASLocationPath = "/_matrix/app/unstable/thirdparty/location"
85+
ASProtocolLegacyPath = "/_matrix/app/unstable/thirdparty/protocol/"
86+
ASUserLegacyPath = "/_matrix/app/unstable/thirdparty/user"
87+
ASLocationLegacyPath = "/_matrix/app/unstable/thirdparty/location"
88+
ASRoomAliasExistsLegacyPath = "/rooms/"
89+
ASUserExistsLegacyPath = "/users/"
90+
91+
ASProtocolPath = "/_matrix/app/v1/thirdparty/protocol/"
92+
ASUserPath = "/_matrix/app/v1/thirdparty/user"
93+
ASLocationPath = "/_matrix/app/v1/thirdparty/location"
94+
ASRoomAliasExistsPath = "/_matrix/app/v1/rooms/"
95+
ASUserExistsPath = "/_matrix/app/v1/users/"
8896
)
8997

9098
type ProtocolRequest struct {

appservice/consumers/roomserver.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,21 @@ func (s *OutputRoomEventConsumer) sendEvents(
206206
}
207207

208208
// Send the transaction to the appservice.
209-
// https://matrix.org/docs/spec/application_service/r0.1.2#put-matrix-app-v1-transactions-txnid
210-
address := fmt.Sprintf("%s/transactions/%s?access_token=%s", state.RequestUrl(), txnID, url.QueryEscape(state.HSToken))
209+
// https://spec.matrix.org/v1.9/application-service-api/#pushing-events
210+
path := "_matrix/app/v1/transactions"
211+
if s.cfg.LegacyPaths {
212+
path = "transactions"
213+
}
214+
address := fmt.Sprintf("%s/%s/%s", state.RequestUrl(), path, txnID)
215+
if s.cfg.LegacyAuth {
216+
address += "?access_token=" + url.QueryEscape(state.HSToken)
217+
}
211218
req, err := http.NewRequestWithContext(ctx, "PUT", address, bytes.NewBuffer(transaction))
212219
if err != nil {
213220
return err
214221
}
215222
req.Header.Set("Content-Type", "application/json")
223+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", state.HSToken))
216224
resp, err := state.HTTPClient.Do(req)
217225
if err != nil {
218226
return state.backoffAndPause(err)

appservice/query/query.go

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ package query
1919
import (
2020
"context"
2121
"encoding/json"
22+
"fmt"
2223
"io"
2324
"net/http"
2425
"net/url"
25-
"strings"
2626
"sync"
2727

2828
log "github.com/sirupsen/logrus"
@@ -32,9 +32,6 @@ import (
3232
"github.com/matrix-org/dendrite/setup/config"
3333
)
3434

35-
const roomAliasExistsPath = "/rooms/"
36-
const userIDExistsPath = "/users/"
37-
3835
// AppServiceQueryAPI is an implementation of api.AppServiceQueryAPI
3936
type AppServiceQueryAPI struct {
4037
Cfg *config.AppServiceAPI
@@ -55,21 +52,31 @@ func (a *AppServiceQueryAPI) RoomAliasExists(
5552
// Determine which application service should handle this request
5653
for _, appservice := range a.Cfg.Derived.ApplicationServices {
5754
if appservice.URL != "" && appservice.IsInterestedInRoomAlias(request.Alias) {
55+
path := api.ASRoomAliasExistsPath
56+
if a.Cfg.LegacyPaths {
57+
path = api.ASRoomAliasExistsLegacyPath
58+
}
5859
// The full path to the rooms API, includes hs token
59-
URL, err := url.Parse(appservice.RequestUrl() + roomAliasExistsPath)
60+
URL, err := url.Parse(appservice.RequestUrl() + path)
6061
if err != nil {
6162
return err
6263
}
6364

6465
URL.Path += request.Alias
65-
apiURL := URL.String() + "?access_token=" + appservice.HSToken
66+
if a.Cfg.LegacyAuth {
67+
q := URL.Query()
68+
q.Set("access_token", appservice.HSToken)
69+
URL.RawQuery = q.Encode()
70+
}
71+
apiURL := URL.String()
6672

6773
// Send a request to each application service. If one responds that it has
6874
// created the room, immediately return.
6975
req, err := http.NewRequest(http.MethodGet, apiURL, nil)
7076
if err != nil {
7177
return err
7278
}
79+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", appservice.HSToken))
7380
req = req.WithContext(ctx)
7481

7582
resp, err := appservice.HTTPClient.Do(req)
@@ -123,19 +130,29 @@ func (a *AppServiceQueryAPI) UserIDExists(
123130
for _, appservice := range a.Cfg.Derived.ApplicationServices {
124131
if appservice.URL != "" && appservice.IsInterestedInUserID(request.UserID) {
125132
// The full path to the rooms API, includes hs token
126-
URL, err := url.Parse(appservice.RequestUrl() + userIDExistsPath)
133+
path := api.ASUserExistsPath
134+
if a.Cfg.LegacyPaths {
135+
path = api.ASUserExistsLegacyPath
136+
}
137+
URL, err := url.Parse(appservice.RequestUrl() + path)
127138
if err != nil {
128139
return err
129140
}
130141
URL.Path += request.UserID
131-
apiURL := URL.String() + "?access_token=" + appservice.HSToken
142+
if a.Cfg.LegacyAuth {
143+
q := URL.Query()
144+
q.Set("access_token", appservice.HSToken)
145+
URL.RawQuery = q.Encode()
146+
}
147+
apiURL := URL.String()
132148

133149
// Send a request to each application service. If one responds that it has
134150
// created the user, immediately return.
135151
req, err := http.NewRequest(http.MethodGet, apiURL, nil)
136152
if err != nil {
137153
return err
138154
}
155+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", appservice.HSToken))
139156
resp, err := appservice.HTTPClient.Do(req.WithContext(ctx))
140157
if resp != nil {
141158
defer func() {
@@ -176,25 +193,22 @@ type thirdpartyResponses interface {
176193
api.ASProtocolResponse | []api.ASUserResponse | []api.ASLocationResponse
177194
}
178195

179-
func requestDo[T thirdpartyResponses](client *http.Client, url string, response *T) (err error) {
180-
origURL := url
181-
// try v1 and unstable appservice endpoints
182-
for _, version := range []string{"v1", "unstable"} {
183-
var resp *http.Response
184-
var body []byte
185-
asURL := strings.Replace(origURL, "unstable", version, 1)
186-
resp, err = client.Get(asURL)
187-
if err != nil {
188-
continue
189-
}
190-
defer resp.Body.Close() // nolint: errcheck
191-
body, err = io.ReadAll(resp.Body)
192-
if err != nil {
193-
continue
194-
}
195-
return json.Unmarshal(body, &response)
196+
func requestDo[T thirdpartyResponses](as *config.ApplicationService, url string, response *T) error {
197+
req, err := http.NewRequest(http.MethodGet, url, nil)
198+
if err != nil {
199+
return err
196200
}
197-
return err
201+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", as.HSToken))
202+
resp, err := as.HTTPClient.Do(req)
203+
if err != nil {
204+
return err
205+
}
206+
defer resp.Body.Close() // nolint: errcheck
207+
body, err := io.ReadAll(resp.Body)
208+
if err != nil {
209+
return err
210+
}
211+
return json.Unmarshal(body, &response)
198212
}
199213

200214
func (a *AppServiceQueryAPI) Locations(
@@ -207,16 +221,22 @@ func (a *AppServiceQueryAPI) Locations(
207221
return err
208222
}
209223

224+
path := api.ASLocationPath
225+
if a.Cfg.LegacyPaths {
226+
path = api.ASLocationLegacyPath
227+
}
210228
for _, as := range a.Cfg.Derived.ApplicationServices {
211229
var asLocations []api.ASLocationResponse
212-
params.Set("access_token", as.HSToken)
230+
if a.Cfg.LegacyAuth {
231+
params.Set("access_token", as.HSToken)
232+
}
213233

214-
url := as.RequestUrl() + api.ASLocationPath
234+
url := as.RequestUrl() + path
215235
if req.Protocol != "" {
216236
url += "/" + req.Protocol
217237
}
218238

219-
if err := requestDo[[]api.ASLocationResponse](as.HTTPClient, url+"?"+params.Encode(), &asLocations); err != nil {
239+
if err := requestDo[[]api.ASLocationResponse](&as, url+"?"+params.Encode(), &asLocations); err != nil {
220240
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'locations' from application service")
221241
continue
222242
}
@@ -242,16 +262,22 @@ func (a *AppServiceQueryAPI) User(
242262
return err
243263
}
244264

265+
path := api.ASUserPath
266+
if a.Cfg.LegacyPaths {
267+
path = api.ASUserLegacyPath
268+
}
245269
for _, as := range a.Cfg.Derived.ApplicationServices {
246270
var asUsers []api.ASUserResponse
247-
params.Set("access_token", as.HSToken)
271+
if a.Cfg.LegacyAuth {
272+
params.Set("access_token", as.HSToken)
273+
}
248274

249-
url := as.RequestUrl() + api.ASUserPath
275+
url := as.RequestUrl() + path
250276
if req.Protocol != "" {
251277
url += "/" + req.Protocol
252278
}
253279

254-
if err := requestDo[[]api.ASUserResponse](as.HTTPClient, url+"?"+params.Encode(), &asUsers); err != nil {
280+
if err := requestDo[[]api.ASUserResponse](&as, url+"?"+params.Encode(), &asUsers); err != nil {
255281
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'user' from application service")
256282
continue
257283
}
@@ -272,6 +298,10 @@ func (a *AppServiceQueryAPI) Protocols(
272298
req *api.ProtocolRequest,
273299
resp *api.ProtocolResponse,
274300
) error {
301+
protocolPath := api.ASProtocolPath
302+
if a.Cfg.LegacyPaths {
303+
protocolPath = api.ASProtocolLegacyPath
304+
}
275305

276306
// get a single protocol response
277307
if req.Protocol != "" {
@@ -289,7 +319,7 @@ func (a *AppServiceQueryAPI) Protocols(
289319
response := api.ASProtocolResponse{}
290320
for _, as := range a.Cfg.Derived.ApplicationServices {
291321
var proto api.ASProtocolResponse
292-
if err := requestDo[api.ASProtocolResponse](as.HTTPClient, as.RequestUrl()+api.ASProtocolPath+req.Protocol, &proto); err != nil {
322+
if err := requestDo[api.ASProtocolResponse](&as, as.RequestUrl()+protocolPath+req.Protocol, &proto); err != nil {
293323
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'protocol' from application service")
294324
continue
295325
}
@@ -319,7 +349,7 @@ func (a *AppServiceQueryAPI) Protocols(
319349
for _, as := range a.Cfg.Derived.ApplicationServices {
320350
for _, p := range as.Protocols {
321351
var proto api.ASProtocolResponse
322-
if err := requestDo[api.ASProtocolResponse](as.HTTPClient, as.RequestUrl()+api.ASProtocolPath+p, &proto); err != nil {
352+
if err := requestDo[api.ASProtocolResponse](&as, as.RequestUrl()+protocolPath+p, &proto); err != nil {
323353
log.WithError(err).WithField("application_service", as.ID).Error("unable to get 'protocol' from application service")
324354
continue
325355
}

dendrite-sample.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ app_service_api:
154154
# to be sent to an insecure endpoint.
155155
disable_tls_validation: false
156156

157+
# Send the access_token query parameter with appservice requests in addition
158+
# to the Authorization header. This can cause hs_tokens to be saved to logs,
159+
# so it should not be enabled unless absolutely necessary.
160+
legacy_auth: false
161+
# Use the legacy unprefixed paths for appservice requests.
162+
legacy_paths: false
163+
157164
# Appservice configuration files to load into this homeserver.
158165
config_files:
159166
# - /path/to/appservice_registration.yaml

setup/config/config_appservice.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ type AppServiceAPI struct {
4040
// on appservice endpoints. This is not recommended in production!
4141
DisableTLSValidation bool `yaml:"disable_tls_validation"`
4242

43+
LegacyAuth bool `yaml:"legacy_auth"`
44+
LegacyPaths bool `yaml:"legacy_paths"`
45+
4346
ConfigFiles []string `yaml:"config_files"`
4447
}
4548

0 commit comments

Comments
 (0)