1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 05:02:36 +01:00

OAuth from Third Party Client <> Server finished

This commit is contained in:
David Bomba 2019-05-22 13:18:18 +10:00
parent 914496b75a
commit d2a2378f4b
7 changed files with 293 additions and 10 deletions

View File

@ -15,7 +15,7 @@ use App\Http\Controllers\BaseController;
use App\Http\Controllers\Controller;
use App\Jobs\Account\CreateAccount;
use App\Libraries\MultiDB;
use App\Libraries\OAuth;
use App\Libraries\OAuth\OAuth;
use App\Models\User;
use App\Transformers\UserTransformer;
use App\Utils\Traits\UserSessionAttributes;
@ -59,8 +59,9 @@ class LoginController extends BaseController
*/
public function __construct()
{
parent::__construct();
// $this->middleware('guest:user')->except('logout');
}
/**
@ -110,19 +111,17 @@ class LoginController extends BaseController
/**
* Received the returning object from the provider
* which we will use to resolve the user
* which we will use to resolve the user, we return the response in JSON format
*
* @return redirect
* @return json
*/
public function handleProviderCallback(string $provider)
public function handleProviderCallbackApiUser(string $provider)
{
$socialite_user = Socialite::driver($provider)->stateless()->user();
if($user = OAuth::handleAuth($socialite_user, $provider))
{
//Auth::login($user, true);
return $this->itemResponse($user);
//return redirect($this->redirectTo); //todo return USERACCOUNT json
}
else if(MultiDB::checkUserEmailExists($socialite_user->getEmail()))
{
@ -149,4 +148,74 @@ class LoginController extends BaseController
}
/**
* We use this function when OAUTHING via the web interface
*
* @return redirect
*/
public function handleProviderCallback(string $provider)
{
$socialite_user = Socialite::driver($provider)->stateless()->user();
if($user = OAuth::handleAuth($socialite_user, $provider))
{
Auth::login($user, true);
return redirect($this->redirectTo)
}
else if(MultiDB::checkUserEmailExists($socialite_user->getEmail()))
{
Session::flash('error', 'User exists in system, but not with this authentication method'); //todo add translations
return view('auth.login');
}
/** 3. Automagically creating a new account here. */
else {
//todo
$name = OAuth::splitName($socialite_user->getName());
$new_account = [
'first_name' => $name[0],
'last_name' => $name[1],
'password' => '',
'email' => $socialite_user->getEmail(),
'oauth_user_id' => $socialite_user->getId(),
'oauth_provider_id' => $provider
];
$account = CreateAccount::dispatchNow($new_account);
Auth::login($user, true);
return redirect($this->redirectTo);
}
}
/**
* A client side authentication has taken place.
* We now digest the token and confirm authentication with
* the authentication server, the correct user object
* is returned to us here and we send back the correct
* user object payload - or error.
*
* return User $user
*/
public function oauthApiLogin()
{
$user = false;
$oauth = new OAuth();
$user = $oauth->getProvider(request()->input('provider'))->getTokenResponse(request()->input('token'));
if ($user)
return $this->itemResponse($user);
else
return $this->errorResponse(['message' => 'Invalid credentials'], 401);
}
}

View File

@ -22,6 +22,10 @@ use Laravel\Socialite\Facades\Socialite;
class OAuth
{
protected $provider_instance;
protected $provider_id;
/**
* Socialite Providers
*/
@ -101,4 +105,39 @@ class OAuth
return SOCIAL_BITBUCKET;
}
}
public function getProvider($provider)
{
switch ($provider)
{
case 'google';
$this->provider_instance = new Providers\Google();
$this->provider_id = self::SOCIAL_GOOGLE;
return $this;
default:
return null;
break;
}
}
public function getTokenResponse($token)
{
$user = null;
$payload = $this->provider_instance->getTokenResponse($token);
$oauthUserId = $this->provider_instance->harvestSubField($payload);
LookupUser::setServerByField('oauth_user_key', $this->providerId . '-' . $oauthUserId);
if($this->provider_instance)
$user = User::where('oauth_user_id', $oauthUserId)->where('oauth_provider_id', $this->provider_id)->first();
if ($user)
return $user;
else
return false;
}
}

