Skip to content

Commit 3fddabe

Browse files
authored
Move MakeLeave to GMSL (#385)
Mostly copy & paste from `MakeJoin`
1 parent b92e84b commit 3fddabe

File tree

2 files changed

+315
-0
lines changed

2 files changed

+315
-0
lines changed

handleleave.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright 2023 The Matrix.org Foundation C.I.C.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package gomatrixserverlib
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/matrix-org/gomatrixserverlib/spec"
21+
)
22+
23+
type HandleMakeLeaveResponse struct {
24+
LeaveTemplateEvent ProtoEvent
25+
RoomVersion RoomVersion
26+
}
27+
28+
type HandleMakeLeaveInput struct {
29+
UserID spec.UserID // The user wanting to leave the room
30+
RoomID spec.RoomID // The room the user wants to leave
31+
RoomVersion RoomVersion // The room version for the room being left
32+
RequestOrigin spec.ServerName // The server that sent the /make_leave federation request
33+
LocalServerName spec.ServerName // The name of this local server
34+
LocalServerInRoom bool // Whether this local server has a user currently joined to the room
35+
36+
// Returns a fully built version of the proto event and a list of state events required to auth this event
37+
BuildEventTemplate func(*ProtoEvent) (PDU, []PDU, error)
38+
}
39+
40+
func HandleMakeLeave(input HandleMakeLeaveInput) (*HandleMakeLeaveResponse, error) {
41+
42+
if input.UserID.Domain() != input.RequestOrigin {
43+
return nil, spec.Forbidden(fmt.Sprintf("The leave must be sent by the server of the user. Origin %s != %s",
44+
input.RequestOrigin, input.UserID.Domain()))
45+
}
46+
47+
// Check if we think we are still joined to the room
48+
if !input.LocalServerInRoom {
49+
return nil, spec.NotFound(fmt.Sprintf("Local server not currently joined to room: %s", input.RoomID.String()))
50+
}
51+
52+
// Try building an event for the server
53+
rawUserID := input.UserID.String()
54+
proto := ProtoEvent{
55+
Sender: input.UserID.String(),
56+
RoomID: input.RoomID.String(),
57+
Type: spec.MRoomMember,
58+
StateKey: &rawUserID,
59+
}
60+
content := MemberContent{
61+
Membership: spec.Leave,
62+
}
63+
64+
if err := proto.SetContent(content); err != nil {
65+
return nil, spec.InternalServerError{Err: "builder.SetContent failed"}
66+
}
67+
68+
event, stateEvents, templateErr := input.BuildEventTemplate(&proto)
69+
if templateErr != nil {
70+
return nil, templateErr
71+
}
72+
if event == nil {
73+
return nil, spec.InternalServerError{Err: "template builder returned nil event"}
74+
}
75+
if stateEvents == nil {
76+
return nil, spec.InternalServerError{Err: "template builder returned nil event state"}
77+
}
78+
if event.Type() != spec.MRoomMember {
79+
return nil, spec.InternalServerError{Err: fmt.Sprintf("expected leave event from template builder. got: %s", event.Type())}
80+
}
81+
82+
provider := NewAuthEvents(stateEvents)
83+
if err := Allowed(event, &provider); err != nil {
84+
return nil, spec.Forbidden(err.Error())
85+
}
86+
87+
makeLeaveResponse := HandleMakeLeaveResponse{
88+
LeaveTemplateEvent: proto,
89+
RoomVersion: input.RoomVersion,
90+
}
91+
return &makeLeaveResponse, nil
92+
}

handleleave_test.go

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package gomatrixserverlib
2+
3+
import (
4+
"crypto/rand"
5+
"fmt"
6+
"testing"
7+
"time"
8+
9+
"github.com/matrix-org/gomatrixserverlib/spec"
10+
"github.com/stretchr/testify/assert"
11+
"golang.org/x/crypto/ed25519"
12+
)
13+
14+
func TestHandleMakeLeave(t *testing.T) {
15+
validUser, err := spec.NewUserID("@user:remote", true)
16+
assert.Nil(t, err)
17+
validRoom, err := spec.NewRoomID("!room:remote")
18+
assert.Nil(t, err)
19+
joinedUser, err := spec.NewUserID("@joined:local", true)
20+
assert.Nil(t, err)
21+
22+
_, sk, err := ed25519.GenerateKey(rand.Reader)
23+
if err != nil {
24+
t.Fatalf("Failed generating key: %v", err)
25+
}
26+
keyID := KeyID("ed25519:1234")
27+
28+
stateKey := ""
29+
eb := MustGetRoomVersion(RoomVersionV10).NewEventBuilderFromProtoEvent(&ProtoEvent{
30+
Sender: validUser.String(),
31+
RoomID: validRoom.String(),
32+
Type: spec.MRoomCreate,
33+
StateKey: &stateKey,
34+
PrevEvents: []interface{}{},
35+
AuthEvents: []interface{}{},
36+
Depth: 0,
37+
Content: spec.RawJSON(`{"creator":"@user:local","m.federate":true,"room_version":"10"}`),
38+
Unsigned: spec.RawJSON(""),
39+
})
40+
createEvent, err := eb.Build(time.Now(), validUser.Domain(), keyID, sk)
41+
if err != nil {
42+
t.Fatalf("Failed building create event: %v", err)
43+
}
44+
45+
stateKey = ""
46+
joinRulesEB := MustGetRoomVersion(RoomVersionV10).NewEventBuilderFromProtoEvent(&ProtoEvent{
47+
Sender: validUser.String(),
48+
RoomID: validRoom.String(),
49+
Type: spec.MRoomJoinRules,
50+
StateKey: &stateKey,
51+
PrevEvents: []interface{}{createEvent.EventID()},
52+
AuthEvents: []interface{}{createEvent.EventID()},
53+
Depth: 1,
54+
Content: spec.RawJSON(`{"join_rule":"public"}`),
55+
Unsigned: spec.RawJSON(""),
56+
})
57+
joinRulesEvent, err := joinRulesEB.Build(time.Now(), validUser.Domain(), keyID, sk)
58+
if err != nil {
59+
t.Fatalf("Failed building join_rules event: %v", err)
60+
}
61+
62+
stateKey = ""
63+
powerLevelsEB := MustGetRoomVersion(RoomVersionV10).NewEventBuilderFromProtoEvent(&ProtoEvent{
64+
Sender: validUser.String(),
65+
RoomID: validRoom.String(),
66+
Type: spec.MRoomJoinRules,
67+
StateKey: &stateKey,
68+
PrevEvents: []interface{}{createEvent.EventID()},
69+
AuthEvents: []interface{}{createEvent.EventID()},
70+
Depth: 2,
71+
Content: spec.RawJSON(`{"users":{"@joined:local":100}}`),
72+
Unsigned: spec.RawJSON(""),
73+
})
74+
powerLevelsEvent, err := powerLevelsEB.Build(time.Now(), validUser.Domain(), keyID, sk)
75+
if err != nil {
76+
t.Fatalf("Failed building power_levels event: %v", err)
77+
}
78+
79+
stateKey = validUser.String()
80+
joinEB := MustGetRoomVersion(RoomVersionV10).NewEventBuilderFromProtoEvent(&ProtoEvent{
81+
Sender: validUser.String(),
82+
RoomID: validRoom.String(),
83+
Type: spec.MRoomMember,
84+
StateKey: &stateKey,
85+
PrevEvents: []interface{}{powerLevelsEvent.EventID()},
86+
AuthEvents: []interface{}{createEvent.EventID(), joinRulesEvent.EventID(), powerLevelsEvent.EventID()},
87+
Depth: 3,
88+
Content: spec.RawJSON(`{"membership":"join"}`),
89+
Unsigned: spec.RawJSON(""),
90+
})
91+
joinEvent, err := joinEB.Build(time.Now(), validUser.Domain(), keyID, sk)
92+
if err != nil {
93+
t.Fatalf("Failed building join event: %v", err)
94+
}
95+
96+
stateKey = validUser.String()
97+
leaveEB := MustGetRoomVersion(RoomVersionV10).NewEventBuilderFromProtoEvent(&ProtoEvent{
98+
Sender: validUser.String(),
99+
RoomID: validRoom.String(),
100+
Type: spec.MRoomMember,
101+
StateKey: &stateKey,
102+
PrevEvents: []interface{}{powerLevelsEvent.EventID()},
103+
AuthEvents: []interface{}{createEvent.EventID(), joinRulesEvent.EventID(), powerLevelsEvent.EventID()},
104+
Depth: 3,
105+
Content: spec.RawJSON(`{"membership":"leave"}`),
106+
Unsigned: spec.RawJSON(""),
107+
})
108+
leaveEvent, err := leaveEB.Build(time.Now(), validUser.Domain(), keyID, sk)
109+
if err != nil {
110+
t.Fatalf("Failed building join event: %v", err)
111+
}
112+
113+
tests := []struct {
114+
name string
115+
input HandleMakeLeaveInput
116+
want *HandleMakeLeaveResponse
117+
wantErr assert.ErrorAssertionFunc
118+
}{
119+
{
120+
name: "wrong destination",
121+
input: HandleMakeLeaveInput{
122+
UserID: *joinedUser,
123+
RequestOrigin: "notLocalhost",
124+
},
125+
wantErr: assert.Error,
126+
},
127+
{
128+
name: "localhost not in room",
129+
input: HandleMakeLeaveInput{
130+
UserID: *validUser,
131+
RequestOrigin: "remote",
132+
LocalServerInRoom: false,
133+
},
134+
wantErr: assert.Error,
135+
},
136+
{
137+
name: "template error",
138+
input: HandleMakeLeaveInput{
139+
RoomID: *validRoom,
140+
UserID: *validUser,
141+
RequestOrigin: "remote",
142+
LocalServerInRoom: true,
143+
BuildEventTemplate: func(protoEvent *ProtoEvent) (PDU, []PDU, error) {
144+
return nil, nil, fmt.Errorf("error")
145+
},
146+
},
147+
wantErr: assert.Error,
148+
},
149+
{
150+
name: "template error - no event",
151+
input: HandleMakeLeaveInput{
152+
RoomID: *validRoom,
153+
UserID: *validUser,
154+
RequestOrigin: "remote",
155+
LocalServerInRoom: true,
156+
BuildEventTemplate: func(protoEvent *ProtoEvent) (PDU, []PDU, error) {
157+
return nil, nil, nil
158+
},
159+
},
160+
wantErr: assert.Error,
161+
},
162+
{
163+
name: "template error - no state",
164+
input: HandleMakeLeaveInput{
165+
RoomID: *validRoom,
166+
UserID: *validUser,
167+
RequestOrigin: "remote",
168+
LocalServerInRoom: true,
169+
BuildEventTemplate: func(protoEvent *ProtoEvent) (PDU, []PDU, error) {
170+
return joinEvent, nil, nil
171+
},
172+
},
173+
wantErr: assert.Error,
174+
},
175+
{
176+
name: "template error - not a membership event",
177+
input: HandleMakeLeaveInput{
178+
RoomID: *validRoom,
179+
UserID: *validUser,
180+
RequestOrigin: "remote",
181+
LocalServerInRoom: true,
182+
BuildEventTemplate: func(protoEvent *ProtoEvent) (PDU, []PDU, error) {
183+
return createEvent, []PDU{createEvent, joinRulesEvent}, nil
184+
},
185+
},
186+
wantErr: assert.Error,
187+
},
188+
{
189+
name: "not allowed to leave, wrong state events",
190+
input: HandleMakeLeaveInput{
191+
RoomID: *validRoom,
192+
UserID: *validUser,
193+
RequestOrigin: "remote",
194+
LocalServerInRoom: true,
195+
BuildEventTemplate: func(protoEvent *ProtoEvent) (PDU, []PDU, error) {
196+
return leaveEvent, []PDU{joinRulesEvent}, nil
197+
},
198+
},
199+
wantErr: assert.Error,
200+
},
201+
{
202+
name: "allowed to leave",
203+
input: HandleMakeLeaveInput{
204+
RoomID: *validRoom,
205+
UserID: *validUser,
206+
RequestOrigin: "remote",
207+
LocalServerInRoom: true,
208+
BuildEventTemplate: func(protoEvent *ProtoEvent) (PDU, []PDU, error) {
209+
return leaveEvent, []PDU{createEvent, joinRulesEvent}, nil
210+
},
211+
},
212+
wantErr: assert.NoError,
213+
},
214+
}
215+
for _, tt := range tests {
216+
t.Run(tt.name, func(t *testing.T) {
217+
_, err := HandleMakeLeave(tt.input)
218+
if !tt.wantErr(t, err, fmt.Sprintf("HandleMakeLeave(%v)", tt.input)) {
219+
return
220+
}
221+
})
222+
}
223+
}

0 commit comments

Comments
 (0)