Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add GDPR-Export for user #2444

Open
wants to merge 22 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .idea/php.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions app/Enum/ExportableColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ public function title(): string {
}
return $title;
}

// public
HerrLevin marked this conversation as resolved.
Show resolved Hide resolved
}
21 changes: 21 additions & 0 deletions app/Http/Controllers/GdprExportController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Backend\PrivacyPolicyController;
use App\Jobs\MonitoredPersonalDataExportJob;
use App\Models\User;
use Illuminate\Support\Facades\Auth;

class GdprExportController extends Controller
{

public function export($userId=null) {
HerrLevin marked this conversation as resolved.
Show resolved Hide resolved
$user = Auth::user();
if ($userId !== null) {
$user = User::findOrFail($userId);
}
dispatch(new MonitoredPersonalDataExportJob($user));
}

}
15 changes: 15 additions & 0 deletions app/Jobs/MonitoredPersonalDataExportJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace App\Jobs;

use romanzipp\QueueMonitor\Traits\IsMonitored;
use Spatie\PersonalDataExport\Jobs\CreatePersonalDataExportJob;

class MonitoredPersonalDataExportJob extends CreatePersonalDataExportJob
{

use IsMonitored;

public $timeout = 30 * 60;

}
1 change: 1 addition & 0 deletions app/Models/MastodonServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class MastodonServer extends Model
protected $casts = [
'id' => 'integer',
];
protected $hidden = ['client_id', 'client_secret'];

