1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-09 20:52:56 +01:00

Working on Social Authentication (#2458)

* Laravel Socialite + Refactor for searching for Users across multiple databases

* Refactor for Unique User Rules, add Middleware for setting active DB connection per request, more tests
This commit is contained in:
David Bomba 2018-10-18 16:04:36 +11:00 committed by GitHub
parent 62e2444a2c
commit f745c3f0a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 685 additions and 40 deletions

View File

@ -22,8 +22,13 @@ if (! defined('APP_NAME')) {
define('TEST_CLIENTNAME', env('TEST_CLIENTNAME', 'client@example.com'));
define('TEST_PASSWORD', 'password');
define('BANK_LIBRARY_OFX', 1);
define('MULTI_DBS', serialize(['db-ninja-1', 'db-ninja-2']));
define('SOCIAL_GOOGLE', 'Google');
define('SOCIAL_FACEBOOK', 'Facebook');
define('SOCIAL_GITHUB', 'GitHub');
define('SOCIAL_LINKEDIN', 'LinkedIn');
define('SOCIAL_TWITTER', 'Twitter');
define('SOCIAL_BITBUCKET', 'Bitbucket');
}

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Laravel\Socialite\Facades\Socialite;
class LoginController extends Controller
{
@ -36,4 +37,16 @@ class LoginController extends Controller
{
$this->middleware('guest:user')->except('logout');
}
public function redirectToProvider($provider)
{
return Socialite::driver($provider)->redirect();
}
public function handleProviderCallback($provider)
{
$user = Socialite::driver('github')->user();
dd($user);
}
}

View File

@ -2,8 +2,6 @@
namespace App\Http\Controllers;
use App\Http\Requests\SignupRequest;
class HomeController extends Controller
{
/**
@ -33,14 +31,5 @@ class HomeController extends Controller
return view('dashboard.index');
}
public function signup()
{
return view('signup.index');
}
public function processSignup(SignupRequest $request)
{
dd($request->validated());
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\SignupRequest;
/**
* Class SignupController
* @package App\Http\Controllers
*/
class SignupController extends Controller
{
/**
* SignupController constructor.
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function signup()
{
return view('signup.index');
}
/**
* @param SignupRequest $request
*/
public function processSignup(SignupRequest $request)
{
dd($request->validated());
}
}

View File

@ -41,6 +41,9 @@ class Kernel extends HttpKernel
'throttle:60,1',
'bindings',
],
'db' => [
],
];
/**

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use App\Libraries\MultiDB;
use Closure;
class setDb
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (!config('auth.providers.users.driver') == 'eloquent') {
//MultiDB::setDB(auth()->user()->)
}
return $next($request);
}
}

View File

@ -2,6 +2,7 @@
namespace App\Http\ValidationRules;
use App\Libraries\MultiDB;
use App\Models\User;
use Illuminate\Contracts\Validation\Rule;
@ -10,7 +11,7 @@ class UniqueUserRule implements Rule
public function passes($attribute, $value)
{
return $this->checkIfEmailExists($value);
return ! $this->checkIfEmailExists($value); //if it exists, return false!
}
public function message()
@ -20,23 +21,7 @@ class UniqueUserRule implements Rule
private function checkIfEmailExists($email) : bool
{
if (config('auth.providers.users.driver') == 'eloquent') //default eloquent = single DB
{
return User::where(['email' => $email])->get()->count() == 0 ?? false; // true -> 0 emails found / false -> >=1 emails found
}
//multi-db active
foreach (unserialize(MULTI_DBS) as $db)
{
if(User::on($db)->where(['email' => $email])->get()->count() >=1) // if user already exists, validation will fail
return false;
}
return true;
return MultiDB::checkUserEmailExists($email);
}
}

71
app/Libraries/MultiDB.php Normal file
View File

@ -0,0 +1,71 @@
<?php
namespace App\Libraries;
use App\Models\User;
/**
* Class MultiDB
* @package App\Libraries
*/
class MultiDB
{
/**
* @param $email
* @return bool
*/
public static function checkUserEmailExists($email) : bool
{
if (config('auth.providers.users.driver') == 'eloquent') //default eloquent = single DB
{
return User::where(['email' => $email])->get()->count() >= 1 ?? false; // true >= 1 emails found / false -> == emails found
}
//multi-db active
foreach (unserialize(MULTI_DBS) as $db)
{
if(User::on($db)->where(['email' => $email])->get()->count() >=1) // if user already exists, validation will fail
return true;
}
return false;
}
/**
* @param array $data
* @return bool
*/
public static function getUser(array $data)
{
if (config('auth.providers.users.driver') == 'eloquent') //default eloquent = single DB
{
return User::where($data)->first();
}
foreach (unserialize(MULTI_DBS) as $db)
{
self::setDB($db);
$user = User::where($data)->first();
if($user)
return $user;
}
return false;
}
/**
* @param $database
*/
public static function setDB($database) : void
{
/* This will set the default configuration for the request */
config(['database.default' => $database]);
app('db')->connection(config('database.connections.database.'.$database));
}
}

35
app/Libraries/OAuth.php Normal file
View File

