Appservice Login (2nd attempt) (#3078)

Rebase of #2936 as @vijfhoek wrote he got no time to work on this, and I
kind of needed it for my experiments.
I checked the tests, and it is working with my example code (i.e.
impersonating, registering, creating channel, invite people, write
messages).
I'm not a huge `go` pro, and still learning, but I tried to fix and/or
integrate the changes as best as possible with the current `main` branch
changes.
If there is anything left, let me know and I'll try to figure it out.

Signed-off-by: `Kuhn Christopher <kuhnchris+git@kuhnchris.eu>`

---------

Signed-off-by: Sijmen <me@sijman.nl>
Signed-off-by: Sijmen Schoon <me@sijman.nl>
Co-authored-by: Sijmen Schoon <me@sijman.nl>
Co-authored-by: Sijmen Schoon <me@vijf.life>
Co-authored-by: Till <2353100+S7evinK@users.noreply.github.com>
This commit is contained in:
KuhnChris 2023-11-24 22:34:13 +01:00 committed by GitHub
parent b8f91485b4
commit 4f943771fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 530 additions and 35 deletions

View file

@ -3,9 +3,11 @@ package internal
import (
"net/http"
"reflect"
"regexp"
"strings"
"testing"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util"
)
@ -38,7 +40,7 @@ func Test_validatePassword(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
gotErr := ValidatePassword(tt.password)
if !reflect.DeepEqual(gotErr, tt.wantError) {
t.Errorf("validatePassword() = %v, wantJSON %v", gotErr, tt.wantError)
t.Errorf("validatePassword() = %v, wantError %v", gotErr, tt.wantError)
}
if got := PasswordResponse(gotErr); !reflect.DeepEqual(got, tt.wantJSON) {
@ -167,3 +169,133 @@ func Test_validateUsername(t *testing.T) {
})
}
}
// This method tests validation of the provided Application Service token and
// username that they're registering
func TestValidateApplicationServiceRequest(t *testing.T) {
// Create a fake application service
regex := "@_appservice_.*"
fakeNamespace := config.ApplicationServiceNamespace{
Exclusive: true,
Regex: regex,
RegexpObject: regexp.MustCompile(regex),
}
fakeSenderLocalpart := "_appservice_bot"
fakeApplicationService := config.ApplicationService{
ID: "FakeAS",
URL: "null",
ASToken: "1234",
HSToken: "4321",
SenderLocalpart: fakeSenderLocalpart,
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
"users": {fakeNamespace},
},
}
// Create a second fake application service where userIDs ending in
// "_overlap" overlap with the first.
regex = "@_.*_overlap"
fakeNamespace = config.ApplicationServiceNamespace{
Exclusive: true,
Regex: regex,
RegexpObject: regexp.MustCompile(regex),
}
fakeApplicationServiceOverlap := config.ApplicationService{
ID: "FakeASOverlap",
URL: fakeApplicationService.URL,
ASToken: fakeApplicationService.ASToken,
HSToken: fakeApplicationService.HSToken,
SenderLocalpart: "_appservice_bot_overlap",
NamespaceMap: map[string][]config.ApplicationServiceNamespace{
"users": {fakeNamespace},
},
}
// Set up a config
fakeConfig := &config.Dendrite{}
fakeConfig.Defaults(config.DefaultOpts{
Generate: true,
})
fakeConfig.Global.ServerName = "localhost"
fakeConfig.ClientAPI.Derived.ApplicationServices = []config.ApplicationService{fakeApplicationService, fakeApplicationServiceOverlap}
tests := []struct {
name string
localpart string
asToken string
wantError bool
wantASID string
}{
// Access token is correct, userID omitted so we are acting as SenderLocalpart
{
name: "correct access token but omitted userID",
localpart: fakeSenderLocalpart,
asToken: fakeApplicationService.ASToken,
wantError: false,
wantASID: fakeApplicationService.ID,
},
// Access token is incorrect, userID omitted so we are acting as SenderLocalpart
{
name: "incorrect access token but omitted userID",
localpart: fakeSenderLocalpart,
asToken: "xxxx",
wantError: true,
wantASID: "",
},
// Access token is correct, acting as valid userID
{
name: "correct access token and valid userID",
localpart: "_appservice_bob",
asToken: fakeApplicationService.ASToken,
wantError: false,
wantASID: fakeApplicationService.ID,
},
// Access token is correct, acting as invalid userID
{
name: "correct access token but invalid userID",
localpart: "_something_else",
asToken: fakeApplicationService.ASToken,
wantError: true,
wantASID: "",
},
// Access token is correct, acting as userID that matches two exclusive namespaces
{
name: "correct access token but non-exclusive userID",
localpart: "_appservice_overlap",
asToken: fakeApplicationService.ASToken,
wantError: true,
wantASID: "",
},
// Access token is correct, acting as matching userID that is too long
{
name: "correct access token but too long userID",
localpart: "_appservice_" + strings.Repeat("a", maxUsernameLength),
asToken: fakeApplicationService.ASToken,
wantError: true,
wantASID: "",
},
// Access token is correct, acting as userID that matches but is invalid
{
name: "correct access token and matching but invalid userID",
localpart: "@_appservice_bob::",
asToken: fakeApplicationService.ASToken,
wantError: true,
wantASID: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotASID, gotResp := ValidateApplicationServiceRequest(&fakeConfig.ClientAPI, tt.localpart, tt.asToken)
if tt.wantError && gotResp == nil {
t.Error("expected an error, but succeeded")
}
if !tt.wantError && gotResp != nil {
t.Errorf("expected success, but returned error: %v", *gotResp)
}
if gotASID != tt.wantASID {
t.Errorf("returned '%s', but expected '%s'", gotASID, tt.wantASID)
}
})
}
}