Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix reverse proxy #57

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/docker/custom-container/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ RUN cp expfactory/config_dummy.py expfactory/config.py && \
chmod u+x /opt/expfactory/script/generate_key.sh && \
/bin/bash /opt/expfactory/script/generate_key.sh /opt/expfactory/expfactory/config.py
RUN python3 setup.py install
RUN python3 -m pip install pyaml pymysql psycopg2
RUN python3 -m pip install pyaml pymysql psycopg2 beautifulsoup4
RUN apt-get clean # tests, mysql, postgres

########################################
Expand Down
68 changes: 47 additions & 21 deletions expfactory/cli/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,21 @@

'''

import os
import sys
from expfactory.validator import ExperimentValidator
from expfactory.experiment import load_experiment
from expfactory.utils import (
get_viewsdir,
get_template,
run_command,
get_template,
sub_template,
save_template
)
from expfactory.logger import bot
import tempfile
import sys
import os
from bs4 import BeautifulSoup


def main(args,parser,subparser):
def main(args, parser, subparser):

folder = args.folder
if folder is None:
Expand All @@ -59,7 +58,7 @@ def main(args,parser,subparser):
# Is the experiment valid?
cli = ExperimentValidator()
valid = cli.validate(source, cleanup=False)
exp_id = os.path.basename(source).replace('.git','')
exp_id = os.path.basename(source).replace('.git', '')

if valid is True:

Expand All @@ -68,20 +67,20 @@ def main(args,parser,subparser):
config = load_experiment(source)
source = os.path.abspath(source)
else:
config = load_experiment("%s/%s" %(cli.tmpdir,exp_id))
source = "%s/%s" %(cli.tmpdir,exp_id)
config = load_experiment("%s/%s" % (cli.tmpdir, exp_id))
source = "%s/%s" % (cli.tmpdir, exp_id)

exp_id = config['exp_id']
python_module = exp_id.replace('-','_').lower()
python_module = exp_id.replace('-', '_').lower()
else:
bot.error('%s is not valid.' % exp_id)
sys.exit(1)

# Move static files to output folder
dest = "%s/%s" %(folder,exp_id)
dest = "%s/%s" % (folder, exp_id)

bot.log("Installing %s to %s" % (exp_id, dest))

bot.log("Installing %s to %s" %(exp_id, dest))

# Building container
in_container = False
if os.environ.get('SINGULARITY_IMAGE') is not None:
Expand All @@ -103,27 +102,54 @@ def main(args,parser,subparser):

# 1. Python blueprint
views = get_viewsdir(base=args.base)
view_output = "%s/%s.py" %(views, python_module)
view_output = "%s/%s.py" % (views, python_module)
save_template(view_output, template, base=views)

# 2. append to __init__
init = "%s/__init__.py" % views
with open(init,'a') as filey:
filey.writelines('from .%s import *\n' %python_module)
filey.writelines('from .%s import *\n' % python_module)

# 3. Instructions
if "instructions" in config:
instruct = "%s/%s.help" %(views, python_module)
with open(instruct,'w') as filey:
instruct = "%s/%s.help" % (views, python_module)
with open(instruct, 'w') as filey:
filey.writelines(config['instructions'])

if not os.path.exists(dest):
os.system('mkdir -p %s' %dest)
os.system('mkdir -p %s' % dest)
else:
if args.force is False:
bot.error('%s is not empty! Use --force to delete and re-create.' %folder)
sys.exit(1)
bot.error('%s is not empty! Use --force to delete and re-create.'
% folder)
sys.exit(1)

# We don't need to copy if experiment already there
if source != dest:
os.system('cp -R %s/* %s' %(source, dest))
os.system('cp -R %s/* %s' % (source, dest))

# Insert template into index.html
template = """
// Set SCRIPT_ROOT in case experiment factory is deployed at a
// non-root URL.
var SCRIPT_ROOT = {{ request.script_root|tojson|safe }};

var csrf_token = "{{ csrf_token() }}";

$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrf_token);
}
}
});
"""

with open("%s/%s" % (dest, "index.html"), "r+") as f:
index = BeautifulSoup(f, "html.parser")
script_tag = index.new_tag('script')
script_tag.string = template
exp_script = index.find("script", {"src": "experiment.js"})
exp_script.insert_before(script_tag)
f.seek(0)
f.write(str(index))
46 changes: 46 additions & 0 deletions expfactory/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,54 @@ def finish_experiment(self, session, exp_id):
EFServer.finish_user = finish_user
EFServer.restart_user = restart_user

class ReverseProxied(object):
'''Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind
this to a URL other than / and to an HTTP scheme that is
different than what is used locally.

In nginx:
location /myprefix {
proxy_pass http://192.168.0.1:5001;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /myprefix;
}

:param app: the WSGI application
'''
def __init__(self, app):
self.app = app

def __call__(self, environ, start_response):
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
if script_name:
environ['SCRIPT_NAME'] = script_name
path_info = environ['PATH_INFO']
if path_info.startswith(script_name):
environ['PATH_INFO'] = path_info[len(script_name):]

# scheme = environ.get('HTTP_X_SCHEME', '')
# if scheme:
# environ['wsgi.url_scheme'] = scheme

# server = environ.get('HTTP_X_FORWARDED_SERVER', '')
# if server:
# environ['HTTP_HOST'] = server

return self.app(environ, start_response)


# Import the fixer
from werkzeug.contrib.fixers import ProxyFix

app = EFServer(__name__)
app.config.from_object('expfactory.config')
# Use the fixer
app.wsgi_app = ProxyFix(app.wsgi_app, 2)
app.wsgi_app = ReverseProxied(app.wsgi_app)


# EXPERIMENTS #################################################################

Expand Down
2 changes: 1 addition & 1 deletion expfactory/templates/build/docker/Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ RUN cp expfactory/config_dummy.py expfactory/config.py && \
chmod u+x /opt/expfactory/script/generate_key.sh && \
/bin/bash /opt/expfactory/script/generate_key.sh /opt/expfactory/expfactory/config.py
RUN python3 setup.py install
RUN python3 -m pip install pyaml pymysql psycopg2
RUN python3 -m pip install pyaml pymysql psycopg2 beautifulsoup4
RUN apt-get clean # tests, mysql, postgres

########################################
Expand Down
2 changes: 1 addition & 1 deletion expfactory/templates/build/docker/Dockerfile.template
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ RUN cp expfactory/config_dummy.py expfactory/config.py && \
chmod u+x /opt/expfactory/script/generate_key.sh && \
/bin/bash /opt/expfactory/script/generate_key.sh /opt/expfactory/expfactory/config.py
RUN python3 setup.py install
RUN python3 -m pip install pyaml pymysql psycopg2
RUN python3 -m pip install pyaml pymysql psycopg2 beautifulsoup4
RUN apt-get clean # tests, mysql, postgres

########################################
Expand Down
12 changes: 0 additions & 12 deletions expfactory/templates/experiments/experiment.html
Original file line number Diff line number Diff line change
@@ -1,13 +1 @@
{% include experiment %}

<script type="text/javascript">
var csrf_token = "{{ csrf_token() }}";

$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrf_token);
}
}
});
</script>
4 changes: 2 additions & 2 deletions expfactory/templates/routes/start.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ <h4 class="js-title-step"></h4>
<h2>Welcome {{ session['username'] }}!</h2>
{% include "routes/start_message.html" %}
<br><br>
<a href="/finish"><button id="disagree_button"
<a href="finish"><button id="disagree_button"
type="button"
class="btn btn-default">Disagree</button></a>
</div>
<div class="modal-footer">
<a href="/next"><button type="button"
<a href="next"><button type="button"
id="next_button"
class="btn btn-default js-btn-step"
data-orientation="next">Start</button></a>
Expand Down
3 changes: 2 additions & 1 deletion expfactory/views/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
render_template,
request,
redirect,
url_for,
session
)

Expand Down Expand Up @@ -76,7 +77,7 @@ def portal():
flash('Participant ID: "%s" <br> Name %s <br> Randomize: "%s" <br> Experiments: %s' %
(subid, username, app.randomize,
str(form.exp_ids.data)))
return redirect('/start')
return redirect(url_for('start'))

# Submit but not valid
return render_template('portal/index.html', experiments=app.lookup,
Expand Down
5 changes: 3 additions & 2 deletions expfactory/views/headless.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
render_template,
request,
redirect,
url_for,
session
)

Expand Down Expand Up @@ -70,7 +71,7 @@ def login():
# If not headless, we don't need to login
if not app.headless:
app.logger.debug('Not running in headless mode, redirect to /start.')
redirect('/start')
redirect(url_for('start'))

subid = session.get('subid')
if not subid:
Expand All @@ -85,7 +86,7 @@ def login():
session['token'] = token

app.logger.info('Logged in user [subid] %s' %subid)
return redirect('/next')
return redirect(url_for('next'))


# Denied Entry for Headless
Expand Down
16 changes: 8 additions & 8 deletions expfactory/views/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
render_template,
request,
redirect,
url_for,
session
)

Expand Down Expand Up @@ -97,7 +98,7 @@ def home():
form = EntryForm()
session['experiments'] = [os.path.basename(x) for x in app.experiments] # list
return render_template('routes/entry.html', form=form)
return redirect('/next')
return redirect(url_for('next'))

return portal()

Expand All @@ -121,9 +122,8 @@ def save():
app.logger.info('Finished %s, %s remaining.' % (exp_id, len(experiments)))

# Note, this doesn't seem to be enough to trigger ajax success
return json.dumps({'success':True}), 200, {'ContentType':'application/json'}
return json.dumps({'success':False}), 403, {'ContentType':'application/json'}

return jsonify(success=True)
return jsonify(success=False)


@app.route('/next', methods=['POST', 'GET'])
Expand All @@ -138,10 +138,10 @@ def next():

if experiment is not None:
app.logger.debug('Next experiment is %s' % experiment)
return perform_checks('/experiments/%s' % experiment, do_redirect=True,
return perform_checks('experiments/%s' % experiment, do_redirect=True,
next=experiment)

return redirect('/finish')
return redirect(url_for('finish'))


# Reset/Logout
Expand All @@ -150,7 +150,7 @@ def logout():

# If the user has finished, clear session
clear_session()
return redirect('/')
return redirect(url_for('home'))


# Finish
Expand All @@ -167,7 +167,7 @@ def finish():
app.finish_user(subid)
clear_session()
return render_template('routes/finish.html')
return redirect('/')
return redirect(url_for('home'))


@app.route('/start')
Expand Down
9 changes: 5 additions & 4 deletions expfactory/views/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
flash,
render_template,
redirect,
url_for,
session
)

Expand All @@ -60,23 +61,23 @@ def perform_checks(template, do_redirect=False, context=None, next=None, quiet=F
# Headless mode requires token
if "token" not in session and app.headless is True:
flash('A token is required for these experiments.')
return redirect('/')
return redirect(url_for('home'))

# Update the user / log
if quiet is False:
app.logger.info("[router] %s --> %s for [subid] %s [username] %s" %(last, next, subid, username))

if username is None and app.headless is False:
flash('You must start a session before doing experiments.')
return redirect('/')
return redirect(url_for('home'))

if subid is None:
flash('You must have a participant identifier before doing experiments')
return redirect('/')
return redirect(url_for('home'))

if next is None:
flash('Congratulations, you have finished the battery!')
return redirect('/finish')
return redirect(url_for('finish'))

if do_redirect is True:
app.logger.debug('Redirecting to %s' %template)
Expand Down