mirror of
https://github.com/hoernschen/dendrite.git
synced 2025-07-29 12:42:46 +00:00
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
This commit is contained in:
parent
fad997303b
commit
4d1d503d43
13 changed files with 532 additions and 23 deletions
2
vendor/manifest
vendored
2
vendor/manifest
vendored
|
@ -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
|
||||
}
|
||||
*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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
27
vendor/src/github.com/matrix-org/gomatrixserverlib/event_examples_test.go
vendored
Normal file
27
vendor/src/github.com/matrix-org/gomatrixserverlib/event_examples_test.go
vendored
Normal file
|
@ -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…
Add table
Add a link
Reference in a new issue