Пример #1
0
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(
            _("Time reset can only be done on Raspberry Pi systems."))

    # 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.body))
    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."))
Пример #2
0
    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 JsonResponseMessageError(_("Must specify process_id or process_name"))

        return handler(request, process_log, *args, **kwargs)
Пример #3
0
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)  # has to be that video_id, could be any 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: %(err_msg)s") % {"err_msg": 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: %(err_msg)s") % {"err_msg": e}
            return JsonResponseMessageError(error_message)

    return JsonResponseMessageSuccess(_("Uploaded %(num_exercises)d exercises and %(num_videos)d videos") % {
        "num_exercises": n_exercises_uploaded,
        "num_videos": n_videos_uploaded,
    })
Пример #4
0
def create_session(request):
    data = simplejson.loads(request.body 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(),
    })
Пример #5
0
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: %(err)s") % {"err": e})

    if "points" in request.session:
        del request.session["points"]  # will be recomputed when needed

    return JsonResponse({
        "points": videolog.points,
        "complete": videolog.complete,
    })
Пример #6
0
def handler_403(request, *args, **kwargs):
    context = RequestContext(request)
    #message = None  # Need to retrieve, but can't figure it out yet.

    if request.is_ajax():
        return JsonResponseMessageError(_("You must be logged in with an account authorized to view this page (API)."), status=403)
    else:
        messages.error(request, mark_safe(_("You must be logged in with an account authorized to view this page.")))
        return HttpResponseRedirect(set_query_params(reverse("login"), {"next": request.get_full_path()}))
Пример #7
0
 def require_sync_session_wrapper_fn(request):
     if request.body:
         data = simplejson.loads(request.body)
     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
Пример #8
0
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 not next_exercise:
            return JsonResponseMessageSuccess(
                _("You have mastered this exercise and this topic!"))
        else:
            return JsonResponseMessageSuccess(
                _("You have mastered this exercise!  Please continue on to <a href='%(href)s'>%(title)s</a>"
                  ) % {
                      "href": next_exercise["path"],
                      "title": _(next_exercise["title"]),
                  })

    # Return no message in release mode; "data saved" message in debug mode.
    return JsonResponse({})
Пример #9
0
def facility_delete(request, facility_id=None):
    if not request.is_django_user:
        raise PermissionDenied("Teachers cannot delete facilities.")

    if request.method != 'POST':
        return JsonResponseMessageError(_("Method is not allowed."))

    facility_id = facility_id or simplejson.loads(request.body or "{}").get("facility_id")
    fac = get_object_or_404(Facility, id=facility_id)

    fac.soft_delete()
    return JsonResponseMessageSuccess(_("Deleted facility %(facility_name)s successfully.") % {"facility_name": fac.name})
Пример #10
0
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))
Пример #11
0
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:
        print "Exception downloading models (in api_views): %s, %s, %s" % (e.__class__.__name__, e.message, e.args)
        result = { "error": e.message, "count": 0 }

    session.models_downloaded += result["count"]
    session.errors += result.has_key("error")
    return JsonResponse(result)
Пример #12
0
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))
Пример #13
0
def facility_delete(request, facility_id=None):
    if not request.is_django_user:
        raise PermissionDenied("Teachers cannot delete facilities.")

    facility_id = facility_id or simplejson.loads(request.raw_post_data
                                                  or "{}").get("facility_id")
    fac = get_object_or_404(Facility, id=facility_id)
    if not fac.is_deletable():
        return JsonResponseMessageError(
            _("Facility %(facility_name)s is not deletable.") %
            {"facility_name": fac.name})

    fac.delete()
    return JsonResponseMessageSuccess(
        _("Deleted facility %(facility_name)s successfully.") %
        {"facility_name": fac.name})
Пример #14
0
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:
        print "Exception uploading models (in api_views): %s, %s, %s" % (e.__class__.__name__, e.message, e.args)
        result = { "error": e.message, "saved_model_count": 0 }

    session.models_uploaded += result["saved_model_count"]
    session.errors += result.has_key("error")
    return JsonResponse(result)
Пример #15
0
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.body 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=EC.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=EC.CLIENT_DEVICE_NOT_DEVICE)

    try:
        if not client_device.verify():
            # We've been getting this verification error a lot, even when we shouldn't. Send more details to us by email so we can diagnose.
            msg = "\n\n".join([request.body, client_device._hashable_representation(), str(client_device.validate()), client_device.signed_by_id, client_device.id, str(request)])
            send_mail("Client device did not verify", msg, "*****@*****.**", ["*****@*****.**"])
            return JsonResponseMessageError("Client device must be self-signed with a signature matching its own public key!", code=EC.CLIENT_DEVICE_INVALID_SIGNATURE)
    except Exception as e:
        # Can't properly namespace to a particular Exception here, since the only reason we would be getting here is
        # that what should be proper exception namespacing in code being called isn't correctly catching this exception
        msg = "\n\n".join([request.body, client_device._hashable_representation(), "Exception: %s" % e, str(type(e)), client_device.signed_by_id, client_device.id, str(request)])
        send_mail("Exception while verifying client device", msg, "*****@*****.**", ["*****@*****.**"])
        return JsonResponseMessageError("Client device must be self-signed with a signature matching its own public key!", code=EC.CLIENT_DEVICE_INVALID_SIGNATURE)

    try:
        zone = register_self_registered_device(client_device, models, data)
    except Exception as e:
        if e.args[0] == "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=EC.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()

            # Use the RegisteredDevicePublicKey, now that we've initialized the device and put it in its zone
            zone = registration.zone

        except RegisteredDevicePublicKey.DoesNotExist:
            try:
                # A redirect loop here is also possible, if a Device exists in the central server database 
                # corresponding to the client_device, but no corresponding RegisteredDevicePublicKey exists
                device = Device.objects.get(public_key=client_device.public_key)
                return JsonResponseMessageError("This device has already been registered", code=EC.DEVICE_ALREADY_REGISTERED)
            except Device.DoesNotExist:
                return JsonResponseMessageError("Device registration with public key not found; login and register first?", code=EC.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)
    )
Пример #16
0
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))