def time_set(request): """ Receives a date-time string and sets the system date-time RPi only. """ if not settings.ENABLE_CLOCK_SET: return JsonResponseMessageError( _("This can only be done on Raspberry Pi systems"), status=403) # Form does all the data validation - including ensuring that the data passed is a proper date time. # This is necessary to prevent arbitrary code being run on the system. form = DateTimeForm(data=simplejson.loads(request.raw_post_data)) if not form.is_valid(): return JsonResponseMessageError( _("Could not read date and time: Unrecognized input data format.")) try: if os.system('sudo date +%%F%%T -s "%s"' % form.data["date_time"]): raise PermissionDenied except PermissionDenied as e: return JsonResponseMessageError( _("System permissions prevented time setting, please run with root permissions" )) now = datetime.datetime.now().isoformat(" ").split(".")[0] return JsonResponseMessage( _("System time was reset successfully; current system time: %s") % now)
def save_video_log(request): """ Receives a video_id and relevant data, saves it to the currently authorized user. """ # Form does all the data validation, including the video_id form = VideoLogForm(data=simplejson.loads(request.raw_post_data)) if not form.is_valid(): raise ValidationError(form.errors) data = form.data user = request.session["facility_user"] try: videolog = VideoLog.update_video_log( facility_user=user, video_id=data["video_id"], youtube_id=data["youtube_id"], total_seconds_watched=data[ "total_seconds_watched"], # don't set incrementally, to avoid concurrency issues points=data["points"], language=data.get("language") or request.language, ) except ValidationError as e: return JsonResponseMessageError(_("Could not save VideoLog: %s") % e) if "points" in request.session: del request.session["points"] # will be recomputed when needed return JsonResponse({ "points": videolog.points, "complete": videolog.complete, "messages": {}, })
def launch_mplayer(request): """ Launch an mplayer instance in a new thread, to play the video requested via the API. """ if not settings.USE_MPLAYER: raise PermissionDenied( "You can only initiate mplayer if USE_MPLAYER is set to True.") if "youtube_id" not in request.REQUEST: return JsonResponseMessageError(_("No youtube_id specified")) youtube_id = request.REQUEST["youtube_id"] video_id = request.REQUEST["video_id"] facility_user = request.session.get("facility_user") callback = partial( _update_video_log_with_points, video_id=video_id, youtube_id=youtube_id, facility_user=facility_user, language=request.language, ) play_video_in_new_thread(youtube_id, content_root=settings.CONTENT_ROOT, callback=callback) return JsonResponse({})
def server_restart(request): try: server_restart_util(request) return JsonResponse({}) except Exception as e: return JsonResponseMessageError( _("Unable to restart the server; please restart manually. Error: %(error_info)s" ) % {"error_info": e})
def save_exercise_log(request): """ Receives an exercise_id and relevant data, saves it to the currently authorized user. """ # Form does all data validation, including of the exercise_id form = ExerciseLogForm(data=simplejson.loads(request.raw_post_data)) if not form.is_valid(): raise Exception(form.errors) data = form.data # More robust extraction of previous object user = request.session["facility_user"] (exerciselog, was_created) = ExerciseLog.get_or_initialize( user=user, exercise_id=data["exercise_id"]) previously_complete = exerciselog.complete exerciselog.attempts = data[ "attempts"] # don't increment, because we fail to save some requests exerciselog.streak_progress = data["streak_progress"] exerciselog.points = data["points"] exerciselog.language = data.get("language") or request.language try: exerciselog.full_clean() exerciselog.save() except ValidationError as e: return JsonResponseMessageError( _("Could not save ExerciseLog") + u": %s" % e) if "points" in request.session: del request.session["points"] # will be recomputed when needed # Special message if you've just completed. # NOTE: it's important to check this AFTER calling save() above. if not previously_complete and exerciselog.complete: exercise = get_node_cache("Exercise").get(data["exercise_id"], [None])[0] junk, next_exercise = get_neighbor_nodes( exercise, neighbor_kind="Exercise") if exercise else None if next_exercise: return JsonResponse({ "success": _("You have mastered this exercise! Please continue on to <a href='%(href)s'>%(title)s</a>" ) % { "href": next_exercise["path"], "title": _(next_exercise["title"]), } }) else: return JsonResponse({ "success": _("You have mastered this exercise and this topic!") }) # Return no message in release mode; "data saved" message in debug mode. return JsonResponse({})
def wrapper_fn_pfr(request, *args, **kwargs): if request.GET.get("process_id", None): # Get by ID--direct! if not isnumeric(request.GET["process_id"]): return JsonResponseMessageError( _("process_id is not numeric.")) else: process_log = get_object_or_404(UpdateProgressLog, id=request.GET["process_id"]) elif request.GET.get("process_name", None): process_name = request.GET["process_name"] if "start_time" not in request.GET: start_time = datetime.datetime.now() else: start_time = make_naive( dateutil.parser.parse(request.GET["start_time"]), get_current_timezone()) try: # Get the latest one of a particular name--indirect process_log = UpdateProgressLog.get_active_log( process_name=process_name, create_new=False) if not process_log: # Still waiting; get the very latest, at least. logs = UpdateProgressLog.objects \ .filter(process_name=process_name, completed=True, end_time__gt=start_time) \ .order_by("-end_time") if logs: process_log = logs[0] except Exception as e: # The process finished before we started checking, or it's been deleted. # Best to complete silently, but for debugging purposes, will make noise for now. return JsonResponseMessageError(unicode(e)) else: return JsonResponse( {"error": _("Must specify process_id or process_name")}) return handler(request, process_log, *args, **kwargs)
def wrapper_fn(request): if request.raw_post_data: data = simplejson.loads(request.raw_post_data) else: data = request.GET try: if "client_nonce" not in data: return JsonResponseMessageError( "Client nonce must be specified.") session = SyncSession.objects.get( client_nonce=data["client_nonce"]) if not session.verified: return JsonResponseMessageError( "Session has not yet been verified.") if session.closed: return JsonResponseMessageError("Session is already closed.") except SyncSession.DoesNotExist: return JsonResponseMessageError( "Session with specified client nonce could not be found.") response = handler(data, session) session.save() return response
def get_exercise_logs(request): """ Given a list of exercise_ids, retrieve a list of video logs for this user. """ data = simplejson.loads(request.raw_post_data or "[]") if not isinstance(data, list): return JsonResponseMessageError( _("Could not load ExerciseLog objects: Unrecognized input data format." )) user = request.session["facility_user"] logs = ExerciseLog.objects \ .filter(user=user, exercise_id__in=data) \ .values("exercise_id", "streak_progress", "complete", "points", "struggling", "attempts") return JsonResponse(list(logs))
def get_video_logs(request): """ Given a list of video_ids, retrieve a list of video logs for this user. """ data = simplejson.loads(request.raw_post_data or "[]") if not isinstance(data, list): return JsonResponseMessageError( _("Could not load VideoLog objects: Unrecognized input data format." )) user = request.session["facility_user"] logs = VideoLog.objects \ .filter(user=user, video_id__in=data) \ .values("video_id", "complete", "total_seconds_watched", "points") return JsonResponse(list(logs))
def model_upload(data, session): """This device is getting data-related objects from another device.""" if "models" not in data: return JsonResponseMessageError("Must provide models.", data={"saved_model_count": 0}) try: # Unserialize, knowing that the models were serialized by a client of its given version. # dest_version assumed to be this device's version result = save_serialized_models(data["models"], src_version=session.client_version) except Exception as e: logging.debug("Exception uploading models: %s" % e) result = {"error": e.message, "saved_model_count": 0} session.models_uploaded += result["saved_model_count"] session.errors += result.has_key("error") return JsonResponse(result)
def model_download(data, session): """This device is having its own data downloaded""" if "device_counters" not in data: return JsonResponseMessageError("Must provide device counters.", data={"count": 0}) try: # Return the objects serialized to the version of the other device. result = get_serialized_models(data["device_counters"], zone=session.client_device.get_zone(), include_count=True, dest_version=session.client_version) except Exception as e: logging.debug("Exception downloading models: %s" % e) result = {"error": e.message, "count": 0} session.models_downloaded += result["count"] session.errors += result.has_key("error") return JsonResponse(result)
def register_device(request): """Receives the client device info from the distributed server. Tries to register either because the device has been pre-registered, or because it has a valid INSTALL_CERTIFICATE.""" # attempt to load the client device data from the request data data = simplejson.loads(request.raw_post_data or "{}") if "client_device" not in data: return JsonResponseMessageError( "Serialized client device must be provided.") try: # When hand-shaking on the device models, since we don't yet know the version, # we have to just TRY with our own version. # # This is currently "central server" code, so # this will only fail (currently) if the central server version # is less than the version of a client--something that should never happen try: local_version = Device.get_own_device().get_version() models = engine.deserialize(data["client_device"], src_version=local_version, dest_version=local_version) except db_models.FieldDoesNotExist as fdne: raise Exception( "Central server version is lower than client version. This is ... impossible!" ) client_device = models.next().object except Exception as e: return JsonResponseMessageError( "Could not decode the client device model: %s" % e, code="client_device_corrupted") # Validate the loaded data if not isinstance(client_device, Device): return JsonResponseMessageError( "Client device must be an instance of the 'Device' model.", code="client_device_not_device") if not client_device.verify(): return JsonResponseMessageError( "Client device must be self-signed with a signature matching its own public key.", code="client_device_invalid_signature") try: zone = register_self_registered_device(client_device, models, data) except Exception as e: if e.message == "Client not yet on zone.": zone = None else: # Client not on zone: allow fall-through via "old route" # This is the codepath for unregistered devices trying to start a session. # This would only get hit, however, if they visit the registration page. # But still, good to keep track of! UnregisteredDevicePing.record_ping(id=client_device.id, ip=get_request_ip(request)) return JsonResponseMessageError( "Failed to validate the chain of trust (%s)." % e, code="chain_of_trust_invalid") if not zone: # old code-path try: registration = RegisteredDevicePublicKey.objects.get( public_key=client_device.public_key) if not registration.is_used(): registration.use() elif get_object_or_None(Device, public_key=client_device.public_key): return JsonResponseMessageError( "This device has already been registered", code="device_already_registered") else: # If not... we're in a very weird state--we have a record of their # registration, but no device record. # Let's just let the registration happens, so we can refresh things here. # No harm, and some failsafe benefit. # So, pass through... no code :) pass # Use the RegisteredDevicePublicKey, now that we've initialized the device and put it in its zone zone = registration.zone except RegisteredDevicePublicKey.DoesNotExist: try: device = Device.objects.get( public_key=client_device.public_key) return JsonResponseMessageError( "This device has already been registered", code="device_already_registered") except Device.DoesNotExist: return JsonResponseMessageError( "Device registration with public key not found; login and register first?", code="public_key_unregistered") client_device.save(imported=True) try: device_zone = DeviceZone.objects.get(device=client_device, zone=zone) device_zone.save( ) # re-save, to give it a central server signature that will be honored by old clients except DeviceZone.DoesNotExist: device_zone = DeviceZone(device=client_device, zone=zone) device_zone.save( ) # create the DeviceZone for the new device, with an 'upgraded' signature # return our local (server) Device, its Zone, and the newly created DeviceZone, to the client # Note the order :) # # Addition: always back central server object--in case they didn't get it during install, # they need it for software updating. return JsonResponse( engine.serialize([ Device.get_central_server(), Device.get_own_device(), zone, device_zone ], dest_version=client_device.version, ensure_ascii=False))
def create_session(request): data = simplejson.loads(request.raw_post_data or "{}") if "client_nonce" not in data: return JsonResponseMessageError("Client nonce must be specified.") if len(data["client_nonce"]) != 32 or re.match("[^0-9a-fA-F]", data["client_nonce"]): return JsonResponseMessageError( "Client nonce is malformed (must be 32-digit hex).") if "client_device" not in data: return JsonResponseMessageError("Client device must be specified.") if "server_nonce" not in data: if SyncSession.objects.filter( client_nonce=data["client_nonce"]).count(): return JsonResponseMessageError( "Session already exists; include server nonce and signature.") session = SyncSession() session.client_nonce = data["client_nonce"] session.client_os = data.get("client_os", "") session.client_version = data.get("client_version", "") session.ip = get_request_ip(request) try: client_device = Device.objects.get(pk=data["client_device"]) session.client_device = client_device except Device.DoesNotExist: # This is the codepath for unregistered devices trying to start a session. # This would only get hit, however, if they manually run syncmodels. # But still, good to keep track of! UnregisteredDevicePing.record_ping(id=data["client_device"], ip=session.ip) return JsonResponseMessageError( "Client device matching id could not be found. (id=%s)" % data["client_device"]) session.server_nonce = uuid.uuid4().hex session.server_device = Device.get_own_device() if session.client_device.pk == session.server_device.pk: return JsonResponseMessageError( "I know myself when I see myself, and you're not me.") session.save() else: try: session = SyncSession.objects.get( client_nonce=data["client_nonce"]) except SyncSession.DoesNotExist: return JsonResponseMessageError( "Session with specified client nonce could not be found.") if session.server_nonce != data["server_nonce"]: return JsonResponseMessageError( "Server nonce did not match saved value.") if not data.get("signature", ""): return JsonResponseMessageError("Must include signature.") if not session.verify_client_signature(data["signature"]): return JsonResponseMessageError("Signature did not match.") session.verified = True session.save() # Return the serializd session, in the version intended for the other device return JsonResponse({ "session": serialize([session], dest_version=session.client_version, ensure_ascii=False, sign=False, increment_counters=False), "signature": session.sign(), })
def update_all_distributed_callback(request): """ """ if request.method != "POST": raise PermissionDenied("Only POST allowed to this URL endpoint.") videos = json.loads(request.POST["video_logs"]) exercises = json.loads(request.POST["exercise_logs"]) user = FacilityUser.objects.get(id=request.POST["user_id"]) node_cache = get_node_cache() # Save videos n_videos_uploaded = 0 for video in videos: video_id = video['video_id'] youtube_id = video['youtube_id'] # Only save video logs for videos that we recognize. if video_id not in node_cache["Video"]: logging.warn("Skipping unknown video %s" % video_id) continue try: (vl, _) = VideoLog.get_or_initialize(user=user, video_id=video_id, youtube_id=youtube_id) for key, val in video.iteritems(): setattr(vl, key, val) logging.debug("Saving video log for %s: %s" % (video_id, vl)) vl.save() n_videos_uploaded += 1 except KeyError: # logging.error( "Could not save video log for data with missing values: %s" % video) except Exception as e: error_message = "Unexpected error importing videos: %s" % e return JsonResponseMessageError(error_message) # Save exercises n_exercises_uploaded = 0 for exercise in exercises: # Only save video logs for videos that we recognize. if exercise['exercise_id'] not in node_cache['Exercise']: logging.warn("Skipping unknown video %s" % exercise['exercise_id']) continue try: (el, _) = ExerciseLog.get_or_initialize( user=user, exercise_id=exercise["exercise_id"]) for key, val in exercise.iteritems(): setattr(el, key, val) logging.debug("Saving exercise log for %s: %s" % (exercise['exercise_id'], el)) el.save() n_exercises_uploaded += 1 except KeyError: logging.error( "Could not save exercise log for data with missing values: %s" % exercise) except Exception as e: error_message = "Unexpected error importing exercises: %s" % e return JsonResponseMessageError(error_message) return JsonResponse({ "success": "Uploaded %d exercises and %d videos" % (n_exercises_uploaded, n_videos_uploaded) })