public function socialProfiles(): HasMany {
return $this->hasMany(SocialLoginProfile::class, 'mastodon_server', 'id');
Expand Down
4 changes: 4 additions & 0 deletions app/Models/OAuthClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ class OAuthClient extends PassportClient {
'revoked' => 'bool',
];

protected $hidden = [
'secret',
];
MrKrisKrisu marked this conversation as resolved.
Show resolved Hide resolved

public static function newFactory() {
return parent::newFactory();
}
Expand Down
3 changes: 3 additions & 0 deletions app/Models/Status.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ public function getFavoritedAttribute(): ?bool {
}

public function getDescriptionAttribute(): string {
if ($this->checkin === null) {
MrKrisKrisu marked this conversation as resolved.
Show resolved Hide resolved
return $this->body ?? '';
}
return __('description.status', [
'username' => $this->user->name,
'origin' => $this->checkin->originStation->name .
Expand Down
78 changes: 77 additions & 1 deletion app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\Enum\StatusVisibility;
use App\Exceptions\RateLimitExceededException;
use App\Http\Controllers\Backend\Social\MastodonProfileDetails;
use App\Http\Controllers\Backend\User\TokenController;
use App\Jobs\SendVerificationEmail;
use Carbon\Carbon;
use Illuminate\Contracts\Auth\MustVerifyEmail;
Expand All @@ -18,11 +19,14 @@
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\RateLimiter;
use Laravel\Passport\HasApiTokens;
use Mastodon;
use Spatie\Permission\Traits\HasRoles;
use Spatie\PersonalDataExport\ExportsPersonalData;
use Spatie\PersonalDataExport\PersonalDataSelection;

/**
* @property int id
Expand Down Expand Up @@ -52,7 +56,7 @@
* @todo remove "twitterUrl" (Twitter isn't used by traewelling anymore)
* @mixin Builder
*/
class User extends Authenticatable implements MustVerifyEmail
class User extends Authenticatable implements MustVerifyEmail, ExportsPersonalData
{

use Notifiable, HasApiTokens, HasFactory, HasRoles;
Expand Down Expand Up @@ -284,4 +288,76 @@ public function preferredLocale(): string {
protected function getDefaultGuardName(): string {
return 'web';
}

protected function oAuthClients(): HasMany {
return $this->hasMany(OAuthClient::class, 'user_id', 'id');
}

public function selectPersonalData(PersonalDataSelection $personalDataSelection): void {
MrKrisKrisu marked this conversation as resolved.
Show resolved Hide resolved
$user = $this->toArray();
$user['email'] = $this->email;
$user['email_verified_at'] = $this->email_verified_at;
$user['privacy_ack_at'] = $this->privacy_ack_at;
$user['last_login'] = $this->last_login;
$user['created_at'] = $this->created_at;
$user['updated_at'] = $this->updated_at;

$webhooks = $this->webhooks()->with('events')->get();
$webhooks = $webhooks->map(function($webhook) {
$webhook['created_at'] = $webhook->created_at;
$webhook['updated_at'] = $webhook->updated_at;
$webhook['client_id'] = (int) $webhook->oauth_client_id ?? null;
unset($webhook['url']);
return $webhook;
});


if ($this->avatar && file_exists(public_path('/uploads/avatars/' . $this->avatar))) {
$personalDataSelection
->addFile(public_path('/uploads/avatars/' . $this->avatar));
}

$personalDataSelection
->add('user.json', $user)
->add('statuses.json', $this->statuses()->with('tags')->get())
->add('notifications.json', $this->notifications()->get()->toJson())
->add('likes.json', $this->likes()->get()->toJson())
->add('social_profile.json', $this->socialProfile()->with('mastodonserver')->get())
->add('event_suggestions.json', EventSuggestion::where('user_id', $this->id)->get()->toJson())
->add('events.json', Event::where('approved_by', $this->id)->get()->toJson())
->add('webhooks.json', $webhooks)
->add(
'webhook_creation_requests.json',
WebhookCreationRequest::where('user_id', $this->id)->get()->toJson()
)
->add('tokens.json', TokenController::index($this)->toJson())
->add('ics_tokens.json', $this->icsTokens()->get()->toJson())
->add(
'password_resets.json',
DB::table('password_resets')->select(['email','created_at'])->where('email', $this->email)->get()
)
->add('apps.json', $this->oAuthClients()->get()->toJson())
->add('follows.json', DB::table('follows')->where('user_id', $this->id)->get())
->add('followings.json', DB::table('follows')->where('follow_id', $this->id)->get())
->add('blocks.json', DB::table('user_blocks')->where('user_id', $this->id)->get())
->add('blocked_by.json', DB::table('user_blocks')->where('blocked_id', $this->id)->get())
HerrLevin marked this conversation as resolved.
Show resolved Hide resolved
->add('mutes.json', DB::table('user_mutes')->where('user_id', $this->id)->get())
->add('muted_by.json', DB::table('user_mutes')->where('muted_id', $this->id)->get())
->add('follow_requests.json', DB::table('follow_requests')->where('user_id', $this->id)->get())
->add('follows_requests.json', DB::table('follow_requests')->where('follow_id', $this->id)->get())
->add('sessions.json', $this->sessions()->get()->toJson())
->add('home.json', $this->home()->get()->toJson())
->add('hafas_trips.json', DB::table('hafas_trips')->where('user_id', $this->id)->get())
->add('mentions.json', Mention::where('user_id', $this->id)->get()->toJson())
->add('roles.json', $this->roles()->get()->toJson())
->add(
'activity_log.json',
DB::table('activity_log')->where('causer_type', get_class($this))->where('causer_id', $this->id)->get()
)
->add('permissions.json', $this->permissions()->get()->toJson());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ich finde es langfristig gesehen etwas kritisch, wenn wir einfach DB Queries raw ausspucken. Später hinzugefügte Columns, die nicht exposed werden sollten (z.B. irgendwelche Keys o.ä.) werden hier sicher vergessen. Wäre es nicht sinnvoller das noch einmal durch die Resourcen zu jagen?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternativ einfach ein ->select([...]) mit den gewünschten Columns, damit nichts falsches exposed wird.

}

public function personalDataExportName(): string {
return $this->username;
}
}
1 change: 1 addition & 0 deletions app/Models/WebhookCreationRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
class WebhookCreationRequest extends Model {
public $timestamps = false;
protected $fillable = ['id', 'user_id', 'oauth_client_id', 'revoked', 'expires_at', 'events', 'url'];
protected $hidden = ['url'];
protected $casts = [
'id' => 'string',
'user_id' => 'integer',
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"spatie/icalendar-generator": "^2.0",
"spatie/laravel-activitylog": "^4.7",
"spatie/laravel-permission": "^6.1",
"spatie/laravel-personal-data-export": "^4.2",
"spatie/laravel-prometheus": "^1.0",
"spatie/laravel-sitemap": "^7.0",
"spatie/laravel-validation-rules": "^3.2",
Expand Down
77 changes: 77 additions & 0 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions config/filesystems.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@

'disks' => [

'personal-data-exports' => [
'driver' => 'local',
'root' => storage_path('app/personal-data-exports'),
],

'local' => [
'driver' => 'local',
'root' => storage_path('app'),
Expand Down
39 changes: 39 additions & 0 deletions config/personal-data-export.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

return [
HerrLevin marked this conversation as resolved.
Show resolved Hide resolved
/*
* The disk where the exports will be stored by default.
*/
'disk' => 'personal-data-exports',

/*
* If you want to keep the original directory structure for added files,
*/
'keep_directory_structure' => true,

/*
* The amount of days the exports will be available.
*/
'delete_after_days' => 5,

/*
* Determines whether the user should be logged in to be able
* to access the export.
*/
'authentication_required' => true,

/*
* The notification which will be sent to the user when the export
* has been created.
*/
'notification' => \Spatie\PersonalDataExport\Notifications\PersonalDataExportedNotification::class,

/*
* Configure the queue and connection used by `CreatePersonalDataExportJob`
* which will create the export.
*/
'job' => [
'queue' => 'export',
'connection' => null,
],
];
Loading