Exemple #1
0
def unfollow_user(user_id):
    """Stop following the user with a given ID."""
    username = get_jwt_identity()

    login = Login.query.filter_by(username=username).first()

    if login is None:
        raise APIError(
            400, "User {username} does not exist on this server".format(
                username=username))

    following_user = login.user

    followed_user = User.query.get(user_id)
    if followed_user is None:
        raise APIError(404, "Attempting to follow a user who does not exist")

    follow_ids = [f.id for f in following_user.following]
    if user_id in follow_ids:
        del following_user.following[follow_ids.index(user_id)]

        login.last_action = datetime.now()
        db.session.add(following_user, login)
        db.session.commit()

    return jsonify([u.to_dict() for u in following_user.following]), 200
Exemple #2
0
def get_accounts():
    """Retrieve accounts on this server. Can use the following query parameters:

    * max: The maximum number of records to return
    * page: The "page" of records
    """

    error_on_unauthorized()

    accounts = Login.query.order_by(Login.id)
    total_num = accounts.count()

    if total_num == 0:
        return jsonify(total=0, uploads=[])

    try:
        count = int(request.args.get('max', total_num))
        page = int(request.args.get('page', 1))

        if count <= 0 or page <= 0:
            raise APIError(422, "Query parameters out of range")

        begin = (page - 1) * count
        end = min(begin + count, total_num)

        return jsonify(
            total=total_num,
            users=[login_to_dict(l) for l in accounts.all()[begin:end]]), 200
    except ValueError:
        raise APIError(422, "Invalid query parameter")
Exemple #3
0
def edit_avatar():
    """Change the avatar of the logged-in user."""

    username = get_jwt_identity()
    origin = current_app.config['SERVER_ORIGIN']

    profile = User.query.filter_by(username=username, origin=origin).first()
    if profile is None:
        # This shouldn't happen, but an attacker could feasibly try to edit
        # a nonexistent profile.
        raise APIError(400, "User {username} does not exist on this server".format(username=username))

    # Get the new avatar from the request and put it in the DB
    if 'avatar' in request.files:
        for f in request.files.getlist('avatar'):
            try:
                fid = flake_id()
                name_with_id = printable_id(fid) + f.name
                filename = liblio_uploads.save(f)
                avatar = Avatar(flake=fid, filename=filename, user=profile)
                profile.current_avatar = avatar

                db.session.add(avatar, profile)
                db.session.commit()

                return jsonify(msg="Avatar changed for user {0}".format(profile.username)), \
                    200, \
                    { 'Location': avatar.uri }
            except UploadNotAllowed as error:
                print("Upload failed: {0}", str(error))
                raise APIError(415, "Can't upload files of this type")
Exemple #4
0
def get_by_name(username):
    """Find a user by name. This can be done in one of two ways. For local users,
    simply passing the username will find the user with that name. If the query
    parameter contains an @ character, the search will instead be across all known
    servers.

    Examples:
    * "/by-name/foo" searches for a user named "foo" on this server.
    * "/by-name/[email protected]" searches for a user with the name "foo" from
        the origin server "example.com".
    """

    if '@' in username:
        # Username/origin pair, so search both
        name, origin = username.split('@')
        user = User.query.filter_by(username=name, origin=origin).first()

        if user is not None:
            return jsonify(user.to_dict()), 200
        else:
            raise APIError(
                404, "No user {username} found.".format(username=username))
    else:
        # Local username search
        user = User.query.filter_by(username=username).first()

        if user is not None:
            return jsonify(user.to_dict()), 200
        else:
            raise APIError(
                404, "User {name} does not exist on this server".format(
                    name=username))
Exemple #5
0
def get_avatars():
    """Retrieves avatar metadata for all users known to this server. Can
    use the following query parameters:

    * max: The maximum number of records to return
    * page: The page of records
    """

    error_on_unauthorized()

    media = Avatar.query.order_by(Avatar.id)
    total_num = media.count()

    if total_num == 0:
        return jsonify(total=0, uploads=[])

    try:
        count = int(request.args.get('max', total_num))
        page = int(request.args.get('page', 1))

        if count <= 0 or page <= 0:
            raise APIError(422, "Query parameters out of range")

        begin = (page - 1) * count
        end = min(begin + count, total_num)

        return jsonify(
            total=total_num,
            uploads=[avatar_to_dict(a) for a in media.all()[begin:end]]), 200
    except ValueError:
        raise APIError(422, "Invalid query parameter")
Exemple #6
0
def create_post(args):
    """Create a new post."""
    username = get_jwt_identity()

    login = Login.query.filter_by(username=username).first()

    if login is None:
        raise APIError(400, "User {username} does not exist on this server".format(username=username))

    post = Post(
        user=login.user,
        subject=args.get('subject'),
        source=args['source'],
    )

    # Posts without parents are allowed; they're just "top-level"
    parent_id = args.get('parent_id')
    if parent_id is not None:
        post.parent_id = parent_id

    meta = args.get('metadata')
    if meta is not None:
        mdict = flask.json.loads(meta)
        post.post_meta = mdict

        # Posts can have tags
        tags = mdict.get('tags')
        if tags is not None:
            for t in tags:
                tid = Tag.query.filter_by(name=Tag.normalize_name(t)).first()
                post.tags.append(tid)

    # This does affect the "last active" time
    login.last_action = datetime.now()

    db.session.add(post, login)

    # Posts may have attached media, so store that
    if 'files' in request.files:
        for f in request.files.getlist('files'):
            try:
                fid = flake_id()
                name_with_id = printable_id(fid) + f.name
                filename = liblio_uploads.save(f)
                upload = Upload(flake=fid, filename=filename, user=login.user, post=post, mimetype=f.mimetype)
                db.session.add(upload)
            except UploadNotAllowed as error:
                print("Upload failed: {0}", str(error))
                raise APIError(415, "Can't upload files of this type")

    db.session.commit()

    return jsonify(post.to_dict()), 201, { 'Location': post.uri }
Exemple #7
0
def error_on_unauthorized():
    """Raises appropriate API errors on bad usernames and for users
    who are not administrators. This is a common authentication check
    for essentially every admin route.
    """

    username = get_jwt_identity()
    user = Login.query.filter_by(username=username).first()

    if user is None:
        raise APIError(
            400, "User {username} does not exist on this server".format(
                username=username))
    elif user.role is not Role.admin:
        raise APIError(401, "Only administrators have access to this page")
Exemple #8
0
def edit_profile(args):
    """Edit a user's profile."""

    username = get_jwt_identity()
    origin = current_app.config['SERVER_ORIGIN']

    profile = User.query.filter_by(username=username, origin=origin).first()
    if profile is None:
        # This shouldn't happen, but an attacker could feasibly try to edit
        # a nonexistent profile.
        raise APIError(400, "User {username} does not exist on this server".format(username=username))

    new_name = args['name']
    new_bio = args['bio']
    new_tags = args['tags']

    if new_name is not None:
        profile.display_name = new_name
    
    if new_bio is not None:
        profile.bio = new_bio
    
    if new_tags is not None:
        profile.tags = Tag.query.filter(Tag.name.in_(new_tags)).all()
    # TODO: do the same thing for roles and tags, once they're implemented

    # This is an API action, so update the activity timestamp
    profile.login.last_action = datetime.now()

    db.session.add(profile)
    db.session.commit()

    return make_response(jsonify(profile=profile.to_profile_dict()), 200)
Exemple #9
0
def get_by_id(post_id):
    """Get the post with the given database ID."""
    post = Post.query.get(post_id)

    if post is not None:
        return jsonify(post.to_dict()), 200
    else:
        raise APIError(404, "Post with ID {id} does not exist on this server".format(id=post_id))
Exemple #10
0
def get_by_flake(flake_id):
    """Get the post with the given Flake ID."""
    post = Post.query.filter_by(flake=decode_printable_id(flake_id)).first()

    if post is not None:
        return jsonify(post.to_dict()), 200
    else:
        raise APIError(404, "Post with Flake ID {id} does not exist on this server".format(id=flake_id))
Exemple #11
0
def get_tag(tagname):
    """Get the tag with a given name."""
    tag = Tag.query.filter_by(name=tagname).first()

    if tag is not None:
        return jsonify(tag.to_dict()), 200
    else:
        raise APIError(
            404, "No tag {tagname} on this server".format(tagname=tagname))
