Skip to content

Commit 20b3917

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 20b3917

File tree

9 files changed

+99
-73
lines changed

9 files changed

+99
-73
lines changed

.golangci.yml

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ run:
2828
# the dependency descriptions in go.mod.
2929
#modules-download-mode: (release|readonly|vendor)
3030

31-
3231
# output configuration options
3332
output:
3433
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
@@ -41,7 +40,6 @@ output:
4140
# print linter name in the end of issue text, default is true
4241
print-linter-name: true
4342

44-
4543
# all available settings of specific linters
4644
linters-settings:
4745
errcheck:
@@ -72,22 +70,12 @@ linters-settings:
7270
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
7371
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
7472
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
75-
golint:
76-
# minimal confidence for issues, default is 0.8
77-
min-confidence: 0.8
7873
gofmt:
7974
# simplify code: gofmt with `-s` option, true by default
8075
simplify: true
81-
goimports:
82-
# put imports beginning with prefix after 3rd-party packages;
83-
# it's a comma-separated list of prefixes
84-
#local-prefixes: github.com/org/project
8576
gocyclo:
8677
# minimal code complexity to report, 30 by default (but we recommend 10-20)
8778
min-complexity: 25
88-
maligned:
89-
# print struct with more effective memory layout or not, false by default
90-
suggest-new: true
9179
dupl:
9280
# tokens count to trigger issue, 150 by default
9381
threshold: 100
@@ -96,30 +84,17 @@ linters-settings:
9684
min-len: 3
9785
# minimal occurrences count to trigger, 3 by default
9886
min-occurrences: 3
99-
depguard:
100-
list-type: blacklist
101-
include-go-root: false
102-
packages:
103-
# - github.com/davecgh/go-spew/spew
10487
misspell:
10588
# Correct spellings using locale preferences for US or UK.
10689
# Default is to use a neutral variety of English.
10790
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
10891
locale: UK
109-
ignore-words:
110-
# - someword
11192
lll:
11293
# max line length, lines longer will be reported. Default is 120.
11394
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
11495
line-length: 96
11596
# tab width in spaces. Default to 1.
11697
tab-width: 1
117-
unused:
118-
# treat code as a program (not a library) and report unused exported identifiers; default is false.
119-
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
120-
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
121-
# with golangci-lint call it on a directory with the changed file.
122-
check-exported: false
12398
unparam:
12499
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
125100
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
@@ -189,7 +164,6 @@ linters:
189164
- unconvert # Should turn back on soon
190165
- goconst # Slightly annoying, as it reports "issues" in SQL statements
191166
disable-all: false
192-
presets:
193167
fast: false
194168

195169

@@ -212,13 +186,6 @@ issues:
212186
- bin
213187
- docs
214188

215-
# List of regexps of issue texts to exclude, empty list by default.
216-
# But independently from this option we use default exclude patterns,
217-
# it can be disabled by `exclude-use-default: false`. To list all
218-
# excluded by default patterns execute `golangci-lint run --help`
219-
exclude:
220-
# - abcdef
221-
222189
# Excluding configuration per-path, per-linter, per-text and per-source
223190
exclude-rules:
224191
# Exclude some linters from running on tests files.

appservice/appservice.go

Lines changed: 8 additions & 6 deletions
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

Lines changed: 4 additions & 4 deletions
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

Lines changed: 9 additions & 6 deletions
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
}

clientapi/routing/admin.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -584,14 +584,15 @@ func AdminUserDeviceRetrieveCreate(
584584
if !userDeviceExists {
585585
var rs userapi.PerformDeviceCreationResponse
586586
if err = userAPI.PerformDeviceCreation(req.Context(), &userapi.PerformDeviceCreationRequest{
587-
Localpart: local,
588-
ServerName: domain,
589-
DeviceID: &payload.DeviceID,
590-
DeviceDisplayName: &deviceDisplayName,
591-
IPAddr: "",
592-
UserAgent: req.UserAgent(),
593-
NoDeviceListUpdate: false,
594-
FromRegistration: false,
587+
Localpart: local,
588+
ServerName: domain,
589+
DeviceID: &payload.DeviceID,
590+
DeviceDisplayName: &deviceDisplayName,
591+
IPAddr: "",
592+
UserAgent: req.UserAgent(),
593+
NoDeviceListUpdate: false,
594+
FromRegistration: false,
595+
AccessTokenUniqueConstraintDisabled: true,
595596
}, &rs); err != nil {
596597
logger.WithError(err).Error("PerformDeviceCreation")
597598
return util.JSONResponse{

setup/mscs/msc3861/msc3861_user_verifier.go

Lines changed: 24 additions & 8 deletions
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: appServiceUserID,
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

Lines changed: 5 additions & 0 deletions
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

Lines changed: 9 additions & 2 deletions
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

Lines changed: 31 additions & 6 deletions
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,42 @@ 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},
477496
},
478497
}
479498

@@ -618,7 +637,13 @@ func TestDeviceIDReuse(t *testing.T) {
618637
res := api.PerformDeviceCreationResponse{}
619638
// create a first device
620639
deviceID := util.RandomString(8)
621-
req := api.PerformDeviceCreationRequest{Localpart: "alice", ServerName: "test", DeviceID: &deviceID, NoDeviceListUpdate: true}
640+
req := api.PerformDeviceCreationRequest{
641+
Localpart: "alice",
642+
ServerName: "test",
643+
DeviceID: &deviceID,
644+
NoDeviceListUpdate: true,
645+
AccessTokenUniqueConstraintDisabled: true,
646+
}
622647
err := intAPI.PerformDeviceCreation(ctx, &req, &res)
623648
if err != nil {
624649
t.Fatal(err)

0 commit comments

Comments
 (0)