Repository: zulip/zulip

Issue:
Customize permissions for new organizations by organization type
At present, all new organizations have the same default settings. These settings may not be appropriate for all types of organizations. Since we now collect organization type at sign-up for Zulip Cloud organizations, we should make use of that information to set better defaults.

In particular, education organizations likely wish to restrict many administrative permissions to course staff. The new defaults should be the following.

Org types:
* Education (for-profit)
* Education (non-profit)

Default permissions different from current default:
- Who can invite new users. (New default: Admins)
- Who can access user email addresses. (New default: Admins only)
- Who can create streams. (New default: Admins for public streams, members for private streams)
- Who can add users to streams. (New default: Admins and moderators)
- Who can move messages between streams. (New default: Admins and moderators)
- Who can create and manage user groups. (New default: Admins and moderators)


Discussion:
Hello @zulip/server-settings members, this issue was labeled with the "area: settings (admin/org)" label, so you may want to check it out!

<!-- areaLabelAddition -->

@alya   I would like to work on this issue, I was just having a couple of doubts reagarding this issue,
- changes regarding this has to be changes in backend model where we have to set defaults ?
- do `Admins` and `Admins only` means the same ??
can i work on this ?? 
I believe @eeshangarg has started work on this. @eeshangarg is that right? If you are not too far along, perhaps you could guide @madrix01  on this.
> do Admins and Admins only means the same ??

Yes; for whatever reason, we use one or the other for different settings.
@alya I haven't started yet. @madrix01 Thanks for offering to work on this! Please feel free to reach out on czo if you have any questions!

I would recommend by starting to track the code for when you go through the  `localhost:9991/new` flow. Going through the code for how a realm is created should help in understanding how everything comes together. A couple of starting points:
- Look at `do_create_realm` in `zerver/lib/actions.py`.
- `Realm` in `zerver/models.py` should have "policy types" such as `POLICY_ADMINS_ONLY` that represent permissions. The defaults are set in the `Realm` model itself.
- My guess is we could set custom permissions on realm creation based on the org type in `do_create_realm` or one of the functions it calls.

Thanks!

@eeshangarg thanks for helping me on this issue !!
I went through the `do_create_realm` method and also through `Realm` in  `models.py`. I think the approach you gave is plausible.
I was having the doubt that when I create new realm I'm getting [this error](https://chat.zulip.org/#narrow/stream/49-development-help/topic/unable.20to.20create.20new.20realm.20in.20dev.20env/near/1254875)
And how do I check the Policies for the new realm created.
@zulipbot claim

Resolving patch:
diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py
--- a/zerver/lib/actions.py
+++ b/zerver/lib/actions.py
@@ -4991,6 +4991,36 @@ def do_change_stream_message_retention_days(
     send_event(stream.realm, event, can_access_stream_user_ids(stream))
 
 
+def set_realm_permissions_based_on_org_type(realm: Realm) -> None:
+    """This function implements overrides for the default configuration
+    for new organizations when the administrator selected specific
+    organization types.
+
+    This substantially simplifies our /help/ advice for folks setting
+    up new organizations of these types.
+    """
+
+    # Custom configuration for educational organizations.  The present
+    # defaults are designed for a single class, not a department or
+    # larger institution, since those are more common.
+    if (
+        realm.org_type == Realm.ORG_TYPES["education_nonprofit"]["id"]
+        or realm.org_type == Realm.ORG_TYPES["education"]["id"]
+    ):
+        # Limit email address visibility and user creation to administrators.
+        realm.email_address_visibility = Realm.EMAIL_ADDRESS_VISIBILITY_ADMINS
+        realm.invite_to_realm_policy = Realm.POLICY_ADMINS_ONLY
+        # Restrict public stream creation to staff, but allow private
+        # streams (useful for study groups, etc.).
+        realm.create_public_stream_policy = Realm.POLICY_ADMINS_ONLY
+        # Don't allow members (students) to manage user groups or
+        # stream subscriptions.
+        realm.user_group_edit_policy = Realm.POLICY_MODERATORS_ONLY
+        realm.invite_to_stream_policy = Realm.POLICY_MODERATORS_ONLY
+        # Allow moderators (TAs?) to move topics between streams.
+        realm.move_messages_between_streams_policy = Realm.POLICY_MODERATORS_ONLY
+
+
 def do_create_realm(
     string_id: str,
     name: str,
@@ -5038,6 +5068,8 @@ def do_create_realm(
             realm.demo_organization_scheduled_deletion_date = (
                 realm.date_created + datetime.timedelta(days=settings.DEMO_ORG_DEADLINE_DAYS)
             )
+
+        set_realm_permissions_based_on_org_type(realm)
         realm.save()
 
         RealmAuditLog.objects.create(


Test patch:
diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py
--- a/zerver/tests/test_realm.py
+++ b/zerver/tests/test_realm.py
@@ -64,6 +64,34 @@ def test_realm_creation_on_social_auth_subdomain_disallowed(self) -> None:
             with self.assertRaises(AssertionError):
                 do_create_realm("zulipauth", "Test Realm")
 
+    def test_permission_for_education_non_profit_organization(self) -> None:
+        realm = do_create_realm(
+            "test_education_non_profit",
+            "education_org_name",
+            org_type=Realm.ORG_TYPES["education_nonprofit"]["id"],
+        )
+
+        self.assertEqual(realm.create_public_stream_policy, Realm.POLICY_ADMINS_ONLY)
+        self.assertEqual(realm.create_private_stream_policy, Realm.POLICY_MEMBERS_ONLY)
+        self.assertEqual(realm.invite_to_realm_policy, Realm.POLICY_ADMINS_ONLY)
+        self.assertEqual(realm.move_messages_between_streams_policy, Realm.POLICY_MODERATORS_ONLY)
+        self.assertEqual(realm.user_group_edit_policy, Realm.POLICY_MODERATORS_ONLY)
+        self.assertEqual(realm.invite_to_stream_policy, Realm.POLICY_MODERATORS_ONLY)
+
+    def test_permission_for_education_for_profit_organization(self) -> None:
+        realm = do_create_realm(
+            "test_education_for_profit",
+            "education_org_name",
+            org_type=Realm.ORG_TYPES["education"]["id"],
+        )
+
+        self.assertEqual(realm.create_public_stream_policy, Realm.POLICY_ADMINS_ONLY)
+        self.assertEqual(realm.create_private_stream_policy, Realm.POLICY_MEMBERS_ONLY)
+        self.assertEqual(realm.invite_to_realm_policy, Realm.POLICY_ADMINS_ONLY)
+        self.assertEqual(realm.move_messages_between_streams_policy, Realm.POLICY_MODERATORS_ONLY)
+        self.assertEqual(realm.user_group_edit_policy, Realm.POLICY_MODERATORS_ONLY)
+        self.assertEqual(realm.invite_to_stream_policy, Realm.POLICY_MODERATORS_ONLY)
+
     def test_do_set_realm_name_caching(self) -> None:
         """The main complicated thing about setting realm names is fighting the
         cache, and we start by populating the cache for Hamlet, and we end