Пример #1
0
def delete_url(url_key: str):
    """Deletes a URL with a given key.
    
    This runs a check on the user's token to see if they're allowed to delete the file."""

    user = utils.check_user(token=request.headers.get("Authorization"))

    if user is None:
        return utils.respond(code=403, msg="Invalid API token.")

    found_url = utils.first(iterable=cache["urls"],
                            condition=lambda url: url.key == url_key)

    if found_url is None:
        return utils.respond(code=404, msg="URL not found.")

    # ========================================================
    # - User isn't admin and isn't trying to delete their file
    # - Both admin but user is not superuser
    # ========================================================
    if (not user.admin and user.id != found_url.owner.id) \
       or (user.admin and found_url.owner.admin and user.id != found_url.owner.id and user.id != const.superuser.id):
        return utils.respond(code=403, msg="You don't own this URL.")

    with postgres.cursor() as con:
        query = """DELETE FROM urls
                   WHERE key = %(key)s;"""

        con.execute(query, dict(key=url_key))

    cache["urls"].remove(found_url)

    return utils.respond(code=200, msg="URL has been deleted.")
Пример #2
0
def delete_file(filename: str):
    """Deletes a file with a given filename.
    
    This runs a check on the user's token to see if they're allowed to delete the file."""

    user = utils.check_user(token=request.headers.get("Authorization"))

    if user is None:
        return utils.respond(code=403, msg="Invalid API token.")

    file = utils.first(iterable=cache["files"],
                       condition=lambda file: file.key == filename)

    if file is None:
        return utils.respond(code=404, msg="File not found.")

    # ========================================================
    # - User isn't admin and isn't trying to delete their file
    # - Both admin but user is not superuser
    # ========================================================
    if (not user.admin and user.id != file.owner.id) \
       or (user.admin and file.owner.admin and user.id != file.owner.id and user.id != const.superuser.id):
        return utils.respond(code=403, msg="You don't own this file.")
    with postgres.cursor() as con:
        query = """DELETE FROM files
                    WHERE key = %(key)s;"""

        con.execute(query, dict(key=filename))

    os.remove(path=f"static/uploads/{filename}")
    cache["files"].remove(file)

    return utils.respond(code=200, msg="File has been deleted.")
Пример #3
0
def login():
    """Sends the user to the login screen."""

    if utils.check_user(token=request.headers.get("Authorization")) is None:
        return render_template(template_name_or_list="login.html"), 200

    return redirect(location="/home", code=303), 303
Пример #4
0
def shorten_url():
    """Takes a URL and shortens it. This is useful for particularly long URLs like Google Form links."""

    user = utils.check_user(token=request.headers.get("Authorization"))

    if user is None:
        return utils.respond(code=403, msg="Invalid API token.")

    if not request.is_json:
        return utils.respond(code=403, msg="Missing JSON data.")

    to_shorten = request.json.get("url")

    if to_shorten is None:
        return utils.respond(code=403, msg="Missing JSON data.")

    found_url = utils.first(iterable=cache["urls"],
                            condition=lambda url: url.url == to_shorten)

    if found_url:
        return utils.respond(
            code=409,
            msg="That URL has already been shortened.",
            link=
            f"https://{request.url_root.lstrip('http://')}u/{found_url.key}")

    if match(pattern=URL_REGEX, string=to_shorten) is None:
        return utils.respond(code=403, msg="That URL is invalid.")

    key = request.headers.get("URL-Name")

    if not (user.admin
            and config.url_shortening.custom_url.admin_only) or not key:
        key = utils.generate_key(cache_obj="urls")

    with postgres.cursor() as con:
        query = """INSERT INTO urls (owner_id, key, url, created_at)
                   VALUES (%(owner_id)s, %(key)s, %(url)s, %(created_at)s)
                   
                   RETURNING id;"""

        con.execute(
            query,
            dict(owner_id=user.id,
                 key=key,
                 url=to_shorten,
                 created_at=datetime.utcnow()))

        url_id = con.fetchone()[0]
        url_obj = URL(id=url_id,
                      key=key,
                      url=to_shorten,
                      created_at=datetime.utcnow(),
                      owner=user)

        cache["urls"].insert(0, url_obj)

    return f"https://{request.url_root.lstrip('http://')}u/{key}", 200
