3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-02-05 00:15:39 +00:00

Merge pull request #17115 from marcusmoore/17067-limit-cc-if-acceptance-required

Fixed #17067: Allow only sending cc email when acceptance required
This commit is contained in:
snipe
2025-06-06 07:41:26 +01:00
committed by GitHub
12 changed files with 355 additions and 101 deletions

View File

@ -650,6 +650,7 @@ class SettingsController extends Controller
$setting->alert_email = $alert_email;
$setting->admin_cc_email = $admin_cc_email;
$setting->admin_cc_always = $request->validated('admin_cc_always');
$setting->alerts_enabled = $request->input('alerts_enabled', '0');
$setting->alert_interval = $request->input('alert_interval');
$setting->alert_threshold = $request->input('alert_threshold');

View File

@ -5,6 +5,7 @@ namespace App\Http\Requests;
use App\Models\Accessory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
class StoreNotificationSettings extends FormRequest
{
@ -26,6 +27,9 @@ class StoreNotificationSettings extends FormRequest
return [
'alert_email' => 'email_array|nullable',
'admin_cc_email' => 'email_array|nullable',
'admin_cc_always' => [
Rule::in('0', '1'),
],
'alert_threshold' => 'numeric|nullable',
'alert_interval' => 'numeric|nullable|gt:0',
'audit_warning_days' => 'numeric|nullable',

View File

@ -69,16 +69,16 @@ class CheckoutableListener
return;
}
$acceptance = $this->getCheckoutAcceptance($event);
$shouldSendEmailToUser = $this->shouldSendCheckoutEmailToUser($event->checkoutable);
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress();
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress($acceptance);
$shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) {
return;
}
$acceptance = $this->getCheckoutAcceptance($event);
if ($shouldSendEmailToUser || $shouldSendEmailToAlertAddress) {
$mailable = $this->getCheckoutMailType($event, $acceptance);
$notifiable = $this->getNotifiableUser($event);
@ -419,9 +419,19 @@ class CheckoutableListener
return false;
}
private function shouldSendEmailToAlertAddress(): bool
private function shouldSendEmailToAlertAddress($acceptance = null): bool
{
return Setting::getSettings() && Setting::getSettings()->admin_cc_email;
$setting = Setting::getSettings();
if (!$setting) {
return false;
}
if (is_null($acceptance) && !$setting->admin_cc_always) {
return false;
}
return (bool) $setting->admin_cc_email;
}
private function getFormattedAlertAddresses(): array

View File

@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('settings', function (Blueprint $table) {
$table->boolean('admin_cc_always')->after('admin_cc_email')->default(1);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('settings', function (Blueprint $table) {
$table->dropColumn('admin_cc_always');
});
}
};

View File

@ -9,6 +9,8 @@ return [
'ad_append_domain_help' => 'User isn\'t required to write "username@domain.local", they can just type "username".',
'admin_cc_email' => 'CC Email',
'admin_cc_email_help' => 'Send a copy of checkin/checkout emails to this address.',
'admin_cc_always' => 'Always send copy upon checkin/checkout',
'admin_cc_when_acceptance_required' => 'Only send copy upon checkout if acceptance is required',
'admin_settings' => 'Admin Settings',
'is_ad' => 'This is an Active Directory server',
'alerts' => 'Alerts',

View File

@ -96,8 +96,28 @@
<input type="email" name="admin_cc_email" value="{{ old('admin_cc_email', $setting->admin_cc_email) }}" class="form-control" placeholder="admin@yourcompany.com" maxlength="191">
{!! $errors->first('admin_cc_email', '<span class="alert-msg" aria-hidden="true">:message</span><br>') !!}
<p class="help-block">{{ trans('admin/settings/general.admin_cc_email_help') }}</p>
</div>
</div>
<div class="form-group">
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
<input
type="radio"
name="admin_cc_always"
value="1"
@checked($setting->admin_cc_always == 1)
>
{{ trans('admin/settings/general.admin_cc_always') }}
</label>
<label class="form-control">
<input
type="radio"
name="admin_cc_always"
value="0"
@checked($setting->admin_cc_always == 0)
>
{{ trans('admin/settings/general.admin_cc_when_acceptance_required') }}
</label>
</div>
</div>
</fieldset>

View File

@ -0,0 +1,126 @@
<?php
namespace Tests\Feature\Notifications\Email;
use App\Events\CheckoutableCheckedIn;
use App\Mail\CheckinAssetMail;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use PHPUnit\Framework\Attributes\Group;
use Tests\TestCase;
#[Group('notifications')]
class EmailNotificationsToAdminAlertEmailUponCheckinTest extends TestCase
{
private Asset $asset;
private AssetModel $assetModel;
private Category $category;
private User $user;
protected function setUp(): void
{
parent::setUp();
Mail::fake();
$this->user = User::factory()->create();
$this->category = Category::factory()->create([
'checkin_email' => false,
'eula_text' => null,
'require_acceptance' => false,
'use_default_eula' => false,
]);
$this->assetModel = AssetModel::factory()->for($this->category)->create();
$this->asset = Asset::factory()
->for($this->assetModel, 'model')
->assignedToUser($this->user)
->create();
}
public function test_admin_alert_email_sends()
{
$this->settings->enableAdminCC('cc@example.com');
$this->category->update(['checkin_email' => true]);
$this->fireCheckInEvent($this->asset, $this->user);
Mail::assertSent(CheckinAssetMail::class, function ($mail) {
return $mail->hasTo($this->user->email) && $mail->hasCc('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_category_email_is_not_set_to_send_email_to_user()
{
$this->settings->enableAdminCC('cc@example.com');
$this->category->update(['checkin_email' => false]);
$this->fireCheckInEvent($this->asset, $this->user);
Mail::assertSent(CheckinAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
{
$this->settings->enableAdminCC('cc@example.com');
$this->user->update(['email' => null]);
$this->category->update(['checkin_email' => true]);
$this->fireCheckInEvent($this->asset, $this->user);
Mail::assertSent(CheckinAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
public function test_admin_alert_email_sent_when_always_send_is_true_and_asset_does_not_require_acceptance()
{
$this->settings
->enableAdminCC('cc@example.com')
->enableAdminCCAlways();
$this->category->update(['checkin_email' => false]);
$this->fireCheckInEvent($this->asset, $this->user);
Mail::assertSent(CheckinAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com') || $mail->hasCc('cc@example.com');
});
}
public function test_admin_alert_email_not_sent_when_always_send_is_false_and_asset_does_not_require_acceptance()
{
$this->settings
->enableAdminCC('cc@example.com')
->disableAdminCCAlways();
$this->category->update(['checkin_email' => false]);
$this->fireCheckInEvent($this->asset, $this->user);
Mail::assertNotSent(CheckinAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com') || $mail->hasCc('cc@example.com');
});
}
private function fireCheckInEvent($asset, $user): void
{
event(new CheckoutableCheckedIn(
$asset,
$user,
User::factory()->checkinAssets()->create(),
''
));
}
}

View File

@ -0,0 +1,119 @@
<?php
namespace Tests\Feature\Notifications\Email;
use App\Events\CheckoutableCheckedOut;
use App\Mail\CheckoutAssetMail;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use PHPUnit\Framework\Attributes\Group;
use Tests\TestCase;
#[Group('notifications')]
class EmailNotificationsToAdminAlertEmailUponCheckoutTest extends TestCase
{
private Asset $asset;
private AssetModel $assetModel;
private Category $category;
private User $user;
protected function setUp(): void
{
parent::setUp();
Mail::fake();
$this->category = Category::factory()->create([
'checkin_email' => false,
'eula_text' => null,
'require_acceptance' => false,
'use_default_eula' => false,
]);
$this->assetModel = AssetModel::factory()->for($this->category)->create();
$this->asset = Asset::factory()->for($this->assetModel, 'model')->create();
$this->user = User::factory()->create();
}
public function test_admin_alert_email_sends()
{
$this->settings->enableAdminCC('cc@example.com');
$this->category->update(['checkin_email' => true]);
$this->fireCheckoutEvent();
Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
return $mail->hasCc('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_category_is_not_set_to_send_email_to_user()
{
$this->settings->enableAdminCC('cc@example.com');
$this->fireCheckoutEvent();
Mail::assertSent(CheckoutAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
{
$this->settings->enableAdminCC('cc@example.com');
$this->category->update(['checkin_email' => true]);
$this->user->update(['email' => null]);
$this->fireCheckoutEvent();
Mail::assertSent(CheckoutAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
public function test_admin_alert_email_sent_when_always_send_is_true_and_asset_does_not_require_acceptance()
{
$this->settings
->enableAdminCC('cc@example.com')
->enableAdminCCAlways();
$this->category->update(['checkin_email' => false]);
$this->fireCheckoutEvent();
Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
return $mail->hasTo('cc@example.com') || $mail->hasCc('cc@example.com');
});
}
public function test_admin_alert_email_not_sent_when_always_send_is_false_and_asset_does_not_require_acceptance()
{
$this->settings
->enableAdminCC('cc@example.com')
->disableAdminCCAlways();
$this->category->update(['checkin_email' => false]);
$this->fireCheckoutEvent();
Mail::assertNotSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
return $mail->hasTo('cc@example.com') || $mail->hasCc('cc@example.com');
});
}
private function fireCheckoutEvent(): void
{
event(new CheckoutableCheckedOut(
$this->asset,
$this->user,
User::factory()->superuser()->create(),
'',
));
}
}

View File

@ -4,8 +4,6 @@ namespace Tests\Feature\Notifications\Email;
use App\Mail\CheckinAssetMail;
use App\Models\Accessory;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Consumable;
use App\Models\LicenseSeat;
use Illuminate\Support\Facades\Mail;
@ -16,7 +14,7 @@ use App\Models\User;
use Tests\TestCase;
#[Group('notifications')]
class EmailNotificationsUponCheckinTest extends TestCase
class EmailNotificationsToUserUponCheckinTest extends TestCase
{
protected function setUp(): void
{
@ -101,57 +99,6 @@ class EmailNotificationsUponCheckinTest extends TestCase
Mail::assertNothingSent();
}
public function test_admin_alert_email_sends()
{
$this->settings->enableAdminCC('cc@example.com');
$user = User::factory()->create();
$asset = Asset::factory()->assignedToUser($user)->create();
$asset->model->category->update(['checkin_email' => true]);
$this->fireCheckInEvent($asset, $user);
Mail::assertSent(CheckinAssetMail::class, function ($mail) use ($user) {
return $mail->hasTo($user->email) && $mail->hasCc('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_category_email_is_not_set_to_send_email_to_user()
{
$this->settings->enableAdminCC('cc@example.com');
$category = Category::factory()->create([
'checkin_email' => false,
'eula_text' => null,
'use_default_eula' => false,
]);
$assetModel = AssetModel::factory()->create(['category_id' => $category->id]);
$asset = Asset::factory()->create(['model_id' => $assetModel->id]);
$this->fireCheckInEvent($asset, User::factory()->create());
Mail::assertSent(CheckinAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
{
$this->settings->enableAdminCC('cc@example.com');
$user = User::factory()->create(['email' => null]);
$asset = Asset::factory()->assignedToUser($user)->create();
$asset->model->category->update(['checkin_email' => true]);
$this->fireCheckInEvent($asset, $user);
Mail::assertSent(CheckinAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
private function fireCheckInEvent($asset, $user): void
{
event(new CheckoutableCheckedIn(

View File

@ -13,7 +13,7 @@ use PHPUnit\Framework\Attributes\Group;
use Tests\TestCase;
#[Group('notifications')]
class EmailNotificationsUponCheckoutTest extends TestCase
class EmailNotificationsToUserUponCheckoutTest extends TestCase
{
private Asset $asset;
private AssetModel $assetModel;
@ -87,44 +87,6 @@ class EmailNotificationsUponCheckoutTest extends TestCase
Mail::assertNothingSent();
}
public function test_admin_alert_email_sends()
{
$this->settings->enableAdminCC('cc@example.com');
$this->category->update(['checkin_email' => true]);
$this->fireCheckoutEvent();
Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) {
return $mail->hasCc('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_category_is_not_set_to_send_email_to_user()
{
$this->settings->enableAdminCC('cc@example.com');
$this->fireCheckoutEvent();
Mail::assertSent(CheckoutAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
public function test_admin_alert_email_still_sent_when_user_has_no_email_address()
{
$this->settings->enableAdminCC('cc@example.com');
$this->category->update(['checkin_email' => true]);
$this->user->update(['email' => null]);
$this->fireCheckoutEvent();
Mail::assertSent(CheckoutAssetMail::class, function ($mail) {
return $mail->hasTo('cc@example.com');
});
}
private function fireCheckoutEvent(): void
{
event(new CheckoutableCheckedOut(

View File

@ -18,7 +18,10 @@ class AlertsSettingTest extends TestCase
public function testAdminCCEmailArrayCanBeSaved()
{
$response = $this->actingAs(User::factory()->superuser()->create())
->post(route('settings.alerts.save', ['alert_email' => 'me@example.com,you@example.com']))
->post(route('settings.alerts.save', [
'alert_email' => 'me@example.com,you@example.com',
'admin_cc_always' => '1',
]))
->assertStatus(302)
->assertValid('alert_email')
->assertRedirect(route('settings.index'))
@ -26,4 +29,23 @@ class AlertsSettingTest extends TestCase
$this->followRedirects($response)->assertSee('alert-success');
}
public function test_can_update_admin_cc_always_to_true()
{
$this->settings->disableAdminCCAlways();
$this->actingAs(User::factory()->superuser()->create())
->post(route('settings.alerts.save', ['admin_cc_always' => '1']));
$this->assertDatabaseHas('settings', ['admin_cc_always' => '1']);
}
public function test_can_update_admin_cc_always_to_false()
{
$this->settings->enableAdminCC()->enableAdminCCAlways();
$this->actingAs(User::factory()->superuser()->create())
->post(route('settings.alerts.save', ['admin_cc_always' => '0']));
$this->assertDatabaseHas('settings', ['admin_cc_always' => '0']);
}
}

View File

@ -60,6 +60,20 @@ class Settings
]);
}
public function enableAdminCCAlways(): Settings
{
return $this->update([
'admin_cc_always' => 1,
]);
}
public function disableAdminCCAlways(): Settings
{
return $this->update([
'admin_cc_always' => 0,
]);
}
public function enableMultipleFullCompanySupport(): Settings
{
return $this->update(['full_multiple_companies_support' => 1]);