Using gzip / compression in Symfony 2 without mod_deflate - apache

I am working on two different Symfony 2.8 projects running on different servers. It would like to use compression for faster loading. All resources I found point to mod_deflate. But while the first server does not offer mod_deflate at all, the second server cannot use mod_deflate while FastCGI is enabled.
I only found the information, that one can enable compression within the server (mod_deflate) or "in script". But I did not found any detailed on this "in script" solution.
Is is somehow possible to enable compression in Symfony without using mod_deflate?

You can try to gzip content manually in kernel.response event:
namespace AppBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class CompressionListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => array(array('onKernelResponse', -256))
);
}
public function onKernelResponse($event)
{
//return;
if ($event->getRequestType() != HttpKernelInterface::MASTER_REQUEST) {
return;
}
$request = $event->getRequest();
$response = $event->getResponse();
$encodings = $request->getEncodings();
if (in_array('gzip', $encodings) && function_exists('gzencode')) {
$content = gzencode($response->getContent());
$response->setContent($content);
$response->headers->set('Content-encoding', 'gzip');
} elseif (in_array('deflate', $encodings) && function_exists('gzdeflate')) {
$content = gzdeflate($response->getContent());
$response->setContent($content);
$response->headers->set('Content-encoding', 'deflate');
}
}
}
And register this listener in config:
app.listener.compression:
class: AppBundle\EventListener\CompressionListener
arguments:
tags:
- { name: kernel.event_subscriber }

Related

What are cloudflare KV preview_ids and how to get one?

I have a following wrangler.toml. When I would like to use dev or preview (e.g. npx wrangler dev or npx wrangler preview) wrangler asks to add a preview_id to the KV namespaces. Is this an identifier to an existing KV namespace?
I see there is a ticket in Cloudflare Workers GH at https://github.com/cloudflare/wrangler/issues/1458 that tells this ought to be clarified but the ticket is closed with adding an error message
In order to preview a worker with KV namespaces, you must designate a preview_id in your configuration file for each KV namespace you'd like to preview."
which is the reason I'm here. :)
As for larger context I would be really glad if someone could clarify: I see that if I give a value of an existing namespace, I can preview and I see a KV namespace of type __some-worker-dev-1234-workers_sites_assets_preview is generated in Cloudflare. This has a different identifier than the KV namespace pointed by the identifier used in the preview_id and the KV namespace pointed by the identifier I used in preview_id is empty. Why does giving an identifier of an existing KV namespace remove the error message, deploys the assets and allow for previwing but the actual KV namespace is empty and a new one is created?
How do does kv-asset-handler know to look into this generated namespace to retrieve the assets?
I'm currently testing with the default generated Cloudare Worker to my site and I wonder if I have misunderstood something or if there is some mechanics that bundles during preview/publish the site namespace to the scipt.
If there is some random mechanics with automatic mapping, can this be then so that every developer can have their own private preview KV namespace?
type = "javascript"
name = "some-worker-dev-1234"
account_id = "<id>"
workers_dev = true
kv_namespaces = [
{ binding = "site_data", id = "<test-site-id>" }
]
[site]
# The location for the site.
bucket = "./dist"
# The entry directory for the package.json that contains
# main field for the file name of the compiled worker file in "main" field.
entry-point = ""
[env.production]
name = "some-worker-1234"
zone_id = "<zone-id>"
routes = [
"http://<site>/*",
"https://www.<site>/*"
]
# kv_namespaces = [
# { binding = "site_data", id = "<production-site-id>" }
# ]
import { getAssetFromKV, mapRequestToAsset } from '#cloudflare/kv-asset-handler'
/**
* The DEBUG flag will do two things that help during development:
* 1. we will skip caching on the edge, which makes it easier to
* debug.
* 2. we will return an error message on exception in your Response rather
* than the default 404.html page.
*/
const DEBUG = false
addEventListener('fetch', event => {
try {
event.respondWith(handleEvent(event))
} catch (e) {
if (DEBUG) {
return event.respondWith(
new Response(e.message || e.toString(), {
status: 500,
}),
)
}
event.respondWith(new Response('Internal Error', { status: 500 }))
}
})
async function handleEvent(event) {
const url = new URL(event.request.url)
let options = {}
/**
* You can add custom logic to how we fetch your assets
* by configuring the function `mapRequestToAsset`
*/
// options.mapRequestToAsset = handlePrefix(/^\/docs/)
try {
if (DEBUG) {
// customize caching
options.cacheControl = {
bypassCache: true,
}
}
const page = await getAssetFromKV(event, options)
// allow headers to be altered
const response = new Response(page.body, page)
response.headers.set('X-XSS-Protection', '1; mode=block')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('Referrer-Policy', 'unsafe-url')
response.headers.set('Feature-Policy', 'none')
return response
} catch (e) {
// if an error is thrown try to serve the asset at 404.html
if (!DEBUG) {
try {
let notFoundResponse = await getAssetFromKV(event, {
mapRequestToAsset: req => new Request(`${new URL(req.url).origin}/404.html`, req),
})
return new Response(notFoundResponse.body, { ...notFoundResponse, status: 404 })
} catch (e) {}
}
return new Response(e.message || e.toString(), { status: 500 })
}
}
/**
* Here's one example of how to modify a request to
* remove a specific prefix, in this case `/docs` from
* the url. This can be useful if you are deploying to a
* route on a zone, or if you only want your static content
* to exist at a specific path.
*/
function handlePrefix(prefix) {
return request => {
// compute the default (e.g. / -> index.html)
let defaultAssetKey = mapRequestToAsset(request)
let url = new URL(defaultAssetKey.url)
// strip the prefix from the path for lookup
url.pathname = url.pathname.replace(prefix, '/')
// inherit all other props from the default request
return new Request(url.toString(), defaultAssetKey)
}
}
In case the format is not obvious (it wasn't to me) here is a sample config block from the docs with the preview_id specified for a couple of KV Namespaces:
kv_namespaces = [
{ binding = "FOO", id = "0f2ac74b498b48028cb68387c421e279", preview_id = "6a1ddb03f3ec250963f0a1e46820076f" },
{ binding = "BAR", id = "068c101e168d03c65bddf4ba75150fb0", preview_id = "fb69528dbc7336525313f2e8c3b17db0" }
]
You can generate a new namespace ID in the Workers KV section of the dashboard or with the Wrangler CLI:
wrangler kv:namespace create "SOME_NAMESPACE" --preview
This answer applies to versions of Wrangler >= 1.10.0
wrangler asks to add a preview_id to the KV namespaces. Is this an identifier to an existing KV namespace?
Yes! The reason there is a different identifier for preview namespaces is so that when developing with wrangler dev or wrangler preview you don't accidentally write changes to your existing production data with possibly buggy or incompatible code. You can add a --preview flag to most wrangler kv commands to interact with your preview namespaces.
For your situation here there are actually a few things going on.
You are using Workers Sites
You have a KV namespace defined in wrangler.toml
Workers Sites will automatically configure a production namespace for each environment you run wrangler publish on, and a preview namespace for each environment you run wrangler dev or wrangler preview on. If all you need is Workers Sites, then there is no need at all to specify a kv-namepsaces table in your manifest. That table is for additional KV namespaces that you may want to read data from or write data to. If that is what you need, you'll need to configure your own namespace and add id to wrangler.toml if you want to use wrangler publish, and preview_id (which should be different) if you want to use wrangler dev or wrangler preview.

phpBB 3.1+ Authentication Plugin

I could really use some help with this.
BACKGROUND:
I've got phpBB 3.0 installed and have working external authentication from my own site's database. My working is an implementation of this excellent worked example: https://github.com/nzeyimana/PhpBBAuthDbExt/blob/master/auth_dbext.php
I now want to upgrade my Forum to 3.2 (current version).
PROBLEM:
Trying to follow the example in the documentation https://area51.phpbb.com/docs/dev/32x/extensions/tutorial_authentication.html#authentication-providers and also phpBB community/viewtopic.php?f=461&t=2272371
I've copied the class file from the example documentation, calling it db2.php and placed in "ext/acme/demo/auth/provider/"
I've also copied the service file from the example documentation, calling it services.yml and placed in "ext/acme/demo/config/"
Copies of both file contents at bottom below.
According to the documentation, I should then see db2 in the list of authentication methods in the Authentication part of Access Control Panel (ACP) - but nothing appears. I've flushed the forum cache, flushed my browsers cache etc, to no avail.
Am I missing something? Any help REALLY appreciated.
Kevin
This is the content of the db2.php file:
#
<?php
namespace acme\demo\auth\provider;
/**
* Database authentication provider for phpBB3
*
* This is for authentication via the integrated user table
*/
class db2 extends \phpbb\auth\provider\base
{
/** #var \phpbb\db\driver\driver_interface $db */
protected $db;
/**
* Database Authentication Constructor
*
* #param \phpbb\db\driver\driver_interface $db
*/
public function __construct(\phpbb\db\driver\driver_interface $db)
{
$this->db = $db;
}
/**
* {#inheritdoc}
*/
public function login($username, $password)
{
// Auth plugins get the password untrimmed.
// For compatibility we trim() here.
$password = trim($password);
// do not allow empty password
if (!$password)
{
return array(
'status' => LOGIN_ERROR_PASSWORD,
'error_msg' => 'NO_PASSWORD_SUPPLIED',
'user_row' => array('user_id' => ANONYMOUS),
);
}
if (!$username)
{
return array(
'status' => LOGIN_ERROR_USERNAME,
'error_msg' => 'LOGIN_ERROR_USERNAME',
'user_row' => array('user_id' => ANONYMOUS),
);
}
$username_clean = utf8_clean_string($username);
$sql = 'SELECT user_id, username, user_password, user_passchg, user_pass_convert, user_email, user_type, user_login_attempts
FROM ' . USERS_TABLE . "
WHERE username_clean = '" . $this->db->sql_escape($username_clean) . "'";
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
// Successful login... set user_login_attempts to zero...
return array(
'status' => LOGIN_SUCCESS,
'error_msg' => false,
'user_row' => $row,
);
}
}
#
This is the content of the services.yml file:
#
services:
auth.provider.db2:
class: acme\demo\auth\provider\db2
arguments:
- '#dbal.conn'
tags:
- { name: auth.provider }
#
Unfortunately, the documentation is missing an important part - every extension should have its composer.json file in order to identify the extension - link.
You can refer to OneAll phpBB extension to see its structure and code. Use it as an example.
Once you have your composer.json, you should see you extension in the extension management list. Then enable your extension and you should see it in the Authentication section in your Access Control Panel (ACP)
I hope this helps.

Zend 2 and auth configuration routing

I'm working curently on a Zend2 project where there is an authentifaction system for the whole website, it was fine until we had to develop a module which is an public web service.
I would like to know if it's possible to allow users to access to a specific module/routing of Zend 2 ?
The Zend\Authentication\Adapter\Http provides an easy way for Apache like authentication in Zend Framework 2 applications.
It comes with two implementations Basic and Digest HTTP Authentication, which can be combined with two sub components - the class itself or a FileResolver. We are going to use the FileResolver to read the stored credentials and compare them to the submitted values.
First thing first. There are few important things to know.
Create a folder with name auth in MODULE_NAME/config/. Inside that folder create two files basic.txt and digest.txt. The file formats are smillar to Apache .htpasswd files.
Basic - <username>:<realm>:<credentials>, here credentials should be written in clear text, e.g.: basic:authentication:plaintextpassword.
Digest - <username>:<realm>:<credentials>, where <credentials> is the md5 hash of all 3 parts, e.g.: digest:authentication:dc45122ef294d83e84a8b5a3a6c5356b
In the same module, where we have just created our auth folder, open module.config.php file and place this code.
The code tells us which authentication schemes we accept, the realm (must be the same as the realm in the basic/digest.txt files, digest_domains (only when we use digest authentication) is the URL(s) where we want to apply the same valid information, nonce_timeout sets the number of seconds for which the nonce is valid.
/**
* Used for basic authentication
*/
'authentication_basic' => [
'adapter' => [
'config' => [
'accept_schemes' => 'basic',
'realm' => 'authentication',
'nonce_timeout' => 3600,
],
'basic' => __DIR__.'/auth/basic.txt',
],
],
/**
* Used for digest authentication
*/
'authentication_digest' => [
'adapter' => [
'config' => [
'accept_schemes' => 'digest',
'realm' => 'authentication',
'digest_domains' => '/learn-zf2-authentication/digest',
'nonce_timeout' => 3600,
],
'digest' => __DIR__.'/auth/digest.txt',
],
]
LearnZF2Authentication\Factory\BasicAuthenticationAdapterFactory
$config = $serviceLocator->get('Config');
$authConfig = $config['authentication_basic']['adapter'];
$authAdapter = new HttpAdapter($authConfig['config']);
$basic = new FileResolver();
$basic->setFile($authConfig['basic']);
$authAdapter->setBasicResolver($basic);
return $authAdapter;
LearnZF2Authentication\Factory\DigestAuthenticationAdapterFactory
$config = $serviceLocator->get('Config');
$authConfig = $config['authentication_digest']['adapter'];
$authAdapter = new HttpAdapter($authConfig['config']);
$digest = new FileResolver();
$digest->setFile($authConfig['digest']);
$authAdapter->setDigestResolver($digest);
return $authAdapter;
These are the codes we use to pass the authentication information
Module.php
/**
* #var MvcEvent $e
*/
$request = $e->getRequest();
$response = $e->getResponse();
$view = $e->getApplication()->getMvcEvent()->getViewModel();
$sm = $e->getApplication()->getServiceManager();
$authAdapter = $sm->get('LearnZF2Authentication\BasicAuthenticationAdapter');
/**
* Not HTTP? Stop!
*/
if (!($request instanceof Http\Request && $response instanceof Http\Response)) {
return;
}
/**
* Call the factory class and try to authenticate
*/
if ($e->getRouteMatch()->getParam('action') == 'digest') {
$authAdapter = $sm->get('LearnZF2Authentication\DigestAuthenticationAdapter');
}
$authAdapter->setRequest($request);
$authAdapter->setResponse($response);
if($e->getRouteMatch()->getParam('action') == 'basic' || $e->getRouteMatch()->getParam('action') == 'digest') {
$result = $authAdapter->authenticate();
/**
* Pass the information to the view and see what we got
*/
if ($result->isValid()) {
return $view->identity = $result->getIdentity();
} else {
/**
* Create a log function or just use the one from LearnZF2.
* Also make sure to redirect to another page, 404 for example
*/
foreach ($result->getMessages() as $msg) {
return $view->authProblem = $msg;
}
}
}
This is the code we use to pass the authentication information
One last important thing to note is that you must include a special header called Authorization n your request, replace :
RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L]
with
PHP compiled as CGI does not support apache_response_headers function, but we need this header in order to do basic HTTP authtentication when running with CGI or FastCGI.
RewriteRule ^(.*)$ %{ENV:BASE}index.php [E=HTTP_AUTHORIZATION:% {HTTP:Authorization},L,NC]
and add in top of public/index.php
if (isset($_SERVER["REDIRECT_HTTP_AUTHORIZATION"])) {
$_SERVER["HTTP_AUTHORIZATION"] = $_SERVER["REDIRECT_HTTP_AUTHORIZATION"];
}
Some things to note. The auth folder as well the authentication code from module.config.php is best to be placed in your main config folder, where the global|local.php files are and excluded from commits.

grunt qunit in conjunction with grunt server

While running grunt server for developing, How can I separately use the grunt qunit task to run the tests.
While trying to pass ["test/**/*.html"] to the all property, but that fails to run and returns (Warning: 0/0 assertions ran (0ms) Use)
It looks like, it doesn't fire off a phantomjs instance and doesn't find these pates.
So I tried the following
grunt.initConfig({
....
qunit: {
all: {
options: {
urls: ['http://localhost:<%= connect.options.port %>/test/tests/foo.html']
}
}
}
....
});
It only works if when manually include all test html pages (like in the example).
The problem is
My Question is, can grunt qUnit work properly even while using grunt server.
And how can i have ["test/**/*.html"] syntax work correctly. I am sure there must be a better way this should work!
Also, how can use grunt.file.expand be utilized to programmatically add matching files to run in the grunt qunit task.
I've done something like this:
grunt.initConfig({
...
'qunit': {
'files': ['test/**/*.html']
}
...
});
...
// Wrap the qunit task
grunt.renameTask('qunit', 'contrib-qunit');
grunt.registerTask('qunit', function(host, protocol) {
host = host || 'localhost';
protocol = protocol || 'http';
// Turn qunit.files into urls for conrib-qunit
var urls = grunt.util._.map(grunt.file.expand(grunt.config.get('qunit.files')), function(file) {
return protocol + '://' + host + '/' + file;
});
var config = { 'options': { 'urls' : urls } };
grunt.config.set('contrib-qunit.all', config);
grunt.task.run('contrib-qunit');
});

Blocking access via HTTP Authentication with Zend Framework 2

I'm trying to implement HTTP-based authentication through a Zend\Authentication\Adapter\Http as explained in the ZF2 documentation about the HTTP Authentication Adapter.
I want to block every incoming request until the user agent is authenticated, however I'm unsure about how to implement this in my module.
How would I setup my Zend\Mvc application to deny access to my controllers?
What you are looking for is probably a listener attached to the Zend\Mvc\MvcEvent::EVENT_DISPATCH event of your application.
In order, here's what you have to do to block access to any action through an authentication adapter. First of all, define a factory that is responsible for producing your authentication adapter:
namespace MyApp\ServiceFactory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Authentication\Adapter\Http as HttpAdapter;
use Zend\Authentication\Adapter\Http\FileResolver;
class AuthenticationAdapterFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$config = $serviceLocator->get('Config');
$authConfig = $config['my_app']['auth_adapter'];
$authAdapter = new HttpAdapter($authConfig['config']);
$basicResolver = new FileResolver();
$digestResolver = new FileResolver();
$basicResolver->setFile($authConfig['basic_passwd_file']);
$digestResolver->setFile($authConfig['digest_passwd_file']);
$adapter->setBasicResolver($basicResolver);
$adapter->setDigestResolver($digestResolver);
return $adapter;
}
}
This factory will basically give you a configured auth adapter, and abstract its instantiation logic away.
Let's move on and attach a listener to our application's dispatch event so that we can block any request with invalid authentication headers:
namespace MyApp;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
use Zend\ModuleManager\Feature\BootstrapListenerInterface;
use Zend\EventManager\EventInterface;
use Zend\Mvc\MvcEvent;
use Zend\Http\Request as HttpRequest;
use Zend\Http\Response as HttpResponse;
class MyModule implements ConfigProviderInterface, BootstrapListenerInterface
{
public function getConfig()
{
// moved out for readability on SO, since config is pretty short anyway
return require __DIR__ . '/config/module.config.php';
}
public function onBootstrap(EventInterface $event)
{
/* #var $application \Zend\Mvc\ApplicationInterface */
$application = $event->getTarget();
$serviceManager = $application->getServiceManager();
// delaying instantiation of everything to the latest possible moment
$application
->getEventManager()
->attach(function (MvcEvent $event) use ($serviceManager) {
$request = $event->getRequest();
$response = $event->getResponse();
if ( ! (
$request instanceof HttpRequest
&& $response instanceof HttpResponse
)) {
return; // we're not in HTTP context - CLI application?
}
/* #var $authAdapter \Zend\Authentication\Adapter\Http */
$authAdapter = $serviceManager->get('MyApp\AuthenticationAdapter');
$authAdapter->setRequest($request);
$authAdapter->setResponse($response);
$result = $adapter->authenticate();
if ($result->isValid()) {
return; // everything OK
}
$response->setBody('Access denied');
$response->setStatusCode(HttpResponse::STATUS_CODE_401);
$event->setResult($response); // short-circuit to application end
return false; // stop event propagation
}, MvcEvent::EVENT_DISPATCH);
}
}
And then the module default configuration, which in this case was moved to MyModule/config/module.config.php:
return array(
'my_app' => array(
'auth_adapter' => array(
'config' => array(
'accept_schemes' => 'basic digest',
'realm' => 'MyApp Site',
'digest_domains' => '/my_app /my_site',
'nonce_timeout' => 3600,
),
'basic_passwd_file' => __DIR__ . '/dummy/basic.txt',
'digest_passwd_file' => __DIR__ . '/dummy/digest.txt',
),
),
'service_manager' => array(
'factories' => array(
'MyApp\AuthenticationAdapter'
=> 'MyApp\ServiceFactory\AuthenticationAdapterFactory',
),
),
);
This is the essence of how you can get it done.
Obviously, you need to place something like an my_app.auth.local.php file in your config/autoload/ directory, with the settings specific to your current environment (please note that this file should NOT be committed to your SCM):
<?php
return array(
'my_app' => array(
'auth_adapter' => array(
'basic_passwd_file' => __DIR__ . '/real/basic_passwd.txt',
'digest_passwd_file' => __DIR__ . '/real/digest_passwd.txt',
),
),
);
Eventually, if you also want to have better testable code, you may want to move the listener defined as a closure to an own class implementing the Zend\EventManager\ListenerAggregateInterface.
You can achieve the same results by using ZfcUser backed by a Zend\Authentication\Adapter\Http, combined with BjyAuthorize, which handles the listener logic on unauthorized actions.
Answer of #ocramius is accept answer But you forget to describe How to write two files basic_password.txt and digest_passwd.txt
According to Zend 2 Official Doc about Basic Http Authentication:
basic_passwd.txt file contains username, realm(the same realm into your configuration) and plain password -> <username>:<realm>:<credentials>\n
digest_passwd.txt file contains username, realm(the same realm into your configuration) and password hashing Using MD5 hash -> <username>:<realm>:<credentials hashed>\n
Example:
if basic_passwd.txt file:
user:MyApp Site:password\n
Then digest_passwd.txt file:
user:MyApp Site:5f4dcc3b5aa765d61d8327deb882cf99\n
Alternatively you can use Apache Resolver for HTTP Adapter
use Zend\Authentication\Adapter\Http\ApacheResolver;
$path = 'data/htpasswd';
// Inject at instantiation:
$resolver = new ApacheResolver($path);
// Or afterwards:
$resolver = new ApacheResolver();
$resolver->setFile($path);
According to https://zendframework.github.io/zend-authentication/adapter/http/#resolvers