Skip to content

Commit

Permalink
Log activity when modifying account details
Browse files Browse the repository at this point in the history
  • Loading branch information
DaneEveritt committed May 29, 2022
1 parent 0b2c0db commit 287fd60
Show file tree
Hide file tree
Showing 15 changed files with 85 additions and 57 deletions.
11 changes: 8 additions & 3 deletions app/Http/Controllers/Api/Client/AccountController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Http\Response;
use Illuminate\Auth\AuthManager;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Facades\Activity;
use Pterodactyl\Services\Users\UserUpdateService;
use Pterodactyl\Transformers\Api\Client\AccountTransformer;
use Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest;
Expand Down Expand Up @@ -43,14 +44,16 @@ public function index(Request $request): array

/**
* Update the authenticated user's email address.
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function updateEmail(UpdateEmailRequest $request): JsonResponse
{
$original = $request->user()->email;
$this->updateService->handle($request->user(), $request->validated());

Activity::event('user:account.email-changed')
->property(['old' => $original, 'new' => $request->input('email')])
->log();

return new JsonResponse([], Response::HTTP_NO_CONTENT);
}

Expand All @@ -76,6 +79,8 @@ public function updatePassword(UpdatePasswordRequest $request): JsonResponse
$guard->logoutOtherDevices($request->input('password'));
}

Activity::event('user:account.password-changed')->log();

return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
}
57 changes: 15 additions & 42 deletions app/Http/Controllers/Api/Client/ApiKeyController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,14 @@

use Pterodactyl\Models\ApiKey;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Facades\Activity;
use Pterodactyl\Exceptions\DisplayException;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Services\Api\KeyCreationService;
use Pterodactyl\Repositories\Eloquent\ApiKeyRepository;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
use Pterodactyl\Transformers\Api\Client\ApiKeyTransformer;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Pterodactyl\Http\Requests\Api\Client\Account\StoreApiKeyRequest;

class ApiKeyController extends ClientApiController
{
/**
* @var \Pterodactyl\Services\Api\KeyCreationService
*/
private $keyCreationService;

/**
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
private $encrypter;

/**
* @var \Pterodactyl\Repositories\Eloquent\ApiKeyRepository
*/
private $repository;

/**
* ApiKeyController constructor.
*/
public function __construct(
Encrypter $encrypter,
KeyCreationService $keyCreationService,
ApiKeyRepository $repository
) {
parent::__construct();

$this->encrypter = $encrypter;
$this->keyCreationService = $keyCreationService;
$this->repository = $repository;
}

/**
* Returns all of the API keys that exist for the given client.
*
Expand Down Expand Up @@ -75,6 +42,11 @@ public function store(StoreApiKeyRequest $request)
$request->input('allowed_ips')
);

Activity::event('user:api-key.create')
->subject($token->accessToken)
->property('identifier', $token->accessToken->identifier)
->log();

return $this->fractal->item($token->accessToken)
->transformWith($this->getTransformer(ApiKeyTransformer::class))
->addMeta(['secret_token' => $token->plainTextToken])
Expand All @@ -88,15 +60,16 @@ public function store(StoreApiKeyRequest $request)
*/
public function delete(ClientApiRequest $request, string $identifier)
{
$response = $this->repository->deleteWhere([
'key_type' => ApiKey::TYPE_ACCOUNT,
'user_id' => $request->user()->id,
'identifier' => $identifier,
]);
$key = $request->user()->apiKeys()
->where('key_type', ApiKey::TYPE_ACCOUNT)
->where('identifier', $identifier)
->first();

if (!$response) {
throw new NotFoundHttpException();
}
Activity::event('user:api-key.delete')
->property('identifer', $key->identifer)
->log();

$key->delete();

return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
}
Expand Down
15 changes: 14 additions & 1 deletion app/Http/Controllers/Api/Client/SSHKeyController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Pterodactyl\Http\Controllers\Api\Client;

