From 4d1d503d43232192d9581c498af0aaf71afb7f81 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 8 Sep 2017 15:17:12 +0100 Subject: [PATCH] Processing of pending invites on 3PID binding (#218) * Add missing file headers * Move the ID server's signatures verification to common * Allow verification without specifying a server name * Add third-party structs to membership events content * Add processing of 3PID onbind requests * Use reference for third party invite data * Fix return arguments order * Revert "Move the ID server's signatures verification to common" This reverts commit 93442010316ce71a77ac58ffd3613754ce8fe969. * Revert "Allow verification without specifying a server name" This reverts commit fd27afbf82eac50fe9f7b83b26cfce3c66d530d2. * Remove checks that are already occurring in gomatrixserverlib * Change return type of createInviteFrom3PIDInvite * Add doc, add checks in fillDisplayName * Use MakeFedAPI * Invert condition * Use AuthEvents to retrieve the 3PID invite * Update comment * Remove unused parameter * gb vendor update github.com/matrix-org/gomatrixserverlib --- .../dendrite/common/eventcontent.go | 23 ++- .../dendrite/federationapi/routing/routing.go | 7 + .../dendrite/federationapi/writers/invite.go | 14 ++ .../dendrite/federationapi/writers/send.go | 14 ++ .../federationapi/writers/threepid.go | 192 ++++++++++++++++++ vendor/manifest | 2 +- .../matrix-org/gomatrixserverlib/base64.go | 9 +- .../gomatrixserverlib/base64_test.go | 13 ++ .../matrix-org/gomatrixserverlib/event.go | 4 +- .../gomatrixserverlib/event_examples_test.go | 27 +++ .../matrix-org/gomatrixserverlib/eventauth.go | 62 +++++- .../gomatrixserverlib/eventauth_test.go | 149 +++++++++++++- .../gomatrixserverlib/eventcontent.go | 39 +++- 13 files changed, 532 insertions(+), 23 deletions(-) create mode 100644 src/github.com/matrix-org/dendrite/federationapi/writers/threepid.go create mode 100644 vendor/src/github.com/matrix-org/gomatrixserverlib/event_examples_test.go diff --git a/src/github.com/matrix-org/dendrite/common/eventcontent.go b/src/github.com/matrix-org/dendrite/common/eventcontent.go index 0dfb37a1..971c4f0a 100644 --- a/src/github.com/matrix-org/dendrite/common/eventcontent.go +++ b/src/github.com/matrix-org/dendrite/common/eventcontent.go @@ -22,11 +22,24 @@ type CreateContent struct { // MemberContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-member type MemberContent struct { - Membership string `json:"membership"` - DisplayName string `json:"displayname,omitempty"` - AvatarURL string `json:"avatar_url,omitempty"` - Reason string `json:"reason,omitempty"` - // TODO: ThirdPartyInvite string `json:"third_party_invite,omitempty"` + Membership string `json:"membership"` + DisplayName string `json:"displayname,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + Reason string `json:"reason,omitempty"` + ThirdPartyInvite *TPInvite `json:"third_party_invite,omitempty"` +} + +// TPInvite is the "Invite" structure defined at http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-member +type TPInvite struct { + DisplayName string `json:"display_name"` + Signed TPInviteSigned `json:"signed"` +} + +// TPInviteSigned is the "signed" structure defined at http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-member +type TPInviteSigned struct { + MXID string `json:"mxid"` + Signatures map[string]map[string]string `json:"signatures"` + Token string `json:"token"` } // ThirdPartyInviteContent is the content event for https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-third-party-invite diff --git a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go index b4d01dab..8d49800d 100644 --- a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go @@ -79,6 +79,13 @@ func Setup( }, )) + v1fedmux.Handle("/3pid/onbind", common.MakeFedAPI( + "3pid_onbind", cfg.Matrix.ServerName, keys, + func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { + return writers.CreateInvitesFrom3PIDInvites(httpReq, query, cfg, producer) + }, + )) + v1fedmux.Handle("/event/{eventID}", common.MakeFedAPI( "federation_get_event", cfg.Matrix.ServerName, keys, func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { diff --git a/src/github.com/matrix-org/dendrite/federationapi/writers/invite.go b/src/github.com/matrix-org/dendrite/federationapi/writers/invite.go index 03972d36..20048103 100644 --- a/src/github.com/matrix-org/dendrite/federationapi/writers/invite.go +++ b/src/github.com/matrix-org/dendrite/federationapi/writers/invite.go @@ -1,3 +1,17 @@ +// Copyright 2017 Vector Creations Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package writers import ( diff --git a/src/github.com/matrix-org/dendrite/federationapi/writers/send.go b/src/github.com/matrix-org/dendrite/federationapi/writers/send.go index f253162b..cd276f52 100644 --- a/src/github.com/matrix-org/dendrite/federationapi/writers/send.go +++ b/src/github.com/matrix-org/dendrite/federationapi/writers/send.go @@ -1,3 +1,17 @@ +// Copyright 2017 Vector Creations Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package writers import ( diff --git a/src/github.com/matrix-org/dendrite/federationapi/writers/threepid.go b/src/github.com/matrix-org/dendrite/federationapi/writers/threepid.go new file mode 100644 index 00000000..ad9b4856 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/federationapi/writers/threepid.go @@ -0,0 +1,192 @@ +// Copyright 2017 Vector Creations Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package writers + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/matrix-org/dendrite/clientapi/httputil" + "github.com/matrix-org/dendrite/clientapi/producers" + "github.com/matrix-org/dendrite/common" + "github.com/matrix-org/dendrite/common/config" + "github.com/matrix-org/dendrite/roomserver/api" + + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" +) + +type invite struct { + MXID string `json:"mxid"` + RoomID string `json:"room_id"` + Sender string `json:"sender"` + Token string `json:"token"` + Signed common.TPInviteSigned `json:"signed"` +} + +type invites struct { + Medium string `json:"medium"` + Address string `json:"address"` + MXID string `json:"mxid"` + Invites []invite `json:"invites"` +} + +// CreateInvitesFrom3PIDInvites implements POST /_matrix/federation/v1/3pid/onbind +func CreateInvitesFrom3PIDInvites( + req *http.Request, queryAPI api.RoomserverQueryAPI, cfg config.Dendrite, + producer *producers.RoomserverProducer, +) util.JSONResponse { + var body invites + if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil { + return *reqErr + } + + evs := []gomatrixserverlib.Event{} + for _, inv := range body.Invites { + event, err := createInviteFrom3PIDInvite(queryAPI, cfg, inv) + if err != nil { + return httputil.LogThenError(req, err) + } + if event != nil { + evs = append(evs, *event) + } + } + + // Send all the events + if err := producer.SendEvents(evs, cfg.Matrix.ServerName); err != nil { + return httputil.LogThenError(req, err) + } + + return util.JSONResponse{ + Code: 200, + JSON: struct{}{}, + } +} + +// createInviteFrom3PIDInvite processes an invite provided by the identity server +// and creates a m.room.member event (with "invite" membership) from it. +// Returns an error if there was a problem building the event or fetching the +// necessary data to do so. +func createInviteFrom3PIDInvite( + queryAPI api.RoomserverQueryAPI, cfg config.Dendrite, inv invite, +) (*gomatrixserverlib.Event, error) { + // Build the event + builder := &gomatrixserverlib.EventBuilder{ + Type: "m.room.member", + Sender: inv.Sender, + RoomID: inv.RoomID, + StateKey: &inv.MXID, + } + + content := common.MemberContent{ + // TODO: Load the profile + Membership: "invite", + ThirdPartyInvite: &common.TPInvite{ + Signed: inv.Signed, + }, + } + + if err := builder.SetContent(content); err != nil { + return nil, err + } + + eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder) + if err != nil { + return nil, err + } + + // Ask the roomserver for information about this room + queryReq := api.QueryLatestEventsAndStateRequest{ + RoomID: builder.RoomID, + StateToFetch: eventsNeeded.Tuples(), + } + var queryRes api.QueryLatestEventsAndStateResponse + if err = queryAPI.QueryLatestEventsAndState(&queryReq, &queryRes); err != nil { + return nil, err + } + + if !queryRes.RoomExists { + // TODO: Use federation to auth the event + return nil, nil + } + + // Finish building the event + builder.Depth = queryRes.Depth + builder.PrevEvents = queryRes.LatestEvents + + authEvents := gomatrixserverlib.NewAuthEvents(nil) + + for i := range queryRes.StateEvents { + authEvents.AddEvent(&queryRes.StateEvents[i]) + } + + if err = fillDisplayName(builder, content, authEvents); err != nil { + return nil, err + } + + refs, err := eventsNeeded.AuthEventReferences(&authEvents) + if err != nil { + return nil, err + } + builder.AuthEvents = refs + + eventID := fmt.Sprintf("$%s:%s", util.RandomString(16), cfg.Matrix.ServerName) + now := time.Now() + event, err := builder.Build(eventID, now, cfg.Matrix.ServerName, cfg.Matrix.KeyID, cfg.Matrix.PrivateKey) + if err != nil { + return nil, err + } + + return &event, nil +} + +// fillDisplayName looks in a list of auth events for a m.room.third_party_invite +// event with the state key matching a given m.room.member event's content's token. +// If such an event is found, fills the "display_name" attribute of the +// "third_party_invite" structure in the m.room.member event with the display_name +// from the m.room.third_party_invite event. +// Returns an error if there was a problem parsing the m.room.third_party_invite +// event's content or updating the m.room.member event's content. +// Returns nil if no m.room.third_party_invite with a matching token could be +// found. Returning an error isn't necessary in this case as the event will be +// rejected by gomatrixserverlib. +func fillDisplayName( + builder *gomatrixserverlib.EventBuilder, content common.MemberContent, + authEvents gomatrixserverlib.AuthEvents, +) error { + // Look for the m.room.third_party_invite event + thirdPartyInviteEvent, _ := authEvents.ThirdPartyInvite(content.ThirdPartyInvite.Signed.Token) + + if thirdPartyInviteEvent == nil { + // If the third party invite event doesn't exist then we can't use it to set the display name. + return nil + } + + var thirdPartyInviteContent common.ThirdPartyInviteContent + if err := json.Unmarshal(thirdPartyInviteEvent.Content(), &thirdPartyInviteContent); err != nil { + return err + } + + // Use the m.room.third_party_invite event to fill the "displayname" and + // update the m.room.member event's content with it + content.ThirdPartyInvite.DisplayName = thirdPartyInviteContent.DisplayName + if err := builder.SetContent(content); err != nil { + return err + } + + return nil +} diff --git a/vendor/manifest b/vendor/manifest index 8646a7df..d2c71eda 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -116,7 +116,7 @@ { "importpath": "github.com/matrix-org/gomatrixserverlib", "repository": "https://github.com/matrix-org/gomatrixserverlib", - "revision": "768a8767051a4aca7f5e41f912954ae04d5f1efb", + "revision": "2e9caead882bcdeb999cf4677cfff47e39d3271e", "branch": "master" }, { diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/base64.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/base64.go index 54e42ca3..63ff90bc 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/base64.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/base64.go @@ -18,6 +18,7 @@ package gomatrixserverlib import ( "encoding/base64" "encoding/json" + "strings" ) // A Base64String is a string of bytes that are base64 encoded when used in JSON. @@ -43,6 +44,12 @@ func (b64 *Base64String) UnmarshalJSON(raw []byte) (err error) { if err = json.Unmarshal(raw, &str); err != nil { return } - *b64, err = base64.RawStdEncoding.DecodeString(str) + // We must check whether the string was encoded in a URL-safe way in order + // to use the appropriate encoding. + if strings.ContainsAny(str, "-_") { + *b64, err = base64.RawURLEncoding.DecodeString(str) + } else { + *b64, err = base64.RawStdEncoding.DecodeString(str) + } return } diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/base64_test.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/base64_test.go index aa637aa8..9ef94046 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/base64_test.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/base64_test.go @@ -45,6 +45,19 @@ func TestUnmarshalBase64(t *testing.T) { } } +func TestUnmarshalUrlSafeBase64(t *testing.T) { + input := []byte(`"dGhpc_9pc_9h_3Rlc3Q"`) + want := "this\xffis\xffa\xfftest" + var got Base64String + err := json.Unmarshal(input, &got) + if err != nil { + t.Fatal(err) + } + if string(got) != want { + t.Fatalf("json.Unmarshal(%q): wanted %q got %q", string(input), want, string(got)) + } +} + func TestMarshalBase64Struct(t *testing.T) { input := struct{ Value Base64String }{Base64String("this\xffis\xffa\xfftest")} want := `{"Value":"dGhpc/9pc/9h/3Rlc3Q"}` diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go index e8c2de0f..c41109b9 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/event.go @@ -422,7 +422,7 @@ func (e Event) CheckFields() error { if e.fields.Type != MRoomMember { return fmt.Errorf( "gomatrixserverlib: sender domain doesn't match origin: %q != %q", - eventDomain, e.fields.Origin, + senderDomain, e.fields.Origin, ) } c, err := newMemberContentFromEvent(e) @@ -432,7 +432,7 @@ func (e Event) CheckFields() error { if c.Membership != invite || c.ThirdPartyInvite == nil { return fmt.Errorf( "gomatrixserverlib: sender domain doesn't match origin: %q != %q", - eventDomain, e.fields.Origin, + senderDomain, e.fields.Origin, ) } } diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/event_examples_test.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/event_examples_test.go new file mode 100644 index 00000000..4882f8e5 --- /dev/null +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/event_examples_test.go @@ -0,0 +1,27 @@ +/* Copyright 2016-2017 Vector Creations Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gomatrixserverlib + +import "fmt" + +func ExampleSplitID() { + localpart, domain, err := SplitID('@', "@alice:localhost:8080") + if err != nil { + panic(err) + } + fmt.Println(localpart, domain) + // Output: alice localhost:8080 +} diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go index f9b938a3..16e369cc 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth.go @@ -18,6 +18,9 @@ package gomatrixserverlib import ( "encoding/json" "fmt" + "strings" + + "golang.org/x/crypto/ed25519" "github.com/matrix-org/util" ) @@ -224,15 +227,7 @@ func accumulateStateNeeded(result *StateNeeded, eventType, sender string, stateK } // thirdPartyInviteToken extracts the token from the third_party_invite. -func thirdPartyInviteToken(thirdPartyInviteData rawJSON) (string, error) { - var thirdPartyInvite struct { - Signed struct { - Token string `json:"token"` - } `json:"signed"` - } - if err := json.Unmarshal(thirdPartyInviteData, &thirdPartyInvite); err != nil { - return "", err - } +func thirdPartyInviteToken(thirdPartyInvite *memberThirdPartyInvite) (string, error) { if thirdPartyInvite.Signed.Token == "" { return "", fmt.Errorf("missing 'third_party_invite.signed.token' JSON key") } @@ -774,6 +769,8 @@ type membershipAllower struct { powerLevels powerLevelContent // The m.room.join_rules content for the room. joinRule joinRuleContent + // The m.room.third_party_invite content referenced by this event. + thirdPartyInvite thirdPartyInviteContent } // newMembershipAllower loads the information needed to authenticate the m.room.member event @@ -808,6 +805,13 @@ func newMembershipAllower(authEvents AuthEventProvider, event Event) (m membersh return } } + // If this event comes from a third_party_invite, we need to check it against the original event. + if m.newMember.ThirdPartyInvite != nil { + token := m.newMember.ThirdPartyInvite.Signed.Token + if m.thirdPartyInvite, err = newThirdPartyInviteContentFromAuthEvents(authEvents, token); err != nil { + return + } + } return } @@ -840,10 +844,10 @@ func (m *membershipAllower) membershipAllowed(event Event) error { // Otherwise fall back to the normal checks. } - if m.newMember.Membership == invite && len(m.newMember.ThirdPartyInvite) != 0 { + if m.newMember.Membership == invite && m.newMember.ThirdPartyInvite != nil { // Special case third party invites // https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L393 - panic(fmt.Errorf("ThirdPartyInvite not implemented")) + return m.membershipAllowedFromThirdPartyInvite() } if m.targetID == m.senderID { @@ -855,6 +859,42 @@ func (m *membershipAllower) membershipAllowed(event Event) error { return m.membershipAllowedOther() } +// membershipAllowedFronThirdPartyInvite determines if the member events is following +// up the third_party_invite event it claims. +func (m *membershipAllower) membershipAllowedFromThirdPartyInvite() error { + // Check if the event's target matches with the Matrix ID provided by the + // identity server. + if m.targetID != m.newMember.ThirdPartyInvite.Signed.MXID { + return errorf( + "The invite target %s doesn't match with the Matrix ID provided by the identity server %s", + m.targetID, m.newMember.ThirdPartyInvite.Signed.MXID, + ) + } + // Marshal the "signed" so it can be verified by VerifyJSON. + marshalledSigned, err := json.Marshal(m.newMember.ThirdPartyInvite.Signed) + if err != nil { + return err + } + // Check each signature with each public key. If one signature could be + // verified with one public key, accept the event. + for _, publicKey := range m.thirdPartyInvite.PublicKeys { + for domain, signatures := range m.newMember.ThirdPartyInvite.Signed.Signatures { + for keyID := range signatures { + if strings.HasPrefix(keyID, "ed25519") { + if err = VerifyJSON( + domain, KeyID(keyID), + ed25519.PublicKey(publicKey.PublicKey), + marshalledSigned, + ); err == nil { + return nil + } + } + } + } + } + return errorf("Couldn't verify signature on third-party invite for %s", m.targetID) +} + // membershipAllowedSelf determines if the change made by the user to their own membership is allowed. func (m *membershipAllower) membershipAllowedSelf() error { if m.newMember.Membership == join { diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth_test.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth_test.go index ca11c42e..ab59cb57 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth_test.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventauth_test.go @@ -169,7 +169,12 @@ func TestStateNeededForInvite3PID(t *testing.T) { StateKey: &skey, Sender: "@u1:a", } - b.SetContent(memberContent{"invite", rawJSON(`{"signed":{"token":"my_token"}}`)}) + + b.SetContent(memberContent{"invite", &memberThirdPartyInvite{ + Signed: memberThirdPartyInviteSigned{ + Token: "my_token", + }, + }}) testStateNeededForAuth(t, `[{ "type": "m.room.member", "state_key": "@u2:b", @@ -280,7 +285,7 @@ func testEventAllowed(t *testing.T, testCaseJSON string) { panic(err) } if err := Allowed(event, &tc.AuthEvents); err == nil { - t.Fatalf("Expected %q to not be allowed but it was: %q", string(data), err) + t.Fatalf("Expected %q to not be allowed but it was", string(data)) } } } @@ -532,6 +537,146 @@ func TestAllowedWithNoPowerLevels(t *testing.T) { }`) } +func TestAllowedInviteFrom3PID(t *testing.T) { + testEventAllowed(t, `{ + "auth_events": { + "create": { + "type": "m.room.create", + "state_key": "", + "sender": "@u1:a", + "room_id": "!r1:a", + "event_id": "$e1:a", + "content": {"creator": "@u1:a"} + }, + "member": { + "@u1:a": { + "type": "m.room.member", + "sender": "@u1:a", + "room_id": "!r1:a", + "state_key": "@u1:a", + "event_id": "$e2:a", + "content": {"membership": "join"} + } + }, + "third_party_invite": { + "my_token": { + "type": "m.room.third_party_invite", + "sender": "@u1:a", + "room_id": "!r1:a", + "state_key": "my_token", + "event_id": "$e3:a", + "content": { + "display_name": "foo...@bar...", + "public_key": "pubkey", + "key_validity_url": "https://example.tld/isvalid", + "public_keys": [ + { + "public_key": "mrV51jApZKahGjfMhlevp+QtSSTDKCLaLVCzYc4HELY", + "key_validity_url": "https://example.tld/isvalid" + } + ] + } + } + } + }, + "allowed": [{ + "type": "m.room.member", + "sender": "@u1:a", + "room_id": "!r1:a", + "state_key": "@u2:a", + "event_id": "$e4:a", + "content": { + "membership": "invite", + "third_party_invite": { + "display_name": "foo...@bar...", + "signed": { + "token": "my_token", + "mxid": "@u2:a", + "signatures": { + "example.tld": { + "ed25519:0": "CibGFS0vX93quJFppsQbYQKJFIwxiYEK87lNmekS/fdetUMXPdR2wwNDd09J1jJ28GCH3GogUTuFDB1ScPFxBg" + } + } + } + } + } + }], + "not_allowed": [{ + "type": "m.room.member", + "sender": "@u1:a", + "room_id": "!r1:a", + "state_key": "@u2:a", + "event_id": "$e4:a", + "content": { + "membership": "invite", + "third_party_invite": { + "display_name": "foo...@bar...", + "signed": { + "token": "my_token", + "mxid": "@u2:a", + "signatures": { + "example.tld": { + "ed25519:0": "some_signature" + } + } + } + } + }, + "unsigned": { + "not_allowed": "Bad signature" + } + }, { + "type": "m.room.member", + "sender": "@u1:a", + "room_id": "!r1:a", + "state_key": "@u2:a", + "event_id": "$e5:a", + "content": { + "membership": "invite", + "third_party_invite": { + "display_name": "foo...@bar...", + "signed": { + "token": "my_token", + "mxid": "@u3:a", + "signatures": { + "example.tld": { + "ed25519:0": "CibGFS0vX93quJFppsQbYQKJFIwxiYEK87lNmekS/fdetUMXPdR2wwNDd09J1jJ28GCH3GogUTuFDB1ScPFxBg" + } + } + } + } + }, + "unsigned": { + "not_allowed": "MXID doesn't match state key" + } + }, { + "type": "m.room.member", + "sender": "@u1:a", + "room_id": "!r1:a", + "state_key": "@u2:a", + "event_id": "$e6:a", + "content": { + "membership": "invite", + "third_party_invite": { + "display_name": "foo...@bar...", + "signed": { + "token": "my_other_token", + "mxid": "@u2:a", + "signatures": { + "example.tld": { + "ed25519:0": "CibGFS0vX93quJFppsQbYQKJFIwxiYEK87lNmekS/fdetUMXPdR2wwNDd09J1jJ28GCH3GogUTuFDB1ScPFxBg" + } + } + } + } + }, + "unsigned": { + "not_allowed": "Token doesn't refer to a known third-party invite" + } + }] + }`) +} + func TestAllowedNoFederation(t *testing.T) { testEventAllowed(t, `{ "auth_events": { diff --git a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcontent.go b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcontent.go index 27babcb4..ad4e7751 100644 --- a/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcontent.go +++ b/vendor/src/github.com/matrix-org/gomatrixserverlib/eventcontent.go @@ -108,7 +108,18 @@ type memberContent struct { // We use the membership key in order to check if the user is in the room. Membership string `json:"membership"` // We use the third_party_invite key to special case thirdparty invites. - ThirdPartyInvite rawJSON `json:"third_party_invite,omitempty"` + ThirdPartyInvite *memberThirdPartyInvite `json:"third_party_invite,omitempty"` +} + +type memberThirdPartyInvite struct { + DisplayName string `json:"display_name"` + Signed memberThirdPartyInviteSigned `json:"signed"` +} + +type memberThirdPartyInviteSigned struct { + MXID string `json:"mxid"` + Signatures map[string]map[string]string `json:"signatures"` + Token string `json:"token"` } // newMemberContentFromAuthEvents loads the member content from the member event for the user ID in the auth events. @@ -137,6 +148,32 @@ func newMemberContentFromEvent(event Event) (c memberContent, err error) { return } +// thirdPartyInviteContent is the JSON content of a m.room.third_party_invite event needed for auth checks. +// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-third-party-invite for descriptions of the fields. +type thirdPartyInviteContent struct { + // Public keys are used to verify the signature of a m.room.member event that + // came from a m.room.third_party_invite event + PublicKeys []struct { + PublicKey Base64String `json:"public_key"` + } `json:"public_keys"` +} + +func newThirdPartyInviteContentFromAuthEvents(authEvents AuthEventProvider, token string) (t thirdPartyInviteContent, err error) { + var thirdPartyInviteEvent *Event + if thirdPartyInviteEvent, err = authEvents.ThirdPartyInvite(token); err != nil { + return + } + if thirdPartyInviteEvent == nil { + // If there isn't a third_party_invite event, then we return with an error + err = errorf("Couldn't find third party invite event") + return + } + if err = json.Unmarshal(thirdPartyInviteEvent.Content(), &t); err != nil { + err = errorf("unparsable third party invite event content: %s", err.Error()) + } + return +} + // joinRuleContent is the JSON content of a m.room.join_rules event needed for auth checks. // See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-join-rules for descriptions of the fields. type joinRuleContent struct {