Skip to content

Conversation

chrisbobbe
Copy link
Collaborator

Fixes #1850.

Also does the refactor Greg suggested here: #1842 (comment)

@chrisbobbe chrisbobbe added the maintainer review PR ready for review by Zulip maintainers label Sep 16, 2025
@chrisbobbe chrisbobbe force-pushed the pr-centralize-group-permission-defaults branch from b6fa709 to 2f0956d Compare September 17, 2025 01:31
Comment on lines 719 to 726
/// A known special string
/// that [PermissionSettingsItem.defaultGroupName] might be.
///
/// See server implementation, e.g.
/// `can_administer_channel_group` in zerver/models/streams.py.
@JsonEnum(valueField: 'apiValue', alwaysCreate: true)
enum PseudoSystemGroupName implements SettableAsDefaultGroupName {
streamCreatorOrNobody(apiValue: 'stream_creator_or_nobody'),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On CZO I proposed documenting this: #api design > stream_creator_or_nobody @ 💬

From that discussion, it looks like it might also get renamed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Let's include a link to that thread in a comment, too.

Copy link
Member

@rajveermalviya rajveermalviya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this @chrisbobbe! LGTM, moving over to Greg's review.

@rajveermalviya rajveermalviya added integration review Added by maintainers when PR may be ready for integration and removed maintainer review PR ready for review by Zulip maintainers labels Sep 18, 2025
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for building this! Comments below.

Comment on lines 752 to 754
enum SystemGroupName implements SettableAsDefaultGroupName {
everyoneOnInternet(apiValue: 'role:internet'),
everyone(apiValue: 'role:everyone'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like it's not specifically about the initial snapshot — it'll eventually be relevant to other parts of the API too. It could go in model.dart… but perhaps better:

Let's have a prep commit move SupportedPermissionSettings and PermissionSettingsItem to their own file, say permission.dart. Then these new types can go there too.

Comment on lines 692 to 700
sealed class SettableAsDefaultGroupName {
String toJson();
}

class DefaultGroupNameConverter extends JsonConverter<SettableAsDefaultGroupName, String> {
const DefaultGroupNameConverter();

@override
SettableAsDefaultGroupName fromJson(String json) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this converter be merged into the class? Seems like any time we'd want this class, we'd want this same conversion, right?

(The fromJson method then becomes a factory constructor on that class. See other non-generated fromJson implementations, like on GroupSettingValue.)

Comment on lines 686 to 692
/// A value of [PermissionSettingsItem.defaultGroupName].
///
/// Can be any of these:
/// - a known system group [SystemGroupName]
/// - a known special string [PseudoSystemGroupName]
/// - an unknown system group or special string [DefaultGroupNameUnknown]
sealed class SettableAsDefaultGroupName {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some information that the "settable as" part of the name is meant to convey here? How does the expressed meaning differ from if we called it DefaultGroupName?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah nope 🙂 I think I'd had the idea that it should be considered an interface rather than a base class, but I don't really remember why, and I don't see anything wrong with DefaultGroupName when I look at it again :)

Comment on lines 719 to 726
/// A known special string
/// that [PermissionSettingsItem.defaultGroupName] might be.
///
/// See server implementation, e.g.
/// `can_administer_channel_group` in zerver/models/streams.py.
@JsonEnum(valueField: 'apiValue', alwaysCreate: true)
enum PseudoSystemGroupName implements SettableAsDefaultGroupName {
streamCreatorOrNobody(apiValue: 'stream_creator_or_nobody'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Let's include a link to that thread in a comment, too.

Comment on lines 257 to 308
switch (config.defaultGroupName) {
case DefaultGroupNameUnknown():
// When we know about a permission, we should also know about the group
// we've said is the default value for it.
assert(false);
return true;
case PseudoSystemGroupName.streamCreatorOrNobody:
// TODO implement
return true;
case SystemGroupName.everyoneOnInternet:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's move all this logic to its own method, even if it's a private method located in this same place.

That way selfHasPermissionForGroupSetting gets to stay focused on the main logic it's about, rather than these details which are for a fallback case. As a bonus, this case can go into an early return, so that the method ends with the call to selfInGroupSetting which is the thrust of the method's main narrative.

Comment on lines 263 to 307
case PseudoSystemGroupName.streamCreatorOrNobody:
// TODO implement
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm — this is a concerning-looking type of TODO comment, because it feels like it's not something we'd necessarily see when the time comes that we need to carry it out.

Is there an issue for a feature where we'd expect to start hitting this case?

Oh also: these defaults come from our code and not the server, so it would take a change to our code for us to start hitting this. Let's add an assert — that should catch any such change while in dev, to remind us this isn't yet implemented.

Copy link
Collaborator Author

@chrisbobbe chrisbobbe Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points, yep—

Is there an issue for a feature where we'd expect to start hitting this case?

#1102

Comment on lines 271 to 274
case SystemGroupName.fullMembers:
// TODO check waiting period (nontrivial to get access to that logic
// here in RealmStore; it's on UserStore, which depends on RealmStore)
return _selfUserRole.isAtLeast(UserRole.member);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. That's the hasPassedWaitingPeriod method, right?

We can just move that to the RealmStore mixin instead of UserStore — it's not using anything from the user store itself, just a User object. Can include a comment justifying that placement by saying it's needed for permissions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I can do that. RealmStore doesn't have access to the self-user User object, and it would feel odd to ask selfHasPermissionForGroupSetting's callers to provide it. But the passed-waiting-period method just uses .dateJoined from the user, and we can store that in RealmStoreImpl as ._selfUserDateJoined, the way we already have ._selfUserRole there, if that makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm true. But yeah, that solution works. The implementation will then live on RealmStoreImpl rather than on the mixin, which is fine.

Comment on lines +266 to +310
case SystemGroupName.everyoneOnInternet:
case SystemGroupName.everyone:
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have an issue for spectator view? If not, probably good to file one for tracking. (Can be terse and dashed off — it'll be MXB.) Then the everyoneOnInternet line here can get a TODO line pointing to that… or perhaps better, have such a comment at the definition of that enum value, saying to audit all its references.

Comment on lines 114 to 147
case SystemGroupName.everyone:
test('everyone', () {
prepare();
doCheck(GroupSettingType.realm, 'can_access_all_users_group', true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: strengthen the test

Suggested change
case SystemGroupName.everyone:
test('everyone', () {
prepare();
doCheck(GroupSettingType.realm, 'can_access_all_users_group', true);
case SystemGroupName.everyone:
test('everyone', () {
prepare(selfUserRole: UserRole.guest);
doCheck(GroupSettingType.realm, 'can_access_all_users_group', true);

and similarly .owner for the .nobody case at the end

And we'll use the new selfUserRole param for testing zulip#1850, coming
up.
The "role:nobody" group is the default value for both of these group
settings (see defaultGroupName in upcoming commits), so it's helpful
to be explicit here.
This doesn't change our parsing of server data because we don't
currently look at server_supported_permission_settings in the
initial snapshot, as noted in the added TODO. It just adds data to
the fixture, modeled on current server code.
This will support full-member permissions checks in the logic we're
about to add, for defaults when a server doesn't know about a given
permission. There aren't currently any defaults with the
"role:fullmembers" value, so this won't control any behavior now.
But some might be added in the future, and we might as well include
it for completeness.
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.
@chrisbobbe chrisbobbe force-pushed the pr-centralize-group-permission-defaults branch from 2f0956d to 8e1932d Compare September 19, 2025 00:13
@chrisbobbe
Copy link
Collaborator Author

Thanks for the review! Revision pushed.

required int channelId,
bool showTopicListButton = true,
}) {
final now = DateTime.now();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I think I should change this to ZulipBinding.instance.utcNow(); will do that in my next revision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integration review Added by maintainers when PR may be ready for integration
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make role:administrators the default for realm_can_delete_any_message_group
3 participants