diff --git a/docs/banning_users.md b/docs/banning_users.md new file mode 100644 index 000000000..b0a81c4da --- /dev/null +++ b/docs/banning_users.md @@ -0,0 +1,45 @@ +# Banning Users + +Shield provides a way to ban users from your application. This is useful if you need to prevent a user from logging in, or logging them out in the event that they breach your terms of service. + +- [Checking if the User is Banned](#check-if-a-user-is-banned) +- [Banning a User](#banning-a-user) +- [Unbanning a User](#unbanning-a-user) +- [Getting the Reason for Ban ](#getting-the-reason-for-ban) + +### Check if a User is Banned + +You can check if a user is banned using `isBanned()` method on the `User` entity. The method returns a boolean `true`/`false`. + +```php +if ($user->isBanned()) { + //... +} +``` + +### Banning a User + +To ban a user from the application, the `ban(?string $message = null)` method can be called on the `User` entity. The method takes an optional string as a parameter. The string acts as the reason for the ban. + +```php +// banning a user without passing a message +$user->ban(); +// banning a user with a message and reason for the ban passed. +$user->ban('Your reason for banning the user here'); +``` + +### Unbanning a User + +Unbanning a user can be done using the `unBan()` method on the `User` entity. This method will also reset the `status_message` property. + +```php +$user->unBan(); +``` + +### Getting the Reason for Ban + +The reason for the ban can be obtained user the `getBanMessage()` method on the `User` entity. + +```php +$user->getBanMessage(); +``` \ No newline at end of file diff --git a/src/Authentication/AuthenticationException.php b/src/Authentication/AuthenticationException.php index 68d34ecf5..ced1ff2e4 100644 --- a/src/Authentication/AuthenticationException.php +++ b/src/Authentication/AuthenticationException.php @@ -29,6 +29,11 @@ public static function forInvalidUser(): self return new self(lang('Auth.invalidUser')); } + public static function forBannedUser(): self + { + return new self(lang('Auth.invalidUser')); + } + public static function forNoEntityProvided(): self { return new self(lang('Auth.noUserEntity'), 500); diff --git a/src/Authentication/Authenticators/AccessTokens.php b/src/Authentication/Authenticators/AccessTokens.php index 905033a7f..a09000703 100644 --- a/src/Authentication/Authenticators/AccessTokens.php +++ b/src/Authentication/Authenticators/AccessTokens.php @@ -65,6 +65,15 @@ public function attempt(array $credentials): Result $user = $result->extraInfo(); + if ($user->isBanned()) { + $this->user = null; + + return new Result([ + 'success' => false, + 'reason' => $user->getBanMessage() ?? lang('Auth.bannedUser'), + ]); + } + $user = $user->setAccessToken( $user->getAccessToken($this->getBearerToken()) ); diff --git a/src/Authentication/Authenticators/Session.php b/src/Authentication/Authenticators/Session.php index 4f09b784e..c765f1aa8 100644 --- a/src/Authentication/Authenticators/Session.php +++ b/src/Authentication/Authenticators/Session.php @@ -147,6 +147,15 @@ public function attempt(array $credentials): Result /** @var User $user */ $user = $result->extraInfo(); + if ($user->isBanned()) { + $this->user = null; + + return new Result([ + 'success' => false, + 'reason' => $user->getBanMessage() ?? lang('Auth.bannedUser'), + ]); + } + $this->user = $user; // Update the user's last used date on their password identity. diff --git a/src/Entities/User.php b/src/Entities/User.php index ca0fc96ae..00a420fe7 100644 --- a/src/Entities/User.php +++ b/src/Entities/User.php @@ -12,6 +12,7 @@ use CodeIgniter\Shield\Models\LoginModel; use CodeIgniter\Shield\Models\UserIdentityModel; use CodeIgniter\Shield\Traits\Activatable; +use CodeIgniter\Shield\Traits\Bannable; use CodeIgniter\Shield\Traits\Resettable; /** @@ -29,6 +30,7 @@ class User extends Entity use HasAccessTokens; use Resettable; use Activatable; + use Bannable; /** * @var UserIdentity[]|null diff --git a/src/Filters/SessionAuth.php b/src/Filters/SessionAuth.php index 294773c7d..d95f89c07 100644 --- a/src/Filters/SessionAuth.php +++ b/src/Filters/SessionAuth.php @@ -51,6 +51,15 @@ public function before(RequestInterface $request, $arguments = null) // Block inactive users when Email Activation is enabled $user = $authenticator->getUser(); + + if ($user->isBanned()) { + $error = $user->getBanMessage() ?? lang('Auth.logOutBannedUser'); + $authenticator->logout(); + + return redirect()->to(config('Auth')->logoutRedirect()) + ->with('error', $error); + } + if ($user !== null && ! $user->isActivated()) { $authenticator->logout(); diff --git a/src/Language/de/Auth.php b/src/Language/de/Auth.php index 6e426a847..6da1207ad 100644 --- a/src/Language/de/Auth.php +++ b/src/Language/de/Auth.php @@ -7,6 +7,8 @@ 'unknownAuthenticator' => '{0} ist kein gültiger Authentifikator.', 'unknownUserProvider' => 'Der zu verwendende User Provider konnte nicht ermittelt werden.', 'invalidUser' => 'Der angegebene Benutzer kann nicht gefunden werden.', + 'bannedUser' => '(To be translated) Can not log you in as you are currently banned.', + 'logOutBannedUser' => '(To be translated) You have been logged out because you have been banned.', 'badAttempt' => 'Sie konnten nicht angemeldet werden. Bitte überprüfen Sie Ihre Anmeldedaten.', 'noPassword' => 'Kann einen Benutzer ohne Passwort nicht validieren.', 'invalidPassword' => 'Sie können nicht angemeldet werden. Bitte überprüfen Sie Ihr Passwort.', diff --git a/src/Language/en/Auth.php b/src/Language/en/Auth.php index 1e58cc6e0..ac75d3315 100644 --- a/src/Language/en/Auth.php +++ b/src/Language/en/Auth.php @@ -7,6 +7,8 @@ 'unknownAuthenticator' => '{0} is not a valid authenticator.', 'unknownUserProvider' => 'Unable to determine the User Provider to use.', 'invalidUser' => 'Unable to locate the specified user.', + 'bannedUser' => 'Can not log you in as you are currently banned.', + 'logOutBannedUser' => 'You have been logged out because you have been banned.', 'badAttempt' => 'Unable to log you in. Please check your credentials.', 'noPassword' => 'Cannot validate a user without a password.', 'invalidPassword' => 'Unable to log you in. Please check your password.', diff --git a/src/Language/es/Auth.php b/src/Language/es/Auth.php index 869b0853f..6c0970f16 100644 --- a/src/Language/es/Auth.php +++ b/src/Language/es/Auth.php @@ -7,6 +7,8 @@ 'unknownAuthenticator' => '{0} no es un handler válido.', 'unknownUserProvider' => 'No podemos determinar que Proveedor de Usuarios usar.', 'invalidUser' => 'No podemos localizar este usuario.', + 'bannedUser' => '(To be translated) Can not log you in as you are currently banned.', + 'logOutBannedUser' => '(To be translated) You have been logged out because you have been banned.', 'badAttempt' => 'No puedes entrar. Por favor, comprueba tus creenciales.', 'noPassword' => 'No se puede validar un usuario sin una contraseña.', 'invalidPassword' => 'No uedes entrar. Por favor, comprueba tu contraseña.', diff --git a/src/Language/fa/Auth.php b/src/Language/fa/Auth.php index 62a80d8e5..ebf34a003 100644 --- a/src/Language/fa/Auth.php +++ b/src/Language/fa/Auth.php @@ -7,6 +7,8 @@ 'unknownAuthenticator' => '{0} احراز هویت معتبری نمی باشد.', 'unknownUserProvider' => 'قادر به تعیین ارائه دهنده کاربر برای استفاده نیست.', 'invalidUser' => 'قادر به پیداکردن کاربر مشخص شده نیست', + 'bannedUser' => 'در حال حاضر نمی توانید وارد شوید، چون مسدود شده اید.', + 'logOutBannedUser' => 'شما به دلیل مسدود شدن، از سیستم خارج شده اید.', 'badAttempt' => 'امکان ورود به سیستم نیست. لطفا اعتبارنامه خود را بررسی کنید.', 'noPassword' => 'تایید کاربر بدون رمز عبور ممکن نیست.', 'invalidPassword' => 'ناتوان در ورود به سیستم. لطفا رمز عبور خود را بررسی کنید.', diff --git a/src/Language/fr/Auth.php b/src/Language/fr/Auth.php index ee80e4d0e..9b7b7f263 100644 --- a/src/Language/fr/Auth.php +++ b/src/Language/fr/Auth.php @@ -7,6 +7,8 @@ 'unknownAuthenticator' => '{0} n\'est pas un authentificateur valide.', 'unknownUserProvider' => 'Impossible de déterminer le User Provider à utiliser.', 'invalidUser' => 'Impossible de trouver l\'utilisateur.', + 'bannedUser' => '(To be translated) Can not log you in as you are currently banned.', + 'logOutBannedUser' => '(To be translated) You have been logged out because you have been banned.', 'badAttempt' => 'Connexion impossible. Veuillez vérifier les informations saisies.', 'noPassword' => 'Impossible de valider un utilisateur sans mot de passe.', 'invalidPassword' => 'Connexion impossible. Veuillez vérifier votre mot de passe.', diff --git a/src/Language/id/Auth.php b/src/Language/id/Auth.php index b5afa7918..b71c9a477 100644 --- a/src/Language/id/Auth.php +++ b/src/Language/id/Auth.php @@ -7,6 +7,8 @@ 'unknownAuthenticator' => '{0} bukan otentikator yang sah.', 'unknownUserProvider' => 'Tidak dapat menentukan Penyedia Pengguna yang akan digunakan.', 'invalidUser' => 'Tidak dapat menemukan pengguna yang spesifik.', + 'bannedUser' => '(To be translated) Can not log you in as you are currently banned.', + 'logOutBannedUser' => '(To be translated) You have been logged out because you have been banned.', 'badAttempt' => 'Anda tidak dapat masuk. Harap periksa kredensial Anda.', 'noPassword' => 'Tidak dapat memvalidasi pengguna tanpa kata sandi.', 'invalidPassword' => 'Anda tidak dapat masuk. Harap periksa kata sandi Anda.', diff --git a/src/Language/it/Auth.php b/src/Language/it/Auth.php index d3fb2b432..d9cd2e4a6 100644 --- a/src/Language/it/Auth.php +++ b/src/Language/it/Auth.php @@ -7,6 +7,8 @@ 'unknownAuthenticator' => '{0} non è un autenticatore valido.', 'unknownUserProvider' => 'Impossibile determinare lo User Provider da usare.', 'invalidUser' => 'Impossibile trovere l\'utente specificato.', + 'bannedUser' => '(To be translated) Can not log you in as you are currently banned.', + 'logOutBannedUser' => '(To be translated) You have been logged out because you have been banned.', 'badAttempt' => 'Impossibile accedere. Si prega di verificare le proprie credenziali.', 'noPassword' => 'Impossibile validare un utente senza una password.', 'invalidPassword' => 'Impossibile accedere. Si prega di verificare la propria password.', diff --git a/src/Language/ja/Auth.php b/src/Language/ja/Auth.php index eff2edccf..a4bd4a54e 100644 --- a/src/Language/ja/Auth.php +++ b/src/Language/ja/Auth.php @@ -7,6 +7,8 @@ 'unknownAuthenticator' => '{0} は有効なオーセンティケーターではありません。', // '{0} is not a valid authenticator.', 'unknownUserProvider' => '使用するユーザープロバイダーを決定できません。', // 'Unable to determine the User Provider to use.', 'invalidUser' => '指定されたユーザーを見つけることができません。', // 'Unable to locate the specified user.', + 'bannedUser' => '現在あなたはアクセスが禁止されているため、ログインできません。', + 'logOutBannedUser' => 'アクセスが禁止されたため、ログアウトされました。', 'badAttempt' => 'ログインできません。認証情報を確認してください。', // 'Unable to log you in. Please check your credentials.', 'noPassword' => 'パスワードのないユーザーは認証できません。', // 'Cannot validate a user without a password.', 'invalidPassword' => 'ログインできません。パスワードを確認してください。', // 'Unable to log you in. Please check your password.', diff --git a/src/Language/pt-BR/Auth.php b/src/Language/pt-BR/Auth.php index 7e078617a..2feb6a2df 100644 --- a/src/Language/pt-BR/Auth.php +++ b/src/Language/pt-BR/Auth.php @@ -7,6 +7,8 @@ 'unknownAuthenticator' => '{0} não é um autenticador válido.', 'unknownUserProvider' => 'Não foi possível determinar o provedor de usuário a ser usado.', 'invalidUser' => 'Não foi possível localizar o usuário especificado.', + 'bannedUser' => '(To be translated) Can not log you in as you are currently banned.', + 'logOutBannedUser' => '(To be translated) You have been logged out because you have been banned.', 'badAttempt' => 'Não foi possível fazer login. Por favor, verifique suas credenciais.', 'noPassword' => 'Não é possível validar um usuário sem uma senha.', 'invalidPassword' => 'Não foi possível fazer login. Por favor, verifique sua senha.', diff --git a/src/Language/sk/Auth.php b/src/Language/sk/Auth.php index 74df1dbe6..5a73ba29f 100644 --- a/src/Language/sk/Auth.php +++ b/src/Language/sk/Auth.php @@ -7,6 +7,8 @@ 'unknownAuthenticator' => '{0} nie je platný autentifikátor.', 'unknownUserProvider' => 'Nie je možné určiť poskytovateľa používateľa, ktorý sa má použiť.', 'invalidUser' => 'Nie je možné nájsť zadaného používateľa.', + 'bannedUser' => '(To be translated) Can not log you in as you are currently banned.', + 'logOutBannedUser' => '(To be translated) You have been logged out because you have been banned.', 'badAttempt' => 'Prihlásenie zlyhalo. Skontrolujte svoje prihlasovacie údaje.', 'noPassword' => 'Nie je možné overiť používateľa bez hesla.', 'invalidPassword' => 'Prihlásenie zlyhalo. Skontrolujte svoje heslo.', diff --git a/src/Language/tr/Auth.php b/src/Language/tr/Auth.php index 70b924652..56cea0615 100644 --- a/src/Language/tr/Auth.php +++ b/src/Language/tr/Auth.php @@ -7,6 +7,8 @@ 'unknownAuthenticator' => '{0} geçerli bir kimlik doğrulayıcı değil.', 'unknownUserProvider' => 'Kullanılacak Kullanıcı Sağlayıcı belirlenemiyor.', 'invalidUser' => 'Belirtilen kullanıcı bulunamadı.', + 'bannedUser' => '(To be translated) Can not log you in as you are currently banned.', + 'logOutBannedUser' => '(To be translated) You have been logged out because you have been banned.', 'badAttempt' => 'Oturumunuz açılamıyor. Lütfen kimlik bilgilerinizi kontrol edin.', 'noPassword' => 'Parola olmadan bir kullanıcı doğrulanamaz.', 'invalidPassword' => 'Oturumunuz açılamıyor. Lütfen şifrenizi kontrol edin.', diff --git a/src/Traits/Bannable.php b/src/Traits/Bannable.php new file mode 100644 index 000000000..b151c1b64 --- /dev/null +++ b/src/Traits/Bannable.php @@ -0,0 +1,58 @@ +status && $this->status === 'banned'; + } + + /** + * Ban the user from logging in. + * + * @return $this + */ + public function ban(?string $message = null): self + { + $this->status = 'banned'; + $this->status_message = $message; + + $users = auth()->getProvider(); + + $users->save($this); + + return $this; + } + + /** + * Unban the user and allow them to login + * + * @return $this + */ + public function unBan(): self + { + $this->status = null; + $this->status_message = null; + + $users = auth()->getProvider(); + + $users->save($this); + + return $this; + } + + /** + * Returns the ban message. + */ + public function getBanMessage(): ?string + { + return $this->status_message; + } +} diff --git a/tests/Authorization/AuthorizableTest.php b/tests/Authorization/AuthorizableTest.php index aeeaeb7c7..464cfcf1c 100644 --- a/tests/Authorization/AuthorizableTest.php +++ b/tests/Authorization/AuthorizableTest.php @@ -326,4 +326,33 @@ public function testCreatedAtIfDefaultLocaleSetFaWithAddGroup(): void Locale::setDefault($currentLocale); Time::setTestNow(); } + + public function testBanningUser(): void + { + $this->assertFalse($this->user->isBanned()); + + $this->user->ban(); + + $this->assertTrue($this->user->isBanned()); + } + + public function testUnbanningUser(): void + { + $this->user->ban(); + + $this->assertTrue($this->user->isBanned()); + + $this->user->unBan(); + + $this->assertFalse($this->user->isBanned()); + } + + public function testGetBanMessage(): void + { + $this->assertNull($this->user->getBanMessage()); + + $this->user->ban('You are banned'); + + $this->assertSame('You are banned', $this->user->getBanMessage()); + } }