Check that events pass authentication checks.

Record the list of events that the event passes authentication checks
against.
This commit is contained in:
Mark Haines 2017-02-08 14:36:25 +00:00
parent 600f56b4b8
commit c7112de7f2
5 changed files with 419 additions and 19 deletions

View file

@ -30,7 +30,9 @@ type InputRoomEvent struct {
Kind int
// The event JSON for the event to add.
Event []byte
// List of state event IDs that authenticate this event.
AuthEventIDs []string
// Optional list of state event IDs forming the state before this event.
// These state events must have already been persisted.
State []string
StateEventIDs []string
}

View file

@ -1,13 +1,23 @@
package input
import (
"fmt"
"github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/dendrite/roomserver/types"
"github.com/matrix-org/gomatrixserverlib"
"sort"
)
// A RoomEventDatabase has the storage APIs needed to store a room event.
type RoomEventDatabase interface {
StoreEvent(event gomatrixserverlib.Event) error
// Stores a matrix room event in the database
StoreEvent(event gomatrixserverlib.Event, authEventNIDs []int64) error
// Lookup the state entries for a list of string event IDs
StateEntriesForEventIDs(eventIDs []string) ([]types.StateEntry, error)
// Lookup the numeric IDs for a list of string event state keys.
EventStateKeyNIDs(eventStateKeys []string) ([]types.IDPair, error)
// Lookup the Events for a list of numeric event IDs.
Events(eventNIDs []int64) ([]types.Event, error)
}
func processRoomEvent(db RoomEventDatabase, input api.InputRoomEvent) error {
@ -17,12 +27,16 @@ func processRoomEvent(db RoomEventDatabase, input api.InputRoomEvent) error {
return err
}
if err := db.StoreEvent(event); err != nil {
// Check that the event passes authentication checks.
authEventNIDs, err := checkAuthEvents(db, event, input.AuthEventIDs)
if err != nil {
return err
}
// TODO:
// * Check that the event passes authentication checks.
// Store the event
if err := db.StoreEvent(event, authEventNIDs); err != nil {
return err
}
if input.Kind == api.KindOutlier {
// For outliers we can stop after we've stored the event itself as it
@ -44,3 +58,191 @@ func processRoomEvent(db RoomEventDatabase, input api.InputRoomEvent) error {
// - The changes to the current state of the room.
panic("Not implemented")
}
// checkAuthEvents checks that the event passes authentication checks
// Returns the numeric IDs for the auth events.
func checkAuthEvents(db RoomEventDatabase, event gomatrixserverlib.Event, authEventIDs []string) ([]int64, error) {
authStateEntries, err := db.StateEntriesForEventIDs(authEventIDs)
if err != nil {
return nil, err
}
if len(authStateEntries) < len(authEventIDs) {
return nil, fmt.Errorf("input: Some of the auth event IDs were missing from the database")
}
stateNeeded := gomatrixserverlib.StateNeededForAuth([]gomatrixserverlib.Event{event})
authEvents, err := loadAuthEvents(db, stateNeeded, authStateEntries)
if err != nil {
return nil, err
}
if err = gomatrixserverlib.Allowed(event, &authEvents); err != nil {
return nil, err
}
result := make([]int64, len(authStateEntries))
for i := range authStateEntries {
result[i] = authStateEntries[i].EventNID
}
return result, nil
}
type authEvents struct {
stateNIDMap idMap
state stateEntryMap
events eventMap
}
func (ae *authEvents) Create() (*gomatrixserverlib.Event, error) {
return ae.lookupEventWithEmptyStateKey(types.MRoomCreateNID), nil
}
func (ae *authEvents) PowerLevels() (*gomatrixserverlib.Event, error) {
return ae.lookupEventWithEmptyStateKey(types.MRoomPowerLevelsNID), nil
}
func (ae *authEvents) JoinRules() (*gomatrixserverlib.Event, error) {
return ae.lookupEventWithEmptyStateKey(types.MRoomJoinRulesNID), nil
}
func (ae *authEvents) Member(stateKey string) (*gomatrixserverlib.Event, error) {
return ae.lookupEvent(types.MRoomMemberNID, stateKey), nil
}
func (ae *authEvents) ThirdPartyInvite(stateKey string) (*gomatrixserverlib.Event, error) {
return ae.lookupEvent(types.MRoomThirdPartyInviteNID, stateKey), nil
}
func (ae *authEvents) lookupEventWithEmptyStateKey(typeNID int64) *gomatrixserverlib.Event {
eventNID, ok := ae.state.lookup(types.StateKey{typeNID, types.EmptyStateKeyNID})
if !ok {
return nil
}
event, ok := ae.events.lookup(eventNID)
if !ok {
return nil
}
return &event.Event
}
func (ae *authEvents) lookupEvent(typeNID int64, stateKey string) *gomatrixserverlib.Event {
stateKeyNID, ok := ae.stateNIDMap.lookup(stateKey)
if !ok {
return nil
}
eventNID, ok := ae.state.lookup(types.StateKey{typeNID, stateKeyNID})
if !ok {
return nil
}
event, ok := ae.events.lookup(eventNID)
if !ok {
return nil
}
return &event.Event
}
func loadAuthEvents(
db RoomEventDatabase,
needed gomatrixserverlib.StateNeeded,
state []types.StateEntry,
) (result authEvents, err error) {
// Lookup the numeric IDs for the state keys
var eventStateKeys []string
eventStateKeys = append(eventStateKeys, needed.Member...)
eventStateKeys = append(eventStateKeys, needed.ThirdPartyInvite...)
stateKeyNIDs, err := db.EventStateKeyNIDs(eventStateKeys)
if err != nil {
return
}
result.stateNIDMap = newIDMap(stateKeyNIDs)
// Load the events we need.
keysNeeded := stateKeysNeeded(result.stateNIDMap, needed)
var eventNIDs []int64
result.state = newStateEntryMap(state)
for _, keyNeeded := range keysNeeded {
eventNID, ok := result.state.lookup(keyNeeded)
if ok {
eventNIDs = append(eventNIDs, eventNID)
}
}
result.events, err = db.Events(eventNIDs)
if err != nil {
return
}
return
}
func stateKeysNeeded(stateNIDMap idMap, stateNeeded gomatrixserverlib.StateNeeded) []types.StateKey {
var keys []types.StateKey
if stateNeeded.Create {
keys = append(keys, types.StateKey{types.MRoomCreateNID, types.EmptyStateKeyNID})
}
if stateNeeded.PowerLevels {
keys = append(keys, types.StateKey{types.MRoomPowerLevelsNID, types.EmptyStateKeyNID})
}
if stateNeeded.JoinRules {
keys = append(keys, types.StateKey{types.MRoomJoinRulesNID, types.EmptyStateKeyNID})
}
for _, member := range stateNeeded.Member {
stateKeyNID, ok := stateNIDMap.lookup(member)
if ok {
keys = append(keys, types.StateKey{types.MRoomMemberNID, stateKeyNID})
}
}
for _, token := range stateNeeded.ThirdPartyInvite {
stateKeyNID, ok := stateNIDMap.lookup(token)
if ok {
keys = append(keys, types.StateKey{types.MRoomThirdPartyInviteNID, stateKeyNID})
}
}
return keys
}
type idMap map[string]int64
func newIDMap(ids []types.IDPair) idMap {
result := make(map[string]int64)
for _, pair := range ids {
result[pair.ID] = pair.NID
}
return idMap(result)
}
func (m idMap) lookup(id string) (nid int64, ok bool) {
nid, ok = map[string]int64(m)[id]
return
}
type stateEntryMap []types.StateEntry
func newStateEntryMap(stateEntries []types.StateEntry) stateEntryMap {
return stateEntryMap(stateEntries)
}
func (m stateEntryMap) lookup(stateKey types.StateKey) (eventNID int64, ok bool) {
list := []types.StateEntry(m)
i := sort.Search(len(list), func(i int) bool {
return !list[i].StateKey.LessThan(stateKey)
})
if i < len(list) && list[i].StateKey == stateKey {
ok = true
eventNID = list[i].EventNID
}
return
}
type eventMap []types.Event
func (m eventMap) lookup(eventNID int64) (event *types.Event, ok bool) {
list := []types.Event(m)
i := sort.Search(len(list), func(i int) bool {
return list[i].EventNID >= eventNID
})
if i < len(list) && list[i].EventNID == eventNID {
ok = true
event = &list[i]
}
return
}

View file

@ -2,20 +2,24 @@ package storage
import (
"database/sql"
"github.com/lib/pq"
"github.com/matrix-org/dendrite/roomserver/types"
)
type statements struct {
selectPartitionOffsetsStmt *sql.Stmt
upsertPartitionOffsetStmt *sql.Stmt
insertEventTypeNIDStmt *sql.Stmt
selectEventTypeNIDStmt *sql.Stmt
insertEventStateKeyNIDStmt *sql.Stmt
selectEventStateKeyNIDStmt *sql.Stmt
insertRoomNIDStmt *sql.Stmt
selectRoomNIDStmt *sql.Stmt
insertEventStmt *sql.Stmt
insertEventJSONStmt *sql.Stmt
selectPartitionOffsetsStmt *sql.Stmt
upsertPartitionOffsetStmt *sql.Stmt
insertEventTypeNIDStmt *sql.Stmt
selectEventTypeNIDStmt *sql.Stmt
insertEventStateKeyNIDStmt *sql.Stmt
selectEventStateKeyNIDStmt *sql.Stmt
selectEventStateKeyNIDsStmt *sql.Stmt
insertRoomNIDStmt *sql.Stmt
selectRoomNIDStmt *sql.Stmt
insertEventStmt *sql.Stmt
selectStateEventsByIDStmt *sql.Stmt
insertEventJSONStmt *sql.Stmt
selectEventJSONsStmt *sql.Stmt
}
func (s *statements) prepare(db *sql.DB) error {
@ -196,6 +200,9 @@ func (s *statements) prepareEventStateKeys(db *sql.DB) (err error) {
if s.selectEventStateKeyNIDStmt, err = db.Prepare(selectEventStateKeyNIDSQL); err != nil {
return
}
if s.selectEventStateKeyNIDsStmt, err = db.Prepare(selectEventStateKeyNIDsSQL); err != nil {
return
}
return
}
@ -230,6 +237,11 @@ const insertEventStateKeyNIDSQL = "" +
const selectEventStateKeyNIDSQL = "" +
"SELECT event_state_key_nid FROM event_state_keys WHERE event_state_key = $1"
const selectEventStateKeyNIDsSQL = "" +
"SELECT event_state_key, event_state_key_nid FROM event_state_keys" +
" WHERE event_state_key = ANY($1)" +
" ORDER BY event_state_key ASC"
func (s *statements) insertEventStateKeyNID(eventStateKey string) (eventStateKeyNID int64, err error) {
err = s.insertEventStateKeyNIDStmt.QueryRow(eventStateKey).Scan(&eventStateKeyNID)
return
@ -240,6 +252,24 @@ func (s *statements) selectEventStateKeyNID(eventStateKey string) (eventStateKey
return
}
func (s *statements) selectEventStateKeyNIDs(eventStateKeys []string) ([]types.IDPair, error) {
rows, err := s.selectEventStateKeyNIDsStmt.Query(pq.StringArray(eventStateKeys))
if err != nil {
return nil, err
}
defer rows.Close()
results := make([]types.IDPair, len(eventStateKeys))
i := 0
for rows.Next() {
if err := rows.Scan(&results[i].ID, &results[i].NID); err != nil {
return nil, err
}
i++
}
return results[:i], nil
}
func (s *statements) prepareRooms(db *sql.DB) (err error) {
_, err = db.Exec(roomsSchema)
if err != nil {
@ -307,17 +337,24 @@ CREATE TABLE IF NOT EXISTS events (
event_id TEXT NOT NULL CONSTRAINT event_id_unique UNIQUE,
-- The sha256 reference hash for the event.
-- Needed for setting reference hashes when sending new events.
reference_sha256 BYTEA NOT NULL
reference_sha256 BYTEA NOT NULL,
-- A list of numeric IDs for events that can authenticate this event.
auth_events BIGINT[] NOT NULL,
);
`
const insertEventSQL = "" +
"INSERT INTO events (room_nid, event_type_nid, event_state_key_nid, event_id, reference_sha256)" +
" VALUES ($1, $2, $3, $4, $5)" +
"INSERT INTO events (room_nid, event_type_nid, event_state_key_nid, event_id, reference_sha256, auth_events)" +
" VALUES ($1, $2, $3, $4, $5, $6)" +
" ON CONFLICT ON CONSTRAINT event_id_unique" +
" DO UPDATE SET event_id = $1" +
" RETURNING event_nid"
const selectStateEventsByIDSQL = "" +
"SELECT event_type_nid, event_state_key_nid, event_nid FROM events" +
" WHERE event_id = ANY($1)" +
" ORDER BY event_type_nid, event_state_key_nid ASC"
func (s *statements) prepareEvents(db *sql.DB) (err error) {
_, err = db.Exec(eventsSchema)
if err != nil {
@ -326,6 +363,9 @@ func (s *statements) prepareEvents(db *sql.DB) (err error) {
if s.insertEventStmt, err = db.Prepare(insertEventSQL); err != nil {
return
}
if s.selectStateEventsByIDStmt, err = db.Prepare(selectStateEventsByIDSQL); err != nil {
return
}
return
}
@ -333,13 +373,36 @@ func (s *statements) insertEvent(
roomNID, eventTypeNID, eventStateKeyNID int64,
eventID string,
referenceSHA256 []byte,
authEventNIDs []int64,
) (eventNID int64, err error) {
err = s.insertEventStmt.QueryRow(
roomNID, eventTypeNID, eventStateKeyNID, eventID, referenceSHA256,
pq.Int64Array(authEventNIDs),
).Scan(&eventNID)
return
}
func (s *statements) selectStateEventsByID(eventIDs []string) ([]types.StateEntry, error) {
results := make([]types.StateEntry, len(eventIDs))
rows, err := s.selectStateEventsByIDStmt.Query(pq.StringArray(eventIDs))
if err != nil {
return nil, err
}
defer rows.Close()
i := 0
for ; rows.Next(); i++ {
result := &results[i]
if err = rows.Scan(
&result.EventNID,
&result.EventTypeNID,
&result.EventStateKeyNID,
); err != nil {
return nil, err
}
}
return results[:i], err
}
func (s *statements) prepareEventJSON(db *sql.DB) (err error) {
_, err = db.Exec(eventJSONSchema)
if err != nil {
@ -348,6 +411,9 @@ func (s *statements) prepareEventJSON(db *sql.DB) (err error) {
if s.insertEventJSONStmt, err = db.Prepare(insertEventJSONSQL); err != nil {
return
}
if s.selectEventJSONsStmt, err = db.Prepare(selectEventJSONsSQL); err != nil {
return
}
return
}
@ -372,7 +438,35 @@ const insertEventJSONSQL = "" +
"INSERT INTO event_json (event_nid, event_json) VALUES ($1, $2)" +
" ON CONFLICT DO NOTHING"
const selectEventJSONsSQL = "" +
"SELECT event_nid, event_json FROM event_json" +
" WHERE event_nid = ANY($1)" +
" ORDER BY event_nid ASC"
func (s *statements) insertEventJSON(eventNID int64, eventJSON []byte) error {
_, err := s.insertEventJSONStmt.Exec(eventNID, eventJSON)
return err
}
type eventJSONPair struct {
EventNID int64
EventJSON []byte
}
func (s *statements) selectEventJSONs(eventNIDs []int64) ([]eventJSONPair, error) {
rows, err := s.selectEventJSONsStmt.Query(pq.Int64Array(eventNIDs))
if err != nil {
return nil, err
}
defer rows.Close()
results := make([]eventJSONPair, len(eventNIDs))
i := 0
for rows.Next() {
if err := rows.Scan(&results[i].EventNID, &results[i].EventJSON); err != nil {
return nil, err
}
i++
}
return results[:i], nil
}

View file

@ -38,7 +38,7 @@ func (d *Database) SetPartitionOffset(topic string, partition int32, offset int6
}
// StoreEvent implements input.EventDatabase
func (d *Database) StoreEvent(event gomatrixserverlib.Event) error {
func (d *Database) StoreEvent(event gomatrixserverlib.Event, authEventNIDS []int64) error {
var (
roomNID int64
eventTypeNID int64
@ -70,6 +70,7 @@ func (d *Database) StoreEvent(event gomatrixserverlib.Event) error {
eventStateKeyNID,
event.EventID(),
event.EventReference().EventSHA256,
authEventNIDS,
); err != nil {
return err
}
@ -115,3 +116,32 @@ func (d *Database) assignStateKeyNID(eventStateKey string) (int64, error) {
}
return eventStateKeyNID, nil
}
// StateEntriesForEventIDs implements input.EventDatabase
func (d *Database) StateEntriesForEventIDs(eventIDs []string) ([]types.StateEntry, error) {
return d.statements.selectStateEventsByID(eventIDs)
}
// EventStateKeyNIDs implements input.EventDatabase
func (d *Database) EventStateKeyNIDs(eventStateKeys []string) ([]types.IDPair, error) {
return d.statements.selectEventStateKeyNIDs(eventStateKeys)
}
// Events implements input.EventDatabase
func (d *Database) Events(eventNIDs []int64) ([]types.Event, error) {
eventJSONs, err := d.statements.selectEventJSONs(eventNIDs)
if err != nil {
return nil, err
}
results := make([]types.Event, len(eventJSONs))
for i, eventJSON := range eventJSONs {
result := &results[i]
result.EventNID = eventJSON.EventNID
// TODO: Use NewEventFromTrustedJSON for efficiency
result.Event, err = gomatrixserverlib.NewEventFromUntrustedJSON(eventJSON.EventJSON)
if err != nil {
return nil, err
}
}
return results, nil
}

View file

@ -1,6 +1,10 @@
// Package types provides the types that are used internally within the roomserver.
package types
import (
"github.com/matrix-org/gomatrixserverlib"
)
// A PartitionOffset is the offset into a partition of the input log.
type PartitionOffset struct {
// The ID of the partition.
@ -8,3 +12,71 @@ type PartitionOffset struct {
// The offset into the partition.
Offset int64
}
// A StateKey is a pair of a numeric event type and a numeric state key.
// It is used to lookup state entries.
type StateKey struct {
// The numeric ID for the event type.
EventTypeNID int64
// The numeric ID for the state key or 0 if the event is not a state event.
EventStateKeyNID int64
}
// LessThan returns true if this state key is less than the other state key.
func (a StateKey) LessThan(b StateKey) bool {
if a.EventTypeNID != b.EventTypeNID {
return a.EventTypeNID < b.EventTypeNID
}
return a.EventStateKeyNID < b.EventStateKeyNID
}
// A StateEntry is an entry in the room state of a matrix room.
type StateEntry struct {
StateKey
// The numeric ID for the event.
EventNID int64
}
// LessThan returns true if this state entry is less than the other state entry.
func (a StateEntry) LessThan(b StateEntry) bool {
if a.StateKey != b.StateKey {
return a.StateKey.LessThan(b.StateKey)
}
return a.EventNID < b.EventNID
}
// An IDPair is a pair of a string ID and the corresponding numeric ID.
// It is used when performing bulk numeric ID lookup in the database.
type IDPair struct {
ID string
NID int64
}
// An Event is a gomatrixserverlib.Event with the numeric event ID attached.
// It is when performing bulk event lookup in the database.
type Event struct {
EventNID int64
gomatrixserverlib.Event
}
const (
// MRoomCreateNID is the numeric ID for the "m.room.create" event type.
MRoomCreateNID = 1
// MRoomPowerLevelsNID is the numeric ID for the "m.room.power_levels" event type.
MRoomPowerLevelsNID = 2
// MRoomJoinRulesNID is the numeric ID for the "m.room.join_rules" event type.
MRoomJoinRulesNID = 3
// MRoomThirdPartyInviteNID is the numeric ID for the "m.room.third_party_invite" event type.
MRoomThirdPartyInviteNID = 4
// MRoomMemberNID is the numeric ID for the "m.room.member" event type.
MRoomMemberNID = 5
// MRoomRedactionNID is the numeric ID for the "m.room.redaction" event type.
MRoomRedactionNID = 6
// MRoomHistoryVisibilityNID is the numeric ID for the "m.room.history_visibility" event type.
MRoomHistoryVisibilityNID = 7
)
const (
// EmptyStateKeyNID is the numeric ID for the empty state key.
EmptyStateKeyNID = 1
)