Skip to content

Commit 0cc5333

Browse files
authored
Test auth of events with rejected auth_events (#210)
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 0e57c33 commit 0cc5333

File tree

4 files changed

+246
-5
lines changed

4 files changed

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

0 commit comments

Comments
 (0)