Skip to content

Commit 2f0956d

Browse files
committed
initial_snapshot: Centralize (most of) the group-permission defaults
Now these are explicit and it's clear where they come from. Thanks Greg for this suggestion: zulip#1842 (comment) The exception is the pre-291 fallback, which doesn't fit into a static fixture because it depends on the realm setting realmDeleteOwnMessagePolicy.
1 parent 1947b22 commit 2f0956d

File tree

4 files changed

+151
-37
lines changed

4 files changed

+151
-37
lines changed

lib/model/channel.dart

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -156,25 +156,14 @@ mixin ChannelStore on UserStore {
156156

157157
bool _selfHasContentAccessViaGroupPermissions(ZulipStream channel) {
158158
// Compare web's stream_data.has_content_access_via_group_permissions.
159-
// TODO(#814) try to clean up this logic; perhaps record more explicitly
160-
// what default/fallback value to use for a given group-based permission
161-
// on older servers.
162-
163-
if (channel.canAddSubscribersGroup != null
164-
&& selfHasPermissionForGroupSetting(channel.canAddSubscribersGroup!,
165-
GroupSettingType.stream, 'can_add_subscribers_group')) {
166-
// The behavior before this permission was introduced was equivalent to
167-
// the "nobody" group.
168-
// TODO(server-10): simplify
159+
160+
if (selfHasPermissionForGroupSetting(channel.canAddSubscribersGroup,
161+
GroupSettingType.stream, 'can_add_subscribers_group')) {
169162
return true;
170163
}
171164

172-
if (channel.canSubscribeGroup != null
173-
&& selfHasPermissionForGroupSetting(channel.canSubscribeGroup!,
174-
GroupSettingType.stream, 'can_subscribe_group')) {
175-
// The behavior before this permission was introduced was equivalent to
176-
// the "nobody" group.
177-
// TODO(server-10): simplify
165+
if (selfHasPermissionForGroupSetting(channel.canSubscribeGroup,
166+
GroupSettingType.stream, 'can_subscribe_group')) {
178167
return true;
179168
}
180169

lib/model/message.dart

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -107,19 +107,14 @@ mixin MessageStore on ChannelStore {
107107
return false;
108108
}
109109

110-
if (realmCanDeleteAnyMessageGroup != null) {
111-
if (selfHasPermissionForGroupSetting(realmCanDeleteAnyMessageGroup!,
112-
GroupSettingType.realm, 'can_delete_any_message_group')) {
113-
return true;
114-
}
115-
} else if (selfUser.role.isAtLeast(UserRole.administrator)) {
110+
if (selfHasPermissionForGroupSetting(realmCanDeleteAnyMessageGroup,
111+
GroupSettingType.realm, 'can_delete_any_message_group')) {
116112
return true;
117113
}
118114

119115
if (channel != null) {
120-
if (channel.canDeleteAnyMessageGroup != null
121-
&& selfHasPermissionForGroupSetting(channel.canDeleteAnyMessageGroup!,
122-
GroupSettingType.stream, 'can_delete_any_message_group')) {
116+
if (selfHasPermissionForGroupSetting(channel.canDeleteAnyMessageGroup,
117+
GroupSettingType.stream, 'can_delete_any_message_group')) {
123118
return true;
124119
}
125120
}
@@ -138,6 +133,9 @@ mixin MessageStore on ChannelStore {
138133
// that's impossible here because `message` can't be an [OutboxMessage]
139134
// (it's a [Message] from [MessageStore.messages]).
140135

136+
// (selfHasPermissionForGroupSetting isn't equipped to handle the old-server
137+
// fallback logic for this specific permission; it's dynamic and depends on
138+
// realmDeleteOwnMessagePolicy, so we do our own null check here.)
141139
if (realmCanDeleteOwnMessageGroup != null) {
142140
if (!selfHasPermissionForGroupSetting(realmCanDeleteOwnMessageGroup!,
143141
GroupSettingType.realm, 'can_delete_own_message_group')) {
@@ -146,11 +144,8 @@ mixin MessageStore on ChannelStore {
146144
return false;
147145
}
148146

149-
if (
150-
channel.canDeleteOwnMessageGroup == null
151-
|| !selfHasPermissionForGroupSetting(channel.canDeleteOwnMessageGroup!,
152-
GroupSettingType.stream, 'can_delete_own_message_group')
153-
) {
147+
if (!selfHasPermissionForGroupSetting(channel.canDeleteOwnMessageGroup,
148+
GroupSettingType.stream, 'can_delete_own_message_group')) {
154149
return false;
155150
}
156151
}

lib/model/realm.dart

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ mixin RealmStore on PerAccountStoreBase, UserGroupStore {
129129
}
130130

131131
/// Whether the self-user has the given (group-based) permission.
132-
bool selfHasPermissionForGroupSetting(GroupSettingValue value,
132+
///
133+
/// If the server doesn't know about the permission,
134+
/// pass null for [value] and a reasonable default will be chosen.
135+
bool selfHasPermissionForGroupSetting(GroupSettingValue? value,
133136
GroupSettingType type, String name);
134137
}
135138

@@ -180,7 +183,7 @@ mixin ProxyRealmStore on RealmStore {
180183
@override
181184
List<CustomProfileField> get customProfileFields => realmStore.customProfileFields;
182185
@override
183-
bool selfHasPermissionForGroupSetting(GroupSettingValue value, GroupSettingType type, String name) =>
186+
bool selfHasPermissionForGroupSetting(GroupSettingValue? value, GroupSettingType type, String name) =>
184187
realmStore.selfHasPermissionForGroupSetting(value, type, name);
185188
}
186189

@@ -225,7 +228,7 @@ class RealmStoreImpl extends HasUserGroupStore with RealmStore {
225228
customProfileFields = _sortCustomProfileFields(initialSnapshot.customProfileFields);
226229

227230
@override
228-
bool selfHasPermissionForGroupSetting(GroupSettingValue value,
231+
bool selfHasPermissionForGroupSetting(GroupSettingValue? value,
229232
GroupSettingType type, String name) {
230233
// Compare web's settings_data.user_has_permission_for_group_setting.
231234
//
@@ -236,11 +239,48 @@ class RealmStoreImpl extends HasUserGroupStore with RealmStore {
236239
// That exists for deciding whether to offer the "Generate email address"
237240
// button, and if so then which users to offer in the dropdown;
238241
// it's predicting whether /api/get-stream-email-address would succeed.
239-
if (_selfUserRole == UserRole.guest) {
240-
final config = _groupSettingConfig(type, name);
241-
if (!config.allowEveryoneGroup) return false;
242+
243+
final config = _groupSettingConfig(type, name);
244+
245+
if (_selfUserRole == UserRole.guest && !config.allowEveryoneGroup) {
246+
return false;
247+
}
248+
249+
if (value != null) {
250+
return selfInGroupSetting(value);
251+
}
252+
253+
// [value] will be null when the server doesn't know about the permission.
254+
// *We* know about it (or presumably we wouldn't have called this method),
255+
// and we know a reasonable default; use that.
256+
257+
switch (config.defaultGroupName) {
258+
case DefaultGroupNameUnknown():
259+
// When we know about a permission, we should also know about the group
260+
// we've said is the default value for it.
261+
assert(false);
262+
return true;
263+
case PseudoSystemGroupName.streamCreatorOrNobody:
264+
// TODO implement
265+
return true;
266+
case SystemGroupName.everyoneOnInternet:
267+
case SystemGroupName.everyone:
268+
return true;
269+
case SystemGroupName.members:
270+
return _selfUserRole.isAtLeast(UserRole.member);
271+
case SystemGroupName.fullMembers:
272+
// TODO check waiting period (nontrivial to get access to that logic
273+
// here in RealmStore; it's on UserStore, which depends on RealmStore)
274+
return _selfUserRole.isAtLeast(UserRole.member);
275+
case SystemGroupName.moderators:
276+
return _selfUserRole.isAtLeast(UserRole.moderator);
277+
case SystemGroupName.administrators:
278+
return _selfUserRole.isAtLeast(UserRole.administrator);
279+
case SystemGroupName.owners:
280+
return _selfUserRole.isAtLeast(UserRole.owner);
281+
case SystemGroupName.nobody:
282+
return false;
242283
}
243-
return selfInGroupSetting(value);
244284
}
245285

246286
/// The metadata for how to interpret the given group-based permission setting.

test/model/realm_test.dart

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import 'package:checks/checks.dart';
22
import 'package:test/scaffolding.dart';
33
import 'package:zulip/api/model/events.dart';
4+
import 'package:zulip/api/model/initial_snapshot.dart';
45
import 'package:zulip/api/model/model.dart';
56
import 'package:zulip/model/realm.dart';
7+
import 'package:zulip/model/store.dart';
68

79
import '../example_data.dart' as eg;
810

@@ -82,6 +84,94 @@ void main() {
8284
check(hasPermission(selfUser, group, 'can_send_message_group'))
8385
.isFalse();
8486
});
87+
88+
group('fallbacks for permissions not known to the server', () {
89+
late PerAccountStore store;
90+
91+
void prepare({UserRole? selfUserRole}) {
92+
final selfUser = eg.user(role: selfUserRole);
93+
store = eg.store(selfUser: selfUser,
94+
initialSnapshot: eg.initialSnapshot(realmUsers: [selfUser]));
95+
}
96+
97+
void doCheck(GroupSettingType type, String name, bool expected) {
98+
check(store.selfHasPermissionForGroupSetting(null, type, name))
99+
.equals(expected);
100+
}
101+
102+
for (final pseudoSystemGroupName in PseudoSystemGroupName.values) {
103+
switch (pseudoSystemGroupName) {
104+
case PseudoSystemGroupName.streamCreatorOrNobody:
105+
// TODO implement and test
106+
}
107+
}
108+
109+
for (final systemGroupName in SystemGroupName.values) {
110+
switch (systemGroupName) {
111+
case SystemGroupName.everyoneOnInternet:
112+
// (No permissions where we use this default value; continue.)
113+
break;
114+
case SystemGroupName.everyone:
115+
test('everyone', () {
116+
prepare();
117+
doCheck(GroupSettingType.realm, 'can_access_all_users_group', true);
118+
});
119+
case SystemGroupName.members:
120+
test('members, is guest', () {
121+
prepare(selfUserRole: UserRole.guest);
122+
doCheck(GroupSettingType.realm, 'can_add_custom_emoji_group', false);
123+
});
124+
test('members, is member', () {
125+
prepare(selfUserRole: UserRole.member);
126+
doCheck(GroupSettingType.realm, 'can_add_custom_emoji_group', true);
127+
});
128+
case SystemGroupName.fullMembers:
129+
// (No permissions where we use this default value; continue.)
130+
break;
131+
case SystemGroupName.moderators:
132+
test('moderators, is member', () {
133+
prepare(selfUserRole: UserRole.member);
134+
doCheck(GroupSettingType.realm, 'can_set_delete_message_policy_group', false);
135+
});
136+
test('moderators, is moderator', () {
137+
prepare(selfUserRole: UserRole.moderator);
138+
doCheck(GroupSettingType.realm, 'can_set_delete_message_policy_group', true);
139+
});
140+
case SystemGroupName.administrators:
141+
test('administrators, is moderator', () {
142+
prepare(selfUserRole: UserRole.moderator);
143+
doCheck(GroupSettingType.stream, 'can_remove_subscribers_group', false);
144+
});
145+
test('administrators, is administrator', () {
146+
prepare(selfUserRole: UserRole.administrator);
147+
doCheck(GroupSettingType.stream, 'can_remove_subscribers_group', true);
148+
});
149+
case SystemGroupName.owners:
150+
test('owners, is administrator', () {
151+
prepare(selfUserRole: UserRole.administrator);
152+
doCheck(GroupSettingType.realm, 'can_create_web_public_channel_group', false);
153+
});
154+
test('owners, is owner', () {
155+
prepare(selfUserRole: UserRole.owner);
156+
doCheck(GroupSettingType.realm, 'can_create_web_public_channel_group', true);
157+
});
158+
case SystemGroupName.nobody:
159+
test('nobody', () {
160+
prepare();
161+
doCheck(GroupSettingType.stream, 'can_delete_own_message_group', false);
162+
});
163+
}
164+
}
165+
166+
test('throw on unknown name', () {
167+
// We should know about all the permissions we're trying to implement,
168+
// even the ones old servers don't know about.
169+
prepare(selfUserRole: UserRole.member);
170+
check(() => store.selfHasPermissionForGroupSetting(null,
171+
GroupSettingType.realm, 'example_future_permission_name'),
172+
).throws<Error>();
173+
});
174+
});
85175
});
86176

87177
group('customProfileFields', () {

0 commit comments

Comments
 (0)