Exemple #12
0
def get_posts_by_id(user_id):
    """Get all posts for the user with the given database ID."""
    user = User.query.get(user_id)

    if user is not None:
        return jsonify([p.to_dict() for p in user.posts]), 200
    else:
        raise APIError(
            404, "User with ID {id} does not exist on this server".format(
                id=user_id))
Exemple #13
0
def get_by_id(user_id):
    """Get the user with the given database ID"""
    user = User.query.get(user_id)

    if user is not None:
        return jsonify(user.to_dict()), 200
    else:
        raise APIError(
            404, "User with ID {id} does not exist on this server".format(
                id=user_id))
Exemple #14
0
def get_user_following(user_id):
    """Get all users this user is following."""
    user = User.query.get(user_id)

    if user is not None:
        return jsonify([f.to_dict() for f in user.following]), 200
    else:
        raise APIError(
            404, "User with ID {id} does not exist on this server".format(
                id=user_id))
Exemple #15
0
def get_avatar(media_fid):
    """Get uploaded avatar by its Flake ID."""

    avatar = Avatar.query.filter_by(
        flake=decode_printable_id(media_fid)).first()

    if avatar is not None:
        path = liblio_uploads.path(avatar.filename)
        return send_file(path)
    else:
        raise APIError(404, "Media not found")
Exemple #16
0
def get_media(media_fid):
    """Get uploaded media by its Flake ID."""

    media = Upload.query.filter_by(
        flake=decode_printable_id(media_fid)).first()

    if media is not None:
        path = liblio_uploads.path(media.filename)
        return send_file(path)
    else:
        raise APIError(404, "Media not found")
Exemple #17
0
def create_account(args):
    """Create a new account on this server."""
    if request.method == 'POST':

        # TODO: Check for a user who is already logged in

        username = args['username']
        email = args['email']
        password = args['password']

        user = Login.query.filter_by(username=username).first()
        if user is not None:
            raise APIError(message="Username already exists on this server")

        em = Login.query.filter_by(email=email).first()
        if em is not None:
            raise APIError(
                message=
                "A user with this email address already exists on this server")

        login = Login(username=username, email=email)
        login.set_password(password)

        # Create a blank profile for this user (we can add to it later)
        u = User(username=username, origin=current_app.config['SERVER_ORIGIN'])
        login.user = u

        # Add to the DB
        db.session.add(login)
        db.session.commit()

        return make_response(
            jsonify({
                'username':
                username,
                'temp_token':
                create_access_token(username,
                                    expires_delta=timedelta(seconds=60))
            }), 201)
Exemple #18
0
def unshare_post(post_id):
    """Remove a share from a given post."""
    username = get_jwt_identity()

    login = Login.query.filter_by(username=username).first()

    if login is None:
        raise APIError(400, "User {username} does not exist on this server".format(username=username))

    share_ids = [p.id for p in login.user.sharing]
    if post_id in share_ids:
        post = login.user.sharing[share_ids.index(post_id)]
        del login.user.sharing[share_ids.index(post_id)]

        login.last_action = datetime.now()
        db.session.add(post, login)
        db.session.commit()

        return jsonify(sharing=[p.id for p in login.user.sharing]), 200
    else:
        # Trying to remove the share from a post that isn't shared
        raise APIError(404, "User has not shared this post")
Exemple #19
0
def get_posts():
    """Retrieve posts known to this server. This can be filtered by origin, and
    takes the following query parameters:


    * max: The maximum number of records to return
    * page: The "page" of records
    * origin: If specified, only those users with this origin will be returned
    """

    error_on_unauthorized()

    posts = Post.query.order_by(Post.id)
    total_num = posts.count()

    if total_num == 0:
        return jsonify(total=0, uploads=[])

    try:
        count = int(request.args.get('max', total_num))
        page = int(request.args.get('page', 1))
        origin = request.args.get('origin', None)

        if count <= 0 or page <= 0:
            raise APIError(422, "Query parameters out of range")

        if origin is not None:
            posts = posts.filter(User.origin == origin)

        begin = (page - 1) * count
        end = min(begin + count, total_num)

        return jsonify(total=total_num,
                       posts=[p.to_dict()
                              for p in posts.all()[begin:end]]), 200
    except ValueError:
        raise APIError(422, "Invalid query parameter")
