Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 90
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractApp
0.00% covered (danger)
0.00%
0 / 90
0.00% covered (danger)
0.00%
0 / 5
110
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 64
0.00% covered (danger)
0.00%
0 / 1
20
 configureSlim
n/a
0 / 0
n/a
0 / 0
0
 configureIoc
n/a
0 / 0
n/a
0 / 0
0
 configureView
n/a
0 / 0
n/a
0 / 0
0
 configureRoutes
n/a
0 / 0
n/a
0 / 0
0
 run
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 redirect
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 template
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 configureHeaderMiddleware
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @section LICENSE
4 * This file is part of Wikimedia Slim application library
5 *
6 * Wikimedia Slim application library is free software: you can
7 * redistribute it and/or modify it under the terms of the GNU General Public
8 * License as published by the Free Software Foundation, either version 3 of
9 * the License, or (at your option) any later version.
10 *
11 * Wikimedia Slim application library is distributed in the hope that it
12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with Wikimedia Grants Review application.  If not, see
18 * <http://www.gnu.org/licenses/>.
19 *
20 * @file
21 * @copyright © 2015 Bryan Davis, Wikimedia Foundation and contributors.
22 */
23
24namespace Wikimedia\Slimapp;
25
26use Monolog\Formatter\LogstashFormatter;
27use Monolog\Handler\Udp2logHandler;
28use Monolog\Logger;
29use Monolog\Processor\ProcessIdProcessor;
30use Monolog\Processor\PsrLogMessageProcessor;
31use Monolog\Processor\UidProcessor;
32use Monolog\Processor\WebProcessor;
33use Psr\Log\LogLevel;
34use Slim\Helper\Set;
35use Slim\Slim;
36use Slim\View;
37use Slim\Views\Twig;
38
39/**
40 * Grants review application.
41 *
42 * @author Bryan Davis <bd808@wikimedia.org>
43 * @copyright © 2015 Bryan Davis, Wikimedia Foundation and contributors.
44 */
45abstract class AbstractApp {
46
47    /**
48     * @var string
49     */
50    protected $deployDir;
51
52    /**
53     * @var Slim
54     */
55    protected $slim;
56
57    /**
58     * @param string $deployDir Full path to code deployment
59     * @param array $settings Associative array of application settings
60     */
61    public function __construct( $deployDir, array $settings = [] ) {
62        $this->deployDir = $deployDir;
63
64        $this->slim = new Slim( array_merge(
65            [
66                'mode' => 'production',
67                'debug' => false,
68                'log.channel' => Config::getStr( 'LOG_CHANNEL', 'app' ),
69                'log.level' => Config::getStr(
70                    'LOG_LEVEL', LogLevel::NOTICE
71                ),
72                'log.file' => Config::getStr( 'LOG_FILE', 'php://stderr' ),
73                'view' => new Twig(),
74                'view.cache' => Config::getStr(
75                    'CACHE_DIR', "{$this->deployDir}/data/cache"
76                ),
77                'templates.path' => Config::getStr(
78                    'TEMPLATE_DIR', "{$this->deployDir}/data/templates"
79                ),
80                'i18n.path' => Config::getStr(
81                    'I18N_DIR', "{$this->deployDir}/data/i18n"
82                ),
83                'i18n.default' => Config::getstr( 'DEFAULT_LANG', 'en' ),
84                'smtp.host' => Config::getStr( 'SMTP_HOST', 'localhost' ),
85            ],
86            $settings
87        ) );
88
89        // Slim does not natively understand being behind a proxy. If not
90        // corrected template links created via siteUrl() may use the wrong
91        // Protocol (http instead of https).
92        if ( getenv( 'HTTP_X_FORWARDED_PROTO' ) ) {
93            $proto = getenv( 'HTTP_X_FORWARDED_PROTO' );
94            $this->slim->environment['slim.url_scheme'] = $proto;
95
96            $port = getenv( 'HTTP_X_FORWARDED_PORT' );
97            if ( $port === false ) {
98                $port = ( $proto == 'https' ) ? '443' : '80';
99            }
100            $this->slim->environment['SERVER_PORT'] = $port;
101        }
102
103        $this->configureSlim( $this->slim );
104
105        // Replace default logger with monolog.
106        // Done before configureIoc() so subclasses can easily switch it again
107        // if desired.
108        $this->slim->container->singleton( 'log', static function ( $c ) {
109            // Convert string level to Monolog integer value
110            $level = strtoupper( $c->settings['log.level'] );
111            $level = constant( "\Monolog\Logger::{$level}" );
112
113            $log = new Logger( $c->settings['log.channel'] );
114            $handler = new Udp2logHandler(
115                $c->settings['log.file'],
116                $level
117            );
118            $handler->setFormatter( new LogstashFormatter(
119                $c->settings['log.channel'], null, null, '',
120                LogstashFormatter::V1
121            ) );
122            $handler->pushProcessor(
123                new PsrLogMessageProcessor()
124            );
125            $handler->pushProcessor(
126                new ProcessIdProcessor()
127            );
128            $handler->pushProcessor( new UidProcessor() );
129            $handler->pushProcessor( new WebProcessor() );
130            $log->pushHandler( $handler );
131            return $log;
132        } );
133
134        $this->configureIoc( $this->slim->container );
135        $this->configureView( $this->slim->view );
136
137        $this->slim->add(
138            new HeaderMiddleware( $this->configureHeaderMiddleware() )
139        );
140
141        // Add CSRF protection for POST requests
142        $this->slim->add( new CsrfMiddleware() );
143
144        $this->configureRoutes( $this->slim );
145    }
146
147    /**
148     * Apply settings to the Slim application.
149     *
150     * @param Slim $slim Application
151     */
152    abstract protected function configureSlim( Slim $slim );
153
154    /**
155     * Configure inversion of control/dependency injection container.
156     *
157     * @param Set $container IOC container
158     */
159    abstract protected function configureIoc( Set $container );
160
161    /**
162     * Configure view behavior.
163     *
164     * @param View $view Default view
165     */
166    abstract protected function configureView( View $view );
167
168    /**
169     * Configure routes to be handled by application.
170     *
171     * @param Slim $slim Application
172     */
173    abstract protected function configureRoutes( Slim $slim );
174
175    /**
176     * Main entry point for all requests.
177     */
178    public function run() {
179        session_name( '_s' );
180        session_cache_limiter( false );
181        ini_set( 'session.cookie_httponly', true );
182        session_start();
183        register_shutdown_function( 'session_write_close' );
184        $this->slim->run();
185    }
186
187    /**
188     * Add a redirect route to the app.
189     * @param Slim $slim App
190     * @param string $name Page name
191     * @param string $to Redirect target route name
192     * @param string $routeName Name for the route
193     */
194    public static function redirect(
195        Slim $slim, $name, $to, $routeName = null
196    ) {
197        $routeName = $routeName ?: $name;
198
199        $slim->get( $name, static function () use ( $slim, $to ) {
200            $slim->flashKeep();
201            $slim->redirect( $slim->urlFor( $to ) );
202        } )->name( $routeName );
203    }
204
205    /**
206     * Add a static template route to the app.
207     * @param Slim $slim App
208     * @param string $name Page name
209     * @param string $routeName Name for the route
210     */
211    public static function template(
212        Slim $slim, $name, $routeName = null
213    ) {
214        $routeName = $routeName ?: $name;
215
216        $slim->get( $name, static function () use ( $slim, $name ) {
217            $slim->render( "{$name}.html" );
218        } )->name( $routeName );
219    }
220
221    /**
222     * Configure the default HeaderMiddleware installed for all routes.
223     *
224     * Default configuration adds these headers:
225     * - "Vary: Cookie" to help upstream caches to the right thing
226     * - "X-Frame-Options: DENY"
227     * - A fairly strict 'self' only Content-Security-Policy to help protect
228     *   against XSS attacks
229     * - "Content-Type: text/html; charset=UTF-8"
230     *
231     * @return array
232     */
233    protected function configureHeaderMiddleware() {
234        // Add headers to all responses:
235        return [
236            'Vary' => 'Cookie',
237            'X-Frame-Options' => 'DENY',
238            'Content-Security-Policy' =>
239                "default-src 'self'; " .
240                "frame-src 'none'; " .
241                "object-src 'none'; " .
242                // Needed for css data:... sprites
243                "img-src 'self' data:; " .
244                // Needed for jQuery and Modernizr feature detection
245                "style-src 'self' 'unsafe-inline'",
246            // Don't forget to override this for any content that is not
247            // actually HTML (e.g. json)
248            'Content-Type' => 'text/html; charset=UTF-8',
249        ];
250    }
251}