Exemple #1
0
def set_server_or_user_default_language(request):
    if request.method == 'GET':
        raise Exception(
            _("Can only handle default language changes through GET requests"))

    elif request.method == 'POST':
        data = json.loads(
            request.raw_post_data
        )  # POST is getting interpreted wrong again by Django
        lang_code = data['lang']

        if request.is_django_user and lang_code != get_default_language():
            logging.debug("setting server default language to %s" % lang_code)
            set_default_language(lang_code)
        elif not request.is_django_user and request.is_logged_in and lang_code != request.session[
                "facility_user"].default_language:
            logging.debug("setting user default language to %s" % lang_code)
            request.session["facility_user"].default_language = lang_code
            request.session["facility_user"].save()

        if lang_code != request.session.get("default_language"):
            logging.debug("setting session language to %s" % lang_code)
            request.session["default_language"] = lang_code

        set_request_language(request, lang_code)

        return JsonResponse({"status": "OK"})
Exemple #2
0
def device_counters(data, session):

    device_counters = get_device_counters(
        zone=session.client_device.get_zone())
    return JsonResponse({
        "device_counters": device_counters,
    })
Exemple #3
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,
    })
Exemple #4
0
def get_annotated_topic_tree(request, lang_code=None):
    call_command("videoscan")  # Could potentially be very slow, blocking request... but at least it's via an API request!

    lang_code = lang_code or request.language      # Get annotations for the current language.
    statusdict = dict(VideoFile.objects.values_list("youtube_id", "percent_complete"))

    return JsonResponse(annotate_topic_tree(get_topic_tree(), statusdict=statusdict, lang_code=lang_code))
Exemple #5
0
def delete_language_pack(request):
    """
    API endpoint for deleting language pack which fetches the language code (in delete_id) which has to be deleted.
    That particular language folders are deleted and that language gets removed.
    """
    lang_code = simplejson.loads(request.raw_post_data or "{}").get("lang")
    delete_language(lang_code)

    return JsonResponse({"success": _("Deleted language pack for language %(lang_code)s successfully.") % {"lang_code": lang_code}})
Exemple #6
0
def device_download(data, session):
    """This device is having its own devices downloaded"""
    zone = session.client_device.get_zone()
    devicezones = list(DeviceZone.all_objects.filter(zone=zone, device__in=data["devices"]))  # including deleted devicezones
    devices = [devicezone.device for devicezone in devicezones]
    session.models_downloaded += len(devices) + len(devicezones)

    # Return the objects serialized to the version of the other device.
    return JsonResponse({"devices": serialize(devices + devicezones, dest_version=session.client_version, ensure_ascii=False)})
Exemple #7
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({})
Exemple #8
0
def status(request):
    """In order to promote (efficient) caching on (low-powered)
    distributed devices, we do not include ANY user data in our
    templates.  Instead, an AJAX request is made to download user
    data, and javascript used to update the page.

    This view is the view providing the json blob of user information,
    for each page view on the distributed server.

    Besides basic user data, we also provide access to the
    Django message system through this API, again to promote
    caching by excluding any dynamic information from the server-generated
    templates.
    """
    # Build a list of messages to pass to the user.
    #   Iterating over the messages removes them from the
    #   session storage, thus they only appear once.
    message_dicts = []
    for message in get_messages(request):
        # Make sure to escape strings not marked as safe.
        # Note: this duplicates a bit of Django template logic.
        msg_txt = message.message
        if not (isinstance(msg_txt, SafeString)
                or isinstance(msg_txt, SafeUnicode)):
            msg_txt = cgi.escape(unicode(msg_txt))
        msg_type = message.tags
        message_dicts.append({msg_type: msg_txt})

    # Default data
    data = {
        "is_logged_in": request.is_logged_in,
        "registered": request.session["registered"],
        "is_admin": request.is_admin,
        "is_django_user": request.is_django_user,
        "points": 0,
        "current_language": request.session[settings.LANGUAGE_COOKIE_NAME],
        "messages": message_dicts,
    }
    # Override properties using facility data
    if "facility_user" in request.session:  # Facility user
        user = request.session["facility_user"]
        data["is_logged_in"] = True
        data["username"] = user.get_name()
        if "points" not in request.session:
            request.session["points"] = compute_total_points(user)
        data["points"] = request.session["points"]
    # Override data using django data
    if request.user.is_authenticated():  # Django user
        data["is_logged_in"] = True
        data["username"] = request.user.username

    return JsonResponse(data)
Exemple #9
0
def status(request):
    """In order to promote (efficient) caching on (low-powered)
    distributed devices, we do not include ANY user data in our
    templates.  Instead, an AJAX request is made to download user
    data, and javascript used to update the page.

    This view is the view providing the json blob of user information,
    for each page view on the distributed server.

    Besides basic user data, we also provide access to the
    Django message system through this API, again to promote
    caching by excluding any dynamic information from the server-generated
    templates.
    """
    # Build a list of messages to pass to the user.
    #   Iterating over the messages removes them from the
    #   session storage, thus they only appear once.

    message_dicts = get_messages_for_api_calls(request)

    # Default data
    data = {
        "is_logged_in": request.is_logged_in,
        "registered": request.session.get("registered", True),
        "is_admin": request.is_admin,
        "is_django_user": request.is_django_user,
        "points": 0,
        "current_language": request.session[settings.LANGUAGE_COOKIE_NAME],
        "messages": message_dicts,
        "status_timestamp": datetime.datetime.now(),
        "version": version.VERSION,
    }

    # Override properties using facility data
    if "facility_user" in request.session:  # Facility user
        user = request.session["facility_user"]
        data["is_logged_in"] = True
        data["username"] = user.get_name()
        # TODO(jamalex): re-enable this conditional once tastypie endpoints invalidate cached session value
        # if "points" not in request.session:
        request.session["points"] = compute_total_points(user)
        data["points"] = request.session["points"] if request.session["points"] else 0
        data["user_id"] = user.id
        data["user_uri"] = reverse("api_dispatch_detail", kwargs={"resource_name": "user", "pk": user.id})
        data["facility_id"] = user.facility.id

    # Override data using django data
    if request.user.is_authenticated():  # Django user
        data["is_logged_in"] = True
        data["username"] = request.user.username

    return JsonResponse(data)
Exemple #10
0
def get_dubbed_video_mappings(request):
    """Return dict of available language packs"""

    # On central, loop through available language packs in static/language_packs/
    try:
        if not os.path.exists(DUBBED_VIDEOS_MAPPING_FILEPATH):
            call_command("generate_dubbed_video_mappings")
        with open(DUBBED_VIDEOS_MAPPING_FILEPATH, "r") as fp:
            dubbed_videos_mapping = json.load(fp)
    except:
        raise Http404

    return JsonResponse(dubbed_videos_mapping)
Exemple #11
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(),
    })
Exemple #12
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))
Exemple #13
0
def get_available_language_packs(request, version):
    """Return dict of available language packs"""

    # On central, loop through available language packs in static/language_packs/
    try:
        with open(get_language_pack_availability_filepath(version=version),
                  "r") as fp:
            language_packs_available = json.load(fp)
    except Exception as e:
        logging.debug("Unexpected error getting available language packs: %s" %
                      e)
        language_packs_available = {}
    return JsonResponse(
        sorted(language_packs_available.values(),
               key=lambda lp: lp["name"].lower()))
Exemple #14
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)
Exemple #15
0
def get_subtitle_counts(request):
    """
    Sort and return a dict in the following format that gives the count of srt files available by language:
        {"gu": {"count": 45, "name": "Gujarati"}, etc.. }
    """

    # Get the subtitles file
    if not os.path.exists(SUBTITLE_COUNTS_FILEPATH):
        # could call-command, but return 404 for now.
        raise Http404("Subtitles count file %s not found." %
                      SUBTITLE_COUNTS_FILEPATH)

    with open(SUBTITLE_COUNTS_FILEPATH, "r") as fp:
        subtitle_counts = json.load(fp)

    return JsonResponse(subtitle_counts)
Exemple #16
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))
Exemple #17
0
def device_upload(data, session):
    """This device is getting device-related objects from another device"""

    # TODO(jamalex): check that the uploaded devices belong to the client device's zone and whatnot
    # (although it will only save zones from here if centrally signed, and devices if registered in a zone)
    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.get("devices", "[]"), src_version=session.client_version)
    except Exception as e:
        logging.debug("Exception uploading devices (in api_views): %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)
Exemple #18
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)
Exemple #19
0
def get_available_language_packs(request, version):
    """Return list of available language packs"""

    # Open language pack availability file
    try:
        with open(get_language_pack_availability_filepath(version=version),
                  "r") as fp:
            language_packs_available = json.load(fp)
    except Exception as e:
        logging.debug("Unexpected error getting available language packs: %s" %
                      e)
        language_packs_available = {}

    # Turn the dictionary into a sorted list of language packs, alphabetized by name
    available_packs = sorted(language_packs_available.values(),
                             key=lambda lp: lp["name"].lower())

    return JsonResponse(available_packs)
Exemple #20
0
def knowledge_map_json(request, topic_id):
    """
    Topic nodes can now have a "knowledge_map" stamped on them.
    This code currently exposes that data to the kmap-editor code,
    mostly as it expects it now.

    So this is kind of a hack-ish mix of code that avoids rewriting kmap-editor.js,
    but allows a cleaner rewrite of the stored data, and bridges the gap between
    that messiness and the cleaner back-end.
    """

    # Try and get the requested topic, and make sure it has knowledge map data available.
    topic = topic_tools.get_node_cache("Topic").get(topic_id)
    if not topic:
        raise Http404("Topic '%s' not found" % topic_id)
    elif not "knowledge_map" in topic[0]:
        raise Http404("Topic '%s' has no knowledge map metadata." % topic_id)

    # For each node (can be of any type now), pull out only
    #   the relevant data.
    kmap = topic[0]["knowledge_map"]
    nodes_out = {}
    for id, kmap_data in kmap["nodes"].iteritems():
        cur_node = topic_tools.get_node_cache(kmap_data["kind"])[id][0]
        nodes_out[id] = {
            "id": cur_node["id"],
            "title": _(cur_node["title"]),
            "h_position": kmap_data["h_position"],
            "v_position": kmap_data["v_position"],
            "icon_url": cur_node.get("icon_url",
                                     cur_node.get("icon_src")),  # messy
            "path": cur_node["path"],
        }
        if not "polylines" in kmap:  # messy
            # Two ways to define lines:
            # 1. have "polylines" defined explicitly
            # 2. use prerequisites to compute lines on the fly.
            nodes_out[id]["prerequisites"] = cur_node.get("prerequisites", [])

    return JsonResponse({
        "nodes": nodes_out,
        "polylines": kmap.get("polylines"),  # messy
    })
Exemple #21
0
def get_topic_tree_by_kinds(request, topic_path, kinds_to_query=None):
    """Given a root path, returns all topic nodes that contain the requested kind(s).
    Topic nodes without those kinds are removed.
    """
    def convert_topic_tree_for_dynatree(node, kinds_to_query):
        """Converts topic tree from standard dictionary nodes
        to dictionary nodes usable by the dynatree app"""

        if node["kind"] != "Topic":
            # Should never happen, but only run this function for topic nodes.
            return None

        elif not set(kinds_to_query).intersection(set(node["contains"])):
            # Eliminate topics that don't contain the requested kinds
            return None

        topic_children = []
        for child_node in node["children"]:
            child_dict = convert_topic_tree_for_dynatree(
                child_node, kinds_to_query)
            if child_dict:
                # Only keep children that themselves have the requsted kind
                topic_children.append(child_dict)

        return {
            "title": _(node["title"]),
            "tooltip": re.sub(r'<[^>]*?>', '',
                              _(node.get("description")) or ""),
            "isFolder": True,
            "key": node["path"],
            "children": topic_children,
            "expand": False,  # top level
        }

    kinds_to_query = kinds_to_query or request.GET.get("kinds",
                                                       "Exercise").split(",")
    topic_node = get_topic_by_path(topic_path)
    if not topic_node:
        raise Http404

    return JsonResponse(
        convert_topic_tree_for_dynatree(topic_node, kinds_to_query))
Exemple #22
0
def register_public_key_server_auto(request):
    """This function allows an anonymous client to request a device key
    to be associated with a new zone.

    This allows registration to occur without a single login; the device
    will be associated with a headless zone.
    """
    public_key = urllib.unquote(request.GET.get("device_key", ""))
    if RegisteredDevicePublicKey.objects.filter(public_key=public_key):
        return HttpResponseForbidden("Device is already registered.")

    # Create some zone.
    zone = Zone(name="Zone for public key %s" % public_key[:50])
    zone.save()

    # Add an association between a device 's public key and this zone,
    #   so that when registration is attempted by the distributed server
    #   with this key, it will register and receive this zone info.
    RegisteredDevicePublicKey(zone=zone, public_key=public_key).save()

    # Report success
    return JsonResponse({})
Exemple #23
0
def topic_tree(request, channel):
    return JsonResponse(get_topic_tree(channel=channel))
Exemple #24
0
def flat_topic_tree(request, lang_code):
    return JsonResponse(get_flat_topic_tree(lang_code=lang_code))
Exemple #25
0
def api_data(request, xaxis="", yaxis=""):
    """Request contains information about what data are requested (who, what, and how).

    Response should be a JSON object
    * data contains the data, structred by user and then datatype
    * the rest of the data is metadata, useful for displaying detailed info about data.
    """

    # Get the request form
    try:
        form = get_data_form(request, xaxis=xaxis, yaxis=yaxis)  # (data=request.REQUEST)
    except Exception as e:
        # In investigating #1509: we can catch SQL errors here and communicate clearer error
        #   messages with the user here.  For now, we have no such error to catch, so just
        #   pass the errors on to the user (via the @api_handle_error_with_json decorator).
        raise e

    # Query out the data: who?
    if form.data.get("user"):
        facility = []
        groups = []
        users = [get_object_or_404(FacilityUser, id=form.data.get("user"))]
    elif form.data.get("group"):
        facility = []
        groups = [get_object_or_404(FacilityGroup, id=form.data.get("group"))]
        users = FacilityUser.objects.filter(group=form.data.get("group"), is_teacher=False).order_by("last_name", "first_name")
    elif form.data.get("facility"):
        facility = get_object_or_404(Facility, id=form.data.get("facility"))
        groups = FacilityGroup.objects.filter(facility__in=[form.data.get("facility")])
        users = FacilityUser.objects.filter(facility__in=[form.data.get("facility")], is_teacher=False).order_by("last_name", "first_name")
    else:
        return HttpResponseNotFound(_("Did not specify facility, group, nor user."))

    # Query out the data: where?
    if not form.data.get("topic_path"):
        return HttpResponseNotFound(_("Must specify a topic path"))

    # Query out the data: what?
    computed_data = compute_data(data_types=[form.data.get("xaxis"), form.data.get("yaxis")], who=users, where=form.data.get("topic_path"))

    # Quickly add back in exercise meta-data (could potentially be used in future for other data too!)
    ex_nodes = get_node_cache()["Exercise"]
    exercises = []
    for e in computed_data["exercises"]:
        exercises.append({
            "slug": e,
            "full_name": ex_nodes[e][0]["display_name"],
            "url": ex_nodes[e][0]["path"],
        })  

    json_data = {
        "data": computed_data["data"],
        "exercises": exercises,
        "videos": computed_data["videos"],
        "users": dict(zip([u.id for u in users],
                          ["%s, %s" % (u.last_name, u.first_name) for u in users]
                     )),
        "groups": dict(zip([g.id for g in groups],
                           dict(zip(["id", "name"], [(g.id, g.name) for g in groups])),
                     )),
        "facility": None if not facility else {
            "name": facility.name,
            "id": facility.id,
        }
    }

    if "facility_user" in request.session:
        try:
            # Log a "begin" and end here
            user = request.session["facility_user"]
            UserLog.begin_user_activity(user, activity_type="coachreport")
            UserLog.update_user_activity(user, activity_type="login")  # to track active login time for teachers
            UserLog.end_user_activity(user, activity_type="coachreport")
        except ValidationError as e:
            # Never report this error; don't want this logging to block other functionality.
            logging.error("Failed to update Teacher userlog activity login: %s" % e)

    # Now we have data, stream it back with a handler for date-times
    return JsonResponse(json_data)
Exemple #26
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)
    )
Exemple #27
0
def get_server_info(request):
    """This function is used to check connection to central or local server and also to get specific data from server.

    Args:
        The http request.

    Returns:
        A json object containing general data from the server.

    """
    device = None
    zone = None

    device_info = {"status": "OK", "invalid_fields": []}

    for field in request.GET.get("fields", "").split(","):

        if field == "version":
            device = device or Device.get_own_device()
            device_info[field] = device.get_version()

        elif field == "device_name":
            device = device or Device.get_own_device()
            device_info[field] = device.name

        elif field == "device_description":
            device = device or Device.get_own_device()
            device_info[field] = device.description

        elif field == "device_description":
            device = device or Device.get_own_device()
            device_info[field] = device.description

        elif field == "device_id":
            device = device or Device.get_own_device()
            device_info[field] = device.id

        elif field == "zone_name":
            if settings.CENTRAL_SERVER:
                continue
            device = device or Device.get_own_device()
            zone = zone or device.get_zone()
            device_info[field] = zone.name if zone else None

        elif field == "zone_id":
            if settings.CENTRAL_SERVER:
                continue
            device = device or Device.get_own_device()
            zone = zone or device.get_zone()
            device_info[field] = zone.id if zone else None

        elif field == "online":
            if settings.CENTRAL_SERVER:
                device_info[field] =  True
            else:
                device_info[field] = am_i_online(url="%s://%s%s" % (settings.SECURESYNC_PROTOCOL, settings.CENTRAL_SERVER_HOST, reverse("get_server_info")))

        elif field:
            # the field isn't one we know about, so add it to the list of invalid fields
            device_info["invalid_fields"].append(field)

    return JsonResponse(device_info)
Exemple #28
0
def installed_language_packs(request):
    return JsonResponse(get_installed_language_packs(force=True).values())
Exemple #29
0
def check_update_progress(request, process_log):
    """
    API endpoint for getting progress data on downloads.
    """
    return JsonResponse(_process_log_to_dict(process_log))
Exemple #30
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))