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 = null; + $this->contactNumber = null; + $this->dateOfBirth = Carbon::create(1900, 1, 1, 0, 0, 0); + $this->discordUsername = null; + + 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..34d8b300b 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; @@ -23,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, @@ -30,7 +32,8 @@ class User implements HasRoleContract, HasPermissionsContract, AuthorizableContract, - MustVerifyEmailContract + MustVerifyEmailContract, + EntityObfuscatableInterface { use CanResetPassword, Notifiable, @@ -487,4 +490,14 @@ public function routeNotificationForDiscord() ]) ->id; } + + /** + * Obfuscate personal information + */ + public function obfuscate() + { + $this->email = 'deleted-account+' . $this->username . '@deleted-accounts.local'; + $this->account = null; + return $this; + } } diff --git a/app/Http/Controllers/Auth/DeleteAccountController.php b/app/Http/Controllers/Auth/DeleteAccountController.php new file mode 100644 index 000000000..5e5aa33f4 --- /dev/null +++ b/app/Http/Controllers/Auth/DeleteAccountController.php @@ -0,0 +1,117 @@ +passwordStore = $passwordStore; + $this->userRepository = $userRepository; + $this->profileRepository = $profileRepository; + $this->roleManager = $roleManager; + } + + /** + * 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(); + } + + // 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. + + $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); + + // 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'); + } + +} 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']);