Esempio n. 1
0
 def inner(*args, **kwds):
     auth = request.headers.get("authorization", None)
     if auth is None:
         raise APIError(
             {
                 "code": "missing_authorization",
                 "description": "No authorization header"
             }, 401)
     pieces = auth.split()
     if len(pieces) != 2:
         raise APIError(
             {
                 "code": "invalid_header",
                 "description": "Authorization header must have length 2"
             }, 401)
     email, token = pieces
     user = SeminarsUser(email=email)
     if user.id is None:
         raise APIError(
             {
                 "code": "missing_user",
                 "description": "User %s not found" % email
             }, 401)
     if token == user.api_token:
         kwds["user"] = user
         return fn(*args, **kwds)
     else:
         raise APIError(
             {
                 "code": "invalid_token",
                 "description": "Token not valid"
             }, 401)
Esempio n. 2
0
def search_series(version=0):
    if version != 0:
        raise version_error(version)
    if request.method == "POST":
        raw_data = get_request_json()
        query = raw_data.pop("query", {})
        # FIXME
        # projection = raw_data.pop("projection", 1)
        tz = raw_data.pop("timezone", "UTC")
    else:
        query = get_request_args_json()
        tz = current_user.tz # Is this the right choice?
        for col, val in query.items():
            if col in db.seminars.col_type:
                query[col] = process_user_input(val, col, db.seminars.col_type[col], tz)
            else:
                raise APIError({"code": "unknown_column",
                                "col": col,
                                "description": "%s not a column of seminars" % col})
        raw_data = {}
    query["visibility"] = 2
    # TODO: encode the times....
    try:
        results = list(seminars_search(query, objects=False, sanitized=True, **raw_data))
    except Exception as err:
        raise APIError({"code": "search_error",
                        "description": "error in executing search",
                        "error": str(err)})
    ans = {"code": "success", "results": results}
    callback = raw_data.get("callback", False)
    return str_jsonify(ans, callback)
Esempio n. 3
0
def search_talks(version=0):
    if version != 0:
        raise version_error(version)
    if request.method == "POST":
        raw_data = get_request_json()
        query = raw_data.pop("query", {})
        projection = raw_data.pop("projection", 1)
        # FIXME
        # tz = raw_data.pop("timezone", "UTC")
    else:
        query = get_request_args_json()
        projection = 1
        raw_data = {}
    query["hidden"] = False
    visible_series = set(seminars_search({"visibility": 2}, "shortname"))
    # TODO: Need to check visibility on the seminar
    try:
        results = talks_search(query, projection, objects=False, **raw_data)
    except Exception as err:
        raise APIError({
            "code": "search_error",
            "description": "error in executing search",
            "error": str(err)
        })
    results = [rec for rec in results if rec["seminar_id"] in visible_series]
    ans = {"code": "success", "results": results}
    callback = raw_data.get("callback", False)
    return str_jsonify(ans, callback)
Esempio n. 4
0
def lookup_series(version=0, user=None):
    if version != 0:
        raise version_error(version)
    if request.method == "POST":
        raw_data = get_request_json()
    else:
        raw_data = get_request_args_json()
    series_id = _get_col("series_id", raw_data, "looking up a series")

    try:
        if user is None:
            sanitized = True
        else:
            sem = seminars_lookup(series_id)
            sanitized = not sem.user_can_edit(user=user)
        result = seminars_lookup(series_id, objects=False, sanitized=sanitized)
    except Exception as err:
        raise APIError({
            "code": "lookup_error",
            "description": "an error occurred looking up the series",
            "error": str(err)
        })
    talks = list(
        talks_search({"seminar_id": series_id},
                     sort=["start_time"],
                     sanitized=sanitized,
                     objects=False))
    # tz = pytz.timezone(raw_data.get("timezone", result.get("timezone", "UTC")))
    # TODO: adapt the times, support daterange, sort
    ans = {"code": "success", "properties": result, "talks": talks}
    callback = raw_data.get("callback", False)
    return str_jsonify(ans, callback)
Esempio n. 5
0
def get_request_args_json():
    try:
        return dict( (key, json.loads(value)) for key, value in dict(request.args).items())
    except Exception as err:
        raise APIError({"code": "json_parse_error",
                        "description": "could not parse json",
                        "error": str(err)})
