Exemplo n.º 1
0
    def setup(cls: "ErrorView", manager: "pysite.route_manager.RouteManager",
              blueprint: Blueprint):
        """
        Set up the view by registering it as the error handler for the HTTP status codes specified in the class
        attributes - this will also deal with multiple inheritance by calling `super().setup()` as appropriate.

        :param manager: Instance of the current RouteManager
        :param blueprint: Current Flask blueprint to register the error handler for
        """

        if hasattr(super(), "setup"):
            super().setup(manager, blueprint)  # pragma: no cover

        if not cls.name or not cls.error_code:
            raise RuntimeError(
                "Error views must have both `name` and `error_code` defined")

        if isinstance(cls.error_code, int):
            cls.error_code = [cls.error_code]

        if isinstance(cls.error_code, Iterable):
            for code in cls.error_code:
                if isinstance(code, int) and code not in default_exceptions:
                    continue  # Otherwise we'll possibly get an exception thrown during blueprint registration

                if cls.register_on_app:
                    manager.app.errorhandler(code)(cls.as_view(cls.name))
                else:
                    blueprint.errorhandler(code)(cls.as_view(cls.name))
        else:
            raise RuntimeError(
                "Error views must have an `error_code` that is either an `int` or an iterable"
            )  # pragma: no cover # noqa: E501
Exemplo n.º 2
0
def create_api_blueprint(name,
                         import_name,
                         url_prefix=None,
                         jsonize=True,
                         handle_http_error=True):
    """
    幺蛾子, 就是因为flask写API挂路由太累了, 搞了这么个东西.
    会把url_prefix挂到/api/下.
    比如url_prefix是test, 那么route全部在/api/test下
    """
    if url_prefix and url_prefix.startswith('/'):
        raise URLPrefixError('url_prefix ("%s") must not start with /' %
                             url_prefix)

    bp_url_prefix = '/api/'
    if url_prefix:
        bp_url_prefix = os.path.join(bp_url_prefix, url_prefix)
    bp = Blueprint(name, import_name, url_prefix=bp_url_prefix)

    if handle_http_error:

        def _error_hanlder(error):
            return jsonify({'error': error.description}), error.code

        for code in ERROR_CODES:
            bp.errorhandler(code)(_error_hanlder)

    # 如果不需要自动帮忙jsonize, 就不要
    # 可能的场景比如返回一个stream
    if jsonize:
        patch_blueprint_route(bp)

    return bp
Exemplo n.º 3
0
def create_page_blueprint(name, import_name, url_prefix=None):
    bp = Blueprint(name, import_name, url_prefix=url_prefix)

    def _error_hanlder(error):
        return render_template('/error/%s.mako' % error.code, err=error)

    for code in ERROR_CODES:
        bp.errorhandler(code)(_error_hanlder)

    return bp
Exemplo n.º 4
0
def create_api_blueprint(name, import_name, url_prefix=None):
    bp = Blueprint(name, import_name, url_prefix=url_prefix)

    def _error_hanlder(error):
        return jsonify({"error": error.description}), error.code

    for code in ERROR_CODES:
        bp.errorhandler(code)(_error_hanlder)

    patch_blueprint_route(bp)
    return bp
Exemplo n.º 5
0
def create_ajax_blueprint(name, import_name, url_prefix=None):
    bp = Blueprint(name, import_name, url_prefix=url_prefix)

    def _error_hanlder(error):
        return jsonify({'error': error.description}), error.code

    for code in ERROR_CODES:
        bp.errorhandler(code)(_error_hanlder)

    patch_blueprint_route(bp)
    return bp
Exemplo n.º 6
0
def create_blueprint(endpoints):
    """Create Invenio-Deposit-REST blueprint."""
    blueprint = Blueprint(
        'b2share_deposit_rest',
        __name__,
        url_prefix='',
    )
    blueprint.errorhandler(PIDInvalidAction)(create_api_errorhandler(
        status=403, message='Invalid action'))
    blueprint.errorhandler(InvalidOperationError)(create_api_errorhandler(
        status=403, message='Invalid operation'))

    for endpoint, options in (endpoints or {}).items():
        for rule in records_rest_url_rules(endpoint, **options):
            blueprint.add_url_rule(**rule)

    return blueprint
Exemplo n.º 7
0
def create_blueprint(endpoints):
    """Create Invenio-Deposit-REST blueprint."""
    blueprint = Blueprint(
        'b2share_deposit_rest',
        __name__,
        url_prefix='',
    )
    blueprint.errorhandler(PIDInvalidAction)(create_api_errorhandler(
        status=403, message='Invalid action'
    ))
    blueprint.errorhandler(InvalidOperationError)(create_api_errorhandler(
        status=403, message='Invalid operation'
    ))

    for endpoint, options in (endpoints or {}).items():
        for rule in records_rest_url_rules(endpoint, **options):
            blueprint.add_url_rule(**rule)

    return blueprint
Exemplo n.º 8
0
        return apology(message="subject doesn't exist")

    db.execute("DELETE FROM subjects WHERE user_id = :id AND subject = :s AND type = :t AND lecturer = :l AND day = :d AND place = :p AND start_time = :st AND end_time = :e",
               {"id": session["user_id"], "s": s, "t": t, "l": l, "d": d, "p": p, "st": st, "e": e})
    db.commit()

    # Update session['subjects'] (used in navbar and filling forms) because this might be the last period of the subject (which deletes it entirely)
    subject_count = db.execute("SELECT COUNT(subject) FROM subjects WHERE user_id = :id AND subject = :s", {"id": session["user_id"], 's': s}).fetchone()[0]
    if subject_count == 0:
        session["subjects"] = rowproxy_to_dict(db.execute("SELECT DISTINCT subject FROM subjects WHERE user_id = :id ORDER BY subject",
                                         {"id": session["user_id"]}).fetchall())
        # Delete the subject's dues
        db.execute("DELETE FROM dues WHERE user_id = :id AND subject = :s", {"id": session["user_id"], "s": s})
        # Delete the subjetc's notes
        db.execute("DELETE FROM notes WHERE user_id = :id AND subject = :s", {"id": session["user_id"], "s": s})
        db.commit()
    flash("Period deleted!")
    return redirect("/schedule")


def errorhandler(e):
    """Handle error"""
    if not isinstance(e, HTTPException):
        e = InternalServerError()
    return apology(e.name, e.code)


# Listen for errors
for code in default_exceptions:
    periods.errorhandler(code)(errorhandler)
Exemplo n.º 9
0
from . import core, ext
from ._compat import text_type
from .core import CtfException


bp = Blueprint('api', __name__)
ext.csrf.exempt(bp)


def handle_error(exc):
    return jsonify({'message': exc.description}), exc.code


for code in exceptions.default_exceptions.keys():
    if code != 500:
        bp.errorhandler(code)(handle_error)


def param(key, desired_type=None):
    """Return a decorator to parse a JSON request value."""
    def decorator(view_func):
        """The actual decorator"""
        @wraps(view_func)
        def inner(*args, **kwargs):
            data = request.get_json()  # May raise a 400
            try:
                value = data[key]
            except (KeyError, TypeError):
                abort(400, "Missing JSON value '{0}'.".format(key))
            if desired_type and not isinstance(value, desired_type):
                # For the error message
Exemplo n.º 10
0
    def setup(cls: "ErrorView", blueprint: Blueprint):
        if not cls.name or not cls.error_code:
            raise RuntimeError(
                "Error views must have both `name` and `error_code` defined")

        blueprint.errorhandler(cls.error_code)(cls.as_view(cls.name))
Exemplo n.º 11
0
        else:
            return apology("must enter stock ticker symbol", 403)

    # request symbol information from IEX cloud
    quoteInfo = lookup(symbol)

    # redirect to buy page with error message if no symbol was found
    if quoteInfo is None:
        return apology(
            "The symbol was not found or something else went wrong.", 403)

    # get number of owned stocks and average price of aquisition
    for row in holdings:
        if symbol == row.symbol:
            sharesOwned = row.shares
            avgPrice = row.avgprice
            amount = row.total
            break

    # load sell page with stock ticker information filled in
    return render_template("sell.html",
                           quoteInfo=quoteInfo,
                           sharesOwned=sharesOwned,
                           avgPrice=avgPrice,
                           amount=amount)


# Listen for errors
for code in default_exceptions:
    transactions.errorhandler(code)(errorhandler)
Exemplo n.º 12
0
from app.main import errors

auth = Blueprint('auth', __name__)

auth.add_url_rule('/login/', 'login', views.login, methods=['GET', 'POST'])
auth.add_url_rule('/logout/', 'logout', views.logout)
auth.add_url_rule('/registration/',
                  'register',
                  views.register,
                  methods=['GET', 'POST'])
auth.add_url_rule('/registration/su',
                  'register_su',
                  views.register,
                  defaults={'create_su': True},
                  methods=['GET', 'POST'])
auth.add_url_rule('/add-user/',
                  'add_user',
                  views.add_user,
                  methods=['GET', 'POST'])
auth.add_url_rule('/users/', view_func=views.Users.as_view('users'))
auth.add_url_rule('/users/edit/<username>',
                  view_func=views.User.as_view('edit_user'))
