Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test auth of events with rejected auth_events #210

Merged
merged 6 commits into from
Oct 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
12 changes: 8 additions & 4 deletions internal/federation/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,16 @@ 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 {
var stateNeeded gomatrixserverlib.StateNeeded
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
}
212 changes: 212 additions & 0 deletions tests/federation_room_event_auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// These tests currently fail on Dendrite, due to Dendrite bugs.
// +build !dendrite_blacklist

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.
*
* They are regression tests for https://github.com/matrix-org/synapse/issues/9595.
*
* In order to inject an outlier, we include it as an extra auth_event in a
richvdh marked this conversation as resolved.
Show resolved Hide resolved
* 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 a "prev_event" link (older events are at the top)
* .... represents an "auth_event" link
richvdh marked this conversation as resolved.
Show resolved Hide resolved
* 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.
richvdh marked this conversation as resolved.
Show resolved Hide resolved
*/

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),

// accept incoming /event requests (dendrite asks for the outlier via /event; synapse calls /event_auth instead)
federation.HandleEventRequests(),
)
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(),
},
})
// add it to room.Timeline so that HandleEventRequests() can find it, but
// don't use room.AddEvent(), because we don't want it to be a forward extremity.
room.Timeline = append(room.Timeline, outlierEvent)
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)
}
})
}