diff --git a/gracedb/api.py b/gracedb/api.py
index b3f820c55a1ee97212466e4913c886a801620fda..ef2a04c77655db17105c1f2407686affd2173d78 100644
--- a/gracedb/api.py
+++ b/gracedb/api.py
@@ -29,7 +29,8 @@ from view_utils import reverse
 
 from translator import handle_uploaded_data
 from forms import CreateEventForm
-from permission_utils import user_has_perm, filter_events_for_user
+from permission_utils import user_has_perm, filter_events_for_user, is_external
+from permission_utils import check_external_file_access
 from guardian.models import GroupObjectPermission
 
 from throttles import EventCreationThrottle, AnnotationThrottle
@@ -738,6 +739,11 @@ class EventLogList(APIView):
     @event_and_auth_required
     def get(self, request, event):
         logset = event.eventlog_set.order_by("created","N")
+
+        # Filter log messages for external users.
+        if is_external(request.user):
+            logset = logset.filter(tag__name=settings.EXTERNAL_ACCESS_TAGNAME)
+
         count = logset.count()
 
         log = [ eventLogToDict(log, request)
@@ -802,6 +808,12 @@ class EventLogList(APIView):
             return Response("Failed to save log entry: %s" % str(e),
                     status=status.HTTP_503_SERVICE_UNAVAILABLE)
 
+        # XXX For external users, make sure that the new log entry is tagged so
+        # they'll be able to see it later.
+        if is_external(request.user):
+            if settings.EXTERNAL_ACCESS_TAGNAME not in tagnames:
+                tagnames.append(settings.EXTERNAL_ACCESS_TAGNAME)
+
         tw_dict = {}
         if tagnames and len(tagnames):
             for tagname in tagnames:
@@ -839,6 +851,12 @@ class EventLogDetail(APIView):
     @event_and_auth_required
     @eventlog_required
     def get(self, request, event, eventlog):
+        # XXX Access control to log messages for external users.
+        if is_external(request.user):
+            tagnames = [t.name for t in eventlog.tag_set.all()]
+            if settings.EXTERNAL_ACCESS_TAGNAME not in tagnames:
+                msg = 'You do not have permission to view this log message.'
+                return HttpResponseForbidden(msg)
         return Response(eventLogToDict(eventlog, request=request))
 
 
@@ -1000,7 +1018,7 @@ class EMObservationDetail(APIView):
 #==================================================================
 # Tags
 
-
+# XXX This serializer should be moved to view_utils along with the others.
 def tagToDict(tag, columns=None, request=None, event=None, n=None):
     """Convert a tag to a dictionary.
        Output depends on the level of specificity.
@@ -1010,6 +1028,9 @@ def tagToDict(tag, columns=None, request=None, event=None, n=None):
     rv['name'] = tag.name
     rv['displayName'] = tag.displayName
     if event:
+        # XXX Technically, this list should be filtered based on whether the
+        # user is external and has access to the logs. I don't think this is 
+        # a big deal, though.
         if n:
             # We want a link to the self only.  End of the line.
             rv['links'] = {
@@ -1092,6 +1113,9 @@ class EventTagList(APIView):
     @event_and_auth_required
     def get(self, request, event):
         # Return a list of links to all tags for this event.
+        # XXX Technically, this list should be filtered based on whether the
+        # user is external and has access to the logs. I don't think this is 
+        # a big deal, though.
         rv = {
                 'tags' : [ reverse("eventtag-detail",args=[event.graceid(),
                                    tag.name],
@@ -1166,6 +1190,11 @@ class EventLogTagDetail(APIView):
             msg = "Log already has tag %s" % unicode(tag)
             return Response(msg,status=status.HTTP_409_CONFLICT)
         except:
+            # Check authorization
+            if is_external(request.user) and tagname == settings.EXTERNAL_ACCESS_TAGNAME:
+                msg = "You do not have permission to add or remove this tag."
+                return HttpResponseForbidden(msg)            
+
             # Look for the tag.  If it doesn't already exist, create it.
             try:
                 tag = Tag.objects.filter(name=tagname)[0]
@@ -1531,6 +1560,12 @@ def download(request, graceid, filename=""):
     except Event.DoesNotExist:
         return HttpResponseNotFound("Event not found")
 
+    # If the user is external, check for authorization
+    if is_external(request.user):
+        if not check_external_file_access(event, filename):
+            msg = "You do not have permission to view this file."
+            return HttpResponseForbidden(msg)
+
     filepath = os.path.join(event.datadir(), filename)
 
     if not os.path.exists(filepath):
@@ -1601,6 +1636,12 @@ class Files(APIView):
             response = HttpResponseNotFound("File not readable")
         elif os.path.isfile(filepath):
             # get an actual file.
+            # If the user is external, check for authorization
+            if is_external(request.user):
+                if not check_external_file_access(event, filename):
+                    msg = "You do not have permission to view this file."
+                    return HttpResponseForbidden(msg)
+
             content_type, encoding = VersionedFile.guess_mimetype(filepath)
             content_type = content_type or "application/octet-stream"
             # XXX encoding should probably not be ignored.
@@ -1613,19 +1654,40 @@ class Files(APIView):
             # Get list of files w/urls.
             rv = {}
             filepath = event.datadir()
+            fnames = []
+            # Filter files for external users.
+            if is_external(request.user):
+                # XXX Note that the following snippet is repeated in views.py.
+                # Construct the file list, filtering as necessary:
+                for l in event.eventlog_set.all():
+                    filename = l.filename
+                    if len(filename):
+                        version = l.file_version
+                        tagnames = [t.name for t in l.tag_set.all()]
+                        if settings.EXTERNAL_ACCESS_TAGNAME not in tagnames:
+                            continue
+                        if version>=0:
+                            fnames.append(filename + ',' + str(version))
+                        # We only want the unadorned filename once.
+                        if filename not in fnames:
+                            fnames.append(filename)
+            else:
+                for dirname, dirnames, filenames in os.walk(filepath):
+                    dirname = dirname[len(filepath):]  # cut off base event dir path
+                    for filename in filenames:
+                        # relative path from root of event data dir
+                        filename = os.path.join(dirname, filename)
+                        fnames.append(filename)
+
             files = []
-            for dirname, dirnames, filenames in os.walk(filepath):
-                dirname = dirname[len(filepath):]  # cut off base event dir path
-                for filename in filenames:
-                    # relative path from root of event data dir
-                    filename = os.path.join(dirname, filename)
-                    rv[filename] = reverse("files", args=[graceid, filename], request=request)
-                    files.append({
-                            'name' : filename,
-                            'link' :  reverse("files",
-                                args=[graceid, filename],
-                                request=request),
-                            })
+            for filename in fnames:
+                rv[filename] = reverse("files", args=[graceid, filename], request=request)
+                files.append({
+                        'name' : filename,
+                        'link' :  reverse("files",
+                            args=[graceid, filename],
+                            request=request),
+                        })
             response = Response(rv)
         elif os.path.isdir(filepath):
             # XXX Really?
@@ -1675,6 +1737,15 @@ class Files(APIView):
             # XXX something should be done here.
             pass
 
+        # If the user is external, we need to try to tag the log entry appropriately
+        if is_external(request.user):
+            try:
+                tag = Tag.objects.get(name=settings.EXTERNAL_ACCESS_TAGNAME)
+                tag.eventlogs.add(logentry)
+            except:
+                # XXX probably should at least log a warning here.
+                pass
+
         try:
             description = "UPLOAD: {0}".format(filename)
             issueAlertForUpdate(event, description, doxmpp=True, 
@@ -1704,8 +1775,8 @@ class PerformanceInfo(APIView):
         allowed_groups = set([])
         try:
             allowed_groups = set([
-                AuthGroup.objects.get(name='Communities:LSCVirgoLIGOGroupMembers'),
-                AuthGroup.objects.get(name='executives'),
+                AuthGroup.objects.get(name=settings.LVC_GROUP),
+                AuthGroup.objects.get(name=settings.EXEC_GROUP),
             ])
         except:
             pass
diff --git a/gracedb/permission_utils.py b/gracedb/permission_utils.py
index 9e73e6322a1e1806ab27fd67689c2c31df15bae1..5acda637fedbfaf122acf17b5f5c9dc84ca9d979 100644
--- a/gracedb/permission_utils.py
+++ b/gracedb/permission_utils.py
@@ -1,9 +1,11 @@
+from django.conf import settings
 from django.db.models import Q
 from guardian.shortcuts import assign_perm
 from django.contrib.auth.models import Group
 from django.utils.functional import wraps
 from django.http import HttpResponseForbidden
 from gracedb.models import Event
+import os
 
 #-------------------------------------------------------------------------------
 # A convenient wrapper for permission checks.
@@ -37,8 +39,8 @@ def filter_events_for_user(events, user, shortname):
 #-------------------------------------------------------------------------------
 def assign_default_event_perms(event):
     # Retrieve the group objects
-    executives = Group.objects.get(name='executives')
-    internal   = Group.objects.get(name='Communities:LSCVirgoLIGOGroupMembers')
+    executives = Group.objects.get(name=settings.EXEC_GROUP)
+    internal   = Group.objects.get(name=settings.LVC_GROUP)
 
     # Need to find the *type* of event. Could be a subclass.
     model = event.__class__
@@ -53,7 +55,7 @@ def assign_default_event_perms(event):
 
     # If the event is an MDC event, then we expose it to LV-EM also
     if event.search and event.search.name == 'MDC':
-        lvem = Group.objects.get(name='gw-astronomy:LV-EM')
+        lvem = Group.objects.get(name=settings.LVEM_GROUP)
         assign_perm(view_codename, lvem, event)
         assign_perm(change_codename, lvem, event)
 
@@ -66,7 +68,7 @@ def internal_user_required(view):
     def inner(request, *args, **kwargs):
         # XXX Should probably move this list of internal groups into settings.
         internal_groups = Group.objects.filter(
-            name__in=['Communities:LSCVirgoLIGOGroupMembers', 'executives'])
+            name__in=[settings.LVC_GROUP, settings.EXEC_GROUP])
         if not set(list(internal_groups)) & set(list(request.user.groups.all())):
             return HttpResponseForbidden("Forbidden")
         return view(request, *args, **kwargs)
@@ -80,8 +82,77 @@ def lvem_user_required(view):
     @wraps(view)
     def inner(request, *args, **kwargs):
         # XXX Should probably move this list of internal groups into settings.
-        lvem_groups = [Group.objects.get(name='gw-astronomy:LV-EM')]
+        lvem_groups = [Group.objects.get(name=settings.LVEM_GROUP)]
         if not set(list(lvem_groups)) & set(list(request.user.groups.all())):
             return HttpResponseForbidden("Forbidden")
         return view(request, *args, **kwargs)
     return inner
+
+#-------------------------------------------------------------------------------
+# A utility for determining whether a user is 'external' (i.e., not part of the 
+# LVC). This is useful for controlling which pieces of information to display
+# in a view.
+#-------------------------------------------------------------------------------
+def is_external(user):
+    if user:
+        user_groups = [g.name for g in user.groups.all()]
+        if settings.LVC_GROUP not in user_groups:
+            return True
+        return False
+    else:
+        return True
+#    return True
+
+#-------------------------------------------------------------------------------
+# A utility for determining whether an external user should have access to a 
+# particular file, given the event and filename. This is done by finding the
+# log message associated with that file and checking that the log message is
+# tagged for external access. Returns True if the user should have access, and 
+# False if not. Note that this presumes that the user is external and does not
+# check this.
+#-------------------------------------------------------------------------------
+def get_file_version(filename):
+    version = None
+    base_filename = filename
+    if len(filename.split(',')) > 1:
+        try:
+            toks = filename.split(',')
+            base_filename = toks[0]
+            version = int(toks[1])
+        except:
+            pass
+    return base_filename, version
+
+def check_external_file_access(event, filename):
+    filename, version = get_file_version(filename)
+    if version is None:
+        # Figure out the version by following the link
+        filepath = os.path.join(event.datadir(), filename)
+        if os.path.islink(filepath):
+            target_file = os.path.realpath(filepath)
+            target_basename = os.path.basename(target_file)
+            filename, version = get_file_version(target_basename)
+        else:
+            return False
+
+    # find the associated log message
+    logs = event.eventlog_set.filter(filename=filename, file_version=version)
+    count = logs.count()
+    log = None
+    if count == 0:
+        # Could not find logfile 
+        # XXX Write log
+        return False
+    elif count > 1:
+        # There should only be one file. Ugh. What's going on?
+        # XXX Write log 
+        return False
+    else:
+        log = logs[0]
+
+    # check access
+    tagnames = [t.name for t in log.tag_set.all()]
+    if settings.EXTERNAL_ACCESS_TAGNAME not in tagnames:
+        return False
+    return True
+
diff --git a/gracedb/view_utils.py b/gracedb/view_utils.py
index 70b3d51673ee9b6e04575da2742603bbf3684b29..90d0b0cb206649d989ce7c2800bac7c921eccbdc 100644
--- a/gracedb/view_utils.py
+++ b/gracedb/view_utils.py
@@ -9,6 +9,7 @@ from django.utils.safestring import mark_safe
 from gracedb.models import SingleInspiral
 
 from utils.vfile import VersionedFile
+from permission_utils import is_external
 
 import os
 from django.conf import settings
@@ -138,140 +139,142 @@ def eventToDict(event, columns=None, request=None):
                   request=request))
           for labelling in event.labelling_set.all()])
     # XXX Try to produce a dictionary of analysis specific attributes.  Duck typing.
-    rv['extra_attributes'] = {}
-    try:
-        # GrbEvent
-        rv['extra_attributes']['GRB'] = {
-              "ivorn" : event.ivorn,
-              "author_ivorn" : event.author_ivorn,
-              "author_shortname" : event.author_shortname,
-              "observatory_location_id" : event.observatory_location_id,
-              "coord_system" : event.coord_system,
-              "ra" : event.ra,
-              "dec" : event.dec,
-              "error_radius" : event.error_radius,
-              "how_description" : event.how_description,
-              "how_reference_url" : event.how_reference_url,
-              "T90" : event.t90,
-              "trigger_duration": event.trigger_duration,
-              "designation": event.designation,
-              "redshift": event.redshift,
-              "trigger_id": event.trigger_id,
-              }
-    except:
-        pass
-    try:
-        # CoincInspiralEvent
-        rv['extra_attributes']['CoincInspiral'] = {
-              "ifos" : event.ifos,
-              "end_time" : event.end_time,
-              "end_time_ns" : event.end_time_ns,
-              "mass" : event.mass,
-              "mchirp" : event.mchirp,
-              "minimum_duration" : event.minimum_duration,
-              "snr" : event.snr,
-              "false_alarm_rate" : event.false_alarm_rate,
-              "combined_far" : event.combined_far,
-              }
-    except:
-        pass
-    try:
-        # SimInspiralEvent
-        rv['extra_attributes']['SimInspiral'] = {
-                "source_channel": event.source_channel,
-                "destination_channel": event.destination_channel,
-                "mass1": event.mass1,
-                "mass2": event.mass2,
-                "eta": event.eta,
-                "mchirp": event.mchirp,
-                "amp_order": event.amp_order,
-                "coa_phase": event.coa_phase,
-                "spin1y": event.spin1y,
-                "spin1x": event.spin1x,
-                "spin1z": event.spin1z,
-                "spin2x": event.spin2x,
-                "spin2y": event.spin2y,
-                "spin2z": event.spin2z,
-                "geocent_end_time": event.geocent_end_time,
-                "geocent_end_time_ns": event.geocent_end_time_ns,
-                "end_time_gmst": event.end_time_gmst,
-                "f_lower": event.f_lower,
-                "f_final": event.f_final,
-                "distance": event.distance,
-                "latitude": event.latitude,
-                "longitude": event.longitude,
-                "polarization": event.polarization,
-                "inclination": event.inclination,
-                "theta0": event.theta0,
-                "phi0": event.phi0,
-                "waveform": event.waveform,
-                "numrel_mode_min": event.numrel_mode_min,
-                "numrel_mode_max": event.numrel_mode_max,
-                "numrel_data": event.numrel_data,
-                "source": event.source,
-                "taper": event.taper,
-                "bandpass": event.bandpass,
-                "alpha": event.alpha,
-                "beta": event.beta,
-                "psi0": event.psi0,
-                "psi3": event.psi3,
-                "alpha1": event.alpha1,
-                "alpha2": event.alpha2,
-                "alpha3": event.alpha3,
-                "alpha4": event.alpha4,
-                "alpha5": event.alpha5,
-                "alpha6": event.alpha6,
-                "g_end_time": event.g_end_time,
-                "g_end_time_ns": event.g_end_time_ns,
-                "h_end_time": event.h_end_time,
-                "h_end_time_ns": event.h_end_time_ns,
-                "l_end_time": event.l_end_time,
-                "l_end_time_ns": event.l_end_time_ns,
-                "t_end_time": event.t_end_time,
-                "t_end_time_ns": event.t_end_time_ns,
-                "v_end_time": event.v_end_time,
-                "v_end_time_ns": event.v_end_time_ns,
-                "eff_dist_g": event.eff_dist_g,
-                "eff_dist_h": event.eff_dist_h,
-                "eff_dist_l": event.eff_dist_l,
-                "eff_dist_t": event.eff_dist_t,
-                "eff_dist_v": event.eff_dist_v,
-            }
-    except:
-        pass
-    try:
-        # MultiBurstEvent
-        rv['extra_attributes']['MultiBurst'] = {
-              "ifos" : event.ifos,
-              "start_time" : event.start_time,
-              "start_time_ns" : event.start_time_ns,
-              "duration" : event.duration,
-              "peak_time" : event.peak_time,
-              "peak_time_ns" : event.peak_time_ns,
-              "central_freq" : event.central_freq,
-              "bandwidth" : event.bandwidth,
-              "amplitude" : event.amplitude,
-              "snr" : event.snr,
-              "confidence" : event.confidence,
-              "false_alarm_rate" : event.false_alarm_rate,
-              "ligo_axis_ra" : event.ligo_axis_ra,
-              "ligo_axis_dec" : event.ligo_axis_dec,
-              "ligo_angle" : event.ligo_angle,
-              "ligo_angle_sig" : event.ligo_angle_sig,
-              }
-    except:
-        pass
-
-    # Finally add extra attributes for any SingleInspiral objects associated with this event
-    # This will be a list of dictionaries.
-    si_set = event.singleinspiral_set.all()
-    if si_set.count():
-        rv['extra_attributes']['SingleInspiral'] = [ singleInspiralToDict(si) for si in si_set ]
+    # XXX These extra attributes should only be seen by internal users.
+    if request and request.user and not is_external(request.user):
+        rv['extra_attributes'] = {}
+        try:
+            # GrbEvent
+            rv['extra_attributes']['GRB'] = {
+                  "ivorn" : event.ivorn,
+                  "author_ivorn" : event.author_ivorn,
+                  "author_shortname" : event.author_shortname,
+                  "observatory_location_id" : event.observatory_location_id,
+                  "coord_system" : event.coord_system,
+                  "ra" : event.ra,
+                  "dec" : event.dec,
+                  "error_radius" : event.error_radius,
+                  "how_description" : event.how_description,
+                  "how_reference_url" : event.how_reference_url,
+                  "T90" : event.t90,
+                  "trigger_duration": event.trigger_duration,
+                  "designation": event.designation,
+                  "redshift": event.redshift,
+                  "trigger_id": event.trigger_id,
+                  }
+        except:
+            pass
+        try:
+            # CoincInspiralEvent
+            rv['extra_attributes']['CoincInspiral'] = {
+                  "ifos" : event.ifos,
+                  "end_time" : event.end_time,
+                  "end_time_ns" : event.end_time_ns,
+                  "mass" : event.mass,
+                  "mchirp" : event.mchirp,
+                  "minimum_duration" : event.minimum_duration,
+                  "snr" : event.snr,
+                  "false_alarm_rate" : event.false_alarm_rate,
+                  "combined_far" : event.combined_far,
+                  }
+        except:
+            pass
+        try:
+            # SimInspiralEvent
+            rv['extra_attributes']['SimInspiral'] = {
+                    "source_channel": event.source_channel,
+                    "destination_channel": event.destination_channel,
+                    "mass1": event.mass1,
+                    "mass2": event.mass2,
+                    "eta": event.eta,
+                    "mchirp": event.mchirp,
+                    "amp_order": event.amp_order,
+                    "coa_phase": event.coa_phase,
+                    "spin1y": event.spin1y,
+                    "spin1x": event.spin1x,
+                    "spin1z": event.spin1z,
+                    "spin2x": event.spin2x,
+                    "spin2y": event.spin2y,
+                    "spin2z": event.spin2z,
+                    "geocent_end_time": event.geocent_end_time,
+                    "geocent_end_time_ns": event.geocent_end_time_ns,
+                    "end_time_gmst": event.end_time_gmst,
+                    "f_lower": event.f_lower,
+                    "f_final": event.f_final,
+                    "distance": event.distance,
+                    "latitude": event.latitude,
+                    "longitude": event.longitude,
+                    "polarization": event.polarization,
+                    "inclination": event.inclination,
+                    "theta0": event.theta0,
+                    "phi0": event.phi0,
+                    "waveform": event.waveform,
+                    "numrel_mode_min": event.numrel_mode_min,
+                    "numrel_mode_max": event.numrel_mode_max,
+                    "numrel_data": event.numrel_data,
+                    "source": event.source,
+                    "taper": event.taper,
+                    "bandpass": event.bandpass,
+                    "alpha": event.alpha,
+                    "beta": event.beta,
+                    "psi0": event.psi0,
+                    "psi3": event.psi3,
+                    "alpha1": event.alpha1,
+                    "alpha2": event.alpha2,
+                    "alpha3": event.alpha3,
+                    "alpha4": event.alpha4,
+                    "alpha5": event.alpha5,
+                    "alpha6": event.alpha6,
+                    "g_end_time": event.g_end_time,
+                    "g_end_time_ns": event.g_end_time_ns,
+                    "h_end_time": event.h_end_time,
+                    "h_end_time_ns": event.h_end_time_ns,
+                    "l_end_time": event.l_end_time,
+                    "l_end_time_ns": event.l_end_time_ns,
+                    "t_end_time": event.t_end_time,
+                    "t_end_time_ns": event.t_end_time_ns,
+                    "v_end_time": event.v_end_time,
+                    "v_end_time_ns": event.v_end_time_ns,
+                    "eff_dist_g": event.eff_dist_g,
+                    "eff_dist_h": event.eff_dist_h,
+                    "eff_dist_l": event.eff_dist_l,
+                    "eff_dist_t": event.eff_dist_t,
+                    "eff_dist_v": event.eff_dist_v,
+                }
+        except:
+            pass
+        try:
+            # MultiBurstEvent
+            rv['extra_attributes']['MultiBurst'] = {
+                  "ifos" : event.ifos,
+                  "start_time" : event.start_time,
+                  "start_time_ns" : event.start_time_ns,
+                  "duration" : event.duration,
+                  "peak_time" : event.peak_time,
+                  "peak_time_ns" : event.peak_time_ns,
+                  "central_freq" : event.central_freq,
+                  "bandwidth" : event.bandwidth,
+                  "amplitude" : event.amplitude,
+                  "snr" : event.snr,
+                  "confidence" : event.confidence,
+                  "false_alarm_rate" : event.false_alarm_rate,
+                  "ligo_axis_ra" : event.ligo_axis_ra,
+                  "ligo_axis_dec" : event.ligo_axis_dec,
+                  "ligo_angle" : event.ligo_angle,
+                  "ligo_angle_sig" : event.ligo_angle_sig,
+                  }
+        except:
+            pass
+
+        # Finally add extra attributes for any SingleInspiral objects associated with this event
+        # This will be a list of dictionaries.
+        si_set = event.singleinspiral_set.all()
+        if si_set.count():
+            rv['extra_attributes']['SingleInspiral'] = [ singleInspiralToDict(si) for si in si_set ]
 
     rv['links'] = {
           "neighbors" : reverse("neighbors", args=[graceid], request=request),
           "log"   : reverse("eventlog-list", args=[graceid], request=request),
-          "embb"   : reverse("embbeventlog-list", args=[graceid], request=request),
+          "emobservations"   : reverse("emobservation-list", args=[graceid], request=request),
           "files" : reverse("files", args=[graceid], request=request),
           "filemeta" : reverse("filemeta", args=[graceid], request=request),
           "labels" : reverse("labels", args=[graceid], request=request),
diff --git a/gracedb/views.py b/gracedb/views.py
index 8b7db09cbabb75ef2e8d30d15ef209e2a45c2a71..909f6815b374a5af544fdc40dc96fb4fefb22ca0 100644
--- a/gracedb/views.py
+++ b/gracedb/views.py
@@ -14,7 +14,7 @@ from django.contrib.auth.models import User, Permission
 from django.contrib.auth.models import Group as AuthGroup
 from django.contrib.contenttypes.models import ContentType
 from permission_utils import filter_events_for_user, user_has_perm
-from permission_utils import internal_user_required
+from permission_utils import internal_user_required, is_external
 from guardian.models import GroupObjectPermission
 
 from view_logic import _createEventFromForm
@@ -241,6 +241,22 @@ def logentry(request, event, num=None):
                 msg = msg + "\n However, the log message itself was saved."
                 return HttpResponse(msg)
 
+        # XXX If the user is external, tag the message appropriately.
+        if is_external(request.user):
+            try:
+                tag = Tag.objects.get(name=settings.EXTERNAL_ACCESS_TAGNAME)
+            except:
+                displayName = request.POST.get('displayName')
+                tag = Tag(name=tagname, displayName=displayName)
+                tag.save()
+            # I'm putting this in a try/except in case the user has already
+            # added the external access tagname somehow, and the following 
+            # would result in an IntegrityError
+            try:
+                tag.eventlogs.add(elog)
+            except:
+                pass
+
     elif request.method == "GET":
         if not user_has_perm(request.user, 'view', event):
             return HttpResponseForbidden("Forbidden")
@@ -248,6 +264,13 @@ def logentry(request, event, num=None):
             elog = event.eventlog_set.filter(N=num)[0]
         except Exception, e:
             raise Http404
+        
+        # Check authorization for this log message
+        if is_external(request.user):
+            tagnames = [t.name for t in elog.tag_set.all()]
+            if settings.EXTERNAL_ACCESS_TAGNAME not in tagnames:
+                msg = "You do not have permission to view this log message."
+                return HttpResponseForbidden(msg)
     else:
         return HttpResponseBadRequest
 
@@ -313,16 +336,15 @@ def view(request, event):
     can_expose_to_lvem, can_protect_from_lvem = get_lvem_perm_status(request,event)
     context['can_expose_to_lvem'] = can_expose_to_lvem
     context['can_protect_from_lvem'] = can_protect_from_lvem
-    lvem_group_name = ''
-    try:
-        lvem_group_name = AuthGroup.objects.get(name__contains='LV-EM').name
-    except:
-        pass
-    context['lvem_group_name'] = lvem_group_name
+    context['lvem_group_name'] = settings.LVEM_GROUP
 
     if event.pipeline.name in settings.GRB_PIPELINES:
         context['can_modify_t90'] = request.user.has_perm('gracedb.t90_grbevent')
 
+    # Is the user an external user? (I.e., not part of the LVC?) The template 
+    # needs to know that in order to decide what pieces of information to show.
+    context['user_is_external'] = is_external(request.user)
+
     # Does the user have permission to sign off on the event?
     signoff_authorized = False
     # XXX Note that this may not be the best way to perform the authorization check.
@@ -609,6 +631,11 @@ def taglogentry(request, event, num, tagname):
             msg = "Log already has tag %s" % tagname
             return HttpResponse(msg, content_type="text")
         except:
+            # Check authorization
+            if is_external(request.user) and tagname == settings.EXTERNAL_ACCESS_TAGNAME:
+                msg = "You do not have permission to add or remove this tag."
+                return HttpResponseForbidden(msg)
+
             # Look for the tag.  If it doesn't already exist, create it.
             try:
                 tag = Tag.objects.filter(name=tagname)[0]
@@ -686,9 +713,24 @@ def performance(request):
 @event_and_auth_required
 def file_list(request, event):
     f = []
-    for dirname, dirnames, filenames in os.walk(event.datadir()):
-        f.extend(filenames)
-        break
+    if is_external(request.user):
+        # Construct the file list, filtering as necessary:
+        for l in event.eventlog_set.all():
+            filename = l.filename
+            if len(filename):
+                version = l.file_version
+                tagnames = [t.name for t in l.tag_set.all()]
+                if settings.EXTERNAL_ACCESS_TAGNAME not in tagnames:
+                    continue
+                if version>=0:
+                    f.append(filename + ',' + str(version))
+                # We only want the unadorned filename once.
+                if filename not in f:
+                    f.append(filename)
+    else:
+        for dirname, dirnames, filenames in os.walk(event.datadir()):
+            f.extend(filenames)
+            break
 
     context = {}
     context['file_list'] = f
diff --git a/settings/default.py b/settings/default.py
index 73cc15a16591c9cc3c99032a3bb3c9cebe83bb0e..d34996d78ea091439a4b938ec15b7325eaa4a45b 100644
--- a/settings/default.py
+++ b/settings/default.py
@@ -47,6 +47,12 @@ EMBB_IGNORE_ADDRESSES = ['Mailer-Daemon@gracedb.phys.uwm.edu',]
 # Added for django 1.7.8
 TEST_RUNNER = 'django.test.runner.DiscoverRunner'
 
+# Some proper names related to authorization
+LVC_GROUP = 'Communities:LSCVirgoLIGOGroupMembers'
+LVEM_GROUP = 'gw-astronomy:LV-EM'
+EXEC_GROUP = 'executives'
+EXTERNAL_ACCESS_TAGNAME = 'lvem'
+
 # 11/18/14. No longer checking XMPP_ALERT_CHANNELS. This is not necessary.
 # If someone sends out an event, an alert should go out. Listerers have the 
 # option to unsubscribe from nodes if so desired.
diff --git a/templates/gracedb/event_detail.html b/templates/gracedb/event_detail.html
index 003559c38acc084d2d6d1c8a6e27cc57108da4af..425676f77431314e05fdd8447f8b8f0fa896b325 100644
--- a/templates/gracedb/event_detail.html
+++ b/templates/gracedb/event_detail.html
@@ -161,12 +161,14 @@
 {% endblock %}
 </div>
 
+{% if not user_is_external %}
 <div class="content-area">
 {# Analysis-specific attributes #}
 {% block analysis_specific %}
 {# This block is empty in the base event_detail template #}
 {% endblock %}
 </div>
+{% endif %}
 
 {# Neighbors #}
 <script type="text/javascript">