Exemple #1
0
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)

    # If PyOpenSSL is available, configure requests to use it.
    # This helps improve security on older python versions.
    if HAS_PYOPENSSL:
        requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3()

    settings = config.registry.settings
    import_settings_from_environment_variables(settings)

    # Sanity-check the deployment settings and provide sensible defaults.
    public_url = settings.get("syncserver.public_url")
    if public_url is None:
        raise RuntimeError("you must 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")

    # Automagically configure from IdP if one is given.
    idp = settings.get("syncserver.identity_provider")
    if idp is not None:
        r = requests.get(urljoin(idp, '/.well-known/fxa-client-configuration'))
        r.raise_for_status()
        idp_config = r.json()

    # 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"] = DEFAULT_TOKENSERVER_BACKEND
    if settings["tokenserver.backend"] == DEFAULT_TOKENSERVER_BACKEND:
        # Provide some additional defaults for the default backend,
        # unless overridden in the config.
        if "tokenserver.sqluri" not in settings:
            settings["tokenserver.sqluri"] = sqluri
        if "tokenserver.node_url" not in settings:
            settings["tokenserver.node_url"] = public_url
        if "endpoints.sync-1.5" not in settings:
            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 "storage.batch_upload_enabled" not in settings:
        settings["storage.batch_upload_enabled"] = True
    if "browserid.backend" not in settings:
        # Default to local verifier to reduce external dependencies.
        settings["browserid.backend"] = "tokenserver.verifiers.LocalVerifier"
        # Use base of public_url as only audience
        audience = urlunparse(urlparse(public_url)._replace(path=""))
        settings["browserid.audiences"] = audience
        # If an IdP was specified, allow it and only it as issuer.
        if idp is not None:
            issuer = urlparse(idp_config["auth_server_base_url"]).netloc
            settings["browserid.trusted_issuers"] = [issuer]
            settings["browserid.allowed_issuers"] = [issuer]
    if "oauth.backend" not in settings:
        settings["oauth.backend"] = "tokenserver.verifiers.RemoteOAuthVerifier"
        # If an IdP was specified, use it for oauth verification.
        if idp is not None:
            settings["oauth.server_url"] = idp_config["oauth_server_base_url"]
    if "loggers" not in settings:
        # Default to basic logging config.
        root_logger = logging.getLogger("")
        if not root_logger.handlers:
            logging.basicConfig(level=logging.WARN)
    if "fxa.metrics_uid_secret_key" not in settings:
        # Default to a randomly-generated secret.
        # This setting isn't useful in a self-hosted setup
        # and setting a default avoids scary-sounding warnings.
        settings["fxa.metrics_uid_secret_key"] = os.urandom(16).encode("hex")

    # Include the relevant sub-packages.
    config.scan("syncserver", ignore=["syncserver.wsgi_app"])
    config.include("syncstorage", route_prefix="/storage")
    config.include("tokenserver", route_prefix="/token")

    # Add a top-level "it works!" view.
    def itworks(request):
        return Response("it works!")

    config.add_route('itworks', '/')
    config.add_view(itworks, route_name='itworks')
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")

    # Add a top-level "it works!" view.
    def itworks(request):
        return Response("it works!")

    config.add_route('itworks', '/')
    config.add_view(itworks, route_name='itworks')
Exemple #3
0
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)

    # If PyOpenSSL is available, configure requests to use it.
    # This helps improve security on older python versions.
    if HAS_PYOPENSSL:
        requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3()

    # 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")

    # Add a top-level "it works!" view.
    def itworks(request):
        return Response("it works!")

    config.add_route('itworks', '/')
    config.add_view(itworks, route_name='itworks')
Exemple #4
0
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 "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 public_url as only audience
        settings["browserid.backend"] = "tokenserver.verifiers.RemoteVerifier"
        settings["browserid.audiences"] = public_url
    if "metlog.backend" not in settings:
        # Default to sending metlog output to stdout.
        settings["metlog.backend"] = "mozsvc.metrics.MetlogPlugin"
        settings["metlog.sender_class"] = "metlog.senders.StdOutSender"
        settings["metlog.enabled"] = True
    if "cef.use" not in settings:
        # Default to sensible CEF logging settings
        settings["cef.use"] = False
        settings["cef.file"] = "syslog"
        settings["cef.vendor"] = "mozilla"
        settings["cef.version"] = 0
        settings["cef.device_version"] = 0
        settings["cef.product"] = "syncserver"

    # Include the relevant sub-packages.
    config.scan("syncserver")
    config.include("syncstorage", route_prefix="/storage")
    config.include("tokenserver", route_prefix="/token")

    # Add a top-level "it works!" view.
    def itworks(request):
        return Response("it works!")

    config.add_route('itworks', '/')
    config.add_view(itworks, route_name='itworks')
Exemple #5
0
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')
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(0o077)

    # If PyOpenSSL is available, configure requests to use it.
    # This helps improve security on older python versions.
    if HAS_PYOPENSSL:
        requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3()

    settings = config.registry.settings
    import_settings_from_environment_variables(settings)

    # Sanity-check the deployment settings and provide sensible defaults.
    public_url = settings.get("syncserver.public_url")
    if public_url is None:
        raise RuntimeError("you must 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 = generate_random_hex_key(64)
    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")

    # Automagically configure from IdP if one is given.
    idp = settings.get("syncserver.identity_provider")
    if idp is not None:
        r = requests.get(urljoin(idp, '/.well-known/fxa-client-configuration'))
        r.raise_for_status()
        idp_config = r.json()
        idp_issuer = urlparse(idp_config["auth_server_base_url"]).netloc

    # 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"] = DEFAULT_TOKENSERVER_BACKEND
    if settings["tokenserver.backend"] == DEFAULT_TOKENSERVER_BACKEND:
        # Provide some additional defaults for the default backend,
        # unless overridden in the config.
        if "tokenserver.sqluri" not in settings:
            settings["tokenserver.sqluri"] = sqluri
        if "tokenserver.node_url" not in settings:
            settings["tokenserver.node_url"] = public_url
        if "endpoints.sync-1.5" not in settings:
            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 "storage.batch_upload_enabled" not in settings:
        settings["storage.batch_upload_enabled"] = True
    if "browserid.backend" not in settings:
        # Default to local verifier to reduce external dependencies,
        # unless an explicit verifier URL has been configured.
        verifier_url = settings.get("syncserver.browserid_verifier")
        if not verifier_url:
            settings["browserid.backend"] = \
                "tokenserver.verifiers.LocalBrowserIdVerifier"
        else:
            settings["browserid.backend"] = \
                "tokenserver.verifiers.RemoteBrowserIdVerifier"
            settings["browserid.verifier_url"] = verifier_url
        # Use base of public_url as only audience
        audience = urlunparse(urlparse(public_url)._replace(path=""))
        settings["browserid.audiences"] = audience
        # If an IdP was specified, allow it and only it as issuer.
        if idp is not None:
            settings["browserid.trusted_issuers"] = [idp_issuer]
            settings["browserid.allowed_issuers"] = [idp_issuer]
    if "oauth.backend" not in settings:
        settings["oauth.backend"] = "tokenserver.verifiers.RemoteOAuthVerifier"
        # If an IdP was specified, use it for oauth verification.
        if idp is not None:
            settings["oauth.server_url"] = idp_config["oauth_server_base_url"]
            settings["oauth.default_issuer"] = idp_issuer
    if "loggers" not in settings:
        # Default to basic logging config.
        root_logger = logging.getLogger("")
        if not root_logger.handlers:
            logging.basicConfig(level=logging.WARN)
    if "fxa.metrics_uid_secret_key" not in settings:
        # Default to a randomly-generated secret.
        # This setting isn't useful in a self-hosted setup
        # and setting a default avoids scary-sounding warnings.
        settings["fxa.metrics_uid_secret_key"] = generate_random_hex_key(32)

    # Include the relevant sub-packages.
    config.scan("syncserver", ignore=["syncserver.wsgi_app"])
    config.include("syncstorage", route_prefix="/storage")
    config.include("tokenserver", route_prefix="/token")

    # Add a top-level "it works!" view.
    def itworks(request):
        return Response("it works!")

    config.add_route('itworks', '/')
    config.add_view(itworks, route_name='itworks')