Esempio n. 6
0
def get_request_json():
    try:
        return request.get_json()
    except Exception as err:
        raise APIError({"code": "json_parse_error",
                        "description": "could not parse json",
                        "error": str(err)})
Esempio n. 7
0
def save_talk(version=0, user=None):
    if version != 0:
        raise APIError({"code": "invalid_version",
                        "description": "Unknown API version: %s" % version})
    raw_data = request.get_json()
    # Temporary measure while we rename seminar_id
    series_id = raw_data.pop("series_id", None)
    raw_data["seminar_id"] = series_id
    if series_id is None:
        raise APIError({"code": "unspecified_series_id",
                        "description": "You must specify series_id when saving a talk"})
    series = seminars_lookup(series_id)
    if series is None:
        raise APIError({"code": "no_series",
                        "description": "The series %s does not exist (or is deleted)" % series_id})
    else:
        # Make sure user has permission to edit
        if not series.user_can_edit(user):
            raise APIError({"code": "unauthorized_user",
                            "description": "You do not have permission to edit %s." % series_id}, 401)
    # Temporary measure while we rename seminar_ctr
    series_ctr = raw_data.pop("series_ctr", None)
    raw_data["seminar_ctr"] = series_ctr
    if series_ctr is None:
        # Creating new talk
        talk = WebTalk(series_id, seminar=series, editing=True)
    else:
        talk = talks_lookup(series_id, series_ctr)
        if talk is None:
            raise APIError({"code": "no_talk",
                            "description": "The talk %s/%s does not exist (or is deleted)" % (series_id, series_ctr)})
    warnings = []
    def warn(msg, *args):
        warnings.append(msg % args)
    data, errmsgs = process_save_talk(talk, raw_data, warn, format_error, format_input_error)
    if errmsgs:
        raise APIError({"code": "processing_error",
                        "description": "Error in processing input",
                        "errors": errmsgs})
    else:
        new_version = WebTalk(talk.seminar_id, data=data)
    sanity_check_times(new_version.start_time, new_version.end_time)
    if talk.new or new_version != talk:
        # Talks saved by the API are not displayed until user approves
        new_version.display = False
        new_version.by_api = True
        new_version.save(user)
    else:
        raise APIError({"code": "no_changes",
                        "description": "No changes detected"})
    edittype = "created" if talk.new else "edited"
    if warnings:
        response = jsonify({"code": "warning",
                            "description": "series successfully %s, but..." % edittype,
                            "warnings": warnings})
    else:
        response = jsonify({"code": "success",
                            "description": "series successfully %s" % edittype})
    return response
Esempio n. 8
0
def _get_col(col, raw_data, activity):
    val = raw_data.get(col)
    if val is None:
        raise APIError({
            "code":
            "unspecified_%s" % col,
            "description":
            "You must specify %s when %s" % (col, activity)
        })
    return val
Esempio n. 9
0
def save_series(version=0, user=None):
    if version != 0:
        raise version_error(version)
    try:
        raw_data = get_request_json()
    except Exception:
        raw_data = None
    if not isinstance(raw_data, dict):
        raise APIError({
            "code": "invalid_json",
            "description": "request must contain a json dictionary"
        })
    # Temporary measure while we rename shortname
    series_id = raw_data.pop("series_id", None)
    raw_data["shortname"] = series_id
    if series_id is None:
        raise APIError({
            "code":
            "unspecified_series_id",
            "description":
            "You must specify series_id when saving a series"
        })
    series = seminars_lookup(series_id, include_deleted=True)
    if series is None:
        # Creating new series
        if not allowed_shortname(
                series_id) or len(series_id) < 3 or len(series_id) > 32:
            raise APIError({
                "code":
                "invalid_series_id",
                "description":
                "The identifier must be 3 to 32 characters in length and can include only letters, numbers, hyphens and underscores."
            })
        if "organizers" not in raw_data:
            raise APIError({
                "code":
                "organizers_required",
                "description":
                "You must specify organizers when creating new series"
            })
        update_organizers = True
        series = WebSeminar(series_id, data=None, editing=True, user=user)
    else:
        # Make sure user has permission to edit
        if not series.user_can_edit(user):
            raise APIError(
                {
                    "code":
                    "unauthorized_user",
                    "description":
                    "You do not have permission to edit %s." % series_id
                }, 401)
        if series.deleted:
            raise APIError({
                "code":
                "norevive",
                "description":
                "You cannot revive a series through the API"
            })
        if "organizers" in raw_data:
            raise APIError({
                "code":
                "organizers_prohibited",
                "description":
                "You may not specify organizers when editing series"
            })
        update_organizers = False
    # Check that there aren't extraneous keys (which might be misspelled)
    extra_keys = [
        key for key in raw_data
        if key not in db.seminars.search_cols + ["slots", "organizers"]
    ]
    if extra_keys:
        raise APIError({
            "code": "extra_keys",
            "description": "Unrecognized keys",
            "errors": extra_keys
        })
    # Time slots/weekdays and organizers are handled differently by the processing code
    # We want to allow them to be unspecified (in which case we fall back on the current value)
    # and for an API call it's also more convenient to specify them using a list
    if "slots" in raw_data:
        slots = raw_data["slots"]
    else:
        slots = [
            short_weekdays[day] + " " + slot
            for (day, slot) in zip(series.weekdays, series.time_slots)
        ]
    if not isinstance(slots, list) or len(slots) > MAX_SLOTS or not all(
            isinstance(slot, str) for slot in slots):
        raise APIError({
            "code":
            "processing_error",
            "description":
            "Error in processing slots",
            "errors": [
                "slots must be a list of strings of length at most %s" %
                MAX_SLOTS
            ]
        })
    for i, slot in enumerate(slots):
        try:
            day, time = slot.split(None, 1)
            day = short_weekdays.index(day)
        except ValueError:
            raise APIError({
                "code":
                "processing_error",
                "description":
                "Error in processing slots",
                "errors": [
                    "slots must be a three letter day-of-week followed by a time range after a space"
                ]
            })
        raw_data["weekday%s" % i] = str(day)
        raw_data["time_slot%s" % i] = time
    for i in range(len(slots), MAX_SLOTS):
        raw_data["weekday%s" % i] = raw_data["time_slot%s" % i] = ""
    raw_data["num_slots"] = len(slots)

    if update_organizers:
        # We require specifying the organizers of a new seminar and don't allow updates,
        # so we don't need to get anything from the seminar object
        organizers = raw_data.get("organizers", [])
        fixed_cols = list(db.seminar_organizers.search_cols)
        i = fixed_cols.index("curator")
        fixed_cols[i] = "organizer"
        if not (isinstance(organizers, list)
                and len(organizers) <= MAX_ORGANIZERS and all(
                    isinstance(OD, dict) and all(key in fixed_cols
                                                 for key in OD)
                    for OD in organizers)):
            raise APIError({
                "code":
                "processing_error",
                "description":
                "Error in processing organizers",
                "errors": [
                    "organizers must be a list of dictionaries (max length %s) with keys %s"
                    % (MAX_ORGANIZERS, ", ".join(fixed_cols))
                ]
            })
        for i, OD in enumerate(organizers):
            for col in db.seminar_organizers.search_cols:
                default = True if col == "display" else ""
                raw_data["org_%s%s" % (col, i)] = OD.get(col, default)
            # We store curator in the database but ask for organizer from the API
            raw_data["org_curator%s" % i] = not OD.get("organizer", True)

    warnings = []

    def warn(msg, *args):
        warnings.append(msg % args)

    new_version, errmsgs = process_save_seminar(series,
                                                raw_data,
                                                warn,
                                                format_error,
                                                format_input_error,
                                                update_organizers,
                                                incremental_update=True,
                                                user=user)
    if new_version is None:
        raise APIError({
            "code": "processing_error",
            "description": "Error in processing input",
            "errors": errmsgs
        })
    if series.new or new_version != series:
        # Series saved by the API are not displayed until user approves
        new_version.display = False
        new_version.by_api = True
        new_version.save(user)
    else:
        raise APIError({
            "code": "no_changes",
            "description": "No changes detected"
        })
    if series.new:
        new_version.save_organizers()
    edittype = "created" if series.new else "edited"
    if warnings:
        response = jsonify({
            "code": "warning",
            "description": "series successfully %s, but..." % edittype,
            "warnings": warnings
        })
    else:
        response = jsonify({
            "code": "success",
            "description": "series successfully %s" % edittype
        })
    return response
Esempio n. 10
0
def version_error(version):
    return APIError({
        "code": "invalid_version",
        "description": "Unknown API version: %s" % version
    })