I'd like to create a decorator for Flask routes to flag certain routes as public, so I can do things like this:
#public
#app.route('/welcome')
def welcome():
return render_template('/welcome.html')
Elsewhere, here's what I was thinking the decorator and check would look like:
_public_urls = set()
def public(route_function):
# add route_function's url to _public_urls
# _public_urls.add(route_function ...?.url_rule)
def decorator(f):
return f
def requested_url_is_public():
from flask import request
return request.url_rule in _public_urls
Then when a request is made, I have a context function that checks requested_url_is_public.
I'm a bit stumped because I don't know how to get the url rule for a given function in the public decorator.
Perhaps this isn't the best design choice for Flask, but I'd expect there's another simple & elegant way to achieve this.
I've seen this patterns like this before, and would like to mimic it. For example, this is something of a counterpart to Django's login_required decorator.
I'd enjoy reading thoughts on this.
Flask already has a login_required decorator (see view decorators). If you are using public_urls to decide which urls to require authentication for, you are most likely better off using that.
I ended up doing something like this:
def public(endpoint):
"""A decorator for endpoints that flags them as publicly accessible
The endpoint is the Flask endpoint function. This is later tested by the
_is_public function, which is called before every request.
Note that #public must come AFTER route.add i.e.
#app.route('...')
#public
def handler(): ...
"""
#wraps(endpoint)
def public_endpoint(*args, **kwargs):
return endpoint(*args, **kwargs)
public_endpoint._is_public = True
return public_endpoint
and
def _is_public(endpoint):
"""Return true if the given endpoint function is public
Tests whether the #public decorator has been applied to the url.
"""
return getattr(endpoint, '_is_public', False) is True
#blueprint.before_app_request # or #app.before_request
def security_check():
"""Check all incoming requests for a current user.
"""
if current_user.is_logged_in: # need current_user test elsewhere
# we don't need to check if we have a public url if the user is
# logged in
return
try:
if _is_public(current_app.view_functions[request.endpoint]):
# we just go perform the endpoint function if it is public
return
except KeyError:
# There is no endpoint matching the request
abort(404)
# user is not logged in and it's not a public url
logging.info("No current user and %s is not public" % request.path[1:])
# send the user to the welcome page
return redirect(url_for("some_public_page"))
Related
I've been moving from bottle to flask. I'm the type of person that prefers writing my own code instead of downloading packages from the internet if I the code needed is 20 lines or less. Take for example support for Basic authentication protocol. In bottle I could write:
def allow_anonymous():
"""assign a _allow_anonymous flag to functions not requiring authentication"""
def wrapper(fn):
fn._allow_anonymous = True
return fn
return wrapper
def auth_middleware(fn):
"""perform authentication (pre-req)"""
def wrapper(*a, **ka):
# if the allow_anonymous annotation is set then bypass this auth
if hasattr(fn, '_allow_anonymous') and fn._allow_anonymous:
return fn(*a, **ka)
user, password = request.auth or (None, None)
if user is None or not check(user, password):
err = HTTPError(401, text)
err.add_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
return err
return fn(*a, **ka)
return wrapper
...
app = Bottle()
app.install(middleware.auth_middleware)
The above code gave me full support for basic auth protocol for all methods unless explicitly decorated with the #allow_anonymous wrapper. I'm just a beginner with flask. I'm having a hard time accomplishing the bottle-compatible code above in flask without adding dependencies on more python packages or excessive boiler-plate. How is this handled directly and clearly in flask?
You can definitely some of the functionality of flask-httpauth yourself, if you wish :-P
I would think you will need to play some before_request games (not very beautiful), or alternatively call flask's add_url_rule with a decorated method for each api endpoint (or have a route decorator of your own that will do this). The add_url_rule gets a view function that is usually your api endpoint handler, but in your case, will be a wrapped method in a manner very much like the one you gave in the post (auth_middleware).
The gist of it:
from flask import Flask, make_response, request
app = Flask(__name__)
def view_wrapper(fn):
"""
Create a wrapped view function that checks user authorization
"""
def protected_view(*a, **ka):
# if the allow_anonymous annotation is set then bypass this auth
if hasattr(fn, '_allow_anonymous') and fn._allow_anonymous:
return fn(*a, **ka)
# consult werkzeug's authorization mixin
user, password = (request.authorization.username, request.authorization.password) if request.authorization else (None, None)
if user is None or not check(user, password):
err_response = make_response(text, 401)
err_response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % realm
return err_response
return fn(*a, **ka)
return protected_view
# An endpoint
def hello():
return 'hello there'
app.add_url_rule('/', 'hello', view_wrapper(hello))
Of course, this can (and should) be further enhanced with Blueprints, etc.
Note #1: this is cribbed from separate answers in SO.
Note #2: this doesnt use blueprints. Again, I'm new to flask and I appreciate that blueprints will help the app scale but one step at a time...
def allow_anonymous(decorated_function):
decorated_function.is_public = True
return decorated_function
#app.before_request
def auth_middleware():
fn = app.view_functions[request.endpoint]
if hasattr(fn, 'allow_anonymous') and fn.allow_anonymous:
# anonymous permitted
return
elif my_custom_authentication():
# anonymous not permitted authentication succeeded
return
else:
# anonymous not permitted authentication failed
err_response = make_response(text, 401)
err_response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % realm
return err_response
#app.route('/public_thing')
#allow_anonymous
def public_thing():
return 'yo'
#app.route('/regular_thing')
def regular_thing():
return 'if you can read this youre authenticated.'
I have a blueprint and some url functions,
admin_bp = Blueprint('admin', __name__)
#admin_bp.route('/dashboard', methods=['GET', ])
#flask_login.login_required
def dashboard():
context = {}
page = 'admin/dashboard.html'
return render_template(page, **context)
#admin_bp.route('/deny', methods=['GET', ])
#flask_login.login_required
def deny():
return 'hey bro you dont belong here'
I don't want to copy paste #flask_login.login_required decorator for all url functions under this blueprint. Is there a better way that I can apply decorator for all blueprint urls?
You can add before_request() as a function that will run before each request in a view.
You will then want to add decorators to inject additional functionality to the before_request function. You will want to import the login_required decorator to ensure each endpoint requires a logged in user. This decorator is part of the flask_login library.
Since it looks like your views are part of an admin, I'd also recommend adding a custom decorator to your before_request function with something like #role_required('admin'). The functionality for that decorator will live somewhere else and be imported.
#admin_bp.before_request
#login_required
def before_request():
""" Protect all of the admin endpoints. """
pass
Subclass Blueprint and override the route method.
import flask
class MyBlueprint(flask.Blueprint):
def route(self, rule, **options):
def decorator(f):
# these lines are copied from flask.Blueprint.route
endpoint = options.pop("endpoint", f.__name__)
self.add_url_rule(rule, endpoint, f, **options)
# At this point flask.Blueprint.route simply returns f.
# But you can nest a decorator.
def inner(*args, **kwargs):
# stuff you want to do before each request goes here
try:
result = f(*args, **kwargs)
# stuff you want to do on successful responses (probing status, headers, etc.) goes here
except Exception as e:
# stuff you want to do on error responses goes here
raise
return inner
Now use the new subclass in your blueprints:
-v1_blueprint = Blueprint('v1', __name__)
+v1_blueprint = MyBlueprint('v1', __name__)
No changes needed to individual routes.
The drawback of this approach is that it copies code from inside Flask. If the implementation of flask.Blueprint.route were to change in a future version, you'd need to sync MyBlueprint with it when you upgrade Flask.
How about checking the user first:
from flask.ext.login import current_user
#admin_bp.before_request
def check_user():
if not current_user.is_authenticated():
abort(401)
# your other functions without `#flask_login.login_required`
I have 2 Flask apps (different projects) that work together . One implements some API which uses tokens for auth. The second one consumes the API and makes a web interface for it. Now I have a login function that sends the username and password to the API, and if correct, gets the auth token in return. Once I have the token, I save it to the session of the user and the user should now be considered as logged in/ autheticated. How can I implement the login_required decorator for such a case.
Here is my login function -
def login(self):
response = make_request(BASE_URL + 'login/', clean_data(self.data))
if response.status_code == 200:
session['auth_token'] = response.json().get('auth_token')
return True
return False
How can I make the login_required decorator?
Also I am using Redis to store sessions if that matters.
Have a look at the official flask docs regarding decorators:
https://flask.palletsprojects.com/en/1.1.x/patterns/viewdecorators/ or the python docs https://www.python.org/dev/peps/pep-0318/ as well.
Your decorator should look something like:
from functools import wraps
from flask import abort
import jwt
def authorize(f):
#wraps(f)
def decorated_function(*args, **kws):
if not 'Authorization' in request.headers:
abort(401)
user = None
data = request.headers['Authorization'].encode('ascii','ignore')
token = str.replace(str(data), 'Bearer ','')
try:
user = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])['sub']
except:
abort(401)
return f(user, *args, **kws)
return decorated_function
... and then in your app.py you may have:
#app.route('/api/game', methods=['POST'])
#authorize
def create(user):
data = json.loads(request.data)
....
In this particular case I have used JWT as token and your token can be different respectively the decoding of the token can be your custom implementation, but the basic mechanisms are pretty much as on the example above.
I would place the following decorator function in somewhere common
def validate_api_token(validation_func):
def decorator(f):
#wraps(f)
def decorated_function(*args, **kws):
api_token = request.headers.get('Authorization')
is_valid_api_token = validation_func(api_token)
if is_valid_api_token:
return f(*args, **kws)
return 'Invalid API Token', 401
return decorated_function
return decorator
For small POC flask apps, if you're ok with storing the tokens in a non-versioned file, the following can work:
# tokens are read from a non-versioned `.tokens` file and loaded into a set
api_tokens = load_api_tokens()
def simple_api_token_validation(api_token):
return api_token in api_tokens
#app.route("/v1/my/secret/function", methods=['POST'])
#validate_api_token(simple_api_token_validation)
def my_secret_function():
body = request.get_json()
# ...
Another simple option is to query against a database (e.g. redis):
redis_session = Redis(host=REDIS_HOST, password=REDIS_PASSWORD)
def redis_api_token_validation(api_token):
if not api_token:
return False
api_token_hash = hashlib.sha256(api_token.encode()).hexdigest()
return redis_session.exists(f'api:tokens:{api_token_hash}')
#app.route("/v1/my/secret/function", methods=['POST'])
#validate_api_token(redis_api_token_validation)
def my_secret_function():
body = request.get_json()
# ...
Best IMO as #Velin answered is to use jwt to validate the token
Given that each subsequent request will contain the API token, the decorator should do the following
Accept a generic request. You can use *args and **kargs for that
Extract the token from the header and compare it with the token stored in db (not Redis, but wherever the token generated is stored in the backend)
If authenticated, the *args and **kargs should be passed on to the decorated function
The output of the decorated function should then be returned as is
If the authentication failed, an error message should be returned.
For explanation on decorators, check out this link:
http://thecodeship.com/patterns/guide-to-python-function-decorators/
I have a flask app where I have to delete and update user information, like this (simplified):
#app.route('/<user>')
def show_user(user):
""" Show user information """
render_template('show_user.html')
#app.route('/delete/<user>')
def delete_user(user):
""" Delete user from database """
delete_user_from_db(user)
return redirect(url_for('show_users', user=user)
#app.route('/update/<user>', method=["POST"])
def update_user(user):
""" Update user information """
update(user, stuff_from_POST)
return redirect(url_for('show_users', user=user)
For each of these methods I need to verify whether the user specified in the URL is really a valid user, so I'd do something like this at the beginning of all those functions:
if user not in mydb:
do(something)
abort(404)
This is rather cumbersome, and since I will be having more functions that depend on the user to be valid, I was wondering if it were possible to wrap that block in another function that gets automatically executed when those routes are called.
Thanks in advance.
Use the before_request hook function see docs
#app.before_request
def before_request():
if user not in mydb:
do(something)
abort(404)
Edit:
I tried this
from flask import Flask, request
app = Flask(__name__)
db = ['paul', 'steve', 'anna']
#app.before_request
def before_request():
if request.endpoint in ['show_user', 'delete_user', 'update_user']:
user = request.path[request.path.rfind('/') + 1:]
if user not in db:
return 'user not found', 404
#app.route('/<user>')
def show_user(user):
""" Show user information """
return 'hello %s' % user
#app.route('/other')
def show_other():
""" Demonstrates other routes are not effected by before_request """
return 'other stuff'
if __name__ == "__main__":
app.run()
It's not actually as neat as I hoped but it works.
It's a little unfortunate to have to maintain which endpoints do what in the before_request function. If you were doing REST you might be able to merge these endpoints into one and simply use different http methods for each action.
For every request in Bottle I would like to check if the request is eligible through HTTP authentication. My idea is to use a function, which is called at the start of every #route function.
def check_authentificaiton(requests):
auth = request.headers.get('Authorization')
credentials = parse_auth(auth)
if credentials[0] is not 'user' or credentials[1] is not 'password':
raise Exception('Request is not authorized')
This seems a bit redundant, since I want to protect every request, and it could fail if I forget to call it. Is there a better way?
I think you are looking for a decorator which mandates a route to be accessed only if the user is loggedin. Like in the example below, #require_uid is a decorator which you can use around any function where you need user to be logged in. Flask has a login_required decorator.
Using decorators to require sign in with bottle.py
def require_uid(fn):
def check_uid(**kwargs):
cookie_uid = request.get_cookie('cookieName', secret='cookieSignature')
if cookie_uid:
# do stuff with a user object
return fn(**kwargs)
else:
redirect("/loginagain")
return check_uid
#route('/userstuff', method='GET')
#require_uid
#view('app')
def app_userstuff():
# doing things is what i like to do
return dict(foo="bar")