Skip to content

Commit

Permalink
misc fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
imorland committed Nov 3, 2024
1 parent d9be45f commit 62f12bf
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 27 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ composer require blomstra/gdpr:@beta

All forum users now have a `Personal Data` section within their account settings page:

![image](https://github.com/blomstra/flarum-ext-gdpr/assets/16573496/4e469956-709f-4ba3-a5fe-d3fcb0401b73)
![image](https://github.com/flarum/gdpr/assets/16573496/4e469956-709f-4ba3-a5fe-d3fcb0401b73)

From here, users may self-service export their data from the forum, or start an erasure request. Erasure requests are queued up for admins/moderators to process. Any unprocessed requests that are still pending after 30 days will be processed automatically using the configured default method (Deletion or Anonymization).

Expand Down Expand Up @@ -109,5 +109,4 @@ These are the known extensions which offer GDPR data integration with this exten

### FAQ & Recommendations

- Generating the zip archive can be pushed to [queue functionality](https://extiverse.com/?filter[q]=queue). This is exceptionally important on larger communities and with more extensions that work with the gdpr extension to allow data exports.

- Generating the zip archive can be pushed to queue functionality. This is exceptionally important on larger communities and with more extensions that work with the gdpr extension to allow data exports.
17 changes: 8 additions & 9 deletions js/package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
{
"private": true,
"name": "@blomstra/gdpr",
"name": "@flarum/gdpr",
"prettier": "@flarum/prettier-config",
"dependencies": {
"flarum-webpack-config": "^2.0.0",
"@flarum/prettier-config": "^1.0.0",
"flarum-tsconfig": "^1.0.2",
"lodash-es": "^4.17.21",
"pusher-js": "^7.0.6",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4"
"pusher-js": "^7.0.6"
},
"scripts": {
"dev": "webpack --mode development --watch",
Expand All @@ -23,9 +18,13 @@
"format-check": "prettier --check src"
},
"devDependencies": {
"flarum-webpack-config": "^2.0.0",
"@flarum/prettier-config": "^1.0.0",
"flarum-tsconfig": "^1.0.3",
"prettier": "^3.0.3",
"flarum-tsconfig": "^1.0.2",
"typescript": "^4.5.4",
"typescript-coverage-report": "^0.6.1"
"typescript-coverage-report": "^0.6.1",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4"
}
}
10 changes: 10 additions & 0 deletions js/src/@types/shims.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'flarum/forum/ForumApplication';
import 'flarum/common/models/User';
import 'flarum/forum/components/SettingsPage';

declare module 'flarum/forum/ForumApplication' {
import ErasureRequestsListState from '../forum/states/ErasureRequestsListState';
Expand All @@ -18,3 +19,12 @@ declare module 'flarum/common/models/User' {
erasureRequest: ErasureRequest;
}
}

declare module 'flarum/forum/components/SettingsPage' {
import ItemList from 'flarum/common/utils/ItemList';
import Mithril from 'mithril';

export default interface SettingsPage {
dataItems(): ItemList<Mithril.Children>;
}
}
2 changes: 1 addition & 1 deletion js/src/forum/components/ExportAvailableNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default class ExportAvailableNotification extends Notification {
const exportModel = this.attrs.notification.subject() as Export;

// Building the full url scheme so that Mithril treats this as an external link, so the download will work correctly.
return app.forum.attribute('baseUrl') + `/gdpr/export/${exportModel.file()}`;
return app.forum.attribute<string>('baseUrl') + `/gdpr/export/${exportModel.file()}`;
}

content() {
Expand Down
8 changes: 2 additions & 6 deletions js/src/forum/extenders/extendUserSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,12 @@ export default function extendUserSettingsPage() {
items.add(
'dataItems',
<FieldSet className="Settings-gdpr" label={app.translator.trans('blomstra-gdpr.forum.settings.data.heading')}>
{
/** @ts-ignore **/
this.dataItems().toArray()
}
{this.dataItems().toArray()}
</FieldSet>,
90
-100
);
});

/** @ts-ignore */
SettingsPage.prototype.dataItems = function (): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

use Flarum\Database\Migration;

return Migration::addColumns('gdpr_erasure', [
'cancelled_at' => ['datetime', 'nullable' => true]
]);
12 changes: 6 additions & 6 deletions src/Api/Controller/DeleteErasureRequestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@

namespace Flarum\Gdpr\Api\Controller;

use Carbon\Carbon;
use Flarum\Api\Controller\AbstractDeleteController;
use Flarum\Gdpr\Models\ErasureRequest;
use Flarum\Gdpr\Notifications\ErasureRequestCancelledBlueprint;
use Flarum\Http\RequestUtil;
use Flarum\Notification\NotificationSyncer;
use Flarum\User\Exception\NotAuthenticatedException;
use Flarum\User\Exception\PermissionDeniedException;
use Illuminate\Support\Arr;
use Psr\Http\Message\ServerRequestInterface;
Expand All @@ -30,10 +30,7 @@ public function __construct(protected NotificationSyncer $notifications)
public function delete(ServerRequestInterface $request)
{
$actor = RequestUtil::getActor($request);

if ($actor->isGuest()) {
throw new NotAuthenticatedException();
}
$actor->assertRegistered();

$id = Arr::get($request->getQueryParams(), 'id');
$erasureRequest = ErasureRequest::query()->where('id', $id)->firstOrFail();
Expand All @@ -44,7 +41,10 @@ public function delete(ServerRequestInterface $request)
throw new PermissionDeniedException();
}

// TODO: set status to cancelled with timestamp/actor
$erasureRequest->status = ErasureRequest::STATUS_CANCELLED;
$erasureRequest->cancelled_at = Carbon::now();

$erasureRequest->save();

$this->notifications->sync(new ErasureRequestCancelledBlueprint($erasureRequest), [$erasureRequest->user]);
}
Expand Down
5 changes: 5 additions & 0 deletions src/Api/Controller/UpdateErasureRequestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public function data(ServerRequestInterface $request, Document $document)

$erasureRequest = ErasureRequest::findOrFail($id);

// if the request is cancelled, we should not proceed, but throw an error
if ($erasureRequest->status === ErasureRequest::STATUS_CANCELLED || $erasureRequest->cancelled_at !== null) {
throw new ValidationException(['user' => 'Erasure request is cancelled.']);
}

// if the request is not confirmed, or already processed we should not proceed, but throw an error
if ($erasureRequest->status !== ErasureRequest::STATUS_USER_CONFIRMED || $erasureRequest->user_confirmed_at === null) {
throw new ValidationException(['user' => 'Erasure request is not confirmed.']);
Expand Down
14 changes: 12 additions & 2 deletions src/Models/ErasureRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Flarum\Database\AbstractModel;
use Flarum\Database\ScopeVisibilityTrait;
use Flarum\User\User;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
* @property int $id
Expand All @@ -30,6 +31,7 @@
* @property string|null $processor_comment
* @property Carbon|null $processed_at
* @property string|null $processed_mode
* @property Carbon|null $cancelled_at
*/
class ErasureRequest extends AbstractModel
{
Expand All @@ -46,19 +48,27 @@ class ErasureRequest extends AbstractModel

protected $table = 'gdpr_erasure';

protected $guarded = ['id'];

protected $casts = [
'created_at' => 'datetime',
'user_confirmed_at' => 'datetime',
'processed_at' => 'datetime',
'cancelled_at' => 'datetime',
];

public function user()
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}

public function processedBy()
public function processedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'processed_by');
}

public function isConfirmed(): bool
{
return $this->status === ErasureRequest::STATUS_USER_CONFIRMED && $this->user_confirmed_at;
}
}
13 changes: 13 additions & 0 deletions tests/integration/api/CancelErasureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Carbon\Carbon;
use Flarum\Extend;
use Flarum\Gdpr\Models\ErasureRequest;
use Flarum\Notification\Notification;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
Expand Down Expand Up @@ -93,6 +94,10 @@ public function user_can_cancel_own_unconfirmed_erasure_request()