Exemple #20
0
def get_my_profile():
    """Get the profile for the logged-in user. (This may have sensitive/private info.)"""

    username = get_jwt_identity()
    origin = current_app.config['SERVER_ORIGIN']

    profile = User.query.filter_by(username=username, origin=origin).first()
    if profile is None:
        # This shouldn't happen.
        raise APIError(400, "User {username} does not exist on this server".format(username=username))

    # Update last activity time
    profile.login.last_action = datetime.now()

    return make_response(jsonify(profile=profile.to_profile_dict()), 200)
Exemple #21
0
def get_users():
    """Retrieve users known to this server, whether local or foreign. Can use the
    following query parameters:

    * max: The maximum number of records to return
    * page: The "page" of records
    * origin: If specified, only those users with this origin will be returned
    """

    error_on_unauthorized()

    users = User.query.order_by(User.id)
    total_num = users.count()

    if total_num == 0:
        return jsonify(total=0, uploads=[])

    try:
        count = int(request.args.get('max', total_num))
        page = int(request.args.get('page', 1))
        origin = request.args.get('origin', None)

        if count <= 0 or page <= 0:
            raise APIError(422, "Query parameters out of range")

        if origin is not None:
            users = users.filter_by(origin=origin)

        begin = (page - 1) * count
        end = min(begin + count, total_num)

        return jsonify(total=total_num,
                       users=[u.to_dict()
                              for u in users.all()[begin:end]]), 200
    except ValueError:
        raise APIError(422, "Invalid query parameter")
Exemple #22
0
def share_post(post_id):
    """Share (aka boost/announce) a given post."""
    username = get_jwt_identity()

    login = Login.query.filter_by(username=username).first()

    if login is None:
        raise APIError(400, "User {username} does not exist on this server".format(username=username))

    if post_id not in [p.id for p in login.user.sharing]:
        post = Post.query.get(post_id)
        login.user.sharing.append(post)

        login.last_action = datetime.now()
        db.session.add(post, login)
        db.session.commit()

    return jsonify(sharing=[p.id for p in login.user.sharing]), 201
Exemple #23
0
def like_post(post_id):
    """Like the post with a given ID."""
    username = get_jwt_identity()

    login = Login.query.filter_by(username=username).first()

    if login is None:
        raise APIError(400, "User {username} does not exist on this server".format(username=username))

    if post_id not in [p.id for p in login.user.likes]:
        post = Post.query.get(post_id)
        login.user.likes.append(post)

        login.last_action = datetime.now()
        db.session.add(post, login)
        db.session.commit()

    return jsonify({ 'likes': [p.id for p in login.user.likes] }), 201
Exemple #24
0
def get_my_info():
    """Get the likes, shares, and following/followed lists for the logged-in user."""

    username = get_jwt_identity()
    origin = current_app.config['SERVER_ORIGIN']

    profile = User.query.filter_by(username=username, origin=origin).first()
    if profile is None:
        # This shouldn't happen.
        raise APIError(400, "User {username} does not exist on this server".format(username=username))

    # Update last activity time
    profile.login.last_action = datetime.now()

    return jsonify(
        likes=[l.id for l in profile.likes],
        shares=[s.id for s in profile.sharing],
        followers=[f.id for f in profile.followers],
        following=[f.id for f in profile.following]
    ), 200
Exemple #25
0
def login(args):
    """Log in to this server, receiving an authentication token in response."""

    username = args['username']
    password = args['password']

    login = Login.query.filter_by(username=username).first()
    if login is None or not login.check_password(password):
        # Best security practice is to avoid telling a user whether
        # the username or password is incorrect.
        raise APIError(401, "Invalid username or password")

    login.last_login = datetime.now()
    login.last_action = datetime.now()
    db.session.add(login)
    db.session.commit()

    token = create_access_token(username)
    refresh = create_refresh_token(username)
    return jsonify(access_token=token,
                   refresh_token=refresh,
                   role=login.role.name), 200
Exemple #26
0
def create_account_get():
    """This endpoint does not support GET, so send a formatted response."""
    raise APIError(405, "POST to this endpoint to create an account")