# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. import os import logging from urlparse import urlparse, urlunparse from pyramid.events import NewRequest, subscriber from pyramid.static import static_view from pyramid.view import view_config from pyramid.renderers import render, render_to_response from pyramid.response import Response import mozsvc.config from tokenserver.util import _JSONError logger = logging.getLogger("syncserver") def includeme(config): """Install SyncServer application into the given Pyramid configurator.""" # Set the umask so that files are created with secure permissions. # Necessary for e.g. created-on-demand sqlite database files. os.umask(0077) # Sanity-check the deployment settings and provide sensible defaults. settings = config.registry.settings public_url = settings.get("syncserver.public_url") if public_url is None: raise RuntimeError("you much configure syncserver.public_url") public_url = public_url.rstrip("/") settings["syncserver.public_url"] = public_url secret = settings.get("syncserver.secret") if secret is None: secret = os.urandom(32).encode("hex") sqluri = settings.get("syncserver.sqluri") if sqluri is None: rootdir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) sqluri = "sqlite:///" + os.path.join(rootdir, "syncserver.db") # Configure app-specific defaults based on top-level configuration. settings.pop("config", None) if "tokenserver.backend" not in settings: # Default to our simple static node-assignment backend settings["tokenserver.backend"] =\ "syncserver.staticnode.StaticNodeAssignment" settings["tokenserver.sqluri"] = sqluri settings["tokenserver.node_url"] = public_url settings["endpoints.sync-1.5"] = "{node}/storage/1.5/{uid}" if "tokenserver.monkey_patch_gevent" not in settings: # Default to no gevent monkey-patching settings["tokenserver.monkey_patch_gevent"] = False if "tokenserver.applications" not in settings: # Default to just the sync-1.5 application settings["tokenserver.applications"] = "sync-1.5" if "tokenserver.secrets.backend" not in settings: # Default to a single fixed signing secret settings["tokenserver.secrets.backend"] = "mozsvc.secrets.FixedSecrets" settings["tokenserver.secrets.secrets"] = [secret] if "tokenserver.allow_new_users" not in settings: allow_new_users = settings.get("syncserver.allow_new_users") if allow_new_users is not None: settings["tokenserver.allow_new_users"] = allow_new_users if "hawkauth.secrets.backend" not in settings: # Default to the same secrets backend as the tokenserver for key in settings.keys(): if key.startswith("tokenserver.secrets."): newkey = "hawkauth" + key[len("tokenserver"):] settings[newkey] = settings[key] if "storage.backend" not in settings: # Default to sql syncstorage backend settings["storage.backend"] = "syncstorage.storage.sql.SQLStorage" settings["storage.sqluri"] = sqluri settings["storage.create_tables"] = True if "browserid.backend" not in settings: # Default to remote verifier, with base of public_url as only audience audience = urlunparse(urlparse(public_url)._replace(path="")) settings["browserid.backend"] = "tokenserver.verifiers.RemoteVerifier" settings["browserid.audiences"] = audience if "loggers" not in settings: # Default to basic logging config. root_logger = logging.getLogger("") if not root_logger.handlers: logging.basicConfig(level=logging.INFO) # Include the relevant sub-packages. config.scan("syncserver") config.include("syncstorage", route_prefix="/storage") config.include("tokenserver", route_prefix="/token") config.include('pyramid_chameleon') # Add a top-level explaination view. # First view, available at http://localhost:6543/ def page(request): result = render('page/index.pt', {'public_url':public_url}, request=request) response = Response(result) return response config.add_route('page', '/') config.add_view(page, route_name='page') www = static_view( os.path.realpath(os.path.dirname(__file__)+"/page/"), use_subpath=True ) # Documentation for Hybrid routing can be found here # http://docs.pylonsproject.org/projects/pyramid/en/1.0-branch/narr/hybrid.html#using-subpath-in-a-route-pattern config.add_route('index', '/*subpath', 'www') # subpath is a reserved word config.add_view(www, route_name='index') @subscriber(NewRequest) def reconcile_wsgi_environ_with_public_url(event): """Event-listener that checks and tweaks WSGI environ based on public_url. This is a simple trick to help ensure that the configured public_url matches the actual deployed address. It fixes fixes parts of the WSGI environ where it makes sense (e.g. SCRIPT_NAME) and warns about any parts that seem obviously mis-configured (e.g. http:// versus https://). It's very important to get public_url and WSGI environ matching exactly, since they're used for browserid audience checking and HAWK signature validation, so mismatches can easily cause strange and cryptic errors. """ request = event.request public_url = request.registry.settings["syncserver.public_url"] p_public_url = urlparse(public_url) # If we don't have a SCRIPT_NAME, take it from the public_url. # This is often the case if we're behind e.g. an nginx proxy that # is serving us at some sub-path. if not request.script_name: request.script_name = p_public_url.path.rstrip("/") # If the environ does not match public_url, requests are almost certainly # going to fail due to auth errors. We can either bail out early, or we # can forcibly clobber the WSGI environ with the values from public_url. # This is a security risk if you've e.g. mis-configured the server, so # it's not enabled by default. application_url = request.application_url if public_url != application_url: if not request.registry.settings.get("syncserver.force_wsgi_environ"): msg = "\n".join(( "The public_url setting doesn't match the application url.", "This will almost certainly cause authentication failures!", " public_url setting is: %s" % (public_url,), " application url is: %s" % (application_url,), "You can disable this check by setting the force_wsgi_environ", "option in your config file, but do so at your own risk.", )) logger.error(msg) raise _JSONError([msg], status_code=500) request.scheme = p_public_url.scheme request.host = p_public_url.netloc request.script_name = p_public_url.path.rstrip("/") def get_configurator(global_config, **settings): """Load a SyncStorge configurator object from deployment settings.""" config = mozsvc.config.get_configurator(global_config, **settings) config.begin() try: config.include(includeme) finally: config.end() return config def main(global_config, **settings): """Load a SyncStorage WSGI app from deployment settings.""" config = get_configurator(global_config, **settings) return config.make_wsgi_app()