@ -0,0 +1,35 @@
<?php
namespace App\Libraries;
use Laravel\Socialite\Facades\Socialite;
/**
* Class OAuth
* @package App\Libraries
*/
class OAuth
{
/**
* Socialite Providers
*/
const SOCIAL_GOOGLE = 1;
const SOCIAL_FACEBOOK = 2;
const SOCIAL_GITHUB = 3;
const SOCIAL_LINKEDIN = 4;
const SOCIAL_TWITTER = 5;
const SOCIAL_BITBUCKET = 6;
/**
* @param Socialite $user
*/
public static function handleAuth($user)
{
if(MultiDB::checkUserEmailExists($user->getEmail())) //if email is in the system this is an existing user -> check they are arriving on the correct provider
{
}
}
}

View File

@ -208,7 +208,7 @@ class MultiDatabaseUserProvider implements UserProvider
private function setDefaultDatabase($id = false, $email = false, $token = false) : void
{
$databases = ['db-ninja-1', 'db-ninja-2'];
$databases = unserialize(MULTI_DBS);
foreach ($databases as $database) {
$this->setDB($database);

View File

@ -23,6 +23,7 @@
"davejamesmiller/laravel-breadcrumbs": "5.x",
"fideloper/proxy": "^4.0",
"laravel/framework": "5.7.*",
"laravel/socialite": "^3.1",
"laravel/tinker": "^1.0",
"spatie/laravel-html": "^2.19",
"webpatser/laravel-countries": "dev-master#75992ad"

359
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c9e2e008fc8ade0315e2c39e1934152d",
"content-hash": "0a3a5aeaabd9e3362e0da55be647367c",
"packages": [
{
"name": "asgrim/ofxparser",
@ -481,6 +481,187 @@
],
"time": "2018-02-07T20:20:57+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "6.3.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"shasum": ""
},
"require": {
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.4",
"php": ">=5.5"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.0"
},
"suggest": {
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.3-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle is a PHP HTTP client library",
"homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"rest",
"web service"
],
"time": "2018-04-22T15:46:56+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "v1.3.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
"shasum": ""
},
"require": {
"php": ">=5.5.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"time": "2016-12-20T10:07:11+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Schultze",
"homepage": "https://github.com/Tobion"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"request",
"response",
"stream",
"uri",
"url"
],
"time": "2017-03-20T17:10:46+00:00"
},
{
"name": "jakub-onderka/php-console-color",
"version": "v0.2",
@ -709,6 +890,69 @@
],
"time": "2018-10-09T13:28:28+00:00"
},
{
"name": "laravel/socialite",
"version": "v3.1.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
"reference": "65f771ff4f266ebae23de1a3f18efd80a1da2f8d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/socialite/zipball/65f771ff4f266ebae23de1a3f18efd80a1da2f8d",
"reference": "65f771ff4f266ebae23de1a3f18efd80a1da2f8d",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "~6.0",
"illuminate/contracts": "~5.4",
"illuminate/http": "~5.4",
"illuminate/support": "~5.4",
"league/oauth1-client": "~1.0",
"php": ">=5.6.4"
},
"require-dev": {
"mockery/mockery": "~0.9",
"phpunit/phpunit": "~4.0|~5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
},
"laravel": {
"providers": [
"Laravel\\Socialite\\SocialiteServiceProvider"
],
"aliases": {
"Socialite": "Laravel\\Socialite\\Facades\\Socialite"
}
}
},
"autoload": {
"psr-4": {
"Laravel\\Socialite\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.",
"homepage": "https://laravel.com",
"keywords": [
"laravel",
"oauth"
],
"time": "2018-08-16T12:51:21+00:00"
},
{
"name": "laravel/tinker",
"version": "v1.0.8",
@ -856,6 +1100,69 @@
],
"time": "2018-10-15T13:53:10+00:00"
},
{
"name": "league/oauth1-client",
"version": "1.7.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/oauth1-client.git",
"reference": "fca5f160650cb74d23fc11aa570dd61f86dcf647"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/fca5f160650cb74d23fc11aa570dd61f86dcf647",
"reference": "fca5f160650cb74d23fc11aa570dd61f86dcf647",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.0",
"php": ">=5.5.0"
},
"require-dev": {
"mockery/mockery": "^0.9",
"phpunit/phpunit": "^4.0",
"squizlabs/php_codesniffer": "^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"League\\OAuth1\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ben Corlett",
"email": "bencorlett@me.com",
"homepage": "http://www.webcomm.com.au",
"role": "Developer"
}
],
"description": "OAuth 1.0 Client Library",
"keywords": [
"Authentication",
"SSO",
"authorization",
"bitbucket",
"identity",
"idp",
"oauth",
"oauth1",
"single sign on",
"trello",
"tumblr",
"twitter"
],
"time": "2016-08-17T00:36:58+00:00"
},
{
"name": "monolog/monolog",
"version": "1.23.0",
@ -1195,6 +1502,56 @@
],
"time": "2017-02-14T16:28:37+00:00"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "psr/log",
"version": "1.0.2",

View File

