You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
7.6 KiB

10 years ago
# 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
8 years ago
from pyramid.view import view_config
from pyramid.renderers import render, render_to_response
from pyramid.response import Response
10 years ago
import mozsvc.config
10 years ago
from tokenserver.util import _JSONError
logger = logging.getLogger("syncserver")
10 years ago
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")
8 years ago
config.include('pyramid_chameleon')
10 years ago
# Add a top-level explaination view.
8 years ago
# 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')
10 years ago
@subscriber(NewRequest)
10 years ago
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://).
10 years ago
10 years ago
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.
10 years ago
"""
request = event.request
public_url = request.registry.settings["syncserver.public_url"]
10 years ago
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.
10 years ago
if not request.script_name:
10 years ago
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.
10 years ago
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("/")
10 years ago
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()