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
|
@ -26,7 +26,20 @@ type MemberContent struct {
|
|||
DisplayName string `json:"displayname,omitempty"`
|
||||
AvatarURL string `json:"avatar_url,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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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",
|
||||
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
||||
"revision": "768a8767051a4aca7f5e41f912954ae04d5f1efb",
|
||||
"revision": "2e9caead882bcdeb999cf4677cfff47e39d3271e",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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"}`
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
"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 {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue