Skip to content

Commit

Permalink
Implment the need
Browse files Browse the repository at this point in the history
  • Loading branch information
Reza Shadman committed Nov 25, 2015
1 parent c72a03a commit bfbc764
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/vendor/
/.idea/
composer.lock
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# laravel-django-auth
An authentication driver for Laravel5 which allows to authenticate users using their legacy Django PBKDF2 passwords.
# Laravel Django Authentication
An authentication driver for Laravel5 which allows to authenticate users using their legacy Django PBKDF2 passwords.
27 changes: 27 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "jobinja/laravel-djangoable-auth",
"description": "An authentication driver for Laravel5 which allows to authenticate users using their legacy Django PBKDF2",
"license": "GPLv3.0",
"authors": [
{
"name": "Reza Shadman",
"email": "[email protected]"
}
],
"require": {
"php": ">=5.5.9",
"illuminate/database": "5.1.*",
"illuminate/auth" : "5.1.*",
"illuminate/contracts" : "5.1.*",
"illuminate/hashing" : "5.1.*"
},
"require-dev" : {
"mockery/mockery": "~0.9.1",
"phpunit/phpunit": "~4.0"
},
"autoload": {
"psr-4": {
"Jobinja\\Djangoable\\": "src/"
}
}
}
17 changes: 17 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="./tests/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false">
<testsuites>
<testsuite name="Jobinja Djangoable test suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
</phpunit>
52 changes: 52 additions & 0 deletions src/DjangoableDatabaseUserProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php
namespace Jobinja\Djangoable;

use Illuminate\Auth\DatabaseUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;

class DjangoableDatabaseUserProvider extends DatabaseUserProvider implements UserProvider
{
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
$plain = $credentials['password'];

$laravel = $this->hasher->check($plain, $user->getAuthPassword());

if ($laravel) {
return true;
}

$django = $this->hasher->checkForDjango($plain, $user->getAuthPassword());

if ($django) {
if (config('auth.rehash_django', true)) {
$this->updatePassword($user, $plain);
}
return true;
}

return false;
}

/**
* Update user password if he wants
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param $plainPass
*/
public function updatePassword(Authenticatable $user, $plainPass)
{
$field = config('auth.password_field', 'password');
$this->conn->table($this->table)
->where('id', $user->getAuthIdentifier())
->update([$field => $this->hasher->make($plainPass)]);
}
}
51 changes: 51 additions & 0 deletions src/DjangoableEloquentUserProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
namespace Jobinja\Djangoable;

use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;

class DjangoableEloquentUserProvider extends EloquentUserProvider implements UserProvider
{
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
$plain = $credentials['password'];

$laravel = $this->hasher->check($plain, $user->getAuthPassword());

if ($laravel) {
return true;
}

$django = $this->hasher->checkForDjango($plain, $user->getAuthPassword());

if ($django) {
if (config('auth.rehash_django', true)) {
$this->updatePassword($user, $plain);
}
return true;
}

return false;
}

/**
* Update user password if he wants
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param $plainPass
*/
public function updatePassword(Authenticatable $user, $plainPass)
{
$field = config('auth.password_field', 'password');
$user->$field = $this->hasher->make($plainPass);
$user->save();
}
}
76 changes: 76 additions & 0 deletions src/DjangoableHasher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php
namespace Jobinja\Djangoable;

use Illuminate\Hashing\BcryptHasher;

class DjangoableHasher extends BcryptHasher implements DjangoableHasherInterface
{
/**
* Supported hash algos
*
* @var array
*/
private static $supportedHashAlgos = ['md5', 'sha256', 'sha32'];

/**
* Check for django passwords
*
* @param $value
* @param $hashedValue
* @param array $options
* @return bool
*/
public function checkForDjango($value, $hashedValue, array $options = [])
{
if (strlen($value) === 0) {
return false;
}

$exploded = explode('$', $hashedValue);

// If the given hashed value is not in Django we will return false
if (count($exploded) !== 4 || $exploded[0] !== 'pbkdf2_sha256') {
return false;
}

list($algo, $rounds, $salt, $trueHash) = $exploded;

// We check that given django format is supported
if (!$this->algoIsSupported($algo)) {
return false;
}

return hash_equals(
$trueHash,
base64_encode(hash_pbkdf2($this->extractHashAlgo($algo), $value, $salt, $rounds, strlen(base64_decode($trueHash)), true))
);
}

/**
* Ensure that algo is supported
*
* @param $algo
* @return bool
*/
private function algoIsSupported($algo)
{
$exploded = explode('_', $algo);

if (count($exploded) !== 2 || $exploded[0] !== 'pbkdf2' || !in_array($exploded[1], self::$supportedHashAlgos)) {
return false;
}

return true;
}

/**
* Hash algo
*
* @param $algo
* @return string
*/
private function extractHashAlgo($algo)
{
return explode('_', $algo)[1];
}
}
17 changes: 17 additions & 0 deletions src/DjangoableHasherInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
namespace Jobinja\Djangoable;

use Illuminate\Contracts\Hashing\Hasher;

interface DjangoableHasherInterface extends Hasher
{
/**
* Check for django algos
*
* @param $value
* @param $hashedValue
* @param array $options
* @return bool
*/
public function checkForDjango($value, $hashedValue, array $options = []);
}
37 changes: 37 additions & 0 deletions src/DjangoableServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
namespace Jobinja\Djangoable;

use Illuminate\Auth\AuthManager;
use Illuminate\Support\ServiceProvider;

class DjangoableServiceProvider extends ServiceProvider
{
/**
* Boot method
*
* @return void
*/
public function boot()
{
/** @var AuthManager $auth */
$auth = $this->app['auth'];

$auth->extend('djangoable', function () {
return new DjangoableEloquentUserProvider(new DjangoableHasher(), config('auth.model'));
});

$auth->extend('djangoable_database', function () {
return new DjangoableDatabaseUserProvider(new DjangoableHasher(), config('auth.table'));
});
}

/**
* Register the service provider.
*
* @return void
*/
public function register()
{
// TODO: Implement register() method.
}
}
12 changes: 12 additions & 0 deletions tests/DjangoableHasherTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

class DjangoableHasherTest extends PHPUnit_Framework_TestCase
{
public function test_it_can_check_django_passwords()
{
$checker = new \Jobinja\Djangoable\DjangoableHasher();
$django = 'pbkdf2_sha256$20000$oeKBbG1rDmrL$ZQrhj9hRFzy7DQqAFNLAeEVv9W8pKNj0sHtPdnmSzcY=';
$pass = '123456';
$this->assertTrue($checker->checkForDjango($pass, $django));
}
}
3 changes: 3 additions & 0 deletions tests/autoload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

require __DIR__.'/../vendor/autoload.php';

0 comments on commit bfbc764

Please sign in to comment.