auth.add_url_rule('/su-users/', view_func=views.SuUsers.as_view('su_users'))
auth.add_url_rule('/su-users/edit/<username>',
                  view_func=views.SuUser.as_view('su_edit_user'))
auth.add_url_rule('/user/settings/',
                  view_func=views.Profile.as_view('settings'))
auth.add_url_rule('/user/password/',
                  view_func=views.Password.as_view('password'))
auth.errorhandler(403)(errors.handle_forbidden)
Exemplo n.º 13
0
        }).fetchone()
    if not diary:
        return error_page("Diary doesn't exist")
    with open('diary.md', 'w') as d:
        header = f"{diary['title']}\nby {username}.\n{diary['date']}\nThat day was {diary['rating']}.\n"
        d.write(header)
        d.write(diary['diary'])
    return send_file('diary.md', as_attachment=True)


@diary.route('/public_diaries')
@login_required
def public_diaries():
    diaries = db.execute("""SELECT title, rating, diary, date, username 
                            FROM diaries JOIN users ON diaries.user_id = users.id
                            WHERE users.visibility = '1' ORDER BY date DESC"""
                         ).fetchall()
    return render_template('public_diaries.html', diaries=diaries)


def errorhandler(e):
    """Handle error"""
    if not isinstance(e, HTTPException):
        e = InternalServerError()
    return apology(e.name, e.code)


# Listen for errors
for code in default_exceptions:
    diary.errorhandler(code)(errorhandler)
Exemplo n.º 14
0
from . import core, ext
from ._compat import text_type
from .core import CtfException


bp = Blueprint('api', __name__)
ext.csrf.exempt(bp)


def handle_error(exc):
    return jsonify({'message': exc.description}), exc.code


for code in exceptions.default_exceptions.keys():
    if code != 500:
        bp.errorhandler(code)(handle_error)


def param(key, desired_type=None):
    """Return a decorator to parse a JSON request value."""
    def decorator(view_func):
        """The actual decorator"""
        @wraps(view_func)
        def inner(*args, **kwargs):
            data = request.get_json()  # May raise a 400
            try:
                value = data[key]
            except (KeyError, TypeError):
                abort(400, "Missing JSON value '{0}'.".format(key))
            if desired_type and not isinstance(value, desired_type):
                # For the error message
Exemplo n.º 15
0
from flask import Blueprint

from . import views
from utils import errors

main = Blueprint('main', __name__)

main.errorhandler(404)(errors.page_not_found)
main.errorhandler(401)(errors.handle_unauthorized)
main.add_url_rule('/', view_func=views.IndexView.as_view('index'))
main.add_url_rule('/enterprise/', view_func=views.EnterpriseView.as_view('enterprise'))
main.add_url_rule('/users/<username>/', view_func=views.UserView.as_view('user_view'))
# main.add_url_rule('/test-celery/', 'test-celery', views.test_celery)
# main.add_url_rule('/test/', 'test', views.test)
main.add_url_rule('/import-repo/', view_func=views.ImportRepoView.as_view('import_repo'), defaults={'starred':True})
main.add_url_rule('/starred-repos/', view_func=views.StarredRepoView.as_view('starred_repos'))
main.add_url_rule('/users/<username>/starred-repos/', view_func=views.StarredRepoView.as_view('user_starred_repos'))
main.add_url_rule('/all-repos/', view_func=views.ReposView.as_view('all_repos'))
main.add_url_rule('/search/github/', view_func=views.GitHubResultView.as_view('github_result'))
main.add_url_rule('/user/collections/', view_func=views.MyCollectionsView.as_view('my_collections'))
main.add_url_rule('/users/<username>/collections/', view_func=views.UserCollectionsView.as_view('user_collections'))
main.add_url_rule('/user/collections/<collection_id>/edit/', view_func=views.MyCollectionEditView.as_view('edit_collection'))
main.add_url_rule('/user/collections/<collection_id>/detail/', view_func=views.CollectionView.as_view('collection_detail'))
main.add_url_rule('/user/collections/<collection_id>/detail/edit/', view_func=views.CollectionDetailEditView.as_view('collection_detail_edit'))
main.add_url_rule('/user/collections/<collection_id>/detail/edit/search', view_func=views.Search4Collection.as_view('collection_detail_edit_search'))
Exemplo n.º 16
0
        return render_template("feedback.html")
    else:
        feedback_type = request.form.get("type")
        email = request.form.get("email")
        feedback = request.form.get("feedback")
        if not (feedback_type or email or feedback):
            return apology("please fill the form")
        db.execute(
            "INSERT INTO feedback (user_id, email, feedback, feedback_type) VALUES(:id, :email, :feedback, :type)",
            {
                "id": session["user_id"],
                "email": email,
                "feedback": feedback,
                "type": feedback_type
            })
        db.commit()
        flash("Feedback submitted! Thanks for your feedback!")
        return redirect("/")


def errorhandler(e):
    """Handle error"""
    if not isinstance(e, HTTPException):
        e = InternalServerError()
    return apology(e.name, e.code)


# Listen for errors
for code in default_exceptions:
    settings.errorhandler(code)(errorhandler)
Exemplo n.º 17
0
            'message': error.description,
            'id': error.id,
        }
        status_code = error.code
    elif isinstance(error, HTTPException):
        message = error.description
        status_code = error.code
        error_data = {
            'message': message,
            'id': "internal",
            'code': status_code
        }
    else:
        message = "Unknown error"
        status_code = 500
        error_data = {
            'message': message,
            'id': "internal",
            'code': status_code
        }

    response = jsonify(status='error', error=error_data)
    response.status_code = status_code

    return response


for code, _ in default_exceptions.items():
    auth_bp.app_errorhandler(code)(error_handler)
    auth_bp.errorhandler(code)(error_handler)
Exemplo n.º 18
0
 def as_blueprint(self) -> Blueprint:
     bp = Blueprint('athena_controllers', __name__)
     bp.route('/')(self.index_handler)
     bp.route('/du')(self.du_handler)
     bp.errorhandler(HTTPException)(error_handler)
     return bp
Exemplo n.º 19
0
def create_api_blueprint(name, import_name, url_prefix=None, jsonize=True):
    """方便创建 api blueprint 增加 4xx 请求处理,返回值 json 化
    name, import_name, url_prefix 同 Blueprint
    jsonize 等于 Ture 时,自动帮忙序列化,反之没有处理

    在 debug 模式下,500 的错误不进行处理,走 flask 默认的处理,方便调试

    使用:

    from flask import abort

    abort(404, 'xxx not found')
    request 返回 {'msg': 'xxx not found', 'code': 404}, 200

    # raise 非自定义错误
    raise Exception('xxx')
    request 返回 {'msg': '服务器错误'}, 500

    raise CustomError('error', code, payload)
    request 返回 {'msg': 'error', 'code': 200, payload}, 200

    """

    if url_prefix and url_prefix.startswith('/'):
        raise URLPrefixError(
            'url_prefix ("{}") must not start with /'.format(url_prefix))

    bp_url_prefix = '/api/'
    if url_prefix:
        bp_url_prefix = os.path.join(bp_url_prefix, url_prefix)
    bp = Blueprint(name, import_name, url_prefix=bp_url_prefix)

    def _error_hanlder(error):
        print(error)
        # 处理自定义错误
        if issubclass(error.__class__, CustomError):
            return jsonify(error.to_dict())

        # 处理 abort 错误
        if isinstance(error, HTTPException):
            return jsonify({
                'msg': error.description,
                'code': error.code,
            })

        if isinstance(error, JWTError):
            return jsonify({'msg': error.description, 'code': 401})

        # 处理其他错误
        return jsonify({
            'msg': '服务器错误',
            'code': INTERNAL_SERVER_ERROR,
        }), INTERNAL_SERVER_ERROR

    for code in ERROR_CODES:
        bp.errorhandler(code)(_error_hanlder)

    bp.errorhandler(CustomError)(_error_hanlder)
    bp.errorhandler(JWTError)(_error_hanlder)

    if not DEBUG:
        bp.errorhandler(INTERNAL_SERVER_ERROR)(_error_hanlder)

    if jsonize:
        patch_blueprint_route(bp)

    return bp
