validator for ExpenseMailbox property

This commit is contained in:
paulwer 2023-12-15 18:15:55 +01:00
parent dd9727a701
commit 36279be694
10 changed files with 471 additions and 352 deletions

View File

@ -11,10 +11,17 @@
namespace App\Helpers\Mail\Webhook;
use App\Models\Company;
interface BaseWebhookHandler
{
public function process()
{
}
protected function matchCompany(string $email)
{
return Company::where("expense_mailbox", $email)->first();
}
}

View File

@ -11,12 +11,21 @@
namespace App\Helpers\Mail\Webhook\Postmark;
use App\Factory\ExpenseFactory;
use App\Helpers\Mail\Webhook\BaseWebhookHandler;
interface PostmarkWebhookHandler extends BaseWebhookHandler
{
public function process()
public function process($data)
{
$email = '';
$company = $this->matchCompany($email);
if (!$company)
return false;
$expense = ExpenseFactory::create($company->id, $company->owner()->id);
}
}

View File

@ -13,6 +13,7 @@ namespace App\Http\Requests\Company;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Company\ValidCompanyQuantity;
use App\Http\ValidationRules\Company\ValidExpenseMailbox;
use App\Http\ValidationRules\Company\ValidSubdomain;
use App\Http\ValidationRules\ValidSettingsRule;
use App\Models\Company;
@ -28,7 +29,7 @@ class StoreCompanyRequest extends Request
*
* @return bool
*/
public function authorize() : bool
public function authorize(): bool
{
/** @var \App\Models\User auth()->user */
$user = auth()->user();
@ -55,6 +56,8 @@ class StoreCompanyRequest extends Request
}
}
$rules['expense_mailbox'] = new ValidExpenseMailbox($this->company->key, $this->company->account->isPaid() && $this->company->account->plan == 'enterprise'); // @turbo124 check if this is right
return $rules;
}

View File

@ -13,6 +13,7 @@ namespace App\Http\Requests\Company;
use App\DataMapper\CompanySettings;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Company\ValidExpenseMailbox;
use App\Http\ValidationRules\Company\ValidSubdomain;
use App\Http\ValidationRules\ValidSettingsRule;
use App\Utils\Ninja;
@ -35,7 +36,7 @@ class UpdateCompanyRequest extends Request
*
* @return bool
*/
public function authorize() : bool
public function authorize(): bool
{
/** @var \App\Models\User $user */
$user = auth()->user();
@ -67,6 +68,8 @@ class UpdateCompanyRequest extends Request
$rules['subdomain'] = ['nullable', 'regex:/^[a-zA-Z0-9.-]+[a-zA-Z0-9]$/', new ValidSubdomain()];
}
$rules['expense_mailbox'] = new ValidExpenseMailbox($this->company->key, $this->company->account->isPaid() && $this->company->account->plan == 'enterprise'); // @turbo124 check if this is right
return $rules;
}
@ -80,14 +83,14 @@ class UpdateCompanyRequest extends Request
}
if (array_key_exists('settings', $input)) {
$input['settings'] = (array)$this->filterSaveableSettings($input['settings']);
$input['settings'] = (array) $this->filterSaveableSettings($input['settings']);
}
if(array_key_exists('subdomain', $input) && $this->company->subdomain == $input['subdomain']) {
if (array_key_exists('subdomain', $input) && $this->company->subdomain == $input['subdomain']) {
unset($input['subdomain']);
}
if(array_key_exists('e_invoice_certificate_passphrase', $input) && empty($input['e_invoice_certificate_passphrase'])) {
if (array_key_exists('e_invoice_certificate_passphrase', $input) && empty($input['e_invoice_certificate_passphrase'])) {
unset($input['e_invoice_certificate_passphrase']);
}
@ -115,17 +118,17 @@ class UpdateCompanyRequest extends Request
}
if (isset($settings['email_style_custom'])) {
$settings['email_style_custom'] = str_replace(['{{','}}'], ['',''], $settings['email_style_custom']);
$settings['email_style_custom'] = str_replace(['{{', '}}'], ['', ''], $settings['email_style_custom']);
}
if (! $account->isFreeHostedClient()) {
if (!$account->isFreeHostedClient()) {
return $settings;
}
$saveable_casts = CompanySettings::$free_plan_casts;
foreach ($settings as $key => $value) {
if (! array_key_exists($key, $saveable_casts)) {
if (!array_key_exists($key, $saveable_casts)) {
unset($settings->{$key});
}
}
@ -137,7 +140,7 @@ class UpdateCompanyRequest extends Request
{
if (Ninja::isHosted()) {
$url = str_replace('http://', '', $url);
$url = parse_url($url, PHP_URL_SCHEME) === null ? $scheme.$url : $url;
$url = parse_url($url, PHP_URL_SCHEME) === null ? $scheme . $url : $url;
}
return rtrim($url, '/');

View File

@ -0,0 +1,64 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\ValidationRules\Company;
use App\Libraries\MultiDB;
use App\Utils\Ninja;
use Illuminate\Contracts\Validation\Rule;
/**
* Class ValidCompanyQuantity.
*/
class ValidExpenseMailbox implements Rule
{
private $validated_schema = false;
private $company_key = false;
private $isEnterprise = false;
public function __construct(string $company_key, bool $isEnterprise = false)
{
$this->company_key = $company_key;
$this->isEnterprise = $isEnterprise;
}
public function passes($attribute, $value)
{
if (empty($value)) {
return true;
}
// early return, if we dont have any additional validation
if (!config('ninja.inbound_expense.webhook.mailbox_schema') && !(Ninja::isHosted() && config('ninja.inbound_expense.webhook.mailbox_schema_enterprise'))) {
$this->validated_schema = true;
return MultiDB::checkExpenseMailboxAvailable($value);
}
// Validate Schema
$validated = !config('ninja.inbound_expense.webhook.mailbox_schema') || (preg_match(config('ninja.inbound_expense.webhook.mailbox_schema'), $value) && (!config('ninja.inbound_expense.webhook.mailbox_schema_hascompanykey') || str_contains($value, $this->company_key))) ? true : false;
$validated_enterprise = !config('ninja.inbound_expense.webhook.mailbox_schema_enterprise') || (Ninja::isHosted() && $this->isEnterprise && preg_match(config('ninja.inbound_expense.webhook.mailbox_schema_enterprise'), $value));
if (!$validated && !$validated_enterprise)
return false;
$this->validated_schema = true;
return MultiDB::checkExpenseMailboxAvailable($value);
}
/**
* @return string
*/
public function message()
{
return $this->validated_schema ? ctrans('texts.expense_mailbox_taken') : ctrans('texts.expense_mailbox_invalid');
}
}

View File

@ -71,18 +71,20 @@ class MultiDB
'socket',
];
private static $protected_expense_mailboxes = [];
/**
* @return array
*/
public static function getDbs() : array
public static function getDbs(): array
{
return self::$dbs;
}
public static function checkDomainAvailable($subdomain) : bool
public static function checkDomainAvailable($subdomain): bool
{
if (! config('ninja.db.multi_db_enabled')) {
if (!config('ninja.db.multi_db_enabled')) {
return Company::whereSubdomain($subdomain)->count() == 0;
}
@ -105,9 +107,35 @@ class MultiDB
return true;
}
public static function checkUserEmailExists($email) : bool
public static function checkExpenseMailboxAvailable($expense_mailbox): bool
{
if (! config('ninja.db.multi_db_enabled')) {
if (!config('ninja.db.multi_db_enabled')) {
return Company::where("expense_mailbox", $expense_mailbox)->withTrashed()->exists();
}
if (in_array($expense_mailbox, self::$protected_expense_mailboxes)) {
return false;
}
$current_db = config('database.default');
foreach (self::$dbs as $db) {
if (Company::on($db)->where("expense_mailbox", $expense_mailbox)->withTrashed()->exists()) {
self::setDb($current_db);
return false;
}
}
self::setDb($current_db);
return true;
}
public static function checkUserEmailExists($email): bool
{
if (!config('ninja.db.multi_db_enabled')) {
return User::where(['email' => $email])->withTrashed()->exists();
} // true >= 1 emails found / false -> == emails found
@ -139,7 +167,7 @@ class MultiDB
* @param string $company_key The company key
* @return bool True|False
*/
public static function checkUserAndCompanyCoExist($email, $company_key) :bool
public static function checkUserAndCompanyCoExist($email, $company_key): bool
{
$current_db = config('database.default');
@ -166,9 +194,9 @@ class MultiDB
* @param array $data
* @return User|null
*/
public static function hasUser(array $data) : ?User
public static function hasUser(array $data): ?User
{
if (! config('ninja.db.multi_db_enabled')) {
if (!config('ninja.db.multi_db_enabled')) {
return User::where($data)->withTrashed()->first();
}
@ -190,9 +218,9 @@ class MultiDB
* @param string $email
* @return ClientContact|null
*/
public static function hasContact(string $email) : ?ClientContact
public static function hasContact(string $email): ?ClientContact
{
if (! config('ninja.db.multi_db_enabled')) {
if (!config('ninja.db.multi_db_enabled')) {
return ClientContact::where('email', $email)->withTrashed()->first();
}
@ -217,9 +245,9 @@ class MultiDB
* @param array $search
* @return ClientContact|null
*/
public static function findContact(array $search) : ?ClientContact
public static function findContact(array $search): ?ClientContact
{
if (! config('ninja.db.multi_db_enabled')) {
if (!config('ninja.db.multi_db_enabled')) {
return ClientContact::where($search)->first();
}
@ -240,7 +268,7 @@ class MultiDB
return null;
}
public static function contactFindAndSetDb($token) :bool
public static function contactFindAndSetDb($token): bool
{
$current_db = config('database.default');
@ -257,7 +285,7 @@ class MultiDB
return false;
}
public static function userFindAndSetDb($email) : bool
public static function userFindAndSetDb($email): bool
{
$current_db = config('database.default');
@ -275,7 +303,7 @@ class MultiDB
return false;
}
public static function documentFindAndSetDb($hash) : bool
public static function documentFindAndSetDb($hash): bool
{
$current_db = config('database.default');
@ -293,7 +321,7 @@ class MultiDB
return false;
}
public static function findAndSetDb($token) :bool
public static function findAndSetDb($token): bool
{
$current_db = config('database.default');
@ -310,7 +338,7 @@ class MultiDB
return false;
}
public static function findAndSetDbByCompanyKey($company_key) :bool
public static function findAndSetDbByCompanyKey($company_key): bool
{
$current_db = config('database.default');
@ -327,7 +355,7 @@ class MultiDB
return false;
}
public static function findAndSetDbByCompanyId($company_id) :?Company
public static function findAndSetDbByCompanyId($company_id): ?Company
{
$current_db = config('database.default');
@ -344,7 +372,7 @@ class MultiDB
return null;
}
public static function findAndSetDbByShopifyName($shopify_name) :?Company
public static function findAndSetDbByShopifyName($shopify_name): ?Company
{
$current_db = config('database.default');
@ -361,7 +389,7 @@ class MultiDB
return null;
}
public static function findAndSetDbByAccountKey($account_key) :bool
public static function findAndSetDbByAccountKey($account_key): bool
{
$current_db = config('database.default');
@ -378,7 +406,7 @@ class MultiDB
return false;
}
public static function findAndSetDbByInappTransactionId($transaction_id) :bool
public static function findAndSetDbByInappTransactionId($transaction_id): bool
{
$current_db = config('database.default');
@ -396,7 +424,7 @@ class MultiDB
}
public static function findAndSetDbByContactKey($contact_key) :bool
public static function findAndSetDbByContactKey($contact_key): bool
{
$current_db = config('database.default');
@ -413,7 +441,7 @@ class MultiDB
return false;
}
public static function findAndSetDbByVendorContactKey($contact_key) :bool
public static function findAndSetDbByVendorContactKey($contact_key): bool
{
$current_db = config('database.default');
@ -430,7 +458,7 @@ class MultiDB
return false;
}
public static function findAndSetDbByClientHash($client_hash) :bool
public static function findAndSetDbByClientHash($client_hash): bool
{
$current_db = config('database.default');
@ -447,7 +475,7 @@ class MultiDB
return false;
}
public static function findAndSetDbByClientId($client_id) :?Client
public static function findAndSetDbByClientId($client_id): ?Client
{
$current_db = config('database.default');
@ -466,7 +494,7 @@ class MultiDB
public static function findAndSetDbByDomain($query_array)
{
if (! config('ninja.db.multi_db_enabled')) {
if (!config('ninja.db.multi_db_enabled')) {
return Company::where($query_array)->first();
}
@ -487,7 +515,7 @@ class MultiDB
public static function findAndSetDbByInvitation($entity, $invitation_key)
{
$class = 'App\Models\\'.ucfirst(Str::camel($entity)).'Invitation';
$class = 'App\Models\\' . ucfirst(Str::camel($entity)) . 'Invitation';
$current_db = config('database.default');
foreach (self::$dbs as $db) {
@ -507,9 +535,9 @@ class MultiDB
* @param string $phone
* @return bool
*/
public static function hasPhoneNumber(string $phone) : bool
public static function hasPhoneNumber(string $phone): bool
{
if (! config('ninja.db.multi_db_enabled')) {
if (!config('ninja.db.multi_db_enabled')) {
return Account::where('account_sms_verification_number', $phone)->where('account_sms_verified', true)->exists();
}
@ -548,7 +576,7 @@ class MultiDB
$string .= $consonants[rand(0, 19)];
$string .= $vowels[rand(0, 4)];
}
} while (! self::checkDomainAvailable($string));
} while (!self::checkDomainAvailable($string));
self::setDb($current_db);
@ -559,7 +587,7 @@ class MultiDB
* @param $database
* @return void
*/
public static function setDB(string $database) : void
public static function setDB(string $database): void
{
/* This will set the database connection for the request */
config(['database.default' => $database]);

View File

@ -90,7 +90,7 @@ class AccountTransformer extends EntityTransformer
'set_react_as_default_ap' => (bool) $account->set_react_as_default_ap,
'trial_days_left' => Ninja::isHosted() ? (int) $account->getTrialDays() : 0,
'account_sms_verified' => (bool) $account->account_sms_verified,
'has_iap_plan' => (bool)$account->inapp_transaction_id,
'has_iap_plan' => (bool) $account->inapp_transaction_id,
'tax_api_enabled' => (bool) config('services.tax.zip_tax.key') ? true : false
];

View File

@ -146,7 +146,7 @@ class CompanyTransformer extends EntityTransformer
'enabled_modules' => (int) $company->enabled_modules,
'updated_at' => (int) $company->updated_at,
'archived_at' => (int) $company->deleted_at,
'created_at' =>(int) $company->created_at,
'created_at' => (int) $company->created_at,
'slack_webhook_url' => (string) $company->slack_webhook_url,
'google_analytics_url' => (string) $company->google_analytics_key, //@deprecate 1-2-2021
'google_analytics_key' => (string) $company->google_analytics_key,
@ -158,7 +158,7 @@ class CompanyTransformer extends EntityTransformer
'is_large' => (bool) $this->isLarge($company),
'is_disabled' => (bool) $company->is_disabled,
'enable_shop_api' => (bool) $company->enable_shop_api,
'mark_expenses_invoiceable'=> (bool) $company->mark_expenses_invoiceable,
'mark_expenses_invoiceable' => (bool) $company->mark_expenses_invoiceable,
'mark_expenses_paid' => (bool) $company->mark_expenses_paid,
'invoice_expense_documents' => (bool) $company->invoice_expense_documents,
'invoice_task_timelog' => (bool) $company->invoice_task_timelog,
@ -168,10 +168,10 @@ class CompanyTransformer extends EntityTransformer
'use_credits_payment' => 'always', // @deprecate 1-2-2021
'default_task_is_date_based' => (bool) $company->default_task_is_date_based,
'enable_product_discount' => (bool) $company->enable_product_discount,
'calculate_expense_tax_by_amount' =>(bool) $company->calculate_expense_tax_by_amount,
'calculate_expense_tax_by_amount' => (bool) $company->calculate_expense_tax_by_amount,
'hide_empty_columns_on_pdf' => false, // @deprecate 1-2-2021
'expense_inclusive_taxes' => (bool) $company->expense_inclusive_taxes,
'expense_amount_is_pretax' =>(bool) true, //@deprecate 1-2-2021
'expense_amount_is_pretax' => (bool) true, //@deprecate 1-2-2021
'oauth_password_required' => (bool) $company->oauth_password_required,
'session_timeout' => (int) $company->session_timeout,
'default_password_timeout' => (int) $company->default_password_timeout,
@ -204,6 +204,7 @@ class CompanyTransformer extends EntityTransformer
'invoice_task_project_header' => (bool) $company->invoice_task_project_header,
'invoice_task_item_description' => (bool) $company->invoice_task_item_description,
'origin_tax_data' => $company->origin_tax_data ?: new \stdClass,
'expense_mailbox' => $company->expense_mailbox,
];
}

View File

@ -238,7 +238,9 @@ return [
],
'webhook' => [
'mailbox_template' => env('INBOUND_EXPENSE_WEBHOOK_MAILBOXTEMPLATE', null),
'mailbox_template_enterprise' => env('INBOUND_EXPENSE_WEBHOOK_MAILBOXTEMPLATE_ENTERPRISE', '{{input}}@expense.invoicing.co'),
'mailbox_schema' => env('INBOUND_EXPENSE_WEBHOOK_MAILBOX_SCHEMA', null),
'mailbox_schema_hascompanykey' => env('INBOUND_EXPENSE_WEBHOOK_MAILBOX_SCHEMA_HASCOMPANYKEY', false),
'mailbox_schema_enterprise' => env('INBOUND_EXPENSE_WEBHOOK_MAILBOX_SCHEMA_ENTERPRISE', '.*@expense\.invoicing\.co$'),
],
],
];

View File

@ -2534,6 +2534,8 @@ $lang = array(
'local_storage_required' => 'Error: local storage is not available.',
'your_password_reset_link' => 'Your Password Reset Link',
'subdomain_taken' => 'The subdomain is already in use',
'expense_mailbox_taken' => 'The mailbox is already in use',
'expense_mailbox_invalid' => 'The mailbox does not match the required schema',
'client_login' => 'Client Login',
'converted_amount' => 'Converted Amount',
'default' => 'Default',