$this->assertEquals(204, $response->getStatusCode());

$erasureRequest = ErasureRequest::query()->find(1);

$this->assertEquals(ErasureRequest::STATUS_CANCELLED, $erasureRequest->status);

$notification = Notification::query()->where('user_id', 4)->where('type', 'gdpr_erasure_cancelled')->first();

$this->assertNotNull($notification);
Expand All @@ -111,6 +116,10 @@ public function user_can_cancel_own_confirmed_erasure_request()

$this->assertEquals(204, $response->getStatusCode());

$erasureRequest = ErasureRequest::query()->find(2);

$this->assertEquals(ErasureRequest::STATUS_CANCELLED, $erasureRequest->status);

$notification = Notification::query()->where('user_id', 5)->where('type', 'gdpr_erasure_cancelled')->first();

$this->assertNotNull($notification);
Expand Down Expand Up @@ -143,6 +152,10 @@ public function moderator_can_cancel_others_erasure_request()

$this->assertEquals(204, $response->getStatusCode());

$erasureRequest = ErasureRequest::query()->find(1);

$this->assertEquals(ErasureRequest::STATUS_CANCELLED, $erasureRequest->status);

$notification = Notification::query()->where('user_id', 4)->where('type', 'gdpr_erasure_cancelled')->first();

$this->assertNotNull($notification);
Expand Down
32 changes: 32 additions & 0 deletions tests/integration/api/ProcessErasureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public function setUp(): void
['id' => 3, 'username' => 'moderator', 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', 'email' => '[email protected]', 'is_email_confirmed' => 1],
['id' => 4, 'username' => 'user4', 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', 'email' => '[email protected]', 'is_email_confirmed' => 1],
['id' => 5, 'username' => 'user5', 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', 'email' => '[email protected]', 'is_email_confirmed' => 1, 'joined_at' => Carbon::now(), 'last_seen_at' => Carbon::now(), 'avatar_url' => 'avatar.jpg'],
['id' => 6, 'username' => 'user6', 'password' => '$2y$10$LO59tiT7uggl6Oe23o/O6.utnF6ipngYjvMvaxo1TciKqBttDNKim', 'email' => '[email protected]', 'is_email_confirmed' => 1, 'joined_at' => Carbon::now(), 'last_seen_at' => Carbon::now(), 'avatar_url' => 'avatar.jpg'],
],
'groups' => [
['id' => 5, 'name_singular' => 'customgroup', 'name_plural' => 'customgroups'],
Expand All @@ -48,6 +49,7 @@ public function setUp(): void
'gdpr_erasure' => [
['id' => 1, 'user_id' => 4, 'verification_token' => 'abc123', 'status' => 'awaiting_user_confirmation', 'reason' => 'I want to be forgotten', 'created_at' => Carbon::now()],
['id' => 2, 'user_id' => 5, 'verification_token' => '123abc', 'status' => 'user_confirmed', 'reason' => 'I also want to be forgotten', 'created_at' => Carbon::now(), 'user_confirmed_at' => Carbon::now()],
['id' => 3, 'user_id' => 6, 'verification_token' => '321zyx', 'status' => 'cancelled', 'created_at' => Carbon::now()->subDay(), 'cancelled_at' => Carbon::now()],
],
]);
}
Expand Down Expand Up @@ -363,4 +365,34 @@ public function user_bio_is_anonimized()
$this->assertEquals('Anonymous2', $user->username);
$this->assertNull($user->bio);
}

/**
* @test
*/
public function cancelled_erasure_requests_are_not_processed()
{
$this->setting('blomstra-gdpr.allow-deletion', true);

$response = $this->send(
$this->request('PATCH', '/api/user-erasure-requests/3', [
'authenticatedAs' => 3,
'json' => [
'data' => [
'attributes' => [
'processor_comment' => 'I have processed this request',
'meta' => [
'mode' => ErasureRequest::MODE_DELETION,
],
],
],
],
])
);

$this->assertEquals(422, $response->getStatusCode());

$data = json_decode($response->getBody()->getContents(), true);

$this->assertEquals('Erasure request is cancelled.', $data['errors'][0]['detail']);
}
}

0 comments on commit 62f12bf

Please sign in to comment.