Repository: zulip/zulip

Issue:
Deactivating a web-public stream should un-mark all attachments as web-public.
If a web-public stream is deactivated, it leaves all of the URLs of attachments which were sent to that stream still web-public accessible.  Ideally we would reuse `do_change_stream_permission` in the deactivation process to unset this cached property, except that this creates audit log entries that we don't want, and also events (`send_change_stream_permission_notification`).

The right fix is likely to:
```
Attachment.objects.filter(messages__recipient_id=stream.recipient_id).update(
    is_realm_public=None
)
ArchivedAttachment.objects.filter(messages__recipient_id=stream.recipient_id).update(
    is_realm_public=None
)
```
...with a comment there and `do_change_stream_permission` about the duplicated functionality.  `do_unarchive_stream` is already doing step, and already has a reference to why it does not use `do_change_stream_permission` in it.

**Zulip Server and web app version:**

- [x] Zulip Cloud (`*.zulipchat.com`)
- [x] Zulip Server 7.0+
- [ ] Zulip Server 6.0+
- [ ] Zulip Server 5.0 or older
- [ ] Other or not sure



Discussion:
@sahil839 do you have time to pick this one up?
Hello @zulip/server-streams members, this issue was labeled with the "area: stream settings" label, so you may want to check it out!

<!-- areaLabelAddition -->

Yeah, I can work on this.

Resolving patch:
diff --git a/zerver/actions/streams.py b/zerver/actions/streams.py
--- a/zerver/actions/streams.py
+++ b/zerver/actions/streams.py
@@ -93,6 +93,13 @@ def do_deactivate_stream(stream: Stream, *, acting_user: Optional[UserProfile])
     )
 
     was_invite_only = stream.invite_only
+    was_public = stream.is_public()
+    was_web_public = stream.is_web_public
+
+    # We do not use do_change_stream_permission because no users need to
+    # be notified, and we do not want to create audit log entries for
+    # changing stream privacy. And due to this we also need to duplicate
+    # the code to unset is_web_public field on attachments below.
     stream.deactivated = True
     stream.invite_only = True
     # Preserve as much as possible the original stream name while giving it a
@@ -110,6 +117,26 @@ def do_deactivate_stream(stream: Stream, *, acting_user: Optional[UserProfile])
     stream.name = new_name[: Stream.MAX_NAME_LENGTH]
     stream.save(update_fields=["name", "deactivated", "invite_only"])
 
+    assert stream.recipient_id is not None
+    if was_web_public:
+        assert was_public
+        # Unset the is_web_public and is_realm_public cache on attachments,
+        # since the stream is now private.
+        Attachment.objects.filter(messages__recipient_id=stream.recipient_id).update(
+            is_web_public=None, is_realm_public=None
+        )
+        ArchivedAttachment.objects.filter(messages__recipient_id=stream.recipient_id).update(
+            is_web_public=None, is_realm_public=None
+        )
+    elif was_public:
+        # Unset the is_realm_public cache on attachments, since the stream is now private.
+        Attachment.objects.filter(messages__recipient_id=stream.recipient_id).update(
+            is_realm_public=None
+        )
+        ArchivedAttachment.objects.filter(messages__recipient_id=stream.recipient_id).update(
+            is_realm_public=None
+        )
+
     # If this is a default stream, remove it, properly sending a
     # notification to browser clients.
     if DefaultStream.objects.filter(realm_id=stream.realm_id, stream_id=stream.id).exists():
@@ -201,10 +228,13 @@ def do_unarchive_stream(
     ).only("id")
     cache_delete_many(to_dict_cache_key_id(message.id) for message in messages)
 
-    # Unset the is_web_public cache on attachments, since the stream is now private.
-    Attachment.objects.filter(messages__recipient_id=stream.recipient_id).update(is_web_public=None)
+    # Unset the is_web_public and is_realm_public cache on attachments,
+    # since the stream is now private.
+    Attachment.objects.filter(messages__recipient_id=stream.recipient_id).update(
+        is_web_public=None, is_realm_public=None
+    )
     ArchivedAttachment.objects.filter(messages__recipient_id=stream.recipient_id).update(
-        is_web_public=None
+        is_web_public=None, is_realm_public=None
     )
 
     RealmAuditLog.objects.create(


Test patch: