Add RealmAuditLog table and record user activation/deactivation events.

The RealmAuditLog will make it easier for server admins to replay history.
This commit is contained in:
Rishi Gupta 2017-02-14 19:35:10 -08:00 committed by Tim Abbott
parent 3fdad8b64a
commit 51b7677db7
6 changed files with 145 additions and 14 deletions

View File

@ -11,7 +11,8 @@ from analytics.lib.time_utils import time_range
from analytics.models import BaseCount, InstallationCount, RealmCount, \
UserCount, StreamCount, FillState
from zerver.lib.timestamp import floor_to_day
from zerver.models import Realm, UserProfile, Stream, Message, Client
from zerver.models import Realm, UserProfile, Stream, Message, Client, \
RealmAuditLog
from datetime import datetime, timedelta
@ -26,10 +27,14 @@ class Command(BaseCommand):
def create_user(self, email, full_name, is_staff, date_joined, realm):
# type: (Text, Text, Text, bool, datetime, Realm) -> UserProfile
return UserProfile.objects.create(
user = UserProfile.objects.create(
email=email, full_name=full_name, is_staff=is_staff,
realm=realm, short_name=full_name, pointer=-1, last_pointer_updater='none',
api_key='42', date_joined=date_joined)
RealmAuditLog.objects.create(
realm=realm, modified_user=user, event_type='user_created',
event_time=user.date_joined)
return user
def generate_fixture_data(self, stat, business_hours_base, non_business_hours_base,
growth, autocorrelation, spikiness, holiday_rate=0):

View File

@ -27,7 +27,7 @@ from zerver.lib.message import (
)
from zerver.lib.realm_icon import realm_icon_url
from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity, RealmAlias, \
Subscription, Recipient, Message, Attachment, UserMessage, \
Subscription, Recipient, Message, Attachment, UserMessage, RealmAuditLog, \
Client, DefaultStream, UserPresence, Referral, PushDeviceToken, MAX_SUBJECT_LENGTH, \
MAX_MESSAGE_LENGTH, get_client, get_stream, get_recipient, get_huddle, \
get_user_profile_by_id, PreregistrationUser, get_display_recipient, \
@ -399,8 +399,19 @@ def do_create_user(email, password, realm, full_name, short_name,
default_all_public_streams=None, prereg_user=None,
newsletter_data=None):
# type: (Text, Text, Realm, Text, Text, bool, Optional[int], Optional[UserProfile], Optional[Text], Text, Optional[Stream], Optional[Stream], bool, Optional[PreregistrationUser], Optional[Dict[str, str]]) -> UserProfile
user_profile = create_user(email=email, password=password, realm=realm,
full_name=full_name, short_name=short_name,
active=active, bot_type=bot_type, bot_owner=bot_owner,
tos_version=tos_version, avatar_source=avatar_source,
default_sending_stream=default_sending_stream,
default_events_register_stream=default_events_register_stream,
default_all_public_streams=default_all_public_streams)
RealmAuditLog.objects.create(realm=user_profile.realm, modified_user=user_profile,
event_type='user_created', event_time=user_profile.date_joined)
event = {'type': 'user_created',
'timestamp': time.time(),
'timestamp': datetime_to_timestamp(user_profile.date_joined),
'full_name': full_name,
'short_name': short_name,
'user': email,
@ -410,14 +421,6 @@ def do_create_user(email, password, realm, full_name, short_name,
event['bot_owner'] = bot_owner.email
log_event(event)
user_profile = create_user(email=email, password=password, realm=realm,
full_name=full_name, short_name=short_name,
active=active, bot_type=bot_type, bot_owner=bot_owner,
tos_version=tos_version, avatar_source=avatar_source,
default_sending_stream=default_sending_stream,
default_events_register_stream=default_events_register_stream,
default_all_public_streams=default_all_public_streams)
notify_created_user(user_profile)
if bot_type:
notify_created_bot(user_profile)
@ -629,9 +632,13 @@ def do_deactivate_user(user_profile, log=True, _cascade=True):
delete_user_sessions(user_profile)
event_time = timezone.now()
RealmAuditLog.objects.create(realm=user_profile.realm, modified_user=user_profile,
event_type='user_deactivated', event_time=event_time)
if log:
log_event({'type': 'user_deactivated',
'timestamp': time.time(),
'timestamp': datetime_to_timestamp(event_time),
'user': user_profile.email,
'domain': user_profile.realm.domain})
@ -1846,9 +1853,14 @@ def do_activate_user(user_profile, log=True, join_date=timezone.now()):
user_profile.save(update_fields=["is_active", "date_joined", "password",
"is_mirror_dummy", "tos_version"])
event_time = timezone.now()
RealmAuditLog.objects.create(realm=user_profile.realm, modified_user=user_profile,
event_type='user_activated', event_time=event_time)
if log:
domain = user_profile.realm.domain
log_event({'type': 'user_activated',
'timestamp': datetime_to_timestamp(event_time),
'user': user_profile.email,
'domain': domain})
@ -1861,8 +1873,13 @@ def do_reactivate_user(user_profile):
user_profile.is_active = True
user_profile.save(update_fields=["is_active"])
event_time = timezone.now()
RealmAuditLog.objects.create(realm=user_profile.realm, modified_user=user_profile,
event_type='user_reactivated', event_time=event_time)
domain = user_profile.realm.domain
log_event({'type': 'user_reactivated',
'timestamp': datetime_to_timestamp(event_time),
'user': user_profile.email,
'domain': domain})

View File

@ -3,7 +3,7 @@ from typing import Any, Dict, Iterable, List, Mapping, Optional, Set, Tuple, Tex
from zerver.lib.initial_password import initial_password
from zerver.models import Realm, Stream, UserProfile, Huddle, \
Subscription, Recipient, Client, get_huddle_hash
Subscription, Recipient, Client, RealmAuditLog, get_huddle_hash
from zerver.lib.create_user import create_user_profile
def bulk_create_realms(realm_list):
@ -35,6 +35,11 @@ def bulk_create_users(realm, users_raw, bot_type=None, tos_version=None):
profiles_to_create.append(profile)
UserProfile.objects.bulk_create(profiles_to_create)
RealmAuditLog.objects.bulk_create(
[RealmAuditLog(realm=profile_.realm, modified_user=profile_,
event_type='user_created', event_time=profile_.date_joined)
for profile_ in profiles_to_create])
profiles_by_email = {} # type: Dict[Text, UserProfile]
profiles_by_id = {} # type: Dict[int, UserProfile]
for profile in UserProfile.objects.select_related().all():

View File

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-04 07:33
from __future__ import unicode_literals
from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
from django.db.migrations.state import StateApps
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django.utils import timezone
from zerver.models import RealmAuditLog, UserProfile
def backfill_user_activations_and_deactivations(apps, schema_editor):
# type: (StateApps, DatabaseSchemaEditor) -> None
migration_time = timezone.now()
for user in UserProfile.objects.all():
RealmAuditLog.objects.create(realm=user.realm, modified_user=user,
event_type='user_created', event_time=user.date_joined,
backfilled=False)
for user in UserProfile.objects.filter(is_active=False):
RealmAuditLog.objects.create(realm=user.realm, modified_user=user,
event_type='user_deactivated', event_time=migration_time,
backfilled=True)
def reverse_code(apps, schema_editor):
# type: (StateApps, DatabaseSchemaEditor) -> None
RealmAuditLog.objects.filter(event_type='user_created').delete()
RealmAuditLog.objects.filter(event_type='user_deactivated').delete()
class Migration(migrations.Migration):
dependencies = [
('zerver', '0056_userprofile_emoji_alt_code'),
]
operations = [
migrations.CreateModel(
name='RealmAuditLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('event_type', models.CharField(max_length=40)),
('backfilled', models.BooleanField(default=False)),
('event_time', models.DateTimeField()),
('acting_user', models.ForeignKey(null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='+',
to=settings.AUTH_USER_MODEL)),
('modified_stream', models.ForeignKey(null=True,
on_delete=django.db.models.deletion.CASCADE,
to='zerver.Stream')),
('modified_user', models.ForeignKey(null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='+',
to=settings.AUTH_USER_MODEL)),
('realm', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='zerver.Realm')),
],
),
migrations.RunPython(backfill_user_activations_and_deactivations,
reverse_code=reverse_code),
]

View File

@ -1485,3 +1485,12 @@ class ScheduledJob(models.Model):
# Kind if like a ForeignKey, but table is determined by type.
filter_id = models.IntegerField(null=True) # type: Optional[int]
filter_string = models.CharField(max_length=100) # type: Text
class RealmAuditLog(models.Model):
realm = models.ForeignKey(Realm) # type: Realm
acting_user = models.ForeignKey(UserProfile, null=True, related_name='+') # type: Optional[UserProfile]
modified_user = models.ForeignKey(UserProfile, null=True, related_name='+') # type: Optional[UserProfile]
modified_stream = models.ForeignKey(Stream, null=True) # type: Optional[Stream]
event_type = models.CharField(max_length=40) # type: Text
event_time = models.DateTimeField() # type: datetime.datetime
backfilled = models.BooleanField(default=False) # type: bool

View File

@ -0,0 +1,27 @@
from django.utils import timezone
from zerver.lib.actions import do_create_user, do_deactivate_user, \
do_activate_user, do_reactivate_user
from zerver.lib.test_classes import ZulipTestCase
from zerver.models import RealmAuditLog, get_realm
from datetime import timedelta
class TestUserActivation(ZulipTestCase):
def test_user_activation(self):
# type: () -> None
realm = get_realm('zulip')
now = timezone.now()
user = do_create_user('email', 'password', realm, 'full_name', 'short_name')
do_deactivate_user(user)
do_activate_user(user)
do_deactivate_user(user)
do_reactivate_user(user)
self.assertEqual(RealmAuditLog.objects.filter(event_time__gte=now).count(), 5)
event_types = list(RealmAuditLog.objects.filter(
realm=realm, acting_user=None, modified_user=user, modified_stream=None,
event_time__gte=now, event_time__lte=now+timedelta(minutes=60))
.order_by('event_time').values_list('event_type', flat=True))
self.assertEqual(event_types, ['user_created', 'user_deactivated', 'user_activated',
'user_deactivated', 'user_reactivated'])