Пример #5
0
def edit_user_page(victim_id: int):
    """This provides a simple form for admins to edit a user."""

    user = utils.check_user(token=request.cookies.get("_auth_token"))

    if user is None:
        return redirect(location="/api/login", code=303), 303

    victim = utils.check_user(token=victim_id)

    if victim is None:
        return jsonify(dict(code=422,
                            message="Invalid or missing user ID.")), 422

    if not user.admin:
        abort(status=403)

    return render_template(template_name_or_list="admin/edit.html",
                           victim=victim,
                           user=user)
Пример #6
0
def account_page():
    """Sends the user to their account page."""

    user = utils.check_user(token=request.cookies.get("_auth_token"))

    if user is None:
        return redirect(location="/api/login", code=303), 303

    return render_template(template_name_or_list="home/account.html",
                           user=user,
                           superuser=user.id == const.superuser.id)
Пример #7
0
def files_page():
    """Displays a table of the user's files."""

    user = utils.check_user(token=request.cookies.get("_auth_token"))

    if user is None:
        return redirect(location="/api/login", code=303), 303

    return render_template(template_name_or_list="home/files.html",
                           user=user,
                           files=utils.all(iterable=cache.files,
                                           condition=lambda file: file.owner.id
                                           == user.id and not file.deleted))
Пример #8
0
def shortened_urls():
    """Displays a table of the user's shortened URLs."""

    user = utils.check_user(token=request.cookies.get("_auth_token"))

    if user is None:
        return redirect(location="/api/login", code=303), 303

    return render_template(template_name_or_list="home/urls.html",
                           user=user,
                           urls=utils.all(
                               iterable=cache.urls,
                               condition=lambda url: url.owner.id == user.id))
Пример #9
0
def new_url():
    """Allows a user to shorten a URL from the dashboard."""

    user = utils.check_user(token=request.cookies.get("_auth_token"))

    if user is None:
        return redirect(location="/api/login", code=303), 303

    return render_template(
        template_name_or_list="home/new.html",
        user=user,
        can_custom_name=(user.admin
                         and config.url_shortening.custom_url.admin_only)
        or not config.url_shortening.custom_url.admin_only)
Пример #10
0
def new_user_page():
    """This provides a simple form for admins to provide data for a new user."""

    user = utils.check_user(token=request.cookies.get("_auth_token"))

    if user is None:
        return redirect(location="/api/login", code=303), 303

    if not user.admin:
        abort(status=403)

    return render_template(template_name_or_list="admin/new.html",
                           is_superuser=user.id == const.superuser.id,
                           user=user)
Пример #11
0
def reset_token():
    """Resets the API token of a given user ID.
    
    All necessary checks are performed here."""

    user = utils.check_user(token=request.headers.get("Authorization"))

    if user is None:
        return utils.respond(code=403, msg="Invalid API token.")

    if not request.is_json:
        return utils.respond(code=422, msg="Missing JSON data.")

    victim = utils.get_user(token_or_id=request.json.get("id"))

    if victim is None:
        return utils.respond(code=422, msg="Invalid user ID.")

    # ============================================================
    # - User isn't admin and isn't trying to change themselves
    # - Victim is superuser
    # - Both admin but user is not superuser and user isn't victim
    # ============================================================
    if (not user.admin and user.id != victim.id) \
       or (victim.id == const.superuser.id) \
       or (user.admin and victim.admin and not user.id == victim.id and user.id != const.superuser.id):
        return utils.respond(code=403,
                             msg="You cannot reset that user's token.")

    new_token = utils.generate_token()

    with postgres.cursor() as con:
        query = """UPDATE users
                   SET token = %(token)s
                   WHERE id = %(id)s;"""

        con.execute(query, dict(token=new_token, id=victim.id))

        # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        # our task of updating the cache is easy here,
        # all we have to do is change the variable of victim
        # because the victim variable points directly to cache
        # and isn't a deep or shallow copy
        # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        victim.token = new_token

    return utils.respond(code=200,
                         msg="Token has been reset.",
                         new_token=new_token)
