From bfee06d39b004cbaa563a4e2bdd5c3632665bac0 Mon Sep 17 00:00:00 2001 From: Aaron Jackson Date: Thu, 17 Aug 2023 22:14:54 +0100 Subject: [PATCH 1/7] Account deletion controller, view and routes --- .../Auth/DeleteAccountController.php | 76 ++++++++++++++++++ resources/views/user/deleteAccount.blade.php | 77 +++++++++++++++++++ resources/views/user/show.blade.php | 2 + routes/web.php | 4 + 4 files changed, 159 insertions(+) create mode 100644 app/Http/Controllers/Auth/DeleteAccountController.php create mode 100644 resources/views/user/deleteAccount.blade.php diff --git a/app/Http/Controllers/Auth/DeleteAccountController.php b/app/Http/Controllers/Auth/DeleteAccountController.php new file mode 100644 index 000000000..d3d5dd32f --- /dev/null +++ b/app/Http/Controllers/Auth/DeleteAccountController.php @@ -0,0 +1,76 @@ +passwordStore = $passwordStore; + } + + /** + * Show the form for editing the specified resource. + * + * @return \Illuminate\Http\Response + */ + public function info() + { + return view('user.deleteAccount')->with('user', Auth::user()); + } + + /** + * Initiate the account removal process + * + * @param \Illuminate\Http\Request $request + * + * @return \Illuminate\Http\Response + */ + public function delete(Request $request) + { + $user = Auth::user(); + + $valideCurrentPassword = Auth::guard()->validate([ + 'username' => $user->getUsername(), + 'password' => $request->currentPassword, + ]); + + if (! $valideCurrentPassword) { + flash('Your current password does not matches with the password you provided. Please try again.')->error(); + return redirect()->back(); + } + + //event(new AccountDeletion($user)); + + return redirect()->route('home'); + } + + /** + * Set the user's password. + * + * @param \Illuminate\Contracts\Auth\CanResetPassword $user + * @param string $password + * + * @return void + */ + protected function setUserPassword($user, $password) + { + $this->passwordStore->setPassword($user->getUsername(), $password); + } +} diff --git a/resources/views/user/deleteAccount.blade.php b/resources/views/user/deleteAccount.blade.php new file mode 100644 index 000000000..552017a04 --- /dev/null +++ b/resources/views/user/deleteAccount.blade.php @@ -0,0 +1,77 @@ +@extends('layouts.app') + +@section('pageTitle', 'Account Removal '.$user->getFirstname()) + + @section('content') +
+
+
+
+
+
Account Removal
+
+ +
+ @csrf + @method('PUT') +
+ +

+ As described in our privacy policy, it is possible + to remove your account from the hackspace. This + happens automatically after being an ex-member for + six years. If you decide you want perform the + account removal immediately, you need to understand + the following. +

+ +
    + +
  • + If you wish to resume your membership to the + hackspace, you must reattend a tour regardless of + how long you were a member prior to making this + account removal request. +
  • +
  • + The hackspace will retainq your full name and + address will be retained for the duration of ten + years from the date of this request. This is a + legal obligation of being a member of a Limited by + Guarentee company. +
  • +
  • + Information relating to your use of tools and + access to the building will be annonymised + immediately, but retained indefinitely. +
  • +
  • + Payment transactions are retained for the duration + of seven years, as required by HMRC for tax + purposes. Transactions older than this will + automatically be anonymised. +
  • +
+ +

+ If you wish to remove your account, please confirm your password + below. +

+ + + @if ($errors->has('currentPassword')) + + {{ $errors->first('currentPassword') }} +
+ @endif + +
+ +
+
+
+
+
+ @endsection diff --git a/resources/views/user/show.blade.php b/resources/views/user/show.blade.php index 0255d635f..787bf7481 100644 --- a/resources/views/user/show.blade.php +++ b/resources/views/user/show.blade.php @@ -77,6 +77,8 @@ Update Details @elseif (($user == Auth::user() && Auth::user()->can('profile.edit.self')) || ($user->getId() != Auth::user()->getId() && Auth::user()->can('profile.edit.all'))) Edit +
+ Remove Account @endif @endsection diff --git a/routes/web.php b/routes/web.php index a5209d8ae..6828431f2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -107,6 +107,10 @@ Route::get('change-password', 'Auth\ChangePasswordController@edit')->name('users.changePassword'); Route::put('change-password', 'Auth\ChangePasswordController@update')->name('users.changePassword.update'); + // User account deletion + Route::get('delete-account', 'Auth\DeleteAccountController@info')->name('users.deleteAccount'); + Route::put('delete-account', 'Auth\DeleteAccountController@delete')->name('users.deleteAccount.deleted'); + // Meta area covers various setting for HMS Route::resource('metas', 'MetaController') ->except(['show', 'store', 'create', 'destroy']); From 5019073b48acb09d850ad1e8c389c8f08f3a2b09 Mon Sep 17 00:00:00 2001 From: Aaron Jackson Date: Thu, 17 Aug 2023 23:27:38 +0100 Subject: [PATCH 2/7] Interface for obfuscatable entities --- app/HMS/Entities/Banking/BankTransaction.php | 19 +++++++++++++++++- .../Entities/EntityObfuscatableInterface.php | 13 ++++++++++++ app/HMS/Entities/Profile.php | 16 ++++++++++++++- app/HMS/Entities/Snackspace/VendLog.php | 20 ++++++++++++++++++- app/HMS/Entities/Tools/Usage.php | 12 ++++++++++- app/HMS/Entities/User.php | 13 +++++++++++- .../Auth/DeleteAccountController.php | 9 ++++++++- 7 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 app/HMS/Entities/EntityObfuscatableInterface.php diff --git a/app/HMS/Entities/Banking/BankTransaction.php b/app/HMS/Entities/Banking/BankTransaction.php index a5e000db4..314a5d36e 100644 --- a/app/HMS/Entities/Banking/BankTransaction.php +++ b/app/HMS/Entities/Banking/BankTransaction.php @@ -4,8 +4,9 @@ use Carbon\Carbon; use HMS\Entities\Snackspace\Transaction; +use HMS\Entities\EntityObfuscatableInterface; -class BankTransaction +class BankTransaction implements EntityObfuscatableInterface { /** * @var int @@ -171,4 +172,20 @@ public function setTransaction($transaction) return $this; } + + /** + * Remove any personal information from the transaction. + * This should only happen after 7 years if the member has deleted their account. + */ + public function obfuscate() { + $historic = Carbon::now(); + $historic->subYears(7); + + if ($this->transactionDate() < $historic) { + // The description may contain names etc. + $this->description = "obfuscated"; + } + + return $this; + } } diff --git a/app/HMS/Entities/EntityObfuscatableInterface.php b/app/HMS/Entities/EntityObfuscatableInterface.php new file mode 100644 index 000000000..4ef98f9d5 --- /dev/null +++ b/app/HMS/Entities/EntityObfuscatableInterface.php @@ -0,0 +1,13 @@ +unlockText = "deleted"; + $this->contactNumber = "deleted"; + $this->dateOfBirth = Carbon::create(1900, 1, 1, 0, 0, 0); + $this->discordUsername = null; + $this->creditLimit = 0; + $this->balance = 0; + + return $this; + } } diff --git a/app/HMS/Entities/Snackspace/VendLog.php b/app/HMS/Entities/Snackspace/VendLog.php index 1add77180..b7b202254 100644 --- a/app/HMS/Entities/Snackspace/VendLog.php +++ b/app/HMS/Entities/Snackspace/VendLog.php @@ -4,8 +4,9 @@ use Carbon\Carbon; use HMS\Entities\User; +use HMS\Entities\EntityObfuscatableInterface; -class VendLog +class VendLog implements EntityObfuscatableInterface { /** * @var int @@ -213,4 +214,21 @@ public function setPosition($position) return $this; } + + + /** + * Remove any personal information from the transaction. + * This should only happen after 7 years if the member has deleted their account. + */ + public function obfuscate() { + $historic = Carbon::now(); + $historic->subYears(7); + + if ($this->transactionDate() < $historic) { + $this->rfidSerial = null; + $this->user = null; + } + + return $this; + } } diff --git a/app/HMS/Entities/Tools/Usage.php b/app/HMS/Entities/Tools/Usage.php index 1c20ec168..0bf86ba84 100644 --- a/app/HMS/Entities/Tools/Usage.php +++ b/app/HMS/Entities/Tools/Usage.php @@ -4,8 +4,9 @@ use Carbon\Carbon; use HMS\Entities\User; +use HMS\Entities\EntityObfuscatableInterface; -class Usage +class Usage implements EntityObfuscatableInterface { /** * @var int @@ -179,4 +180,13 @@ public function setStatus($status) return $this; } + + /** + * Disassociate the usage from a specific user + */ + public function obfuscate() { + $this->user = null; + + return $this; + } } diff --git a/app/HMS/Entities/User.php b/app/HMS/Entities/User.php index 5e7f58ec0..d788293c1 100644 --- a/app/HMS/Entities/User.php +++ b/app/HMS/Entities/User.php @@ -11,6 +11,7 @@ use HMS\Traits\Entities\SoftDeletable; use HMS\Traits\Entities\Timestampable; use HMS\Traits\HasApiTokens; +use HMS\Entities\EntityObfuscatableInterface; use Illuminate\Auth\Passwords\CanResetPassword; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; @@ -30,7 +31,8 @@ class User implements HasRoleContract, HasPermissionsContract, AuthorizableContract, - MustVerifyEmailContract + MustVerifyEmailContract, + EntityObfuscatableInterface { use CanResetPassword, Notifiable, @@ -487,4 +489,13 @@ public function routeNotificationForDiscord() ]) ->id; } + + /** + * Obfuscate personal information + */ + public function obfuscate() + { + $this->email = null; + return $this; + } } diff --git a/app/Http/Controllers/Auth/DeleteAccountController.php b/app/Http/Controllers/Auth/DeleteAccountController.php index d3d5dd32f..ba4b9cef4 100644 --- a/app/Http/Controllers/Auth/DeleteAccountController.php +++ b/app/Http/Controllers/Auth/DeleteAccountController.php @@ -56,7 +56,14 @@ public function delete(Request $request) return redirect()->back(); } - //event(new AccountDeletion($user)); + // BankTransactions will be handled by audit job. These will + // be obfuscated if they are older than seven years and the + // account has been removed. Same for VendLog. + // Unimportant information from Profile will be removed. + // Email address is removed from User. + + + return redirect()->route('home'); } From c4e351b0d7547a79b9d94bc68d17b3ce81743748 Mon Sep 17 00:00:00 2001 From: Aaron Jackson Date: Sun, 20 Aug 2023 00:42:48 +0100 Subject: [PATCH 3/7] Change obfuscated values --- app/HMS/Entities/Profile.php | 6 ++---- app/HMS/Entities/User.php | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/HMS/Entities/Profile.php b/app/HMS/Entities/Profile.php index f44bca865..42437d876 100644 --- a/app/HMS/Entities/Profile.php +++ b/app/HMS/Entities/Profile.php @@ -456,12 +456,10 @@ public function setVotingPreferenceStatedAt(?Carbon $votingPreferenceStatedAt): * Remove any personal information from the profile */ public function obfuscate() { - $this->unlockText = "deleted"; - $this->contactNumber = "deleted"; + $this->unlockText = null; + $this->contactNumber = null; $this->dateOfBirth = Carbon::create(1900, 1, 1, 0, 0, 0); $this->discordUsername = null; - $this->creditLimit = 0; - $this->balance = 0; return $this; } diff --git a/app/HMS/Entities/User.php b/app/HMS/Entities/User.php index d788293c1..0a2c600d1 100644 --- a/app/HMS/Entities/User.php +++ b/app/HMS/Entities/User.php @@ -24,6 +24,7 @@ use LaravelDoctrine\ACL\Permissions\HasPermissions; use LaravelDoctrine\ACL\Roles\HasRoles; use LaravelDoctrine\ORM\Notifications\Notifiable; +use Carbon\Carbon; class User implements AuthenticatableContract, @@ -495,7 +496,7 @@ public function routeNotificationForDiscord() */ public function obfuscate() { - $this->email = null; + $this->email = 'deleted-account+' . $this->username . '@deleted-accounts.local'; return $this; } } From d020d8f2b8513e4175f14ebb8fda4277dbe5926e Mon Sep 17 00:00:00 2001 From: Aaron Jackson Date: Sun, 20 Aug 2023 00:43:23 +0100 Subject: [PATCH 4/7] Perform obfuscation and soft deletion --- .../Auth/DeleteAccountController.php | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/app/Http/Controllers/Auth/DeleteAccountController.php b/app/Http/Controllers/Auth/DeleteAccountController.php index ba4b9cef4..48b08317a 100644 --- a/app/Http/Controllers/Auth/DeleteAccountController.php +++ b/app/Http/Controllers/Auth/DeleteAccountController.php @@ -2,11 +2,15 @@ namespace App\Http\Controllers\Auth; +use App\Events\User\AccountDeletion; use App\Http\Controllers\Controller; use HMS\Auth\PasswordStore; use HMS\Entities\User; +use HMS\Repositories\UserRepository; +use HMS\Repositories\ProfileRepository; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Carbon\Carbon; class DeleteAccountController extends Controller { @@ -15,14 +19,29 @@ class DeleteAccountController extends Controller */ protected $passwordStore; + /** + * @var UserRepository + */ + protected $userRepository; + + /** + * @var ProfileRepository + */ + protected $profileRepository; + /** * Create a new controller instance. * * @return void */ - public function __construct(PasswordStore $passwordStore) - { + public function __construct( + PasswordStore $passwordStore, + UserRepository $userRepository, + ProfileRepository $profileRepository + ) { $this->passwordStore = $passwordStore; + $this->userRepository = $userRepository; + $this->profileRepository = $profileRepository; } /** @@ -62,22 +81,20 @@ public function delete(Request $request) // Unimportant information from Profile will be removed. // Email address is removed from User. + $profile = $user->getProfile(); + + $user->setDeletedAt(Carbon::now()) + ->obfuscate(); + + $profile->obfuscate(); + $this->userRepository->save($user); + $this->profileRepository->save($profile); +// event(new AccountDeletion($user)); return redirect()->route('home'); } - /** - * Set the user's password. - * - * @param \Illuminate\Contracts\Auth\CanResetPassword $user - * @param string $password - * - * @return void - */ - protected function setUserPassword($user, $password) - { - $this->passwordStore->setPassword($user->getUsername(), $password); - } + } From af564c6fee9adc1af523a17dd9da16b9448ffb6a Mon Sep 17 00:00:00 2001 From: Aaron Jackson Date: Sun, 20 Aug 2023 00:47:34 +0100 Subject: [PATCH 5/7] Also nullify user's account id --- app/HMS/Entities/User.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/HMS/Entities/User.php b/app/HMS/Entities/User.php index 0a2c600d1..34d8b300b 100644 --- a/app/HMS/Entities/User.php +++ b/app/HMS/Entities/User.php @@ -497,6 +497,7 @@ public function routeNotificationForDiscord() public function obfuscate() { $this->email = 'deleted-account+' . $this->username . '@deleted-accounts.local'; + $this->account = null; return $this; } } From f37d54c30ef4a7036504140e8ec723cc166c8fd5 Mon Sep 17 00:00:00 2001 From: Aaron Jackson Date: Sun, 20 Aug 2023 00:53:46 +0100 Subject: [PATCH 6/7] Remove deleted user from all roles --- .../Auth/DeleteAccountController.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Auth/DeleteAccountController.php b/app/Http/Controllers/Auth/DeleteAccountController.php index 48b08317a..f781f679f 100644 --- a/app/Http/Controllers/Auth/DeleteAccountController.php +++ b/app/Http/Controllers/Auth/DeleteAccountController.php @@ -8,6 +8,7 @@ use HMS\Entities\User; use HMS\Repositories\UserRepository; use HMS\Repositories\ProfileRepository; +use HMS\User\Permissions\RoleManager; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Carbon\Carbon; @@ -29,6 +30,11 @@ class DeleteAccountController extends Controller */ protected $profileRepository; + /** + * @var RoleManager + */ + private $roleManager; + /** * Create a new controller instance. * @@ -37,11 +43,13 @@ class DeleteAccountController extends Controller public function __construct( PasswordStore $passwordStore, UserRepository $userRepository, - ProfileRepository $profileRepository + ProfileRepository $profileRepository, + RoleManager $roleManager ) { $this->passwordStore = $passwordStore; $this->userRepository = $userRepository; $this->profileRepository = $profileRepository; + $this->roleManager = $roleManager; } /** @@ -83,11 +91,18 @@ public function delete(Request $request) $profile = $user->getProfile(); + // Remove all roles regardless of whether it's retained. + foreach ($user->getRoles() as $role) { + $this->roleManager->removeUserFromRole($user, $role); + } + + // Obfuscate user and profile and flag user as soft deleted. + // Soft delete will result in logout on next page load. $user->setDeletedAt(Carbon::now()) ->obfuscate(); - $profile->obfuscate(); + // Commit all changes. $this->userRepository->save($user); $this->profileRepository->save($profile); From b36901f8f14ee94c514dc489f8a517bbdf54d901 Mon Sep 17 00:00:00 2001 From: Aaron Jackson Date: Sun, 20 Aug 2023 11:02:13 +0100 Subject: [PATCH 7/7] comment regarding unique usernames --- app/Http/Controllers/Auth/DeleteAccountController.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Auth/DeleteAccountController.php b/app/Http/Controllers/Auth/DeleteAccountController.php index f781f679f..5e5aa33f4 100644 --- a/app/Http/Controllers/Auth/DeleteAccountController.php +++ b/app/Http/Controllers/Auth/DeleteAccountController.php @@ -106,10 +106,12 @@ public function delete(Request $request) $this->userRepository->save($user); $this->profileRepository->save($profile); -// event(new AccountDeletion($user)); + // event(new AccountDeletion($user)); + + // TODO: illuminate unique is excluding deleted items during validation + // but database insertion fails. i dont want usernames to be recycled anyway. return redirect()->route('home'); } - }