Exemplo n.º 20
0
def make_blueprint(config: SDConfig) -> Blueprint:
    api = Blueprint("api", __name__)

    @api.route("/")
    def get_endpoints() -> Tuple[flask.Response, int]:
        endpoints = {
            "sources_url": "/api/v1/sources",
            "current_user_url": "/api/v1/user",
            "all_users_url": "/api/v1/users",
            "submissions_url": "/api/v1/submissions",
            "replies_url": "/api/v1/replies",
            "seen_url": "/api/v1/seen",
            "auth_token_url": "/api/v1/token",
        }
        return jsonify(endpoints), 200

    # Before every post, we validate the payload before processing the request
    @api.before_request
    def validate_data() -> None:
        if request.method == "POST":
            # flag, star, and logout can have empty payloads
            if not request.data:
                dataless_endpoints = [
                    "add_star",
                    "remove_star",
                    "flag",
                    "logout",
                ]
                for endpoint in dataless_endpoints:
                    if request.endpoint == "api." + endpoint:
                        return
                abort(400, "malformed request")
            # other requests must have valid JSON payload
            else:
                try:
                    json.loads(request.data.decode("utf-8"))
                except (ValueError):
                    abort(400, "malformed request")

    @api.route("/token", methods=["POST"])
    def get_token() -> Tuple[flask.Response, int]:
        creds = json.loads(request.data.decode("utf-8"))

        username = creds.get("username", None)
        passphrase = creds.get("passphrase", None)
        one_time_code = creds.get("one_time_code", None)

        if username is None:
            abort(400, "username field is missing")
        if passphrase is None:
            abort(400, "passphrase field is missing")
        if one_time_code is None:
            abort(400, "one_time_code field is missing")

        try:
            journalist = Journalist.login(username, passphrase, one_time_code)
            token_expiry = datetime.now(
                timezone.utc) + timedelta(seconds=TOKEN_EXPIRATION_MINS * 60)

            response = jsonify({
                "token":
                journalist.generate_api_token(
                    expiration=TOKEN_EXPIRATION_MINS * 60),
                "expiration":
                token_expiry,
                "journalist_uuid":
                journalist.uuid,
                "journalist_first_name":
                journalist.first_name,
                "journalist_last_name":
                journalist.last_name,
            })

            # Update access metadata
            journalist.last_access = datetime.now(timezone.utc)
            db.session.add(journalist)
            db.session.commit()

            return response, 200
        except (
                LoginThrottledException,
                InvalidUsernameException,
                BadTokenException,
                InvalidOTPSecretException,
                WrongPasswordException,
        ):
            return abort(403, "Token authentication failed.")

    @api.route("/sources", methods=["GET"])
    @token_required
    def get_all_sources() -> Tuple[flask.Response, int]:
        sources = Source.query.filter_by(pending=False, deleted_at=None).all()
        return jsonify({"sources":
                        [source.to_json() for source in sources]}), 200

    @api.route("/sources/<source_uuid>", methods=["GET", "DELETE"])
    @token_required
    def single_source(source_uuid: str) -> Tuple[flask.Response, int]:
        if request.method == "GET":
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            return jsonify(source.to_json()), 200
        elif request.method == "DELETE":
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            utils.delete_collection(source.filesystem_id)
            return jsonify({"message": "Source and submissions deleted"}), 200
        else:
            abort(405)

    @api.route("/sources/<source_uuid>/add_star", methods=["POST"])
    @token_required
    def add_star(source_uuid: str) -> Tuple[flask.Response, int]:
        source = get_or_404(Source, source_uuid, column=Source.uuid)
        utils.make_star_true(source.filesystem_id)
        db.session.commit()
        return jsonify({"message": "Star added"}), 201

    @api.route("/sources/<source_uuid>/remove_star", methods=["DELETE"])
    @token_required
    def remove_star(source_uuid: str) -> Tuple[flask.Response, int]:
        source = get_or_404(Source, source_uuid, column=Source.uuid)
        utils.make_star_false(source.filesystem_id)
        db.session.commit()
        return jsonify({"message": "Star removed"}), 200

    @api.route("/sources/<source_uuid>/flag", methods=["POST"])
    @token_required
    def flag(source_uuid: str) -> Tuple[flask.Response, int]:
        return jsonify(
            {"message": "Sources no longer need to be flagged for reply"}), 200

    @api.route("/sources/<source_uuid>/conversation", methods=["DELETE"])
    @token_required
    def source_conversation(source_uuid: str) -> Tuple[flask.Response, int]:
        if request.method == "DELETE":
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            utils.delete_source_files(source.filesystem_id)
            return jsonify({"message": "Source data deleted"}), 200
        else:
            abort(405)

    @api.route("/sources/<source_uuid>/submissions", methods=["GET"])
    @token_required
    def all_source_submissions(source_uuid: str) -> Tuple[flask.Response, int]:
        source = get_or_404(Source, source_uuid, column=Source.uuid)
        return (
            jsonify({
                "submissions":
                [submission.to_json() for submission in source.submissions]
            }),
            200,
        )

    @api.route("/sources/<source_uuid>/submissions/<submission_uuid>/download",
               methods=["GET"])
    @token_required
    def download_submission(source_uuid: str,
                            submission_uuid: str) -> flask.Response:
        get_or_404(Source, source_uuid, column=Source.uuid)
        submission = get_or_404(Submission,
                                submission_uuid,
                                column=Submission.uuid)
        return utils.serve_file_with_etag(submission)

    @api.route("/sources/<source_uuid>/replies/<reply_uuid>/download",
               methods=["GET"])
    @token_required
    def download_reply(source_uuid: str, reply_uuid: str) -> flask.Response:
        get_or_404(Source, source_uuid, column=Source.uuid)
        reply = get_or_404(Reply, reply_uuid, column=Reply.uuid)

        return utils.serve_file_with_etag(reply)

    @api.route("/sources/<source_uuid>/submissions/<submission_uuid>",
               methods=["GET", "DELETE"])
    @token_required
    def single_submission(source_uuid: str,
                          submission_uuid: str) -> Tuple[flask.Response, int]:
        if request.method == "GET":
            get_or_404(Source, source_uuid, column=Source.uuid)
            submission = get_or_404(Submission,
                                    submission_uuid,
                                    column=Submission.uuid)
            return jsonify(submission.to_json()), 200
        elif request.method == "DELETE":
            get_or_404(Source, source_uuid, column=Source.uuid)
            submission = get_or_404(Submission,
                                    submission_uuid,
                                    column=Submission.uuid)
            utils.delete_file_object(submission)
            return jsonify({"message": "Submission deleted"}), 200
        else:
            abort(405)

    @api.route("/sources/<source_uuid>/replies", methods=["GET", "POST"])
    @token_required
    def all_source_replies(source_uuid: str) -> Tuple[flask.Response, int]:
        if request.method == "GET":
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            return jsonify(
                {"replies":
                 [reply.to_json() for reply in source.replies]}), 200
        elif request.method == "POST":
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            if request.json is None:
                abort(400, "please send requests in valid JSON")

            if "reply" not in request.json:
                abort(400, "reply not found in request body")

            user = _authenticate_user_from_auth_header(request)

            data = request.json
            if not data["reply"]:
                abort(400, "reply should not be empty")

            source.interaction_count += 1
            try:
                filename = Storage.get_default().save_pre_encrypted_reply(
                    source.filesystem_id,
                    source.interaction_count,
                    source.journalist_filename,
                    data["reply"],
                )
            except NotEncrypted:
                return jsonify(
                    {"message": "You must encrypt replies client side"}), 400

            # issue #3918
            filename = path.basename(filename)

            reply = Reply(user, source, filename, Storage.get_default())

            reply_uuid = data.get("uuid", None)
            if reply_uuid is not None:
                # check that is is parseable
                try:
                    UUID(reply_uuid)
                except ValueError:
                    abort(400, "'uuid' was not a valid UUID")
                reply.uuid = reply_uuid

            try:
                db.session.add(reply)
                seen_reply = SeenReply(reply=reply, journalist=user)
                db.session.add(seen_reply)
                db.session.add(source)
                db.session.commit()
            except IntegrityError as e:
                db.session.rollback()
                if "UNIQUE constraint failed: replies.uuid" in str(e):
                    abort(409, "That UUID is already in use.")
                else:
                    raise e

            return (
                jsonify({
                    "message": "Your reply has been stored",
                    "uuid": reply.uuid,
                    "filename": reply.filename,
                }),
                201,
            )
        else:
            abort(405)

    @api.route("/sources/<source_uuid>/replies/<reply_uuid>",
               methods=["GET", "DELETE"])
    @token_required
    def single_reply(source_uuid: str,
                     reply_uuid: str) -> Tuple[flask.Response, int]:
        get_or_404(Source, source_uuid, column=Source.uuid)
        reply = get_or_404(Reply, reply_uuid, column=Reply.uuid)
        if request.method == "GET":
            return jsonify(reply.to_json()), 200
        elif request.method == "DELETE":
            utils.delete_file_object(reply)
            return jsonify({"message": "Reply deleted"}), 200
        else:
            abort(405)

    @api.route("/submissions", methods=["GET"])
    @token_required
    def get_all_submissions() -> Tuple[flask.Response, int]:
        submissions = Submission.query.all()
        return (
            jsonify({
                "submissions": [
                    submission.to_json() for submission in submissions
                    if submission.source
                ]
            }),
            200,
        )

    @api.route("/replies", methods=["GET"])
    @token_required
    def get_all_replies() -> Tuple[flask.Response, int]:
        replies = Reply.query.all()
        return jsonify({
            "replies": [reply.to_json() for reply in replies if reply.source]
        }), 200

    @api.route("/seen", methods=["POST"])
    @token_required
    def seen() -> Tuple[flask.Response, int]:
        """
        Lists or marks the source conversation items that the journalist has seen.
        """
        user = _authenticate_user_from_auth_header(request)

        if request.method == "POST":
            if request.json is None or not isinstance(request.json,
                                                      collections.abc.Mapping):
                abort(400, "Please send requests in valid JSON.")

            if not any(map(request.json.get,
                           ["files", "messages", "replies"])):
                abort(400, "Please specify the resources to mark seen.")

            # gather everything to be marked seen. if any don't exist,
            # reject the request.
            targets = set()  # type: Set[Union[Submission, Reply]]
            for file_uuid in request.json.get("files", []):
                f = Submission.query.filter(
                    Submission.uuid == file_uuid).one_or_none()
                if f is None or not f.is_file:
                    abort(404, "file not found: {}".format(file_uuid))
                targets.add(f)

            for message_uuid in request.json.get("messages", []):
                m = Submission.query.filter(
                    Submission.uuid == message_uuid).one_or_none()
                if m is None or not m.is_message:
                    abort(404, "message not found: {}".format(message_uuid))
                targets.add(m)

            for reply_uuid in request.json.get("replies", []):
                r = Reply.query.filter(Reply.uuid == reply_uuid).one_or_none()
                if r is None:
                    abort(404, "reply not found: {}".format(reply_uuid))
                targets.add(r)

            # now mark everything seen.
            utils.mark_seen(list(targets), user)

            return jsonify({"message": "resources marked seen"}), 200

        abort(405)

    @api.route("/user", methods=["GET"])
    @token_required
    def get_current_user() -> Tuple[flask.Response, int]:
        user = _authenticate_user_from_auth_header(request)
        return jsonify(user.to_json()), 200

    @api.route("/users", methods=["GET"])
    @token_required
    def get_all_users() -> Tuple[flask.Response, int]:
        users = Journalist.query.all()
        return jsonify(
            {"users": [user.to_json(all_info=False) for user in users]}), 200

    @api.route("/logout", methods=["POST"])
    @token_required
    def logout() -> Tuple[flask.Response, int]:
        user = _authenticate_user_from_auth_header(request)
        auth_token = request.headers["Authorization"].split(" ")[1]
        utils.revoke_token(user, auth_token)
        return jsonify({"message": "Your token has been revoked."}), 200

    def _handle_api_http_exception(
        error: werkzeug.exceptions.HTTPException,
    ) -> Tuple[flask.Response, int]:
        # Workaround for no blueprint-level 404/5 error handlers, see:
        # https://github.com/pallets/flask/issues/503#issuecomment-71383286
        response = jsonify({"error": error.name, "message": error.description})

        return response, error.code  # type: ignore

    for code in default_exceptions:
        api.errorhandler(code)(_handle_api_http_exception)

    return api
