2022-12-22 11:54:03 +01:00
package clientapi
import (
"context"
"net/http"
"net/http/httptest"
"testing"
2023-03-22 09:21:32 +01:00
"time"
2022-12-22 11:54:03 +01:00
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
2023-01-19 21:02:32 +01:00
"github.com/matrix-org/dendrite/federationapi"
2023-03-17 12:09:45 +01:00
"github.com/matrix-org/dendrite/internal/caching"
2023-03-22 09:21:32 +01:00
"github.com/matrix-org/dendrite/internal/httputil"
"github.com/matrix-org/dendrite/internal/sqlutil"
2022-12-22 11:54:03 +01:00
"github.com/matrix-org/dendrite/roomserver"
2023-01-19 21:02:32 +01:00
"github.com/matrix-org/dendrite/roomserver/api"
2022-12-22 11:54:03 +01:00
"github.com/matrix-org/dendrite/setup/config"
2023-03-22 09:21:32 +01:00
"github.com/matrix-org/dendrite/setup/jetstream"
2023-01-19 21:02:32 +01:00
"github.com/matrix-org/dendrite/syncapi"
2022-12-22 11:54:03 +01:00
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
"github.com/tidwall/gjson"
"github.com/matrix-org/dendrite/test"
"github.com/matrix-org/dendrite/test/testrig"
"github.com/matrix-org/dendrite/userapi"
uapi "github.com/matrix-org/dendrite/userapi/api"
)
func TestAdminResetPassword ( t * testing . T ) {
aliceAdmin := test . NewUser ( t , test . WithAccountType ( uapi . AccountTypeAdmin ) )
bob := test . NewUser ( t , test . WithAccountType ( uapi . AccountTypeUser ) )
vhUser := & test . User { ID : "@vhuser:vh1" }
ctx := context . Background ( )
test . WithAllDatabases ( t , func ( t * testing . T , dbType test . DBType ) {
2023-03-22 09:21:32 +01:00
cfg , processCtx , close := testrig . CreateConfig ( t , dbType )
defer close ( )
natsInstance := jetstream . NATSInstance { }
2022-12-22 11:54:03 +01:00
// add a vhost
2023-03-22 09:21:32 +01:00
cfg . Global . VirtualHosts = append ( cfg . Global . VirtualHosts , & config . VirtualHost {
2022-12-22 11:54:03 +01:00
SigningIdentity : gomatrixserverlib . SigningIdentity { ServerName : "vh1" } ,
} )
2023-03-22 09:21:32 +01:00
routers := httputil . NewRouters ( )
cm := sqlutil . NewConnectionManager ( processCtx , cfg . Global . DatabaseOptions )
caches := caching . NewRistrettoCache ( 128 * 1024 * 1024 , time . Hour , caching . DisableMetrics )
rsAPI := roomserver . NewInternalAPI ( processCtx , cfg , cm , & natsInstance , caches , caching . DisableMetrics )
2022-12-22 11:54:03 +01:00
// Needed for changing the password/login
2023-03-22 09:21:32 +01:00
userAPI := userapi . NewInternalAPI ( processCtx , cfg , cm , & natsInstance , rsAPI , nil )
2022-12-22 11:54:03 +01:00
// We mostly need the userAPI for this test, so nil for other APIs/caches etc.
2023-03-22 09:21:32 +01:00
AddPublicRoutes ( processCtx , routers , cfg , & natsInstance , nil , rsAPI , nil , nil , nil , userAPI , nil , nil , caching . DisableMetrics )
2022-12-22 11:54:03 +01:00
// Create the users in the userapi and login
accessTokens := map [ * test . User ] string {
aliceAdmin : "" ,
bob : "" ,
vhUser : "" ,
}
for u := range accessTokens {
localpart , serverName , _ := gomatrixserverlib . SplitID ( '@' , u . ID )
userRes := & uapi . PerformAccountCreationResponse { }
password := util . RandomString ( 8 )
if err := userAPI . PerformAccountCreation ( ctx , & uapi . PerformAccountCreationRequest {
AccountType : u . AccountType ,
Localpart : localpart ,
ServerName : serverName ,
Password : password ,
} , userRes ) ; err != nil {
t . Errorf ( "failed to create account: %s" , err )
}
req := test . NewRequest ( t , http . MethodPost , "/_matrix/client/v3/login" , test . WithJSONBody ( t , map [ string ] interface { } {
"type" : authtypes . LoginTypePassword ,
"identifier" : map [ string ] interface { } {
"type" : "m.id.user" ,
"user" : u . ID ,
} ,
"password" : password ,
} ) )
rec := httptest . NewRecorder ( )
2023-03-22 09:21:32 +01:00
routers . Client . ServeHTTP ( rec , req )
2022-12-22 11:54:03 +01:00
if rec . Code != http . StatusOK {
t . Fatalf ( "failed to login: %s" , rec . Body . String ( ) )
}
accessTokens [ u ] = gjson . GetBytes ( rec . Body . Bytes ( ) , "access_token" ) . String ( )
}
testCases := [ ] struct {
name string
requestingUser * test . User
userID string
requestOpt test . HTTPRequestOpt
wantOK bool
withHeader bool
} {
{ name : "Missing auth" , requestingUser : bob , wantOK : false , userID : bob . ID } ,
{ name : "Bob is denied access" , requestingUser : bob , wantOK : false , withHeader : true , userID : bob . ID } ,
{ name : "Alice is allowed access" , requestingUser : aliceAdmin , wantOK : true , withHeader : true , userID : bob . ID , requestOpt : test . WithJSONBody ( t , map [ string ] interface { } {
"password" : util . RandomString ( 8 ) ,
} ) } ,
{ name : "missing userID does not call function" , requestingUser : aliceAdmin , wantOK : false , withHeader : true , userID : "" } , // this 404s
{ name : "rejects empty password" , requestingUser : aliceAdmin , wantOK : false , withHeader : true , userID : bob . ID , requestOpt : test . WithJSONBody ( t , map [ string ] interface { } {
"password" : "" ,
} ) } ,
{ name : "rejects unknown server name" , requestingUser : aliceAdmin , wantOK : false , withHeader : true , userID : "@doesnotexist:localhost" , requestOpt : test . WithJSONBody ( t , map [ string ] interface { } { } ) } ,
{ name : "rejects unknown user" , requestingUser : aliceAdmin , wantOK : false , withHeader : true , userID : "@doesnotexist:test" , requestOpt : test . WithJSONBody ( t , map [ string ] interface { } { } ) } ,
{ name : "allows changing password for different vhost" , requestingUser : aliceAdmin , wantOK : true , withHeader : true , userID : vhUser . ID , requestOpt : test . WithJSONBody ( t , map [ string ] interface { } {
"password" : util . RandomString ( 8 ) ,
} ) } ,
{ name : "rejects existing user, missing body" , requestingUser : aliceAdmin , wantOK : false , withHeader : true , userID : bob . ID } ,
{ name : "rejects invalid userID" , requestingUser : aliceAdmin , wantOK : false , withHeader : true , userID : "!notauserid:test" , requestOpt : test . WithJSONBody ( t , map [ string ] interface { } { } ) } ,
{ name : "rejects invalid json" , requestingUser : aliceAdmin , wantOK : false , withHeader : true , userID : bob . ID , requestOpt : test . WithJSONBody ( t , ` { invalidJSON} ` ) } ,
{ name : "rejects too weak password" , requestingUser : aliceAdmin , wantOK : false , withHeader : true , userID : bob . ID , requestOpt : test . WithJSONBody ( t , map [ string ] interface { } {
"password" : util . RandomString ( 6 ) ,
} ) } ,
{ name : "rejects too long password" , requestingUser : aliceAdmin , wantOK : false , withHeader : true , userID : bob . ID , requestOpt : test . WithJSONBody ( t , map [ string ] interface { } {
"password" : util . RandomString ( 513 ) ,
} ) } ,
}
for _ , tc := range testCases {
2023-01-19 21:02:32 +01:00
tc := tc // ensure we don't accidentally only test the last test case
2022-12-22 11:54:03 +01:00
t . Run ( tc . name , func ( t * testing . T ) {
req := test . NewRequest ( t , http . MethodPost , "/_dendrite/admin/resetPassword/" + tc . userID )
if tc . requestOpt != nil {
req = test . NewRequest ( t , http . MethodPost , "/_dendrite/admin/resetPassword/" + tc . userID , tc . requestOpt )
}
if tc . withHeader {
req . Header . Set ( "Authorization" , "Bearer " + accessTokens [ tc . requestingUser ] )
}
rec := httptest . NewRecorder ( )
2023-03-22 09:21:32 +01:00
routers . DendriteAdmin . ServeHTTP ( rec , req )
2022-12-22 11:54:03 +01:00
t . Logf ( "%s" , rec . Body . String ( ) )
if tc . wantOK && rec . Code != http . StatusOK {
t . Fatalf ( "expected http status %d, got %d: %s" , http . StatusOK , rec . Code , rec . Body . String ( ) )
}
} )
}
} )
}
2023-01-19 21:02:32 +01:00
func TestPurgeRoom ( t * testing . T ) {
aliceAdmin := test . NewUser ( t , test . WithAccountType ( uapi . AccountTypeAdmin ) )
bob := test . NewUser ( t )
room := test . NewRoom ( t , aliceAdmin , test . RoomPreset ( test . PresetTrustedPrivateChat ) )
// Invite Bob
room . CreateAndInsert ( t , aliceAdmin , gomatrixserverlib . MRoomMember , map [ string ] interface { } {
"membership" : "invite" ,
} , test . WithStateKey ( bob . ID ) )
ctx := context . Background ( )
test . WithAllDatabases ( t , func ( t * testing . T , dbType test . DBType ) {
2023-03-22 09:21:32 +01:00
cfg , processCtx , close := testrig . CreateConfig ( t , dbType )
caches := caching . NewRistrettoCache ( 128 * 1024 * 1024 , time . Hour , caching . DisableMetrics )
natsInstance := jetstream . NATSInstance { }
defer close ( )
2023-01-19 21:02:32 +01:00
2023-03-22 09:21:32 +01:00
routers := httputil . NewRouters ( )
cm := sqlutil . NewConnectionManager ( processCtx , cfg . Global . DatabaseOptions )
rsAPI := roomserver . NewInternalAPI ( processCtx , cfg , cm , & natsInstance , caches , caching . DisableMetrics )
userAPI := userapi . NewInternalAPI ( processCtx , cfg , cm , & natsInstance , rsAPI , nil )
2023-01-19 21:02:32 +01:00
// this starts the JetStream consumers
2023-03-22 09:21:32 +01:00
syncapi . AddPublicRoutes ( processCtx , routers , cfg , cm , & natsInstance , userAPI , rsAPI , caches , caching . DisableMetrics )
federationapi . NewInternalAPI ( processCtx , cfg , cm , & natsInstance , nil , rsAPI , caches , nil , true )
2023-01-19 21:02:32 +01:00
rsAPI . SetFederationAPI ( nil , nil )
// Create the room
if err := api . SendEvents ( ctx , rsAPI , api . KindNew , room . Events ( ) , "test" , "test" , "test" , nil , false ) ; err != nil {
t . Fatalf ( "failed to send events: %v" , err )
}
// We mostly need the rsAPI for this test, so nil for other APIs/caches etc.
2023-03-22 09:21:32 +01:00
AddPublicRoutes ( processCtx , routers , cfg , & natsInstance , nil , rsAPI , nil , nil , nil , userAPI , nil , nil , caching . DisableMetrics )
2023-01-19 21:02:32 +01:00
// Create the users in the userapi and login
accessTokens := map [ * test . User ] string {
aliceAdmin : "" ,
}
for u := range accessTokens {
localpart , serverName , _ := gomatrixserverlib . SplitID ( '@' , u . ID )
userRes := & uapi . PerformAccountCreationResponse { }
password := util . RandomString ( 8 )
if err := userAPI . PerformAccountCreation ( ctx , & uapi . PerformAccountCreationRequest {
AccountType : u . AccountType ,
Localpart : localpart ,
ServerName : serverName ,
Password : password ,
} , userRes ) ; err != nil {
t . Errorf ( "failed to create account: %s" , err )
}
req := test . NewRequest ( t , http . MethodPost , "/_matrix/client/v3/login" , test . WithJSONBody ( t , map [ string ] interface { } {
"type" : authtypes . LoginTypePassword ,
"identifier" : map [ string ] interface { } {
"type" : "m.id.user" ,
"user" : u . ID ,
} ,
"password" : password ,
} ) )
rec := httptest . NewRecorder ( )
2023-03-22 09:21:32 +01:00
routers . Client . ServeHTTP ( rec , req )
2023-01-19 21:02:32 +01:00
if rec . Code != http . StatusOK {
t . Fatalf ( "failed to login: %s" , rec . Body . String ( ) )
}
accessTokens [ u ] = gjson . GetBytes ( rec . Body . Bytes ( ) , "access_token" ) . String ( )
}
testCases := [ ] struct {
name string
roomID string
wantOK bool
} {
{ name : "Can purge existing room" , wantOK : true , roomID : room . ID } ,
{ name : "Can not purge non-existent room" , wantOK : false , roomID : "!doesnotexist:localhost" } ,
{ name : "rejects invalid room ID" , wantOK : false , roomID : "@doesnotexist:localhost" } ,
}
for _ , tc := range testCases {
tc := tc // ensure we don't accidentally only test the last test case
t . Run ( tc . name , func ( t * testing . T ) {
req := test . NewRequest ( t , http . MethodPost , "/_dendrite/admin/purgeRoom/" + tc . roomID )
req . Header . Set ( "Authorization" , "Bearer " + accessTokens [ aliceAdmin ] )
rec := httptest . NewRecorder ( )
2023-03-22 09:21:32 +01:00
routers . DendriteAdmin . ServeHTTP ( rec , req )
2023-01-19 21:02:32 +01:00
t . Logf ( "%s" , rec . Body . String ( ) )
if tc . wantOK && rec . Code != http . StatusOK {
t . Fatalf ( "expected http status %d, got %d: %s" , http . StatusOK , rec . Code , rec . Body . String ( ) )
}
} )
}
} )
}