diff --git a/aleksis/apps/chronos/admin.py b/aleksis/apps/chronos/admin.py
index 5145472fdbf9eff3349c81d0cbf10a59c24442f2..a29e816f464aa23e62b26294852050a8ab1ae032 100644
--- a/aleksis/apps/chronos/admin.py
+++ b/aleksis/apps/chronos/admin.py
@@ -24,6 +24,8 @@ from .models import (
 )
 from .util.format import format_date_period, format_m2m
 
+from guardian.admin import GuardedModelAdmin
+
 
 def colour_badge(fg: str, bg: str, val: str):
     html = """
@@ -144,7 +146,7 @@ class LessonAdmin(admin.ModelAdmin):
 admin.site.register(Lesson, LessonAdmin)
 
 
-class RoomAdmin(admin.ModelAdmin):
+class RoomAdmin(GuardedModelAdmin):
     list_display = ("short_name", "name")
     list_display_links = ("short_name", "name")
 
diff --git a/aleksis/apps/chronos/migrations/0005_add_permissions.py b/aleksis/apps/chronos/migrations/0005_add_permissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..9fcc2fba01d69514c27568e632e6ee1dba227672
--- /dev/null
+++ b/aleksis/apps/chronos/migrations/0005_add_permissions.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.1.3 on 2020-12-22 12:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('chronos', '0004_substitution_extra_lesson_year'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='room',
+            options={'ordering': ['name', 'short_name'],
+                     'permissions': (('view_room_timetable', 'Can view room timetable'),),
+                     'verbose_name': 'Room', 'verbose_name_plural': 'Rooms'},
+        ),
+        migrations.AlterModelOptions(
+            name='chronosglobalpermissions',
+            options={'managed': False, 'permissions': (('view_all_room_timetables', 'Can view all room timetables'), ('view_all_group_timetables', 'Can view all group timetables'), ('view_all_person_timetables', 'Can view all person timetables'), ('view_timetable_overview', 'Can view timetable overview'), ('view_lessons_day', 'Can view all lessons per day'))},
+        ),
+    ]
diff --git a/aleksis/apps/chronos/model_extensions.py b/aleksis/apps/chronos/model_extensions.py
index 269ce3fc292aeb4ff587982d1272f38d678f1743..b77770f84945f7cd98b2fc43cc43ce6dbffdd6a4 100644
--- a/aleksis/apps/chronos/model_extensions.py
+++ b/aleksis/apps/chronos/model_extensions.py
@@ -124,3 +124,12 @@ Announcement.field(
 )
 
 Group.foreign_key("subject", Subject, related_name="groups")
+
+# Dynamically add extra permissions to Group and Person models in core
+# Note: requires migrate afterwards
+Group.add_permission(
+    "view_group_timetable", _("Can view group timetable"),
+)
+Person.add_permission(
+    "view_person_timetable", _("Can view person timetable"),
+)
diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index a810fc9b17f1d6c6e353d70c1abb9a4fb90a83fe..9e755bebe14cac939d8379abb91ee4bdad0601db 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -317,6 +317,7 @@ class Room(ExtensibleModel):
         return reverse("timetable", args=["room", self.id])
 
     class Meta:
+        permissions = (("view_room_timetable", _("Can view room timetable")),)
         ordering = ["name", "short_name"]
         verbose_name = _("Room")
         verbose_name_plural = _("Rooms")
@@ -946,7 +947,9 @@ class ChronosGlobalPermissions(ExtensibleModel):
     class Meta:
         managed = False
         permissions = (
-            ("view_all_timetables", _("Can view all timetables")),
+            ("view_all_room_timetables", _("Can view all room timetables")),
+            ("view_all_group_timetables", _("Can view all group timetables")),
+            ("view_all_person_timetables", _("Can view all person timetables")),
             ("view_timetable_overview", _("Can view timetable overview")),
             ("view_lessons_day", _("Can view all lessons per day")),
         )
diff --git a/aleksis/apps/chronos/rules.py b/aleksis/apps/chronos/rules.py
index 7ee9d1e0dcfb7b937b24ef82bb1adf36fa10dc46..a31b3ca12fcff4a0289505f8d83d973061c38eb6 100644
--- a/aleksis/apps/chronos/rules.py
+++ b/aleksis/apps/chronos/rules.py
@@ -1,5 +1,6 @@
 from rules import add_perm
 
+from aleksis.core.models import Group, Person
 from aleksis.core.util.predicates import (
     has_any_object,
     has_global_perm,
@@ -7,20 +8,22 @@ from aleksis.core.util.predicates import (
     has_person,
 )
 
-from .models import LessonSubstitution
+from .models import LessonSubstitution, Room
 from .util.predicates import has_timetable_perm
 
 # View timetable overview
-view_timetable_overview_predicate = has_person & has_global_perm("chronos.view_timetable_overview")
+view_timetable_overview_predicate = has_person & (
+    has_any_object("chronos.view_timetable", Person)
+    | has_any_object("chronos.view_timetable", Group)
+    | has_any_object("chronos.view_timetable", Room)
+)
 add_perm("chronos.view_timetable_overview", view_timetable_overview_predicate)
 
 # View my timetable
 add_perm("chronos.view_my_timetable", has_person)
 
 # View timetable
-view_timetable_predicate = has_person & (
-    has_global_perm("chronos.view_all_timetables") | has_timetable_perm
-)
+view_timetable_predicate = has_person & has_timetable_perm
 add_perm("chronos.view_timetable", view_timetable_predicate)
 
 # View all lessons per day
diff --git a/aleksis/apps/chronos/templates/chronos/all.html b/aleksis/apps/chronos/templates/chronos/all.html
index bedf5ca129d781ff2f13e198714871bf2c4b7b1c..8104f0849fc9fa0caaf32a2213554efd0e02ad29 100644
--- a/aleksis/apps/chronos/templates/chronos/all.html
+++ b/aleksis/apps/chronos/templates/chronos/all.html
@@ -2,7 +2,7 @@
 
 {% extends 'core/base.html' %}
 
-{% load i18n static %}
+{% load i18n static rules %}
 
 {% block extra_head %}
   <link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}">
@@ -17,10 +17,13 @@
       <h5>{% trans "Teachers" %}</h5>
 
       {% for teacher in teachers %}
-        <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary"
-           href="{% url 'timetable' 'teacher' teacher.pk %}">
-          {{ teacher.short_name }}
-        </a>
+        {% has_perm "chronos.view_timetable" user teacher as has_teacher_perm %}
+        {% if has_teacher_perm %}
+          <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary"
+             href="{% url 'timetable' 'teacher' teacher.pk %}">
+            {{ teacher.short_name }}
+          </a>
+        {% endif %}
       {% endfor %}
     </div>
 
@@ -28,10 +31,13 @@
       <h5>{% trans "Groups" %}</h5>
 
       {% for class in classes %}
-        <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary"
-           href="{% url 'timetable' 'group' class.pk %}">
-          {{ class.short_name }}
-        </a>
+        {% has_perm "chronos.view_timetable" user class as has_class_perm %}
+        {% if has_class_perm %}
+          <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary"
+             href="{% url 'timetable' 'group' class.pk %}">
+            {{ class.short_name }}
+          </a>
+        {% endif %}
       {% endfor %}
     </div>
 
@@ -39,10 +45,13 @@
       <h5>{% trans "Rooms" %}</h5>
 
       {% for room in rooms %}
-        <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary"
-           href="{% url 'timetable' 'room' room.pk %}">
-          {{ room.short_name }}
-        </a>
+        {% has_perm "chronos.view_timetable" user room as has_room_perm %}
+        {% if has_room_perm %}
+          <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary"
+             href="{% url 'timetable' 'room' room.pk %}">
+            {{ room.short_name }}
+          </a>
+        {% endif %}
       {% endfor %}
     </div>
   </div>
diff --git a/aleksis/apps/chronos/util/predicates.py b/aleksis/apps/chronos/util/predicates.py
index cdbe5e2c268579558b0f29a184ed82709eda764a..bc9c8d92f419b605302a782a9617025a1437f1cc 100644
--- a/aleksis/apps/chronos/util/predicates.py
+++ b/aleksis/apps/chronos/util/predicates.py
@@ -5,16 +5,29 @@ from rules import predicate
 
 from aleksis.apps.chronos.models import Room
 from aleksis.core.models import Group, Person
+from aleksis.core.util.predicates import has_global_perm, has_object_perm
 
 
 @predicate
 def has_timetable_perm(user: User, obj: Model) -> bool:
     """Predicate which checks whether the user is allowed to access the requested timetable."""
-    if obj.model is Group:
-        return obj in user.person.member_of
-    elif obj.model is Person:
-        return user.person == obj
-    elif obj.model is Room:
-        return True
+    if type(obj) is Group:
+        return (
+            user.person.member_of.filter(id=obj.id).exists()
+            or user.person.primary_group == obj
+            or user.person.owner_of.filter(id=obj.id).exists()
+            or has_global_perm("chronos.view_all_group_timetables")(user)
+            or has_object_perm("core.view_group_timetable")(user, obj)
+        )
+    elif type(obj) is Person:
+        return (
+            user.person == obj
+            or has_global_perm("chronos.view_all_person_timetables")(user)
+            or has_object_perm("core.view_person_timetable")(user, obj)
+        )
+    elif type(obj) is Room:
+        return has_global_perm("chronos.view_all_room_timetables")(user) or has_object_perm(
+            "chronos.view_room_timetable"
+        )(user, obj)
     else:
         return False