Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to protect "hidden" endpoints/routes #107

Closed
Querela opened this issue Aug 10, 2020 · 7 comments
Closed

How to protect "hidden" endpoints/routes #107

Querela opened this issue Aug 10, 2020 · 7 comments
Labels

Comments

@Querela
Copy link

Querela commented Aug 10, 2020

How can I protect arbitrary routes in my webapp with HTTPAuth?
In particular, using flask-apispec (swagger) I just initialize the extension and optionally provide setting on what the routes are, e. g.:

from flask_httpauth import HTTPBasicAuth
from flask_apispec.extension import FlaskApiSpec
from werkzeug.security import check_password_hash

auth = HTTPBasicAuth()
docs = FlaskApiSpec()

def register_extensions(app):
    # ...

    @auth.verify_password
    def verify_password(username, password):  # pylint: disable=unused-variable
        users = app.config["AUTH_USERS"]
        for user in users:
            if user["name"] != username:
                continue
            if check_password_hash(user["password"], password):
                return user
        return False

    @auth.get_user_roles
    def get_user_roles(user):  # pylint: disable=unused-variable
        return user["roles"]

    # ...

    app.config.update(
        {
            "APISPEC_SPEC": ...,
            "APISPEC_SWAGGER_URL": "/swagger/",
            "APISPEC_SWAGGER_UI_URL": "/swagger-ui/",
        }
    )
    docs.init_app(app)

    # ...

Flask-HTTPAuth only provides decorators for me, so how can I protect those routes that are not declared by me? (Besides only enabling the apispec extension in Development/Testing environments.)

@Querela
Copy link
Author

Querela commented Aug 10, 2020

I looked into Flask, this seems to work:
From the flask route decorator: https://github.com/pallets/flask/blob/c74f46979a8b8358437bd7f76e478d04248a9c72/flask/app.py#L1075

# app may also be a blueprint (possibly?)
# endpoint should be known (from flask routes ...)

def auth_wrap_endpoint(app, endpoint, **kwargs):
    view_func = app.view_functions.get(endpoint)
    if view_func is not None:
        view_func_authd = auth.login_required(**kwargs)(view_func)
        app.view_functions[endpoint] = view_func_authd
auth_wrap_endpoint(app, "flask-apispec.static", role="api-rw")
auth_wrap_endpoint(app, "flask-apispec.swagger-json", role="api-rw")
auth_wrap_endpoint(app, "flask-apispec.swagger-ui", role="api-rw")

And I'm not sure about the decorator priority/order. Calling the auth decorator immediately after the route is determined seems good as no further decorators are called but this may not always be best.

@miguelgrinberg
Copy link
Owner

@Querela What you did is a bit hacky, but should work. An alternative would be to add the decorator to a before_request handler.

@Querela
Copy link
Author

Querela commented Aug 11, 2020

@Querela What you did is a bit hacky, but should work.

I know ... but it works really well so far. Just those possible other decorators are an unknown factor.

An alternative would be to add the decorator to a before_request handler.

Well yes, but as far as I know, I can (a) only protect my whole Flask application or (b) a single blueprint? But the extension declares a blueprint itself and makes it not publicly available for other uses.

Source excerpt in flask-apispec


Update

I found the blueprints property of a Flask app. After looking in the above code, I can use the name of the blueprint to get the reference and then add the before_request handler. I may have to test how to use the decorators in a declarative way correctly...

I tried but failed with:

def auth_wrap_blueprint(app, blueprint_name, **kwargs):
    blueprint = app.blueprints[blueprint_name]

    from flask import current_app
    from flask import request

    @blueprint.before_request
    @auth.login_required(**kwargs)
    def before_request_rw():  # pylint: disable=unused-variable
        """Protect blueprint routes"""
        current_app.logger.debug(
            "%s.before_request: [%s] %s",
            blueprint_name,
            request.method,
            request.endpoint,
        )
auth_wrap_blueprint(app, "flask-apispec", role="api-rw")

This is rather curious. The blueprint can be retrieved but the httpauth is not called ... (And using the same scheme I successfully protected my own blueprint. The only difference is that my own had the before_request handler added before they were registered to the Flask app.)
I found that registered handlers are merged into the Flask app handler list afterwards (here).

@miguelgrinberg
Copy link
Owner

You seem to always resort to hacking the Flask internals to get what you need. :)

What I was thinking was a custom decorator that uses request.blueprint and/or request.endpoint to figure out what protection is needed.

@Querela
Copy link
Author

Querela commented Aug 12, 2020

You seem to always resort to hacking the Flask internals to get what you need. :)

Mhh ... Not healthy and stable.
But some things can be better understood if you can look how they work. :-)

What I was thinking was a custom decorator that uses request.blueprint and/or request.endpoint to figure out what protection is needed.

Well. I could hook into the app handlers and then check. This seems easy.
But how do I call the httpauth machinery? I would like to reuse the automatic authentiation handshakes etc. (401 ...)
Do I just call auth.auth_error_callback(status)? from here

@miguelgrinberg
Copy link
Owner

I really don't think I should be giving you advice on this type of solution based on hacking attributes of the Flask object, because I don't really believe in it.

But all I'm going to say is that Flask-HTTPAuth's login_required is a decorator, so you call this "machinery" by decorating your function, or by chaining this decorator with your own.

@Querela
Copy link
Author

Querela commented Aug 13, 2020

Mh. OK.
My issue is just that are part of the application is open for public use and the swagger endpoint should be protected as it allows everything, more or less. But the swagger endpoint is not declared myself so I can't easily decorate it. Decorating the app would block the other public endpoints...
I will search for better approaches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants