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
Related
I am developing an API using Flask, a service not meant to be rendering any templates but simply returning json in the form of {"message": "message here", "status_code": 200, "data": {}}".
I have several routes and each modifies a global response object attached to Flask.g:
from flask import g
from project import app
#app.route('/users/', methods=["GET"])
def get_users():
...
g.res.data["message"] = "Successfully queried users as specified."
# default g.res.status_code == 200
#app.route('/users/', methods=["POST"])
def post_users():
...
g.res.data["message"] = "Successfully created User."
g.res.status_code = 201
Then, returning the response data to the user is handled by an app.after_request:
from flask import g, jsonify
from project import app
#app.after_request
def after_request(response: None):
return jsonify(g.res)
However, Flask still (correctly) believes that the view function is not returning a response, since technically the view function is not, but the after_request handler is.
Is there any inherit problems in my approach to handling the response data? I purposely switched to using app.after_request to avoid duplicating return jsonify(g.res) at the end of each view function.
If there are no problems with this way, how should I go about disabling these warnings?
Edit: The error is being raised in Flask.app.make_response where a ValueError is raised on the response being None and other conversions are attempted later (from a str to a Flask.Response object, for example).
I will likely just modify this file directly to handle my specific use-case until I find an idiomatic way to approach this.
Subclass the Flask class and override the make_response method. Move the logic from your handler into the method. If there was no response returned by the view but there is valid data in g.res, then build the response and return it, otherwise defer to the default behavior.
class GResFlask(Flask):
def make_response(self, rv):
if not rv and g.res.data:
return jsonify(g.res)
return super().make_response(rv)
app = GResFlask(__name__)
It's important to note that after_request handlers may be added by extensions, and the interface provided by Flask means that they will be expecting a valid response object. So you always want to return a valid response from make_response.
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 want to access request.url in middleware.
Flask app - test.py
from flask import Flask
from middleware import TestMiddleware
app = Flask(__name__)
app.wsgi_app = TestMiddleware(app.wsgi_app)
#app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
middleware.py:
from flask import request
class TestMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
# How do I access request object here.
print "I'm in middleware"
return self.app(environ, start_response)
I understand request can be accessed in Flask application context. We normally use
with app.test_request_context()
But in middleware, I don't have access to Flask app object.
How do I proceed?
Thanks for any help..
It's the application object that constructs the request object: it doesn't exist until the app is called, so there's no way for middleware to look at it beforehand. You can, however, construct your own request object within the middleware (using Werkzeug directly rather than Flask):
from werkzeug.wrappers import Request
req = Request(environ, shallow=True)
You might even be able to construct Flask's own Request object (flask.wrappers.Request, which is a subclass of Werkzeug's Request class) the same way. Looking at the source I don't see anything that should stop you from doing this, but since it isn't designed to be used that way you're probably best off sticking with the Werkzeug one unless you need one of the extra properties added by Flask's subclass.
Middleware stands between your WSGI server and Flask Application. The request object is created in the Flask Application. So there isn't any request object in the middleware.
Perhaps you need a #before_request handler called just before your view?
You can't have a Request object before the application finds an URL rule and creates it. However, after the application has done its thing, you can find the Request object in the environ:
environ['werkzeug.request']
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.
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")