I have created a web app which will be publicly facing. There are some admin tools that the IT department will use to administer certain things in databases for example.
I have all my routes and models for the database, I just want to get some insight as to whether my function would be a suitable method of whitelisting IP addresses to routes and if I have missed something.
def allowed_ip(request):
if not request:
now = time.strftime("%b-%d-%Y_%H:%M:%S", time.gmtime(time.time()))
app.logger.info('No request was sent -=- {}'.format(now))
return False
if request and request.headers['X-Real-IP']:
if request.headers['X-Real-IP'] not in config.Config.ALLOWED_IPS:
now = time.strftime("%b-%d-%Y_%H:%M:%S", time.gmtime(time.time()))
app.logger.info('Request received from non-whitelist client {} -=- {}'.format(request.headers['X-Real-IP'],
now))
return False
else:
now = time.strftime("%b-%d-%Y_%H:%M:%S", time.gmtime(time.time()))
app.logger.info('Request received from whitelisted client {} -=- {}'.format(request.headers['X-Real-IP'],
now))
return True
else:
now = time.strftime("%b-%d-%Y_%H:%M:%S", time.gmtime(time.time()))
app.logger.info('Request received from but no IP sent -=- {}'.format(now))
return False
The function checks if it received a request, (I know that seems pointless but I was receiving some weird errors without this line), if it has received a request, it checks the X-Real-IP header to see if it in our whitelist.
Is there anything I'm missing that could be manipulated here?
I appreciate this may be a broad or off-topic question but I'm also open to other methods of doing this. Perhaps it would be better for me to manage whitelisting at Nginx's level?
My answer adapted into my code:
from functools import wraps
def whitelisted(f):
#wraps(f)
def decorated_function(*args, **kwargs):
if request.headers['X-Real-IP'] not in app.config.get('ALLOWED_IPS'):
return redirect(url_for('login', next=request.url))
return f(*args, **kwargs)
return decorated_function
Now this is possible:
#app.route('/map')
#whitelisted
#login_required
def show_all():
I will do something like this:
# helpers.py
from flask import request, current_app
def check_ip():
def decorator(f):
def wrapped_function(*args, **kwargs):
ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr)
if ip:
if ip in current_app.config.get('ALLOWED_IPS'):
return f(*args, **kwargs)
return 'Nice try! <3' # Do a real thing like real http code for forbiden, etc ... use response
return update_wrapper(wrapped_function, f)
return decorator
# routes.py
index = Blueprint('index ', __name__)
#index.route('/')
#check_ip()
def hello_world():
return render_template('home.html')
But just using the IP is not secure, if you want something better you should use flask_login or something like this in my opinion.
Related
I have a application with a large number of automatically defined rules. I need every rule to check for a Bearer token before proceeding.
I have found app.url_map.iter_rules() which does let me see all the defined rules. I want a way to redefine every rule to use its current function, but wrap it with the authenticate_admin decorator defined below.
from flask import Flask
from functools import wraps
app = Flask(__name__)
# Check that the request has the correct `bearer_token`.
def authenticate_admin(func):
#wraps(func)
def wrapped(*args, **kwargs):
bearer_token = vault.get('secret/oauth')['bearer_token']
expected = ("Bearer " + bearer_token)
if expected != request.headers.get('Authorization'):
return jsonify({'error': "Authorization token incorrect"}), 401
return func(*args, **kwargs)
return wrapped
# .... Define a bunch of routes (Elided) ....
for rule in app.url_map.iter_rules():
# NEXT LINE IS PSEUDOCODE; IT IS WHAT I WANT TO ACHIEVE
rule.fx = authenticate_admin(rule.fx)
What I want is that after the above for loop executes, all rules will now require you to supply a Bearer token.
In the end, I just ended up adding following davidism's advice and made authenticate a function that is run by all endpoints. I added a whitelist of endpoints that do NOT require authentication.
from flask import request
import vault
app = Flask(__name__)
# .... Define a bunch of routes (Elided) ....
WHITELIST_POST = ['/post1', '/post2', '/post3']
WHITELIST_GET = ['/', '/get1', '/get2']
def authenticate():
if request.method == "GET" and request.url_rule.rule in WHITELIST_GET:
return
if request.method == "POST" and request.url_rule.rule in WHITELIST_POST:
return
bearer_token = vault.get('secret/oauth')['bearer_token']
expected = ("Bearer " + bearer_token)
if expected != request.headers.get('Authorization'):
abort(401)
app.before_request(authenticate)
On a side note, my boss liked this even more. It means that any endpoint we create on the server by default requires a bearer token. In the past we have forgotten to require a bearer token for some endpoints. Now it is opt out instead of opt in.
I would like to route to a different Flask views based on the Accept HTTP header, for example:
#api.route('/test', accept='text/html')
def test_html():
return "<html><body>Test</body></html>"
#api.route('/test', accept='text/json')
def test_json():
return jsonify(test="Test")
I haven't found relevant option in Werkzeug Rule constructor, which is used by Flask. Is it a missing feature or is it possible to achieve the same effect differently, for example by intercepting and modifying URL path before routing?
I don't want to merge the views into one because it would complicate code significantly, there are many of them and they reside in different blueprints.
I am aware that similar question has been asked, but nobody answered it using Flask. It's possible to do it in different web frameworks, for example in Pyramid using predicates - sample code can be found in this answer.
I know this is an old question but I ended up here looking for something similar so I hope it helps someone else.
flask_accept has the functionality to handle different Accept types through different routes.
from flask import Flask, jsonify
from flask_accept import accept
app = Flask(__name__)
#app.route('/')
#accept('text/html')
def hello_world():
return 'Hello World!'
#hello_world.support('application/json')
def hello_world_json():
return jsonify(result="Hello World!")
if __name__ == '__main__':
app.run()
if you just want to reject requests depending on whether they are a specific data type you could also use Flask-Negotiate
from flask import Flask
from flask_negotiate import consumes, produces
app = Flask(__name__)
#app.route('/consumes_json_only')
#consumes('application/json')
def consumes_json_only():
return 'consumes json only'
When one tries to access the endpoint without a valid Accept header:
$ curl localhost:5000 -I
HTTP 415 (Unsupported Media Type)
I wrote a decorator which does that (copying here for posterity). It's just a rough idea that could be improved further (e.g. returning 406 Not Acceptable response instead of using the default handler when there are no handlers that match given MIME type). More explanations are in the comments.
import functools
from flask import Flask, request, jsonify
app = Flask(__name__)
def accept(func_or_mimetype=None):
"""Decorator which allows to use multiple MIME type handlers for a single
endpoint.
"""
# Default MIME type.
mimetype = 'text/html'
class Accept(object):
def __init__(self, func):
self.default_mimetype = mimetype
self.accept_handlers = {mimetype: func}
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
default = self.default_mimetype
mimetypes = request.accept_mimetypes
best = mimetypes.best_match(self.accept_handlers.keys(), default)
# In case of Accept: */*, choose default handler.
if best != default and mimetypes[best] == mimetypes[default]:
best = default
return self.accept_handlers[best](*args, **kwargs)
def accept(self, mimetype):
"""Register a MIME type handler."""
def decorator(func):
self.accept_handlers[mimetype] = func
return func
return decorator
# If decorator is called without argument list, return Accept instance.
if callable(func_or_mimetype):
return Accept(func_or_mimetype)
# Otherwise set new MIME type (if provided) and let Accept act as a
# decorator.
if func_or_mimetype is not None:
mimetype = func_or_mimetype
return Accept
#app.route('/')
#accept # Or: #accept('text/html')
def index():
return '<strong>foobar</strong>'
#index.accept('application/json')
def index_json():
return jsonify(foobar=True)
#index.accept('text/plain')
def index_text():
return 'foobar\n', 200, {'Content-Type': 'text/plain'}
You can return different response types based on the Accept header using request. Example.
if request.accept_mimetypes['application/json']:
return jsonify(<object>), '200 OK'
Is there anyway to get suds returning the SoapRequest (in XML) without sending it?
The idea is that the upper levels of my program can call my API with an additional boolean argument (simulation).
If simulation == false then process the other params and send the request via suds
If simulation == false then process the other params, create the XML using suds (or any other way) and return it to the caller without sending it to the host.
I already implemented a MessagePlugin follwing https://fedorahosted.org/suds/wiki/Documentation#MessagePlugin, but I am not able to get the XML, stop the request and send back the XML to the caller...
Regards
suds uses a "transport" class called HttpAuthenticated by default. That is where the actual send occurs. So theoretically you could try subclassing that:
from suds.client import Client
from suds.transport import Reply
from suds.transport.https import HttpAuthenticated
class HttpAuthenticatedWithSimulation(HttpAuthenticated):
def send(self, request):
is_simulation = request.headers.pop('simulation', False)
if is_simulation:
# don't actually send the SOAP request, just return its XML
return Reply(200, request.headers.dict, request.msg)
return HttpAuthenticated(request)
...
sim_transport = HttpAuthenticatedWithSimulation()
client = Client(url, transport=sim_transport,
headers={'simulation': is_simulation})
It's a little hacky. (For example, this relies on HTTP headers to pass the boolean simulation option down to the transport level.) But I hope this illustrates the idea.
The solution that I implemented is:
class CustomTransportClass(HttpTransport):
def __init__(self, *args, **kwargs):
HttpTransport.__init__(self, *args, **kwargs)
self.opener = MutualSSLHandler() # I use a special opener to enable a mutual SSL authentication
def send(self,request):
print "===================== 1-* request is going ===================="
is_simulation = request.headers['simulation']
if is_simulation == "true":
# don't actually send the SOAP request, just return its XML
print "This is a simulation :"
print request.message
return Reply(200, request.headers, request.message )
return HttpTransport.send(self,request)
sim_transport = CustomTransportClass()
client = Client(url, transport=sim_transport,
headers={'simulation': is_simulation})
Thanks for your help,
I have the following Flask routes and a custom helper:
from spots import app, db
from flask import Response
import simplejson as json
def json_response(action_func):
def create_json_response(*args, **kwargs):
ret = action_func(*args, **kwargs)
code = 200
if len(ret) == 2:
code = ret[0]
resp = ret[1]
else:
resp = ret[0]
return Response(
response=json.dumps(resp, indent=4),
status=code,
content_type='application/json'
)
return create_json_response
#app.route('/test')
#json_response
def test():
return 400, dict(result="Test success")
#app.route('/')
#json_response
def home():
return 200, dict(result="Home success")
I would expect a GET request to /test to return something like {"result":"Test success"} but that is not the case. Instead, any request seems to match the last route, i.e. home. Why?
I wonder if this is caused by some lack of insulation between the different calls to json_response?
Thanks in advance.
As Видул Петров said the solution is to use functools.wraps:
import functools
def json_response(action_func):
#functools.wraps(action_func)
def create_json_response(*args, **kwargs):
...
return create_json_response
The reason is that Flask’s routing system maps URLs to "endpoints", and then endpoints to view functions. The endpoint defaults to the __name__ attribute of the view function. In this case the decorated function was passed to app.route so the endpoint was create_json_response for both rules and the last view defined for that endpoint was used in both cases.
functools.wraps takes the __name__ (and other attributes) from the original function and fixes this. It is always a good idea to use it in decorated wrappers.
Having a code inspired from http://code.djangoproject.com/wiki/XML-RPC :
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
from django.http import HttpResponse
dispatcher = SimpleXMLRPCDispatcher(allow_none=False, encoding=None) # Python 2.5
def rpc_handler(request):
"""
the actual handler:
if you setup your urls.py properly, all calls to the xml-rpc service
should be routed through here.
If post data is defined, it assumes it's XML-RPC and tries to process as such
Empty post assumes you're viewing from a browser and tells you about the service.
"""
if len(request.POST):
response = HttpResponse(mimetype="application/xml")
response.write(dispatcher._marshaled_dispatch(request.raw_post_data))
else:
pass # Not interesting
response['Content-length'] = str(len(response.content))
return response
def post_log(message = "", tags = []):
""" Code called via RPC. Want to know here the remote IP (or hostname). """
pass
dispatcher.register_function(post_log, 'post_log')
How could get the IP address of the client within the "post_log" definition?
I have seen IP address of client in Python SimpleXMLRPCServer? but can't apply it to my case.
Thanks.
Ok I could do it ... with some nifty tips ...
First, I created my own copy of SimpleXMLRPCDispatcher which inherit everything from it and overides 2 methods :
class MySimpleXMLRPCDispatcher (SimpleXMLRPCDispatcher) :
def _marshaled_dispatch(self, data, dispatch_method = None, request = None):
# copy and paste from /usr/lib/python2.6/SimpleXMLRPCServer.py except
response = self._dispatch(method, params)
# which becomes
response = self._dispatch(method, params, request)
def _dispatch(self, method, params, request = None):
# copy and paste from /usr/lib/python2.6/SimpleXMLRPCServer.py except
return func(*params)
# which becomes
return func(request, *params)
Then in my code, all to do is :
# ...
if len(request.POST):
response = HttpResponse(mimetype="application/xml")
response.write(dispatcher._marshaled_dispatch(request.raw_post_data, request = request))
# ...
def post_log(request, message = "", tags = []):
""" Code called via RPC. Want to know here the remote IP (or hostname). """
ip = request.META["REMOTE_ADDR"]
hostname = socket.gethostbyaddr(ip)[0]
That's it.
I know it's not very clean... Any suggestion for cleaner solution is welcome!