Skip to content

Commit 2723329

Browse files
authored
Merge pull request #9 from sander3/data-retention-feature
Data retention feature
2 parents bd13ba4 + 4314f1e commit 2723329

File tree

10 files changed

+358
-3
lines changed

10 files changed

+358
-3
lines changed

config/gdpr.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,34 @@
2929
'auth',
3030
],
3131

32+
/*
33+
|--------------------------------------------------------------------------
34+
| Cleanup Strategy
35+
|--------------------------------------------------------------------------
36+
|
37+
| This strategy will be used to clean up inactive users. Do not forget to
38+
| mention these thresholds in your terms and conditions.
39+
|
40+
*/
41+
42+
'cleanup' => [
43+
44+
'strategy' => 'Soved\Laravel\Gdpr\Jobs\Cleanup\Strategies\DefaultStrategy',
45+
46+
'defaultStrategy' => [
47+
48+
/*
49+
* The number of months for which inactive users must be kept.
50+
*/
51+
'keepInactiveUsersForMonths' => 6,
52+
53+
/*
54+
* The number of days before deletion at which inactive users will be notified.
55+
*/
56+
'notifyUsersDaysBeforeDeletion' => 14,
57+
58+
],
59+
60+
],
61+
3262
];

readme.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
# GDPR compliant data portability with ease
1+
# GDPR compliance with ease
22