Exemplo n.º 21
0
from flask import Blueprint

from . import views, errors

main = Blueprint('main', __name__)

main.add_url_rule('/', 'index', views.index, methods=['GET'])
main.add_url_rule('/show_more', 'show_more', views.show_more, methods=['POST'])
main.errorhandler(404)(errors.page_not_found)
main.add_url_rule('/<path:invalid_path>', 'handle_unmatchable',
                  errors.handle_unmatchable)
Exemplo n.º 22
0
                "s": subject,
                "n": note
            })
        db.commit()
    except Exception as x:
        return apology(x)
    flash("Note deleted!")
    return redirect("/notes")


@notes.route("/delete/notes", methods=["POST"])
@login_required
def delete_notes():
    db.execute("DELETE FROM notes WHERE user_id = :id",
               {"id": session["user_id"]})
    db.commit()
    flash("All notes deleted!")
    return redirect("/")


def errorhandler(e):
    """Handle error"""
    if not isinstance(e, HTTPException):
        e = InternalServerError()
    return apology(e.name, e.code)


# Listen for errors
for code in default_exceptions:
    notes.errorhandler(code)(errorhandler)
Exemplo n.º 23
0
        current = Users.query.filter(Users.id == user_id).first()

        if operation == "add":
            new_amount = current.cash + amount
            message = "You successfully added funds to your account"

        if operation == "withdraw":
            if amount > current.cash:
                return apology(
                    "The amount of cash in your account is not enough", 403)
            new_amount = current.cash - amount
            message = "You successfully withdrew funds from your account"

        # Udate cash amount in database
        current.cash = new_amount
        db.session.commit()

        # Flash success message
        flash(message)

        # Redirect user to home page
        return redirect(url_for("home.index"))

    else:
        return redirect(url_for("admin.account"))


# Listen for errors
for code in default_exceptions:
    admin.errorhandler(code)(errorhandler)
Exemplo n.º 24
0
from twilio import TwilioRestException
from sqlalchemy.exc import SQLAlchemyError

from ..extensions import csrf, db

from .models import Call, Session
from ..campaign.constants import ORDER_SHUFFLE, LOCATION_POSTAL, LOCATION_DISTRICT
from ..campaign.models import Campaign, Target
from ..political_data.lookup import locate_targets

from .decorators import crossdomain, abortJSON, stripANSI

call = Blueprint('call', __name__, url_prefix='/call')
call_methods = ['GET', 'POST']
csrf.exempt(call)
call.errorhandler(400)(abortJSON)


def play_or_say(r, audio, **kwds):
    """
    Take twilio response and play or say message from an AudioRecording
    Can use mustache templates to render keyword arguments
    """

    if audio:
        if (hasattr(audio, 'text_to_speech') and not (audio.text_to_speech == '')):
            msg = pystache.render(audio.text_to_speech, kwds)
            r.say(msg)
        elif (hasattr(audio, 'file_storage') and (audio.file_storage.fp is not None)):
            r.play(audio.file_url())
        elif type(audio) == str:
Exemplo n.º 25
0
def create_blueprint(endpoints):
    """Create Invenio-Deposit-REST blueprint."""
    blueprint = Blueprint(
        'invenio_deposit_rest',
        __name__,
        url_prefix='',
    )
    blueprint.errorhandler(PIDInvalidAction)(create_api_errorhandler(
        status=403, message='Invalid action'))
    blueprint.errorhandler(InvalidOperationError)(create_api_errorhandler(
        status=403, message='Invalid operation'))
    blueprint.errorhandler(ValidationError)(create_api_errorhandler(
        status=400, message='Validation error'))

    for endpoint, options in (endpoints or {}).items():
        options = deepcopy(options)

        if 'files_serializers' in options:
            files_serializers = options.get('files_serializers')
            files_serializers = {
                mime: obj_or_import_string(func)
                for mime, func in files_serializers.items()
            }
            del options['files_serializers']
        else:
            files_serializers = {}

        if 'record_serializers' in options:
            serializers = options.get('record_serializers')
            serializers = {
                mime: obj_or_import_string(func)
                for mime, func in serializers.items()
            }
        else:
            serializers = {}

        file_list_route = options.pop(
            'file_list_route', '{0}/files'.format(options['item_route']))
        file_item_route = options.pop(
            'file_item_route',
            '{0}/files/<path:key>'.format(options['item_route']))

        options.setdefault('search_class', DepositSearch)
        search_class = obj_or_import_string(options['search_class'])

        # records rest endpoints will use the deposit class as record class
        options.setdefault('record_class', Deposit)
        record_class = obj_or_import_string(options['record_class'])

        for rule in records_rest_url_rules(endpoint, **options):
            blueprint.add_url_rule(**rule)

        search_class_kwargs = {}
        if options.get('search_index'):
            search_class_kwargs['index'] = options['search_index']

        if options.get('search_type'):
            search_class_kwargs['doc_type'] = options['search_type']

        ctx = dict(
            read_permission_factory=obj_or_import_string(
                options.get('read_permission_factory_imp')),
            create_permission_factory=obj_or_import_string(
                options.get('create_permission_factory_imp')),
            update_permission_factory=obj_or_import_string(
                options.get('update_permission_factory_imp')),
            delete_permission_factory=obj_or_import_string(
                options.get('delete_permission_factory_imp')),
            record_class=record_class,
            search_class=partial(search_class, **search_class_kwargs),
            default_media_type=options.get('default_media_type'),
        )

        deposit_actions = DepositActionResource.as_view(
            DepositActionResource.view_name.format(endpoint),
            serializers=serializers,
            pid_type=options['pid_type'],
            ctx=ctx,
        )

        blueprint.add_url_rule(
            '{0}/actions/<any(publish,edit,discard):action>'.format(
                options['item_route']),
            view_func=deposit_actions,
            methods=['POST'],
        )

        deposit_files = DepositFilesResource.as_view(
            DepositFilesResource.view_name.format(endpoint),
            serializers=files_serializers,
            pid_type=options['pid_type'],
            ctx=ctx,
        )

        blueprint.add_url_rule(
            file_list_route,
            view_func=deposit_files,
            methods=['GET', 'POST', 'PUT'],
        )

        deposit_file = DepositFileResource.as_view(
            DepositFileResource.view_name.format(endpoint),
            serializers=files_serializers,
            pid_type=options['pid_type'],
            ctx=ctx,
        )

        blueprint.add_url_rule(
            file_item_route,
            view_func=deposit_file,
            methods=['GET', 'PUT', 'DELETE'],
        )
    return blueprint
Exemplo n.º 26
0
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from flask import Blueprint
from . import views
from app.main import errors

auth = Blueprint('auth', __name__)

auth.add_url_rule('/login/', 'login', views.login, methods=['GET', 'POST'])
auth.add_url_rule('/logout/', 'logout', views.logout)
auth.add_url_rule('/registration/', 'register', views.register, methods=['GET', 'POST'])
auth.add_url_rule('/registration/su', 'register_su',
                      views.register, defaults={'create_su': True}, methods=['GET', 'POST'])
auth.add_url_rule('/add-user/', 'add_user', views.add_user, methods=['GET', 'POST'])
auth.add_url_rule('/users/', view_func=views.Users.as_view('users'))
auth.add_url_rule('/users/edit/<username>', view_func=views.User.as_view('edit_user'))
auth.add_url_rule('/su-users/', view_func=views.SuUsers.as_view('su_users'))
auth.add_url_rule('/su-users/edit/<username>', view_func=views.SuUser.as_view('su_edit_user'))
auth.add_url_rule('/user/settings/', view_func=views.Profile.as_view('settings'))
auth.add_url_rule('/user/password/', view_func=views.Password.as_view('password'))
auth.errorhandler(403)(errors.handle_forbidden)




Exemplo n.º 27
0
accounts = Blueprint('accounts', __name__)

accounts.add_url_rule('/login/', 'login', views.login, methods=['GET', 'POST'])
accounts.add_url_rule('/logout/', 'logout', views.logout)
accounts.add_url_rule('/registration/',
                      'register',
                      views.register,
                      methods=['GET', 'POST'])
accounts.add_url_rule('/registration/su',
                      'register_su',
                      views.register,
                      defaults={'create_su': True},
                      methods=['GET', 'POST'])
accounts.add_url_rule('/add-user/',
                      'add_user',
                      views.add_user,
                      methods=['GET', 'POST'])
accounts.add_url_rule('/users/', view_func=views.Users.as_view('users'))
accounts.add_url_rule('/users/edit/<username>',
                      view_func=views.User.as_view('edit_user'))
accounts.add_url_rule('/su-users/',
                      view_func=views.SuUsers.as_view('su_users'))
accounts.add_url_rule('/su-users/edit/<username>',
                      view_func=views.SuUser.as_view('su_edit_user'))
accounts.add_url_rule('/user/settings/',
                      view_func=views.Profile.as_view('settings'))
accounts.add_url_rule('/user/password/',
                      view_func=views.Password.as_view('password'))

accounts.errorhandler(403)(errors.handle_forbidden)
Exemplo n.º 28
0
def search():
    """Search for something (Optional)"""

    results = ''
    return render_template("results.html", results=results)


@main.route("/profile")
@login_required
def profile():
    """Display user profile"""

    # Username and email are already stored in the session, registeration time isn't, you can query for registeration time only instead
    # Values in profile.html are from this query but you can use the values in the session instead
    info = db.execute('SELECT * FROM users WHERE id = :id', {
        'id': session['user_id']
    }).fetchone()
    return render_template("profile.html", info=info)


def errorhandler(e):
    """Handle error"""
    if not isinstance(e, HTTPException):
        e = InternalServerError()
    return apology(e.name, e.code)


# Listen for errors
for code in default_exceptions:
    main.errorhandler(code)(errorhandler)
Exemplo n.º 29
0
                  views.post_detail_general)
main.add_url_rule('/pages/<slug>/',
                  'page_detail',
                  views.post_detail,
                  defaults={'post_type': 'page'})
main.add_url_rule('/wechats/<slug>/',
                  'wechat_detail',
                  views.post_detail,
                  defaults={'post_type': 'wechat'})
main.add_url_rule('/archive/', 'archive', views.archive)
main.add_url_rule('/users/<username>/', 'author_detail', views.author_detail)
main.add_url_rule('/atom/', 'recent_feed', views.recent_feed)
main.add_url_rule('/sitemap.xml/', 'sitemap', views.sitemap)
main.add_url_rule('/uploads/<filename>/', 'uploaded_file',
                  upload.uploaded_file)
main.errorhandler(404)(errors.page_not_found)
main.errorhandler(401)(errors.handle_unauthorized)
main.add_url_rule('/<path:invalid_path>', 'handle_unmatchable',
                  errors.handle_unmatchable)

blog_admin = Blueprint('blog_admin', __name__)

blog_admin.add_url_rule('/', view_func=admin_views.AdminIndex.as_view('index'))

blog_admin.add_url_rule('/posts/',
                        view_func=admin_views.PostsList.as_view('posts'))
blog_admin.add_url_rule('/posts/draft/',
                        view_func=admin_views.DraftList.as_view('drafts'))
blog_admin.add_url_rule('/new-post/',
                        view_func=admin_views.Post.as_view('new_post'))
