fix p2p permission refresh during load tests
This commit is contained in:
@@ -2367,6 +2367,7 @@ func serveIMP2PConversation(wrt http.ResponseWriter, req *http.Request) {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
return
|
||||
}
|
||||
publishIMP2PPermissionsRefresh(principal.TenantID, topic, principal.Uid, peer)
|
||||
ctx, err := store.Relations.GetRelationshipContext(principal.TenantID, principal.Uid, peer, topic)
|
||||
if err != nil {
|
||||
writeIMRelationError(wrt, req, err)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tinode/chat/server/logs"
|
||||
"github.com/tinode/chat/server/store/types"
|
||||
)
|
||||
|
||||
const p2pPermissionsRefreshedEvent = "p2p.permissions.refreshed"
|
||||
|
||||
type p2pPermissionsPayload struct {
|
||||
UserIDs []string `json:"user_ids"`
|
||||
}
|
||||
|
||||
// publishIMP2PPermissionsRefresh asks the topic master to reload permissions
|
||||
// after the HTTP API has repaired or created the two P2P subscriptions.
|
||||
func publishIMP2PPermissionsRefresh(tenantID types.TenantID, topic string, uids ...types.Uid) {
|
||||
topic = strings.TrimSpace(topic)
|
||||
if !tenantID.IsValid() || topic == "" {
|
||||
return
|
||||
}
|
||||
payload := p2pPermissionsPayload{UserIDs: make([]string, 0, len(uids))}
|
||||
for _, uid := range uids {
|
||||
if !uid.IsZero() {
|
||||
payload.UserIDs = append(payload.UserIDs, uid.UserId())
|
||||
}
|
||||
}
|
||||
if len(payload.UserIDs) == 0 {
|
||||
return
|
||||
}
|
||||
encoded, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
logs.Warn.Printf("p2p permissions refresh: encode topic=%s: %v", topic, err)
|
||||
return
|
||||
}
|
||||
message := &ServerComMessage{
|
||||
TenantID: tenantID,
|
||||
RcptTo: topic,
|
||||
Timestamp: time.Now().UTC().Round(time.Millisecond),
|
||||
Info: &MsgServerInfo{
|
||||
Topic: topic, What: "p2p", Event: p2pPermissionsRefreshedEvent,
|
||||
Payload: json.RawMessage(encoded),
|
||||
},
|
||||
}
|
||||
if globals.hub != nil {
|
||||
select {
|
||||
case globals.hub.routeSrv <- message:
|
||||
default:
|
||||
logs.Warn.Printf("p2p permissions refresh: hub queue full topic=%s", topic)
|
||||
}
|
||||
}
|
||||
publishRealtimeTopicMessage(topic, message)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/tinode/chat/server/store/types"
|
||||
)
|
||||
|
||||
func TestHandleP2PPermissionsRefreshReloadsLoadedTopic(t *testing.T) {
|
||||
helper := TopicTestHelper{}
|
||||
helper.setUp(t, 2, types.TopicCatP2P, "p2p-test", true)
|
||||
defer helper.tearDown()
|
||||
|
||||
uid := helper.uids[0]
|
||||
stale := types.ModeCP2P &^ types.ModeWrite
|
||||
pud := helper.topic.perUser[uid]
|
||||
pud.modeWant = stale
|
||||
pud.modeGiven = stale
|
||||
helper.topic.perUser[uid] = pud
|
||||
helper.ss.EXPECT().Get(types.TenantID(1), "p2p-test", uid, true).Return(&types.Subscription{
|
||||
TenantID: 1, User: uid.String(), Topic: "p2p-test",
|
||||
ModeWant: types.ModeCP2P, ModeGiven: types.ModeCP2P,
|
||||
}, nil)
|
||||
|
||||
payload, err := json.Marshal(p2pPermissionsPayload{UserIDs: []string{uid.UserId()}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
helper.topic.handleServerMsg(&ServerComMessage{
|
||||
TenantID: 1,
|
||||
Info: &MsgServerInfo{What: "p2p", Event: p2pPermissionsRefreshedEvent, Payload: payload},
|
||||
})
|
||||
helper.finish()
|
||||
|
||||
got := helper.topic.perUser[uid]
|
||||
if !got.modeWant.IsWriter() || !got.modeGiven.IsWriter() {
|
||||
t.Fatalf("permissions were not refreshed: want=%s given=%s", got.modeWant, got.modeGiven)
|
||||
}
|
||||
}
|
||||
|
||||
func TestP2PPublishReloadsStalePermissions(t *testing.T) {
|
||||
helper := TopicTestHelper{}
|
||||
helper.setUp(t, 2, types.TopicCatP2P, "p2p-test", true)
|
||||
defer helper.tearDown()
|
||||
|
||||
uid := helper.uids[0]
|
||||
stale := types.ModeCP2P &^ types.ModeWrite
|
||||
pud := helper.topic.perUser[uid]
|
||||
pud.modeWant = stale
|
||||
pud.modeGiven = stale
|
||||
helper.topic.perUser[uid] = pud
|
||||
helper.ss.EXPECT().Get(types.TenantID(1), "p2p-test", uid, true).Return(&types.Subscription{
|
||||
TenantID: 1, User: uid.String(), Topic: "p2p-test",
|
||||
ModeWant: types.ModeCP2P, ModeGiven: types.ModeCP2P,
|
||||
}, nil)
|
||||
helper.mm.EXPECT().Save(types.TenantID(1), gomock.Any(), gomock.Any(), gomock.Any()).
|
||||
Return(&types.MessageSaveResult{SeqId: 1, MarkedReadBySender: true}, nil)
|
||||
|
||||
err := helper.topic.saveAndBroadcastMessage(&ClientComMessage{
|
||||
Id: "pub-1", Original: "p2p-test", Timestamp: types.TimeNow(), sess: helper.sessions[0],
|
||||
}, uid, true, nil, nil, "test")
|
||||
helper.finish()
|
||||
if err != nil {
|
||||
t.Fatalf("publish failed after permission reload: %v", err)
|
||||
}
|
||||
}
|
||||
+30
-1
@@ -678,6 +678,10 @@ func (t *Topic) handleServerMsg(msg *ServerComMessage) {
|
||||
// Ignore message - the topic is paused or being deleted.
|
||||
return
|
||||
}
|
||||
if msg.Info != nil && msg.Info.What == "p2p" && msg.Info.Event == p2pPermissionsRefreshedEvent {
|
||||
t.handleP2PPermissionsRefresh(msg.Info)
|
||||
return
|
||||
}
|
||||
if msg.Pres != nil {
|
||||
t.handlePresence(msg)
|
||||
} else if msg.Info != nil || msg.Data != nil {
|
||||
@@ -700,6 +704,24 @@ func (t *Topic) handleServerMsg(msg *ServerComMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Topic) handleP2PPermissionsRefresh(info *MsgServerInfo) {
|
||||
if t.cat != types.TopicCatP2P || t.isProxy {
|
||||
return
|
||||
}
|
||||
var payload p2pPermissionsPayload
|
||||
if err := json.Unmarshal(info.Payload, &payload); err != nil {
|
||||
logs.Warn.Printf("topic[%s]: failed to decode p2p permissions refresh: %v", t.name, err)
|
||||
return
|
||||
}
|
||||
uids := make([]types.Uid, 0, len(payload.UserIDs))
|
||||
for _, raw := range payload.UserIDs {
|
||||
if uid := types.ParseUserId(raw); !uid.IsZero() {
|
||||
uids = append(uids, uid)
|
||||
}
|
||||
}
|
||||
t.refreshPerUserFromDB(types.ZeroUid, uids...)
|
||||
}
|
||||
|
||||
// flexUID accepts a JSON string or number representation of a user id: the
|
||||
// HTTP interface publishes base32 user id strings, while Kafka admin commands
|
||||
// publish numeric database ids.
|
||||
@@ -1282,8 +1304,15 @@ func (t *Topic) saveAndBroadcastMessage(msg *ClientComMessage, asUid types.Uid,
|
||||
return types.ErrPermissionDenied
|
||||
}
|
||||
} else {
|
||||
// EnsureP2P may have repaired the subscription in the database while this
|
||||
// topic was already loaded. Reload once on denied P2P publishes so a lost
|
||||
// or delayed refresh event cannot leave the conversation permanently stale.
|
||||
if t.cat == types.TopicCatP2P && userFound && !(pud.modeWant & pud.modeGiven).IsWriter() && store.Subs != nil {
|
||||
t.refreshPerUserFromDB(asUid, asUid)
|
||||
pud, userFound = t.perUser[asUid]
|
||||
}
|
||||
// If it's not 'sys' check write permission.
|
||||
if !(pud.modeWant & pud.modeGiven).IsWriter() {
|
||||
if !userFound || !(pud.modeWant & pud.modeGiven).IsWriter() {
|
||||
msg.sess.queueOut(ErrPermissionDenied(msg.Id, t.original(asUid), msg.Timestamp))
|
||||
return types.ErrPermissionDenied
|
||||
}
|
||||
|
||||
@@ -818,6 +818,10 @@ func TestHandleBroadcastDataMissingWritePermission(t *testing.T) {
|
||||
pud := helper.topic.perUser[uid1]
|
||||
pud.modeGiven = types.ModeRead | types.ModeJoin
|
||||
helper.topic.perUser[uid1] = pud
|
||||
helper.ss.EXPECT().Get(types.TenantID(1), topicName, uid1, true).Return(&types.Subscription{
|
||||
TenantID: 1, User: uid1.String(), Topic: topicName,
|
||||
ModeWant: pud.modeWant, ModeGiven: pud.modeGiven,
|
||||
}, nil)
|
||||
|
||||
// Make test message.
|
||||
from := helper.uids[0].UserId()
|
||||
|
||||
Reference in New Issue
Block a user