Skip to content

Commit 464ddbf

Browse files
committed
msc3861: validate access_token uniqueness manually
If MAS is in use, server does not generate access tokens for its client devices. As a result, the access_token value in device table will always be empty. The unique constraint is enabled for the access_token it does not allow storing empty values in the database so we had to drop it. Since we still have old login logic, the constraint is still required, so we have to check the uniqueness manually.
1 parent 950555a commit 464ddbf

File tree

7 files changed

+84
-31
lines changed

7 files changed

+84
-31
lines changed

appservice/appservice.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func NewInternalAPI(
5353
if err := generateAppServiceAccount(userAPI, appservice, cfg.Global.ServerName); err != nil {
5454
logrus.WithFields(logrus.Fields{
5555
"appservice": appservice.ID,
56+
"as_token": appservice.ASToken,
5657
}).WithError(err).Panicf("failed to generate bot account for appservice")
5758
}
5859
}
@@ -92,12 +93,13 @@ func generateAppServiceAccount(
9293
}
9394
var devRes userapi.PerformDeviceCreationResponse
9495
err = userAPI.PerformDeviceCreation(context.Background(), &userapi.PerformDeviceCreationRequest{
95-
Localpart: as.SenderLocalpart,
96-
ServerName: serverName,
97-
AccessToken: as.ASToken,
98-
DeviceID: &as.SenderLocalpart,
99-
DeviceDisplayName: &as.SenderLocalpart,
100-
NoDeviceListUpdate: true,
96+
Localpart: as.SenderLocalpart,
97+
ServerName: serverName,
98+
AccessToken: as.ASToken,
99+
DeviceID: &as.SenderLocalpart,
100+
DeviceDisplayName: &as.SenderLocalpart,
101+
NoDeviceListUpdate: true,
102+
AccessTokenUniqueConstraintDisabled: false,
101103
}, &devRes)
102104
return err
103105
}

appservice/appservice_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func TestAppserviceInternalAPI(t *testing.T) {
139139
as := &config.ApplicationService{
140140
ID: "someID",
141141
URL: srv.URL,
142-
ASToken: "",
142+
ASToken: util.RandomString(12),
143143
HSToken: "",
144144
SenderLocalpart: "senderLocalPart",
145145
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
@@ -233,7 +233,7 @@ func TestAppserviceInternalAPI_UnixSocket_Simple(t *testing.T) {
233233
as := &config.ApplicationService{
234234
ID: "someID",
235235
URL: fmt.Sprintf("unix://%s", socket),
236-
ASToken: "",
236+
ASToken: util.RandomString(8),
237237
HSToken: "",
238238
SenderLocalpart: "senderLocalPart",
239239
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
@@ -377,7 +377,7 @@ func TestRoomserverConsumerOneInvite(t *testing.T) {
377377
as := &config.ApplicationService{
378378
ID: "someID",
379379
URL: srv.URL,
380-
ASToken: "",
380+
ASToken: util.RandomString(8),
381381
HSToken: "",
382382
SenderLocalpart: "senderLocalPart",
383383
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
@@ -510,7 +510,7 @@ func TestOutputAppserviceEvent(t *testing.T) {
510510
as := &config.ApplicationService{
511511
ID: "someID",
512512
URL: srv.URL,
513-
ASToken: "",
513+
ASToken: util.RandomString(8),
514514
HSToken: "",
515515
SenderLocalpart: "senderLocalPart",
516516
NamespaceMap: map[string][]config.ApplicationServiceNamespace{

clientapi/admin_test.go

+9-6
Original file line numberDiff line numberDiff line change
@@ -1637,8 +1637,9 @@ func TestAdminUserDeviceRetrieveCreate(t *testing.T) {
16371637
t.Run("Retrieve device", func(t *testing.T) {
16381638
var deviceRes uapi.PerformDeviceCreationResponse
16391639
if err := userAPI.PerformDeviceCreation(ctx, &uapi.PerformDeviceCreationRequest{
1640-
Localpart: alice.Localpart,
1641-
ServerName: cfg.Global.ServerName,
1640+
Localpart: alice.Localpart,
1641+
ServerName: cfg.Global.ServerName,
1642+
AccessTokenUniqueConstraintDisabled: true,
16421643
}, &deviceRes); err != nil {
16431644
t.Errorf("failed to create account: %s", err)
16441645
}
@@ -1747,8 +1748,9 @@ func TestAdminUserDeviceDelete(t *testing.T) {
17471748
t.Run("Delete existing device", func(t *testing.T) {
17481749
var deviceRes uapi.PerformDeviceCreationResponse
17491750
if err := userAPI.PerformDeviceCreation(ctx, &uapi.PerformDeviceCreationRequest{
1750-
Localpart: alice.Localpart,
1751-
ServerName: cfg.Global.ServerName,
1751+
Localpart: alice.Localpart,
1752+
ServerName: cfg.Global.ServerName,
1753+
AccessTokenUniqueConstraintDisabled: true,
17521754
}, &deviceRes); err != nil {
17531755
t.Errorf("failed to create account: %s", err)
17541756
}
@@ -1844,8 +1846,9 @@ func TestAdminUserDevicesDelete(t *testing.T) {
18441846
t.Run("Delete existing user's devices", func(t *testing.T) {
18451847
var deviceRes uapi.PerformDeviceCreationResponse
18461848
if err := userAPI.PerformDeviceCreation(ctx, &uapi.PerformDeviceCreationRequest{
1847-
Localpart: alice.Localpart,
1848-
ServerName: cfg.Global.ServerName,
1849+
Localpart: alice.Localpart,
1850+
ServerName: cfg.Global.ServerName,
1851+
AccessTokenUniqueConstraintDisabled: true,
18491852
}, &deviceRes); err != nil {
18501853
t.Errorf("failed to create account: %s", err)
18511854
}

setup/mscs/msc3861/msc3861_user_verifier.go

+24-8
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ func (r *mscError) Error() string {
9696

9797
// VerifyUserFromRequest authenticates the HTTP request, on success returns Device of the requester.
9898
func (m *MSC3861UserVerifier) VerifyUserFromRequest(req *http.Request) (*api.Device, *util.JSONResponse) {
99-
util.GetLogger(req.Context()).Debug("MSC3861.VerifyUserFromRequest")
99+
ctx := req.Context()
100+
util.GetLogger(ctx).Debug("MSC3861.VerifyUserFromRequest")
100101
// Try to find the Application Service user
101102
token, err := auth.ExtractAccessToken(req)
102103
if err != nil {
@@ -105,8 +106,22 @@ func (m *MSC3861UserVerifier) VerifyUserFromRequest(req *http.Request) (*api.Dev
105106
JSON: spec.MissingToken(err.Error()),
106107
}
107108
}
108-
// TODO: try to get appservice user first. See https://github.com/element-hq/synapse/blob/develop/synapse/api/auth/msc3861_delegated.py#L273
109-
userData, err := m.getUserByAccessToken(req.Context(), token)
109+
if appServiceUserID := req.URL.Query().Get("user_id"); appServiceUserID != "" {
110+
var res api.QueryAccessTokenResponse
111+
err = m.userAPI.QueryAccessToken(ctx, &api.QueryAccessTokenRequest{
112+
AccessToken: token,
113+
AppServiceUserID: req.URL.Query().Get("user_id"),
114+
}, &res)
115+
if err != nil {
116+
util.GetLogger(ctx).WithError(err).Error("userAPI.QueryAccessToken failed")
117+
return nil, &util.JSONResponse{
118+
Code: http.StatusInternalServerError,
119+
JSON: spec.InternalServerError{},
120+
}
121+
}
122+
}
123+
124+
userData, err := m.getUserByAccessToken(ctx, token)
110125
if err != nil {
111126
switch e := err.(type) {
112127
case (*mscError):
@@ -306,11 +321,12 @@ func (m *MSC3861UserVerifier) getUserByAccessToken(ctx context.Context, token st
306321
var rs api.PerformDeviceCreationResponse
307322
deviceDisplayName := "OIDC-native client"
308323
if err := m.userAPI.PerformDeviceCreation(ctx, &api.PerformDeviceCreationRequest{
309-
Localpart: localpart,
310-
ServerName: m.serverName,
311-
AccessToken: "",
312-
DeviceID: &deviceID,
313-
DeviceDisplayName: &deviceDisplayName,
324+
Localpart: localpart,
325+
ServerName: m.serverName,
326+
AccessToken: "",
327+
DeviceID: &deviceID,
328+
DeviceDisplayName: &deviceDisplayName,
329+
AccessTokenUniqueConstraintDisabled: true,
314330
// TODO: Cannot add IPAddr and Useragent values here. Should we care about it here?
315331
}, &rs); err != nil {
316332
logger.WithError(err).Error("PerformDeviceCreation")

userapi/api/api.go

+5
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,11 @@ type PerformDeviceCreationRequest struct {
381381
// FromRegistration determines if this request comes from registering a new account
382382
// and is in most cases false.
383383
FromRegistration bool
384+
385+
// AccessTokenUniqueConstraintDisabled determines if unique constraint is applicable for the AccessToken.
386+
// It is false if an external auth service is in use (e.g. MAS) and server does not generate its own
387+
// auth tokens. Otherwise, if traditional login is in use, the value is true. Default is false.
388+
AccessTokenUniqueConstraintDisabled bool
384389
}
385390

386391
// PerformDeviceCreationResponse is the response for PerformDeviceCreation

userapi/internal/user_api.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,15 @@ func (a *UserInternalAPI) PerformDeviceCreation(ctx context.Context, req *api.Pe
306306
"device_id": req.DeviceID,
307307
"display_name": req.DeviceDisplayName,
308308
}).Info("PerformDeviceCreation")
309-
// TODO: Since we have deleted access_token's unique constraint from the db,
310-
// we probably should check its uniqueness if msc3861 is disabled
309+
if !req.AccessTokenUniqueConstraintDisabled {
310+
dev, err := a.DB.GetDeviceByAccessToken(ctx, req.AccessToken)
311+
if err != nil && !errors.Is(err, sql.ErrNoRows) {
312+
return err
313+
}
314+
if dev.UserID != "" {
315+
return errors.New("unique constraint violation. Access token is not unique" + dev.AccessToken)
316+
}
317+
}
311318
dev, err := a.DB.CreateDevice(ctx, req.Localpart, serverName, req.DeviceID, req.AccessToken, req.DeviceDisplayName, req.IPAddr, req.UserAgent)
312319
if err != nil {
313320
return err

userapi/userapi_test.go

+25-5
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,8 @@ func TestAccountData(t *testing.T) {
445445
func TestDevices(t *testing.T) {
446446
ctx := context.Background()
447447

448+
dupeAccessToken := util.RandomString(8)
449+
448450
displayName := "testing"
449451

450452
creationTests := []struct {
@@ -455,25 +457,43 @@ func TestDevices(t *testing.T) {
455457
}{
456458
{
457459
name: "not a local user",
458-
inputData: &api.PerformDeviceCreationRequest{Localpart: "test1", ServerName: "notlocal"},
460+
inputData: &api.PerformDeviceCreationRequest{Localpart: "test1", ServerName: "notlocal", AccessTokenUniqueConstraintDisabled: true},
459461
wantErr: true,
460462
},
461463
{
462464
name: "implicit local user",
463-
inputData: &api.PerformDeviceCreationRequest{Localpart: "test1", AccessToken: util.RandomString(8), NoDeviceListUpdate: true, DeviceDisplayName: &displayName},
465+
inputData: &api.PerformDeviceCreationRequest{Localpart: "test1", AccessToken: util.RandomString(8), NoDeviceListUpdate: true, DeviceDisplayName: &displayName, AccessTokenUniqueConstraintDisabled: true},
464466
},
465467
{
466468
name: "explicit local user",
467-
inputData: &api.PerformDeviceCreationRequest{Localpart: "test2", ServerName: "test", AccessToken: util.RandomString(8), NoDeviceListUpdate: true},
469+
inputData: &api.PerformDeviceCreationRequest{Localpart: "test2", ServerName: "test", AccessToken: util.RandomString(8), NoDeviceListUpdate: true, AccessTokenUniqueConstraintDisabled: true},
468470
},
469471
{
470472
name: "test3 second device", // used to test deletion later
471-
inputData: &api.PerformDeviceCreationRequest{Localpart: "test3", ServerName: "test", AccessToken: util.RandomString(8), NoDeviceListUpdate: true},
473+
inputData: &api.PerformDeviceCreationRequest{Localpart: "test3", ServerName: "test", AccessToken: util.RandomString(8), NoDeviceListUpdate: true, AccessTokenUniqueConstraintDisabled: true},
472474
},
473475
{
474476
name: "test3 third device", // used to test deletion later
475477
wantNewDevID: true,
476-
inputData: &api.PerformDeviceCreationRequest{Localpart: "test3", ServerName: "test", AccessToken: util.RandomString(8), NoDeviceListUpdate: true},
478+
inputData: &api.PerformDeviceCreationRequest{Localpart: "test3", ServerName: "test", AccessToken: util.RandomString(8), NoDeviceListUpdate: true, AccessTokenUniqueConstraintDisabled: true},
479+
},
480+
{
481+
name: "dupe token - ok (unique constraint enabled)",
482+
inputData: &api.PerformDeviceCreationRequest{Localpart: "test3", ServerName: "test", AccessToken: dupeAccessToken, NoDeviceListUpdate: true, AccessTokenUniqueConstraintDisabled: false},
483+
},
484+
{
485+
name: "dupe token - not ok (unique constraint enabled)",
486+
inputData: &api.PerformDeviceCreationRequest{Localpart: "test3", ServerName: "test", AccessToken: dupeAccessToken, NoDeviceListUpdate: true, AccessTokenUniqueConstraintDisabled: false},
487+
wantErr: true,
488+
},
489+
{
490+
name: "dupe token - ok (unique constraint disabled)",
491+
inputData: &api.PerformDeviceCreationRequest{Localpart: "test3", ServerName: "test", AccessToken: dupeAccessToken, NoDeviceListUpdate: true, AccessTokenUniqueConstraintDisabled: true},
492+
},
493+
{
494+
name: "dupe token - not ok (unique constraint disabled)",
495+
inputData: &api.PerformDeviceCreationRequest{Localpart: "test3", ServerName: "test", AccessToken: dupeAccessToken, NoDeviceListUpdate: true, AccessTokenUniqueConstraintDisabled: true},
496+
wantErr: true,
477497
},
478498
}
479499

0 commit comments

Comments
 (0)