mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-11 13:42:49 +01:00
Merge branch 'v5-develop' into v5-stable
This commit is contained in:
commit
953194bb48
@ -1 +1 @@
|
||||
5.4.2
|
||||
5.4.3
|
@ -163,7 +163,7 @@ class CompanySettings extends BaseSettings
|
||||
public $require_quote_signature = false; //@TODO ben to confirm
|
||||
|
||||
//email settings
|
||||
public $email_sending_method = 'default'; //enum 'default','gmail' //@implemented
|
||||
public $email_sending_method = 'default'; //enum 'default','gmail','office365' //@implemented
|
||||
public $gmail_sending_user_id = '0'; //@implemented
|
||||
|
||||
public $reply_to_email = ''; //@implemented
|
||||
|
@ -10,9 +10,10 @@
|
||||
*/
|
||||
namespace App\Helpers\Mail;
|
||||
|
||||
use Illuminate\Mail\MailManager;
|
||||
use App\CustomMailDriver\CustomTransport;
|
||||
use App\Helpers\Mail\Office365MailTransport;
|
||||
use Dacastro4\LaravelGmail\Services\Message\Mail;
|
||||
use Illuminate\Mail\MailManager;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
|
||||
@ -22,4 +23,9 @@ class GmailTransportManager extends MailManager
|
||||
{
|
||||
return new GmailTransport(new Mail);
|
||||
}
|
||||
|
||||
protected function createOffice365Transport()
|
||||
{
|
||||
return new Office365MailTransport();
|
||||
}
|
||||
}
|
301
app/Helpers/Mail/Office365MailTransport.php
Normal file
301
app/Helpers/Mail/Office365MailTransport.php
Normal file
@ -0,0 +1,301 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Helpers\Mail;
|
||||
|
||||
use Illuminate\Mail\Transport\Transport;
|
||||
use Illuminate\Support\Str;
|
||||
use Swift_Mime_SimpleMessage;
|
||||
use Microsoft\Graph\Graph;
|
||||
use Microsoft\Graph\Model\UploadSession;
|
||||
|
||||
class Office365MailTransport extends Transport
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
|
||||
{
|
||||
|
||||
$this->beforeSendPerformed($message);
|
||||
|
||||
$graph = new Graph();
|
||||
$token = $message->getHeaders()->get('GmailToken')->getValue();
|
||||
|
||||
$graph->setAccessToken($token);
|
||||
|
||||
// Special treatment if the message has too large attachments
|
||||
$messageBody = $this->getBody($message, true);
|
||||
$messageBodySizeMb = json_encode($messageBody);
|
||||
$messageBodySizeMb = strlen($messageBodySizeMb);
|
||||
$messageBodySizeMb = $messageBodySizeMb / 1048576; //byte -> mb
|
||||
|
||||
if ($messageBodySizeMb >= 4) {
|
||||
unset($messageBody);
|
||||
$graphMessage = $graph->createRequest("POST", "/users/" . key($message->getFrom()) . "/messages")
|
||||
->attachBody($this->getBody($message))
|
||||
->setReturnType(\Microsoft\Graph\Model\Message::class)
|
||||
->execute();
|
||||
|
||||
foreach ($message->getChildren() as $attachment) {
|
||||
if ($attachment instanceof \Swift_Mime_SimpleMimeEntity) {
|
||||
$fileName = $attachment->getHeaders()->get('Content-Type')->getParameter('name');
|
||||
$content = $attachment->getBody();
|
||||
$fileSize = strlen($content);
|
||||
$size = $fileSize / 1048576; //byte -> mb
|
||||
$id = $attachment->getId();
|
||||
$attachmentMessage = [
|
||||
'AttachmentItem' => [
|
||||
'attachmentType' => 'file',
|
||||
'name' => $fileName,
|
||||
'size' => strlen($content)
|
||||
]
|
||||
];
|
||||
|
||||
if ($size <= 3) { //ErrorAttachmentSizeShouldNotBeLessThanMinimumSize if attachment <= 3mb, then we need to add this
|
||||
$attachmentBody = [
|
||||
"@odata.type" => "#microsoft.graph.fileAttachment",
|
||||
"name" => $attachment->getHeaders()->get('Content-Type')->getParameter('name'),
|
||||
"contentType" => $attachment->getBodyContentType(),
|
||||
"contentBytes" => base64_encode($attachment->getBody()),
|
||||
'contentId' => $id
|
||||
];
|
||||
|
||||
$addAttachment = $graph->createRequest("POST", "/users/" . key($message->getFrom()) . "/messages/" . $graphMessage->getId() . "/attachments")
|
||||
->attachBody($attachmentBody)
|
||||
->setReturnType(UploadSession::class)
|
||||
->execute();
|
||||
} else {
|
||||
//upload the files in chunks of 4mb....
|
||||
$uploadSession = $graph->createRequest("POST", "/users/" . key($message->getFrom()) . "/messages/" . $graphMessage->getId() . "/attachments/createUploadSession")
|
||||
->attachBody($attachmentMessage)
|
||||
->setReturnType(UploadSession::class)
|
||||
->execute();
|
||||
|
||||
$fragSize = 1024 * 1024 * 4; //4mb at once...
|
||||
$numFragments = ceil($fileSize / $fragSize);
|
||||
$contentChunked = str_split($content, $fragSize);
|
||||
$bytesRemaining = $fileSize;
|
||||
|
||||
$i = 0;
|
||||
while ($i < $numFragments) {
|
||||
$chunkSize = $numBytes = $fragSize;
|
||||
$start = $i * $fragSize;
|
||||
$end = $i * $fragSize + $chunkSize - 1;
|
||||
if ($bytesRemaining < $chunkSize) {
|
||||
$chunkSize = $numBytes = $bytesRemaining;
|
||||
$end = $fileSize - 1;
|
||||
}
|
||||
$data = $contentChunked[$i];
|
||||
$content_range = "bytes " . $start . "-" . $end . "/" . $fileSize;
|
||||
$headers = [
|
||||
"Content-Length" => $numBytes,
|
||||
"Content-Range" => $content_range
|
||||
];
|
||||
$client = new \GuzzleHttp\Client();
|
||||
$tmp = $client->put($uploadSession->getUploadUrl(), [
|
||||
'headers' => $headers,
|
||||
'body' => $data,
|
||||
'allow_redirects' => false,
|
||||
'timeout' => 1000
|
||||
]);
|
||||
$result = $tmp->getBody() . '';
|
||||
$result = json_decode($result); //if body == empty, then the file was successfully uploaded
|
||||
$bytesRemaining = $bytesRemaining - $chunkSize;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//definetly send the message
|
||||
$graph->createRequest("POST", "/users/" . key($message->getFrom()) . "/messages/" . $graphMessage->getId() . "/send")->execute();
|
||||
} else {
|
||||
|
||||
try {
|
||||
$graphMessage = $graph->createRequest("POST", "/users/" . key($message->getFrom()) . "/sendmail")
|
||||
->attachBody($messageBody)
|
||||
->setReturnType(\Microsoft\Graph\Model\Message::class)
|
||||
->execute();
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
sleep(5);
|
||||
$graphMessage = $graph->createRequest("POST", "/users/" . key($message->getFrom()) . "/sendmail")
|
||||
->attachBody($messageBody)
|
||||
->setReturnType(\Microsoft\Graph\Model\Message::class)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
$this->sendPerformed($message);
|
||||
|
||||
return $this->numberOfRecipients($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get body for the message.
|
||||
*
|
||||
* @param \Swift_Mime_SimpleMessage $message
|
||||
* @param bool $withAttachments
|
||||
* @return array
|
||||
*/
|
||||
|
||||
protected function getBody(Swift_Mime_SimpleMessage $message, $withAttachments = false)
|
||||
{
|
||||
$messageData = [
|
||||
'from' => [
|
||||
'emailAddress' => [
|
||||
'address' => key($message->getFrom()),
|
||||
'name' => current($message->getFrom())
|
||||
]
|
||||
],
|
||||
'toRecipients' => $this->getTo($message),
|
||||
'ccRecipients' => $this->getCc($message),
|
||||
'bccRecipients' => $this->getBcc($message),
|
||||
'replyTo' => $this->getReplyTo($message),
|
||||
'subject' => $message->getSubject(),
|
||||
'body' => [
|
||||
'contentType' => $message->getBodyContentType() == "text/html" ? 'html' : 'text',
|
||||
'content' => $message->getBody()
|
||||
]
|
||||
];
|
||||
|
||||
if ($withAttachments) {
|
||||
$messageData = ['message' => $messageData];
|
||||
//add attachments if any
|
||||
$attachments = [];
|
||||
foreach ($message->getChildren() as $attachment) {
|
||||
if ($attachment instanceof \Swift_Mime_SimpleMimeEntity && $attachment->getContentType() != 'text/plain') {
|
||||
$attachments[] = [
|
||||
"@odata.type" => "#microsoft.graph.fileAttachment",
|
||||
"name" => $attachment->getHeaders()->get('Content-Type')->getParameter('name'),
|
||||
"contentType" => $attachment->getBodyContentType(),
|
||||
"contentBytes" => base64_encode($attachment->getBody()),
|
||||
'contentId' => $attachment->getId()
|
||||
];
|
||||
}
|
||||
}
|
||||
if (count($attachments) > 0) {
|
||||
$messageData['message']['attachments'] = $attachments;
|
||||
}
|
||||
}
|
||||
|
||||
return $messageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "to" payload field for the API request.
|
||||
*
|
||||
* @param \Swift_Mime_SimpleMessage $message
|
||||
* @return string
|
||||
*/
|
||||
protected function getTo(Swift_Mime_SimpleMessage $message)
|
||||
{
|
||||
return collect((array) $message->getTo())->map(function ($display, $address) {
|
||||
return $display ? [
|
||||
'emailAddress' => [
|
||||
'address' => $address,
|
||||
'name' => $display
|
||||
]
|
||||
] : [
|
||||
'emailAddress' => [
|
||||
'address' => $address
|
||||
]
|
||||
];
|
||||
})->values()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "Cc" payload field for the API request.
|
||||
*
|
||||
* @param \Swift_Mime_SimpleMessage $message
|
||||
* @return string
|
||||
*/
|
||||
protected function getCc(Swift_Mime_SimpleMessage $message)
|
||||
{
|
||||
return collect((array) $message->getCc())->map(function ($display, $address) {
|
||||
return $display ? [
|
||||
'emailAddress' => [
|
||||
'address' => $address,
|
||||
'name' => $display
|
||||
]
|
||||
] : [
|
||||
'emailAddress' => [
|
||||
'address' => $address
|
||||
]
|
||||
];
|
||||
})->values()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "replyTo" payload field for the API request.
|
||||
*
|
||||
* @param \Swift_Mime_SimpleMessage $message
|
||||
* @return string
|
||||
*/
|
||||
protected function getReplyTo(Swift_Mime_SimpleMessage $message)
|
||||
{
|
||||
return collect((array) $message->getReplyTo())->map(function ($display, $address) {
|
||||
return $display ? [
|
||||
'emailAddress' => [
|
||||
'address' => $address,
|
||||
'name' => $display
|
||||
]
|
||||
] : [
|
||||
'emailAddress' => [
|
||||
'address' => $address
|
||||
]
|
||||
];
|
||||
})->values()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "Bcc" payload field for the API request.
|
||||
*
|
||||
* @param \Swift_Mime_SimpleMessage $message
|
||||
* @return string
|
||||
*/
|
||||
protected function getBcc(Swift_Mime_SimpleMessage $message)
|
||||
{
|
||||
return collect((array) $message->getBcc())->map(function ($display, $address) {
|
||||
return $display ? [
|
||||
'emailAddress' => [
|
||||
'address' => $address,
|
||||
'name' => $display
|
||||
]
|
||||
] : [
|
||||
'emailAddress' => [
|
||||
'address' => $address
|
||||
]
|
||||
];
|
||||
})->values()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the contacts for the message.
|
||||
*
|
||||
* @param \Swift_Mime_SimpleMessage $message
|
||||
* @return array
|
||||
*/
|
||||
protected function allContacts(Swift_Mime_SimpleMessage $message)
|
||||
{
|
||||
return array_merge(
|
||||
(array) $message->getTo(),
|
||||
(array) $message->getCc(),
|
||||
(array) $message->getBcc(),
|
||||
(array) $message->getReplyTo()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
23
app/Helpers/Mail/Office365TransportManager.php
Normal file
23
app/Helpers/Mail/Office365TransportManager.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Helpers\Mail;
|
||||
|
||||
use App\Helpers\Mail\Office365MailTransport;
|
||||
use Illuminate\Mail\MailManager;
|
||||
|
||||
class Office365TransportManager extends MailManager
|
||||
{
|
||||
protected function createOffice365Transport()
|
||||
{
|
||||
return new Office365MailTransport();
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ use App\Jobs\Util\SystemLogger;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Libraries\OAuth\OAuth;
|
||||
use App\Libraries\OAuth\Providers\Google;
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyToken;
|
||||
@ -44,6 +45,7 @@ use Illuminate\Support\Str;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use Microsoft\Graph\Model;
|
||||
|
||||
class LoginController extends BaseController
|
||||
{
|
||||
@ -211,44 +213,11 @@ class LoginController extends BaseController
|
||||
$user = $user->fresh();
|
||||
}
|
||||
|
||||
// $user->setCompany($user->account->default_company);
|
||||
// $this->setLoginCache($user);
|
||||
|
||||
// $cu = CompanyUser::query()
|
||||
// ->where('user_id', auth()->user()->id);
|
||||
|
||||
$cu = $this->hydrateCompanyUser();
|
||||
|
||||
if($cu->count() == 0)
|
||||
return response()->json(['message' => 'User found, but not attached to any companies, please see your administrator'], 400);
|
||||
|
||||
// $truth = app()->make(TruthSource::class);
|
||||
|
||||
// $truth->setCompanyUser($cu->first());
|
||||
// $truth->setUser(auth()->user());
|
||||
// $truth->setCompany($user->account->default_company);
|
||||
|
||||
// if(!$cu->exists())
|
||||
// return response()->json(['message' => 'User not linked to any companies'], 403);
|
||||
|
||||
// /* Ensure the user has a valid token */
|
||||
// if($user->company_users()->count() != $user->tokens()->count())
|
||||
// {
|
||||
|
||||
// $user->companies->each(function($company) use($user, $request){
|
||||
|
||||
// if(!CompanyToken::where('user_id', $user->id)->where('company_id', $company->id)->exists()){
|
||||
|
||||
// CreateCompanyToken::dispatchNow($company, $user, $request->server('HTTP_USER_AGENT'));
|
||||
|
||||
// }
|
||||
|
||||
// });
|
||||
|
||||
// }
|
||||
|
||||
// $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*/
|
||||
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);
|
||||
@ -363,17 +332,18 @@ class LoginController extends BaseController
|
||||
if (request()->input('provider') == 'google') {
|
||||
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';
|
||||
}
|
||||
// if (request()->has('token')) {
|
||||
// return $this->handleSocialiteLogin('microsoft', request()->get('token'));
|
||||
// } else {
|
||||
// $message = 'Bearer token missing for the microsoft login';
|
||||
// }
|
||||
return $this->handleMicrosoftOauth();
|
||||
} 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';
|
||||
}
|
||||
// if (request()->has('token')) {
|
||||
// return $this->handleSocialiteLogin('apple', request()->get('token'));
|
||||
// } else {
|
||||
// $message = 'Token is missing for the apple login';
|
||||
// }
|
||||
}
|
||||
|
||||
return response()
|
||||
@ -483,6 +453,9 @@ class LoginController extends BaseController
|
||||
|
||||
$cu = CompanyUser::query()->where('user_id', auth()->user()->id);
|
||||
|
||||
if($cu->count() == 0)
|
||||
return $cu;
|
||||
|
||||
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;
|
||||
else {
|
||||
@ -509,7 +482,7 @@ class LoginController extends BaseController
|
||||
|
||||
CreateCompanyToken::dispatchNow($company, auth()->user(), "Google_O_Auth");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -521,6 +494,100 @@ class LoginController extends BaseController
|
||||
|
||||
}
|
||||
|
||||
private function handleMicrosoftOauth()
|
||||
{
|
||||
if(request()->has('accessToken'))
|
||||
$accessToken = request()->input('accessToken');
|
||||
else
|
||||
return response()->json(['message' => 'Invalid response from oauth server'], 400);
|
||||
|
||||
$graph = new \Microsoft\Graph\Graph();
|
||||
$graph->setAccessToken($accessToken);
|
||||
|
||||
$user = $graph->createRequest("GET", "/me")
|
||||
->setReturnType(Model\User::class)
|
||||
->execute();
|
||||
|
||||
if($user){
|
||||
|
||||
$account = request()->input('account');
|
||||
$email = $user->getMail() ?: $user->getUserPrincipalName();
|
||||
|
||||
$query = [
|
||||
'oauth_user_id' => $user->getId(),
|
||||
'oauth_provider_id'=> 'microsoft',
|
||||
];
|
||||
|
||||
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);
|
||||
|
||||
return $this->existingOauthUser($existing_user);
|
||||
}
|
||||
|
||||
//If this is a result user/email combo - lets add their OAuth details details
|
||||
if($existing_login_user = MultiDB::hasUser(['email' => $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);
|
||||
|
||||
return $this->existingLoginUser($user->getId(), 'microsoft');
|
||||
}
|
||||
|
||||
// Signup!
|
||||
$new_account = [
|
||||
'first_name' => $user->getGivenName() ?: '',
|
||||
'last_name' => $user->getSurname() ?: '' ,
|
||||
'password' => '',
|
||||
'email' => $email,
|
||||
'oauth_user_id' => $user->getId(),
|
||||
'oauth_provider_id' => 'microsoft',
|
||||
];
|
||||
|
||||
return $this->createNewAccount($new_account);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function existingOauthUser($existing_user)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private function existingLoginUser($oauth_user_id, $provider)
|
||||
{
|
||||
|
||||
auth()->user()->update([
|
||||
'oauth_user_id' => $oauth_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 && !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 handleGoogleOauth()
|
||||
{
|
||||
$user = false;
|
||||
@ -541,18 +608,7 @@ class LoginController extends BaseController
|
||||
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);
|
||||
|
||||
return $this->existingOauthUser($existing_user);
|
||||
}
|
||||
|
||||
//If this is a result user/email combo - lets add their OAuth details details
|
||||
@ -563,20 +619,7 @@ class LoginController extends BaseController
|
||||
|
||||
Auth::login($existing_login_user, true);
|
||||
|
||||
auth()->user()->update([
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_provider_id'=> 'google',
|
||||
]);
|
||||
|
||||
$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);
|
||||
return $this->existingLoginUser($google->harvestSubField($user), 'google');
|
||||
}
|
||||
|
||||
}
|
||||
@ -584,7 +627,6 @@ class LoginController extends BaseController
|
||||
if ($user) {
|
||||
|
||||
//check the user doesn't already exist in some form
|
||||
|
||||
if($existing_login_user = MultiDB::hasUser(['email' => $google->harvestEmail($user)]))
|
||||
{
|
||||
if(!$existing_login_user->account)
|
||||
@ -592,26 +634,9 @@ class LoginController extends BaseController
|
||||
|
||||
Auth::login($existing_login_user, true);
|
||||
|
||||
auth()->user()->update([
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_provider_id'=> 'google',
|
||||
]);
|
||||
|
||||
$cu = $this->hydrateCompanyUser();
|
||||
|
||||
// $cu = CompanyUser::query()
|
||||
// ->where('user_id', auth()->user()->id);
|
||||
|
||||
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);
|
||||
return $this->existingLoginUser($google->harvestSubField($user), 'google');
|
||||
}
|
||||
|
||||
|
||||
//user not found anywhere - lets sign them up.
|
||||
$name = OAuth::splitName($google->harvestName($user));
|
||||
|
||||
@ -624,23 +649,7 @@ class LoginController extends BaseController
|
||||
'oauth_provider_id' => 'google',
|
||||
];
|
||||
|
||||
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);
|
||||
return $this->createNewAccount($new_account);
|
||||
}
|
||||
|
||||
return response()
|
||||
@ -649,6 +658,32 @@ class LoginController extends BaseController
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
|
||||
private function createNewAccount($new_account)
|
||||
{
|
||||
|
||||
MultiDB::setDefaultDatabase();
|
||||
|
||||
$account = CreateAccount::dispatchNow($new_account, request()->getClientIp());
|
||||
|
||||
if(!$account instanceOf Account)
|
||||
return $account;
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
public function redirectToProvider(string $provider)
|
||||
{
|
||||
|
||||
@ -662,11 +697,16 @@ class LoginController extends BaseController
|
||||
$parameters = ['access_type' => 'offline', "prompt" => "consent select_account", 'redirect_uri' => config('ninja.app_url')."/auth/google"];
|
||||
}
|
||||
|
||||
if($provider == 'microsoft'){
|
||||
$scopes = ['email', 'Mail.ReadWrite', 'Mail.Send', 'offline_access', 'profile', 'User.Read openid'];
|
||||
$parameters = ['response_type' => 'code', 'redirect_uri' => config('ninja.app_url')."/auth/microsoft"];
|
||||
}
|
||||
|
||||
if (request()->has('code')) {
|
||||
return $this->handleProviderCallback($provider);
|
||||
} else {
|
||||
|
||||
if(!in_array($provider, ['google']))
|
||||
if(!in_array($provider, ['google','microsoft']))
|
||||
return abort(400, 'Invalid provider');
|
||||
|
||||
return Socialite::driver($provider)->with($parameters)->scopes($scopes)->redirect();
|
||||
@ -675,6 +715,10 @@ class LoginController extends BaseController
|
||||
|
||||
public function handleProviderCallback(string $provider)
|
||||
{
|
||||
|
||||
if($provider == 'microsoft')
|
||||
return $this->handleMicrosoftProviderCallback();
|
||||
|
||||
$socialite_user = Socialite::driver($provider)->user();
|
||||
|
||||
$oauth_user_token = '';
|
||||
@ -714,4 +758,43 @@ class LoginController extends BaseController
|
||||
|
||||
return redirect('/#/');
|
||||
}
|
||||
|
||||
public function handleMicrosoftProviderCallback($provider = 'microsoft')
|
||||
{
|
||||
|
||||
$socialite_user = Socialite::driver($provider)->user();
|
||||
nlog($socialite_user);
|
||||
|
||||
nlog("refresh token " . $socialite_user->accessTokenResponseBody['refresh_token']);
|
||||
nlog("access token " . $socialite_user->accessTokenResponseBody['access_token']);
|
||||
|
||||
$oauth_user_token = $socialite_user->accessTokenResponseBody['access_token'];
|
||||
|
||||
if($user = OAuth::handleAuth($socialite_user, $provider))
|
||||
{
|
||||
|
||||
nlog('found user and updating their user record');
|
||||
$name = OAuth::splitName($socialite_user->getName());
|
||||
|
||||
$update_user = [
|
||||
'first_name' => $name[0],
|
||||
'last_name' => $name[1],
|
||||
'email' => $socialite_user->getEmail(),
|
||||
'oauth_user_id' => $socialite_user->getId(),
|
||||
'oauth_provider_id' => $provider,
|
||||
'oauth_user_token' => $oauth_user_token,
|
||||
'oauth_user_refresh_token' => $socialite_user->accessTokenResponseBody['refresh_token']
|
||||
];
|
||||
|
||||
$user->update($update_user);
|
||||
|
||||
}
|
||||
else {
|
||||
nlog("user not found for oauth");
|
||||
}
|
||||
|
||||
return redirect('/#/');
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -80,7 +80,8 @@ class InvitationController extends Controller
|
||||
|
||||
$entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
|
||||
|
||||
$invitation = $entity_obj::where('key', $invitation_key)
|
||||
$invitation = $entity_obj::withTrashed()
|
||||
->where('key', $invitation_key)
|
||||
->whereHas($entity, function ($query) {
|
||||
$query->where('is_deleted',0);
|
||||
})
|
||||
@ -186,7 +187,8 @@ class InvitationController extends Controller
|
||||
|
||||
$entity_obj = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
|
||||
|
||||
$invitation = $entity_obj::where('key', $invitation_key)
|
||||
$invitation = $entity_obj::withTrashed()
|
||||
->where('key', $invitation_key)
|
||||
->with('contact.client')
|
||||
->firstOrFail();
|
||||
|
||||
@ -228,7 +230,8 @@ class InvitationController extends Controller
|
||||
|
||||
public function payInvoice(Request $request, string $invitation_key)
|
||||
{
|
||||
$invitation = InvoiceInvitation::where('key', $invitation_key)
|
||||
$invitation = InvoiceInvitation::withTrashed()
|
||||
->where('key', $invitation_key)
|
||||
->with('contact.client')
|
||||
->firstOrFail();
|
||||
|
||||
|
@ -60,7 +60,7 @@ class LogoutController extends BaseController
|
||||
public function index(Request $request)
|
||||
{
|
||||
$ct = CompanyToken::with('company.tokens')
|
||||
->whereRaw('BINARY `token`= ?', [$request->header('X-API-TOKEN')])
|
||||
->where('token', $request->header('X-API-TOKEN'))
|
||||
->first();
|
||||
|
||||
$ct->company
|
||||
|
@ -46,7 +46,8 @@ class InvitationController extends Controller
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$invitation = PurchaseOrderInvitation::where('key', $invitation_key)
|
||||
$invitation = PurchaseOrderInvitation::withTrashed()
|
||||
->where('key', $invitation_key)
|
||||
->whereHas('purchase_order', function ($query) {
|
||||
$query->where('is_deleted',0);
|
||||
})
|
||||
|
@ -141,6 +141,8 @@ class PurchaseOrderController extends Controller
|
||||
->whereIn('id', $this->transformKeys($data['purchase_orders']))
|
||||
->where('company_id', auth()->guard('vendor')->user()->vendor->company_id)
|
||||
->whereIn('status_id', [PurchaseOrder::STATUS_DRAFT, PurchaseOrder::STATUS_SENT])
|
||||
->where('is_deleted', 0)
|
||||
->withTrashed()
|
||||
->cursor()->each(function ($purchase_order){
|
||||
|
||||
$purchase_order->service()
|
||||
@ -159,7 +161,7 @@ class PurchaseOrderController extends Controller
|
||||
|
||||
if(count($data['purchase_orders']) == 1){
|
||||
|
||||
$purchase_order = PurchaseOrder::whereIn('id', $this->transformKeys($data['purchase_orders']))->first();
|
||||
$purchase_order = PurchaseOrder::withTrashed()->where('is_deleted', 0)->whereIn('id', $this->transformKeys($data['purchase_orders']))->first();
|
||||
|
||||
return redirect()->route('vendor.purchase_order.show', ['purchase_order' => $purchase_order->hashed_id]);
|
||||
|
||||
|
@ -184,12 +184,49 @@ class NinjaMailerJob implements ShouldQueue
|
||||
$this->mailer = 'gmail';
|
||||
$this->setGmailMailer();
|
||||
break;
|
||||
case 'office365':
|
||||
$this->mailer = 'office365';
|
||||
$this->setOfficeMailer();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function setOfficeMailer()
|
||||
{
|
||||
$sending_user = $this->nmo->settings->gmail_sending_user_id;
|
||||
|
||||
$user = User::find($this->decodePrimaryKey($sending_user));
|
||||
|
||||
nlog("Sending via {$user->name()}");
|
||||
|
||||
$token = $this->refreshOfficeToken($user);
|
||||
|
||||
if($token)
|
||||
{
|
||||
$user->oauth_user_token = $token;
|
||||
$user->save();
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
$this->nmo->settings->email_sending_method = 'default';
|
||||
return $this->setMailDriver();
|
||||
|
||||
}
|
||||
|
||||
$this->nmo
|
||||
->mailable
|
||||
->from($user->email, $user->name())
|
||||
->withSwiftMessage(function ($message) use($token) {
|
||||
$message->getHeaders()->addTextHeader('GmailToken', $token);
|
||||
});
|
||||
|
||||
sleep(rand(1,3));
|
||||
}
|
||||
|
||||
private function setGmailMailer()
|
||||
{
|
||||
if(LaravelGmail::check())
|
||||
@ -303,4 +340,25 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
private function refreshOfficeToken($user)
|
||||
{
|
||||
$guzzle = new \GuzzleHttp\Client();
|
||||
$url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
|
||||
|
||||
$token = json_decode($guzzle->post($url, [
|
||||
'form_params' => [
|
||||
'client_id' => config('ninja.o365.client_id') ,
|
||||
'client_secret' => config('ninja.o365.client_secret') ,
|
||||
'scope' => 'email Mail.ReadWrite Mail.Send offline_access profile User.Read openid',
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $user->oauth_user_refresh_token
|
||||
],
|
||||
])->getBody()->getContents());
|
||||
|
||||
if($token)
|
||||
return $token->access_token;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -88,8 +88,14 @@ class TemplateEmail extends Mailable
|
||||
|
||||
$this->from(config('mail.from.address'), $email_from_name);
|
||||
|
||||
if (strlen($settings->bcc_email) > 1)
|
||||
$this->bcc(explode(",",str_replace(" ", "", $settings->bcc_email)));//remove whitespace if any has been inserted.
|
||||
if (strlen($settings->bcc_email) > 1){
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$this->bcc(reset(explode(",",str_replace(" ", "", $settings->bcc_email))));//remove whitespace if any has been inserted.
|
||||
else
|
||||
$this->bcc(explode(",",str_replace(" ", "", $settings->bcc_email)));//remove whitespace if any has been inserted.
|
||||
|
||||
}
|
||||
|
||||
$this->subject($this->build_email->getSubject())
|
||||
->text('email.template.text', [
|
||||
|
@ -374,6 +374,9 @@ class Account extends BaseModel
|
||||
public function getDailyEmailLimit()
|
||||
{
|
||||
|
||||
if(Carbon::createFromTimestamp($this->created_at)->diffInWeeks() == 0)
|
||||
return 50;
|
||||
|
||||
if($this->isPaid()){
|
||||
$limit = $this->paid_plan_email_quota;
|
||||
$limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 100;
|
||||
|
@ -39,7 +39,6 @@ class MailServiceProvider extends MailProvider
|
||||
return new GmailTransportManager($app);
|
||||
});
|
||||
|
||||
|
||||
//this is octane ready - but is untested
|
||||
// $this->app->bind('mail.manager', function ($app){
|
||||
// return new GmailTransportManager($app);
|
||||
|
@ -18,6 +18,7 @@ use App\Models\CreditInvitation;
|
||||
use App\Models\Document;
|
||||
use App\Transformers\ActivityTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
class CreditTransformer extends EntityTransformer
|
||||
{
|
||||
@ -30,6 +31,7 @@ class CreditTransformer extends EntityTransformer
|
||||
|
||||
protected $availableIncludes = [
|
||||
'activities',
|
||||
'client',
|
||||
];
|
||||
|
||||
public function includeActivities(Credit $credit)
|
||||
@ -53,28 +55,13 @@ class CreditTransformer extends EntityTransformer
|
||||
return $this->includeCollection($credit->invitations, $transformer, CreditInvitation::class);
|
||||
}
|
||||
|
||||
/*
|
||||
public function includePayments(quote $credit)
|
||||
{
|
||||
$transformer = new PaymentTransformer($this->account, $this->serializer, $credit);
|
||||
public function includeClient(Credit $credit): Item
|
||||
{
|
||||
$transformer = new ClientTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($credit->payments, $transformer, ENTITY_PAYMENT);
|
||||
}
|
||||
return $this->includeItem($credit->client, $transformer, Client::class);
|
||||
}
|
||||
|
||||
public function includeClient(quote $credit)
|
||||
{
|
||||
$transformer = new ClientTransformer($this->account, $this->serializer);
|
||||
|
||||
return $this->includeItem($credit->client, $transformer, ENTITY_CLIENT);
|
||||
}
|
||||
|
||||
public function includeExpenses(quote $credit)
|
||||
{
|
||||
$transformer = new ExpenseTransformer($this->account, $this->serializer);
|
||||
|
||||
return $this->includeCollection($credit->expenses, $transformer, ENTITY_EXPENSE);
|
||||
}
|
||||
*/
|
||||
public function includeDocuments(Credit $credit)
|
||||
{
|
||||
$transformer = new DocumentTransformer($this->serializer);
|
||||
|
@ -71,6 +71,7 @@
|
||||
"league/fractal": "^0.17.0",
|
||||
"league/omnipay": "^3.1",
|
||||
"livewire/livewire": "^2.6",
|
||||
"microsoft/microsoft-graph": "^1.69",
|
||||
"mollie/mollie-api-php": "^2.36",
|
||||
"nelexa/zip": "^4.0",
|
||||
"nwidart/laravel-modules": "8.3",
|
||||
|
53
composer.lock
generated
53
composer.lock
generated
@ -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": "6845489fdc254427c4536e22f025ff51",
|
||||
"content-hash": "df84a1903809a8e781d937e679821e74",
|
||||
"packages": [
|
||||
{
|
||||
"name": "afosto/yaac",
|
||||
@ -4928,6 +4928,57 @@
|
||||
],
|
||||
"time": "2022-04-07T21:38:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "microsoft/microsoft-graph",
|
||||
"version": "1.69.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/microsoftgraph/msgraph-sdk-php.git",
|
||||
"reference": "dc867afdb2c89ea7ead37d6bcfcaf0389e7a85f4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/dc867afdb2c89ea7ead37d6bcfcaf0389e7a85f4",
|
||||
"reference": "dc867afdb2c89ea7ead37d6bcfcaf0389e7a85f4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/guzzle": "^6.0 || ^7.0",
|
||||
"php": "^8.0 || ^7.3",
|
||||
"psr/http-message": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mikey179/vfsstream": "^1.2",
|
||||
"phpstan/phpstan": "^0.12.90 || ^1.0.0",
|
||||
"phpunit/phpunit": "^8.0 || ^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Microsoft\\Graph\\": "src/",
|
||||
"Beta\\Microsoft\\Graph\\": "src/Beta/Microsoft/Graph/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Microsoft Graph Client Tooling",
|
||||
"email": "graphtooling@service.microsoft.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "The Microsoft Graph SDK for PHP",
|
||||
"homepage": "https://developer.microsoft.com/en-us/graph",
|
||||
"support": {
|
||||
"issues": "https://github.com/microsoftgraph/msgraph-sdk-php/issues",
|
||||
"source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.69.0"
|
||||
},
|
||||
"time": "2022-06-15T11:11:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mollie/mollie-api-php",
|
||||
"version": "v2.44.1",
|
||||
|
@ -73,6 +73,9 @@ return [
|
||||
'gmail' => [
|
||||
'transport' => 'gmail',
|
||||
],
|
||||
'office365' => [
|
||||
'transport' => 'office365',
|
||||
],
|
||||
'cocopostmark' => [
|
||||
'transport' => 'cocopostmark',
|
||||
],
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.4.2',
|
||||
'app_tag' => '5.4.2',
|
||||
'app_version' => '5.4.3',
|
||||
'app_tag' => '5.4.3',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
@ -155,6 +155,11 @@ return [
|
||||
'designs' => [
|
||||
'base_path' => resource_path('views/pdf-designs/'),
|
||||
],
|
||||
'o365' => [
|
||||
'client_secret' => env('MICROSOFT_CLIENT_SECRET', false),
|
||||
'client_id' => env('MICROSOFT_CLIENT_ID', false),
|
||||
'tenant_id' => env('MICROSOFT_TENANT_ID', false),
|
||||
],
|
||||
'maintenance' => [
|
||||
'delete_pdfs' => env('DELETE_PDF_DAYS', 0),
|
||||
'delete_backups' => env('DELETE_BACKUP_DAYS', 0),
|
||||
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ChangeRefreshTokenColumnSize extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->text('oauth_user_refresh_token')->change();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user