use Illuminate\Http\JsonResponse;
use Pterodactyl\Facades\Activity;
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
use Pterodactyl\Transformers\Api\Client\UserSSHKeyTransformer;
use Pterodactyl\Http\Requests\Api\Client\Account\StoreSSHKeyRequest;
Expand Down Expand Up @@ -31,6 +32,11 @@ public function store(StoreSSHKeyRequest $request): array
'fingerprint' => $request->getKeyFingerprint(),
]);

Activity::event('user:ssh-key.create')
->subject($model)
->property('fingerprint', $request->getKeyFingerprint())
->log();

return $this->fractal->item($model)
->transformWith($this->getTransformer(UserSSHKeyTransformer::class))
->toArray();
Expand All @@ -41,7 +47,14 @@ public function store(StoreSSHKeyRequest $request): array
*/
public function delete(ClientApiRequest $request, string $identifier): JsonResponse
{
$request->user()->sshKeys()->where('fingerprint', $identifier)->delete();
$key = $request->user()->sshKeys()->where('fingerprint', $identifier)->firstOrFail();

$key->delete();

Activity::event('user:ssh-key.delete')
->subject($key)
->property('fingerprint', $key->fingerprint)
->log();

return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
}
Expand Down
5 changes: 5 additions & 0 deletions app/Http/Controllers/Api/Client/TwoFactorController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Facades\Activity;
use Illuminate\Contracts\Validation\Factory;
use Illuminate\Validation\ValidationException;
use Pterodactyl\Services\Users\TwoFactorSetupService;
Expand Down Expand Up @@ -89,6 +90,8 @@ public function store(Request $request)

$tokens = $this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true);

Activity::event('user:two-factor.create')->log();

return new JsonResponse([
'object' => 'recovery_tokens',
'attributes' => [
Expand Down Expand Up @@ -117,6 +120,8 @@ public function delete(Request $request)
'use_totp' => false,
]);

Activity::event('user:two-factor.delete')->log();

return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function index(ReportBackupCompleteRequest $request, string $backup)
throw new BadRequestHttpException('Cannot update the status of a backup that is already marked as completed.');
}

$action = $request->boolean('successful') ? 'server:backup.complete' : 'server:backup.failed';
$action = $request->boolean('successful') ? 'server:backup.complete' : 'server:backup.fail';
$log = Activity::event($action)->subject($model, $model->server)->property('name', $model->name);

