module.config.php #1

  • //
  • guest/
  • thomas_gray/
  • jambox/
  • main/
  • swarm/
  • module/
  • Application/
  • config/
  • module.config.php
  • View
  • Commits
  • Open Download .zip Download (27 KB)
<?php
/**
 * Perforce Swarm
 *
 * @copyright   2012 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */

return array(
    'environment' => array(
        'mode'      => getenv('SWARM_MODE') ?: 'production',
        'hostname'  => getenv('SWARM_HOST') ?: null
    ),
    'http_client_options' => array(
        'timeout'   => 5,
        'hosts'     => array()              // optional, per-host overrides; host as key, array of options as value
    ),
    'session' => array(
        'cookie_lifetime'            => 0,          // session cookie lifetime to use when remember me isn't checked
        'remembered_cookie_lifetime' => 30*24*60*60 // session cookie lifetime to use when remember me is checked
    ),
    'security' => array(
        'require_login'          => false,  // if enabled only the login screen will be accessible for anonymous users
        'disable_autojoin'       => false,  // if enabled user will not auto-join the swarm group on login
        'https_strict'           => false,  // if enabled, we'll tell clients to pin on https for 30 days
        'https_strict_redirect'  => true,   // if both https_strict and this are enabled; we meta-refresh HTTP to HTTPS
        'https_port'             => null,   // optionally, specify a non-standard port to use for https
        'emulate_ip_protections' => true,   // if enabled, ip-based protections matching user's remote ip are applied
        'disable_system_info'    => false,  // if enabled, system info is disabled (results in a 403 if accessed)
        'csrf_exempt'            => array('goto')
    ),
    'git_fusion' => array(
        'depot' => '.git-fusion',
        'user'  => 'git-fusion-user',
        'reown' => array(                   // git-fusion commits as its user then re-owns the change to the real author
            'retries'  => 20,               // we'll retry processing up to this many times to get the actual author
            'max_wait' => 60                // the delay between tries starts at 2 seconds and grows up to this limit
        )
    ),
    'css' => array(
        '/build/min.css' => array(
            '/vendor/bootstrap/css/bootstrap.min.css',
            '/vendor/prettify/prettify.css',
            '/swarm/css/style.css'
        )
    ),
    'p4' => array(
        'slow_command_logging'  => array(
            3,    // commands without a specific rule get a 3 second limit
            10 => array('print', 'shelve', 'submit', 'sync', 'unshelve')
        ),
        'max_changelist_files'  => 1000 // limit the number of files displayed in a change or a review
    ),
    'js' => array(
        '/build/min.js' => array(
            '/vendor/jquery/jquery-1.11.1.min.js',
            '/vendor/jquery-sortable/jquery-sortable-min.js',
            '/vendor/bootstrap/js/bootstrap.min.js',
            '/vendor/diff_match_patch/diff_match_patch.js',
            '/vendor/jquery.expander/jquery.expander.min.js',
            '/vendor/jquery.timeago/jquery.timeago.js',
            '/vendor/jsrender/jsrender.js',
            '/vendor/prettify/prettify.js',
            '/vendor/jed/jed.js',
            '/swarm/js/jquery-plugins.js',
            '/swarm/js/bootstrap-extensions.js',
            '/swarm/js/application.js',
            '/swarm/js/activity.js',
            '/swarm/js/users.js',
            '/swarm/js/projects.js',
            '/swarm/js/files.js',
            '/swarm/js/changes.js',
            '/swarm/js/comments.js',
            '/swarm/js/attachments.js',
            '/swarm/js/reviews.js',
            '/swarm/js/jobs.js',
            '/swarm/js/3dviewer.js',
            '/swarm/js/i18n.js',
            '/swarm/js/init.js'
        )
    ),
    'router' => array(
        'routes' => array(
            'about' => array(
                'type' => 'Zend\Mvc\Router\Http\Segment',
                'options' => array(
                    'route'    => '/about[/]',
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'about',
                    ),
                ),
            ),
            'goto'  => array(
                'type' => 'Application\Router\Regex',
                'options' => array(
                    'regex'    => '/(@+)?(?P<id>.+)',
                    'spec'     => '/@%id%',
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'goto',
                        'id'         => null
                    ),
                ),
                'priority' => -1000     // we'll catch anything that falls through by setting a late priority
            ),
            'info' => array(
                'type' => 'Zend\Mvc\Router\Http\Segment',
                'options' => array(
                    'route'    => '/info[/]',
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'info',
                    ),
                ),
            ),
            'archive' => array(
                'type' => 'Zend\Mvc\Router\Http\Segment',
                'options' => array(
                    'route'    => '/info/archive[/]',
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'archive',
                    ),
                ),
            ),
            'log' => array(
                'type' => 'Zend\Mvc\Router\Http\Segment',
                'options' => array(
                    'route'    => '/info/log[/]',
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'log',
                    ),
                ),
            ),
            'phpinfo' => array(
                'type' => 'Zend\Mvc\Router\Http\Segment',
                'options' => array(
                    'route'    => '/info/phpinfo[/]',
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'phpinfo',
                    ),
                ),
            ),
            'upgrade' => array(
                'type' => 'Zend\Mvc\Router\Http\Segment',
                'options' => array(
                    'route'    => '/upgrade[/]',
                    'defaults' => array(
                        'controller' => 'Reviews\Controller\Index',
                        'action'     => 'upgrade',
                    ),
                ),
            ),
        ),
    ),
    'controllers' => array(
        'invokables' => array(
            'Application\Controller\Index' => 'Application\Controller\IndexController'
        ),
    ),
    'service_manager' => array(
        'aliases'   => array(
            'translator' => 'MvcTranslator',
        ),
        'factories' => array(
            'logger'    => function ($services) {
                // @todo    update to use logger factory when available
                //          see PR #2725 (milestone 2.1.0)
                $config = $services->get('config');
                $logger = new Zend\Log\Logger;
                $file   = isset($config['log']['file']) ? $config['log']['file'] : null;

                // if a file was specified but doesn't exist attempt to create
                // it (unless we are running on the command line).
                // for cli usage we don't want to risk the log being owned by
                // a user other than the web-server so we won't touch it here.
                if ($file && !file_exists($file) && php_sapi_name() !== 'cli') {
                    touch($file);
                }

                // if a writable file was specified use it, otherwise just use null
                if ($file && is_writable($file)) {
                    $writer = new Zend\Log\Writer\Stream($file);
                    if (isset($config['log']['priority'])) {
                        $writer->addFilter((int) $config['log']['priority']);
                    }
                    $logger->addWriter($writer);
                } else {
                    $logger->addWriter(new Zend\Log\Writer\Null);
                }

                // register a custom error handler; we can not use the logger's as
                // it would log 'context' which gets vastly too noisy
                set_error_handler(
                    function ($level, $message, $file, $line) use ($logger) {
                        if (error_reporting() & $level) {
                            $map = Zend\Log\Logger::$errorPriorityMap;
                            $logger->log(
                                isset($map[$level]) ? $map[$level] : $logger::INFO,
                                $message,
                                array(
                                     'errno'   => $level,
                                     'file'    => $file,
                                     'line'    => $line
                                )
                            );
                        }

                        return false;
                    }
                );

                return $logger;
            },
            'p4'    => function ($services) {
                // if we have a logged in user, we want to use their connection
                // to perforce. otherwise, we will use the admin connection
                if ($services->get('permissions')->is('authenticated')) {
                    return $services->get('p4_user');
                }

                // doesn't appear anyone is logged in, run as admin
                return $services->get('p4_admin');
            },
            'p4_admin' => function ($services) {
                $config  = $services->get('config') + array('p4' => array());
                $p4      = (array) $config['p4'];

                $factory = new \Application\Connection\ConnectionFactory($p4);
                return $factory->createService($services);
            },
            'p4_user' => function ($services) {
                $config   = $services->get('config') + array('p4' => array());
                $p4       = (array) $config['p4'];
                $auth     = $services->get('auth');
                $identity = $auth->hasIdentity() ? (array) $auth->getIdentity() : array();

                // can't get a user specific connection if user is not authenticated
                if (!isset($identity['id']) || !strlen($identity['id'])) {
                    throw new \Application\Permissions\Exception\UnauthorizedException;
                }

                // tweak the 'p4' settings to use the users id/ticket and ensure password isn't present
                $p4['user']   = $identity['id'];
                $p4['ticket'] = isset($identity['ticket']) ? $identity['ticket'] : null;
                unset($p4['password']);

                $factory    = new \Application\Connection\ConnectionFactory($p4);
                $connection = $factory->createService($services);

                // share a cache with the 'admin' connection
                $connection->setService(
                    'cache',
                    function () use ($services) {
                        return $services->get('p4_admin')->getService('cache');
                    }
                );

                // verify the user is authenticated.
                // if the ticket/password is invalid, try to clean up the auth and user
                // services to reflect the anonymous state (someone may have fetched them
                // before us leaving them otherwise in a bad state).
                if (!$connection->isAuthenticated()) {
                    // if our bad connection is the default; clear it
                    if (P4\Connection\Connection::hasDefaultConnection()
                        && P4\Connection\Connection::getDefaultConnection() === $connection
                    ) {
                        P4\Connection\Connection::clearDefaultConnection();
                    }

                    // if using session-based auth, empty/destroy the session
                    if ($auth->getStorage() instanceof Zend\Authentication\Storage\Session) {
                        $session = $services->get('session');
                        $session->start();
                        $auth->getStorage()->write(null);
                        $session->destroy(array('send_expire_cookie' => true, 'clear_storage' => true));
                        $session->writeClose();
                    }

                    // if the user service is already instantiated, clear
                    // the existing object; we want to try and clear out
                    // anyone who already has a copy.
                    $registered = $services->getRegisteredServices();
                    if (in_array('user', $registered['instances'])) {
                        $services->get('user')
                            ->setId(null)
                            ->setEmail(null)
                            ->setFullName(null)
                            ->setJobView(null)
                            ->setReviews(array())
                            ->setConfig(new \Users\Model\Config);
                    }

                    throw new \Application\Permissions\Exception\UnauthorizedException;
                }

                return $connection;
            },
            'session'   => function ($services) {
                $config  = $services->get('config') + array('session' => array());
                $strict  = isset($config['security']['https_strict']) && $config['security']['https_strict'];
                $config  = $config['session'] + array(
                    'name'                       => null,
                    'save_path'                  => null,
                    'cookie_lifetime'            => null,
                    'remembered_cookie_lifetime' => null
                );

                // detect if we're on https, if we are (or we're strict) we'll set the cookie to secure
                $request = $services->get('request');
                $https   = $request instanceof Zend\Http\Request && $request->getUri()->getScheme() == 'https';

                // by default, relocate session storage if we can to avoid mixing with
                // other php apps using different/default session clean settings.
                $config['save_path'] = $config['save_path'] ?: DATA_PATH . '/sessions';
                is_dir($config['save_path']) ?: @mkdir($config['save_path'], 0700, true);
                if (!is_writable($config['save_path'])) {
                    unset($config['save_path']);
                }

                // by default, we name the session id SWARM and, if its running on a
                // non-standard port, we add the port number. This allows separate
                // Swarm instances to run on a given domain using different ports.
                if (!$config['name']) {
                    // we try to extract the port from the HTTP_HOST if possible.
                    // if we fail to find it there we fall back to the SERVER_PORT variable
                    // SERVER_PORT is fairly certain to be present but known to report 80
                    // even when another port is in use under some apache configurations.
                    $server = $_SERVER + array('HTTP_HOST' => '', 'SERVER_PORT' => null);
                    preg_match('/:(?P<port>[0-9]+)$/', $server['HTTP_HOST'], $matches);
                    $port = isset($matches['port']) && $matches['port']
                        ? $matches['port']
                        : $server['SERVER_PORT'];
                    $config['name'] = 'SWARM'
                                    . ($port && $port != 80 && $port != 443 ? '-' . $port : '');
                }

                // verify the session isn't already started (shouldn't be) and adjust
                // the settings. attempting an adjustment post start produces errors.
                $sessionConfig = new \Zend\Session\Config\SessionConfig;
                if (!session_id()) {
                    // if the user has a 'remember me' cookie utilize the 'remembered' cookie lifetime
                    // note we have to clear the made up remembered_cookie_lifetime regardless as it
                    // would cause an exception if it makes it into the session config.
                    if (isset($_COOKIE['remember']) && $_COOKIE['remember']) {
                        $config['cookie_lifetime'] = $config['remembered_cookie_lifetime'];
                    }
                    unset($config['remembered_cookie_lifetime']);

                    // set the session config by mixing any user provided config
                    // values with our defaults
                    $sessionConfig->setOptions(
                        $config +
                        array(
                            'cookie_httponly'  => true,
                            'cookie_secure'    => $https || $strict,
                            'gc_probability'   => 1,
                            'gc_divisor'       => 100,
                            'gc_maxlifetime'   => 24*60*60 * 30    // 1 month
                        )
                    );
                }

                $session = new Application\Session\SessionManager($sessionConfig);

                // a couple conditions require the session id pre-start, get it if possible
                $sessionName = $sessionConfig->getOption('name');
                $sessionId   = isset($_COOKIE[$sessionName]) ? $_COOKIE[$sessionName] : null;

                // if we have no session cookie, no need to deal with session expiry or
                // read current values from disk; just bail!
                if (!strlen($sessionId)) {
                    return $session;
                }

                // we want to actually enforce the gc lifetime for file-based sessions.
                // to support this, pull the mtime from the session file before we start.
                $sessionFile = strlen($sessionId) && $sessionConfig->getOption('save_handler') == 'files'
                    ? $sessionConfig->getOption('save_path') . '/sess_' . $sessionId
                    : false;
                $sessionTime = $sessionFile && file_exists($sessionFile) ? filemtime($sessionFile) : false;

                // ensure the session is started (to populate session storage data)
                // but promptly close it to minimize locking - anytime we need
                // to update the session later, we need to explicitly open/close it.
                $session->start();

                // if we found a session file mod-time and its expired, destroy the session
                if ($sessionTime
                    && (time() - $sessionTime) > $sessionConfig->getOption('gc_maxlifetime')
                ) {
                    $session->destroy(array('send_expire_cookie' => true, 'clear_storage' => true));
                }

                $session->writeClose();

                return $session;
            },
            'permissions'   => function ($services) {
                return new Application\Permissions\Permissions($services);
            },
            'ip_protects' => function ($services) {
                $config   = $services->get('config');
                $remoteIp = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
                $enabled  = isset($config['security']['emulate_ip_protections'])
                    && $config['security']['emulate_ip_protections'];

                // create and configure ip protections emulation
                $protections = new Application\Permissions\Protections;
                $protections->setEnabled(false);

                if ($enabled && $remoteIp) {
                    $p4 = $services->get('p4');

                    // determine whether connected server is case sensitive or case insensitive
                    // if we can't puzzle it out, treat is as case sensitive (more restrictive)
                    try {
                        $isCaseSensitive = $p4->isCaseSensitive();
                    } catch (P4\Exception $e) {
                        $isCaseSensitive = true;
                    }

                    // collect lines from the protections table to apply
                    // we take non-proxy rules for user's IP, but we also take proxy rules to
                    // express we treat Swarm as an intermediary
                    try {
                        $protectionsData = array_merge(
                            $p4->run('protects', array('-h',            $remoteIp))->getData(),
                            $p4->run('protects', array('-h', 'proxy-' . $remoteIp))->getData()
                        );

                        // sort merged protections data to preserve their original order in the protections table
                        usort(
                            $protectionsData,
                            function (array $a, array $b) {
                                return (int) $a['line'] - (int) $b['line'];
                            }
                        );

                        $protections->setProtections($protectionsData, $isCaseSensitive);
                        $protections->setEnabled(true);
                    } catch (P4\Connection\Exception\CommandException $e) {
                        if (strpos($e->getMessage(), 'Protections table is empty.') === false) {
                            // we don't recognize the message, so re-throw the exception
                            throw $e;
                        }
                    }
                }

                return $protections;
            },
            'depot_storage' => function ($services) {
                $config = $services->get('config');
                $config = $config['depot_storage'] + array('base_path'=>null);

                $depot = new Record\File\FileService($services->get('p4_admin'));
                $depot->setConfig($config);

                return $depot;
            },
            'changes_filter' => function ($services) {
                return new Application\Permissions\RestrictedChanges($services->get('p4'));
            },
            'csrf'  => function ($services) {
                return new Application\Permissions\Csrf\Service($services);
            },
            'MvcTranslator' => function ($services) {
                $config     = $services->get('config');
                $config     = isset($config['translator']) ? $config['translator'] : array();
                $translator = \Application\I18n\Translator::factory($config);

                $translator->setEscaper(new \Application\Escaper\Escaper);

                // add event listener for context fallback on missing translations
                $translator->enableEventManager();
                $translator->getEventManager()->attach(
                    $translator::EVENT_MISSING_TRANSLATION,
                    array($translator, 'handleMissingTranslation')
                );

                // establish default locale settings
                $translator->setLocale($translator->getLocale() ?: 'en_US');
                $translator->setFallbackLocale($translator->getFallbackLocale() ?: 'en_US');

                // try to guess locale from browser language header (using intl if available)
                if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])
                    && (!isset($config['detect_locale']) || $config['detect_locale'] !== false)
                ) {
                    $locale = extension_loaded('intl')
                        ? \Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE'])
                        : str_replace('-', '_', current(explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE'])));

                    // if we can't find an exact match, venture a guess based on language prefix
                    if (!$translator->isSupportedLocale($locale)) {
                        $language = current(preg_split('/[^a-z]/i', $locale));
                        $locale   = $translator->isSupportedLanguage($language) ?: $locale;
                    }

                    $translator->setLocale(strlen($locale) ? $locale : $translator->getLocale());
                }

                return $translator;
            },
        ),
    ),
    'translator' => array(
        'locale'                    => 'en_US',
        'detect_locale'             => true,
        'translation_file_patterns' => array(
            array(
                'type'        => 'gettext',
                'base_dir'    => BASE_PATH . '/language',
                'pattern'     => '%s/default.mo',
            ),
        ),
    ),
    'view_manager' => array(
        'display_not_found_reason' => false,
        'display_exceptions'       => false,
        'doctype'                  => 'HTML5',
        'not_found_template'       => 'error/index',
        'exception_template'       => 'error/index',
        'template_map' => array(
            'layout/layout'           => __DIR__ . '/../view/layout/layout.phtml',
            'layout/toolbar'          => __DIR__ . '/../view/layout/toolbar.phtml',
            'application/index/index' => __DIR__ . '/../view/application/index/index.phtml',
            'error/index'             => __DIR__ . '/../view/error/index.phtml',
        ),
        'template_path_stack' => array(
            __DIR__ . '/../view',
        ),
        'strategies' => array(
            'ViewJsonStrategy', 'ViewFeedStrategy'
        ),
    ),
    'log' => array(
        'file'      => DATA_PATH . '/log',
        'priority'  => 3 // just log errors by default
    ),
    'view_helpers' => array(
        'invokables' => array(
            'breadcrumbs'       => 'Application\View\Helper\Breadcrumbs',
            'bodyClass'         => 'Application\View\Helper\BodyClass',
            'csrf'              => 'Application\View\Helper\Csrf',
            'escapeFullUrl'     => 'Application\View\Helper\EscapeFullUrl',
            'headLink'          => 'Application\View\Helper\HeadLink',
            'headScript'        => 'Application\View\Helper\HeadScript',
            'linkify'           => 'Application\View\Helper\Linkify',
            'permissions'       => 'Application\View\Helper\Permissions',
            'preformat'         => 'Application\View\Helper\Preformat',
            'qualifiedUrl'      => 'Application\View\Helper\QualifiedUrl',
            'request'           => 'Application\View\Helper\Request',
            'shortenStackTrace' => 'Application\View\Helper\ShortenStackTrace',
            'truncate'          => 'Application\View\Helper\Truncate',
            'utf8Filter'        => 'Application\View\Helper\Utf8Filter',
            'wordify'           => 'Application\View\Helper\Wordify',
            'wordWrap'          => 'Application\View\Helper\WordWrap',
            't'                 => 'Application\View\Helper\Translate',
            'te'                => 'Application\View\Helper\TranslateEscape',
            'tp'                => 'Application\View\Helper\TranslatePlural',
            'tpe'               => 'Application\View\Helper\TranslatePluralEscape',
        ),
    ),
    'controller_plugins' => array(
        'invokables' => array(
            'Disconnect'    => 'Application\Controller\Plugin\Disconnect'
        )
    ),
    'depot_storage' => array(
        'base_path' => '//.swarm'
    )
);
# Change User Description Committed
#1 18334 Liz Lam initial add of jambox