View File

@ -0,0 +1,140 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Libraries\OAuth;
use App\Libraries\MultiDB;
use App\Ninja\OAuth\Providers\Google;
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(object $user, string $provider)
{
/** 1. Ensure user arrives on the correct provider **/
$query = [
'oauth_user_id' =>$user->getId(),
'oauth_provider_id'=>$provider
];
if($user = MultiDB::hasUser($query))
{
return $user;
}
else
return false;
}
/* Splits a socialite user name into first and last names */
public static function splitName($name)
{
$name = trim($name);
$last_name = (strpos($name, ' ') === false) ? '' : preg_replace('#.*\s([\w-]*)$#', '$1', $name);
$first_name = trim(preg_replace('#' . preg_quote($last_name, '/') . '#', '', $name));
return [$first_name, $last_name];
}
public static function providerToString(int $social_provider) : string
{
switch ($social_provider)
{
case SOCIAL_GOOGLE:
return 'google';
case SOCIAL_FACEBOOK:
return 'facebook';
case SOCIAL_GITHUB:
return 'github';
case SOCIAL_LINKEDIN:
return 'linkedin';
case SOCIAL_TWITTER:
return 'twitter';
case SOCIAL_BITBUCKET:
return 'bitbucket';
}
}
public static function providerToInt(string $social_provider) : int
{
switch ($social_provider)
{
case 'google':
return SOCIAL_GOOGLE;
case 'facebook':
return SOCIAL_FACEBOOK;
case 'github':
return SOCIAL_GITHUB;
case 'linkedin':
return SOCIAL_LINKEDIN;
case 'twitter':
return SOCIAL_TWITTER;
case 'bitbucket':
return SOCIAL_BITBUCKET;
}
}
public function getProvider($provider)
{
switch ($provider)
{
case 'google';
$this->provider_instance = new Google();
$this->provider_id = self::SOCIAL_GOOGLE;
return $this;
default:
return null;
break;
}
}
public function getTokenResponse($token)
{
$user = false;
$payload = $this->provider_instance->getTokenResponse($token);
$oauth_user_id = $this->provider_instance->harvestSubField($payload);
$data = [
'oauth_user_id' => $oauth_user_id,
'oauth_provider_id' => $this->provider_id
];
if($this->provider_instance)
$user = MultiDB::hasUser($data);
return $user;
}
}

View File

@ -0,0 +1,22 @@
<?php namespace App\Ninja\OAuth\Providers;
class Google implements ProviderInterface
{
public function getTokenResponse($token)
{
$client = new \Google_Client();
return $client->verifyIdToken($token);
}
public function harvestEmail($payload)
{
return $payload['email'];
}
public function harvestSubField($payload)
{
return $payload['sub']; // user ID
}
}

View File

@ -0,0 +1,9 @@
<?php namespace App\Ninja\OAuth\Providers;
interface ProviderInterface
{
public function getTokenResponse($token);
public function harvestEmail($response);
}

View File

@ -218,8 +218,8 @@ class CreateUsersTable extends Migration
$table->integer('theme_id')->nullable();
$table->smallInteger('failed_logins')->nullable();
$table->string('referral_code')->nullable();
$table->string('oauth_user_id',100)->nullable()->unique();
$table->unsignedInteger('oauth_provider_id')->nullable()->unique();
$table->string('oauth_user_id',100)->nullable();
$table->string('oauth_provider_id')->nullable();
$table->string('google_2fa_secret')->nullable();
$table->string('accepted_terms_version')->nullable();
$table->string('avatar', 100)->default('');
@ -233,6 +233,9 @@ class CreateUsersTable extends Migration
$table->timestamps(6);
$table->softDeletes();
$table->unique(['oauth_user_id', 'oauth_provider_id']);
// $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
});

View File

@ -22,7 +22,8 @@ Route::group(['middleware' => ['api_secret_check']], function () {
Route::post('api/v1/signup', 'AccountController@store')->name('signup.submit');
Route::post('api/v1/login', 'Auth\LoginController@apiLogin')->name('login.submit');
Route::post('api/v1/oauth_login', 'Auth\LoginController@oauthApiLogin');
});