$log->transaction(function () use ($model, $request) {
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/Auth/LoginController.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public function login(Request $request): JsonResponse
return $this->sendLoginResponse($user, $request);
}

Activity::event('login.checkpoint')->withRequestMetadata()->subject($user)->log();
Activity::event('auth:checkpoint')->withRequestMetadata()->subject($user)->log();

$request->session()->put('auth_confirmation_token', [
'user_id' => $user->id,
Expand Down
22 changes: 22 additions & 0 deletions app/Http/Middleware/AccountActivitySubject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Pterodactyl\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Pterodactyl\Facades\LogTarget;

class AccountActivitySubject
{
/**
* Sets the actor and default subject for all requests passing through this
* middleware to be the currently logged in user.
*/
public function handle(Request $request, Closure $next)
{
LogTarget::setActor($request->user());
LogTarget::setSubject($request->user());

return $next($request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Pterodactyl\Models\Server;
use Pterodactyl\Facades\LogTarget;

class ServerActivityLogs
class ServerActivitySubject
{
/**
* Attempts to automatically scope all of the activity log events registered
Expand Down
2 changes: 1 addition & 1 deletion app/Listeners/Auth/AuthenticationListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function handle($event): void
}
}

$activity->event($event instanceof Failed ? 'login.failed' : 'login.success')->log();
$activity->event($event instanceof Failed ? 'auth:fail' : 'auth:success')->log();
}

public function subscribe(Dispatcher $events): void
Expand Down
2 changes: 1 addition & 1 deletion app/Listeners/Auth/PasswordResetListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function __construct(Request $request)

public function handle(PasswordReset $event)
{
Activity::event('login.password-reset')
Activity::event('event:password-reset')
->withRequestMetadata()
->subject($event->user)
->log();
Expand Down
2 changes: 1 addition & 1 deletion app/Listeners/Auth/TwoFactorListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class TwoFactorListener
{
public function handle(ProvidedAuthenticationToken $event)
{
Activity::event($event->recovery ? 'login.recovery-token' : 'login.token')
Activity::event($event->recovery ? 'auth:recovery-token' : 'auth:token')
->withRequestMetadata()
->subject($event->user)
->log();
Expand Down
2 changes: 1 addition & 1 deletion app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public function toVueObject(): array
*/
public function sendPasswordResetNotification($token)
{
Activity::event('login.reset-password')
Activity::event('auth:reset-password')
->withRequestMetadata()
->subject($this)
->log('sending password reset email');
Expand Down
4 changes: 4 additions & 0 deletions app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use Pterodactyl\Models\User;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Backup;
use Pterodactyl\Models\ApiKey;
use Pterodactyl\Models\UserSSHKey;
use Illuminate\Support\Facades\URL;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Schema;
Expand Down Expand Up @@ -39,8 +41,10 @@ public function boot()
}

Relation::enforceMorphMap([
'api_key' => ApiKey::class,
'backup' => Backup::class,
'server' => Server::class,
'ssh_key' => UserSSHKey::class,
'user' => User::class,
]);
}
Expand Down
7 changes: 6 additions & 1 deletion app/Services/Activity/ActivityLogService.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
use Illuminate\Support\Arr;
use Webmozart\Assert\Assert;
use Illuminate\Support\Collection;
use Pterodactyl\Models\ActivityLog;
use Illuminate\Support\Facades\Log;
use Pterodactyl\Models\ActivityLog;
use Illuminate\Contracts\Auth\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Request;
Expand Down Expand Up @@ -148,6 +148,11 @@ public function log(string $description = null): ActivityLog
try {
return $this->save();
} catch (\Throwable|\Exception $exception) {
if (config('app.env') !== 'production') {
/* @noinspection PhpUnhandledExceptionInspection */
throw $exception;
}

Log::error($exception);
}

Expand Down
7 changes: 4 additions & 3 deletions routes/api-client.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

use Illuminate\Support\Facades\Route;
use Pterodactyl\Http\Controllers\Api\Client;
use Pterodactyl\Http\Middleware\ServerActivityLogs;
use Pterodactyl\Http\Middleware\ServerActivitySubject;
use Pterodactyl\Http\Middleware\AccountActivitySubject;
use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication;
use Pterodactyl\Http\Middleware\Api\Client\Server\ResourceBelongsToServer;
use Pterodactyl\Http\Middleware\Api\Client\Server\AuthenticateServerAccess;
Expand All @@ -18,7 +19,7 @@
Route::get('/', [Client\ClientController::class, 'index'])->name('api:client.index');
Route::get('/permissions', [Client\ClientController::class, 'permissions']);

Route::group(['prefix' => '/account'], function () {
Route::prefix('/account')->middleware(AccountActivitySubject::class)->group(function () {
Route::prefix('/')->withoutMiddleware(RequireTwoFactorAuthentication::class)->group(function () {
Route::get('/', [Client\AccountController::class, 'index'])->name('api:client.account');
Route::get('/two-factor', [Client\TwoFactorController::class, 'index']);
Expand Down Expand Up @@ -51,7 +52,7 @@
Route::group([
'prefix' => '/servers/{server}',
'middleware' => [
ServerActivityLogs::class,
ServerActivitySubject::class,
AuthenticateServerAccess::class,
ResourceBelongsToServer::class,
],
Expand Down

0 comments on commit 287fd60

Please sign in to comment.