I have an example of a CSRF protected form that runs perfectly in the development environment (Flask runs the server itself with app.run) but fails when I run the app via mod_wsgi in Apache. The versions I use are:
Server version: Apache/2.4.4 (Unix)
Python 2.7.3
Flask==0.10.1
Flask-WTF==0.9.5
WTForms==2.0
Flask-KVSession==0.4
simplekv==0.8.4
The reason that it fails is a csrf_token mismatch during form validation. I log the contents of the flask.session and flask.request.form at the beginning of the view and the contents of the session again at the end of the view. In development mode the content of the csrf_token in the session stays constant across multiple requests, for example,
<KVSession {'csrf_token': '79918c1e3191e4d4fe89a9499f576404a18be8e4'}>
The contents of the form are transmitted correctly in both cases, e.g.,
ImmutableMultiDict([('csrf_token', u'1403778775.86##34f1447f1b8c78808f4e71f2ff037bcd1df41dcd'),
('time', u'8'), ('submit', u'Go'), ('dose', u'Low')])
When I run my app via Apache the session contents are reset with each request. At the beginning of the view the session contents are empty:
<KVSession {}>
and then a new token is set each time which leads to the mismatch. Currently, my __init__.py module looks as follows:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from simplekv.memory import DictStore
from flaskext.kvsession import KVSessionExtension
app = Flask(__name__)
app.config.from_object("myapp.config.Config")
db = SQLAlchemy(app)
store = DictStore()
KVSessionExtension(store, app)
from . import views
I removed the KVSession statements and that didn't change the problem. So I think server side sessions are not the culprit.
And yes, I have set the SECRET_KEY to os.urandom(128) in the config.
The relevant (I think) section of my httpd.conf is:
Listen url.com:8090
<VirtualHost url.com:8090>
# --- Configure VirtualHost ---
LogLevel debug
ServerName url.com
DocumentRoot /path/to/flaskapp/htdocs
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /path/to/flaskapp/htdocs/>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Require all granted
</Directory>
# --- Configure WSGI Listening App(s) ---
WSGIDaemonProcess mysite user=me group=us processes=2 threads=10
WSGIScriptAlias / /path/to/flaskapp/wsgi/wsgi.py
<Directory /path/to/flaskapp/wsgi/>
WSGIProcessGroup mysite
WSGIApplicationGroup %{GLOBAL}
WSGIScriptReloading On
Require all granted
</Directory>
# --- Configure Static Files ---
Alias /static/ /path/to/flaskapp/htdocs/static/
Alias /tmp/ /path/to/flaskapp/htdocs/tmp/
</VirtualHost>
Does anyone know about Apache settings or mod_wsgi with Flask interactions that could cause the session not to persist between requests?
What happens here is that you store your sessions using Flask-KVSession, and provide a memory based DictStore as a storage:
from simplekv.memory import DictStore
store = DictStore()
KVSessionExtension(store, app)
Root cause
In a single-threaded environment, this will work. However, when multiple processes comes into play, they do not share the same memory, and multiple instances of DictStore are created, one per process. As a result, when two subsequent requests are served by two different processes, first request will not be able to pass session changes to a next request.
Or, even shorter: Two processes = two CSRF tokens. Not good.
Solution
Use a persistent storage. This is what I use:
def configure_session(app):
with app.app_context():
if config['other']['local_debug']:
store = simplekv.memory.DictStore()
else:
store = simplekv.db.sql.SQLAlchemyStore(engine, metadata, 'sessions')
# Attach session store
flask_kvsession.KVSessionExtension(store, app)
Related
in development mode with Flask, I am successfully able to read and write data to Firestore using the firebase admin module. However, once I deploy the app on Apache 2 using mod_wsgi, everything works fine (firebase app initialization doesn't throw any error) until I start getting a document, where the code gets stuck and the page loads forever.
Flask function where the problem occurs in __init__.py:
#app.route('/users/<user>')
def login(user=None):
if not user:
abort(404)
userRef = db.collection('users').document(user)
print("1") # This runs.
userDoc = userRef.get() # The code gets stuck here. No error message is ever returned or stored in the error log.
print("2") # This never runs.
flaskapp.wsgi
#!/usr/local/bin/python3.8
import sys
import os
import logging
logging.basicConfig(stream=sys.stderr)
os.chdir("/var/www/html/example.com/FlaskApp/")
sys.path.insert(0,"/var/www/html/example.com/FlaskApp/")
from __init__ import app as application
example.com.conf
LoadModule wsgi_module "/home/username/.local/lib/python3.8/site-packages/mod_wsgi/server/mod_wsgi-py38.cpython-38-x86_64-linux-gnu.so"
WSGIPythonHome "/usr/local"
<VirtualHost *:80>
# Admin email, Server Name (domain name), and any aliases
ServerAdmin username#example.com
ServerName example.com
ServerAlias www.example.com
# Index file and Document Root (where the public files are located)
DirectoryIndex index.html index.php
DocumentRoot /var/www/html/example.com/public_html
WSGIScriptAlias / /var/www/html/example.com/FlaskApp/flaskapp.wsgi
<Directory /var/www/html/example.com/FlaskApp/>
Require all granted
</Directory>
Alias /static /var/www/html/example.com/FlaskApp/static
<Directory /var/www/html/example.com/FlaskApp/static/>
Require all granted
</Directory>
# Log file locations
LogLevel warn
ErrorLog /var/www/html/example.com/log/error.log
CustomLog /var/www/html/example.com/log/access.log combined
</VirtualHost>
Do you know how to make Firebase get() works in a deployed Flask app? Thank you for your help!
Seems like WSGI needs to be forced to use the main Python interpreter. Therefore, you'll need to add application-group=%{GLOBAL} to the end of your ``WSGIScriptAlias line on example.com.conf, so that the line now reads:
WSGIScriptAlias / /var/www/html/example.com/FlaskApp/flaskapp.wsgi application-group=%{GLOBAL}
After that, restart Apache with the terminal command systemctl restart apache and your problem should be solved.
I am attempting to deploy a 2nd flask application to an apache webserver. I have added a 2nd listener to the httpd.conf file for 127.0.0.1:8868 because I am accessing the api through a gateway located on my machine. The first app I deployed was set up in a very similar fashion and works perfectly. The flask app works fine when launched from the .py file. I have attached excerpts from the httdpd.conf file, httpd-vhosts.conf file, and the web.wsgi file. It doesn't record any errors in the apache errors log and or the access logs. It will record it in the access and error logs if I have an error in the .wsgi file. Having both listeners enabled also seems to interfere with the first application listening on port 5000.
httpd listeners:
Listen 127.0.0.1:5000
Listen 127.0.0.1:8868
httpd-vhosts.conf:
<VirtualHost *:5000>
WSGIScriptAlias / C:/Users/me/web_apps/task_manager/app/index/web.wsgi
<Directory C:/Users/me/web_apps/task_manager>
Require all granted
</Directory>
</VirtualHost>
<VirtualHost *:8868>
WSGIScriptAlias / C:/Users/me/web_apps/template_autoupdate/app/index/web.wsgi
<Directory C:/Users/me/web_apps/template_autoupdate>
Require all granted
</Directory>
</VirtualHost>
web.wsgi
import sys
sys.path.insert(0, "C:/Users/me/web_apps/template_autoupdate/app")
from autoupdater import app as application
Here's the issue.
I am trying to deploy my flask app in apache2.4 Server using mod_wsgi.After configuration,my apache server start to run on my computer.But when I visit http://127.0.0.1:5000/ the page doesn't display as my wish.
Here's my flask code.
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello_world():
return "Hello World!"
if __name__ == '__main__':
app.run(port=5000)
And here's my virtual host config.
<VirtualHost *:5000>
ServerAdmin example#company.com
DocumentRoot C:\flask
WSGIScriptAlias / C:\flask\test.wsgi
<Directory "C:\flask">
Require all granted
Require host ip
Allow from all
</Directory>
</VirtualHost>
My wsgi code.
import sys
#Expand Python classes path with your app's path
sys.path.insert(0, "C:/flask")
from test import app as application
#Put logging code (and imports) here ...
#Initialize WSGI app object
application = app
The page is like this:
It says 'Internal Server Error'.
Thank everyone!
remove the code
#Initialize WSGI app object
application = app
maybe it works
first remove last line from test.wsgi file.
(i.e application = app)
then check http://127.0.0.1:5000/, if it works then good,
if not then, add the following lines at the bottom of httpd.conf file.
WSGIScriptAlias / C:\flask\test.wsgi
<Directory C:\flask>
Require all granted
</Directory>
then check again http://127.0.0.1:5000/. it will work definitely.
I am making a request within a request, in order to accommodate my service oriented design. Locally, this code works. However on production using ubuntu libapache2-mod-wsgi-py3, my nested request fails.
A key point is both services exist within the same <Virtualhost> on the server, so I suspect this is a threading issue.
The second request called within newitem() fails with ConnectionRefusedError(111, 'Connection refused') on my WSGI Ubuntu box even though the API endpoint functions as expected when I POST to it outside of this context (for example curl POST to the exact url).
Another important point is this request within reqest design works on my local when I have two separate flask apps running on different ports. I had this exact issue on my local until I ran the API server with --threaded flag.
My server is configured as follows:
app2.wsgi:
#!env/bin/python3
import sys, os
sys.path.append('/var/www/api_app')
from app import create_app
from flask.ext.script import Manager
from flask.ext.migrate import Migrate, MigrateCommand
application = create_app(os.getenv('FLASK_CONFIG') or 'default')
if __name__ == "__main__":
manager.run()
apache2/sites-available/default.conf:
<VirtualHost *:80>
DocumentRoot /var/www/html
# ------ WSGI config ------
WSGIDaemonProcess app1 user=www-data group=www-data threads=5
WSGIDaemonProcess app2 user=www-data group=www-data threads=5
WSGIScriptAlias /front_end /var/www/front_end/app2.wsgi
WSGIScriptAlias /api_app /var/www/api_app/app2.wsgi
WSGIScriptReloading On
<Directory /var/www/api_app>
WSGIProcessGroup app2
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
<Directory /var/www/front_end>
WSGIProcessGroup app1
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
</VirtualHost>
The following code works great on my local environment when I start the API service with the --threaded flag It fails on WSGI Apache2 on the requests.post() line.
#plant_material.route('/plant_material/new', methods=['GET', 'POST'])
def newitem():
form = PlantMaterialForm()
if form.validate_on_submit():
api_uri = current_app.config['API_SERVER_URI']
url = api_uri + "api/plant_material/new"
headers = {'Content-Type': 'application/json'}
print(request.form)
r = requests.post(url, data=json.dumps(request.form), headers=headers)
if r.status_code == 200:
return redirect(url_for('plant_material.index'))
elif r.status_code == 401:
flash('Whiteboard API Unauthorized')
elif r.status_code == 403:
flash('Whiteboard API Forbidden')
elif r.status_code == 500:
flash('Internal Server Error')
return render_template('plant_material/new.html', form=form)
How can I make a request within a request on my production server?
Apache and mod_wsgi take care of concurrency here; there is no reason for a well-formed request from one WSGI app to another on the same server to fail here.
Make sure that there is no firewall blocking requests to the server from localhost. Test the same URL from the command line with curl for example.
Triple-check your assumptions; use the live value of current_app.config['API_SERVER_URI'] when testing if your connections work, not what you assume it to be.
swap out HTTP methods; try running curl from a subprocess call, or use urllib2 to access the API
I'm using Mercurial 1.7 and Apache 2.2.3. I'm trying to use the hgwebdir.cgi script to authenticate and serve my repositories, which are located at /var/lib/mercurial-server/repos.
Although the authentication works, the webpage does not show any of the repositories.
This is my /var/www/cgi-hg/hgwebdir.cgi:
config = "/var/lib/mercurial-server/repos/"
import sys; sys.path.insert(0, "/usr/lib64/python2.4/")
import cgitb; cgitb.enable()
from mercurial import demandimport; demandimport.enable()
from mercurial.hgweb import hgweb, wsgicgi
application = hgweb(config)
wsgicgi.launch(application)
This is my /var/www/cgi-hg/hgwebdir.config:
[collections]
/var/lib/mercurial-server/repos=/var/lib/mercurial-server/repos
[web]
allow_push = *
style = gitweb
push_ssl = False
This is my /etc/httpd/conf/httpd.conf (parts where changes were made):
DocumentRoot "/var/www/cgi-hg"
<Directory />
Options ExecCGI FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</Directory>
<Directory "/var/www/cgi-hg">
Options ExecCGI Indexes FollowSymLinks
AllowOverride None
</Directory>
DirectoryIndes index.html index.html.var hgwebdir.cgi
ScriptAlias /hg "/var/www/cgi-hg/hgwebdir.cgi"
<Location /hg>
AuthType Basic
AuthName "Login Required"
AuthUserFile /usr/local/etc/users
Require valid-user
</Location>
Using config = "/var/lib/mercurial-server/repos/" and config = "/var/hg/hgwebdir.config" in hgwebdir.cgi gives me the empty repository page. Even though there is NO hgwebdir.config in /var/hg/.
Using config = "/var/www/cgi-hg/hgwebdir.config" gives me a page showing OSError. Part of the page shows:
/var/www/cgi-hg/hgwebdir.cgi
(highlighted) 22 application = hgweb(config)
application undefined, hgweb = <function hgweb>, config = '/var/www/cgi-hg/hgwebdir.config'
/usr/lib64/python2.4/site-packages/mercurial/hgweb/__init__.py in hgweb(config='/var/www/cgi-hg/hgwebdir.config', name=None, baseui=None)
(highlighted) 26 return hgwebdir_mod.hgwebdir(config, baseui=baseui)
...
I also noticed that whenever I restart my httpd, I get the 2 messages:
Starting httpd: [date time] [warn] The ScriptAlias directive in /etc/httpd/conf/httpd.conf at line 570 will probably never match because it overlaps an earlier ScriptAliasMatch.
httpd: Could not reliably determine the server's fully qualified domain name, using <IP address> for ServerName
There is no ScriptAliasMatch in my httpd.conf.
When I point my browser to /hg, I'm asked to authenticate, then I either get the empty repository page, or the Python errors, depending on which config I use in the hgwebdir.cgi.
If I use "hg serve --webdir-conf /var/www/cgi-hg/hgwebdir.config", all my repositories show up correctly.
I'm very new to apache, so I'm sure I've gotten something wrong. Please advise.
Thank you.
I don't know about the ScriptAlias warning, but I think line of your /var/www/cgi-hg/hgwebdir.cgi file should be changed from the current:
config = "/var/lib/mercurial-server/repos/"
to
config = "/var/www/cgi-hg/hgwebdir.config"
When you're serving a single repo it's the path to that repo, and when you're serving multiple files it's the path to the hgweb configuration file.
You can make sure that it's reading your hgwebdir.config file by changing the style to something very noticable like coal (which is dark gray). If you don't see that change then it's just running with defaults.
Once you get things going you should lock down that Apache config a bit too. One's DocumentRoot is usually soemthing other than the directory containing the CGIs (you don't want people trolling around the areas outside of /hg) and similarly you shouldn't have ExecCGI option enabled for the whole files system (Directory /) as a general rule.
First, though, make sure it's actually reading your hgwebdir.config file and then work on that.