Пример #12
0
def url_list():
    """This displays the shortened URLs section of the admin panel."""

    user = utils.check_user(token=request.cookies.get("_auth_token"))

    if user is None:
        return redirect(location="/api/login", code=303), 303

    if not user.admin:
        abort(status=403)

    return render_template(template_name_or_list="admin/urls.html",
                           user=user,
                           superuser_id=const.superuser.id,
                           superuser=user.token == const.superuser.token,
                           urls=cache.urls)
Пример #13
0
def upload_file():
    """This allows a user to upload any sort of file to the server."""

    user = utils.check_user(token=request.headers.get("Authorization"))

    if user is None:
        return utils.respond(code=403, msg="Invalid API token.")

    file = request.files.get("upload")

    if file is None:
        return utils.respond(code=422, msg="Missing file data.")

    file_type = utils.filetype(filename=file.filename)

    if file_type is False:
        return utils.respond(code=422, msg="Invalid filetype")

    key = f"{utils.generate_key()}.{file.filename.rsplit('.', 1)[1].lower()}"

    file.save(f"static/uploads/{key}")

    if file_type == "image" and not utils.bypass_optimise(
            header=request.headers.get("Compression-Bypass"), user=user):
        utils.optimise_image(key=key)

    with postgres.cursor() as con:
        query = """INSERT INTO files (owner_id, key, created_at)
                   VALUES (%(owner_id)s, %(key)s, %(created_at)s)
                   
                   RETURNING id;"""

        con.execute(
            query, dict(owner_id=user.id,
                        key=key,
                        created_at=datetime.utcnow()))

        file_id = con.fetchone()[0]
        file_obj = File(id=file_id,
                        key=key,
                        created_at=datetime.utcnow(),
                        owner=user,
                        deleted=False)

        cache["files"].insert(0, file_obj)

    return f"https://{request.url_root.lstrip('http://')}{'f' if file_type != 'image' else 'i'}/{key}", 200
Пример #14
0
def file_gallery():
    """This displays the files section of the admin panel."""

    user = utils.check_user(token=request.cookies.get("_auth_token"))

    if user is None:
        return redirect(location="/api/login", code=303), 303

    if not user.admin:
        abort(status=403)

    return render_template(template_name_or_list="admin/files.html",
                           user=user,
                           superuser_id=const.superuser.id,
                           superuser=user.token == const.superuser.token,
                           files=utils.all(
                               iterable=cache.files,
                               condition=lambda file: not file.deleted))
Пример #15
0
def delete_user():
    """Deletes the provided user ID.
    
    All necessary checks are performed here."""

    user = utils.check_user(token=request.headers.get("Authorization"))

    if user is None:
        return utils.respond(code=403, msg="Invalid API token.")

    if not request.is_json:
        return utils.respond(code=422, msg="Missing JSON data.")

    victim = utils.get_user(token_or_id=request.json.get("id"))

    if victim is None:
        return utils.respond(code=422, msg="Invalid user ID.")

    # ========================================================
    # - User isn't admin and isn't trying to delete themselves
    # - Victim is superuser
    # - Both admin but user is not superuser
    # ========================================================
    if (not user.admin and user.id != victim.id) \
       or (victim.id == const.superuser.id) \
       or (user.admin and victim.admin and user.id != const.superuser.id):
        return utils.respond(code=403, msg="You cannot delete the user.")

    with postgres.cursor() as con:
        query = """DELETE FROM users
                   WHERE id = %(id)s;"""

        con.execute(query, dict(id=victim.id))

        cache["users"].remove(victim)

    return utils.respond(code=200, msg="User has been deleted.")
