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/gomatrixserverlibmain
parent
fad997303b
commit
4d1d503d43
|
@ -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
|
// MemberContent is the event content for http://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-member
|
||||||
type MemberContent struct {
|
type MemberContent struct {
|
||||||
Membership string `json:"membership"`
|
Membership string `json:"membership"`
|
||||||
DisplayName string `json:"displayname,omitempty"`
|
DisplayName string `json:"displayname,omitempty"`
|
||||||
AvatarURL string `json:"avatar_url,omitempty"`
|
AvatarURL string `json:"avatar_url,omitempty"`
|
||||||
Reason string `json:"reason,omitempty"`
|
Reason string `json:"reason,omitempty"`
|
||||||
// TODO: ThirdPartyInvite string `json:"third_party_invite,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
|
// ThirdPartyInviteContent is the content event for https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-third-party-invite
|
||||||
|
|
|
@ -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(
|
v1fedmux.Handle("/event/{eventID}", common.MakeFedAPI(
|
||||||
"federation_get_event", cfg.Matrix.ServerName, keys,
|
"federation_get_event", cfg.Matrix.ServerName, keys,
|
||||||
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse {
|
||||||
|
|
|
@ -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
|
package writers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -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
|
package writers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -116,7 +116,7 @@
|
||||||
{
|
{
|
||||||
"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": "768a8767051a4aca7f5e41f912954ae04d5f1efb",
|
"revision": "2e9caead882bcdeb999cf4677cfff47e39d3271e",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,6 +18,7 @@ package gomatrixserverlib
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Base64String is a string of bytes that are base64 encoded when used in JSON.
|
// 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 {
|
if err = json.Unmarshal(raw, &str); err != nil {
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
func TestMarshalBase64Struct(t *testing.T) {
|
||||||
input := struct{ Value Base64String }{Base64String("this\xffis\xffa\xfftest")}
|
input := struct{ Value Base64String }{Base64String("this\xffis\xffa\xfftest")}
|
||||||
want := `{"Value":"dGhpc/9pc/9h/3Rlc3Q"}`
|
want := `{"Value":"dGhpc/9pc/9h/3Rlc3Q"}`
|
||||||
|
|
|
@ -422,7 +422,7 @@ func (e Event) CheckFields() error {
|
||||||
if e.fields.Type != MRoomMember {
|
if e.fields.Type != MRoomMember {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"gomatrixserverlib: sender domain doesn't match origin: %q != %q",
|
"gomatrixserverlib: sender domain doesn't match origin: %q != %q",
|
||||||
eventDomain, e.fields.Origin,
|
senderDomain, e.fields.Origin,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
c, err := newMemberContentFromEvent(e)
|
c, err := newMemberContentFromEvent(e)
|
||||||
|
@ -432,7 +432,7 @@ func (e Event) CheckFields() error {
|
||||||
if c.Membership != invite || c.ThirdPartyInvite == nil {
|
if c.Membership != invite || c.ThirdPartyInvite == nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"gomatrixserverlib: sender domain doesn't match origin: %q != %q",
|
"gomatrixserverlib: sender domain doesn't match origin: %q != %q",
|
||||||
eventDomain, e.fields.Origin,
|
senderDomain, e.fields.Origin,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -18,6 +18,9 @@ package gomatrixserverlib
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
"github.com/matrix-org/util"
|
"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.
|
// thirdPartyInviteToken extracts the token from the third_party_invite.
|
||||||
func thirdPartyInviteToken(thirdPartyInviteData rawJSON) (string, error) {
|
func thirdPartyInviteToken(thirdPartyInvite *memberThirdPartyInvite) (string, error) {
|
||||||
var thirdPartyInvite struct {
|
|
||||||
Signed struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
} `json:"signed"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(thirdPartyInviteData, &thirdPartyInvite); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if thirdPartyInvite.Signed.Token == "" {
|
if thirdPartyInvite.Signed.Token == "" {
|
||||||
return "", fmt.Errorf("missing 'third_party_invite.signed.token' JSON key")
|
return "", fmt.Errorf("missing 'third_party_invite.signed.token' JSON key")
|
||||||
}
|
}
|
||||||
|
@ -774,6 +769,8 @@ type membershipAllower struct {
|
||||||
powerLevels powerLevelContent
|
powerLevels powerLevelContent
|
||||||
// The m.room.join_rules content for the room.
|
// The m.room.join_rules content for the room.
|
||||||
joinRule joinRuleContent
|
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
|
// 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
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -840,10 +844,10 @@ func (m *membershipAllower) membershipAllowed(event Event) error {
|
||||||
// Otherwise fall back to the normal checks.
|
// 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
|
// Special case third party invites
|
||||||
// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L393
|
// 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 {
|
if m.targetID == m.senderID {
|
||||||
|
@ -855,6 +859,42 @@ func (m *membershipAllower) membershipAllowed(event Event) error {
|
||||||
return m.membershipAllowedOther()
|
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.
|
// membershipAllowedSelf determines if the change made by the user to their own membership is allowed.
|
||||||
func (m *membershipAllower) membershipAllowedSelf() error {
|
func (m *membershipAllower) membershipAllowedSelf() error {
|
||||||
if m.newMember.Membership == join {
|
if m.newMember.Membership == join {
|
||||||
|
|
|
@ -169,7 +169,12 @@ func TestStateNeededForInvite3PID(t *testing.T) {
|
||||||
StateKey: &skey,
|
StateKey: &skey,
|
||||||
Sender: "@u1:a",
|
Sender: "@u1:a",
|
||||||
}
|
}
|
||||||
b.SetContent(memberContent{"invite", rawJSON(`{"signed":{"token":"my_token"}}`)})
|
|
||||||
|
b.SetContent(memberContent{"invite", &memberThirdPartyInvite{
|
||||||
|
Signed: memberThirdPartyInviteSigned{
|
||||||
|
Token: "my_token",
|
||||||
|
},
|
||||||
|
}})
|
||||||
testStateNeededForAuth(t, `[{
|
testStateNeededForAuth(t, `[{
|
||||||
"type": "m.room.member",
|
"type": "m.room.member",
|
||||||
"state_key": "@u2:b",
|
"state_key": "@u2:b",
|
||||||
|
@ -280,7 +285,7 @@ func testEventAllowed(t *testing.T, testCaseJSON string) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if err := Allowed(event, &tc.AuthEvents); err == nil {
|
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) {
|
func TestAllowedNoFederation(t *testing.T) {
|
||||||
testEventAllowed(t, `{
|
testEventAllowed(t, `{
|
||||||
"auth_events": {
|
"auth_events": {
|
||||||
|
|
|
@ -108,7 +108,18 @@ 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 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.
|
// 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
|
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.
|
// 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.
|
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-join-rules for descriptions of the fields.
|
||||||
type joinRuleContent struct {
|
type joinRuleContent struct {
|
||||||
|
|
Loading…
Reference in New Issue