diff --git a/src/app/Backends/Amavis/Policy.php b/src/app/Backends/Amavis/Policy.php new file mode 100644 --- /dev/null +++ b/src/app/Backends/Amavis/Policy.php @@ -0,0 +1,118 @@ + The attributes that should be cast */ + protected $casts = [ + 'spam_tag_level' => 'float', + 'spam_tag2_level' => 'float', + 'spam_tag3_level' => 'float', + 'spam_kill_level' => 'float', + ]; + + /** @var array The attributes that can be null */ + protected $nullable = [ + 'spam_tag_level', + 'spam_tag2_level', + 'spam_tag3_level', + 'spam_kill_level', + ]; + + /** @var string Database table name */ + protected $table = 'amavis_policy'; + + /** + * Return default settings. + */ + public static function defaults(): array + { + return [ + // TODO: Get default values from config? + ]; + } + + /** + * Returns all supported settings with their type + * + * @return array + */ + public static function policyDefinition() + { + return [ + 'spam_tag_level' => 'float', + 'spam_tag2_level' => 'float', + 'spam_tag3_level' => 'float', + 'spam_kill_level' => 'float', + ]; + } + + /** + * Saves/Updates Amavis policy for a user. + * + * @param string $email Email address + * @param array $prefs Policy settings + */ + public static function saveFor(string $email, array $prefs) + { + if (empty($prefs)) { + return; + } + + $user = User::where('email', $email)->first(); + + $policy = $user ? $user->policy : new self(); + + foreach ($prefs as $key => $value) { + $policy->{$key} = $value; + } + + $policy->save(); + + if (empty($user)) { + User::create([ + 'email' => $email, + 'policy_id' => $policy->id, + ]); + } + } + + /** + * Validate policy setting value. + * + * @param string $name Setting name + * @param mixed $value Setting value + */ + public static function validate($name, $value): bool + { + $definition = self::policyDefinition(); + + if (array_key_exists($name, $definition)) { + if ($value === null) { + return true; + } + + switch ($definition[$name]) { + case 'float': + return preg_match('/^[0-9]+(\.[0-9]+)?$/', $value) === 1; + } + } + + return false; + } +} diff --git a/src/app/Backends/Amavis/User.php b/src/app/Backends/Amavis/User.php new file mode 100644 --- /dev/null +++ b/src/app/Backends/Amavis/User.php @@ -0,0 +1,36 @@ + The attributes that are mass assignable */ + protected $fillable = [ + 'email', + 'policy_id' + ]; + + /** + * The policy assigned to the user. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function policy() + { + return $this->belongsTo(Policy::class); + } +} diff --git a/src/app/Backends/Spamassassin/Userpref.php b/src/app/Backends/Spamassassin/Userpref.php new file mode 100644 --- /dev/null +++ b/src/app/Backends/Spamassassin/Userpref.php @@ -0,0 +1,171 @@ + [], + 'blacklist_from' => [], + // 'ok_locales' => [], + ]; + } + + /** + * Interact with the preference value. Value type casting. + * + * @return \Illuminate\Database\Eloquent\Casts\Attribute + */ + protected function value(): Attribute + { + return Attribute::make( + get: fn($value) => $this->formatValueOut($value), + set: fn($value) => $this->formatValueIn($value), + ); + } + + /** + * Convert input preference value into internal representation. + */ + protected function formatValueIn($value) + { + if ($value === null) { + return null; + } + + switch ($this->preference) { + case 'whitelist_from': + case 'blacklist_from': + case 'ok_locales': + return implode(' ', $value); + } + + return $value; + } + + /** + * Convert preference value into an external representation/type. + */ + protected function formatValueOut($value) + { + switch ($this->preference) { + case 'whitelist_from': + case 'blacklist_from': + case 'ok_locales': + return preg_split('/\s+/', $value, -1, PREG_SPLIT_NO_EMPTY); + } + + return $value; + } + + /** + * Saves/Updates SpamAssassin preferences for specified user. + * + * @param string $username Username + * @param array $prefs Preferences (key -> value) + */ + public static function saveFor(string $username, array $prefs) + { + if (empty($prefs)) { + return; + } + + // Update/delete existing prefs + self::where('username', $username)->get() + ->each(function ($userpref) use (&$prefs) { + if (array_key_exists($userpref->preference, $prefs)) { + $value = $prefs[$userpref->preference]; + unset($prefs[$userpref->preference]); + + if ($value === null || (is_array($value) && empty($value))) { + $userpref->delete(); + } else { + $userpref->value = $value; + $userpref->save(); + } + } + }); + + // Create new prefs + foreach ($prefs as $key => $value) { + if (is_array($value) && empty($value)) { + // Ignore empty ones + continue; + } + + $pref = new self(); + $pref->username = $username; + $pref->preference = $key; + $pref->value = $value; + $pref->save(); + } + } + + /** + * Validate preference value. + * + * @param string $name Preference name + * @param mixed $value Preference value + * @param ?int $errorIndex If an array has an invalid element, it's the index of the first invalid element + * + * @return bool True if the value is valid, False otherwise + */ + public static function validate($name, $value, &$errorIndex = null): bool + { + if ($value === null) { + return true; + } + + switch ($name) { + case 'whitelist_from': + case 'blacklist_from': + if (!is_array($value)) { + return false; + } + + $rule = [new EmailPattern()]; + + foreach ($value as $idx => $item) { + $v = Validator::make(['email' => $item], ['email' => $rule]); + if ($v->fails()) { + $errorIndex = $idx; + return false; + } + } + + return true; + + case 'ok_locales': + // TODO + return false; + } + + return false; + } +} diff --git a/src/app/Rules/EmailPattern.php b/src/app/Rules/EmailPattern.php new file mode 100644 --- /dev/null +++ b/src/app/Rules/EmailPattern.php @@ -0,0 +1,52 @@ + $input], ['email' => 'required|email']); + + if ($v->fails()) { + $this->message = \trans('validation.emailinvalid'); + return false; + } + + return true; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message(): ?string + { + return $this->message; + } +} diff --git a/src/app/Traits/UserConfigTrait.php b/src/app/Traits/UserConfigTrait.php --- a/src/app/Traits/UserConfigTrait.php +++ b/src/app/Traits/UserConfigTrait.php @@ -2,10 +2,33 @@ namespace App\Traits; +use App\Backends\Amavis\Policy as AmavisPolicy; +use App\Backends\Amavis\User as AmavisUser; +use App\Backends\Spamassassin\Userpref as SpamPref; use App\Policy\Greylist; trait UserConfigTrait { + /** + * Boot function from Laravel. + */ + protected static function bootUserConfigTrait() + { + // Remove (external) settings on user delete + static::deleted(function ($user) { + // FIXME: Should we remove the settings on force-deleting only? + // if (!$user->isForceDeleting()) { + // return; + // } + + SpamPref::where('username', $user->email)->delete(); + AmavisUser::where('email', $user->email)->each(function ($amavis) { + $amavis->policy()->delete(); + $amavis->delete(); + }); + }); + } + /** * A helper to get the user configuration. */ @@ -27,6 +50,28 @@ 'password_policy' => $settings['password_policy'], ]; + // Merge the Spamassassin/Amavis settings (defaults) + $config = array_merge( + $config, + collect(SpamPref::defaults())->mapWithKeys(fn($val, $key) => ["sa_{$key}" => $val])->all(), + collect(AmavisPolicy::defaults())->mapWithKeys(fn($val, $key) => ["amavis_{$key}" => $val])->all() + ); + + // Spamassassin settings + SpamPref::where('username', $this->email)->get() + ->each(function ($pref) use (&$config) { + $config["sa_{$pref->preference}"] = $pref->value; + }); + + // Amavis settings + if (($amavis_user = AmavisUser::where('email', $this->email)->first()) + && ($policy = $amavis_user->policy()->first()) + ) { + foreach (array_keys($policy->policyDefinition()) as $opt) { + $config["amavis_{$opt}"] = $policy->{$opt}; + } + } + return $config; } @@ -40,9 +85,27 @@ public function setConfig(array $config): array { $errors = []; + $amavis = []; + $sa = []; foreach ($config as $key => $value) { - if ($key == 'greylist_enabled') { + if (strpos($key, 'sa_') === 0) { + $sa_key = substr($key, 3); + if (SpamPref::validate($sa_key, $value, $err_idx)) { + $sa[$sa_key] = $value; + } elseif ($err_idx !== null) { + $errors[$key] = [$err_idx => \trans('validation.option-invalid-value')]; + } else { + $errors[$key] = \trans('validation.option-invalid-value'); + } + } elseif (strpos($key, 'amavis_') === 0) { + $amavis_key = substr($key, 7); + if (AmavisPolicy::validate($amavis_key, $value)) { + $amavis[$amavis_key] = $value; + } else { + $errors[$key] = \trans('validation.option-invalid-value'); + } + } elseif ($key == 'greylist_enabled') { $this->setSetting($key, $value ? 'true' : 'false'); } elseif ($key == 'guam_enabled') { $this->setSetting($key, $value ? 'true' : null); @@ -93,6 +156,9 @@ } } + SpamPref::saveFor($this->email, $sa); + AmavisPolicy::saveFor($this->email, $amavis); + return $errors; } diff --git a/src/database/migrations/2023_02_12_100000_amavis_tables.php b/src/database/migrations/2023_02_12_100000_amavis_tables.php new file mode 100644 --- /dev/null +++ b/src/database/migrations/2023_02_12_100000_amavis_tables.php @@ -0,0 +1,48 @@ +bigIncrements('id'); + //$table->char('virus_lover', 1); + //$table->char('spam_lover', 1); + $table->float('spam_tag_level')->nullable(); + $table->float('spam_tag2_level')->nullable(); + $table->float('spam_tag3_level')->nullable(); + $table->float('spam_kill_level')->nullable(); + $table->timestamps(); + }); + + Schema::create('amavis_users', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('email')->unique(); + $table->bigInteger('policy_id')->unsigned(); + $table->smallInteger('priority')->default(7); // FIXME: do we need this at all? + + $table->foreign('policy_id')->references('id')->on('amavis_policy') + ->onDelete('cascade')->onUpdate('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('amavis_users'); + Schema::dropIfExists('amavis_policy'); + } +}; diff --git a/src/database/migrations/2023_02_14_100000_spamassassin_tables.php b/src/database/migrations/2023_02_14_100000_spamassassin_tables.php new file mode 100644 --- /dev/null +++ b/src/database/migrations/2023_02_14_100000_spamassassin_tables.php @@ -0,0 +1,37 @@ +bigIncrements('id'); + $table->string('username'); + $table->string('preference', 64); + $table->text('value'); + $table->timestamps(); + + $table->index('preference'); + $table->unique(['username', 'preference']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('spamassassin_userprefs'); + } +}; diff --git a/src/resources/lang/en/ui.php b/src/resources/lang/en/ui.php --- a/src/resources/lang/en/ui.php +++ b/src/resources/lang/en/ui.php @@ -513,9 +513,18 @@ 'search' => "User email address or name", 'search-pl' => "User ID, email or domain", 'skureq' => "{sku} requires {list}.", + 'spam' => "Spam", 'subscription' => "Subscription", 'subscriptions-none' => "This user has no subscriptions.", 'users' => "Users", + 'whitelist' => "Whitelist", + 'whitelist-text' => "Use it to whitelist senders who send mail that is often tagged (incorrectly) as spam. " + . "The list entries are file-glob-style patterns, so friend@somewhere.com, *@isp.com, or *.domain.net will all work. " + . "Specifically, * and ? are allowed, but all other metacharacters are not.", + 'blacklist' => "Blacklist", + 'blacklist-text' => "Use it to specify senders who send mail that is often tagged (incorrectly) as non-spam, but which you don't want. " + . "The list entries are file-glob-style patterns, so friend@somewhere.com, *@isp.com, or *.domain.net will all work. " + . "Specifically, * and ? are allowed, but all other metacharacters are not.", ], 'wallet' => [ diff --git a/src/resources/lang/en/validation.php b/src/resources/lang/en/validation.php --- a/src/resources/lang/en/validation.php +++ b/src/resources/lang/en/validation.php @@ -171,6 +171,7 @@ 'password-policy-max-len-error' => 'Maximum password length cannot be more than :max.', 'password-policy-last-error' => 'The minimum value for last N passwords is :last.', 'signuptokeninvalid' => 'The signup token is invalid.', + 'option-invalid-value' => 'Invalid option value.', /* |-------------------------------------------------------------------------- diff --git a/src/resources/themes/forms.scss b/src/resources/themes/forms.scss --- a/src/resources/themes/forms.scss +++ b/src/resources/themes/forms.scss @@ -19,6 +19,13 @@ } } + &.scroll { + margin: 0 -5px 0 -5px; + padding: 5px; + overflow-y: scroll; + max-height: 15em; + } + input.is-invalid { z-index: 2; } diff --git a/src/resources/vue/User/Info.vue b/src/resources/vue/User/Info.vue --- a/src/resources/vue/User/Info.vue +++ b/src/resources/vue/User/Info.vue @@ -11,7 +11,7 @@
{{ $t('user.new') }}
- +
@@ -120,6 +120,29 @@ {{ $t('btn.submit') }}
+
+
+
+ +
+ + + {{ $t('user.whitelist-text') }} + +
+
+
+ +
+ + + {{ $t('user.blacklist-text') }} + +
+
+ {{ $t('btn.submit') }} +
+
@@ -163,7 +186,12 @@ passwordMode: '', user_id: null, user: { aliases: [], config: [] }, - status: {} + status: {}, + tabs: [ + 'form.general', + 'form.settings', + { label: 'user.spam', beta: true } + ] } }, computed: { @@ -296,6 +324,16 @@ this.$toast.success(response.data.message) }) }, + submitSpamSettings() { + this.$root.clearFormValidation($('#spam form')) + + let post = this.$root.pick(this.user.config, ['sa_whitelist_from', 'sa_blacklist_from']) + + axios.post('/api/v4/users/' + this.user_id + '/config', post) + .then(response => { + this.$toast.success(response.data.message) + }) + }, statusUpdate(user) { this.user = Object.assign({}, this.user, user) }, diff --git a/src/resources/vue/Widgets/Tabs.vue b/src/resources/vue/Widgets/Tabs.vue --- a/src/resources/vue/Widgets/Tabs.vue +++ b/src/resources/vue/Widgets/Tabs.vue @@ -7,6 +7,7 @@ :href="'#' + tabKey(tab)" > {{ $t(tabLabel(tab)) + (typeof tab != 'string' && 'count' in tab ? ` (${tab.count})` : '') }} + {{ $t('dashboard.beta') }} @@ -26,11 +27,12 @@ }, methods: { tabClick(event) { - event.preventDefault() + const target = $(event.target).closest('a')[0] + const key = target.id.replace('tab-', '') - new Tab(event.target).show() + event.preventDefault() - const key = event.target.id.replace('tab-', '') + new Tab(target).show() if (key in this.clickHandlers) { this.clickHandlers[key](event) diff --git a/src/tests/Feature/Traits/UserConfigTest.php b/src/tests/Feature/Traits/UserConfigTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Feature/Traits/UserConfigTest.php @@ -0,0 +1,248 @@ +deleteTestUser('user-test@' . \config('app.domain')); + } + + /** + * {@inheritDoc} + */ + public function tearDown(): void + { + $this->deleteTestUser('user-test@' . \config('app.domain')); + + parent::tearDown(); + } + + /** + * Test User::getConfig() and setConfig() methods for UserSettings + */ + public function testUserSettings(): void + { + $user = $this->getTestUser('user-test@' . \config('app.domain')); + $user->setSetting('greylist_enabled', null); + $user->setSetting('guam_enabled', null); + $user->setSetting('password_policy', null); + $user->setSetting('max_password_age', null); + $user->setSetting('limit_geo', null); + + // greylist_enabled + $this->assertSame(true, $user->getConfig()['greylist_enabled']); + + $result = $user->setConfig(['greylist_enabled' => false, 'unknown' => false]); + + $this->assertSame(['unknown' => "The requested configuration parameter is not supported."], $result); + $this->assertSame(false, $user->getConfig()['greylist_enabled']); + $this->assertSame('false', $user->getSetting('greylist_enabled')); + + $result = $user->setConfig(['greylist_enabled' => true]); + + $this->assertSame([], $result); + $this->assertSame(true, $user->getConfig()['greylist_enabled']); + $this->assertSame('true', $user->getSetting('greylist_enabled')); + + // guam_enabled + $this->assertSame(false, $user->getConfig()['guam_enabled']); + + $result = $user->setConfig(['guam_enabled' => false]); + + $this->assertSame([], $result); + $this->assertSame(false, $user->getConfig()['guam_enabled']); + $this->assertSame(null, $user->getSetting('guam_enabled')); + + $result = $user->setConfig(['guam_enabled' => true]); + + $this->assertSame([], $result); + $this->assertSame(true, $user->getConfig()['guam_enabled']); + $this->assertSame('true', $user->getSetting('guam_enabled')); + + // max_apssword_age + $this->assertSame(null, $user->getConfig()['max_password_age']); + + $result = $user->setConfig(['max_password_age' => -1]); + + $this->assertSame([], $result); + $this->assertSame(null, $user->getConfig()['max_password_age']); + $this->assertSame(null, $user->getSetting('max_password_age')); + + $result = $user->setConfig(['max_password_age' => 12]); + + $this->assertSame([], $result); + $this->assertSame('12', $user->getConfig()['max_password_age']); + $this->assertSame('12', $user->getSetting('max_password_age')); + + // password_policy + $result = $user->setConfig(['password_policy' => true]); + + $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); + $this->assertSame(null, $user->getConfig()['password_policy']); + $this->assertSame(null, $user->getSetting('password_policy')); + + $result = $user->setConfig(['password_policy' => 'min:-1']); + + $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); + + $result = $user->setConfig(['password_policy' => 'min:-1']); + + $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); + + $result = $user->setConfig(['password_policy' => 'min:10,unknown']); + + $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); + + \config(['app.password_policy' => 'min:5,max:100']); + $result = $user->setConfig(['password_policy' => 'min:4,max:255']); + + $this->assertSame(['password_policy' => "Minimum password length cannot be less than 5."], $result); + + \config(['app.password_policy' => 'min:5,max:100']); + $result = $user->setConfig(['password_policy' => 'min:10,max:255']); + + $this->assertSame(['password_policy' => "Maximum password length cannot be more than 100."], $result); + + \config(['app.password_policy' => 'min:5,max:255']); + $result = $user->setConfig(['password_policy' => 'min:10,max:255']); + + $this->assertSame([], $result); + $this->assertSame('min:10,max:255', $user->getConfig()['password_policy']); + $this->assertSame('min:10,max:255', $user->getSetting('password_policy')); + + // limit_geo + $this->assertSame([], $user->getConfig()['limit_geo']); + + $result = $user->setConfig(['limit_geo' => '']); + + $err = "Specified configuration is invalid. Expected a list of two-letter country codes."; + $this->assertSame(['limit_geo' => $err], $result); + $this->assertSame(null, $user->getSetting('limit_geo')); + + $result = $user->setConfig(['limit_geo' => ['usa']]); + + $this->assertSame(['limit_geo' => $err], $result); + $this->assertSame(null, $user->getSetting('limit_geo')); + + $result = $user->setConfig(['limit_geo' => []]); + + $this->assertSame([], $result); + $this->assertSame(null, $user->getSetting('limit_geo')); + + $result = $user->setConfig(['limit_geo' => ['US', 'ru']]); + + $this->assertSame([], $result); + $this->assertSame(['US', 'RU'], $user->getConfig()['limit_geo']); + $this->assertSame('["US","RU"]', $user->getSetting('limit_geo')); + } + + /** + * Test User::getConfig()/setConfig() methods with Amavis settings + */ + public function testAmavisOptions(): void + { + $this->markTestIncomplete(); + } + + /** + * Test User::getConfig()/setConfig() methods with Spamassassin settings + */ + public function testSpamassassinOptions(): void + { + $user = $this->getTestUser('user-test@' . \config('app.domain')); + + SpamPref::where('username', $user->email)->delete(); + + // whitelist_from + $this->assertSame([], $user->getConfig()['sa_whitelist_from']); + + $result = $user->setConfig(['sa_whitelist_from' => []]); + + $this->assertSame([], $result); + $this->assertSame([], $user->getConfig()['sa_whitelist_from']); + + $whitelist = ['test@test.com', '*@test.pl', '*.domain.net']; + $result = $user->setConfig(['sa_whitelist_from' => $whitelist]); + + $this->assertSame([], $result); + $this->assertSame($whitelist, $user->getConfig()['sa_whitelist_from']); + + $whitelist[] = 'test test'; + $result = $user->setConfig(['sa_whitelist_from' => $whitelist]); + + $this->assertSame(['sa_whitelist_from' => [3 => "Invalid option value."]], $result); + unset($whitelist[3]); + $this->assertSame($whitelist, $user->getConfig()['sa_whitelist_from']); + + $result = $user->setConfig(['sa_whitelist_from' => []]); + + $this->assertSame([], $result); + $this->assertSame([], $user->getConfig()['sa_whitelist_from']); + + // blacklist_from + $this->assertSame([], $user->getConfig()['sa_blacklist_from']); + + $result = $user->setConfig(['sa_blacklist_from' => []]); + + $this->assertSame([], $result); + $this->assertSame([], $user->getConfig()['sa_blacklist_from']); + + $blacklist = ['test@test.com', '*@test.pl', '*.domain.net']; + $result = $user->setConfig(['sa_blacklist_from' => $whitelist]); + + $this->assertSame([], $result); + $this->assertSame($blacklist, $user->getConfig()['sa_blacklist_from']); + + $blacklist[] = 'test test'; + $result = $user->setConfig(['sa_blacklist_from' => $blacklist]); + + $this->assertSame(['sa_blacklist_from' => [3 => "Invalid option value."]], $result); + unset($blacklist[3]); + $this->assertSame($blacklist, $user->getConfig()['sa_blacklist_from']); + + $result = $user->setConfig(['sa_blacklist_from' => []]); + + $this->assertSame([], $result); + $this->assertSame([], $user->getConfig()['sa_blacklist_from']); + } + + /** + * Test UserConfigTrait actions on user deletion + */ + public function testUserDelete(): void + { + Queue::fake(); + + $user = $this->getTestUser('user-test@' . \config('app.domain')); + + AmavisUser::query()->delete(); + AmavisPolicy::query()->delete(); + SpamPref::saveFor($user->email, ['whitelist_from' => ['test']]); + AmavisPolicy::saveFor($user->email, ['spam_tag_level' => 5]); + + $this->assertSame(1, SpamPref::where('username', $user->email)->count()); + $this->assertSame(1, AmavisUser::where('email', $user->email)->count()); + $this->assertSame(1, AmavisPolicy::count()); + + $user->delete(); + + $this->assertTrue($user->fresh()->trashed()); + $this->assertSame(0, SpamPref::where('username', $user->email)->count()); + $this->assertSame(0, AmavisUser::where('email', $user->email)->count()); + $this->assertSame(0, AmavisPolicy::count()); + } +} diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php --- a/src/tests/Feature/UserTest.php +++ b/src/tests/Feature/UserTest.php @@ -440,125 +440,6 @@ $this->assertNotContains($domain->namespace, $domains); } - /** - * Test User::getConfig() and setConfig() methods - */ - public function testConfigTrait(): void - { - $user = $this->getTestUser('UserAccountA@UserAccount.com'); - $user->setSetting('greylist_enabled', null); - $user->setSetting('guam_enabled', null); - $user->setSetting('password_policy', null); - $user->setSetting('max_password_age', null); - $user->setSetting('limit_geo', null); - - // greylist_enabled - $this->assertSame(true, $user->getConfig()['greylist_enabled']); - - $result = $user->setConfig(['greylist_enabled' => false, 'unknown' => false]); - - $this->assertSame(['unknown' => "The requested configuration parameter is not supported."], $result); - $this->assertSame(false, $user->getConfig()['greylist_enabled']); - $this->assertSame('false', $user->getSetting('greylist_enabled')); - - $result = $user->setConfig(['greylist_enabled' => true]); - - $this->assertSame([], $result); - $this->assertSame(true, $user->getConfig()['greylist_enabled']); - $this->assertSame('true', $user->getSetting('greylist_enabled')); - - // guam_enabled - $this->assertSame(false, $user->getConfig()['guam_enabled']); - - $result = $user->setConfig(['guam_enabled' => false]); - - $this->assertSame([], $result); - $this->assertSame(false, $user->getConfig()['guam_enabled']); - $this->assertSame(null, $user->getSetting('guam_enabled')); - - $result = $user->setConfig(['guam_enabled' => true]); - - $this->assertSame([], $result); - $this->assertSame(true, $user->getConfig()['guam_enabled']); - $this->assertSame('true', $user->getSetting('guam_enabled')); - - // max_apssword_age - $this->assertSame(null, $user->getConfig()['max_password_age']); - - $result = $user->setConfig(['max_password_age' => -1]); - - $this->assertSame([], $result); - $this->assertSame(null, $user->getConfig()['max_password_age']); - $this->assertSame(null, $user->getSetting('max_password_age')); - - $result = $user->setConfig(['max_password_age' => 12]); - - $this->assertSame([], $result); - $this->assertSame('12', $user->getConfig()['max_password_age']); - $this->assertSame('12', $user->getSetting('max_password_age')); - - // password_policy - $result = $user->setConfig(['password_policy' => true]); - - $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); - $this->assertSame(null, $user->getConfig()['password_policy']); - $this->assertSame(null, $user->getSetting('password_policy')); - - $result = $user->setConfig(['password_policy' => 'min:-1']); - - $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); - - $result = $user->setConfig(['password_policy' => 'min:-1']); - - $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); - - $result = $user->setConfig(['password_policy' => 'min:10,unknown']); - - $this->assertSame(['password_policy' => "Specified password policy is invalid."], $result); - - \config(['app.password_policy' => 'min:5,max:100']); - $result = $user->setConfig(['password_policy' => 'min:4,max:255']); - - $this->assertSame(['password_policy' => "Minimum password length cannot be less than 5."], $result); - - \config(['app.password_policy' => 'min:5,max:100']); - $result = $user->setConfig(['password_policy' => 'min:10,max:255']); - - $this->assertSame(['password_policy' => "Maximum password length cannot be more than 100."], $result); - - \config(['app.password_policy' => 'min:5,max:255']); - $result = $user->setConfig(['password_policy' => 'min:10,max:255']); - - $this->assertSame([], $result); - $this->assertSame('min:10,max:255', $user->getConfig()['password_policy']); - $this->assertSame('min:10,max:255', $user->getSetting('password_policy')); - - // limit_geo - $this->assertSame([], $user->getConfig()['limit_geo']); - - $result = $user->setConfig(['limit_geo' => '']); - - $err = "Specified configuration is invalid. Expected a list of two-letter country codes."; - $this->assertSame(['limit_geo' => $err], $result); - $this->assertSame(null, $user->getSetting('limit_geo')); - - $result = $user->setConfig(['limit_geo' => ['usa']]); - - $this->assertSame(['limit_geo' => $err], $result); - $this->assertSame(null, $user->getSetting('limit_geo')); - - $result = $user->setConfig(['limit_geo' => []]); - - $this->assertSame([], $result); - $this->assertSame(null, $user->getSetting('limit_geo')); - - $result = $user->setConfig(['limit_geo' => ['US', 'ru']]); - - $this->assertSame([], $result); - $this->assertSame(['US', 'RU'], $user->getConfig()['limit_geo']); - $this->assertSame('["US","RU"]', $user->getSetting('limit_geo')); - } - /** * Test user account degradation and un-degradation */