fix: align group private chat permission semantics
This commit is contained in:
@@ -406,6 +406,7 @@ type TenantBusinessAdapter interface {
|
|||||||
TenantGroupMemberGet(t.TenantID, string, t.Uid) (*t.GroupMember, error)
|
TenantGroupMemberGet(t.TenantID, string, t.Uid) (*t.GroupMember, error)
|
||||||
TenantGroupMemberList(t.TenantID, string, t.GroupMemberListOptions) ([]t.GroupMember, string, error)
|
TenantGroupMemberList(t.TenantID, string, t.GroupMemberListOptions) ([]t.GroupMember, string, error)
|
||||||
TenantGroupMemberUpdate(t.TenantID, string, t.Uid, t.GroupMemberPatch) (*t.GroupMember, error)
|
TenantGroupMemberUpdate(t.TenantID, string, t.Uid, t.GroupMemberPatch) (*t.GroupMember, error)
|
||||||
|
TenantUsersShareOpenPrivateGroup(t.TenantID, t.Uid, t.Uid) (bool, error)
|
||||||
TenantGroupInvitationCreate(t.TenantID, *t.GroupInvitation) (*t.GroupInvitation, error)
|
TenantGroupInvitationCreate(t.TenantID, *t.GroupInvitation) (*t.GroupInvitation, error)
|
||||||
TenantGroupInvitationAccept(t.TenantID, string, t.Uid, any, time.Time) (*t.GroupInvitation, error)
|
TenantGroupInvitationAccept(t.TenantID, string, t.Uid, any, time.Time) (*t.GroupInvitation, error)
|
||||||
TenantGroupJoinRequestCreate(t.TenantID, *t.GroupJoinRequest) (*t.GroupJoinRequest, error)
|
TenantGroupJoinRequestCreate(t.TenantID, *t.GroupJoinRequest) (*t.GroupJoinRequest, error)
|
||||||
|
|||||||
@@ -171,3 +171,24 @@ func TestSharedDBGroupFileLinkIncludesTenantID(t *testing.T) {
|
|||||||
t.Fatal("TenantGroupFileCreate must not rely on im_filemsglinks.tenant_id default")
|
t.Fatal("TenantGroupFileCreate must not rely on im_filemsglinks.tenant_id default")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTenantUsersShareOpenPrivateGroupChecksBothMembersAndSetting(t *testing.T) {
|
||||||
|
source, err := os.ReadFile("tenant_groups.go")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
body := sourceBetween(t, string(source), "func (a *adapter) TenantUsersShareOpenPrivateGroup", "\nfunc (a *adapter) TenantGroupMemberUpdate")
|
||||||
|
for _, required := range []string{
|
||||||
|
"INNER JOIN im_group_members AS gm2",
|
||||||
|
"gm2.topic=gm1.topic",
|
||||||
|
"INNER JOIN im_group_settings AS gs",
|
||||||
|
"gs.member_private_chat=1",
|
||||||
|
"gm1.member_status=?",
|
||||||
|
"gm2.member_status=?",
|
||||||
|
"gs.group_status=?",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(body, required) {
|
||||||
|
t.Fatalf("TenantUsersShareOpenPrivateGroup must check active shared group and setting; missing %q", required)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -394,6 +394,39 @@ func (a *adapter) TenantGroupMemberList(id t.TenantID, topic string, opts t.Grou
|
|||||||
return items, next, rows.Err()
|
return items, next, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *adapter) TenantUsersShareOpenPrivateGroup(id t.TenantID, userID, peerID t.Uid) (bool, error) {
|
||||||
|
if tenantAdp, err := a.routeGroupTenant(id); err != nil {
|
||||||
|
return false, err
|
||||||
|
} else if tenantAdp != a {
|
||||||
|
return tenantAdp.TenantUsersShareOpenPrivateGroup(id, userID, peerID)
|
||||||
|
}
|
||||||
|
if userID.IsZero() || peerID.IsZero() || userID == peerID {
|
||||||
|
return false, t.ErrMalformed
|
||||||
|
}
|
||||||
|
ctx, cancel := a.getContext()
|
||||||
|
if cancel != nil {
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
var exists int
|
||||||
|
err := a.db.GetContext(ctx, &exists, tenantShardSQL(id, `
|
||||||
|
SELECT 1
|
||||||
|
FROM im_group_members AS gm1
|
||||||
|
INNER JOIN im_group_members AS gm2
|
||||||
|
ON gm2.tenant_id=gm1.tenant_id AND gm2.topic=gm1.topic
|
||||||
|
INNER JOIN im_group_settings AS gs
|
||||||
|
ON gs.tenant_id=gm1.tenant_id AND gs.topic=gm1.topic
|
||||||
|
WHERE gm1.tenant_id=? AND gm1.user_id=? AND gm1.member_status=?
|
||||||
|
AND gm2.user_id=? AND gm2.member_status=?
|
||||||
|
AND gs.group_status=? AND gs.member_private_chat=1
|
||||||
|
LIMIT 1`),
|
||||||
|
id, decodeUID(userID), t.GroupMemberStatusActive,
|
||||||
|
decodeUID(peerID), t.GroupMemberStatusActive, t.GroupStatusActive)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return exists == 1, err
|
||||||
|
}
|
||||||
|
|
||||||
func (a *adapter) TenantGroupMemberUpdate(id t.TenantID, topic string, uid t.Uid, patch t.GroupMemberPatch) (*t.GroupMember, error) {
|
func (a *adapter) TenantGroupMemberUpdate(id t.TenantID, topic string, uid t.Uid, patch t.GroupMemberPatch) (*t.GroupMember, error) {
|
||||||
if tenantAdp, err := a.routeGroupTenant(id); err != nil {
|
if tenantAdp, err := a.routeGroupTenant(id); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -4026,15 +4026,6 @@ func serveIMFriendRequestCreate(wrt http.ResponseWriter, req *http.Request) {
|
|||||||
writeIMError(wrt, req, http.StatusForbidden, 30005, imReasonPermissionDenied, "group membership required", nil)
|
writeIMError(wrt, req, http.StatusForbidden, 30005, imReasonPermissionDenied, "group membership required", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
settings, settingsErr := store.GroupBusiness.GetSettings(principal.TenantID, sourceTopic)
|
|
||||||
if settingsErr != nil {
|
|
||||||
writeIMRelationError(wrt, req, settingsErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if settings == nil || !settings.MemberPrivateChat {
|
|
||||||
writeIMError(wrt, req, http.StatusForbidden, 30005, imReasonPermissionDenied, "member private chat is disabled", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "invalid friend request source", nil)
|
writeIMError(wrt, req, http.StatusBadRequest, 30000, imReasonMalformedRequest, "invalid friend request source", nil)
|
||||||
return
|
return
|
||||||
|
|||||||
+10
-10
@@ -7852,26 +7852,23 @@ func mustP2PNameForUser(t *testing.T, uid types.Uid, topic string) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServeIMFriendRequestCreateHonorsGroupPrivateChatSetting(t *testing.T) {
|
func TestServeIMFriendRequestCreateAllowsGroupSourceWhenPrivateChatDisabled(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
expectIMBearer(t, ctrl, "token-1", 7, 1)
|
mockStore := expectIMBearer(t, ctrl, "token-1", 7, 1)
|
||||||
|
mockStore.EXPECT().GetUidString().Return("group-request-1")
|
||||||
oldRelations := store.Relations
|
oldRelations := store.Relations
|
||||||
oldProfiles := store.Profiles
|
oldProfiles := store.Profiles
|
||||||
oldSubs := store.Subs
|
oldSubs := store.Subs
|
||||||
oldGroups := store.GroupBusiness
|
relations := &testRelationsStore{context: &types.RelationshipContext{Relationship: types.RelationshipNone}}
|
||||||
store.Relations = &testRelationsStore{context: &types.RelationshipContext{Relationship: types.RelationshipNone}}
|
store.Relations = relations
|
||||||
store.Profiles = &testProfilesStore{}
|
store.Profiles = &testProfilesStore{}
|
||||||
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
|
||||||
store.Subs = subs
|
store.Subs = subs
|
||||||
store.GroupBusiness = &testGroupBusinessStore{settings: &types.GroupSettings{
|
|
||||||
Topic: "grpAlpha", MemberPrivateChat: false, GroupStatus: types.GroupStatusActive,
|
|
||||||
}}
|
|
||||||
defer func() {
|
defer func() {
|
||||||
store.Relations = oldRelations
|
store.Relations = oldRelations
|
||||||
store.Profiles = oldProfiles
|
store.Profiles = oldProfiles
|
||||||
store.Subs = oldSubs
|
store.Subs = oldSubs
|
||||||
store.GroupBusiness = oldGroups
|
|
||||||
}()
|
}()
|
||||||
memberMode := types.ModeCPublic
|
memberMode := types.ModeCPublic
|
||||||
for _, uid := range []types.Uid{1, 2} {
|
for _, uid := range []types.Uid{1, 2} {
|
||||||
@@ -7886,8 +7883,11 @@ func TestServeIMFriendRequestCreateHonorsGroupPrivateChatSetting(t *testing.T) {
|
|||||||
req.Header.Set("Idempotency-Key", "fr-group-1")
|
req.Header.Set("Idempotency-Key", "fr-group-1")
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
serveIMFriendRequests(rec, req)
|
serveIMFriendRequests(rec, req)
|
||||||
if rec.Code != http.StatusForbidden {
|
if rec.Code != http.StatusOK {
|
||||||
t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusForbidden, rec.Body.String())
|
t.Fatalf("status: got %d, want %d; body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||||
|
}
|
||||||
|
if relations.created == nil || relations.created.Source != "group" || relations.created.SourceTopic != "grpAlpha" {
|
||||||
|
t.Fatalf("unexpected group friend request: %#v", relations.created)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -3220,13 +3220,26 @@ func (relationsMapper) CanSendP2P(tenantID types.TenantID, userID, peerID types.
|
|||||||
return true, types.RelationshipFriend, nil
|
return true, types.RelationshipFriend, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx, err := (relationsMapper{}).GetRelationshipContext(tenantID, userID, peerID, "")
|
tenantAdp, err := tenantBusinessAdapter(tenantID)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", err
|
||||||
|
}
|
||||||
|
ctx, err := relationshipContext(tenantAdp, tenantID, userID, peerID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", err
|
return false, "", err
|
||||||
}
|
}
|
||||||
if ctx.Relationship == types.RelationshipFriend {
|
if ctx.Relationship == types.RelationshipFriend {
|
||||||
return true, ctx.Relationship, nil
|
return true, ctx.Relationship, nil
|
||||||
}
|
}
|
||||||
|
if ctx.Relationship == types.RelationshipNone {
|
||||||
|
shared, err := tenantAdp.TenantUsersShareOpenPrivateGroup(tenantID, userID, peerID)
|
||||||
|
if err != nil {
|
||||||
|
return false, ctx.Relationship, err
|
||||||
|
}
|
||||||
|
if shared {
|
||||||
|
return true, ctx.Relationship, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return false, ctx.Relationship, nil
|
return false, ctx.Relationship, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user