Skip to content

Commit 6691908

Browse files
committed
Test auth of events with rejected auth_events
We should make sure that events that refer to rejected events in their auth_events are themselves rejected. See matrix-org/synapse#9595.
1 parent 97183bf commit 6691908

File tree

4 files changed

+233
-5
lines changed

4 files changed

+233
-5
lines changed

internal/b/blueprints.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,17 @@ type Event struct {
9595
Sender string
9696
StateKey *string
9797
Content map[string]interface{}
98-
// This field is ignored in blueprints as clients are unable to set it. Used with federation.Server
98+
99+
/* The following fields are ignored in blueprints as clients are unable to set them.
100+
* They are used with federation.Server.
101+
*/
102+
99103
Unsigned map[string]interface{}
104+
105+
// The events needed to authenticate this event.
106+
// This can be either []EventReference for room v1/v2, or []string for room v3 onwards.
107+
// If it is left at nil, MustCreateEvent will populate it automatically based on the room state.
108+
AuthEvents interface{}
100109
}
101110

102111
func MustValidate(bp Blueprint) Blueprint {

internal/federation/server.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,15 @@ func (s *Server) MustCreateEvent(t *testing.T, room *ServerRoom, ev b.Event) *go
196196
RoomID: room.RoomID,
197197
PrevEvents: room.ForwardExtremities,
198198
Unsigned: unsigned,
199+
AuthEvents: ev.AuthEvents,
199200
}
200-
stateNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(&eb)
201-
if err != nil {
202-
t.Fatalf("MustCreateEvent: failed to work out auth_events : %s", err)
201+
if eb.AuthEvents == nil {
202+
stateNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(&eb)
203+
if err != nil {
204+
t.Fatalf("MustCreateEvent: failed to work out auth_events : %s", err)
205+
}
206+
eb.AuthEvents = room.AuthEvents(stateNeeded)
203207
}
204-
eb.AuthEvents = room.AuthEvents(stateNeeded)
205208
signedEvent, err := eb.Build(time.Now(), gomatrixserverlib.ServerName(s.ServerName), s.KeyID, s.Priv, room.Version)
206209
if err != nil {
207210
t.Fatalf("MustCreateEvent: failed to sign event: %s", err)

internal/federation/server_room.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,19 @@ func InitialRoomEvents(roomVer gomatrixserverlib.RoomVersion, creator string) []
167167
},
168168
}
169169
}
170+
171+
// EventIDsOrReferences converts a list of events into a list of EventIDs or EventReferences,
172+
// depending on the room version
173+
func (r *ServerRoom) EventIDsOrReferences(events []*gomatrixserverlib.Event) (refs []interface{}) {
174+
refs = make([]interface{}, len(events))
175+
eventFormat, _ := r.Version.EventFormat()
176+
for i, ev := range events {
177+
switch eventFormat {
178+
case gomatrixserverlib.EventFormatV1:
179+
refs[i] = ev.EventReference()
180+
default:
181+
refs[i] = ev.EventID()
182+
}
183+
}
184+
return
185+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package tests
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"testing"
8+
"time"
9+
10+
"github.com/gorilla/mux"
11+
"github.com/matrix-org/gomatrixserverlib"
12+
"github.com/tidwall/gjson"
13+
14+
"github.com/matrix-org/complement/internal/b"
15+
"github.com/matrix-org/complement/internal/federation"
16+
"github.com/matrix-org/complement/internal/must"
17+
)
18+
19+
func TestInboundFederationRejectsEventsWithRejectedAuthEvents(t *testing.T) {
20+
/* These tests check that events which refer to rejected events in auth_events
21+
* are themselves rejected.
22+
*
23+
* In order to inject an outlier, we include it as an extra auth_event in a
24+
* regular event. Doing so means that the regular event should itself be
25+
* rejected.
26+
*
27+
* We finish up by sending a final, normal, event which should be accepted
28+
* everywhere. This acts as a sentinel so that we can be sure that the
29+
* events have all been correctly propagated.
30+
*
31+
* The DAG ends up looking like this:
32+
*
33+
* C
34+
* / | \
35+
* / R \
36+
* | ^ \
37+
* | .. O
38+
* | ^
39+
* X .......
40+
* |
41+
* S
42+
*
43+
* Where:
44+
* .... represents an "auth_event" link
45+
* C is the room creation series
46+
* R is a rejected event
47+
* O is an outlier, which should be rejected
48+
* X is an event with O among its auth_events, which should be rejected
49+
* as a side-effect of O being rejected
50+
* S is the final regular event, which acts as a sentinel
51+
*
52+
* To check if the outlier is rejected, we simply request the event via
53+
* /rooms/{roomID}/event. If it is rejected, we should get a 404.
54+
*/
55+
56+
deployment := Deploy(t, b.BlueprintAlice)
57+
defer deployment.Destroy(t)
58+
srv := federation.NewServer(t, deployment,
59+
federation.HandleKeyRequests(),
60+
61+
// accept incoming presence transactions, etc
62+
federation.HandleTransactionRequests(nil, nil),
63+
)
64+
cancel := srv.Listen()
65+
defer cancel()
66+
fedClient := srv.FederationClient(deployment)
67+
68+
/* Create a handler for /event_auth */
69+
// a map from event ID to events to be returned by /event_auth
70+
eventAuthMap := make(map[string][]*gomatrixserverlib.Event)
71+
srv.Mux().HandleFunc("/_matrix/federation/v1/event_auth/{roomID}/{eventID}", func(w http.ResponseWriter, req *http.Request) {
72+
vars := mux.Vars(req)
73+
eventID := vars["eventID"]
74+
authEvents, ok := eventAuthMap[eventID]
75+
if !ok {
76+
t.Logf("Unexpected /event_auth request for event %s", eventID)
77+
w.WriteHeader(404)
78+
_, _ = w.Write([]byte("{}"))
79+
return
80+
}
81+
res := gomatrixserverlib.RespEventAuth{AuthEvents: authEvents}
82+
responseBytes, _ := json.Marshal(&res)
83+
w.WriteHeader(200)
84+
_, _ = w.Write(responseBytes)
85+
}).Methods("GET")
86+
87+
// have Alice create a room, and then join it
88+
alice := deployment.Client(t, "hs1", "@alice:hs1")
89+
testRoomID := alice.CreateRoom(t, struct {
90+
Preset string `json:"preset"`
91+
}{
92+
"public_chat",
93+
})
94+
charlie := srv.UserID("charlie")
95+
room := srv.MustJoinRoom(t, deployment, "hs1", testRoomID, charlie)
96+
charlieMembershipEvent := room.CurrentState("m.room.member", charlie)
97+
98+
// have Charlie send a PL event which will be rejected
99+
rejectedEvent := srv.MustCreateEvent(t, room, b.Event{
100+
Type: "m.room.power_levels",
101+
StateKey: b.Ptr(""),
102+
Sender: charlie,
103+
Content: map[string]interface{}{
104+
"users": map[string]interface{}{},
105+
},
106+
})
107+
_, err := fedClient.SendTransaction(context.Background(), gomatrixserverlib.Transaction{
108+
TransactionID: "complement1",
109+
Origin: gomatrixserverlib.ServerName(srv.ServerName),
110+
Destination: "hs1",
111+
OriginServerTS: gomatrixserverlib.AsTimestamp(time.Now()),
112+
PDUs: []json.RawMessage{
113+
rejectedEvent.JSON(),
114+
},
115+
})
116+
must.NotError(t, "failed to SendTransaction", err)
117+
t.Logf("Sent rejected PL event %s", rejectedEvent.EventID())
118+
119+
// create an event to be pulled in as an outlier, which is valid according to its prev events,
120+
// but uses the rejected event among its auth events.
121+
outlierEvent := srv.MustCreateEvent(t, room, b.Event{
122+
Type: "m.room.member",
123+
StateKey: &charlie,
124+
Sender: charlie,
125+
Content: map[string]interface{}{"membership": "join", "test": 1},
126+
AuthEvents: []string{
127+
room.CurrentState("m.room.create", "").EventID(),
128+
room.CurrentState("m.room.join_rules", "").EventID(),
129+
rejectedEvent.EventID(),
130+
charlieMembershipEvent.EventID(),
131+
},
132+
})
133+
t.Logf("Created outlier event %s", outlierEvent.EventID())
134+
135+
// create a regular event which refers to the outlier event in its auth events,
136+
// so that the outlier gets pulled in.
137+
sentEventAuthEvents := []*gomatrixserverlib.Event{
138+
room.CurrentState("m.room.create", ""),
139+
room.CurrentState("m.room.join_rules", ""),
140+
room.CurrentState("m.room.power_levels", ""),
141+
charlieMembershipEvent,
142+
outlierEvent,
143+
}
144+
sentEvent1 := srv.MustCreateEvent(t, room, b.Event{
145+
Type: "m.room.message",
146+
Sender: charlie,
147+
Content: map[string]interface{}{"body": "sentEvent1"},
148+
AuthEvents: room.EventIDsOrReferences(sentEventAuthEvents),
149+
})
150+
room.AddEvent(sentEvent1)
151+
eventAuthMap[sentEvent1.EventID()] = sentEventAuthEvents
152+
t.Logf("Created sent event 1 %s", sentEvent1.EventID())
153+
154+
// finally, a genuine regular event.
155+
sentinelEvent := srv.MustCreateEvent(t, room, b.Event{
156+
Type: "m.room.message",
157+
Sender: charlie,
158+
Content: map[string]interface{}{"body": "sentinelEvent"},
159+
})
160+
t.Logf("Created sentinel event %s", sentinelEvent.EventID())
161+
162+
_, err = fedClient.SendTransaction(context.Background(), gomatrixserverlib.Transaction{
163+
TransactionID: "complement2",
164+
Origin: gomatrixserverlib.ServerName(srv.ServerName),
165+
Destination: "hs1",
166+
OriginServerTS: gomatrixserverlib.AsTimestamp(time.Now()),
167+
PDUs: []json.RawMessage{
168+
sentEvent1.JSON(),
169+
sentinelEvent.JSON(),
170+
},
171+
})
172+
must.NotError(t, "failed to SendTransaction", err)
173+
t.Logf("Sent transaction; awaiting arrival")
174+
175+
// wait for alice to receive sentinelEvent
176+
alice.SyncUntilTimelineHas(
177+
t,
178+
room.RoomID,
179+
func(ev gjson.Result) bool {
180+
return ev.Get("event_id").Str == sentinelEvent.EventID()
181+
},
182+
)
183+
184+
// now inspect the results. Each of the rejected events should give a 404 for /event
185+
t.Run("Outlier should be rejected", func(t *testing.T) {
186+
res := alice.DoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", room.RoomID, "event", outlierEvent.EventID()})
187+
defer res.Body.Close()
188+
if res.StatusCode != 404 {
189+
t.Errorf("Expected a 404 when fetching outlier event, but got %d", res.StatusCode)
190+
}
191+
})
192+
193+
t.Run("sent event 1 should be rejected", func(t *testing.T) {
194+
res := alice.DoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", room.RoomID, "event", sentEvent1.EventID()})
195+
defer res.Body.Close()
196+
if res.StatusCode != 404 {
197+
t.Errorf("Expected a 404 when fetching sent event 1, but got %d", res.StatusCode)
198+
}
199+
})
200+
}

0 commit comments

Comments
 (0)