def get_message(request, global_id): """ Respond to an HTTP GET "<global_id>/message" API call. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get the messages to return. At the same time, remember the messages to # delete. message_ids = [] messages = [] query = Message.objects.filter(recipient__global_id=global_id) for message in query.order_by("timestamp"): message_ids.append(message.id) messages.append({'sender' : message.sender.global_id, 'message' : json.loads(message.message)}) # Delete the messages we retrieved. for message_id in message_ids: Message.objects.filter(id=message_id).delete() # Finally, return the messages back to the caller. print("Returning messages: {}".format(json.dumps(messages))) return JsonResponse(messages, safe=False)
def post_location_session(request, global_id): """ Respond to an HTTP POST "<global_id>/location_session" API call. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Create a new location session for this user, re-using the old one if it # exists. try: session = LocationSession.objects.get(global_id__global_id=global_id) except LocationSession.DoesNotExist: session = None if session == None: session = LocationSession() session.global_id = access_id.global_id session.session_id = uuid.uuid4().hex session.save() # Finally, return the session ID back to the caller. return JsonResponse({'session_id' : session.session_id}, status=201)
def post_location_session(request, global_id): """ Respond to an HTTP POST "<global_id>/location_session" API call. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Create a new location session for this user, re-using the old one if it # exists. try: session = LocationSession.objects.get(global_id__global_id=global_id) except LocationSession.DoesNotExist: session = None if session == None: session = LocationSession() session.global_id = access_id.global_id session.session_id = uuid.uuid4().hex session.save() # Finally, return the session ID back to the caller. return JsonResponse({'session_id': session.session_id}, status=201)
def post_message(request, global_id): """ Respond to an HTTP POST "<global_id>/message" API call. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get our parameters from the body of the request. if request.META['CONTENT_TYPE'] != "application/json": return HttpResponse(status=415) # Unsupported media type. try: request_data = json.loads(request.body.decode("utf-8")) except ValueError: return HttpResponseBadRequest("Invalid JSON request") if "recipient" not in request_data: return HttpResponseBadRequest("Missing recipient") else: recipient = request_data['recipient'] if "message" not in request_data: return HttpResponseBadRequest("Missing message") else: message = request_data['message'] # Create the message. recipient_rec, created = GlobalID.objects.get_or_create( global_id=recipient) message_rec = Message() message_rec.timestamp = utils.current_utc_timestamp() message_rec.sender = access_id.global_id message_rec.recipient = recipient_rec message_rec.message = json.dumps(message) message_rec.save() # Finally, tell the caller that we created the new message print("Created message {} from {} to {}".format(json.dumps(message), access_id.global_id, recipient_rec.global_id)) return HttpResponse(status=201)
def post_message(request, global_id): """ Respond to an HTTP POST "<global_id>/message" API call. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get our parameters from the body of the request. if request.META['CONTENT_TYPE'] != "application/json": return HttpResponse(status=415) # Unsupported media type. try: request_data = json.loads(request.body.decode("utf-8")) except ValueError: return HttpResponseBadRequest("Invalid JSON request") if "recipient" not in request_data: return HttpResponseBadRequest("Missing recipient") else: recipient = request_data['recipient'] if "message" not in request_data: return HttpResponseBadRequest("Missing message") else: message = request_data['message'] # Create the message. recipient_rec,created = GlobalID.objects.get_or_create(global_id=recipient) message_rec = Message() message_rec.timestamp = utils.current_utc_timestamp() message_rec.sender = access_id.global_id message_rec.recipient = recipient_rec message_rec.message = json.dumps(message) message_rec.save() # Finally, tell the caller that we created the new message print("Created message {} from {} to {}".format(json.dumps(message), access_id.global_id, recipient_rec.global_id)) return HttpResponse(status=201)
def get_permission(request, global_id): """ Respond to the HTTP GET "<global_id>/permission" endpoint. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get our query-string parameters. if "global_id" in request.GET: global_id_param = request.GET['global_id'] else: global_id_param = None if "type" in request.GET: type_param = request.GET['type'] else: type_param = None # Build a database query to retrieve the desired set of permissions. query = Permission.objects.filter(issuing_global_id=access_id.global_id) if global_id_param != None: query = query.filter(recipient_global_id__global_id=global_id_param) # Build a list of the matching permissions. permissions = [] for permission in query: if type_param != None: # Only include the permission if it covers the given type of status # update. if not permission.matches_status_type(type_param): continue permissions.append( {'access_type' : permission.access_type, 'global_id' : permission.recipient_global_id.global_id, 'status_type' : permission.status_type}) # Finally, return the results back to the caller. return JsonResponse(permissions, safe=False)
def delete_permission(request, global_id): """ Respond to the HTTP DELETE "<global_id>/permission" endpoint. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get our request parameters. Note that, because Django doesn't parse our # query-string parameters, we have to do it manually. params = urllib.parse.parse_qs(request.META['QUERY_STRING']) if "access_type" not in params or len(params['access_type']) != 1: return HttpResponseBadRequest("Missing or invalid access_type") else: access_type = params['access_type'][0] if "global_id" not in params or len(params['global_id']) != 1: return HttpResponseBadRequest("Missing or invalid global_id") else: global_id = params['global_id'][0] if "status_type" not in params or len(params['status_type']) != 1: return HttpResponseBadRequest("Missing or invalid status_type") else: status_type = params['status_type'][0] # Delete the given permission, if it exists. Permission.objects.filter(issuing_global_id=access_id.global_id, access_type=access_type, recipient_global_id__global_id=global_id, status_type=status_type).delete() return HttpResponse(status=200)
def get_message(request, global_id): """ Respond to an HTTP GET "<global_id>/message" API call. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get the messages to return. At the same time, remember the messages to # delete. message_ids = [] messages = [] query = Message.objects.filter(recipient__global_id=global_id) for message in query.order_by("timestamp"): message_ids.append(message.id) messages.append({ 'sender': message.sender.global_id, 'message': json.loads(message.message) }) # Delete the messages we retrieved. for message_id in message_ids: Message.objects.filter(id=message_id).delete() # Finally, return the messages back to the caller. print("Returning messages: {}".format(json.dumps(messages))) return JsonResponse(messages, safe=False)
def delete_location_session(request, global_id): """ Respond to an HTTP DELETE "<global_id>/location_session" API call. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Delete the location session for this user. try: session = LocationSession.objects.get(global_id__global_id=global_id) except LocationSession.DoesNotExist: return HttpResponseNotFound() session.delete() # Tell the user we succeeded. return HttpResponse(status=200)
def post_status(request, global_id): """ Respond to an HTTP POST "<global_id>/status" API call. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get our parameters from the body of the request. if request.META['CONTENT_TYPE'] != "application/json": return HttpResponse(status=415) # Unsupported media type. try: request_data = json.loads(request.body.decode("utf-8")) except ValueError: return HttpResponseBadRequest("Invalid JSON request") if "type" in request_data: status_type = request_data['type'] else: return HttpResponseBadRequest("Invalid JSON request") if "timestamp" in request_data: status_timestamp = request_data['timestamp'] else: return HttpResponseBadRequest("Invalid JSON request") if "contents" in request_data: status_contents = request_data['contents'] else: return HttpResponseBadRequest("Invalid JSON request") # Check that the parameters are valid. try: status_update_type = StatusUpdateType.objects.get(type=status_type) except StatusUpdateType.DoesNotExist: return HttpResponseBadRequest("Invalid type") try: timestamp = utils.timestamp_to_datetime(status_timestamp) except: return HttpResponseBadRequest("Invalid timestamp") # Based on the type of status update, validate the contents. if status_type == "location/latlong": try: contents = json.loads(status_contents) except ValueError: return HttpResponseBadRequest("Invalid lat/long contents") if not isinstance(contents, dict): return HttpResponseBadRequest("Invalid lat/long contents") if sorted(contents.keys()) != ["latitude", "longitude", "type"]: return HttpResponseBadRequest("Invalid lat/long contents") if not isinstance(contents['latitude'], (int, float)): return HttpResponseBadRequest("Invalid latitude value") if contents['latitude'] < -90 or contents['latitude'] > 90: return HttpResponseBadRequest("Invalid latitude value") if not isinstance(contents['longitude'], (int, float)): return HttpResponseBadRequest("Invalid longitude value") if contents['longitude'] < -180 or contents['longitude'] > 180: return HttpResponseBadRequest("Invalid longitude value") # Create the status update itself. utc_datetime,tz_offset = utils.datetime_to_utc_and_timezone(timestamp) update = StatusUpdate() update.global_id = access_id.global_id update.type = status_update_type update.timestamp = utc_datetime update.tz_offset = tz_offset update.contents = status_contents update.save() # Now, based upon the permissions, create a CurrentStatusUpdateView record # for each global ID able to view this status update. for permission in Permission.objects.filter( issuing_global_id=access_id.global_id, access_type=Permission.ACCESS_TYPE_CURRENT): if permission.matches_status_type(status_type): # Create a CurrentStatusUpdateView record for this recipient, # replacing the existing one if it exists. try: view = CurrentStatusUpdateView.objects.get( issuing_global_id=access_id.global_id, recipient_global_id=permission.recipient_global_id, type=status_update_type) except CurrentStatusUpdateView.DoesNotExist: view = None if view != None: view.delete() view = CurrentStatusUpdateView() view.issuing_global_id = access_id.global_id view.recipient_global_id = permission.recipient_global_id view.status_update = update view.type = status_update_type view.timestamp = utc_datetime view.tz_offset = tz_offset view.contents = status_contents view.save() # Finally, tell the caller that we created the new status update. return HttpResponse(status=201)
def history(request, global_id): """ Respond to the /history endpoint. """ if request.method != "GET": return HttpResponseNotAllowed(["GET"]) # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get our request parameters. if "global_id" in request.GET: global_id_param = request.GET['global_id'] else: return HttpResponseBadRequest("Missing request params") if "type" in request.GET: type_param = request.GET['type'] else: return HttpResponseBadRequest("Missing request params") if "more" in request.GET: more_param = request.GET['more'] else: more_param = None # Check that the request is valid. try: status_type = StatusUpdateType.objects.get(type=type_param) except StatusUpdateType.DoesNotExist: return HttpResponseBadRequest("Invalid type") if access_id.global_id.global_id != global_id_param: # The user is attempting to access someone else's history. Make sure # the other user has created a Permission record to allow this. has_permission = False # initially. for permission in Permission.objects.filter( issuing_global_id__global_id=global_id_param, access_type=Permission.ACCESS_TYPE_HISTORY, recipient_global_id=access_id.global_id): if permission.matches_status_type(type_param): has_permission = True break if not has_permission: return HttpResponseForbidden() if more_param != None: try: more_param = int(more_param) except ValueError: return HttpResponseBadRequest("Invalid more parameter") # Build the database query to retrieve the desired set of status updates. # Note that we use a Paginator to paginate the results. query = StatusUpdate.objects.filter(global_id__global_id=global_id_param, type=status_type) query = query.order_by("-timestamp") paginator = Paginator(query, MAX_PAGE_SIZE) if more_param != None: page_num = more_param else: page_num = 1 if page_num > paginator.num_pages: updates = [] more = None else: updates = [] for update in paginator.page(page_num): date_time = utils.utc_and_timezone_to_datetime( update.timestamp, update.tz_offset) updates.append({ 'global_id': update.global_id.global_id, 'type': update.type.type, 'timestamp': utils.datetime_to_timestamp(date_time), 'contents': update.contents }) if page_num < paginator.num_pages: more = str(page_num + 1) else: more = None # Finally, return the results back to the caller. return JsonResponse({'updates': updates, 'more': more})
def get_status(request, global_id): """ Respond to an HTTP GET "<global_id>/status" API call. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get our request parameters. if "own" in request.GET and request.GET['own'] == "1": param_own = True else: param_own = False if "global_id" in request.GET: param_global_id = request.GET['global_id'] else: param_global_id = None if "type" in request.GET: param_type = request.GET['type'] else: param_type = None if "since" in request.GET and request.GET['since'] != "ALL": try: param_since = utils.timestamp_to_datetime(request.GET['since']) except ValueError: return HttpResponseBadRequest("Invalid 'since' value") else: param_since = None # Build the database query to retrieve the desired set of # CurrentStatusUpdateView records. query = CurrentStatusUpdateView.objects.all() if param_own: query = query.filter(issuing_global_id=access_id.global_id) else: query = query.filter(recipient_global_id=access_id.global_id) if param_global_id != None: query = query.filter(issuing_global_id__global_id=param_global_id) if param_type != None: query = query.filter(type__type=param_type) if param_since != None: query = query.filter(timestamp__gt=param_since) # Assemble the list of status updates which match our database query. updates = [] latest = None for view in query: date_time = utils.utc_and_timezone_to_datetime(view.timestamp, view.tz_offset) updates.append({ 'global_id': view.issuing_global_id.global_id, 'type': view.type.type, 'timestamp': utils.datetime_to_timestamp(date_time), 'contents': view.contents }) if latest == None or view.timestamp > latest: latest = view.timestamp # Calculate the 'since' value to use for retrieving only the new updates. if latest == None: if param_since == None: since = "ALL" else: since = utils.datetime_to_timestamp(param_since) else: since = utils.datetime_to_timestamp(latest) # Finally, return the results back to the caller. return JsonResponse({'updates': updates, 'since': since})
def post_status(request, global_id): """ Respond to an HTTP POST "<global_id>/status" API call. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get our parameters from the body of the request. if request.META['CONTENT_TYPE'] != "application/json": return HttpResponse(status=415) # Unsupported media type. try: request_data = json.loads(request.body.decode("utf-8")) except ValueError: return HttpResponseBadRequest("Invalid JSON request") if "type" in request_data: status_type = request_data['type'] else: return HttpResponseBadRequest("Invalid JSON request") if "timestamp" in request_data: status_timestamp = request_data['timestamp'] else: return HttpResponseBadRequest("Invalid JSON request") if "contents" in request_data: status_contents = request_data['contents'] else: return HttpResponseBadRequest("Invalid JSON request") # Check that the parameters are valid. try: status_update_type = StatusUpdateType.objects.get(type=status_type) except StatusUpdateType.DoesNotExist: return HttpResponseBadRequest("Invalid type") try: timestamp = utils.timestamp_to_datetime(status_timestamp) except: return HttpResponseBadRequest("Invalid timestamp") # Based on the type of status update, validate the contents. if status_type == "location/latlong": try: contents = json.loads(status_contents) except ValueError: return HttpResponseBadRequest("Invalid lat/long contents") if not isinstance(contents, dict): return HttpResponseBadRequest("Invalid lat/long contents") if sorted(contents.keys()) != ["latitude", "longitude", "type"]: return HttpResponseBadRequest("Invalid lat/long contents") if not isinstance(contents['latitude'], (int, float)): return HttpResponseBadRequest("Invalid latitude value") if contents['latitude'] < -90 or contents['latitude'] > 90: return HttpResponseBadRequest("Invalid latitude value") if not isinstance(contents['longitude'], (int, float)): return HttpResponseBadRequest("Invalid longitude value") if contents['longitude'] < -180 or contents['longitude'] > 180: return HttpResponseBadRequest("Invalid longitude value") # Create the status update itself. utc_datetime, tz_offset = utils.datetime_to_utc_and_timezone(timestamp) update = StatusUpdate() update.global_id = access_id.global_id update.type = status_update_type update.timestamp = utc_datetime update.tz_offset = tz_offset update.contents = status_contents update.save() # Now, based upon the permissions, create a CurrentStatusUpdateView record # for each global ID able to view this status update. for permission in Permission.objects.filter( issuing_global_id=access_id.global_id, access_type=Permission.ACCESS_TYPE_CURRENT): if permission.matches_status_type(status_type): # Create a CurrentStatusUpdateView record for this recipient, # replacing the existing one if it exists. try: view = CurrentStatusUpdateView.objects.get( issuing_global_id=access_id.global_id, recipient_global_id=permission.recipient_global_id, type=status_update_type) except CurrentStatusUpdateView.DoesNotExist: view = None if view != None: view.delete() view = CurrentStatusUpdateView() view.issuing_global_id = access_id.global_id view.recipient_global_id = permission.recipient_global_id view.status_update = update view.type = status_update_type view.timestamp = utc_datetime view.tz_offset = tz_offset view.contents = status_contents view.save() # Finally, tell the caller that we created the new status update. return HttpResponse(status=201)
def history(request, global_id): """ Respond to the /history endpoint. """ if request.method != "GET": return HttpResponseNotAllowed(["GET"]) # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get our request parameters. if "global_id" in request.GET: global_id_param = request.GET['global_id'] else: return HttpResponseBadRequest("Missing request params") if "type" in request.GET: type_param = request.GET['type'] else: return HttpResponseBadRequest("Missing request params") if "more" in request.GET: more_param = request.GET['more'] else: more_param = None # Check that the request is valid. try: status_type = StatusUpdateType.objects.get(type=type_param) except StatusUpdateType.DoesNotExist: return HttpResponseBadRequest("Invalid type") if access_id.global_id.global_id != global_id_param: # The user is attempting to access someone else's history. Make sure # the other user has created a Permission record to allow this. has_permission = False # initially. for permission in Permission.objects.filter( issuing_global_id__global_id=global_id_param, access_type=Permission.ACCESS_TYPE_HISTORY, recipient_global_id=access_id.global_id): if permission.matches_status_type(type_param): has_permission = True break if not has_permission: return HttpResponseForbidden() if more_param != None: try: more_param = int(more_param) except ValueError: return HttpResponseBadRequest("Invalid more parameter") # Build the database query to retrieve the desired set of status updates. # Note that we use a Paginator to paginate the results. query = StatusUpdate.objects.filter(global_id__global_id=global_id_param, type=status_type) query = query.order_by("-timestamp") paginator = Paginator(query, MAX_PAGE_SIZE) if more_param != None: page_num = more_param else: page_num = 1 if page_num > paginator.num_pages: updates = [] more = None else: updates = [] for update in paginator.page(page_num): date_time = utils.utc_and_timezone_to_datetime(update.timestamp, update.tz_offset) updates.append( {'global_id' : update.global_id.global_id, 'type' : update.type.type, 'timestamp' : utils.datetime_to_timestamp(date_time), 'contents' : update.contents}) if page_num < paginator.num_pages: more = str(page_num + 1) else: more = None # Finally, return the results back to the caller. return JsonResponse({'updates' : updates, 'more' : more})
def get_status(request, global_id): """ Respond to an HTTP GET "<global_id>/status" API call. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get our request parameters. if "own" in request.GET and request.GET['own'] == "1": param_own = True else: param_own = False if "global_id" in request.GET: param_global_id = request.GET['global_id'] else: param_global_id = None if "type" in request.GET: param_type = request.GET['type'] else: param_type = None if "since" in request.GET and request.GET['since'] != "ALL": try: param_since = utils.timestamp_to_datetime(request.GET['since']) except ValueError: return HttpResponseBadRequest("Invalid 'since' value") else: param_since = None # Build the database query to retrieve the desired set of # CurrentStatusUpdateView records. query = CurrentStatusUpdateView.objects.all() if param_own: query = query.filter(issuing_global_id=access_id.global_id) else: query = query.filter(recipient_global_id=access_id.global_id) if param_global_id != None: query = query.filter(issuing_global_id__global_id=param_global_id) if param_type != None: query = query.filter(type__type=param_type) if param_since != None: query = query.filter(timestamp__gt=param_since) # Assemble the list of status updates which match our database query. updates = [] latest = None for view in query: date_time = utils.utc_and_timezone_to_datetime(view.timestamp, view.tz_offset) updates.append( {'global_id' : view.issuing_global_id.global_id, 'type' : view.type.type, 'timestamp' : utils.datetime_to_timestamp(date_time), 'contents' : view.contents}) if latest == None or view.timestamp > latest: latest = view.timestamp # Calculate the 'since' value to use for retrieving only the new updates. if latest == None: if param_since == None: since = "ALL" else: since = utils.datetime_to_timestamp(param_since) else: since = utils.datetime_to_timestamp(latest) # Finally, return the results back to the caller. return JsonResponse({'updates' : updates, 'since' : since})
def post_permission(request, global_id): """ Respond to the HTTP POST "<global_id>/permission" endpoint. """ # Check the HMAC authentication. access_id = utils.get_access_id(global_id) if access_id == None: return HttpResponseForbidden() if not hmac.check_hmac_authentication(request, access_id.access_secret): return HttpResponseForbidden() # Get our request parameters. if request.META['CONTENT_TYPE'] != "application/json": return HttpResponse(status=415) # Unsupported media type. try: request_data = json.loads(request.body.decode("utf-8")) except ValueError: return HttpResponseBadRequest("Invalid JSON request") if "access_type" in request_data: access_type = request_data['access_type'] else: return HttpResponseBadRequest("Invalid JSON request") if "global_id" in request_data: global_id = request_data['global_id'] else: return HttpResponseBadRequest("Invalid JSON request") if "status_type" in request_data: status_type = request_data['status_type'] else: return HttpResponseBadRequest("Invalid JSON request") # Check that the parameters are valid. if access_type not in ["CURRENT", "HISTORY"]: return HttpResponseBadRequest("Invalid access_type") if "*" in status_type and not status_type.endswith("*"): return HttpResponseBadRequest("Invalid status_type") # If necessary, create a GlobalID record for the recipient. global_id_rec,created = GlobalID.objects.get_or_create(global_id=global_id) # Create the new permission. permission = Permission() permission.issuing_global_id = access_id.global_id permission.access_type = access_type permission.recipient_global_id = global_id_rec permission.status_type = status_type permission.save() # Finally, tell the caller the good news. return HttpResponse(status=201)