Skip to content

Commit 3730c90

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: #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 7e76531 commit 3730c90

File tree

4 files changed

+160
-37
lines changed

4 files changed

+160
-37
lines changed

lib/model/channel.dart

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

175175
bool _selfHasContentAccessViaGroupPermissions(ZulipStream channel) {
176176
// Compare web's stream_data.has_content_access_via_group_permissions.
177-
// TODO(#814) try to clean up this logic; perhaps record more explicitly
178-
// what default/fallback value to use for a given group-based permission
179-
// on older servers.
180-
181-
if (channel.canAddSubscribersGroup != null
182-
&& selfHasPermissionForGroupSetting(channel.canAddSubscribersGroup!,
183-
GroupSettingType.stream, 'can_add_subscribers_group')) {
184-
// The behavior before this permission was introduced was equivalent to
185-
// the "nobody" group.
186-
// TODO(server-10): simplify
177+
178+
if (selfHasPermissionForGroupSetting(channel.canAddSubscribersGroup,
179+
GroupSettingType.stream, 'can_add_subscribers_group')) {
187180
return true;
188181
}
189182

190-
if (channel.canSubscribeGroup != null
191-
&& selfHasPermissionForGroupSetting(channel.canSubscribeGroup!,
192-
GroupSettingType.stream, 'can_subscribe_group')) {
193-
// The behavior before this permission was introduced was equivalent to
194-
// the "nobody" group.
195-
// TODO(server-10): simplify
183+
if (selfHasPermissionForGroupSetting(channel.canSubscribeGroup,
184+
GroupSettingType.stream, 'can_subscribe_group')) {
196185
return true;
197186
}
198187

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: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,11 @@ mixin RealmStore on PerAccountStoreBase, UserGroupStore {
140140
bool selfHasPassedWaitingPeriod({required DateTime byDate});
141141

142142
/// Whether the self-user has the given (group-based) permission.
143-
bool selfHasPermissionForGroupSetting(GroupSettingValue value,
144-
GroupSettingType type, String name);
143+
///
144+
/// If the server doesn't know about the permission,
145+
/// pass null for [value] and a reasonable default will be chosen.
146+
bool selfHasPermissionForGroupSetting(GroupSettingValue? value,
147+
GroupSettingType type, String name);
145148
}
146149

147150
enum GroupSettingType { realm, stream, group }
@@ -194,7 +197,7 @@ mixin ProxyRealmStore on RealmStore {
194197
bool selfHasPassedWaitingPeriod({required DateTime byDate}) =>
195198
realmStore.selfHasPassedWaitingPeriod(byDate: byDate);
196199
@override
197-
bool selfHasPermissionForGroupSetting(GroupSettingValue value, GroupSettingType type, String name) =>
200+
bool selfHasPermissionForGroupSetting(GroupSettingValue? value, GroupSettingType type, String name) =>
198201
realmStore.selfHasPermissionForGroupSetting(value, type, name);
199202
}
200203

@@ -256,7 +259,7 @@ class RealmStoreImpl extends HasUserGroupStore with RealmStore {
256259
}
257260

258261
@override
259-
bool selfHasPermissionForGroupSetting(GroupSettingValue value,
262+
bool selfHasPermissionForGroupSetting(GroupSettingValue? value,
260263
GroupSettingType type, String name) {
261264
// Compare web's settings_data.user_has_permission_for_group_setting.
262265
//
@@ -267,13 +270,60 @@ class RealmStoreImpl extends HasUserGroupStore with RealmStore {
267270
// That exists for deciding whether to offer the "Generate email address"
268271
// button, and if so then which users to offer in the dropdown;
269272
// it's predicting whether /api/get-stream-email-address would succeed.
270-
if (_selfUserRole == UserRole.guest) {
271-
final config = _groupSettingConfig(type, name);
272-
if (!config.allowEveryoneGroup) return false;
273+
274+
final config = _groupSettingConfig(type, name);
275+
276+
if (_selfUserRole == UserRole.guest && !config.allowEveryoneGroup) {
277+
return false;
278+
}
279+
280+
if (value == null) {
281+
// The server doesn't know about the permission. *We* know about it
282+
// (or presumably we wouldn't have called this method),
283+
// and we know a reasonable default; use that.
284+
return _hasPermissionByDefault(config);
273285
}
286+
274287
return selfInGroupSetting(value);
275288
}
276289

290+
bool _hasPermissionByDefault(PermissionSettingsItem config) {
291+
switch (config.defaultGroupName) {
292+
case DefaultGroupNameUnknown():
293+
// When we know about a permission, we should also know about the group
294+
// we've said is the default value for it.
295+
assert(false);
296+
return true;
297+
case PseudoSystemGroupName.streamCreatorOrNobody:
298+
// TODO(#1102) implement
299+
assert(() {
300+
throw UnimplementedError();
301+
}());
302+
return true;
303+
case SystemGroupName.everyoneOnInternet:
304+
case SystemGroupName.everyone:
305+
return true;
306+
case SystemGroupName.members:
307+
return _selfUserRole.isAtLeast(UserRole.member);
308+
case SystemGroupName.fullMembers:
309+
// There aren't any permissions where this is the default, and we
310+
// probably won't add any. So for now we skip the complication of
311+
// doing the waiting-period check.
312+
assert(() {
313+
throw UnimplementedError();
314+
}());
315+
return _selfUserRole.isAtLeast(UserRole.member);
316+
case SystemGroupName.moderators:
317+
return _selfUserRole.isAtLeast(UserRole.moderator);
318+
case SystemGroupName.administrators:
319+
return _selfUserRole.isAtLeast(UserRole.administrator);
320+
case SystemGroupName.owners:
321+
return _selfUserRole.isAtLeast(UserRole.owner);
322+
case SystemGroupName.nobody:
323+
return false;
324+
}
325+
}
326+
277327
/// The metadata for how to interpret the given group-based permission setting.
278328
PermissionSettingsItem _groupSettingConfig(GroupSettingType type, String name) {
279329
final supportedSettings = SupportedPermissionSettings.fixture;

test/model/realm_test.dart

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import 'package:checks/checks.dart';
22
import 'package:test/scaffolding.dart';
33
import 'package:zulip/api/model/events.dart';
44
import 'package:zulip/api/model/model.dart';
5+
import 'package:zulip/api/model/permission.dart';
56
import 'package:zulip/model/realm.dart';
7+
import 'package:zulip/model/store.dart';
68

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

@@ -108,6 +110,93 @@ void main() {
108110
check(hasPermission(selfUser, group, 'can_send_message_group'))
109111
.isFalse();
110112
});
113+
114+
group('fallbacks for permissions not known to the server', () {
115+
late PerAccountStore store;
116+
117+
void prepare({UserRole? selfUserRole}) {
118+
final selfUser = eg.user(role: selfUserRole);
119+
store = eg.store(selfUser: selfUser,
120+
initialSnapshot: eg.initialSnapshot(realmUsers: [selfUser]));
121+
}
122+
123+
void doCheck(GroupSettingType type, String name, bool expected) {
124+
check(store.selfHasPermissionForGroupSetting(null, type, name)).equals(expected);
125+
}
126+
127+
for (final pseudoSystemGroupName in PseudoSystemGroupName.values) {
128+
switch (pseudoSystemGroupName) {
129+
case PseudoSystemGroupName.streamCreatorOrNobody:
130+
// TODO implement and test
131+
}
132+
}
133+
134+
for (final systemGroupName in SystemGroupName.values) {
135+
switch (systemGroupName) {
136+
case SystemGroupName.everyoneOnInternet:
137+
// (No permissions where we use this default value; continue.)
138+
break;
139+
case SystemGroupName.everyone:
140+
test('everyone', () {
141+
prepare(selfUserRole: UserRole.guest);
142+
doCheck(GroupSettingType.realm, 'can_access_all_users_group', true);
143+
});
144+
case SystemGroupName.members:
145+
test('members, is guest', () {
146+
prepare(selfUserRole: UserRole.guest);
147+
doCheck(GroupSettingType.realm, 'can_add_custom_emoji_group', false);
148+
});
149+
test('members, is member', () {
150+
prepare(selfUserRole: UserRole.member);
151+
doCheck(GroupSettingType.realm, 'can_add_custom_emoji_group', true);
152+
});
153+
case SystemGroupName.fullMembers:
154+
// (No permissions where we use this default value; continue.)
155+
break;
156+
case SystemGroupName.moderators:
157+
test('moderators, is member', () {
158+
prepare(selfUserRole: UserRole.member);
159+
doCheck(GroupSettingType.realm, 'can_set_delete_message_policy_group', false);
160+
});
161+
test('moderators, is moderator', () {
162+
prepare(selfUserRole: UserRole.moderator);
163+
doCheck(GroupSettingType.realm, 'can_set_delete_message_policy_group', true);
164+
});
165+
case SystemGroupName.administrators:
166+
test('administrators, is moderator', () {
167+
prepare(selfUserRole: UserRole.moderator);
168+
doCheck(GroupSettingType.stream, 'can_remove_subscribers_group', false);
169+
});
170+
test('administrators, is administrator', () {
171+
prepare(selfUserRole: UserRole.administrator);
172+
doCheck(GroupSettingType.stream, 'can_remove_subscribers_group', true);
173+
});
174+
case SystemGroupName.owners:
175+
test('owners, is administrator', () {
176+
prepare(selfUserRole: UserRole.administrator);
177+
doCheck(GroupSettingType.realm, 'can_create_web_public_channel_group', false);
178+
});
179+
test('owners, is owner', () {
180+
prepare(selfUserRole: UserRole.owner);
181+
doCheck(GroupSettingType.realm, 'can_create_web_public_channel_group', true);
182+
});
183+
case SystemGroupName.nobody:
184+
test('nobody', () {
185+
prepare(selfUserRole: UserRole.owner);
186+
doCheck(GroupSettingType.stream, 'can_delete_own_message_group', false);
187+
});
188+
}
189+
}
190+
191+
test('throw on unknown name', () {
192+
// We should know about all the permissions we're trying to implement,
193+
// even the ones old servers don't know about.
194+
prepare(selfUserRole: UserRole.member);
195+
check(() => store.selfHasPermissionForGroupSetting(null,
196+
GroupSettingType.realm, 'example_future_permission_name'),
197+
).throws<Error>();
198+
});
199+
});
111200
});
112201

113202
group('customProfileFields', () {

0 commit comments

Comments
 (0)