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">