@ -39,5 +39,38 @@ return [
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
],
'stripe' => [
'model' => 'User',
'secret' => '',
],
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => env('GITHUB_OAUTH_REDIRECT'),
],
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_OAUTH_REDIRECT'),
],
'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => env('FACEBOOK_OAUTH_REDIRECT'),
],
'linkedin' => [
'client_id' => env('LINKEDIN_CLIENT_ID'),
'client_secret' => env('LINKEDIN_CLIENT_SECRET'),
'redirect' => env('LINKEDIN_OAUTH_REDIRECT'),
],
'twitter' => [
'client_id' => env('TWITTER_CLIENT_ID'),
'client_secret' => env('TWITTER_CLIENT_SECRET'),
'redirect' => env('TWITTER_OAUTH_REDIRECT'),
],
'bitbucket' => [
'client_id' => env('BITBUCKET_CLIENT_ID'),
'client_secret' => env('BITBUCKET_CLIENT_SECRET'),
'redirect' => env('BITBUCKET_OAUTH_REDIRECT'),
],
];

View File

@ -17,6 +17,11 @@ Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login', 'Auth\LoginController@login');
Route::post('logout', 'Auth\LoginController@logout')->name('logout');
// Social authentication
Route::get('auth/{provider}', 'Auth\LoginController@redirectToProvider');
Route::get('auth/{provider}/callback', 'Auth\LoginController@handleProviderCallback');
// Password Reset Routes...
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
@ -30,8 +35,8 @@ Open Routes
Route::redirect('/', '/login', 301);
Route::get('/signup', 'HomeController@signup')->name('signup');
Route::post('/process_signup', 'HomeController@processSignup')->name('signup.submit');
Route::get('/signup', 'SignupController@signup')->name('signup');
Route::post('/process_signup', 'SignupController@processSignup')->name('signup.submit');
Route::get('/contact/login', 'Auth\ContactLoginController@showLoginForm')->name('contact.login');
Route::post('/contact/login', 'Auth\ContactLoginController@login')->name('contact.login.submit');
@ -40,7 +45,7 @@ Route::post('/contact/login', 'Auth\ContactLoginController@login')->name('contac
/*
Authenticated User Routes
*/
Route::group(['middleware' => ['auth:user']], function () {
Route::group(['middleware' => ['auth:user', 'db']], function () {
Route::get('/dashboard', 'HomeController@user')->name('user.dashboard');
Route::get('/logout', 'Auth\LoginController@logout')->name('user.logout');

View File

@ -0,0 +1,82 @@
<?php
namespace Tests\Unit;
use App\Libraries\MultiDB;
use App\Models\User;
use Illuminate\Foundation\Testing\Concerns\InteractsWithDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;
/**
* @test
* @covers App\Libraries\MultiDB
*
* Proves that we can reliably switch database connections at runtime
*
*/
class MultiDBUserTest extends TestCase
{
//use DatabaseMigrations;
use InteractsWithDatabase;
public function setUp()
{
parent::setUp();
if (config('auth.providers.users.driver') == 'eloquent')
$this->markTestSkipped('Multi DB not enabled - skipping');
User::unguard();
$user = [
'first_name' => 'user_db_1',
'last_name' => 'user_db_1-s',
'phone' => '55555',
'email_verified_at' => now(),
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
'remember_token' => str_random(10),
'email' => 'db1@example.com',
'oauth_user_id' => '123'
];
$user2 = [
'first_name' => 'user_db_2',
'last_name' => 'user_db_2-s',
'phone' => '55555',
'email_verified_at' => now(),
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
'remember_token' => str_random(10),
'email' => 'db2@example.com',
'oauth_user_id' => 'abc'
];
User::on('db-ninja-1')->create($user);
User::on('db-ninja-2')->create($user2);
}
public function test_oauth_user_db2_exists()
{
$user = MultiDB::getUser(['email' => 'db2@example.com', 'oauth_user_id' => 'abc']);
$this->assertEquals($user->email, 'db2@example.com');
}
public function test_oauth_user_db1_exists()
{
$user = MultiDB::getUser(['email' => 'db1@example.com', 'oauth_user_id' => '123']);
$this->assertEquals($user->email, 'db1@example.com');
}
public function tearDown()
{
DB::connection('db-ninja-1')->table('users')->delete();
DB::connection('db-ninja-2')->table('users')->delete();
}
}

View File

@ -5,6 +5,7 @@ namespace Tests\Unit;
use App\Http\ValidationRules\UniqueUserRule;
use App\Models\User;
use Illuminate\Foundation\Testing\Concerns\InteractsWithDatabase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;
@ -15,7 +16,8 @@ use Tests\TestCase;
*/
class UniqueEmailTest extends TestCase
{
use InteractsWithDatabase;
//use InteractsWithDatabase;
//use DatabaseMigrations;
protected $rule;
@ -58,8 +60,8 @@ class UniqueEmailTest extends TestCase
public function tearDown()
{
// DB::connection('db-ninja-1')->table('users')->delete();
// DB::connection('db-ninja-2')->table('users')->delete();
DB::connection('db-ninja-1')->table('users')->delete();
DB::connection('db-ninja-2')->table('users')->delete();
}
}