FreedomBox/plinth.py
Nick Daly 68e186deff Finally fix firstboot redirect problems.
This issue was easy to solve once I started using the Live HTTP
Headers Firefox extension to debug it:

https://addons.mozilla.org/en-US/firefox/addon/live-http-headers/

With this particular InternalRedirect, CherryPy inexplicably responds
with a 301 ("Moved Permanently") unless we manually tell it to use a
307 ("Moved Temporarily") response code.  I don't know why CherryPy
does this, and it may be indicative of a deeper problem that I don't
currently have time to debug, as the request-response process for
redirecting from http://(server)/plinth to
https://(server)/plinth/firstboot is ridiculous and contains about
twice as many requests as I would've expected.
2013-12-08 11:46:33 -06:00

187 lines
6.7 KiB
Python
Executable File

#!/usr/bin/env python
import os, stat, sys, argparse
from gettext import gettext as _
import cfg
if not os.path.join(cfg.file_root, "vendor") in sys.path:
sys.path.append(os.path.join(cfg.file_root, "vendor"))
import cherrypy
from cherrypy import _cpserver
from cherrypy.process.plugins import Daemonizer
Daemonizer(cherrypy.engine).subscribe()
import plugin_mount
import util as u
from logger import Logger
#from modules.auth import AuthController, require, member_of, name_is
from withsqlite.withsqlite import sqlite_db
import socket
__version__ = "0.2.14"
__author__ = "James Vasile"
__copyright__ = "Copyright 2011-2013, James Vasile"
__license__ = "GPLv3 or later"
__maintainer__ = "James Vasile"
__email__ = "james@jamesvasile.com"
__status__ = "Development"
import urlparse
def error_page(status, dynamic_msg, stock_msg):
return u.page_template(template="err", title=status, main="<p>%s</p>%s" % (dynamic_msg, stock_msg))
def error_page_404(status, message, traceback, version):
return error_page(status, message, """<p>If you believe this
missing page should exist, please file a bug with either the Plinth
project (<a href="https://github.com/NickDaly/Plinth/issues">it has
an issue tracker</a>) or the people responsible for the module you
are trying to access.</p>
<p>Sorry for the mistake.</p>
""")
def error_page_500(status, message, traceback, version):
cfg.log.error("500 Internal Server Error. Trackback is above.")
more="""<p>This is an internal error and not something you caused
or can fix. Please report the error on the <a
href="https://github.com/jvasile/Plinth/issues">bug tracker</a> so
we can fix it.</p>"""
return error_page(status, message, "<p>%s</p><pre>%s</pre>" % (more, "\n".join(traceback.split("\n"))))
class Root(plugin_mount.PagePlugin):
@cherrypy.expose
def index(self):
## TODO: firstboot hijacking root should probably be in the firstboot module with a hook in plinth.py
with sqlite_db(cfg.store_file, table="firstboot") as db:
if not 'state' in db:
# if we created a new user db, make sure it can't be read by everyone
userdb_fname = '{}.sqlite3'.format(cfg.user_db)
os.chmod(userdb_fname, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
# cherrypy.InternalRedirect throws a 301, causing the
# browser to cache the redirect, preventing the user from
# navigating to /plinth until the browser is restarted.
raise cherrypy.HTTPRedirect('firstboot', 307)
elif db['state'] < 5:
cfg.log("First Boot state = %d" % db['state'])
raise cherrypy.InternalRedirect('firstboot/state%d' % db['state'])
if cherrypy.session.get(cfg.session_key, None):
raise cherrypy.InternalRedirect('router')
else:
raise cherrypy.InternalRedirect('help/about')
def load_modules():
"""Import all the symlinked .py files in the modules directory and
all the .py files in directories linked in the modules directory
(but don't dive deeper than that). Also, ignore the installed
directory."""
for name in os.listdir("modules"):
if name.endswith(".py") and not name.startswith('.'):
cfg.log.info("importing modules/%s" % name)
try:
__import__("modules.%s" % (name[:-3]))
except ImportError, e:
cfg.log.error(_("Couldn't import modules/%s: %s") % (name, e))
else:
cfg.log("skipping %s" % name)
def parse_arguments():
parser = argparse.ArgumentParser(description='Plinth web interface for the FreedomBox.')
parser.add_argument('--pidfile',
help='specify a file in which the server may write its pid')
# FIXME make this work with basehref for static files.
parser.add_argument('--server_dir',
help='specify where to host the server.')
parser.add_argument("--debug", action="store_true",
help="Debug flag. Don't use.")
args=parser.parse_args()
set_config(args, "pidfile", "plinth.pid")
set_config(args, "server_dir", "/")
set_config(args, "debug", False)
return cfg
def set_config(args, element, default):
"""Sets *cfg* elements based on *args* values, or uses a reasonable default.
- If values are passed in from *args*, use those.
- If values are read from the config file, use those.
- If no values have been given, use default.
"""
try:
# cfg.(element) = args.(element)
setattr(cfg, element, getattr(args, element))
except AttributeError:
# if it fails, we didn't receive that argument.
try:
# if not cfg.(element): cfg.(element) = default
if not getattr(cfg, element):
setattr(cfg, element, default)
except AttributeError:
# it wasn't in the config file, but set the default anyway.
setattr(cfg, element, default)
def setup():
cfg = parse_arguments()
try:
if cfg.pidfile:
from cherrypy.process.plugins import PIDFile
PIDFile(cherrypy.engine, cfg.pidfile).subscribe()
except AttributeError:
pass
os.chdir(cfg.python_root)
cherrypy.config.update({'error_page.404': error_page_404})
cherrypy.config.update({'error_page.500': error_page_500})
cfg.log = Logger()
load_modules()
cfg.html_root = Root()
cfg.users = plugin_mount.UserStoreModule.get_plugins()[0]
cfg.page_plugins = plugin_mount.PagePlugin.get_plugins()
cfg.log("Loaded %d page plugins" % len(cfg.page_plugins))
cfg.forms = plugin_mount.FormPlugin.get_plugins()
# Add an extra server
server = _cpserver.Server()
server.socket_host = '127.0.0.1'
server.socket_port = 52854
server.subscribe()
# Configure default server
cherrypy.config.update(
{'server.socket_host': cfg.host,
'server.socket_port': cfg.port,
'server.thread_pool':10,
'tools.staticdir.root': cfg.file_root,
'tools.sessions.on':True,
'tools.auth.on':True,
'tools.sessions.storage_type':"file",
'tools.sessions.timeout':90,
'tools.sessions.storage_path':"%s/cherrypy_sessions" % cfg.data_dir,})
config = {
'/': {'tools.staticdir.root': '%s/static' % cfg.file_root,
'tools.proxy.on': True,},
'/static': {'tools.staticdir.on': True,
'tools.staticdir.dir': "."},
'/favicon.ico':{'tools.staticfile.on': True,
'tools.staticfile.filename':
"%s/static/theme/favicon.ico" % cfg.file_root}}
cherrypy.tree.mount(cfg.html_root, cfg.server_dir, config=config)
cherrypy.engine.signal_handler.subscribe()
def main():
setup()
cherrypy.engine.start()
cherrypy.engine.block()
if __name__ == '__main__':
main()