Add activity logging for authentication events
This commit is contained in:
		
							parent
							
								
									5bb66a00d8
								
							
						
					
					
						commit
						0999ad7ff0
					
				
							
								
								
									
										18
									
								
								app/Events/Auth/ProvidedAuthenticationToken.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								app/Events/Auth/ProvidedAuthenticationToken.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Pterodactyl\Events\Auth; | ||||
| 
 | ||||
| use Pterodactyl\Models\User; | ||||
| 
 | ||||
| class ProvidedAuthenticationToken | ||||
| { | ||||
|     public User $user; | ||||
| 
 | ||||
|     public bool $recovery; | ||||
| 
 | ||||
|     public function __construct(User $user, bool $recovery = false) | ||||
|     { | ||||
|         $this->user = $user; | ||||
|         $this->recovery = $recovery; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Pterodactyl\Extensions\Illuminate\Events\Contracts; | ||||
| 
 | ||||
| use Illuminate\Contracts\Events\Dispatcher; | ||||
| 
 | ||||
| interface SubscribesToEvents | ||||
| { | ||||
|     public function subscribe(Dispatcher $events): void; | ||||
| } | ||||
| @ -9,11 +9,14 @@ use Pterodactyl\Services\Activity\ActivityLogService; | ||||
| /** | ||||
|  * @method static ActivityLogService anonymous() | ||||
|  * @method static ActivityLogService event(string $action) | ||||
|  * @method static ActivityLogService withDescription(?string $description) | ||||
|  * @method static ActivityLogService withSubject(Model $subject) | ||||
|  * @method static ActivityLogService withActor(Model $actor) | ||||
|  * @method static ActivityLogService description(?string $description) | ||||
|  * @method static ActivityLogService subject(Model $subject) | ||||
|  * @method static ActivityLogService actor(Model $actor) | ||||
|  * @method static ActivityLogService withProperties(\Illuminate\Support\Collection|array $properties) | ||||
|  * @method static ActivityLogService withProperty(string $key, mixed $value) | ||||
|  * @method static ActivityLogService withRequestMetadata() | ||||
|  * @method static ActivityLogService property(string $key, mixed $value) | ||||
|  * @method static \Pterodactyl\Models\ActivityLog log(string $description = null) | ||||
|  * @method static ActivityLogService clone() | ||||
|  * @method static mixed transaction(\Closure $callback) | ||||
|  */ | ||||
| class Activity extends Facade | ||||
|  | ||||
| @ -7,8 +7,10 @@ use Carbon\CarbonInterface; | ||||
| use Pterodactyl\Models\User; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| use PragmaRX\Google2FA\Google2FA; | ||||
| use Illuminate\Support\Facades\Event; | ||||
| use Illuminate\Contracts\Encryption\Encrypter; | ||||
| use Illuminate\Database\Eloquent\ModelNotFoundException; | ||||
| use Pterodactyl\Events\Auth\ProvidedAuthenticationToken; | ||||
| use Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest; | ||||
| use Illuminate\Contracts\Validation\Factory as ValidationFactory; | ||||
| 
 | ||||
| @ -72,12 +74,16 @@ class LoginCheckpointController extends AbstractLoginController | ||||
|         // Recovery tokens go through a slightly different pathway for usage.
 | ||||
|         if (!is_null($recoveryToken = $request->input('recovery_token'))) { | ||||
|             if ($this->isValidRecoveryToken($user, $recoveryToken)) { | ||||
|                 Event::dispatch(new ProvidedAuthenticationToken($user, true)); | ||||
| 
 | ||||
|                 return $this->sendLoginResponse($user, $request); | ||||
|             } | ||||
|         } else { | ||||
|             $decrypted = $this->encrypter->decrypt($user->totp_secret); | ||||
| 
 | ||||
|             if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { | ||||
|                 Event::dispatch(new ProvidedAuthenticationToken($user)); | ||||
| 
 | ||||
|                 return $this->sendLoginResponse($user, $request); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -7,6 +7,7 @@ use Illuminate\Support\Str; | ||||
| use Illuminate\Http\Request; | ||||
| use Pterodactyl\Models\User; | ||||
| use Illuminate\Http\JsonResponse; | ||||
| use Pterodactyl\Facades\Activity; | ||||
| use Illuminate\Contracts\View\View; | ||||
| use Illuminate\Contracts\View\Factory as ViewFactory; | ||||
| use Illuminate\Database\Eloquent\ModelNotFoundException; | ||||
| @ -71,6 +72,8 @@ class LoginController extends AbstractLoginController | ||||
|             return $this->sendLoginResponse($user, $request); | ||||
|         } | ||||
| 
 | ||||
|         Activity::event('login.checkpoint')->withRequestMetadata()->subject($user)->log(); | ||||
| 
 | ||||
|         $request->session()->put('auth_confirmation_token', [ | ||||
|             'user_id' => $user->id, | ||||
|             'token_value' => $token = Str::random(64), | ||||
|  | ||||
							
								
								
									
										40
									
								
								app/Listeners/Auth/AuthenticationListener.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								app/Listeners/Auth/AuthenticationListener.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Pterodactyl\Listeners\Auth; | ||||
| 
 | ||||
| use Pterodactyl\Facades\Activity; | ||||
| use Illuminate\Auth\Events\Login; | ||||
| use Illuminate\Auth\Events\Failed; | ||||
| use Illuminate\Contracts\Events\Dispatcher; | ||||
| use Pterodactyl\Extensions\Illuminate\Events\Contracts\SubscribesToEvents; | ||||
| 
 | ||||
| class AuthenticationListener implements SubscribesToEvents | ||||
| { | ||||
|     /** | ||||
|      * Handles an authentication event by logging the user and information about | ||||
|      * the request. | ||||
|      * | ||||
|      * @param \Illuminate\Auth\Events\Login|\Illuminate\Auth\Events\Failed $event | ||||
|      */ | ||||
|     public function handle($event): void | ||||
|     { | ||||
|         $activity = Activity::withRequestMetadata(); | ||||
|         if ($event->user) { | ||||
|             $activity = $activity->subject($event->user); | ||||
|         } | ||||
| 
 | ||||
|         if ($event instanceof Failed) { | ||||
|             foreach ($event->credentials as $key => $value) { | ||||
|                 $activity = $activity->property($key, $value); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $activity->event($event instanceof Failed ? 'login.failed' : 'login.success')->log(); | ||||
|     } | ||||
| 
 | ||||
|     public function subscribe(Dispatcher $events): void | ||||
|     { | ||||
|         $events->listen(Failed::class, self::class); | ||||
|         $events->listen(Login::class, self::class); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										25
									
								
								app/Listeners/Auth/PasswordResetListener.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/Listeners/Auth/PasswordResetListener.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Pterodactyl\Listeners\Auth; | ||||
| 
 | ||||
| use Illuminate\Http\Request; | ||||
| use Pterodactyl\Facades\Activity; | ||||
| use Illuminate\Auth\Events\PasswordReset; | ||||
| 
 | ||||
| class PasswordResetListener | ||||
| { | ||||
|     protected Request $request; | ||||
| 
 | ||||
|     public function __construct(Request $request) | ||||
|     { | ||||
|         $this->request = $request; | ||||
|     } | ||||
| 
 | ||||
|     public function handle(PasswordReset $event) | ||||
|     { | ||||
|         Activity::event('login.password-reset') | ||||
|             ->withRequestMetadata() | ||||
|             ->subject($event->user) | ||||
|             ->log(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								app/Listeners/Auth/TwoFactorListener.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/Listeners/Auth/TwoFactorListener.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Pterodactyl\Listeners\Auth; | ||||
| 
 | ||||
| use Pterodactyl\Facades\Activity; | ||||
| use Pterodactyl\Events\Auth\ProvidedAuthenticationToken; | ||||
| 
 | ||||
| class TwoFactorListener | ||||
| { | ||||
|     public function handle(ProvidedAuthenticationToken $event) | ||||
|     { | ||||
|         Activity::event($event->recovery ? 'login.recovery-token' : 'login.token') | ||||
|             ->withRequestMetadata() | ||||
|             ->subject($event->user) | ||||
|             ->log(); | ||||
|     } | ||||
| } | ||||
| @ -3,6 +3,7 @@ | ||||
| namespace Pterodactyl\Models; | ||||
| 
 | ||||
| use Pterodactyl\Rules\Username; | ||||
| use Pterodactyl\Facades\Activity; | ||||
| use Illuminate\Support\Collection; | ||||
| use Illuminate\Validation\Rules\In; | ||||
| use Illuminate\Auth\Authenticatable; | ||||
| @ -214,6 +215,11 @@ class User extends Model implements | ||||
|      */ | ||||
|     public function sendPasswordResetNotification($token) | ||||
|     { | ||||
|         Activity::event('login.reset-password') | ||||
|             ->withRequestMetadata() | ||||
|             ->subject($this) | ||||
|             ->log('sending password reset email'); | ||||
| 
 | ||||
|         $this->notify(new ResetPasswordNotification($token)); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -10,6 +10,7 @@ use Pterodactyl\Observers\UserObserver; | ||||
| use Pterodactyl\Observers\ServerObserver; | ||||
| use Pterodactyl\Observers\SubuserObserver; | ||||
| use Pterodactyl\Observers\EggVariableObserver; | ||||
| use Pterodactyl\Listeners\Auth\AuthenticationListener; | ||||
| use Pterodactyl\Events\Server\Installed as ServerInstalledEvent; | ||||
| use Pterodactyl\Notifications\ServerInstalled as ServerInstalledNotification; | ||||
| use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; | ||||
| @ -22,9 +23,11 @@ class EventServiceProvider extends ServiceProvider | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $listen = [ | ||||
|         ServerInstalledEvent::class => [ | ||||
|             ServerInstalledNotification::class, | ||||
|         ], | ||||
|         ServerInstalledEvent::class => [ServerInstalledNotification::class], | ||||
|     ]; | ||||
| 
 | ||||
|     protected $subscribe = [ | ||||
|         AuthenticationListener::class, | ||||
|     ]; | ||||
| 
 | ||||
|     /** | ||||
| @ -39,4 +42,9 @@ class EventServiceProvider extends ServiceProvider | ||||
|         Subuser::observe(SubuserObserver::class); | ||||
|         EggVariable::observe(EggVariableObserver::class); | ||||
|     } | ||||
| 
 | ||||
|     public function shouldDiscoverEvents() | ||||
|     { | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -6,6 +6,7 @@ use Illuminate\Support\Collection; | ||||
| use Pterodactyl\Models\ActivityLog; | ||||
| use Illuminate\Contracts\Auth\Factory; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| use Illuminate\Support\Facades\Request; | ||||
| use Illuminate\Database\ConnectionInterface; | ||||
| 
 | ||||
| class ActivityLogService | ||||
| @ -55,7 +56,7 @@ class ActivityLogService | ||||
|     /** | ||||
|      * Set the description for this activity. | ||||
|      */ | ||||
|     public function withDescription(?string $description): self | ||||
|     public function description(?string $description): self | ||||
|     { | ||||
|         $this->getActivity()->description = $description; | ||||
| 
 | ||||
| @ -65,7 +66,7 @@ class ActivityLogService | ||||
|     /** | ||||
|      * Sets the subject model instance. | ||||
|      */ | ||||
|     public function withSubject(Model $subject): self | ||||
|     public function subject(Model $subject): self | ||||
|     { | ||||
|         $this->getActivity()->subject()->associate($subject); | ||||
| 
 | ||||
| @ -75,7 +76,7 @@ class ActivityLogService | ||||
|     /** | ||||
|      * Sets the actor model instance. | ||||
|      */ | ||||
|     public function withActor(Model $actor): self | ||||
|     public function actor(Model $actor): self | ||||
|     { | ||||
|         $this->getActivity()->actor()->associate($actor); | ||||
| 
 | ||||
| @ -99,28 +100,52 @@ class ActivityLogService | ||||
|      * | ||||
|      * @param mixed $value | ||||
|      */ | ||||
|     public function withProperty(string $key, $value): self | ||||
|     public function property(string $key, $value): self | ||||
|     { | ||||
|         $this->getActivity()->properties = $this->getActivity()->properties->put($key, $value); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Attachs the instance request metadata to the activity log event. | ||||
|      */ | ||||
|     public function withRequestMetadata(): self | ||||
|     { | ||||
|         $this->property('ip', Request::getClientIp()); | ||||
|         $this->property('useragent', Request::userAgent()); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Logs an activity log entry with the set values and then returns the | ||||
|      * model instance to the caller. | ||||
|      */ | ||||
|     public function log(string $description): ActivityLog | ||||
|     public function log(string $description = null): ActivityLog | ||||
|     { | ||||
|         $this->withDescription($description); | ||||
|         $activity = $this->getActivity(); | ||||
| 
 | ||||
|         if (!is_null($description)) { | ||||
|             $activity->description = $description; | ||||
|         } | ||||
| 
 | ||||
|         $activity = $this->activity; | ||||
|         $activity->save(); | ||||
| 
 | ||||
|         $this->activity = null; | ||||
| 
 | ||||
|         return $activity; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns a cloned instance of the service allowing for the creation of a base | ||||
|      * activity log with the ability to change values on the fly without impact. | ||||
|      */ | ||||
|     public function clone(): self | ||||
|     { | ||||
|         return clone $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Executes the provided callback within the scope of a database transaction | ||||
|      * and will only save the activity log entry if everything else succesfully | ||||
| @ -133,7 +158,7 @@ class ActivityLogService | ||||
|     public function transaction(\Closure $callback, string $description = null) | ||||
|     { | ||||
|         if (!is_null($description)) { | ||||
|             $this->withDescription($description); | ||||
|             $this->description($description); | ||||
|         } | ||||
| 
 | ||||
|         return $this->connection->transaction(function () use ($callback) { | ||||
| @ -161,14 +186,14 @@ class ActivityLogService | ||||
|         ]); | ||||
| 
 | ||||
|         if ($subject = $this->targetable->subject()) { | ||||
|             $this->withSubject($subject); | ||||
|             $this->subject($subject); | ||||
|         } | ||||
| 
 | ||||
|         if ($actor = $this->targetable->actor()) { | ||||
|             $this->withActor($actor); | ||||
|             $this->actor($actor); | ||||
|         } elseif ($user = $this->manager->guard()->user()) { | ||||
|             if ($user instanceof Model) { | ||||
|                 $this->withActor($user); | ||||
|                 $this->actor($user); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DaneEveritt
						DaneEveritt