blog_admin.add_url_rule('/posts/<slug>/',
Exemplo n.º 30
0
def create_blueprint(endpoints):
    """Create Invenio-Deposit-REST blueprint."""
    blueprint = Blueprint(
        'invenio_deposit_rest',
        __name__,
        url_prefix='',
    )
    blueprint.errorhandler(PIDInvalidAction)(create_api_errorhandler(
        status=403, message='Invalid action'
    ))
    blueprint.errorhandler(InvalidOperationError)(create_api_errorhandler(
        status=403, message='Invalid operation'
    ))
    blueprint.errorhandler(ValidationError)(create_api_errorhandler(
        status=400, message='Validation error'
    ))

    for endpoint, options in (endpoints or {}).items():
        options = deepcopy(options)

        if 'files_serializers' in options:
            files_serializers = options.get('files_serializers')
            files_serializers = {mime: obj_or_import_string(func)
                                 for mime, func in files_serializers.items()}
            del options['files_serializers']
        else:
            files_serializers = {}

        if 'record_serializers' in options:
            serializers = options.get('record_serializers')
            serializers = {mime: obj_or_import_string(func)
                           for mime, func in serializers.items()}
        else:
            serializers = {}

        file_list_route = options.pop(
            'file_list_route',
            '{0}/files'.format(options['item_route'])
        )
        file_item_route = options.pop(
            'file_item_route',
            '{0}/files/<path:key>'.format(options['item_route'])
        )

        options.setdefault('search_class', DepositSearch)
        search_class = obj_or_import_string(options['search_class'])

        # records rest endpoints will use the deposit class as record class
        options.setdefault('record_class', Deposit)
        record_class = obj_or_import_string(options['record_class'])

        for rule in records_rest_url_rules(endpoint, **options):
            blueprint.add_url_rule(**rule)

        search_class_kwargs = {}
        if options.get('search_index'):
            search_class_kwargs['index'] = options['search_index']

        if options.get('search_type'):
            search_class_kwargs['doc_type'] = options['search_type']

        ctx = dict(
            read_permission_factory=obj_or_import_string(
                options.get('read_permission_factory_imp')
            ),
            create_permission_factory=obj_or_import_string(
                options.get('create_permission_factory_imp')
            ),
            update_permission_factory=obj_or_import_string(
                options.get('update_permission_factory_imp')
            ),
            delete_permission_factory=obj_or_import_string(
                options.get('delete_permission_factory_imp')
            ),
            record_class=record_class,
            search_class=partial(search_class, **search_class_kwargs),
            default_media_type=options.get('default_media_type'),
        )

        deposit_actions = DepositActionResource.as_view(
            DepositActionResource.view_name.format(endpoint),
            serializers=serializers,
            pid_type=options['pid_type'],
            ctx=ctx,
        )

        blueprint.add_url_rule(
            '{0}/actions/<any(publish,edit,discard):action>'.format(
                options['item_route']
            ),
            view_func=deposit_actions,
            methods=['POST'],
        )

        deposit_files = DepositFilesResource.as_view(
            DepositFilesResource.view_name.format(endpoint),
            serializers=files_serializers,
            pid_type=options['pid_type'],
            ctx=ctx,
        )

        blueprint.add_url_rule(
            file_list_route,
            view_func=deposit_files,
            methods=['GET', 'POST', 'PUT'],
        )

        deposit_file = DepositFileResource.as_view(
            DepositFileResource.view_name.format(endpoint),
            serializers=files_serializers,
            pid_type=options['pid_type'],
            ctx=ctx,
        )

        blueprint.add_url_rule(
            file_item_route,
            view_func=deposit_file,
            methods=['GET', 'PUT', 'DELETE'],
        )
    return blueprint
Exemplo n.º 31
0
from flask import Blueprint

from . import views, admin_views, errors

main = Blueprint('main', __name__)

main.add_url_rule('/', 'index', views.list_posts)
main.add_url_rule('/posts/', 'posts', views.list_posts)
main.add_url_rule('/posts/<slug>/', 'post_detail', views.post_detail)
main.add_url_rule('/post/<slug>/', 'post_detail_fix', views.post_detail, defaults={'fix':True})
main.add_url_rule('/pages/<slug>/', 'page_detail', views.post_detail, defaults={'post_type':'page'})
main.add_url_rule('/archive/', 'archive', views.archive)
main.add_url_rule('/users/<username>/', 'author_detail', views.author_detail)
main.add_url_rule('/atom/', 'recent_feed', views.recent_feed)
main.add_url_rule('/sitemap.xml/', 'sitemap', views.sitemap)
main.errorhandler(404)(errors.page_not_found)
main.add_url_rule('/<path:invalid_path>', 'handle_unmatchable', errors.handle_unmatchable)


blog_admin = Blueprint('blog_admin', __name__)

blog_admin.add_url_rule('/', view_func=admin_views.AdminIndex.as_view('index'))
blog_admin.add_url_rule('/posts/', view_func=admin_views.PostsList.as_view('posts'))
blog_admin.add_url_rule('/new-post/', view_func=admin_views.Post.as_view('new_post'))
blog_admin.add_url_rule('/pages/', view_func=admin_views.PostsList.as_view('pages'), defaults={'post_type':'page'})
blog_admin.add_url_rule('/new-page/', view_func=admin_views.Post.as_view('new_page'), defaults={'post_type':'page'})
blog_admin.add_url_rule('/posts/<slug>/', view_func=admin_views.Post.as_view('edit_post'))

blog_admin.add_url_rule('/su/posts/', view_func=admin_views.SuPostsList.as_view('su_posts'))
blog_admin.add_url_rule('/su/posts/<slug>/', view_func=admin_views.SuPost.as_view('su_post_edit'))
blog_admin.errorhandler(404)(errors.admin_page_not_found)
Exemplo n.º 32
0
def make_blueprint(config):
    api = Blueprint('api', __name__)

    @api.route('/')
    def get_endpoints():
        endpoints = {'sources_url': '/api/v1/sources',
                     'current_user_url': '/api/v1/user',
                     'submissions_url': '/api/v1/submissions',
                     'replies_url': '/api/v1/replies',
                     'auth_token_url': '/api/v1/token'}
        return jsonify(endpoints), 200

    # Before every post, we validate the payload before processing the request
    @api.before_request
    def validate_data():
        if request.method == 'POST':
            # flag, star, and logout can have empty payloads
            if not request.data:
                dataless_endpoints = [
                    'add_star',
                    'remove_star',
                    'flag',
                    'logout',
                ]
                for endpoint in dataless_endpoints:
                    if request.endpoint == 'api.' + endpoint:
                        return
                return abort(400, 'malformed request')
            # other requests must have valid JSON payload
            else:
                try:
                    json.loads(request.data.decode('utf-8'))
                except (ValueError):
                    return abort(400, 'malformed request')

    @api.route('/token', methods=['POST'])
    def get_token():
        creds = json.loads(request.data.decode('utf-8'))

        username = creds.get('username', None)
        passphrase = creds.get('passphrase', None)
        one_time_code = creds.get('one_time_code', None)

        if username is None:
            return abort(400, 'username field is missing')
        if passphrase is None:
            return abort(400, 'passphrase field is missing')
        if one_time_code is None:
            return abort(400, 'one_time_code field is missing')

        try:
            journalist = Journalist.login(username, passphrase, one_time_code)
            token_expiry = datetime.utcnow() + timedelta(
                seconds=TOKEN_EXPIRATION_MINS * 60)

            response = jsonify({
                'token': journalist.generate_api_token(expiration=TOKEN_EXPIRATION_MINS * 60),
                'expiration': token_expiry.isoformat() + 'Z',
                'journalist_uuid': journalist.uuid,
                'journalist_first_name': journalist.first_name,
                'journalist_last_name': journalist.last_name,
            })

            # Update access metadata
            journalist.last_access = datetime.utcnow()
            db.session.add(journalist)
            db.session.commit()

            return response, 200
        except (LoginThrottledException, InvalidUsernameException,
                BadTokenException, WrongPasswordException):
            return abort(403, 'Token authentication failed.')

    @api.route('/sources', methods=['GET'])
    @token_required
    def get_all_sources():
        sources = Source.query.filter_by(pending=False).all()
        return jsonify(
            {'sources': [source.to_json() for source in sources]}), 200

    @api.route('/sources/<source_uuid>', methods=['GET', 'DELETE'])
    @token_required
    def single_source(source_uuid):
        if request.method == 'GET':
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            return jsonify(source.to_json()), 200
        elif request.method == 'DELETE':
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            utils.delete_collection(source.filesystem_id)
            return jsonify({'message': 'Source and submissions deleted'}), 200

    @api.route('/sources/<source_uuid>/add_star', methods=['POST'])
    @token_required
    def add_star(source_uuid):
        source = get_or_404(Source, source_uuid, column=Source.uuid)
        utils.make_star_true(source.filesystem_id)
        db.session.commit()
        return jsonify({'message': 'Star added'}), 201

    @api.route('/sources/<source_uuid>/remove_star', methods=['DELETE'])
    @token_required
    def remove_star(source_uuid):
        source = get_or_404(Source, source_uuid, column=Source.uuid)
        utils.make_star_false(source.filesystem_id)
        db.session.commit()
        return jsonify({'message': 'Star removed'}), 200

    @api.route('/sources/<source_uuid>/flag', methods=['POST'])
    @token_required
    def flag(source_uuid):
        source = get_or_404(Source, source_uuid,
                            column=Source.uuid)
        source.flagged = True
        db.session.commit()
        return jsonify({'message': 'Source flagged for reply'}), 200

    @api.route('/sources/<source_uuid>/submissions', methods=['GET'])
    @token_required
    def all_source_submissions(source_uuid):
        source = get_or_404(Source, source_uuid, column=Source.uuid)
        return jsonify(
            {'submissions': [submission.to_json() for
                             submission in source.submissions]}), 200

    @api.route('/sources/<source_uuid>/submissions/<submission_uuid>/download',  # noqa
               methods=['GET'])
    @token_required
    def download_submission(source_uuid, submission_uuid):
        get_or_404(Source, source_uuid, column=Source.uuid)
        submission = get_or_404(Submission, submission_uuid,
                                column=Submission.uuid)

        # Mark as downloaded
        submission.downloaded = True
        db.session.commit()

        return utils.serve_file_with_etag(submission)

    @api.route('/sources/<source_uuid>/replies/<reply_uuid>/download',
               methods=['GET'])
    @token_required
    def download_reply(source_uuid, reply_uuid):
        get_or_404(Source, source_uuid, column=Source.uuid)
        reply = get_or_404(Reply, reply_uuid, column=Reply.uuid)

        return utils.serve_file_with_etag(reply)

    @api.route('/sources/<source_uuid>/submissions/<submission_uuid>',
               methods=['GET', 'DELETE'])
    @token_required
    def single_submission(source_uuid, submission_uuid):
        if request.method == 'GET':
            get_or_404(Source, source_uuid, column=Source.uuid)
            submission = get_or_404(Submission, submission_uuid, column=Submission.uuid)
            return jsonify(submission.to_json()), 200
        elif request.method == 'DELETE':
            get_or_404(Source, source_uuid, column=Source.uuid)
            submission = get_or_404(Submission, submission_uuid, column=Submission.uuid)
            utils.delete_file_object(submission)
            return jsonify({'message': 'Submission deleted'}), 200

    @api.route('/sources/<source_uuid>/replies', methods=['GET', 'POST'])
    @token_required
    def all_source_replies(source_uuid):
        if request.method == 'GET':
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            return jsonify(
                {'replies': [reply.to_json() for
                             reply in source.replies]}), 200
        elif request.method == 'POST':
            source = get_or_404(Source, source_uuid,
                                column=Source.uuid)
            if request.json is None:
                abort(400, 'please send requests in valid JSON')

            if 'reply' not in request.json:
                abort(400, 'reply not found in request body')

            user = get_user_object(request)

            data = request.json
            if not data['reply']:
                abort(400, 'reply should not be empty')

            source.interaction_count += 1
            try:
                filename = current_app.storage.save_pre_encrypted_reply(
                    source.filesystem_id,
                    source.interaction_count,
                    source.journalist_filename,
                    data['reply'])
            except NotEncrypted:
                return jsonify(
                    {'message': 'You must encrypt replies client side'}), 400

            # issue #3918
            filename = path.basename(filename)

            reply = Reply(user, source, filename)

            reply_uuid = data.get('uuid', None)
            if reply_uuid is not None:
                # check that is is parseable
                try:
                    UUID(reply_uuid)
                except ValueError:
                    abort(400, "'uuid' was not a valid UUID")
                reply.uuid = reply_uuid

            try:
                db.session.add(reply)
                db.session.add(source)
                db.session.commit()
            except IntegrityError as e:
                db.session.rollback()
                if 'UNIQUE constraint failed: replies.uuid' in str(e):
                    abort(409, 'That UUID is already in use.')
                else:
                    raise e

            return jsonify({'message': 'Your reply has been stored',
                            'uuid': reply.uuid,
                            'filename': reply.filename}), 201

    @api.route('/sources/<source_uuid>/replies/<reply_uuid>',
               methods=['GET', 'DELETE'])
    @token_required
    def single_reply(source_uuid, reply_uuid):
        get_or_404(Source, source_uuid, column=Source.uuid)
        reply = get_or_404(Reply, reply_uuid, column=Reply.uuid)
        if request.method == 'GET':
            return jsonify(reply.to_json()), 200
        elif request.method == 'DELETE':
            utils.delete_file_object(reply)
            return jsonify({'message': 'Reply deleted'}), 200

    @api.route('/submissions', methods=['GET'])
    @token_required
    def get_all_submissions():
        submissions = Submission.query.all()
        return jsonify({'submissions': [submission.to_json() for
                                        submission in submissions]}), 200

    @api.route('/replies', methods=['GET'])
    @token_required
    def get_all_replies():
        replies = Reply.query.all()
        return jsonify(
            {'replies': [reply.to_json() for reply in replies]}), 200

    @api.route('/user', methods=['GET'])
    @token_required
    def get_current_user():
        user = get_user_object(request)
        return jsonify(user.to_json()), 200

    @api.route('/logout', methods=['POST'])
    @token_required
    def logout():
        user = get_user_object(request)
        auth_token = request.headers.get('Authorization').split(" ")[1]
        utils.revoke_token(user, auth_token)
        return jsonify({'message': 'Your token has been revoked.'}), 200

    def _handle_api_http_exception(error):
        # Workaround for no blueprint-level 404/5 error handlers, see:
        # https://github.com/pallets/flask/issues/503#issuecomment-71383286
        response = jsonify({'error': error.name,
                           'message': error.description})

        return response, error.code

    for code in default_exceptions:
        api.errorhandler(code)(_handle_api_http_exception)

    return api
Exemplo n.º 33
0
def make_blueprint(config: SDConfig) -> Blueprint:
    api = Blueprint('api', __name__)

    @api.route('/')
    def get_endpoints() -> Tuple[flask.Response, int]:
        endpoints = {
            'sources_url': '/api/v1/sources',
            'current_user_url': '/api/v1/user',
            'all_users_url': '/api/v1/users',
            'submissions_url': '/api/v1/submissions',
            'replies_url': '/api/v1/replies',
            'seen_url': '/api/v1/seen',
            'auth_token_url': '/api/v1/token'
        }
        return jsonify(endpoints), 200

    # Before every post, we validate the payload before processing the request
    @api.before_request
    def validate_data() -> None:
        if request.method == 'POST':
            # flag, star, and logout can have empty payloads
            if not request.data:
                dataless_endpoints = [
                    'add_star',
                    'remove_star',
                    'flag',
                    'logout',
                ]
                for endpoint in dataless_endpoints:
                    if request.endpoint == 'api.' + endpoint:
                        return
                abort(400, 'malformed request')
            # other requests must have valid JSON payload
            else:
                try:
                    json.loads(request.data.decode('utf-8'))
                except (ValueError):
                    abort(400, 'malformed request')

    @api.route('/token', methods=['POST'])
    def get_token() -> Tuple[flask.Response, int]:
        creds = json.loads(request.data.decode('utf-8'))

        username = creds.get('username', None)
        passphrase = creds.get('passphrase', None)
        one_time_code = creds.get('one_time_code', None)

        if username is None:
            abort(400, 'username field is missing')
        if passphrase is None:
            abort(400, 'passphrase field is missing')
        if one_time_code is None:
            abort(400, 'one_time_code field is missing')

        try:
            journalist = Journalist.login(username, passphrase, one_time_code)
            token_expiry = datetime.utcnow() + timedelta(
                seconds=TOKEN_EXPIRATION_MINS * 60)

            response = jsonify({
                'token':
                journalist.generate_api_token(
                    expiration=TOKEN_EXPIRATION_MINS * 60),
                'expiration':
                token_expiry.isoformat() + 'Z',
                'journalist_uuid':
                journalist.uuid,
                'journalist_first_name':
                journalist.first_name,
                'journalist_last_name':
                journalist.last_name,
            })

            # Update access metadata
            journalist.last_access = datetime.utcnow()
            db.session.add(journalist)
            db.session.commit()

            return response, 200
        except (LoginThrottledException, InvalidUsernameException,
                BadTokenException, WrongPasswordException):
            return abort(403, 'Token authentication failed.')

    @api.route('/sources', methods=['GET'])
    @token_required
    def get_all_sources() -> Tuple[flask.Response, int]:
        sources = Source.query.filter_by(pending=False, deleted_at=None).all()
        return jsonify({'sources':
                        [source.to_json() for source in sources]}), 200

    @api.route('/sources/<source_uuid>', methods=['GET', 'DELETE'])
    @token_required
    def single_source(source_uuid: str) -> Tuple[flask.Response, int]:
        if request.method == 'GET':
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            return jsonify(source.to_json()), 200
        elif request.method == 'DELETE':
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            utils.delete_collection(source.filesystem_id)
            return jsonify({'message': 'Source and submissions deleted'}), 200
        else:
            abort(405)

    @api.route('/sources/<source_uuid>/add_star', methods=['POST'])
    @token_required
    def add_star(source_uuid: str) -> Tuple[flask.Response, int]:
        source = get_or_404(Source, source_uuid, column=Source.uuid)
        utils.make_star_true(source.filesystem_id)
        db.session.commit()
        return jsonify({'message': 'Star added'}), 201

    @api.route('/sources/<source_uuid>/remove_star', methods=['DELETE'])
    @token_required
    def remove_star(source_uuid: str) -> Tuple[flask.Response, int]:
        source = get_or_404(Source, source_uuid, column=Source.uuid)
        utils.make_star_false(source.filesystem_id)
        db.session.commit()
        return jsonify({'message': 'Star removed'}), 200

    @api.route('/sources/<source_uuid>/flag', methods=['POST'])
    @token_required
    def flag(source_uuid: str) -> Tuple[flask.Response, int]:
        source = get_or_404(Source, source_uuid, column=Source.uuid)
        source.flagged = True
        db.session.commit()
        return jsonify({'message': 'Source flagged for reply'}), 200

    @api.route('/sources/<source_uuid>/submissions', methods=['GET'])
    @token_required
    def all_source_submissions(source_uuid: str) -> Tuple[flask.Response, int]:
        source = get_or_404(Source, source_uuid, column=Source.uuid)
        return jsonify({
            'submissions':
            [submission.to_json() for submission in source.submissions]
        }), 200

    @api.route("/sources/<source_uuid>/submissions/<submission_uuid>/download",
               methods=["GET"])
    @token_required
    def download_submission(source_uuid: str,
                            submission_uuid: str) -> flask.Response:
        get_or_404(Source, source_uuid, column=Source.uuid)
        submission = get_or_404(Submission,
                                submission_uuid,
                                column=Submission.uuid)
        return utils.serve_file_with_etag(submission)

    @api.route('/sources/<source_uuid>/replies/<reply_uuid>/download',
               methods=['GET'])
    @token_required
    def download_reply(source_uuid: str, reply_uuid: str) -> flask.Response:
        get_or_404(Source, source_uuid, column=Source.uuid)
        reply = get_or_404(Reply, reply_uuid, column=Reply.uuid)

        return utils.serve_file_with_etag(reply)

    @api.route('/sources/<source_uuid>/submissions/<submission_uuid>',
               methods=['GET', 'DELETE'])
    @token_required
    def single_submission(source_uuid: str,
                          submission_uuid: str) -> Tuple[flask.Response, int]:
        if request.method == 'GET':
            get_or_404(Source, source_uuid, column=Source.uuid)
            submission = get_or_404(Submission,
                                    submission_uuid,
                                    column=Submission.uuid)
            return jsonify(submission.to_json()), 200
        elif request.method == 'DELETE':
            get_or_404(Source, source_uuid, column=Source.uuid)
            submission = get_or_404(Submission,
                                    submission_uuid,
                                    column=Submission.uuid)
            utils.delete_file_object(submission)
            return jsonify({'message': 'Submission deleted'}), 200
        else:
            abort(405)

    @api.route('/sources/<source_uuid>/replies', methods=['GET', 'POST'])
    @token_required
    def all_source_replies(source_uuid: str) -> Tuple[flask.Response, int]:
        if request.method == 'GET':
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            return jsonify(
                {'replies':
                 [reply.to_json() for reply in source.replies]}), 200
        elif request.method == 'POST':
            source = get_or_404(Source, source_uuid, column=Source.uuid)
            if request.json is None:
                abort(400, 'please send requests in valid JSON')

            if 'reply' not in request.json:
                abort(400, 'reply not found in request body')

            user = _authenticate_user_from_auth_header(request)

            data = request.json
            if not data['reply']:
                abort(400, 'reply should not be empty')

            source.interaction_count += 1
            try:
                filename = current_app.storage.save_pre_encrypted_reply(
                    source.filesystem_id, source.interaction_count,
                    source.journalist_filename, data['reply'])
            except NotEncrypted:
                return jsonify(
                    {'message': 'You must encrypt replies client side'}), 400

            # issue #3918
            filename = path.basename(filename)

            reply = Reply(user, source, filename)

            reply_uuid = data.get('uuid', None)
            if reply_uuid is not None:
                # check that is is parseable
                try:
                    UUID(reply_uuid)
                except ValueError:
                    abort(400, "'uuid' was not a valid UUID")
                reply.uuid = reply_uuid

            try:
                db.session.add(reply)
                db.session.flush()
                seen_reply = SeenReply(reply_id=reply.id,
                                       journalist_id=user.id)
                db.session.add(seen_reply)
                db.session.add(source)
                db.session.commit()
            except IntegrityError as e:
                db.session.rollback()
                if 'UNIQUE constraint failed: replies.uuid' in str(e):
                    abort(409, 'That UUID is already in use.')
                else:
                    raise e

            return jsonify({
                'message': 'Your reply has been stored',
                'uuid': reply.uuid,
                'filename': reply.filename
            }), 201
        else:
            abort(405)

    @api.route('/sources/<source_uuid>/replies/<reply_uuid>',
               methods=['GET', 'DELETE'])
    @token_required
    def single_reply(source_uuid: str,
                     reply_uuid: str) -> Tuple[flask.Response, int]:
        get_or_404(Source, source_uuid, column=Source.uuid)
        reply = get_or_404(Reply, reply_uuid, column=Reply.uuid)
        if request.method == 'GET':
            return jsonify(reply.to_json()), 200
        elif request.method == 'DELETE':
            utils.delete_file_object(reply)
            return jsonify({'message': 'Reply deleted'}), 200
        else:
            abort(405)

    @api.route('/submissions', methods=['GET'])
    @token_required
    def get_all_submissions() -> Tuple[flask.Response, int]:
        submissions = Submission.query.all()
        return jsonify({
            'submissions': [
                submission.to_json() for submission in submissions
                if submission.source
            ]
        }), 200

    @api.route('/replies', methods=['GET'])
    @token_required
    def get_all_replies() -> Tuple[flask.Response, int]:
        replies = Reply.query.all()
        return jsonify({
            'replies': [reply.to_json() for reply in replies if reply.source]
        }), 200

    @api.route("/seen", methods=["POST"])
    @token_required
    def seen() -> Tuple[flask.Response, int]:
        """
        Lists or marks the source conversation items that the journalist has seen.
        """
        user = _authenticate_user_from_auth_header(request)

        if request.method == "POST":
            if request.json is None or not isinstance(request.json,
                                                      collections.abc.Mapping):
                abort(400, "Please send requests in valid JSON.")

            if not any(map(request.json.get,
                           ["files", "messages", "replies"])):
                abort(400, "Please specify the resources to mark seen.")

            # gather everything to be marked seen. if any don't exist,
            # reject the request.
            targets = set()  # type: Set[Union[Submission, Reply]]
            for file_uuid in request.json.get("files", []):
                f = Submission.query.filter(
                    Submission.uuid == file_uuid).one_or_none()
                if f is None or not f.is_file:
                    abort(404, "file not found: {}".format(file_uuid))
                targets.add(f)

            for message_uuid in request.json.get("messages", []):
                m = Submission.query.filter(
                    Submission.uuid == message_uuid).one_or_none()
                if m is None or not m.is_message:
                    abort(404, "message not found: {}".format(message_uuid))
                targets.add(m)

            for reply_uuid in request.json.get("replies", []):
                r = Reply.query.filter(Reply.uuid == reply_uuid).one_or_none()
                if r is None:
                    abort(404, "reply not found: {}".format(reply_uuid))
                targets.add(r)

            # now mark everything seen.
            utils.mark_seen(list(targets), user)

            return jsonify({"message": "resources marked seen"}), 200

        abort(405)

    @api.route('/user', methods=['GET'])
    @token_required
    def get_current_user() -> Tuple[flask.Response, int]:
        user = _authenticate_user_from_auth_header(request)
        return jsonify(user.to_json()), 200

    @api.route('/users', methods=['GET'])
    @token_required
    def get_all_users() -> Tuple[flask.Response, int]:
        users = Journalist.query.all()
        return jsonify(
            {'users': [user.to_json(all_info=False) for user in users]}), 200

    @api.route('/logout', methods=['POST'])
    @token_required
    def logout() -> Tuple[flask.Response, int]:
        user = _authenticate_user_from_auth_header(request)
        auth_token = request.headers['Authorization'].split(" ")[1]
        utils.revoke_token(user, auth_token)
        return jsonify({'message': 'Your token has been revoked.'}), 200

    def _handle_api_http_exception(
        error: werkzeug.exceptions.HTTPException
    ) -> Tuple[flask.Response, int]:
        # Workaround for no blueprint-level 404/5 error handlers, see:
        # https://github.com/pallets/flask/issues/503#issuecomment-71383286
        response = jsonify({'error': error.name, 'message': error.description})

        return response, error.code  # type: ignore

    for code in default_exceptions:
        api.errorhandler(code)(_handle_api_http_exception)

    return api
Exemplo n.º 34
0
@bp.route('/abort/<code>', methods=['GET'])
@bp.route('/abort/<code>/<message>', methods=['GET'])
def route_abort(code, message=None):
    """ Custom abort route """
    if message:
        abort(int(code), message)
    abort(int(code))


@bp.route('/except/<err>', methods=['GET'])
@bp.route('/except/<err>/<message>', methods=['GET'])
def route_except(err, message=None):
    """ Custom exception route """
    exception = eval(err)
    if message:
        raise exception(str(message))
    raise exception()


@bp.route("/<path:invalid_path>",
          methods=['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'])
def route404(*args, **kwargs):
    """ Catch all route within blueprint to force use of 404 errorhandler. """
    abort(404)


# Register errorhandler for specific HTTP codes
for code in (400, 401, 403, 404, 405, 409, 410, 412, 413, 418, 429, 500, 501,
             502, 503, 504, 505):
    bp.errorhandler(code)(handle_abort)
Exemplo n.º 35
0
from flask import Blueprint
import activity


routes = Blueprint("routes", __name__)
activity.routes = routes

@routes.record
def record(state):
    routes.db = state.app.config.get("database")

    if routes.db is None:
        raise Exception("This blueprint expects you to provide database access through database")


routes.route("/", methods=["GET"])(activity.home)
routes.errorhandler(Exception)(activity.error)
Exemplo n.º 36
0
    verify_username = db.execute(
        "SELECT username FROM users WHERE username = :username", {
            "username": username
        }).fetchone()
    if email:
        verify_email = db.execute(
            "SELECT email FROM users WHERE email = :email", {
                "email": email
            }).fetchone()
        if verify_email and verify_username:
            return jsonify("Username and email already taken.")
        if verify_username:
            return jsonify("Username already taken.")
        if verify_email:
            return jsonify("Email already taken.")
    if verify_username:
        return jsonify("Username already taken.")
    return jsonify(True)


def errorhandler(e):
    """Handle error"""
    if not isinstance(e, HTTPException):
        e = InternalServerError()
    return apology(e.name, e.code)


# Listen for errors
for code in default_exceptions:
    auth.errorhandler(code)(errorhandler)
Exemplo n.º 37
0
    TARGET_OFFICE_DISTRICT, TARGET_OFFICE_BUSY)
