I have set up a twisted + flask https server that also does certificate-based client authentication by following the documentation at the Twisted site here. So far, so good.
In addition to authenticating the client using a certificate, the application code within the flask app needs the user name (which is present in the client x509 certificate) in order to do its job. I couldn't find an easy way to access this information. The information (based on the documentation) seems to be in the pyopenssl X509Name object at the time it does authentication, and I need the identity at the flask layer every time I process a request from that client.
The request object flask is getting did not seem to have this information (unless I read it wrong), so I assume I need to modify some options at the Twisted level to send them through to flask. I also need to somehow get them out of the OpenSSL layer.
How would you do this?
Updated: using HTTPChannel.allHeadersReceived instead of Protocol.dataReceived for support of chunked requests.
You can use HTTP headers to store connection information: set them up in HTTPChannel.allHeadersReceived method and retrieve from flask.request.headers, e.g.:
from twisted.application import internet, service
from twisted.internet import reactor
from twisted.web.http import HTTPChannel
from twisted.web.server import Site
from twisted.web.wsgi import WSGIResource
from flask import Flask, request
app = Flask('app')
#app.route('/')
def index():
return 'User ID: %s' % request.headers['X-User-Id']
class MyHTTPChannel(HTTPChannel):
def allHeadersReceived(self):
user_id = 'my_user_id'
req = self.requests[-1]
req.requestHeaders.addRawHeader('X-User-Id', user_id)
HTTPChannel.allHeadersReceived(self)
class MySite(Site):
protocol = MyHTTPChannel
application = service.Application('myapplication')
service = service.IServiceCollection(application)
http_resource = WSGIResource(reactor, reactor.getThreadPool(), app)
http_site = MySite(http_resource)
internet.TCPServer(8008, http_site).setServiceParent(service)
I'm not familiar with using client certificates in twisted. I assume you can retrieve its information in Protocol.transport.
Related
I've been trying to figure out how to properly do a POST with FastAPI.
I'm currently doing a POST with python's "requests module" and passing some json data as shown below:
import requests
from fastapi import FastAPI
json_data = {"user" : MrMinty, "pass" : "password"} #json data
endpoint = "https://www.testsite.com/api/account_name/?access_token=1234567890" #endpoint
print(requests.post(endpoint, json=json_data). content)
I don't understand how to do the same POST using just FastAPI's functions, and reading the response.
The module request is not a FastAPI function, but is everything you need,
first you need to have your FastAPI server running, either in your computer or in a external server that you have access.
Then you need to know the IP or domain of your server, and then you make the request:
import requests
some_info = {'info':'some_info'}
head = 'http://192.168.0.8:8000' #IP and port of your server
# maybe in your case the ip is the localhost
requests.post(f'{head}/send_some_info', data=json.dumps(tablea))
# "send_some_info" is the address of your function in fast api
Your FastApI script would look like this, and should be running while you make your request:
from fastapi import FastAPI
app = FastAPI()
#app.post("/send_some_info")
async def test_function(dict: dict[str, str]):
# do something
return 'Success'
I have two separate WSDL files that are provided to me to interact with a service, one WSDL file just provides a method to login and generate an access token. The other WSDL file provides the methods to actually interact with the system.
If I instantiate the zeep SOAP client with the first WSDL file to login do I need to reinstantiate the client for the next WSDL file or can I simply tell it to go look at the next WSDL file?
from zeep import Client
client = Client("https://url.service.com/Session?wsdl")
token = client.service.login(username, password)
client = Client("https://url.service.com/Object?wsdl")
client.service.find(token, 'filter')
I attempted to use create_service but I don't think I'm using it correctly.
Thank you!
You need to reinstantiate the second Client.
I expect that you also need to extend your code to use the same requests Session and Zeeps Transport.
from requests import Session
from zeep import Client
from zeep.transports import Transport
transport = Transport(session=Session())
client = Client("https://url.service.com/Session?wsdl", transport=transport)
token = client.service.login(username, password)
client = Client("https://url.service.com/Object?wsdl", transport=transport)
client.service.find(token, 'filter')
I'm trying to write an HTTP server in python 2.7. I'm trying to use ready-made classes to simplify the job (such as SimpleHTTPServer, BaseHTTPRequestHandler, etc.).
The server should listen for GET requests, and once it gets one - parse the request (the path and the arguments), and interact with an already initialized object (which accesses a DB, counts number of requests, etc.) - let's call it the 'handler', and return a response.
I understand that the RequestHandler class (e.g. BaseHTTPRequestHandler) will be constructed for each request. How can I pass the 'handler' to the handling routines, so that they could call its methods?
Thanks!
Use a framework to further simplify your job. Here is an example in flask:
from flask import Flask
from flask import request
app = Flask(__name__)
your_handler = SomeHandlerClass()
#app.route("/")
def index():
return your_handler.do_something_with(request)
if __name__ == "__main__":
app.run()
request is a proxy object that holds all the incoming request data.
My website is on http protocol. My flask API is secured via flask-httpauth (https://github.com/miguelgrinberg/Flask-HTTPAuth).
There is a Tornado web server in front of my Flask API which listens on a private port 5000. Client API requests first go to Tornado server which then calls the Flask API
This is the flow I've got going:
My website (on http) ---> corpauthentication (on https) --> back to my website (http) --> client calls Tornado server --> Tornado calls Flask API and returns results
How safe is my API and website? I was reading this link Security of python flask REST API using HTTP Basic Authentication and it seems to me that the API is secure but I can never be sure.
If its not safe, what else do you think I can do to make it more secure? Since corpauthentication is required to get in, I feel on the UI side it is pretty safe. But lets say someone is listening on my port 80, will they be able to track any API requests made even when there is tornado + httpbasic auth in place?
This is my Tornado Server code:
from tornado.wsgi import WSGIContainer
from tornado.ioloop import IOLoop
from tornado.web import FallbackHandler, RequestHandler, Application
from flaskfile import app
class MainHandler(RequestHandler):
def get(self):
self.write("This message comes from Tornado ^_^")
tr = WSGIContainer(app)
application = Application([
(r"/tornado", MainHandler),
(r".*", FallbackHandler, dict(fallback=tr)),
])
if __name__ == "__main__":
application.listen(5000)
IOLoop.instance().start()
This is how I'm calling the API from my Javascript:
$.ajax({
url: 'http://x.x.x:5000/data',
type: 'GET',
dataType: 'json',
async: false,
headers: {
"Authorization": "Basic " + btoa("username" + ":" + "password")
},
data: {start: startdate, end: enddate},
success: function(result) {
data = result.results;
}
});
No, this is not secure - the comments in the other question you linked to are entirely correct. (BTW your question is really a duplicate of that).
Authentication over regular unencrypted HTTP is never secure - the username and password will be visible to any device between the user and the webserver, in plain text. As a first step you should implement SSL/TLS to encrypt the authentication information.
Tornado really needs to sit behind a web proxy of some sort. You could use either Apache or Nginx to fulfil this role. There are instructions for setting up Tornado+Nginx in this related question.
I've recently added a SSL certificate to my webapp. It's deployed on Amazon Web Services uses load balancers. The load balancers work as reverse proxies, handling external HTTPS and sending internal HTTP. So all traffic to my Flask app is HTTP, not HTTPS, despite being a secure connection.
Because the site was already online before the HTTPS migration, I used SSLify to send 301 PERMANENT REDIRECTS to HTTP connections. It works despite all connections being HTTP because the reverse proxy sets the X-Forwarded-Proto request header with the original protocol.
The problem
url_for doesn't care about X-Forwarded-Proto. It will use the my_flask_app.config['PREFERRED_URL_SCHEME'] when a scheme isn't available, but during a request a scheme is available. The HTTP scheme of the connection with the reverse proxy.
So when someone connects to https://example.com, it connects to the load balancer, which then connects to Flask using http://example.com. Flask sees the http and assumes the scheme is HTTP, not HTTPS as it originally was.
That isn't a problem in most url_for used in templates, but any url_for with _external=True will use http instead of https. Personally, I use _external=True for rel=canonical since I heard it was recommended practice. Besides that, using Flask.redirect will prepend non-_external urls with http://example.com, since the redirect header must be a fully qualified URL.
If you redirect on a form post for example, this is what would happen.
Client posts https://example.com/form
Server issues a 303 SEE OTHER to http://example.com/form-posted
SSLify then issues a 301 PERMANENT REDIRECT to https://example.com/form-posted
Every redirect becomes 2 redirects because of SSLify.
Attempted solutions
Adding PREFERRED_URL_SCHEME config
https://stackoverflow.com/a/26636880/1660459
my_flask_app.config['PREFERRED_URL_SCHEME'] = 'https'
Doesn't work because there is a scheme during a request, and that one is used instead. See https://github.com/mitsuhiko/flask/issues/1129#issuecomment-51759359
Wrapping a middleware to mock HTTPS
https://stackoverflow.com/a/28247577/1660459
def _force_https(app):
def wrapper(environ, start_response):
environ['wsgi.url_scheme'] = 'https'
return app(environ, start_response)
return wrapper
app = Flask(...)
app = _force_https(app)
As is, this didn't work because I needed that app later. So I used wsgi_app instead.
def _force_https(wsgi_app):
def wrapper(environ, start_response):
environ['wsgi.url_scheme'] = 'https'
return wsgi_app(environ, start_response)
return wrapper
app = Flask(...)
app.wsgi_app = _force_https(app.wsgi_app)
Because wsgi_app is called before any app.before_request handlers, doing this makes SSLify think the app is already behind a secure request and then it won't do any HTTP-to-HTTPS redirects.
Patching url_for
(I can't even find where I got this one from)
from functools import partial
import Flask
Flask.url_for = partial(Flask.url_for, _scheme='https')
This could work, but Flask will give an error if you set _scheme but not _external. Since most of my app url_for are internal, it doesn't work at all.
I was having these same issues with `redirect(url_for('URL'))' behind an AWS Elastic Load Balancer recently & I solved it this using the werkzeug.contrib.fixers.ProxyFix
call in my code.
example:
from werkzeug.contrib.fixers import ProxyFix
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
The ProxyFix(app.wsgi_app) adds HTTP proxy support to an application that was not designed with HTTP proxies in mind. It sets REMOTE_ADDR, HTTP_HOST from X-Forwarded headers.
Example:
from werkzeug.middleware.proxy_fix import ProxyFix
# App is behind one proxy that sets the -For and -Host headers.
app = ProxyFix(app, x_for=1, x_host=1)
Digging around Flask source code, I found out that url_for uses the Flask._request_ctx_stack.top.url_adapter when there is a request context.
The url_adapter.scheme defines the scheme used. To make the _scheme parameter work, url_for will swap the url_adapter.scheme temporarily and then set it back before the function returns.
(this behavior has been discussed on github as to whether it should be the previous value or PREFERRED_URL_SCHEME)
Basically, what I did was set url_adapter.scheme to https with a before_request handler. This way doesn't mess with the request itself, only with the thing generating the urls.
def _force_https():
# my local dev is set on debug, but on AWS it's not (obviously)
# I don't need HTTPS on local, change this to whatever condition you want.
if not app.debug:
from flask import _request_ctx_stack
if _request_ctx_stack is not None:
reqctx = _request_ctx_stack.top
reqctx.url_adapter.url_scheme = 'https'
app.before_request(_force_https)