Merge pull request #7540 from CirkaN/Cirkovic/INA-12

Cirkovic/ina 12
This commit is contained in:
David Bomba 2022-06-12 17:13:56 +10:00 committed by GitHub
commit 19216934a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 213 additions and 71 deletions

View File

@ -61,3 +61,11 @@ SENTRY_LARAVEL_DSN=https://39389664f3f14969b4c43dadda00a40b@sentry2.invoicing.co
GOOGLE_PLAY_PACKAGE_NAME= GOOGLE_PLAY_PACKAGE_NAME=
APPSTORE_PASSWORD= APPSTORE_PASSWORD=
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_REDIRECT_URI=
APPLE_CLIENT_ID=
APPLE_CLIENT_SECRET=
APPLE_REDIRECT_URI=

View File

@ -92,7 +92,7 @@ class LoginController extends BaseController
* @return void * @return void
* deprecated .1 API ONLY we don't need to set any session variables * deprecated .1 API ONLY we don't need to set any session variables
*/ */
public function authenticated(Request $request, User $user) : void public function authenticated(Request $request, User $user): void
{ {
//$this->setCurrentCompanyId($user->companies()->first()->account->default_company_id); //$this->setCurrentCompanyId($user->companies()->first()->account->default_company_id);
} }
@ -250,7 +250,7 @@ class LoginController extends BaseController
// $truth->setCompanyToken(CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $user->account->default_company->id)->first()); // $truth->setCompanyToken(CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $user->account->default_company->id)->first());
/*On the hosted platform, only owners can login for free/pro accounts*/ /*On the hosted platform, only owners can login for free/pro accounts*/
if(Ninja::isHosted() && !$cu->first()->is_owner && !$user->account->isEnterpriseClient()) if (Ninja::isHosted() && !$cu->first()->is_owner && !$user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
event(new UserLoggedIn($user, $user->account->default_company, Ninja::eventVars($user->id))); event(new UserLoggedIn($user, $user->account->default_company, Ninja::eventVars($user->id)));
@ -317,7 +317,7 @@ class LoginController extends BaseController
{ {
$truth = app()->make(TruthSource::class); $truth = app()->make(TruthSource::class);
if($truth->getCompanyToken()) if ($truth->getCompanyToken())
$company_token = $truth->getCompanyToken(); $company_token = $truth->getCompanyToken();
else else
$company_token = CompanyToken::where('token', $request->header('X-API-TOKEN'))->first(); $company_token = CompanyToken::where('token', $request->header('X-API-TOKEN'))->first();
@ -325,21 +325,20 @@ class LoginController extends BaseController
$cu = CompanyUser::query() $cu = CompanyUser::query()
->where('user_id', $company_token->user_id); ->where('user_id', $company_token->user_id);
if($cu->count() == 0) if ($cu->count() == 0)
return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400);
$cu->first()->account->companies->each(function ($company) use($cu, $request){ $cu->first()->account->companies->each(function ($company) use ($cu, $request) {
if($company->tokens()->where('is_system', true)->count() == 0) if ($company->tokens()->where('is_system', true)->count() == 0) {
{
CreateCompanyToken::dispatchNow($company, $cu->first()->user, $request->server('HTTP_USER_AGENT')); CreateCompanyToken::dispatchNow($company, $cu->first()->user, $request->server('HTTP_USER_AGENT'));
} }
}); });
if($request->has('current_company') && $request->input('current_company') == 'true') if ($request->has('current_company') && $request->input('current_company') == 'true')
$cu->where("company_id", $company_token->company_id); $cu->where("company_id", $company_token->company_id);
if(Ninja::isHosted() && !$cu->first()->is_owner && !$cu->first()->user->account->isEnterpriseClient()) if (Ninja::isHosted() && !$cu->first()->is_owner && !$cu->first()->user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403); return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->refreshResponse($cu); return $this->refreshResponse($cu);
@ -359,24 +358,134 @@ class LoginController extends BaseController
*/ */
public function oauthApiLogin() public function oauthApiLogin()
{ {
$message = 'Provider not supported';
if (request()->input('provider') == 'google') { if (request()->input('provider') == 'google') {
return $this->handleGoogleOauth(); return $this->handleGoogleOauth();
} elseif (request()->input('provider') == 'microsoft') {
if (request()->has('token')) {
return $this->handleSocialiteLogin('microsoft', request()->get('token'));
} else {
$message = 'Bearer token missing for the microsoft login';
}
} elseif (request()->input('provider') == 'apple') {
if (request()->has('token')) {
return $this->handleSocialiteLogin('apple', request()->get('token'));
} else {
$message = 'Token is missing for the apple login';
}
} }
return response() return response()
->json(['message' => 'Provider not supported'], 400) ->json(['message' => $message], 400)
->header('X-App-Version', config('ninja.app_version')) ->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_version')); ->header('X-Api-Version', config('ninja.minimum_client_version'));
} }
private function hydrateCompanyUser() :Builder private function getSocialiteUser(string $provider, string $token)
{
return Socialite::driver($provider)->userFromToken($token);
}
private function handleSocialiteLogin($provider, $token)
{
$user = $this->getSocialiteUser($provider, $token);
if ($user) {
return $this->loginOrCreateFromSocialite($user, $provider);
}
return response()
->json(['message' => ctrans('texts.invalid_credentials')], 401)
->header('X-App-Version', config('ninja.app_version'))
->header('X-Api-Version', config('ninja.minimum_client_version'));
}
private function loginOrCreateFromSocialite($user, $provider)
{
$query = [
'oauth_user_id' => $user->id,
'oauth_provider_id' => $provider,
];
if ($existing_user = MultiDB::hasUser($query)) {
if (!$existing_user->account)
return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400);
Auth::login($existing_user, true);
$cu = $this->hydrateCompanyUser();
if ($cu->count() == 0)
return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400);
if (Ninja::isHosted() && !$cu->first()->is_owner && !$existing_user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu);
}
//If this is a result user/email combo - lets add their OAuth details details
if ($existing_login_user = MultiDB::hasUser(['email' => $user->email])) {
if (!$existing_login_user->account)
return response()->json(['message' => 'User exists, but not attached to any companies! Orphaned user!'], 400);
Auth::login($existing_login_user, true);
auth()->user()->update([
'oauth_user_id' => $user->id,
'oauth_provider_id' => $provider,
]);
$cu = $this->hydrateCompanyUser();
if ($cu->count() == 0)
return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400);
if (Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu);
}
$name = OAuth::splitName($user->name);
$new_account = [
'first_name' => $name[0],
'last_name' => $name[1],
'password' => '',
'email' => $user->email,
'oauth_user_id' => $user->id,
'oauth_provider_id' => $provider,
];
MultiDB::setDefaultDatabase();
$account = CreateAccount::dispatchNow($new_account, request()->getClientIp());
Auth::login($account->default_company->owner(), true);
auth()->user()->email_verified_at = now();
auth()->user()->save();
$cu = $this->hydrateCompanyUser();
if ($cu->count() == 0)
return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400);
if (Ninja::isHosted() && !$cu->first()->is_owner && !auth()->user()->account->isEnterpriseClient())
return response()->json(['message' => 'Pro / Free accounts only the owner can log in. Please upgrade'], 403);
return $this->timeConstrainedResponse($cu);
}
private function hydrateCompanyUser(): Builder
{ {
$cu = CompanyUser::query()->where('user_id', auth()->user()->id); $cu = CompanyUser::query()->where('user_id', auth()->user()->id);
if(CompanyUser::query()->where('user_id', auth()->user()->id)->where('company_id', auth()->user()->account->default_company_id)->exists()) if (CompanyUser::query()->where('user_id', auth()->user()->id)->where('company_id', auth()->user()->account->default_company_id)->exists())
$set_company = auth()->user()->account->default_company; $set_company = auth()->user()->account->default_company;
else{ else {
$set_company = $cu->first()->company; $set_company = $cu->first()->company;
} }
@ -392,12 +501,11 @@ class LoginController extends BaseController
if($cu->count() == 0) if($cu->count() == 0)
return $cu; return $cu;
if(auth()->user()->company_users()->count() != auth()->user()->tokens()->distinct('company_id')->count()) if (auth()->user()->company_users()->count() != auth()->user()->tokens()->distinct('company_id')->count()) {
{
auth()->user()->companies->each(function($company){ auth()->user()->companies->each(function ($company) {
if(!CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $company->id)->exists()){ if (!CompanyToken::where('user_id', auth()->user()->id)->where('company_id', $company->id)->exists()) {
CreateCompanyToken::dispatchNow($company, auth()->user(), "Google_O_Auth"); CreateCompanyToken::dispatchNow($company, auth()->user(), "Google_O_Auth");
@ -494,7 +602,7 @@ class LoginController extends BaseController
// $cu = CompanyUser::query() // $cu = CompanyUser::query()
// ->where('user_id', auth()->user()->id); // ->where('user_id', auth()->user()->id);
if($cu->count() == 0) if ($cu->count() == 0)
return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400); return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400);
if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient()) if(Ninja::isHosted() && !$cu->first()->is_owner && !$existing_login_user->account->isEnterpriseClient())

View File

@ -29,6 +29,8 @@ class OAuth
const SOCIAL_LINKEDIN = 4; const SOCIAL_LINKEDIN = 4;
const SOCIAL_TWITTER = 5; const SOCIAL_TWITTER = 5;
const SOCIAL_BITBUCKET = 6; const SOCIAL_BITBUCKET = 6;
const SOCIAL_MICROSOFT = 7;
const SOCIAL_APPLE = 8;
/** /**
* @param Socialite $user * @param Socialite $user
@ -38,8 +40,8 @@ class OAuth
{ {
/** 1. Ensure user arrives on the correct provider **/ /** 1. Ensure user arrives on the correct provider **/
$query = [ $query = [
'oauth_user_id' =>$socialite_user->getId(), 'oauth_user_id' => $socialite_user->getId(),
'oauth_provider_id'=>$provider, 'oauth_provider_id' => $provider,
]; ];
if ($user = MultiDB::hasUser($query)) { if ($user = MultiDB::hasUser($query)) {
@ -54,12 +56,12 @@ class OAuth
{ {
$name = trim($name); $name = trim($name);
$last_name = (strpos($name, ' ') === false) ? '' : preg_replace('#.*\s([\w-]*)$#', '$1', $name); $last_name = (strpos($name, ' ') === false) ? '' : preg_replace('#.*\s([\w-]*)$#', '$1', $name);
$first_name = trim(preg_replace('#'.preg_quote($last_name, '/').'#', '', $name)); $first_name = trim(preg_replace('#' . preg_quote($last_name, '/') . '#', '', $name));
return [$first_name, $last_name]; return [$first_name, $last_name];
} }
public static function providerToString(int $social_provider) : string public static function providerToString(int $social_provider): string
{ {
switch ($social_provider) { switch ($social_provider) {
case SOCIAL_GOOGLE: case SOCIAL_GOOGLE:
@ -74,10 +76,14 @@ class OAuth
return 'twitter'; return 'twitter';
case SOCIAL_BITBUCKET: case SOCIAL_BITBUCKET:
return 'bitbucket'; return 'bitbucket';
case SOCIAL_MICROSOFT:
return 'microsoft';
case SOCIAL_APPLE:
return 'apple';
} }
} }
public static function providerToInt(string $social_provider) : int public static function providerToInt(string $social_provider): int
{ {
switch ($social_provider) { switch ($social_provider) {
case 'google': case 'google':
@ -92,6 +98,10 @@ class OAuth
return SOCIAL_TWITTER; return SOCIAL_TWITTER;
case 'bitbucket': case 'bitbucket':
return SOCIAL_BITBUCKET; return SOCIAL_BITBUCKET;
case 'microsoft':
return SOCIAL_MICROSOFT;
case 'apple':
return SOCIAL_APPLE;
} }
} }
@ -103,7 +113,6 @@ class OAuth
$this->provider_id = self::SOCIAL_GOOGLE; $this->provider_id = self::SOCIAL_GOOGLE;
return $this; return $this;
default: default:
return null; return null;
break; break;

View File

@ -264,9 +264,9 @@ class EventServiceProvider extends ServiceProvider
* @var array * @var array
*/ */
protected $listen = [ protected $listen = [
AccountCreated::class =>[ AccountCreated::class => [
], ],
MessageSending::class =>[ MessageSending::class => [
], ],
MessageSent::class => [ MessageSent::class => [
MailSentListener::class, MailSentListener::class,
@ -312,35 +312,35 @@ class EventServiceProvider extends ServiceProvider
PaymentWasVoided::class => [ PaymentWasVoided::class => [
PaymentVoidedActivity::class, PaymentVoidedActivity::class,
], ],
PaymentWasRestored::class =>[ PaymentWasRestored::class => [
PaymentRestoredActivity::class, PaymentRestoredActivity::class,
], ],
// Clients // Clients
ClientWasCreated::class =>[ ClientWasCreated::class => [
CreatedClientActivity::class, CreatedClientActivity::class,
], ],
ClientWasArchived::class =>[ ClientWasArchived::class => [
ArchivedClientActivity::class, ArchivedClientActivity::class,
], ],
ClientWasUpdated::class =>[ ClientWasUpdated::class => [
ClientUpdatedActivity::class, ClientUpdatedActivity::class,
], ],
ClientWasDeleted::class =>[ ClientWasDeleted::class => [
DeleteClientActivity::class, DeleteClientActivity::class,
], ],
ClientWasRestored::class =>[ ClientWasRestored::class => [
RestoreClientActivity::class, RestoreClientActivity::class,
], ],
// Documents // Documents
DocumentWasCreated::class =>[ DocumentWasCreated::class => [
], ],
DocumentWasArchived::class =>[ DocumentWasArchived::class => [
], ],
DocumentWasUpdated::class =>[ DocumentWasUpdated::class => [
], ],
DocumentWasDeleted::class =>[ DocumentWasDeleted::class => [
], ],
DocumentWasRestored::class =>[ DocumentWasRestored::class => [
], ],
CreditWasCreated::class => [ CreditWasCreated::class => [
CreatedCreditActivity::class, CreatedCreditActivity::class,
@ -593,7 +593,12 @@ class EventServiceProvider extends ServiceProvider
], ],
VendorWasUpdated::class => [ VendorWasUpdated::class => [
VendorUpdatedActivity::class, VendorUpdatedActivity::class,
] ],
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
// ... Manager won't register drivers that are not added to this listener.
\SocialiteProviders\Apple\AppleExtendSocialite::class . '@handle',
\SocialiteProviders\Microsoft\MicrosoftExtendSocialite::class . '@handle',
],
]; ];

View File

@ -77,6 +77,8 @@
"sentry/sentry-laravel": "^2", "sentry/sentry-laravel": "^2",
"setasign/fpdf": "^1.8", "setasign/fpdf": "^1.8",
"setasign/fpdi": "^2.3", "setasign/fpdi": "^2.3",
"socialiteproviders/apple": "^5.2",
"socialiteproviders/microsoft": "^4.1",
"square/square": "13.0.0.20210721", "square/square": "13.0.0.20210721",
"stripe/stripe-php": "^7.50", "stripe/stripe-php": "^7.50",
"symfony/http-client": "^5.2", "symfony/http-client": "^5.2",

View File

@ -80,4 +80,14 @@ return [
'postmark' => [ 'postmark' => [
'token' => env('POSTMARK_SECRET'), 'token' => env('POSTMARK_SECRET'),
], ],
'microsoft' => [
'client_id' => env('MICROSOFT_CLIENT_ID'),
'client_secret' => env('MICROSOFT_CLIENT_SECRET'),
'redirect' => env('MICROSOFT_REDIRECT_URI')
],
'apple' => [
'client_id' => env('APPLE_CLIENT_ID'),
'client_secret' => env('APPLE_CLIENT_SECRET'),
'redirect' => env('APPLE_REDIRECT_URI')
],
]; ];