from ..campaign.models import Campaign, Target
from ..political_data.lookup import locate_targets, validate_location
from ..political_data.geocode import LocationError
from ..schedule.models import ScheduleCall
from ..schedule.views import schedule_created, schedule_deleted
from ..admin.models import Blocklist
from ..admin.views import admin_phone

from .decorators import abortJSON, stripANSI

call = Blueprint('call', __name__, url_prefix='/call')
cors(call)
call_methods = ['GET', 'POST']
csrf.exempt(call)
call.errorhandler(400)(abortJSON)
call.errorhandler(429)(abortJSON)


def play_or_say(r, audio, voice='alice', lang='en-US', **kwargs):
    """
    Take twilio response and play or say message from an AudioRecording
    Can use mustache templates to render keyword arguments
    """

    if audio:
        # check to ensure lang is in list of valid locales
        if lang not in TWILIO_TTS_LANGUAGES:
            if '-' in lang:
                lang, country = lang.split('-')
            else:
Exemplo n.º 38
0
from flask import Blueprint

from . import views
from main import errors

accounts = Blueprint('accounts', __name__)

accounts.add_url_rule('/login/', 'login', views.login, methods=['GET', 'POST'])
accounts.add_url_rule('/logout/', 'logout', views.logout)
accounts.add_url_rule('/registration/', 'register', views.register, methods=['GET', 'POST'])
accounts.add_url_rule('/registration/su', 'register_su', views.register, defaults={'create_su':True}, methods=['GET', 'POST'])
accounts.add_url_rule('/add-user/', 'add_user', views.add_user, methods=['GET', 'POST'])
accounts.add_url_rule('/users/', view_func=views.Users.as_view('users'))
accounts.add_url_rule('/users/edit/<username>', view_func=views.User.as_view('edit_user'))
accounts.add_url_rule('/su-users/', view_func=views.SuUsers.as_view('su_users'))
accounts.add_url_rule('/su-users/edit/<username>', view_func=views.SuUser.as_view('su_edit_user'))
accounts.add_url_rule('/user/settings/', view_func=views.Profile.as_view('settings'))
accounts.add_url_rule('/user/password/', view_func=views.Password.as_view('password'))

accounts.errorhandler(403)(errors.handle_forbidden)