RSS
 

Archive for October, 2016

CakePHP and connecting to GitHub API

22 Oct

I had to write a small tool to automate releasing certain GitHub repositories, and for that to authenticate I had to connect to GitHub API.
The integration wasn’t super-easy as there was no documentation yet on how this could be done. But I finally figured it out and want to share it.

HybridAuth plugin

I first introduce the plugin pretty quick I chose to use as authentication piece.
HybridAuth is maintained by a CakePHP core developer and bridges the original HybridAuth implementation into CakePHP. That library aims to "act as an abstract API between your application and various social APIs and identities providers". Out of the box it provides quite a few very popular services to connect to.

Getting started

I did install the plugin as documented, I also made sure the Migration file for it has been included because
we do need a "social_profiles" table here.

Then I connected the Users and SocialProfiles table:

    /**
     * @param array $config The configuration for the Table.
     *
     * @return void
     */
    public function initialize(array $config) {
        parent::initialize($config);
        ...
        $this->hasMany('ADmad/HybridAuth.SocialProfiles');
        EventManager::instance()->on('HybridAuth.newUser', [$this, 'createUser']);
    }
    /**
     * @param \Cake\Event\Event $event
     *
     * @throws \RuntimeException
     *
     * @return \App\Model\Entity\User
     */
    public function createUser(Event $event) {
        // Entity representing record in social_profiles table
        $profile = $event->data()['profile'];
        $username = substr($profile->profile_url, strrpos($profile->profile_url, '/') + 1);
        $user = $this->newEntity(
            [
                'username' => $username,
                'email' => $profile->email
            ]
        );
        $result = $this->save($user);
        if (!$result) {
            throw new \RuntimeException('Unable to save new user:' . print_r($user->errors(), true));
        }
        return $result;
    }

I used the "profile_url" data to automatically generate the same user on my website.
Since the login was only allowed via GitHub login, there was no change of collision.

Then I made sure the HybridAuth authentication adapter is added to the list of components in the AppController:

    /*
     * @return \Cake\Network\Response|null|void
     */
    public function initialize() {
        parent::initialize();
        ...
        $this->loadComponent('TinyAuth.Auth', [
            'authenticate' => [
                'Form',
                'ADmad/HybridAuth.HybridAuth',
            ],
        ]);
    }

I also modified the login according to the documentation.

And finally I just needed a link in the navigation menu in the case the user is not logged in yet:

echo $this->Html->link(
    'Login with GitHub',
    ['plugin' => false, 'prefix' => false, 'controller' => 'Account', 'action' => 'login', 
        '?' => ['provider' => 'Github', 'redirect' => $this->request->query('redirect')]
    ]
);

Note that the "redirect" query string is only necessary for CakePHP 3.4+ when the session is not used anymore for remembering the location to redirect to after login. And also note that at this point only a "dev" branch of the plugin supports the 3.4+ version yet.

Figuring out the configuration

Now that was the most difficult part. With a lot of debugging I found out that since the GitHub provider is not one of the core ones I need to provider wrapper path and class here:

    'HybridAuth' => [
        'providers' => [
            'Github' => [
                'enabled' => true,
                'keys' => [
                    'id' => env('AUTH_ID_GITHUB', ''),
                    'secret' => env('AUTH_SECRET_GITHUB', '')
                ],
                'wrapper' => [
                    'path' => ROOT . '/vendor/hybridauth/hybridauth/additional-providers/hybridauth-github/Providers/GitHub.php',
                    'class' => 'Hybrid_Providers_GitHub'
                ],
                'scope' => 'user:email,repo'
            ]
        ],
        'debug_mode' => false,
        'debug_file' => LOGS . 'hybridauth.log',
    ],

Note that I also set custom "scope" permissions here, you can however here leave that out or add more.

Small tweaks

I didn’t want to use the the plugin controller action to authenticate, but my own in AccountController (in order to execute a few custom things upon login).
So I just overwrote the hauth_return_toURL:

'hauth_return_to' => [
	'controller' => 'Account', 'action' => 'authenticated', 'plugin' => false, 'prefix' => false
]

Testing

Yeah, ok, here I did cheat.
The hybridauth library has a little flaw that makes it difficult to connect to CakePHP as plugin: It always forces the session to be started right away. Especially when testing the controllers now this can be super annoying as it throws ugly warnings:

..Warning Error: session_start(): Cannot send session cookie - headers already sent by 
(output started at phar:///home/vagrant/Apps/.../phpunit.phar/phpunit/Util/Printer.php:134) 
in [/home/vagrant/Apps/.../vendor/hybridauth/hybridauth/hybridauth/Hybrid/Storage.php, line 20]

So I just added the adapter when not in CLI mode:

if (PHP_SAPI !== 'cli') {
	$authenticate['ADmad/HybridAuth.HybridAuth'] = [
		...
	];
}
$this->Auth->config('authenticate', $authenticate);

Tests are green again 🙂

In short

All in all HybridAuth is a great CakePHP plugin to connect this HybridAuth library and any OpenID and OAuth authenticated service to your application.
Give it a spin!

Besides the here mentioned GitHub provider I also managed to use Facebook/Google sign-in this way in another app.
Basically all apps, if for technical users or more a social network, can benefit from such a one-click login as it really takes away the pain of double-opt-in registration forms and alike.

 
No Comments

Posted in CakePHP

 

CakePHP Tips Fall 2016

18 Oct

Always use proper version check method

Be aware of version checks that are using the inferior and even wrong </> comparison.
This can lead to wrong conditions with higher minor versions.

I had this in my code from the early 2.x days:

if ((float)Configure::version() < 2.4) {
    ...
}

With the upcoming 2.10 release this will now suddenly start to return true. Luckily my 2.next tests already revealed that and I was able to fix it up properly:

if (version_compare(Configure::version(), '2.4') < 0) {
    ...
}

This will now work in all minors of 2.x.

The same is true for 3.x, of course, even though we are "only" at 3.3.6 at this point in time yet.

Order your flash messages semantically

You can use the Flash plugin to enhance your flash messages a bit.
It works out of the box with your current setup, but adds a few useful things on top:

  • Prevent size and output issues when running into a redirect loop boiling up messages in the session (>> 50) by limiting the stack to 10 by default.
  • Use ordered output (error first, then warning, success and lastly info). Errors should be visible first in case multiple types are to be displayed.

Switch away from basic log files

I would switch away from basic log files to database – even if it is just a simple SQLite one like DebugKit uses.
This way you can more easily navigate and filter through them.
Check out the DatabaseLog plugin which can do exactly that in just a few minutes of configuration.
For not just personal fun apps it is advised to look into a bit more sophisticated approaches, like using Monolog and services like NewRelic to write to. But for smaller projects it can be enough to have a small admin backend to filter through the logs, especially error and warning types.

Keep your controller code lean

That specifically includes all those verbose allow statements to make certain pages public:

use Cake\Event\Event; 
...
 	/**
	 * @return void
	 */
	public function beforeFilter(Event $event) {
		parent::beforeFilter($event);
		$this->Auth->allow('index');
	}

See TinyAuth which since recently allows to more cleanly declare this with a single INI file in your src/config folder.
The complete code above goes away for every controller 🙂

As long as not needed (custom dynamically made decisions based on session or alike) this kind of noise should be kept out of the controllers.
This makes it also possible to modify the public access to actions without the need to be a developer. The next step could be to make the input for TinyAuth database-driven and alike.

 
No Comments

Posted in CakePHP

 

Use 3.x Migrations for your 2.x CakePHP app

03 Oct

In this post I reveal one of my tricks on how to leverage 3.x power in a legacy 2.x project.

You might have already read on how to use some of the splits, like the ORM, in 2.x projects.
Today I want to talk about migration as a topic.

Status Quo

I do have to maintain two remaining CakePHP 2.x apps that have been too large to just upgrade yet.
And time and budget was not on my side so far.
In 2.x there was also not a real powerful database migration tool available so far.

Let’s use the 3.x Migrations plugin

We can use the Migrations plugin quite easily in all 2.x apps do all database modification this way.

First we create a subfolder in your 2.x root folder, let’s call it /upgrade.
This will contain a standalone 3.x app including the Migrations plugin as dependency.
In my case the composer.json looks like this:

	"require": {
		"cakephp/cakephp": "^3.3",
		"cakephp/migrations": "^1.6",
		"dereuromark/cakephp-setup": "^1.0",
		"dereuromark/cakephp-tools": "^1.1"
	},
	"require-dev": {
		"cakephp/bake": "^1.2",
		"cakephp/debug_kit": "^3.2"
	},

Since I include cakephp/bake, I can also leverage the Bake plugin to generate the necessary migration file.

Any time I need a new migration file I simply go to the subfolder and use the 3.x shell:

cd upgrade
bin/cake bake migration CreateArticles ...

Of course you can now modify it further and once complete commit this file into version control.

On the server your deployment script just also needs to contain the following lines then to fully automate it:

cd upgrade
composer install --prefer-dist --no-dev --optimize-autoloader --no-interaction
chmod +x bin/cake
bin/cake Migrations migrate
cd ..

Once you upgrade to 3.x you can move all migration files to the actual place in your app, remove the subfolder and simplify the deployment lines to just the single command 🙂

At least I now have to only remember one way to do migrations, for all 3.x and the old 2.x apps. And I can benefit from all recent improvements in those plugins even in those old apps.

Upgrading from existing 2.x migration tool?

You might be using a 2.x tool like this already, but upgrading to the state of the art 3.x Migrations plugin hotness is not a problem here, either.

Just create a dump of the current schema, put it into an SQL file and include that in your first Migrations file:

public function change() {
	$sql = file_get_contents('dump.sql');
	$this->query($sql);
}

Make sure you mark it as migrated, so it is not accidentally executed again.
As alternative you could build in a switch to auto-detect if one of the tables already exists:

$exists = $this->hasTable('users');
if ($exists) {
	return;
}
...

Tips

Deploy admin user along with the schema

In some cases it can make sense to provide a basic admin login along with the first initial migration:

$data = [
	[
		'username' => 'admin',
		'email' => 'admin@example.com',
		'password' => '...' // Must be changed right afterwards
	]
];
$table = $this->table('users');
$table->insert($data)->save();

For more, it is advised to leverage the seed functionality the Migrations plugin ships with.

Further goodies

As you can see, I also included my Setup and Tools plugin, which in v3 also contain very useful and powerful tools for database maintenance and alike. I can now also leverage them and do not have to backport everything to 2.x anymore.
The same would be true for any such plugin and will help you save time for other things if you can focus on development for the 3.x branch only.

Bottom line

Every pre-3.x project should definitly have a subfolder which runs an up-to-date CakePHP 3.x shell including all useful and required plugin shells.

Feel free to share your ideas and experiences on advancing slowly towards 3.x as comments.
See my old post about it for some more details on how to share the credentials, so you can keep them DRY in your main app config.

 
No Comments

Posted in CakePHP