Lets say I have an API at /api/something. The API requires a definition for api_key, it looks in the request arguments and the cookies. If it finds the api_key, I want it to pass the api_key to the route methods, in this case something.
#app.before_request
def pass_api_key():
api_key = request.args.get('api_key', None)
if api_key is None:
api_key = request.cookies.get('api_key', None)
if api_key is None:
return 'api_key is required'
# add parameter of api_key to something method
#app.route('/api/something')
def something(api_key):
return api_key
Is this possible?
Thanks in advance.
One way to do this would be to use flask.g. From the docs:
To share data that is valid for one request only from one function to another, a global variable is not good enough because it would break in threaded environments. Flask provides you with a special object that ensures it is only valid for the active request and that will return different values for each request.
Set g.api_key to the value you want to store in before_request and read it out in the route method.
flask.g, like flask.request, is what Flask and Werkzeug call a "context local" object - roughly, an object that pretends to be global, but really exposes different values to each request.
This can be done using the url_value_processor decorator:
#app.url_value_preprocessor
def get_project_object(endpoint, values):
api_key = values.get('api_key')
if api_key is None:
api_key = request.cookies.get('api_key', None)
if api_key is None:
raise Exception('api_key is required')
values['api_key'] = api_key
This can also be done in a Blueprint basis, so that it only applies to the views in the specified Blueprint.
Related
I'm creating a Flask app with JWT Authorization and trying to test services with PyTest.
I successfully added tests to endpoints, but when I trying to add unit tests for certain function I can't access current user, because flask_jwt_extended.get_current_user() returns None.
Here is simple example:
#api.route('/listings', methods=['POST'])
#jwt_required
def create_listing():
payload = request.json
listing = listing_svc.create(payload)
return listing
def create(payload):
listing = ListingSchema().load(payload, db.session).data
class ListingSchema(ModelSchema):
id = field_for(Project, 'id', dump_only=True)
creator_user_id = field_for(Project, 'creator_user_id')
# ...
#pre_load
def set_creator_id(self, data):
current_user = flask_jwt_extended.get_current_user()
data['creator_user_id'] = current_user.id
It works when I authorize and send a request using app_context:
with client.application.app_context():
rv = client.post('/listings',
# ...
)
But what I need is to test create function without sending a request to client. In this case flask_jwt_extended.get_current_user() returns None, so I think I should set request context some way before running this function.
I tried to do this...
fake_payload = {}
with client.application.test_request_context('/listings', headers={'Authorization': 'Bearer ' + access_token}):
create(fake_payload)
but still getting current_user is None
This is how I get token:
def login(email, password):
user = get_by_email(email)
if user and check_password_hash(user.password, password):
return access_token = flask_jwt_extended.create_access_token(identity=email)
If you are writing unit tests, using mock can be helpful. For jwt authorization with flask-jwt-extended you can patch the verify_jwt_in_request method which is called from the jwt_required decorator. Then you can also patch the get_jwt_identity function to return a test username. For example:
from unittest.mock import patch
#patch('path.to.some.code.get_jwt_identity')
#patch('flask_jwt_extended.view_decorators.verify_jwt_in_request')
def test_some_code(mock_jwt_required, mock_jwt_identity):
mock_jwt_identity.return_value = 'user1'
# Test jwt protected endpoint
Note: This patch is specific to latest package version flask-jwt-extended==3.21.0. The code may change with new versions.
Question from a long time ago but here is the solution for further readers.
You need to activate the app_context, then the request_context and finally call the function decorator is calling, which is verify_jwt_in_request:
fake_payload = {}
with client.application.app_context():
with client.application.test_request_context('/listings', headers={'Authorization': 'Bearer ' + access_token}):
verify_jwt_in_request()
create(fake_payload)
and now you have your current_user set
If you truly want to unit test you need to unit test one function at a time. This is true test driven development in my opinion. So first write tests for create then load and so on. Use patching to mock the functionality of calls to other functions.
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/
Currently I am working on an Oauthlib-Flask implementation for a non-REST API. But I have two scenarios where I want to change/add a value of the flask request object. Since it is immutable this doesn't come with ease. I tried making a duplicate as it is suggested in Changing values on a werkzeug request object. But since the #oauth.authorize_handler uses the given request object I would have to replace it, which resolves in an UnboundLocalError: local variable 'request' referenced before assignment error. Here is my sample code (it is a part of the implicit grant):
#app.route('/oauth/authorize', methods=['GET', 'POST'])
#login
#oauth.authorize_handler
def authorize(*args, **kwargs):
if request.method == 'GET':
client_id = kwargs.get('client_id')
client = Client.query.filter_by(client_id=client_id).first()
kwargs['client'] = client
return render_template('authorize.html', **kwargs)
r = make_duplicate_request(request)
#Change/add values of r
request = r
return True
Am I doing something wrong or is there another possibility to change the request object?
Thanks for your help!
Update:
The code above describes the situation where I want to pass information to the tokensetter function. This could be done with a global variable, but I wanted to avoid that.
In my signup routine the client sends a request like this to the API:
params_signup = {
"schemas":["urn:scim:schemas:core:2.0:User"],
"expireIn":3600,
"username":"test#web.de",
"password":"123",
"access_token":"",
"externalId":"tmeinhardt",
"grant_type":"password",
"client_id":"1",
"params":{
"age":"20-30",
"gender":"m",
}
}
I need the grant_type and client_id part only for the tokenhandler and wanted to add it manually to the request object. But since this object is immutable...
Writing this for those who will come across this and are trying to implement an OAuth flow.
Don't use decorators use middleware instead
I believe you should handle this in a middleware. In the middleware, you can set the authorization property of the 2nd parameter of the call function which contains the current wsgi app environment variables you have passed in your init function.
Look at code below:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
cookie = Request(environ).cookies.get('access_token')
if cookie is not None:
environ['HTTP_AUTHORIZATION']='Bearer '+cookie
return self.app(environ, start_response)
you can create a new request and splat the headers into a new dict of an HttpRequest (eg. Authlib's HttpRequest)
from flask import request as _req
from authlib.oauth2.rfc6749 import HttpRequest
request = HttpRequest(
_req.method, _req.full_path, _req.data, {**_req.headers, **auth}
)
request.req = _req
then you can work with this new request instead of the Flask request
I am new to Flask and am learning about the #app.after_request and #app.teardown_appcontext. I have a decorated view for oauthlib that takes an argument, data (which is an object).
#app.route('/api/me')
#oauth.require_oauth()
def me(data):
user = data.user
return jsonify(username=user.username)
After this view (and many other views) are executed, I'd like to update my database but need to have access to the variable data. How do I do that with #app.after_request or #app.teardown_appcontext?
#app.after_request
def record_ip(response):
client = data.client # needs access to "data"
.... log stuff in my database ...
return response
You can add the object to the flask.g globals object:
from flask import g
#app.route('/api/me')
#oauth.require_oauth()
def me(req):
user = req.user
g.oauth_request = req
return jsonify(username=user.username)
#app.after_request
def record_ip(response):
req = g.get('oauth_request')
if req is not None:
client = req.client # needs access to "req"
# .... log stuff in my database ...
return response
The global flask.g context is thread safe and tied to the current request; quoting from the documentation:
The application context is created and destroyed as necessary. It never moves between threads and it will not be shared between requests.
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"))