Skip to content

Commit

Permalink
Test auth of events with rejected auth_events
Browse files Browse the repository at this point in the history
We should make sure that events that refer to rejected events in their
auth_events are themselves rejected.

See matrix-org/synapse#9595.
  • Loading branch information
richvdh committed Oct 1, 2021
1 parent 97183bf commit 6691908
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 5 deletions.
11 changes: 10 additions & 1 deletion internal/b/blueprints.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,17 @@ type Event struct {
Sender string
StateKey *string
Content map[string]interface{}
// This field is ignored in blueprints as clients are unable to set it. Used with federation.Server

/* The following fields are ignored in blueprints as clients are unable to set them.
* They are used with federation.Server.
*/

Unsigned map[string]interface{}

// The events needed to authenticate this event.
// This can be either []EventReference for room v1/v2, or []string for room v3 onwards.
// If it is left at nil, MustCreateEvent will populate it automatically based on the room state.
AuthEvents interface{}
}

func MustValidate(bp Blueprint) Blueprint {
Expand Down
11 changes: 7 additions & 4 deletions internal/federation/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,15 @@ func (s *Server) MustCreateEvent(t *testing.T, room *ServerRoom, ev b.Event) *go
RoomID: room.RoomID,
PrevEvents: room.ForwardExtremities,
Unsigned: unsigned,
AuthEvents: ev.AuthEvents,
}
stateNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(&eb)
if err != nil {
t.Fatalf("MustCreateEvent: failed to work out auth_events : %s", err)
if eb.AuthEvents == nil {
stateNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(&eb)
if err != nil {
t.Fatalf("MustCreateEvent: failed to work out auth_events : %s", err)
}
eb.AuthEvents = room.AuthEvents(stateNeeded)
}
eb.AuthEvents = room.AuthEvents(stateNeeded)
signedEvent, err := eb.Build(time.Now(), gomatrixserverlib.ServerName(s.ServerName), s.KeyID, s.Priv, room.Version)
if err != nil {
t.Fatalf("MustCreateEvent: failed to sign event: %s", err)
Expand Down
16 changes: 16 additions & 0 deletions internal/federation/server_room.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,19 @@ func InitialRoomEvents(roomVer gomatrixserverlib.RoomVersion, creator string) []
},
}
}

// EventIDsOrReferences converts a list of events into a list of EventIDs or EventReferences,
// depending on the room version
func (r *ServerRoom) EventIDsOrReferences(events []*gomatrixserverlib.Event) (refs []interface{}) {
refs = make([]interface{}, len(events))
eventFormat, _ := r.Version.EventFormat()
for i, ev := range events {
switch eventFormat {
case gomatrixserverlib.EventFormatV1:
refs[i] = ev.EventReference()
default:
refs[i] = ev.EventID()
}
}
return
}
200 changes: 200 additions & 0 deletions tests/federation_room_event_auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package tests

import (
"context"
"encoding/json"
"net/http"
"testing"
"time"

"github.com/gorilla/mux"
"github.com/matrix-org/gomatrixserverlib"
"github.com/tidwall/gjson"

"github.com/matrix-org/complement/internal/b"
"github.com/matrix-org/complement/internal/federation"
"github.com/matrix-org/complement/internal/must"
)

