HTTP API: hash e-mails in timetable serializer
This is already done in the normal event serializer, there is no reason why we shouldn't do it here too.
diff --git a/indico/modules/events/api.py b/indico/modules/events/api.py
index fb478ea86a..282675c6cd 100644
--- a/indico/modules/events/api.py
+++ b/indico/modules/events/api.py
@@ -81,9 +81,9 @@ class EventTimeTableHook(HTTPAPIHook):
         self._idList = self._pathParams['idlist'].split('-')
 
     def export_timetable(self, user):
-        serializer = TimetableSerializer(management=False)
         events = Event.find_all(Event.id.in_(map(int, self._idList)), ~Event.is_deleted)
-        return {event.id: serializer.serialize_timetable(event) for event in events}
+        return {event.id: TimetableSerializer(event, management=False, user=user).serialize_timetable()
+                for event in events}
 
 
 @HTTPAPIHook.register
diff --git a/indico/modules/events/timetable/controllers/display.py b/indico/modules/events/timetable/controllers/display.py
index 8ca5031420..f51bc93066 100644
--- a/indico/modules/events/timetable/controllers/display.py
+++ b/indico/modules/events/timetable/controllers/display.py
@@ -47,7 +47,7 @@ class RHTimetable(RHConferenceBaseDisplay):
         self.event.preload_all_acl_entries()
         if self.theme is None:
             event_info = serialize_event_info(self.event)
-            timetable_data = TimetableSerializer().serialize_timetable(self.event, strip_empty_days=True)
+            timetable_data = TimetableSerializer(self.event).serialize_timetable(strip_empty_days=True)
             timetable_settings = layout_settings.get(self.event, 'timetable_theme_settings')
             return self.view_class.render_template('display.html', self._conf, event_info=event_info,
                                                    timetable_data=timetable_data, timetable_settings=timetable_settings,
diff --git a/indico/modules/events/timetable/controllers/legacy.py b/indico/modules/events/timetable/controllers/legacy.py
index ba48cdbfc0..4f3614f92b 100644
--- a/indico/modules/events/timetable/controllers/legacy.py
+++ b/indico/modules/events/timetable/controllers/legacy.py
@@ -420,7 +420,7 @@ class RHLegacyTimetableMoveEntry(RHManageTimetableEntryBase):
                                 parent_entry=self.entry.parent)
 
     def _process_POST(self):
-        self.serializer = TimetableSerializer(True)
+        self.serializer = TimetableSerializer(self.event, management=True)
         with track_time_changes(auto_extend=True, user=session.user) as changes:
             entry_data = self._move_entry(request.json)
         rv = dict(serialize_entry_update(self.entry), **entry_data)
diff --git a/indico/modules/events/timetable/controllers/manage.py b/indico/modules/events/timetable/controllers/manage.py
index 8b81ca8db0..24238cbfa4 100644
--- a/indico/modules/events/timetable/controllers/manage.py
+++ b/indico/modules/events/timetable/controllers/manage.py
@@ -43,7 +43,7 @@ class RHManageTimetable(RHManageTimetableBase):
 
     def _process(self):
         event_info = serialize_event_info(self.event)
-        timetable_data = TimetableSerializer(management=True).serialize_timetable(self.event)
+        timetable_data = TimetableSerializer(self.event, management=True).serialize_timetable()
         return WPManageTimetable.render_template('management.html', self.event, event_info=event_info,
                                                  timetable_data=timetable_data)
 
@@ -56,7 +56,7 @@ class RHManageSessionTimetable(RHManageTimetableBase):
     def _process(self):
         event_info = serialize_event_info(self.event)
         event_info['timetableSession'] = serialize_session(self.session)
