Sunday, 21 July 2013

Zend Framework 2: Bcrypt adapter for Zend Authentication component

ZF2 provides us with a class for using bcrypt algorithm for hashing and verifying passwords.

I want to try it with zend authentication component. I'd like to use bcrypt algorithm with dbTable authentication adapter.

Unfortunately, ZF2 has no built-in auth adapter for this.

Ok, let's make our own auth adapter based on DbTable auth adapter.

The logic is:
- fetch all rows from table where identity column value is equal to value of identity field from login form(it could be login name, email, etc)
- check if entered password is valid using bcrypt->verify() method

Implementation:

1. Create class that extends DbTable auth adapter

// define namespace for the class
namespace SomeModule\Auth\Adapter;

class BcryptDbAdapter extends DbTable
{
}

2. It is necessary to redefine two methods in our new class.
First method: authenticateCreateSelect() - method that creates a Zend\Db\Sql\Select object for fetching data from database
Second method: authenticateQuerySelect() - method that fetches data from database using Zend\Db\Sql\Select object from previous method.

Our class will look like below:

namespace SomeModule\Auth\Adapter;

use Zend\Authentication\Adapter\DbTable;
use Zend\Db\Sql;
use Zend\Db\Sql\Predicate\Operator as SqlOp;

class BcryptDbAdapter extends DbTable
{
    protected function authenticateCreateSelect()
    {
        // get select
        $dbSelect = clone $this->getDbSelect();
        $dbSelect->from($this->tableName)
            ->columns(array('*'))
            ->where(new SqlOp($this->identityColumn, '=', $this->identity));

        return $dbSelect;
    }

    protected function authenticateQuerySelect(Sql\Select $dbSelect)
    {
        $sql = new Sql\Sql($this->zendDb);
        $statement = $sql->prepareStatementForSqlObject($dbSelect);

        try {
            $result = $statement->execute();
            $resultIdentities = array();

            // create object ob Bcrypt class
            $bcrypt = new \Zend\Crypt\Password\Bcrypt();

            // iterate result, most cross platform way
            foreach ($result as $row) {
                if ($bcrypt->verify($this->credential, $row[$this->credentialColumn])) {
                    $row['zend_auth_credential_match'] = 1;
                    $resultIdentities[] = $row;
                }
            }

        } catch (\Exception $e) {
            throw new Exception\RuntimeException(
                'The supplied parameters to DbTable failed to '
                    . 'produce a valid sql statement, please check table and column names '
                    . 'for validity.', 0, $e
            );
        }

        return $resultIdentities;
    }
}
3. How to use example.

- Add required namespaces to controller:
use SomeModule\Auth\Adapter\BcryptDbAdapter as AuthAdapter;
use Zend\Authentication\AuthenticationService;

- Authenticate user in login action:

$data = $request->getPost();

$dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');

$authAdapter = new AuthAdapter($dbAdapter);

$authAdapter
  ->setTableName('users')
  ->setIdentityColumn('email')
  ->setCredentialColumn('password');

$authAdapter
  ->setIdentity(addslashes($data['email']))
  ->setCredential($data['password']);

// attempt authentication
$result = $authAdapter->authenticate();

if (!$result->isValid()) {
  // Authentication failed
} else {
  $auth = new AuthenticationService();
  $storage = $auth->getStorage();

  $storage->write($authAdapter->getResultRowObject(
    null,
    'password'
  ));
}

Source code is available on GitHub

Sunday, 19 May 2013

Facebook: Check if user clicked Like page button(just liked the Facebook page)

Very often Facebook applications have "Fangate" page, meaning that if user hasn't liked the page and if he opened application - he can see application page with text something like "Please like our Facebook page before using the app" and can't use the application until he is not page fan.

Our clients as usual ask us to track how many users liked the page from the application.

Signed request contains an information about if user like a page:

$facebook = new Facebook(array(
    'appId' => $app_id,
    'secret' => $app_secret,
    'cookie' => true
));

$signed_request = $facebook->getSignedRequest();
$like_status = $signed_request["page"]["liked"];


So, the challenge is to catch the moment when user clicks like button.

The simples way is to store like status to the session. But it doesn't work in Safari, for iframe Facebook applications Safari doesn't accept cookies and user session is lost.

Nowadays most of polar browsers support local storage. So we can use it to store like status information. Example of source code is pretty simple.

This code example should be in template that is rendered if user is a fan of Facebook page:

<script type="text/javascript">
    if ('localStorage' in window && window['localStorage'] !== null) {
        if (localStorage.getItem('liked_state') == 0) {
            // we've go it: user just clicked like button
        }

        localStorage.setItem('liked_state', 1);
    }
</script>

Pretty simple, isn't it?

Monday, 18 February 2013

Soundcloud API: PHP SDK issue

I tried to fetch track info from the Soundcloud service using 'resolve' method. Code looks like next:
// create a client object with your app credentials
$client = new Services_Soundcloud($clientId, $clientSecret);

// resolve track URL into track resource
$track = json_decode($client->get('resolve', array('url' => $url)));

And I've got fatal error:
Fatal error: Uncaught exception 
'Services_Soundcloud_Invalid_Http_Response_Code_Exception' with message
'The requested URL responded with HTTP code 302.'
in /path-to-project/Services/Soundcloud.php:942

Seems that CURL doesn't follow redirects. It is possible to fix this by setting certain CURL option in Soundcloud class. I've added next to class constructor(line 234):
$this->_curlOptions[CURLOPT_FOLLOWLOCATION] = true;

After that everything started working like a charm.

Problem is already discussed on project google group, so I think the fix will be added to the project repo on GitHub soon.

Monday, 21 January 2013

Nginx error: 502 Bad Gateway. ZF2, APC and segmentation fault.

I tried Zend Framework 2 for my last project. When everything was done with development I moved project to production server but every time I tried to open project url in bowser Nginx returned an error:

502 bad gateway

Server is running under Debian, also I have installed Nginx(1.2.6) + PHP(5.3.20) and PHP-FPM.

Increasing buffer size and read timeout value in Nginx config had no results, still 502 error. I've looked into Nginx logs and found next entries:

… recv() failed (104: Connection reset by peer) while reading response header from upstream ...

So, it seems that problem is not in Nginx config. Something is wrong with backend. PHP-FPM log contains plenty of entries like next:

php5-fpm.log:
WARNING: [pool www] child 7050 exited on signal 11 (SIGSEGV) after 14.101753 seconds from start

Well, something is causing segmentation fault. I'm hosting a lot of projects on this server and everything was fine, problem appeared only for the last project that was developed using ZF2.

Googling a little bit, I found that some of PHP extensions can cause segmentation fault. One by one I started disabling PHP-extensions. After I disabled APC extension problem disappeared and everything started working fine. So, disabling APC extension helped to solve the problem and seems that ZF2 has some problems with APC, or APC has some problems with ZF2 :)

Will try to make some research what exactly was the reason of this problem and post here if I'll find something interesting.

Sunday, 13 January 2013

ZF2: Get db adapter without service locator usage

The problem is: I need to get the instance of TableGateway directly, not via service locator. TableGateway object requires configured dbAdapter object passed to gateway constructor.

Next solution can help with this problem:

1. Use GlobalAdaperFeature as static storage for dbAdapter:
use Zend\Db\TableGateway\Feature;


$feature = new Feature\GlobalAdapterFeature();

2. Add bootstrap method to module config:
public function onBootstrap($e)
{
    // set static adapter for all module table gateways

    $serviceManager = $e->getApplication()->getServiceManager();

    $dbAdapter = $serviceManager->get('Zend\Db\Adapter\Adapter');

    Feature\GlobalAdapterFeature::setStaticAdapter($dbAdapter);
}

3. Now it is possible to get access to already loaded dbAdapter object in TableGateway constructor:
public function __construct()
{
    $this->featureSet = new Feature\FeatureSet();

    $this->featureSet->addFeature(new Feature\GlobalAdapterFeature());

    $this->initialize();
}

So we have dbAdapter injected into gateway constructor. Ofcourse, it's not best practice, but this allows to add some flexibility to your application, and anyway object dbAdapter was instantiated via service locator, but only once for all tableGateways on the stage of module bootstraping.

Wednesday, 2 January 2013

Zend Framework 2: Disable layout rendering

In ZF1.x we have method for disabling layout rendering that looks like:

$this->_helper->layout()->disableLayout(); 

Of course in ZF2 this method doesn't work but there are some other ways how it is possible to do:

1. Set viewModel as standalone model in controller action:

public function someAction() {
    $viewModel = new ViewModel(array(
        'foo' => 'bar'
    ));

    $viewModel->setTerminal(true);

    return $viewModel;
}

2. Create empty layout and use it in controller action:

Create almost empty layout: module/MyModule/view/layout/empty.phtml with only content:
<?php echo $this->content; ?>

Then use this layout in controller action:
$this->layout('layout/empty');

3. Use response object for output content:

$response = $this->getResponse();
$response->setContent("Some content"); 
return $response;

That's all. May be there are some other ways to do this. As for me I prefer the first way, it looks simple and clean.

Thursday, 9 February 2012

Mount Amazon S3 storage as local filesystem on Ubuntu server

Amazon S3 is a high reliability online storage. We have recently started using Amazon S3 in our company as a backup storage for our projects and as a storage for static sites content. Next step-by-step tutorial describes how to mount S3 bucket as a local filesystem using FUSE-based file system s3fs.

1. Install neccessary packages
$ sudo apt-get update
$ sudo apt-get install build-essential libfuse-dev fuse-utils libcurl4-openssl-dev libxml2-dev mime-support
2. Next step is to download archive with latest version of s3fs
$ wget http://s3fs.googlecode.com/files/s3fs-1.61.tar.gz
$ tar xzvf s3fs-1.61.tar.gz
3. Compile sources
$ cd s3fs-1.61/
$ sudo ./configure
$ sudo make
$ sudo make install
4. Allow other users have access to s3 bucket that will be mounted

Edit file /etc/fuse.conf with any text editor, I used vim for this:
$ sudo vim /etc/fuse.conf
And uncomment the following line in the conf file:
...
#user_allow_other

5. Add pare of you Amazon account key id and account access key to /etc/passwd-s3fs file

Edit file /etc/passwd-s3fs and add here string: AWS_ACCESS_KEY_ID:AWS_SECRET_ACCESS_KEY
Where: AWS_ACCESS_KEY_ID -- your amazon account key id
AWS_SECRET_ACCESS_KEY -- your amazon account access key

6. Set permissions to file
$ sudo chmod 0600 /etc/passwd-s3fs    
7. Mount s3 bucket to local filesystem
$ sudo s3fs your_backet_name -o use_cache=/tmp -o allow_other /mnt/s3storage
That's all and now S3 bucket can be used as local filesystem.