func TestInboundFederationRejectsEventsWithRejectedAuthEvents(t *testing.T) {
/* These tests check that events which refer to rejected events in auth_events
* are themselves rejected.
*
* In order to inject an outlier, we include it as an extra auth_event in a
* regular event. Doing so means that the regular event should itself be
* rejected.
*
* We finish up by sending a final, normal, event which should be accepted
* everywhere. This acts as a sentinel so that we can be sure that the
* events have all been correctly propagated.
*
* The DAG ends up looking like this:
*
* C
* / | \
* / R \
* | ^ \
* | .. O
* | ^
* X .......
* |
* S
*
* Where:
* .... represents an "auth_event" link
* C is the room creation series
* R is a rejected event
* O is an outlier, which should be rejected
* X is an event with O among its auth_events, which should be rejected
* as a side-effect of O being rejected
* S is the final regular event, which acts as a sentinel
*
* To check if the outlier is rejected, we simply request the event via
* /rooms/{roomID}/event. If it is rejected, we should get a 404.
*/

deployment := Deploy(t, b.BlueprintAlice)
defer deployment.Destroy(t)
srv := federation.NewServer(t, deployment,
federation.HandleKeyRequests(),

// accept incoming presence transactions, etc
federation.HandleTransactionRequests(nil, nil),
)
cancel := srv.Listen()
defer cancel()
fedClient := srv.FederationClient(deployment)

/* Create a handler for /event_auth */
// a map from event ID to events to be returned by /event_auth
eventAuthMap := make(map[string][]*gomatrixserverlib.Event)
srv.Mux().HandleFunc("/_matrix/federation/v1/event_auth/{roomID}/{eventID}", func(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
eventID := vars["eventID"]
authEvents, ok := eventAuthMap[eventID]
if !ok {
t.Logf("Unexpected /event_auth request for event %s", eventID)
w.WriteHeader(404)
_, _ = w.Write([]byte("{}"))
return
}
res := gomatrixserverlib.RespEventAuth{AuthEvents: authEvents}
responseBytes, _ := json.Marshal(&res)
w.WriteHeader(200)
_, _ = w.Write(responseBytes)
}).Methods("GET")

// have Alice create a room, and then join it
alice := deployment.Client(t, "hs1", "@alice:hs1")
testRoomID := alice.CreateRoom(t, struct {
Preset string `json:"preset"`
}{
"public_chat",
})
charlie := srv.UserID("charlie")
room := srv.MustJoinRoom(t, deployment, "hs1", testRoomID, charlie)
charlieMembershipEvent := room.CurrentState("m.room.member", charlie)

// have Charlie send a PL event which will be rejected
rejectedEvent := srv.MustCreateEvent(t, room, b.Event{
Type: "m.room.power_levels",
StateKey: b.Ptr(""),
Sender: charlie,
Content: map[string]interface{}{
"users": map[string]interface{}{},
},
})
_, err := fedClient.SendTransaction(context.Background(), gomatrixserverlib.Transaction{
TransactionID: "complement1",
Origin: gomatrixserverlib.ServerName(srv.ServerName),
Destination: "hs1",
OriginServerTS: gomatrixserverlib.AsTimestamp(time.Now()),
PDUs: []json.RawMessage{
rejectedEvent.JSON(),
},
})
must.NotError(t, "failed to SendTransaction", err)
t.Logf("Sent rejected PL event %s", rejectedEvent.EventID())

// create an event to be pulled in as an outlier, which is valid according to its prev events,
// but uses the rejected event among its auth events.
outlierEvent := srv.MustCreateEvent(t, room, b.Event{
Type: "m.room.member",
StateKey: &charlie,
Sender: charlie,
Content: map[string]interface{}{"membership": "join", "test": 1},
AuthEvents: []string{
room.CurrentState("m.room.create", "").EventID(),
room.CurrentState("m.room.join_rules", "").EventID(),
rejectedEvent.EventID(),
charlieMembershipEvent.EventID(),
},
})
t.Logf("Created outlier event %s", outlierEvent.EventID())

// create a regular event which refers to the outlier event in its auth events,
// so that the outlier gets pulled in.
sentEventAuthEvents := []*gomatrixserverlib.Event{
room.CurrentState("m.room.create", ""),
room.CurrentState("m.room.join_rules", ""),
room.CurrentState("m.room.power_levels", ""),
charlieMembershipEvent,
outlierEvent,
}
sentEvent1 := srv.MustCreateEvent(t, room, b.Event{
Type: "m.room.message",
Sender: charlie,
Content: map[string]interface{}{"body": "sentEvent1"},
AuthEvents: room.EventIDsOrReferences(sentEventAuthEvents),
})
room.AddEvent(sentEvent1)
eventAuthMap[sentEvent1.EventID()] = sentEventAuthEvents
t.Logf("Created sent event 1 %s", sentEvent1.EventID())

// finally, a genuine regular event.
sentinelEvent := srv.MustCreateEvent(t, room, b.Event{
Type: "m.room.message",
Sender: charlie,
Content: map[string]interface{}{"body": "sentinelEvent"},
})
t.Logf("Created sentinel event %s", sentinelEvent.EventID())

_, err = fedClient.SendTransaction(context.Background(), gomatrixserverlib.Transaction{
TransactionID: "complement2",
Origin: gomatrixserverlib.ServerName(srv.ServerName),
Destination: "hs1",
OriginServerTS: gomatrixserverlib.AsTimestamp(time.Now()),
PDUs: []json.RawMessage{
sentEvent1.JSON(),
sentinelEvent.JSON(),
},
})
must.NotError(t, "failed to SendTransaction", err)
t.Logf("Sent transaction; awaiting arrival")

// wait for alice to receive sentinelEvent
alice.SyncUntilTimelineHas(
t,
room.RoomID,
func(ev gjson.Result) bool {
return ev.Get("event_id").Str == sentinelEvent.EventID()
},
)

// now inspect the results. Each of the rejected events should give a 404 for /event
t.Run("Outlier should be rejected", func(t *testing.T) {
res := alice.DoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", room.RoomID, "event", outlierEvent.EventID()})
defer res.Body.Close()
if res.StatusCode != 404 {
t.Errorf("Expected a 404 when fetching outlier event, but got %d", res.StatusCode)
}
})

t.Run("sent event 1 should be rejected", func(t *testing.T) {
res := alice.DoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", room.RoomID, "event", sentEvent1.EventID()})
defer res.Body.Close()
if res.StatusCode != 404 {
t.Errorf("Expected a 404 when fetching sent event 1, but got %d", res.StatusCode)
}
})
}

0 comments on commit 6691908

Please sign in to comment.