diff --git a/config/settings/base.py b/config/settings/base.py
index 94b2f03df0d75b251a0564aecb60cb13bfa87c71..4d5367d01025e13cc112ffabd70874b3d356838c 100644
--- a/config/settings/base.py
+++ b/config/settings/base.py
@@ -97,6 +97,8 @@ EMBB_IGNORE_ADDRESSES = ['Mailer-Daemon@{fqdn}'.format(fqdn=SERVER_FQDN)]
 LVC_GROUP = 'Communities:LSCVirgoLIGOGroupMembers'
 LVEM_GROUP = 'gw-astronomy:LV-EM'
 LVEM_OBSERVERS_GROUP = 'gw-astronomy:LV-EM:Observers'
+PUBLIC_GROUP = 'public_users'
+
 # Executives group name
 EXEC_GROUP = 'executives'
 # EM Advocate group name
@@ -111,6 +113,7 @@ ADMIN_MANAGED_GROUPS = [EM_ADVOCATE_GROUP, 'executives']
 
 # Tag to apply to log messages to allow EM partners to view
 EXTERNAL_ACCESS_TAGNAME = 'lvem'
+PUBLIC_ACCESS_TAGNAME = 'public'
 
 # FAR floor for outgoing VOEvents intended for GCN
 VOEVENT_FAR_FLOOR = 0 # Hz
diff --git a/gracedb/core/permission_utils.py b/gracedb/core/permission_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..c24995e478d885205a97cee2db2d7be51d76282c
--- /dev/null
+++ b/gracedb/core/permission_utils.py
@@ -0,0 +1,104 @@
+from django.conf import settings
+from django.contrib.auth.models import Group
+
+from guardian.shortcuts import assign_perm, remove_perm
+
+
+def assign_perms_to_obj(perms, group, obj):
+    """
+    perms is a list of strings like ['view', 'annotate', 'add']
+    """
+    # Convert perms to a list of strings like
+    #  {app_label}.{perm}_{model_name}
+    full_perm_fmt = '{app_label}.{perm}_{model_name}'
+    kwargs = {
+        'app_label': obj._meta.app_label,
+        'model_name': obj._meta.model_name,
+    }
+    full_perms = [full_perm_fmt.format(perm=p, **kwargs) for p in perms]
+
+    # Assign all permissions
+    for perm in full_perms:
+        assign_perm(perm, group, obj)
+
+
+def expose_event_or_superevent_to_lvem(obj):
+    """
+    obj is an Event or Superevent instance.
+
+    Currently works for superevents; will eventually be used for events
+    (once the permissions structure is overhauled.
+    """
+    perms = ['view', 'annotate']
+
+    # Get LV-EM group
+    lvem_group = Group.objects.get(name=settings.LVEM_OBSERVERS_GROUP)
+
+    # Assign permissions
+    assign_perms_to_obj(perms, lvem_group, obj)
+
+
+def expose_event_or_superevent_to_public(obj):
+    """
+    obj is an Event or Superevent instance.
+
+    Currently works for superevents; will eventually be used for events
+    (once the permissions structure is overhauled.
+    """
+    perms = ['view']
+
+    # Get public group
+    public_group = Group.objects.get(name=settings.PUBLIC_GROUP)
+
+    # Assign permissions
+    assign_perms_to_obj(perms, public_group, obj)
+
+
+def expose_log(log, group):
+    """
+    Assigns group view permission ([app_label].view_[model_name]) permission to
+    log object.
+    """
+    kwargs = {
+        'app_label': log._meta.app_label,
+        'model_name': log._meta.model_name,
+    }
+    permission = "{app_label}.view_{model_name}".format(**kwargs)
+    assign_perm(permission, group, log)
+
+
+def expose_log_to_lvem(log):
+    """Applies expose_log for LV-EM Observers group"""
+    group = Group.objects.get(name=settings.LVEM_OBSERVERS_GROUP)
+    expose_log(log, group)
+
+
+def expose_log_to_public(log):
+    """Applies expose_log for public group"""
+    group = Group.objects.get(name=settings.PUBLIC_GROUP)
+    expose_log(log, group)
+
+
+def hide_log(log, group):
+    """
+    Removes group view permission ([app_label].view_[model_name]) permission
+    from log object.
+    """
+    kwargs = {
+        'app_label': log._meta.app_label,
+        'model_name': log._meta.model_name,
+    }
+    permission = "{app_label}.view_{model_name}".format(**kwargs)
+    remove_perm(permission, group, log)
+
+
+def hide_log_from_lvem(log):
+    """Applies hide_log for LV-EM Observers group"""
+    group = Group.objects.get(name=settings.LVEM_OBSERVERS_GROUP)
+    hide_log(log, group)
+
+
+def hide_log_from_public(log):
+    """Applies hide_log for public group"""
+    group = Group.objects.get(name=settings.PUBLIC_GROUP)
+    hide_log(log, group)
diff --git a/gracedb/superevents/utils.py b/gracedb/superevents/utils.py
index 049fd9d55319feb463d4181c1bfc71d5e749301e..beee5cc31b3fb2319761ba548f43ea9ef71c18fe 100644
--- a/gracedb/superevents/utils.py
+++ b/gracedb/superevents/utils.py
@@ -18,6 +18,8 @@ from alerts.superevent_utils import issue_alert_for_superevent_creation, \
 from alerts.event_utils import issue_alert_for_event_log
 
 import os
