Update gomatrixserverlib

This commit is contained in:
Kegan Dougal 2017-03-08 15:48:02 +00:00
parent 94c97ed677
commit 07a1084e5a
7 changed files with 184 additions and 104 deletions

6
vendor/manifest vendored
View file

@ -92,13 +92,13 @@
{ {
"importpath": "github.com/matrix-org/gomatrixserverlib", "importpath": "github.com/matrix-org/gomatrixserverlib",
"repository": "https://github.com/matrix-org/gomatrixserverlib", "repository": "https://github.com/matrix-org/gomatrixserverlib",
"revision": "48ee56a33d195dc412dd919a0e81af70c9aaf4a3", "revision": "ce2ae9c5812346444b0ca75d57834794cde03fb7",
"branch": "master" "branch": "master"
}, },
{ {
"importpath": "github.com/matrix-org/util", "importpath": "github.com/matrix-org/util",
"repository": "https://github.com/matrix-org/util", "repository": "https://github.com/matrix-org/util",
"revision": "28bd7491c8aafbf346ca23821664f0f9911ef52b", "revision": "ec8896cd7d9ba6de6143c5f123d1e45413657e7d",
"branch": "master" "branch": "master"
}, },
{ {
@ -206,4 +206,4 @@
"branch": "master" "branch": "master"
} }
] ]
} }

View file

@ -326,6 +326,21 @@ func (e Event) Depth() int64 {
return e.fields.Depth return e.fields.Depth
} }
// UnmarshalJSON implements json.Unmarshaller assuming the Event is from an untrusted source.
// This will cause more checks than might be necessary but is probably better to be safe than sorry.
func (e *Event) UnmarshalJSON(data []byte) (err error) {
*e, err = NewEventFromUntrustedJSON(data)
return
}
// MarshalJSON implements json.Marshaller
func (e Event) MarshalJSON() ([]byte, error) {
if e.eventJSON == nil {
return nil, fmt.Errorf("gomatrixserverlib: cannot serialise uninitialised Event")
}
return e.eventJSON, nil
}
// UnmarshalJSON implements json.Unmarshaller // UnmarshalJSON implements json.Unmarshaller
func (er *EventReference) UnmarshalJSON(data []byte) error { func (er *EventReference) UnmarshalJSON(data []byte) error {
var tuple []rawJSON var tuple []rawJSON

View file

@ -18,7 +18,8 @@ package gomatrixserverlib
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"sort"
"github.com/matrix-org/util"
) )
const ( const (
@ -43,105 +44,108 @@ type StateNeeded struct {
ThirdPartyInvite []string ThirdPartyInvite []string
} }
// StateNeededForAuth returns the event types and state_keys needed to authenticate an event. // StateNeededForEventBuilder returns the event types and state_keys needed to authenticate the
// This takes a list of events to facilitate bulk processing when doing auth checks as part of state conflict resolution. // event being built. These events should be put under 'auth_events' for the event being built.
func StateNeededForAuth(events []Event) (result StateNeeded) { // Returns an error if the state needed could not be calculated with the given builder, e.g
var members []string // if there is a m.room.member without a membership key.
var thirdpartyinvites []string func StateNeededForEventBuilder(builder *EventBuilder) (result StateNeeded, err error) {
// Extract the 'content' object from the event if it is m.room.member as we need to know 'membership'
for _, event := range events { var content *memberContent
switch event.Type() { if builder.Type == "m.room.member" {
case "m.room.create": if err = json.Unmarshal(builder.content, &content); err != nil {
// The create event doesn't require any state to authenticate. err = errorf("unparsable member event content: %s", err.Error())
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L123 return
case "m.room.aliases":
// Alias events need:
// * The create event.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L128
// Alias events need no further authentication.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L160
result.Create = true
case "m.room.member":
// Member events need:
// * The previous membership of the target.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L355
// * The current membership state of the sender.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L348
// * The join rules for the room if the event is a join event.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L361
// * The power levels for the room.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L370
// * And optionally may require a m.third_party_invite event
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L393
content, err := newMemberContentFromEvent(event)
if err != nil {
// If we hit an error decoding the content we ignore it here.
// The event will be rejected when the actual checks encounter the same error.
continue
}
result.Create = true
result.PowerLevels = true
stateKey := event.StateKey()
if stateKey != nil {
members = append(members, event.Sender(), *stateKey)
}
if content.Membership == join {
result.JoinRules = true
}
if content.ThirdPartyInvite != nil {
token, err := thirdPartyInviteToken(content.ThirdPartyInvite)
if err != nil {
// If we hit an error decoding the content we ignore it here.
// The event will be rejected when the actual checks encounter the same error.
continue
} else {
thirdpartyinvites = append(thirdpartyinvites, token)
}
}
default:
// All other events need:
// * The membership of the sender.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L177
// * The power levels for the room.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L196
result.Create = true
result.PowerLevels = true
members = append(members, event.Sender())
} }
} }
err = accumulateStateNeeded(&result, builder.Type, builder.Sender, builder.StateKey, content)
// Deduplicate the state keys. result.Member = util.UniqueStrings(result.Member)
sort.Strings(members) result.ThirdPartyInvite = util.UniqueStrings(result.ThirdPartyInvite)
result.Member = members[:unique(sort.StringSlice(members))]
sort.Strings(thirdpartyinvites)
result.ThirdPartyInvite = thirdpartyinvites[:unique(sort.StringSlice(thirdpartyinvites))]
return return
} }
// Remove duplicate items from a sorted list. // StateNeededForAuth returns the event types and state_keys needed to authenticate an event.
// Takes the same interface as sort.Sort // This takes a list of events to facilitate bulk processing when doing auth checks as part of state conflict resolution.
// Returns the length of the data without duplicates func StateNeededForAuth(events []Event) (result StateNeeded) {
// Uses the last occurrence of a duplicate. for _, event := range events {
// O(n). // Extract the 'content' object from the event if it is m.room.member as we need to know 'membership'
func unique(data sort.Interface) int { var content *memberContent
length := data.Len() if event.Type() == "m.room.member" {
if length == 0 { c, err := newMemberContentFromEvent(event)
return 0 if err == nil {
} content = &c
j := 0 }
for i := 1; i < length; i++ {
if data.Less(i-1, i) {
data.Swap(i-1, j)
j++
} }
// Ignore errors when accumulating state needed.
// The event will be rejected when the actual checks encounter the same error.
_ = accumulateStateNeeded(&result, event.Type(), event.Sender(), event.StateKey(), content)
} }
data.Swap(length-1, j)
return j + 1 // Deduplicate the state keys.
result.Member = util.UniqueStrings(result.Member)
result.ThirdPartyInvite = util.UniqueStrings(result.ThirdPartyInvite)
return
}
func accumulateStateNeeded(result *StateNeeded, eventType, sender string, stateKey *string, content *memberContent) (err error) {
switch eventType {
case "m.room.create":
// The create event doesn't require any state to authenticate.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L123
case "m.room.aliases":
// Alias events need:
// * The create event.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L128
// Alias events need no further authentication.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L160
result.Create = true
case "m.room.member":
// Member events need:
// * The previous membership of the target.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L355
// * The current membership state of the sender.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L348
// * The join rules for the room if the event is a join event.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L361
// * The power levels for the room.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L370
// * And optionally may require a m.third_party_invite event
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L393
if content == nil {
err = errorf("missing memberContent for m.room.member event")
return
}
result.Create = true
result.PowerLevels = true
if stateKey != nil {
result.Member = append(result.Member, sender, *stateKey)
}
if content.Membership == join {
result.JoinRules = true
}
if content.ThirdPartyInvite != nil {
token, tokErr := thirdPartyInviteToken(content.ThirdPartyInvite)
if tokErr != nil {
err = errorf("could not get third-party token: %s", tokErr)
return
}
result.ThirdPartyInvite = append(result.ThirdPartyInvite, token)
}
default:
// All other events need:
// * The membership of the sender.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L177
// * The power levels for the room.
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L196
result.Create = true
result.PowerLevels = true
result.Member = append(result.Member, sender)
}
return
} }
// thirdPartyInviteToken extracts the token from the third_party_invite. // thirdPartyInviteToken extracts the token from the third_party_invite.
func thirdPartyInviteToken(thirdPartyInviteData json.RawMessage) (string, error) { func thirdPartyInviteToken(thirdPartyInviteData rawJSON) (string, error) {
var thirdPartyInvite struct { var thirdPartyInvite struct {
Signed struct { Signed struct {
Token string `json:"token"` Token string `json:"token"`

View file

@ -68,7 +68,7 @@ func (tel *testEventList) UnmarshalJSON(data []byte) error {
return nil return nil
} }
func testStateNeededForAuth(t *testing.T, eventdata string, want StateNeeded) { func testStateNeededForAuth(t *testing.T, eventdata string, builder *EventBuilder, want StateNeeded) {
var events testEventList var events testEventList
if err := json.Unmarshal([]byte(eventdata), &events); err != nil { if err := json.Unmarshal([]byte(eventdata), &events); err != nil {
panic(err) panic(err)
@ -77,11 +77,24 @@ func testStateNeededForAuth(t *testing.T, eventdata string, want StateNeeded) {
if !stateNeededEquals(got, want) { if !stateNeededEquals(got, want) {
t.Errorf("Testing StateNeededForAuth(%#v), wanted %#v got %#v", events, want, got) t.Errorf("Testing StateNeededForAuth(%#v), wanted %#v got %#v", events, want, got)
} }
if builder != nil {
got, err := StateNeededForEventBuilder(builder)
if !stateNeededEquals(got, want) {
t.Errorf("Testing StateNeededForEventBuilder(%#v), wanted %#v got %#v", events, want, got)
}
if err != nil {
panic(err)
}
}
} }
func TestStateNeededForCreate(t *testing.T) { func TestStateNeededForCreate(t *testing.T) {
// Create events don't need anything. // Create events don't need anything.
testStateNeededForAuth(t, `[{"type": "m.room.create"}]`, StateNeeded{}) skey := ""
testStateNeededForAuth(t, `[{"type": "m.room.create"}]`, &EventBuilder{
Type: "m.room.create",
StateKey: &skey,
}, StateNeeded{})
} }
func TestStateNeededForMessage(t *testing.T) { func TestStateNeededForMessage(t *testing.T) {
@ -89,7 +102,10 @@ func TestStateNeededForMessage(t *testing.T) {
testStateNeededForAuth(t, `[{ testStateNeededForAuth(t, `[{
"type": "m.room.message", "type": "m.room.message",
"sender": "@u1:a" "sender": "@u1:a"
}]`, StateNeeded{ }]`, &EventBuilder{
Type: "m.room.message",
Sender: "@u1:a",
}, StateNeeded{
Create: true, Create: true,
PowerLevels: true, PowerLevels: true,
Member: []string{"@u1:a"}, Member: []string{"@u1:a"},
@ -98,18 +114,27 @@ func TestStateNeededForMessage(t *testing.T) {
func TestStateNeededForAlias(t *testing.T) { func TestStateNeededForAlias(t *testing.T) {
// Alias events need only the create event. // Alias events need only the create event.
testStateNeededForAuth(t, `[{"type": "m.room.aliases"}]`, StateNeeded{ testStateNeededForAuth(t, `[{"type": "m.room.aliases"}]`, &EventBuilder{
Type: "m.room.aliases",
}, StateNeeded{
Create: true, Create: true,
}) })
} }
func TestStateNeededForJoin(t *testing.T) { func TestStateNeededForJoin(t *testing.T) {
skey := "@u1:a"
b := EventBuilder{
Type: "m.room.member",
StateKey: &skey,
Sender: "@u1:a",
}
b.SetContent(memberContent{"join", nil})
testStateNeededForAuth(t, `[{ testStateNeededForAuth(t, `[{
"type": "m.room.member", "type": "m.room.member",
"state_key": "@u1:a", "state_key": "@u1:a",
"sender": "@u1:a", "sender": "@u1:a",
"content": {"membership": "join"} "content": {"membership": "join"}
}]`, StateNeeded{ }]`, &b, StateNeeded{
Create: true, Create: true,
JoinRules: true, JoinRules: true,
PowerLevels: true, PowerLevels: true,
@ -118,12 +143,19 @@ func TestStateNeededForJoin(t *testing.T) {
} }
func TestStateNeededForInvite(t *testing.T) { func TestStateNeededForInvite(t *testing.T) {
skey := "@u2:b"
b := EventBuilder{
Type: "m.room.member",
StateKey: &skey,
Sender: "@u1:a",
}
b.SetContent(memberContent{"invite", nil})
testStateNeededForAuth(t, `[{ testStateNeededForAuth(t, `[{
"type": "m.room.member", "type": "m.room.member",
"state_key": "@u2:b", "state_key": "@u2:b",
"sender": "@u1:a", "sender": "@u1:a",
"content": {"membership": "invite"} "content": {"membership": "invite"}
}]`, StateNeeded{ }]`, &b, StateNeeded{
Create: true, Create: true,
PowerLevels: true, PowerLevels: true,
Member: []string{"@u1:a", "@u2:b"}, Member: []string{"@u1:a", "@u2:b"},
@ -131,6 +163,13 @@ func TestStateNeededForInvite(t *testing.T) {
} }
func TestStateNeededForInvite3PID(t *testing.T) { func TestStateNeededForInvite3PID(t *testing.T) {
skey := "@u2:b"
b := EventBuilder{
Type: "m.room.member",
StateKey: &skey,
Sender: "@u1:a",
}
b.SetContent(memberContent{"invite", rawJSON(`{"signed":{"token":"my_token"}}`)})
testStateNeededForAuth(t, `[{ testStateNeededForAuth(t, `[{
"type": "m.room.member", "type": "m.room.member",
"state_key": "@u2:b", "state_key": "@u2:b",
@ -143,7 +182,7 @@ func TestStateNeededForInvite3PID(t *testing.T) {
} }
} }
} }
}]`, StateNeeded{ }]`, &b, StateNeeded{
Create: true, Create: true,
PowerLevels: true, PowerLevels: true,
Member: []string{"@u1:a", "@u2:b"}, Member: []string{"@u1:a", "@u2:b"},

View file

@ -108,7 +108,7 @@ type memberContent struct {
// We use the membership key in order to check if the user is in the room. // We use the membership key in order to check if the user is in the room.
Membership string `json:"membership"` Membership string `json:"membership"`
// We use the third_party_invite key to special case thirdparty invites. // We use the third_party_invite key to special case thirdparty invites.
ThirdPartyInvite json.RawMessage `json:"third_party_invite"` ThirdPartyInvite rawJSON `json:"third_party_invite,omitempty"`
} }
// newMemberContentFromAuthEvents loads the member content from the member event for the user ID in the auth events. // newMemberContentFromAuthEvents loads the member content from the member event for the user ID in the auth events.

View file

@ -80,7 +80,7 @@ func Protect(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, req *http.Request) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
logger := req.Context().Value(ctxValueLogger).(*log.Entry) logger := GetLogger(req.Context())
logger.WithFields(log.Fields{ logger.WithFields(log.Fields{
"panic": r, "panic": r,
}).Errorf( }).Errorf(
@ -108,7 +108,7 @@ func MakeJSONAPI(handler JSONRequestHandler) http.HandlerFunc {
ctx = context.WithValue(ctx, ctxValueRequestID, reqID) ctx = context.WithValue(ctx, ctxValueRequestID, reqID)
req = req.WithContext(ctx) req = req.WithContext(ctx)
logger := req.Context().Value(ctxValueLogger).(*log.Entry) logger := GetLogger(req.Context())
logger.Print("Incoming request") logger.Print("Incoming request")
res := handler.OnIncomingRequest(req) res := handler.OnIncomingRequest(req)
@ -122,7 +122,7 @@ func MakeJSONAPI(handler JSONRequestHandler) http.HandlerFunc {
} }
func respond(w http.ResponseWriter, req *http.Request, res JSONResponse) { func respond(w http.ResponseWriter, req *http.Request, res JSONResponse) {
logger := req.Context().Value(ctxValueLogger).(*log.Entry) logger := GetLogger(req.Context())
// Set custom headers // Set custom headers
if res.Headers != nil { if res.Headers != nil {

View file

@ -194,6 +194,28 @@ func TestProtect(t *testing.T) {
} }
} }
func TestProtectWithoutLogger(t *testing.T) {
log.SetLevel(log.PanicLevel) // suppress logs in test output
mockWriter := httptest.NewRecorder()
mockReq, _ := http.NewRequest("GET", "http://example.com/foo", nil)
h := Protect(func(w http.ResponseWriter, req *http.Request) {
panic("oh noes!")
})
h(mockWriter, mockReq)
expectCode := 500
if mockWriter.Code != expectCode {
t.Errorf("TestProtect wanted HTTP status %d, got %d", expectCode, mockWriter.Code)
}
expectBody := `{"message":"Internal Server Error"}`
actualBody := mockWriter.Body.String()
if actualBody != expectBody {
t.Errorf("TestProtect wanted body %s, got %s", expectBody, actualBody)
}
}
func TestWithCORSOptions(t *testing.T) { func TestWithCORSOptions(t *testing.T) {
log.SetLevel(log.PanicLevel) // suppress logs in test output log.SetLevel(log.PanicLevel) // suppress logs in test output
mockWriter := httptest.NewRecorder() mockWriter := httptest.NewRecorder()