from enum import IntEnum, unique from django.core.serializers.json import DjangoJSONEncoder from django.db import models from django.db.models import CASCADE, Q from typing_extensions import override from zerver.models.groups import NamedUserGroup from zerver.models.realms import Realm from zerver.models.streams import Stream from zerver.models.users import UserProfile @unique class AuditLogEventType(IntEnum): USER_CREATED = 101 USER_ACTIVATED = 102 USER_DEACTIVATED = 103 USER_REACTIVATED = 104 USER_ROLE_CHANGED = 105 USER_DELETED = 106 USER_DELETED_PRESERVING_MESSAGES = 107 USER_SPECIAL_PERMISSION_CHANGED = 108 USER_SOFT_ACTIVATED = 120 USER_SOFT_DEACTIVATED = 121 USER_PASSWORD_CHANGED = 122 USER_AVATAR_SOURCE_CHANGED = 123 USER_FULL_NAME_CHANGED = 124 USER_EMAIL_CHANGED = 125 USER_TERMS_OF_SERVICE_VERSION_CHANGED = 126 USER_API_KEY_CHANGED = 127 USER_BOT_OWNER_CHANGED = 128 USER_DEFAULT_SENDING_STREAM_CHANGED = 129 USER_DEFAULT_REGISTER_STREAM_CHANGED = 130 USER_DEFAULT_ALL_PUBLIC_STREAMS_CHANGED = 131 USER_SETTING_CHANGED = 132 USER_DIGEST_EMAIL_CREATED = 133 REALM_DEACTIVATED = 201 REALM_REACTIVATED = 202 REALM_SCRUBBED = 203 REALM_PLAN_TYPE_CHANGED = 204 REALM_LOGO_CHANGED = 205 REALM_EXPORTED = 206 REALM_PROPERTY_CHANGED = 207 REALM_ICON_SOURCE_CHANGED = 208 REALM_DISCOUNT_CHANGED = 209 REALM_SPONSORSHIP_APPROVED = 210 REALM_BILLING_MODALITY_CHANGED = 211 REALM_REACTIVATION_EMAIL_SENT = 212 REALM_SPONSORSHIP_PENDING_STATUS_CHANGED = 213 REALM_SUBDOMAIN_CHANGED = 214 REALM_CREATED = 215 REALM_DEFAULT_USER_SETTINGS_CHANGED = 216 REALM_ORG_TYPE_CHANGED = 217 REALM_DOMAIN_ADDED = 218 REALM_DOMAIN_CHANGED = 219 REALM_DOMAIN_REMOVED = 220 REALM_PLAYGROUND_ADDED = 221 REALM_PLAYGROUND_REMOVED = 222 REALM_LINKIFIER_ADDED = 223 REALM_LINKIFIER_CHANGED = 224 REALM_LINKIFIER_REMOVED = 225 REALM_EMOJI_ADDED = 226 REALM_EMOJI_REMOVED = 227 REALM_LINKIFIERS_REORDERED = 228 REALM_IMPORTED = 229 REALM_EXPORT_DELETED = 230 SUBSCRIPTION_CREATED = 301 SUBSCRIPTION_ACTIVATED = 302 SUBSCRIPTION_DEACTIVATED = 303 SUBSCRIPTION_PROPERTY_CHANGED = 304 USER_MUTED = 350 USER_UNMUTED = 351 STRIPE_CUSTOMER_CREATED = 401 STRIPE_CARD_CHANGED = 402 STRIPE_PLAN_CHANGED = 403 STRIPE_PLAN_QUANTITY_RESET = 404 CUSTOMER_CREATED = 501 CUSTOMER_PLAN_CREATED = 502 CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN = 503 CUSTOMER_SWITCHED_FROM_ANNUAL_TO_MONTHLY_PLAN = 504 CUSTOMER_PROPERTY_CHANGED = 505 CUSTOMER_PLAN_PROPERTY_CHANGED = 506 CHANNEL_CREATED = 601 CHANNEL_DEACTIVATED = 602 CHANNEL_NAME_CHANGED = 603 CHANNEL_REACTIVATED = 604 CHANNEL_MESSAGE_RETENTION_DAYS_CHANGED = 605 CHANNEL_PROPERTY_CHANGED = 607 CHANNEL_GROUP_BASED_SETTING_CHANGED = 608 USER_GROUP_CREATED = 701 USER_GROUP_DELETED = 702 USER_GROUP_DIRECT_USER_MEMBERSHIP_ADDED = 703 USER_GROUP_DIRECT_USER_MEMBERSHIP_REMOVED = 704 USER_GROUP_DIRECT_SUBGROUP_MEMBERSHIP_ADDED = 705 USER_GROUP_DIRECT_SUBGROUP_MEMBERSHIP_REMOVED = 706 USER_GROUP_DIRECT_SUPERGROUP_MEMBERSHIP_ADDED = 707 USER_GROUP_DIRECT_SUPERGROUP_MEMBERSHIP_REMOVED = 708 # 709 to 719 reserved for membership changes USER_GROUP_NAME_CHANGED = 720 USER_GROUP_DESCRIPTION_CHANGED = 721 USER_GROUP_GROUP_BASED_SETTING_CHANGED = 722 USER_GROUP_DEACTIVATED = 723 SAVED_SNIPPET_CREATED = 800 # The following values are only for remote server/realm logs. # Values should be exactly 10000 greater than the corresponding # value used for the same purpose in realm audit logs (e.g., # REALM_DEACTIVATED = 201, and REMOTE_SERVER_DEACTIVATED = 10201). REMOTE_SERVER_DEACTIVATED = 10201 REMOTE_SERVER_REACTIVATED = 10202 REMOTE_SERVER_PLAN_TYPE_CHANGED = 10204 REMOTE_SERVER_DISCOUNT_CHANGED = 10209 REMOTE_SERVER_SPONSORSHIP_APPROVED = 10210 REMOTE_SERVER_BILLING_MODALITY_CHANGED = 10211 REMOTE_SERVER_SPONSORSHIP_PENDING_STATUS_CHANGED = 10213 REMOTE_SERVER_CREATED = 10215 # This value is for RemoteRealmAuditLog entries tracking changes to the # RemoteRealm model resulting from modified realm information sent to us # via send_server_data_to_push_bouncer. REMOTE_REALM_VALUE_UPDATED = 20001 REMOTE_PLAN_TRANSFERRED_SERVER_TO_REALM = 20002 REMOTE_REALM_LOCALLY_DELETED = 20003 REMOTE_REALM_LOCALLY_DELETED_RESTORED = 20004 class AbstractRealmAuditLog(models.Model): """Defines fields common to RealmAuditLog and RemoteRealmAuditLog.""" event_time = models.DateTimeField(db_index=True) # If True, event_time is an overestimate of the true time. Can be used # by migrations when introducing a new event_type. backfilled = models.BooleanField(default=False) # Keys within extra_data, when extra_data is a json dict. Keys are strings because # json keys must always be strings. OLD_VALUE = "1" NEW_VALUE = "2" ROLE_COUNT = "10" ROLE_COUNT_HUMANS = "11" ROLE_COUNT_BOTS = "12" extra_data = models.JSONField(default=dict, encoder=DjangoJSONEncoder) # See AuditLogEventType class above. event_type = models.PositiveSmallIntegerField() # event_types synced from on-prem installations to Zulip Cloud when # billing for mobile push notifications is enabled. Every billing # event_type should have ROLE_COUNT populated in extra_data. SYNCED_BILLING_EVENTS = [ AuditLogEventType.USER_CREATED, AuditLogEventType.USER_ACTIVATED, AuditLogEventType.USER_DEACTIVATED, AuditLogEventType.USER_REACTIVATED, AuditLogEventType.USER_ROLE_CHANGED, AuditLogEventType.REALM_DEACTIVATED, AuditLogEventType.REALM_REACTIVATED, AuditLogEventType.REALM_IMPORTED, ] HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS = { "existing_user": "At an organization that's using it", "search_engine": "Search engine", "review_site": "Review site", "personal_recommendation": "Personal recommendation", "hacker_news": "Hacker News", "reddit": "Reddit", "ad": "Advertisement", "other": "Other", "forgot": "Don't remember", "refuse_to_answer": "Prefer not to say", } class Meta: abstract = True class RealmAuditLog(AbstractRealmAuditLog): """ RealmAuditLog tracks important changes to users, streams, and realms in Zulip. It is intended to support both debugging/introspection (e.g. determining when a user's left a given stream?) as well as help with some database migrations where we might be able to do a better data backfill with it. Here are a few key details about how this works: * acting_user is the user who initiated the state change * modified_user (if present) is the user being modified * modified_stream (if present) is the stream being modified * modified_user_group (if present) is the user group being modified For example: * When a user subscribes another user to a stream, modified_user, acting_user, and modified_stream will all be present and different. * When an administrator changes an organization's realm icon, acting_user is that administrator and modified_user, modified_stream and modified_user_group will be None. """ realm = models.ForeignKey(Realm, on_delete=CASCADE) acting_user = models.ForeignKey( UserProfile, null=True, related_name="+", on_delete=CASCADE, ) modified_user = models.ForeignKey( UserProfile, null=True, related_name="+", on_delete=CASCADE, ) modified_stream = models.ForeignKey( Stream, null=True, on_delete=CASCADE, ) modified_user_group = models.ForeignKey( NamedUserGroup, null=True, on_delete=CASCADE, ) event_last_message_id = models.IntegerField(null=True) class Meta: ordering = ["id"] indexes = [ models.Index( name="zerver_realmauditlog_realm__event_type__event_time", fields=["realm", "event_type", "event_time"], ), models.Index( name="zerver_realmauditlog_user_subscriptions_idx", fields=["modified_user", "modified_stream"], condition=Q( event_type__in=[ AuditLogEventType.SUBSCRIPTION_CREATED, AuditLogEventType.SUBSCRIPTION_ACTIVATED, AuditLogEventType.SUBSCRIPTION_DEACTIVATED, ] ), ), models.Index( # Used in analytics/lib/counts.py for computing active users for realm_active_humans name="zerver_realmauditlog_user_activations_idx", fields=["modified_user", "event_time"], condition=Q( event_type__in=[ AuditLogEventType.USER_CREATED, AuditLogEventType.USER_ACTIVATED, AuditLogEventType.USER_DEACTIVATED, AuditLogEventType.USER_REACTIVATED, ] ), ), ] @override def __str__(self) -> str: event_type_name = AuditLogEventType(self.event_type).name if self.modified_user is not None: return f"{event_type_name} {self.event_time} (id={self.id}): {self.modified_user!r}" if self.modified_stream is not None: return f"{event_type_name} {self.event_time} (id={self.id}): {self.modified_stream!r}" if self.modified_user_group is not None: return ( f"{event_type_name} {self.event_time} (id={self.id}): {self.modified_user_group!r}" ) return f"{event_type_name} {self.event_time} (id={self.id}): {self.realm!r}"