+from core.permission_utils import expose_log_to_lvem, expose_log_to_public, \
+    hide_log_from_lvem, hide_log_from_public
 
 import logging
 logger = logging.getLogger(__name__)
@@ -232,6 +234,21 @@ def add_tag_to_log(log, tag, user, add_log_message=True, issue_alert=False):
     # Add tag to log
     log.tags.add(tag)
 
+    # If this tag controls whether the log is exposed or not, we need to create
+    # a corresponding GroupObjectPermission.  If we get to this point,
+    # permissions should have already been checked.
+    if (tag.name == settings.EXTERNAL_ACCESS_TAGNAME):
+        expose_log_to_lvem(log)
+    elif (tag.name == settings.PUBLIC_ACCESS_TAGNAME):
+        expose_log_to_public(log)
+        # Publicly exposed tags should also be exposed to LV-EM, if they
+        # aren't already
+        lvem_tag_applied = log.tags.filter(
+            name=settings.EXTERNAL_ACCESS_TAGNAME).exists()
+        if not lvem_tag_applied:
+            lvem_tag = Tag.objects.get(name=settings.EXTERNAL_ACCESS_TAGNAME)
+            add_tag_to_log(log, lvem_tag, user)
+
     # Create log message to record tag addition?
     log_for_tag_addition = None
     if add_log_message:
@@ -250,6 +267,21 @@ def remove_tag_from_log(log, tag, user, add_log_message=True,
     # Remove tag from log
     log.tags.remove(tag)
 
+    # If this tag controls whether the log is exposed or not, we need to create
+    # a corresponding GroupObjectPermission.  If we get to this point,
+    # permissions should have already been checked.
+    if (tag.name == settings.EXTERNAL_ACCESS_TAGNAME):
+        hide_log_from_lvem(log)
+        # If the log is hidden from LV-EM, it should also be hidden from the
+        # public
+        public_tag_applied = log.tags.filter(
+            name=settings.PUBLIC_ACCESS_TAGNAME).exists()
+        if not public_tag_applied:
+            public_tag = Tag.objects.get(name=settings.PUBLIC_ACCESS_TAGNAME)
+            remove_tag_from_log(log, public_tag, user)
+    elif (tag.name == settings.PUBLIC_ACCESS_TAGNAME):
+        hide_log_from_public(log)
+
     # Create log message to record tag removal?
     log_for_tag_removal = None
     if add_log_message: