Bind SettingService & GroupService to the Service Container

This commit is contained in:
Bubka 2022-07-29 18:34:27 +02:00
parent 14609dec95
commit e49c358cda
10 changed files with 166 additions and 84 deletions

View File

@ -9,6 +9,7 @@ use App\Api\v1\Requests\GroupAssignRequest;
use App\Api\v1\Resources\GroupResource; use App\Api\v1\Resources\GroupResource;
use App\Api\v1\Resources\TwoFAccountCollection; use App\Api\v1\Resources\TwoFAccountCollection;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\App;
class GroupController extends Controller class GroupController extends Controller
{ {
@ -21,12 +22,11 @@ class GroupController extends Controller
/** /**
* Create a new controller instance. * Create a new controller instance.
* *
* @param \App\Services\GroupService $groupService
* @return void * @return void
*/ */
public function __construct(GroupService $groupService) public function __construct()
{ {
$this->groupService = $groupService; $this->groupService = App::make(GroupService::class);
} }

View File

@ -4,6 +4,7 @@ namespace App\Models;
use Exception; use Exception;
use App\Services\LogoService; use App\Services\LogoService;
use App\Services\SettingService;
use App\Models\Dto\TotpDto; use App\Models\Dto\TotpDto;
use App\Models\Dto\HotpDto; use App\Models\Dto\HotpDto;
use App\Events\TwoFAccountDeleted; use App\Events\TwoFAccountDeleted;
@ -12,7 +13,6 @@ use App\Exceptions\InvalidOtpParameterException;
use App\Exceptions\UnsupportedOtpTypeException; use App\Exceptions\UnsupportedOtpTypeException;
use App\Exceptions\UndecipherableException; use App\Exceptions\UndecipherableException;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Facades\App\Services\SettingService;
use Spatie\EloquentSortable\Sortable; use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait; use Spatie\EloquentSortable\SortableTrait;
use OTPHP\TOTP; use OTPHP\TOTP;
@ -393,8 +393,10 @@ class TwoFAccount extends Model implements Sortable
if (!$this->icon && $skipIconFetching) { if (!$this->icon && $skipIconFetching) {
$this->icon = $this->getDefaultIcon(); $this->icon = $this->getDefaultIcon();
} }
if (!$this->icon && SettingService::get('getOfficialIcons') && !$skipIconFetching) {
$settingService = App::make(SettingService::class);
if (!$this->icon && $settingService->get('getOfficialIcons') && !$skipIconFetching) {
$this->icon = $this->getDefaultIcon(); $this->icon = $this->getDefaultIcon();
} }
@ -447,7 +449,9 @@ class TwoFAccount extends Model implements Sortable
if ($this->generator->hasParameter('image')) { if ($this->generator->hasParameter('image')) {
$this->icon = $this->storeImageAsIcon($this->generator->getParameter('image')); $this->icon = $this->storeImageAsIcon($this->generator->getParameter('image'));
} }
if (!$this->icon && SettingService::get('getOfficialIcons') && !$skipIconFetching) {
$settingService = App::make(SettingService::class);
if (!$this->icon && $settingService->get('getOfficialIcons') && !$skipIconFetching) {
$this->icon = $this->getDefaultIcon(); $this->icon = $this->getDefaultIcon();
} }
@ -594,8 +598,9 @@ class TwoFAccount extends Model implements Sortable
private function getDefaultIcon() private function getDefaultIcon()
{ {
$logoService = App::make(LogoService::class); $logoService = App::make(LogoService::class);
$settingService = App::make(SettingService::class);
return SettingService::get('getOfficialIcons') ? $logoService->getIcon($this->service) : null; return $settingService->get('getOfficialIcons') ? $logoService->getIcon($this->service) : null;
} }
@ -604,8 +609,9 @@ class TwoFAccount extends Model implements Sortable
*/ */
private function decryptOrReturn($value) private function decryptOrReturn($value)
{ {
$settingService = App::make(SettingService::class);
// Decipher when needed // Decipher when needed
if ( SettingService::get('useEncryption') ) if ( $settingService->get('useEncryption') )
{ {
try { try {
return Crypt::decryptString($value); return Crypt::decryptString($value);
@ -625,8 +631,9 @@ class TwoFAccount extends Model implements Sortable
*/ */
private function encryptOrReturn($value) private function encryptOrReturn($value)
{ {
$settingService = App::make(SettingService::class);
// should be replaced by laravel 8 attribute encryption casting // should be replaced by laravel 8 attribute encryption casting
return SettingService::get('useEncryption') ? Crypt::encryptString($value) : $value; return $settingService->get('useEncryption') ? Crypt::encryptString($value) : $value;
} }
} }

View File

@ -4,6 +4,8 @@ namespace App\Providers;
use App\Services\LogoService; use App\Services\LogoService;
use App\Services\QrCodeService; use App\Services\QrCodeService;
use App\Services\SettingService;
use App\Services\GroupService;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Support\DeferrableProvider; use Illuminate\Contracts\Support\DeferrableProvider;
@ -16,11 +18,19 @@ class TwoFAuthServiceProvider extends ServiceProvider implements DeferrableProvi
*/ */
public function register() public function register()
{ {
$this->app->singleton(LogoService::class, function ($app) { $this->app->singleton(SettingService::class, function () {
return new SettingService();
});
$this->app->singleton(GroupService::class, function ($app) {
return new GroupService($app->make(SettingService::class));
});
$this->app->singleton(LogoService::class, function () {
return new LogoService(); return new LogoService();
}); });
$this->app->singleton(QrCodeService::class, function ($app) { $this->app->singleton(QrCodeService::class, function () {
return new QrCodeService(); return new QrCodeService();
}); });
} }

View File

@ -17,14 +17,19 @@ class SettingService
{ {
/** /**
* Determine if the given setting has been customized by the user * All user settings
* *
* @param string $key * @var Collection
* @return bool
*/ */
public function isUserDefined($key) : bool private Collection $settings;
/**
* Constructor
*/
public function __construct()
{ {
return DB::table('options')->where('key', $key)->exists(); self::build();
} }
@ -36,34 +41,18 @@ class SettingService
*/ */
public function get(string $setting) public function get(string $setting)
{ {
$options = $this->all(); return $this->settings->get($setting);
$value = $options->get($setting);
return $value;
} }
/** /**
* Get all settings * Get all settings
* *
* @return mixed Collection of settings * @return Collection the Settings collection
*/ */
public function all() : Collection public function all() : Collection
{ {
// Get a collection of user saved options return $this->settings;
$userOptions = DB::table('options')->pluck('value', 'key');
$userOptions->transform(function ($item, $key) {
return $this->restoreType($item);
});
// Merge 2fauth/app config values as fallback values
$settings = collect(config('2fauth.options'))->merge($userOptions);
if(!Arr::has($settings, 'lang')) {
$settings['lang'] = 'browser';
}
return $settings;
} }
@ -90,6 +79,8 @@ class SettingService
Option::updateOrCreate(['key' => $setting], ['value' => $value]); Option::updateOrCreate(['key' => $setting], ['value' => $value]);
Log::info(sprintf('Setting %s is now %s', var_export($setting, true), var_export($this->restoreType($value), true))); Log::info(sprintf('Setting %s is now %s', var_export($setting, true), var_export($this->restoreType($value), true)));
} }
self::build();
} }
@ -103,6 +94,40 @@ class SettingService
Option::where('key', $name)->delete(); Option::where('key', $name)->delete();
Log::info(sprintf('Setting %s deleted', var_export($name, true))); Log::info(sprintf('Setting %s deleted', var_export($name, true)));
} }
/**
* Determine if the given setting has been customized by the user
*
* @param string $key
* @return bool
*/
public function isUserDefined($key) : bool
{
return DB::table('options')->where('key', $key)->exists();
}
/**
* Set the settings collection
*/
private function build()
{
// Get a collection of user saved options
$userOptions = DB::table('options')->pluck('value', 'key');
$userOptions->transform(function ($item, $key) {
return $this->restoreType($item);
});
// Merge 2fauth/app config values as fallback values
$settings = collect(config('2fauth.options'))->merge($userOptions);
if(!Arr::has($settings, 'lang')) {
$settings['lang'] = 'browser';
}
$this->settings = $settings;
}
/** /**

View File

@ -5,8 +5,8 @@ namespace Tests\Feature\Services;
use App\Models\Group; use App\Models\Group;
use App\Models\TwoFAccount; use App\Models\TwoFAccount;
use Tests\FeatureTestCase; use Tests\FeatureTestCase;
use Tests\Classes\LocalFile; use App\Services\GroupService;
use Illuminate\Support\Facades\DB; use App\Services\SettingService;
/** /**
@ -15,7 +15,7 @@ use Illuminate\Support\Facades\DB;
class GroupServiceTest extends FeatureTestCase class GroupServiceTest extends FeatureTestCase
{ {
/** /**
* App\Services\QrCodeService $groupService * App\Services\GroupService $groupService
*/ */
protected $groupService; protected $groupService;
@ -58,8 +58,8 @@ class GroupServiceTest extends FeatureTestCase
{ {
parent::setUp(); parent::setUp();
$this->groupService = $this->app->make('App\Services\GroupService'); $this->groupService = $this->app->make(GroupService::class);
$this->settingService = $this->app->make('App\Services\SettingService'); $this->settingService = $this->app->make(SettingService::class);
$this->groupOne = new Group; $this->groupOne = new Group;
$this->groupOne->name = 'MyGroupOne'; $this->groupOne->name = 'MyGroupOne';

View File

@ -6,6 +6,7 @@ use Tests\FeatureTestCase;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use App\Models\TwoFAccount; use App\Models\TwoFAccount;
use App\Services\SettingService;
/** /**
@ -50,7 +51,7 @@ class SettingServiceTest extends FeatureTestCase
{ {
parent::setUp(); parent::setUp();
$this->settingService = $this->app->make('App\Services\SettingService'); $this->settingService = $this->app->make(SettingService::class);
$this->twofaccountOne = new TwoFAccount; $this->twofaccountOne = new TwoFAccount;
$this->twofaccountOne->legacy_uri = self::TOTP_FULL_CUSTOM_URI; $this->twofaccountOne->legacy_uri = self::TOTP_FULL_CUSTOM_URI;
@ -85,9 +86,7 @@ class SettingServiceTest extends FeatureTestCase
*/ */
public function test_get_string_setting_returns_correct_value() public function test_get_string_setting_returns_correct_value()
{ {
DB::table('options')->insert( $this->settingService->set(self::SETTING_NAME, self::SETTING_VALUE_STRING);
[self::KEY => self::SETTING_NAME, self::VALUE => strval(self::SETTING_VALUE_STRING)]
);
$this->assertEquals(self::SETTING_VALUE_STRING, $this->settingService->get(self::SETTING_NAME)); $this->assertEquals(self::SETTING_VALUE_STRING, $this->settingService->get(self::SETTING_NAME));
} }
@ -98,9 +97,7 @@ class SettingServiceTest extends FeatureTestCase
*/ */
public function test_get_boolean_setting_returns_true() public function test_get_boolean_setting_returns_true()
{ {
DB::table('options')->insert( $this->settingService->set(self::SETTING_NAME, self::SETTING_VALUE_TRUE_TRANSFORMED);
[self::KEY => self::SETTING_NAME, self::VALUE => strval(self::SETTING_VALUE_TRUE_TRANSFORMED)]
);
$this->assertEquals(true, $this->settingService->get(self::SETTING_NAME)); $this->assertEquals(true, $this->settingService->get(self::SETTING_NAME));
} }
@ -111,9 +108,7 @@ class SettingServiceTest extends FeatureTestCase
*/ */
public function test_get_boolean_setting_returns_false() public function test_get_boolean_setting_returns_false()
{ {
DB::table('options')->insert( $this->settingService->set(self::SETTING_NAME, self::SETTING_VALUE_FALSE_TRANSFORMED);
[self::KEY => self::SETTING_NAME, self::VALUE => strval(self::SETTING_VALUE_FALSE_TRANSFORMED)]
);
$this->assertEquals(false, $this->settingService->get(self::SETTING_NAME)); $this->assertEquals(false, $this->settingService->get(self::SETTING_NAME));
} }
@ -124,9 +119,7 @@ class SettingServiceTest extends FeatureTestCase
*/ */
public function test_get_int_setting_returns_int() public function test_get_int_setting_returns_int()
{ {
DB::table('options')->insert( $this->settingService->set(self::SETTING_NAME, self::SETTING_VALUE_INT);
[self::KEY => self::SETTING_NAME, self::VALUE => strval(self::SETTING_VALUE_INT)]
);
$value = $this->settingService->get(self::SETTING_NAME); $value = $this->settingService->get(self::SETTING_NAME);
@ -142,9 +135,7 @@ class SettingServiceTest extends FeatureTestCase
{ {
$native_options = config('2fauth.options'); $native_options = config('2fauth.options');
DB::table('options')->insert( $this->settingService->set(self::SETTING_NAME, self::SETTING_VALUE_STRING);
[self::KEY => self::SETTING_NAME, self::VALUE => strval(self::SETTING_VALUE_STRING)]
);
$all = $this->settingService->all(); $all = $this->settingService->all();

View File

@ -6,9 +6,11 @@ use App\Models\Group;
use Tests\TestCase; use Tests\TestCase;
use App\Models\TwoFAccount; use App\Models\TwoFAccount;
use App\Services\GroupService; use App\Services\GroupService;
use App\Services\SettingService;
use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\WithoutMiddleware;
use App\Api\v1\Controllers\GroupController; use App\Api\v1\Controllers\GroupController;
use Mockery; use Mockery;
use Mockery\MockInterface;
/** /**
* @covers \App\Api\v1\Controllers\GroupController * @covers \App\Api\v1\Controllers\GroupController
@ -24,7 +26,7 @@ class GroupControllerTest extends TestCase
/** /**
* @var \App\Api\v1\Controllers\GroupController mocked controller * @var \App\Api\v1\Controllers\GroupController tested controller
*/ */
protected $controller; protected $controller;
@ -39,10 +41,12 @@ class GroupControllerTest extends TestCase
{ {
parent::setUp(); parent::setUp();
$this->groupServiceMock = Mockery::mock($this->app->make(GroupService::class)); $this->groupServiceMock = $this->mock(GroupService::class);
// $this->groupServiceMock = Mockery::mock($this->app->make(GroupService::class));
$this->groupStoreRequest = Mockery::mock('App\Api\v1\Requests\GroupStoreRequest'); $this->groupStoreRequest = Mockery::mock('App\Api\v1\Requests\GroupStoreRequest');
$this->controller = new GroupController($this->groupServiceMock); $this->controller = new GroupController();
} }
@ -146,10 +150,12 @@ class GroupControllerTest extends TestCase
public function test_accounts_returns_api_resources_fetched_using_groupService() public function test_accounts_returns_api_resources_fetched_using_groupService()
{ {
$group = Group::factory()->make(); $group = Group::factory()->make();
\Facades\App\Services\SettingService::shouldReceive('get') $this->mock(SettingService::class, function (MockInterface $mock) {
->with('useEncryption') $mock->shouldReceive('get')
->andReturn(false); ->with('useEncryption')
->andReturn(false);
});
$twofaccounts = TwoFAccount::factory()->count(3)->make(); $twofaccounts = TwoFAccount::factory()->count(3)->make();

View File

@ -5,7 +5,8 @@ namespace Tests\Unit\Events;
use App\Models\TwoFAccount; use App\Models\TwoFAccount;
use App\Events\TwoFAccountDeleted; use App\Events\TwoFAccountDeleted;
use Tests\TestCase; use Tests\TestCase;
use Mockery\MockInterface;
use App\Services\SettingService;
/** /**
* @covers \App\Events\TwoFAccountDeleted * @covers \App\Events\TwoFAccountDeleted
@ -17,9 +18,25 @@ class TwoFAccountDeletedTest extends TestCase
*/ */
public function test_event_constructor() public function test_event_constructor()
{ {
\Facades\App\Services\SettingService::shouldReceive('get') $settingService = $this->mock(SettingService::class, function (MockInterface $settingService) {
->with('useEncryption') $settingService->shouldReceive('get')
->andReturn(false); ->andReturn(false);
});
// SettingService::shouldReceive('get')
// ->andReturn(false);
// $settingService->shouldReceive('get')
// ->with('useEncryption')
// ->andReturn(false);
// $settingService->shouldReceive('get')
// ->with('getOfficialIcons')
// ->andReturn(false);
// \Facades\App\Services\SettingService::shouldReceive('get')
// ->with('useEncryption')
// ->andReturn(false);
$twofaccount = TwoFAccount::factory()->make(); $twofaccount = TwoFAccount::factory()->make();
$event = new TwoFAccountDeleted($twofaccount); $event = new TwoFAccountDeleted($twofaccount);

View File

@ -8,6 +8,8 @@ use Tests\TestCase;
use App\Listeners\CleanIconStorage; use App\Listeners\CleanIconStorage;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Mockery\MockInterface;
use App\Services\SettingService;
/** /**
@ -17,9 +19,15 @@ class CleanIconStorageTest extends TestCase
{ {
public function test_it_deletes_icon_file_on_twofaccount_deletion() public function test_it_deletes_icon_file_on_twofaccount_deletion()
{ {
\Facades\App\Services\SettingService::shouldReceive('get') $settingService = $this->mock(SettingService::class, function (MockInterface $settingService) {
->with('useEncryption') $settingService->shouldReceive('get')
->andReturn(false); ->with('useEncryption')
->andReturn(false);
});
// \Facades\App\Services\SettingService::shouldReceive('get')
// ->with('useEncryption')
// ->andReturn(false);
$twofaccount = TwoFAccount::factory()->make(); $twofaccount = TwoFAccount::factory()->make();
$event = new TwoFAccountDeleted($twofaccount); $event = new TwoFAccountDeleted($twofaccount);

View File

@ -5,9 +5,9 @@ namespace Tests\Unit;
use App\Models\TwoFAccount; use App\Models\TwoFAccount;
use App\Events\TwoFAccountDeleted; use App\Events\TwoFAccountDeleted;
use Tests\ModelTestCase; use Tests\ModelTestCase;
use Illuminate\Support\Facades\Event;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Mockery\MockInterface;
use App\Services\SettingService;
/** /**
* @covers \App\Models\TwoFAccount * @covers \App\Models\TwoFAccount
@ -44,9 +44,15 @@ class TwoFAccountModelTest extends ModelTestCase
*/ */
public function test_sensitive_attributes_are_stored_encrypted(string $attribute) public function test_sensitive_attributes_are_stored_encrypted(string $attribute)
{ {
\Facades\App\Services\SettingService::shouldReceive('get') $settingService = $this->mock(SettingService::class, function (MockInterface $settingService) {
->with('useEncryption') $settingService->shouldReceive('get')
->andReturn(true); ->with('useEncryption')
->andReturn(true);
});
// \Facades\App\Services\SettingService::shouldReceive('get')
// ->with('useEncryption')
// ->andReturn(true);
$twofaccount = TwoFAccount::factory()->make([ $twofaccount = TwoFAccount::factory()->make([
$attribute => 'string', $attribute => 'string',
@ -80,9 +86,15 @@ class TwoFAccountModelTest extends ModelTestCase
*/ */
public function test_sensitive_attributes_are_returned_clear(string $attribute) public function test_sensitive_attributes_are_returned_clear(string $attribute)
{ {
\Facades\App\Services\SettingService::shouldReceive('get') $settingService = $this->mock(SettingService::class, function (MockInterface $settingService) {
->with('useEncryption') $settingService->shouldReceive('get')
->andReturn(false); ->with('useEncryption')
->andReturn(false);
});
// \Facades\App\Services\SettingService::shouldReceive('get')
// ->with('useEncryption')
// ->andReturn(false);
$twofaccount = TwoFAccount::factory()->make(); $twofaccount = TwoFAccount::factory()->make();
@ -97,9 +109,15 @@ class TwoFAccountModelTest extends ModelTestCase
*/ */
public function test_indecipherable_attributes_returns_masked_value(string $attribute) public function test_indecipherable_attributes_returns_masked_value(string $attribute)
{ {
\Facades\App\Services\SettingService::shouldReceive('get') $settingService = $this->mock(SettingService::class, function (MockInterface $settingService) {
->with('useEncryption') $settingService->shouldReceive('get')
->andReturn(true); ->with('useEncryption')
->andReturn(true);
});
// \Facades\App\Services\SettingService::shouldReceive('get')
// ->with('useEncryption')
// ->andReturn(true);
Crypt::shouldReceive('encryptString') Crypt::shouldReceive('encryptString')
->andReturn('indecipherableString'); ->andReturn('indecipherableString');