22
33namespace Pterodactyl \Http \Controllers \Api \Client \Servers ;
44
5+ use Illuminate \Http \Request ;
56use Pterodactyl \Models \Backup ;
67use Pterodactyl \Models \Server ;
8+ use Pterodactyl \Models \AuditLog ;
79use Illuminate \Http \JsonResponse ;
10+ use Pterodactyl \Models \Permission ;
11+ use Illuminate \Validation \UnauthorizedException ;
812use Pterodactyl \Services \Backups \DeleteBackupService ;
9- use Pterodactyl \Repositories \ Eloquent \ BackupRepository ;
13+ use Pterodactyl \Services \ Backups \ DownloadLinkService ;
1014use Pterodactyl \Services \Backups \InitiateBackupService ;
15+ use Pterodactyl \Repositories \Wings \DaemonBackupRepository ;
1116use Pterodactyl \Transformers \Api \Client \BackupTransformer ;
1217use Pterodactyl \Http \Controllers \Api \Client \ClientApiController ;
13- use Pterodactyl \ Http \ Requests \ Api \ Client \ Servers \ Backups \ GetBackupsRequest ;
18+ use Symfony \ Component \ HttpKernel \ Exception \ BadRequestHttpException ;
1419use Pterodactyl \Http \Requests \Api \Client \Servers \Backups \StoreBackupRequest ;
15- use Pterodactyl \Http \Requests \Api \Client \Servers \Backups \DeleteBackupRequest ;
1620
1721class BackupController extends ClientApiController
1822{
@@ -27,33 +31,45 @@ class BackupController extends ClientApiController
2731 private $ deleteBackupService ;
2832
2933 /**
30- * @var \Pterodactyl\Repositories\Eloquent\BackupRepository
34+ * @var \Pterodactyl\Services\Backups\DownloadLinkService
35+ */
36+ private $ downloadLinkService ;
37+
38+ /**
39+ * @var \Pterodactyl\Repositories\Wings\DaemonBackupRepository
3140 */
3241 private $ repository ;
3342
3443 /**
3544 * BackupController constructor.
3645 */
3746 public function __construct (
38- BackupRepository $ repository ,
47+ DaemonBackupRepository $ repository ,
3948 DeleteBackupService $ deleteBackupService ,
40- InitiateBackupService $ initiateBackupService
49+ InitiateBackupService $ initiateBackupService ,
50+ DownloadLinkService $ downloadLinkService
4151 ) {
4252 parent ::__construct ();
4353
54+ $ this ->repository = $ repository ;
4455 $ this ->initiateBackupService = $ initiateBackupService ;
4556 $ this ->deleteBackupService = $ deleteBackupService ;
46- $ this ->repository = $ repository ;
57+ $ this ->downloadLinkService = $ downloadLinkService ;
4758 }
4859
4960 /**
5061 * Returns all of the backups for a given server instance in a paginated
5162 * result set.
5263 *
53- * @return array
64+ * @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation
65+ * @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified
5466 */
55- public function index (GetBackupsRequest $ request , Server $ server )
67+ public function index (Request $ request , Server $ server ): array
5668 {
69+ if (!$ request ->user ()->can (Permission::ACTION_BACKUP_READ , $ server )) {
70+ throw new UnauthorizedException ();
71+ }
72+
5773 $ limit = min ($ request ->query ('per_page ' ) ?? 20 , 50 );
5874
5975 return $ this ->fractal ->collection ($ server ->backups ()->paginate ($ limit ))
@@ -64,17 +80,24 @@ public function index(GetBackupsRequest $request, Server $server)
6480 /**
6581 * Starts the backup process for a server.
6682 *
67- * @return array
68- *
69- * @throws \Exception|\ Throwable
83+ * @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation
84+ * @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified
85+ * @throws \Throwable
7086 */
71- public function store (StoreBackupRequest $ request , Server $ server )
87+ public function store (StoreBackupRequest $ request , Server $ server ): array
7288 {
73- $ backup = $ this ->initiateBackupService
74- ->setIgnoredFiles (
75- explode (PHP_EOL , $ request ->input ('ignored ' ) ?? '' )
76- )
77- ->handle ($ server , $ request ->input ('name ' ));
89+ /** @var \Pterodactyl\Models\Backup $backup */
90+ $ backup = $ server ->audit (AuditLog::SERVER__BACKUP_STARTED , function (AuditLog $ model , Server $ server ) use ($ request ) {
91+ $ backup = $ this ->initiateBackupService
92+ ->setIgnoredFiles (
93+ explode (PHP_EOL , $ request ->input ('ignored ' ) ?? '' )
94+ )
95+ ->handle ($ server , $ request ->input ('name ' ));
96+
97+ $ model ->metadata = ['backup_uuid ' => $ backup ->uuid ];
98+
99+ return $ backup ;
100+ });
78101
79102 return $ this ->fractal ->item ($ backup )
80103 ->transformWith ($ this ->getTransformer (BackupTransformer::class))
@@ -84,10 +107,15 @@ public function store(StoreBackupRequest $request, Server $server)
84107 /**
85108 * Returns information about a single backup.
86109 *
87- * @return array
110+ * @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation
111+ * @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified
88112 */
89- public function view (GetBackupsRequest $ request , Server $ server , Backup $ backup )
113+ public function view (Request $ request , Server $ server , Backup $ backup ): array
90114 {
115+ if (!$ request ->user ()->can (Permission::ACTION_BACKUP_READ , $ server )) {
116+ throw new UnauthorizedException ();
117+ }
118+
91119 return $ this ->fractal ->item ($ backup )
92120 ->transformWith ($ this ->getTransformer (BackupTransformer::class))
93121 ->toArray ();
@@ -97,14 +125,89 @@ public function view(GetBackupsRequest $request, Server $server, Backup $backup)
97125 * Deletes a backup from the panel as well as the remote source where it is currently
98126 * being stored.
99127 *
100- * @return \Illuminate\Http\JsonResponse
128+ * @throws \Throwable
129+ */
130+ public function delete (Request $ request , Server $ server , Backup $ backup ): JsonResponse
131+ {
132+ if (!$ request ->user ()->can (Permission::ACTION_BACKUP_DELETE , $ server )) {
133+ throw new UnauthorizedException ();
134+ }
135+
136+ $ server ->audit (AuditLog::SERVER__BACKUP_DELETED , function (AuditLog $ audit ) use ($ backup ) {
137+ $ audit ->metadata = ['backup_uuid ' => $ backup ->uuid ];
138+
139+ $ this ->deleteBackupService ->handle ($ backup );
140+ });
141+
142+ return new JsonResponse ([], JsonResponse::HTTP_NO_CONTENT );
143+ }
144+
145+ /**
146+ * Download the backup for a given server instance. For daemon local files, the file
147+ * will be streamed back through the Panel. For AWS S3 files, a signed URL will be generated
148+ * which the user is redirected to.
149+ */
150+ public function download (Request $ request , Server $ server , Backup $ backup ): JsonResponse
151+ {
152+ if (!$ request ->user ()->can (Permission::ACTION_BACKUP_DOWNLOAD , $ server )) {
153+ throw new UnauthorizedException ();
154+ }
155+
156+ switch ($ backup ->disk ) {
157+ case Backup::ADAPTER_WINGS :
158+ case Backup::ADAPTER_AWS_S3 :
159+ return new JsonResponse ([
160+ 'object ' => 'signed_url ' ,
161+ 'attributes ' => ['url ' => '' ],
162+ ]);
163+ default :
164+ throw new BadRequestHttpException ();
165+ }
166+ }
167+
168+ /**
169+ * Handles restoring a backup by making a request to the Wings instance telling it
170+ * to begin the process of finding (or downloading) the backup and unpacking it
171+ * over the server files.
172+ *
173+ * If the "truncate" flag is passed through in this request then all of the
174+ * files that currently exist on the server will be deleted before restoring.
175+ * Otherwise the archive will simply be unpacked over the existing files.
101176 *
102177 * @throws \Throwable
103178 */
104- public function delete ( DeleteBackupRequest $ request , Server $ server , Backup $ backup )
179+ public function restore ( Request $ request , Server $ server , Backup $ backup ): JsonResponse
105180 {
106- $ this ->deleteBackupService ->handle ($ backup );
181+ if (!$ request ->user ()->can (Permission::ACTION_BACKUP_RESTORE , $ server )) {
182+ throw new UnauthorizedException ();
183+ }
184+
185+ // Cannot restore a backup unless a server is fully installed and not currently
186+ // processing a different backup restoration request.
187+ if (!is_null ($ server ->status )) {
188+ throw new BadRequestHttpException ('This server is not currently in a state that allows for a backup to be restored. ' );
189+ }
190+
191+ if (!$ backup ->is_successful && !$ backup ->completed_at ) {
192+ throw new BadRequestHttpException ('This backup cannot be restored at this time: not completed or failed. ' );
193+ }
194+
195+ $ server ->audit (AuditLog::SERVER__BACKUP_RESTORE_STARTED , function (AuditLog $ audit , Server $ server ) use ($ backup , $ request ) {
196+ $ audit ->metadata = ['backup_uuid ' => $ backup ->uuid ];
197+
198+ // If the backup is for an S3 file we need to generate a unique Download link for
199+ // it that will allow Wings to actually access the file.
200+ if ($ backup ->disk === Backup::ADAPTER_AWS_S3 ) {
201+ $ url = $ this ->downloadLinkService ->handle ($ backup , $ request ->user ());
202+ }
203+
204+ // Update the status right away for the server so that we know not to allow certain
205+ // actions against it via the Panel API.
206+ $ server ->update (['status ' => Server::STATUS_RESTORING_BACKUP ]);
207+
208+ $ this ->repository ->setServer ($ server )->restore ($ backup , $ url ?? null , $ request ->input ('truncate ' ) === 'true ' );
209+ });
107210
108- return JsonResponse:: create ([], JsonResponse::HTTP_NO_CONTENT );
211+ return new JsonResponse ([], JsonResponse::HTTP_NO_CONTENT );
109212 }
110213}
0 commit comments