33
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/sander3/laravel-gdpr/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/sander3/laravel-gdpr/?branch=master)
44
[![Latest Stable Version](https://poser.pugx.org/soved/laravel-gdpr/v/stable)](https://packagist.org/packages/soved/laravel-gdpr)
55
[![Monthly Downloads](https://poser.pugx.org/soved/laravel-gdpr/d/monthly)](https://packagist.org/packages/soved/laravel-gdpr)
66
[![License](https://poser.pugx.org/soved/laravel-gdpr/license)](https://packagist.org/packages/soved/laravel-gdpr)
77

8-
This package exposes an endpoint where authenticated users can download their data as required by GDPR article 20.
8+
This package exposes an endpoint where authenticated users can download their data as required by GDPR article 20. This package also provides you with a trait to easily [encrypt personal data](#encryption) and a strategy to [clean up inactive users](#data-retention) as required by GDPR article 5e.
99

1010
## Requirements
1111

@@ -195,9 +195,34 @@ class User extends Authenticatable
195195

196196
```
197197

198+
### Data Retention
199+
200+
You may clean up inactive users using the `gdpr:cleanup` command. Schedule the command to run every day at midnight, or run it yourself. In order for this to work, add the `Soved\Laravel\Gdpr\Retentionable` trait to the `App\User` model:
201+
202+
```php
203+
<?php
204+
205+
namespace App;
206+
207+
use Soved\Laravel\Gdpr\Portable;
208+
use Soved\Laravel\Gdpr\Retentionable;
209+
use Illuminate\Notifications\Notifiable;
210+
use Illuminate\Foundation\Auth\User as Authenticatable;
211+
212+
class User extends Authenticatable
213+
{
214+
use Retentionable, Portable, Notifiable;
215+
}
216+
217+
```
218+
219+
You may listen for the `Soved\Laravel\Gdpr\Events\GdprInactiveUser` event to notify your users about their inactivity, which will be dispatched two weeks before deletion. The `Soved\Laravel\Gdpr\Events\GdprInactiveUserDeleted` event will be dispatched upon deletion.
220+
221+
Feel free to customize what happens to inactive users by creating your own cleanup strategy.
222+
198223
## Roadmap
199224

200-
- Data retention
225+
- Tests
201226

202227
## Security Vulnerabilities
203228

src/Console/Commands/Cleanup.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace Soved\Laravel\Gdpr\Console\Commands;
4+
5+
use App\User;
6+
use Illuminate\Console\Command;
7+
use Soved\Laravel\Gdpr\Jobs\Cleanup\CleanupJob;
8+
9+
class Cleanup extends Command
10+
{
11+
/**
12+
* The name and signature of the console command.
13+
*
14+
* @var string
15+
*/
16+
protected $signature = 'gdpr:cleanup';
17+
18+
/**
19+
* The console command description.
20+
*
21+
* @var string
22+
*/
23+
protected $description = 'Cleanup inactive users';
24+
25+
/**
26+
* Create a new command instance.
27+
*
28+
* @return void
29+
*/
30+
public function __construct()
31+
{
32+
parent::__construct();
33+
}
34+
35+
/**
36+
* Execute the console command.
37+
*
38+
* @return mixed
39+
*/
40+
public function handle()
41+
{
42+
$config = config('gdpr');
43+
44+
$users = User::all();
45+
46+
$strategy = app($config['cleanup']['strategy']);
47+
48+
CleanupJob::dispatch($users, $strategy);
49+
50+
$this->info('CleanupJob dispatched');
51+
}
52+
}

src/Events/GdprInactiveUser.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Soved\Laravel\Gdpr\Events;
4+
5+
use App\User;
6+
use Illuminate\Queue\SerializesModels;
7+
8+
class GdprInactiveUser
9+
{
10+
use SerializesModels;
11+
12+
/**
13+
* @var \App\User
14+
*/
15+
public $user;
16+
17+
/**
18+
* Create a new event instance.
19+
*
20+
* @param \App\User $user
21+
* @return void
22+
*/
23+
public function __construct(User $user)
24+
{
25+
$this->user = $user;
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Soved\Laravel\Gdpr\Events;
4+
5+
use App\User;
6+
use Illuminate\Queue\SerializesModels;
7+
8+
class GdprInactiveUserDeleted
9+
{
10+
use SerializesModels;
11+
12+
/**
13+
* @var \App\User
14+
*/
15+
public $user;
16+
17+
/**
18+
* Create a new event instance.
19+
*
20+
* @param \App\User $user
21+
* @return void
22+
*/
23+
public function __construct(User $user)
24+
{
25+
$this->user = $user;
26+
}
27+
}

src/GdprServiceProvider.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Support\Facades\Route;
66
use Illuminate\Support\ServiceProvider;
7+
use Soved\Laravel\Gdpr\Console\Commands\Cleanup;
78

89
class GdprServiceProvider extends ServiceProvider
910
{
@@ -15,6 +16,7 @@ class GdprServiceProvider extends ServiceProvider
1516
public function boot()
1617
{
1718
$this->registerRoutes();
19+
$this->registerCommands();
1820
}
1921

2022
/**
@@ -33,6 +35,20 @@ protected function registerRoutes()
3335
});
3436
}
3537

38+
/**
39+
* Register the GDPR commands.
40+
*
41+
* @return void
42+
*/
43+
protected function registerCommands()
44+
{
45+
if ($this->app->runningInConsole()) {
46+
$this->commands([
47+
Cleanup::class,
48+
]);
49+
}
50+
}
51+
3652
/**
3753
* Register the application services.
3854
*

src/Jobs/Cleanup/CleanupJob.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Soved\Laravel\Gdpr\Jobs\Cleanup;
4+
5+
use Illuminate\Bus\Queueable;
6+
use Illuminate\Queue\SerializesModels;
7+
use Illuminate\Queue\InteractsWithQueue;
8+
use Illuminate\Contracts\Queue\ShouldQueue;
9+
use Illuminate\Foundation\Bus\Dispatchable;
10+
use Illuminate\Database\Eloquent\Collection;
11+
12+
class CleanupJob implements ShouldQueue
13+
{
14+
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
15+
16+
/**
17+
* @var \Illuminate\Database\Eloquent\Collection
18+
*/
19+
public $users;
20+
21+
/**
22+
* @var
23+
*/
24+
public $strategy;
25+
26+
/**
27+
* Create a new job instance.
28+
*
29+
* @param \Illuminate\Database\Eloquent\Collection $users
30+
* @param \Soved\Laravel\Gdpr\Jobs\Cleanup\CleanupStrategy $strategy
31+
* @return void
32+
*/
33+
public function __construct(
34+
Collection $users,
35+
CleanupStrategy $strategy
36+
) {
37+
$this->users = $users;
38+
$this->strategy = $strategy;
39+
}
40+
41+
/**
42+
* Execute the job.
43+
*
44+
* @return void
45+
*/
46+
public function handle()
47+
{
48+
$this->strategy->execute($this->users);
49+
}
50+
}

src/Jobs/Cleanup/CleanupStrategy.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Soved\Laravel\Gdpr\Jobs\Cleanup;
4+
5+
use Illuminate\Contracts\Config\Repository;
6+
use Illuminate\Database\Eloquent\Collection;
7+
8+
abstract class CleanupStrategy
9+
{
10+
/**
11+
* @var \Illuminate\Contracts\Config\Repository
12+
*/
13+
protected $config;
14+
15+
/**
16+
* Create a new cleanup strategy instance.
17+
*
18+
* @param \Illuminate\Contracts\Config\Repository $config
19+
* @return void
20+
*/
21+
public function __construct(Repository $config)
22+
{
23+
$this->config = $config;
24+
}
25+
26+
/**
27+
* Execute cleanup strategy.
28+
*
29+
* @param \Illuminate\Database\Eloquent\Collection $users
30+
* @return void
31+
*/
32+
abstract public function execute(Collection $users);
33+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace Soved\Laravel\Gdpr\Jobs\Cleanup\Strategies;
4+
5+
use App\User;
6+
use Carbon\Carbon;
7+
use Illuminate\Database\Eloquent\Collection;
8+
use Soved\Laravel\Gdpr\Events\GdprInactiveUser;
9+
use Soved\Laravel\Gdpr\Jobs\Cleanup\CleanupStrategy;
10+
use Soved\Laravel\Gdpr\Events\GdprInactiveUserDeleted;
11+
12+
class DefaultStrategy extends CleanupStrategy
13+
{
14+
/**
15+
* Execute cleanup strategy.
16+
*
17+
* @param \Illuminate\Database\Eloquent\Collection $users
18+
* @return void
19+
*/
20+
public function execute(Collection $users)
21+
{
22+
$config = $this->config->get('gdpr.cleanup.defaultStrategy');
23+
24+
// Users are considered inactive if their last activity is older than this timestamp
25+
$inactivity = Carbon::now()
26+
->subMonths($config['keepInactiveUsersForMonths']);
27+
28+
$this->notifyInactiveUsers(
29+
$inactivity,
30+
$config['notifyUsersDaysBeforeDeletion'],
31+
$users
32+
);
33+
34+
$this->deleteInactiveUsers($inactivity, $users);
35+
}
36+
37+
/**
38+
* Notify inactive users about their deletion.
39+
*
40+
* @param \Carbon\Carbon $inactivity
41+
* @param int $notificationThreshold
42+
* @param \Illuminate\Database\Eloquent\Collection $users
43+
* @return void
44+
*/
45+
private function notifyInactiveUsers(
46+
Carbon $inactivity,
47+
int $notificationThreshold,
48+
Collection $users
49+
) {
50+
$users->filter(
51+
function (User $user) use ($inactivity, $notificationThreshold) {
52+
return $user->last_activity->diffInDays($inactivity)
53+
=== $notificationThreshold;
54+
}
55+
)->each(function (User $user) {
56+
event(new GdprInactiveUser($user));
57+
});
58+
}
59+
60+
/**
61+
* Delete inactive users.
62+
*
63+
* @param \Carbon\Carbon $inactivity
64+
* @param \Illuminate\Database\Eloquent\Collection $users
65+
* @return void
66+
*/
67+
private function deleteInactiveUsers(
68+
Carbon $inactivity,
69+
Collection $users
70+
) {
71+
$users->filter(function (User $user) use ($inactivity) {
72+
return $user->last_activity < $inactivity;
73+
})->each(function (User $user) {
74+
$user->delete();
75+
76+
event(new GdprInactiveUserDeleted($user));
77+
});
78+
}
79+
}

0 commit comments

Comments
 (0)