-        timetable_data = TimetableSerializer(management=True).serialize_session_timetable(self.session)
+        timetable_data = TimetableSerializer(self.event, management=True).serialize_session_timetable(self.session)
         management_rights = {
             'can_manage_event': self.event.can_manage(session.user),
             'can_manage_session': self.session.can_manage(session.user),
diff --git a/indico/modules/events/timetable/legacy.py b/indico/modules/events/timetable/legacy.py
index b0e461a6b5..a090bbd30e 100644
--- a/indico/modules/events/timetable/legacy.py
+++ b/indico/modules/events/timetable/legacy.py
@@ -17,9 +17,10 @@
 from __future__ import unicode_literals
 
 from collections import defaultdict
+from hashlib import md5
 from itertools import chain
 
-from flask import session
+from flask import has_request_context, session
 from sqlalchemy.orm import defaultload
 
 from indico.modules.events.contributions.models.persons import AuthorType
@@ -30,14 +31,17 @@ from indico.web.flask.util import url_for
 
 
 class TimetableSerializer(object):
-    def __init__(self, management=False):
+    def __init__(self, event, management=False, user=None):
         self.management = management
+        self.user = user if user is not None and not has_request_context() else session.user
+        self.event = event
+        self.can_manage_event = self.event.can_manage(self.user)
 
-    def serialize_timetable(self, event, days=None, hide_weekends=False, strip_empty_days=False):
-        tzinfo = event.tzinfo if self.management else event.display_tzinfo
-        event.preload_all_acl_entries()
+    def serialize_timetable(self, days=None, hide_weekends=False, strip_empty_days=False):
+        tzinfo = self.event.tzinfo if self.management else self.event.display_tzinfo
+        self.event.preload_all_acl_entries()
         timetable = {}
-        for day in iterdays(event.start_dt.astimezone(tzinfo), event.end_dt.astimezone(tzinfo),
+        for day in iterdays(self.event.start_dt.astimezone(tzinfo), self.event.end_dt.astimezone(tzinfo),
                             skip_weekends=hide_weekends, day_whitelist=days):
             date_str = day.strftime('%Y%m%d')
             timetable[date_str] = {}
@@ -46,7 +50,7 @@ class TimetableSerializer(object):
         contributions_strategy.subqueryload('references')
         query_options = (contributions_strategy,
                          defaultload('session_block').subqueryload('person_links'))
-        query = (TimetableEntry.query.with_parent(event)
+        query = (TimetableEntry.query.with_parent(self.event)
                  .options(*query_options)
                  .order_by(TimetableEntry.type != TimetableEntryType.SESSION_BLOCK))
         for entry in query:
@@ -54,7 +58,7 @@ class TimetableSerializer(object):
             date_str = day.strftime('%Y%m%d')
             if date_str not in timetable:
                 continue
-            if not entry.can_view(session.user):
+            if not entry.can_view(self.user):
                 continue
             data = self.serialize_timetable_entry(entry, load_children=False)
             key = self._get_entry_key(entry)
@@ -72,15 +76,14 @@ class TimetableSerializer(object):
         return timetable
 
     def serialize_session_timetable(self, session_, without_blocks=False, strip_empty_days=False):
-        event = session_.event
-        event_tz = event.tzinfo
+        event_tz = self.event.tzinfo
         timetable = {}
         if session_.blocks:
-            start_dt = min(chain((b.start_dt for b in session_.blocks), (event.start_dt,))).astimezone(event_tz)
-            end_dt = max(chain((b.end_dt for b in session_.blocks), (event.end_dt,))).astimezone(event_tz)
+            start_dt = min(chain((b.start_dt for b in session_.blocks), (self.event.start_dt,))).astimezone(event_tz)
+            end_dt = max(chain((b.end_dt for b in session_.blocks), (self.event.end_dt,))).astimezone(event_tz)
         else:
-            start_dt = event.start_dt_local
-            end_dt = event.end_dt_local
+            start_dt = self.event.start_dt_local
+            end_dt = self.event.end_dt_local
 
         for day in iterdays(start_dt, end_dt):
             timetable[day.strftime('%Y%m%d')] = {}
@@ -91,7 +94,7 @@ class TimetableSerializer(object):
             date_key = block_entry.start_dt.astimezone(event_tz).strftime('%Y%m%d')
             entries = block_entry.children if without_blocks else [block_entry]
             for entry in entries:
-                if not entry.can_view(session.user):
+                if not entry.can_view(self.user):
                     continue
                 entry_key = self._get_entry_key(entry)
                 timetable[date_key][entry_key] = self.serialize_timetable_entry(entry, load_children=True)
@@ -150,6 +153,7 @@ class TimetableSerializer(object):
 
     def serialize_contribution_entry(self, entry):
         from indico.modules.events.api import SerializerBase
+
         block = entry.parent.session_block if entry.parent else None
         contribution = entry.contribution
         data = {}
@@ -276,13 +280,17 @@ class TimetableSerializer(object):
         return data
 
     def _get_person_data(self, person_link):
-        return {'firstName': person_link.first_name,
+        person = person_link.person
+        data = {'firstName': person_link.first_name,
                 'familyName': person_link.last_name,
                 'affiliation': person_link.affiliation,
-                'email': person_link.person.email,
+                'emailHash': md5(person.email.encode('utf-8')).hexdigest() if person.email else None,
                 'name': person_link.get_full_name(last_name_first=False, last_name_upper=False,
                                                   abbrev_first_name=False, show_title=True),
                 'displayOrderKey': person_link.display_order_key}
+        if self.can_manage_event:
+            data['email'] = person.email
+        return data
 
 
 def serialize_contribution(contribution):
@@ -292,8 +300,8 @@ def serialize_contribution(contribution):
 
 
 def serialize_day_update(event, day, block=None, session_=None):
-    serializer = TimetableSerializer(management=True)
-    timetable = serializer.serialize_session_timetable(session_) if session_ else serializer.serialize_timetable(event)
+    serializer = TimetableSerializer(event, management=True)
+    timetable = serializer.serialize_session_timetable(session_) if session_ else serializer.serialize_timetable()
     block_id = serializer._get_entry_key(block) if block else None
     day = day.strftime('%Y%m%d')
     return {'day': day,
@@ -302,7 +310,7 @@ def serialize_day_update(event, day, block=None, session_=None):
 
 
 def serialize_entry_update(entry, with_timetable=False, session_=None):
-    serializer = TimetableSerializer(management=True)
+    serializer = TimetableSerializer(entry.event, management=True)
     day = entry.start_dt.astimezone(entry.event.tzinfo)
     day_update = serialize_day_update(entry.event, day, block=entry.parent, session_=session_)
     return dict({'id': serializer._get_entry_key(entry),
diff --git a/indico/modules/events/timetable/util.py b/indico/modules/events/timetable/util.py
index 14da71e79b..73f40271b2 100644
--- a/indico/modules/events/timetable/util.py
+++ b/indico/modules/events/timetable/util.py
@@ -279,8 +279,8 @@ def render_session_timetable(session, timetable_layout=None, management=False):
     if not session.start_dt:
         # no scheduled sessions present
         return ''
-    timetable_data = TimetableSerializer().serialize_session_timetable(session, without_blocks=True,
-                                                                       strip_empty_days=True)
+    timetable_data = TimetableSerializer(session.event).serialize_session_timetable(session, without_blocks=True,
+                                                                                    strip_empty_days=True)
     event_info = serialize_event_info(session.event)
     tpl = get_template_module('events/timetable/_timetable.html')
     return tpl.render_timetable(timetable_data, event_info, timetable_layout=timetable_layout, management=management)
['indico/modules/events/api.py', 'indico/modules/events/timetable/controllers/legacy.py', 'indico/modules/events/timetable/util.py', 'indico/modules/events/timetable/controllers/manage.py', 'indico/modules/events/timetable/legacy.py', 'indico/modules/events/timetable/controllers/display.py']