Пример #16
0
def edit_user():
    """Edits the provided user ID.
    
    All necessary checks are performed here."""

    user = utils.check_user(token=request.headers.get("Authorization"))

    if user is None:
        return utils.respond(code=403, msg="Invalid API token.")

    if not request.is_json:
        return utils.respond(code=422, msg="Missing JSON data.")

    victim = utils.get_user(token_or_id=request.json.get("id"))

    if victim is None:
        return utils.respond(code=422, msg="Invalid user ID.")

    # ========================================================
    # - User isn't admin and isn't trying to change themselves
    # - Victim is superuser
    # - Both admin but user is not superuser and user isn't victim
    # ========================================================
    if (not user.admin and user.id != victim.id) \
       or (victim.id == const.superuser.id) \
       or (user.admin and victim.admin and not user.id == victim.id and user.id != const.superuser.id):
        return utils.respond(code=403, msg="You cannot edit that user.")

    new_stuff = request.json.get("new_values")

    if not new_stuff:
        return utils.respond(code=422, msg="Missing JSON data.")

    # ==========================
    # Check if username is taken
    # ==========================
    if "username" in new_stuff:
        # --------------------------------------
        # Ignore if the victim has this username
        # --------------------------------------
        if new_stuff.get("username") in (u.username for u in cache["users"]
                                         if u.id != victim.id):
            return utils.respond(code=409, msg="Username has been taken.")

    if "admin" in new_stuff:
        # ====================================================
        # User isn't superuser and trying to set user to admin
        # ====================================================
        if user.id != const.superuser.id:
            return utils.respond(code=403,
                                 msg="You cannot create an admin user.")

        if new_stuff.get("admin") == "toggle":
            new_stuff["admin"] = not victim.admin

    new_values = dict(username=victim.username,
                      password=victim.password,
                      admin=victim.admin,
                      token=victim.token)

    new_values.update(new_stuff)

    with postgres.cursor() as con:
        query = """UPDATE users
                   SET username = %(username)s,
                   password = %(password)s,
                   admin = %(admin)s,
                   token = %(token)s
                   WHERE id = %(id)s;"""

        con.execute(query, dict(id=victim.id, **new_values))

        new_obj = User(id=victim.id,
                       created_at=victim.created_at,
                       **new_values)

        cache["users"][cache["users"].index(victim)] = new_obj

    return utils.respond(code=200,
                         msg="User has been edited.",
                         new_values=new_values)
Пример #17
0
def new_user():
    """Creates a user with the given data.
    
    All necessary checks are performed here."""

    user = utils.check_user(token=request.headers.get("Authorization"))

    if user is None:
        return utils.respond(code=403, msg="Invalid API token.")

    if not user.admin:
        return utils.respond(code=403,
                             msg="You can't create a user.",
                             needed_permission="admin")

    if not request.is_json:
        return utils.respond(code=422,
                             msg="Missing JSON data.",
                             required_fields=["username", "password", "admin"])

    if None in request.json.items():
        return utils.respond(code=422, msg="Missing JSON data.")

    for key in ("username", "password", "admin"):
        if key not in request.json.keys():
            return utils.respond(
                code=422,
                msg="Missing JSON data.",
                required_fields=["username", "password", "admin"])

    # =============================================================
    # User isn't superuser and is trying to create an admin account
    # =============================================================
    if request.json.get(
            "admin") is not False and user.id != const.superuser.id:
        return utils.respond(code=403,
                             msg="You can't create admin users.",
                             needed_permission="superuser")

    if request.json.get("username") in (u.username for u in cache["users"]):
        return utils.respond(code=409,
                             msg="That username has already been taken.")

    stripped_values = dict(username=request.json.get("username"),
                           password=request.json.get("password"),
                           admin=request.json.get("admin"),
                           created_at=datetime.utcnow(),
                           token=utils.generate_token())

    with postgres.cursor() as con:
        query = """INSERT INTO users (username, password, admin, token, created_at)
                   VALUES (%(username)s, %(password)s, %(admin)s, %(token)s, %(created_at)s)
                   
                   RETURNING id;"""

        con.execute(query, stripped_values)

        id = con.fetchone()[0]
        user_obj = User(id=id, **stripped_values)

        cache["users"].append(user_obj)

    return utils.respond(code=200, msg="User has been created.", id=id)