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

[WIP] Account Removal #578

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
19 changes: 18 additions & 1 deletion app/HMS/Entities/Banking/BankTransaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

use Carbon\Carbon;
use HMS\Entities\Snackspace\Transaction;
use HMS\Entities\EntityObfuscatableInterface;

class BankTransaction
class BankTransaction implements EntityObfuscatableInterface
{
/**
* @var int
Expand Down Expand Up @@ -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;
}
}
13 changes: 13 additions & 0 deletions app/HMS/Entities/EntityObfuscatableInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace HMS\Entities;

interface EntityObfuscatableInterface
{

/**
* Obfuscate an entity upon account deletion
* or scheduled governance cleanup.
*/
public function obfuscate();
}
14 changes: 13 additions & 1 deletion app/HMS/Entities/Profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use HMS\Governance\VotingPreference;
use HMS\Traits\Entities\Timestampable;

class Profile
class Profile implements EntityObfuscatableInterface
{
use Timestampable;

Expand Down Expand Up @@ -451,4 +451,16 @@ public function setVotingPreferenceStatedAt(?Carbon $votingPreferenceStatedAt):

return $this;
}

/**
* Remove any personal information from the profile
*/
public function obfuscate() {
$this->unlockText = null;
$this->contactNumber = null;
$this->dateOfBirth = Carbon::create(1900, 1, 1, 0, 0, 0);
$this->discordUsername = null;

return $this;
}
}
20 changes: 19 additions & 1 deletion app/HMS/Entities/Snackspace/VendLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

use Carbon\Carbon;
use HMS\Entities\User;
use HMS\Entities\EntityObfuscatableInterface;

class VendLog
class VendLog implements EntityObfuscatableInterface
{
/**
* @var int
Expand Down Expand Up @@ -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;
}
}
12 changes: 11 additions & 1 deletion app/HMS/Entities/Tools/Usage.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

use Carbon\Carbon;
use HMS\Entities\User;
use HMS\Entities\EntityObfuscatableInterface;

class Usage
class Usage implements EntityObfuscatableInterface
{
/**
* @var int
Expand Down Expand Up @@ -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;
}
}
15 changes: 14 additions & 1 deletion app/HMS/Entities/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,14 +24,16 @@
use LaravelDoctrine\ACL\Permissions\HasPermissions;
use LaravelDoctrine\ACL\Roles\HasRoles;
use LaravelDoctrine\ORM\Notifications\Notifiable;
use Carbon\Carbon;

class User implements
AuthenticatableContract,
CanResetPasswordContract,
HasRoleContract,
HasPermissionsContract,
AuthorizableContract,
MustVerifyEmailContract
MustVerifyEmailContract,
EntityObfuscatableInterface
{
use CanResetPassword,
Notifiable,
Expand Down Expand Up @@ -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;
}
}
117 changes: 117 additions & 0 deletions app/Http/Controllers/Auth/DeleteAccountController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

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 HMS\User\Permissions\RoleManager;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;

class DeleteAccountController extends Controller
{
/**
* @var PasswordStore
*/
protected $passwordStore;

/**
* @var UserRepository
*/
protected $userRepository;

/**
* @var ProfileRepository
*/
protected $profileRepository;

/**
* @var RoleManager
*/
private $roleManager;

/**
* Create a new controller instance.
*
* @return void
*/
public function __construct(
PasswordStore $passwordStore,
UserRepository $userRepository,
ProfileRepository $profileRepository,
RoleManager $roleManager
) {
$this->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');
}

}
77 changes: 77 additions & 0 deletions resources/views/user/deleteAccount.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
@extends('layouts.app')

@section('pageTitle', 'Account Removal '.$user->getFirstname())

@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5>Account Removal</h5>
</div>

<form id="delete-account-form" role="form" method="POST" action="{{ route('users.deleteAccount.deleted') }}">
@csrf
@method('PUT')
<div class="card-body">

<p class="card-text">
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.
</p>

<ul class="card-text">

<li>
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.
</li>
<li>
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.
</li>
<li>
Information relating to your use of tools and
access to the building will be annonymised
immediately, but retained indefinitely.
</li>
<li>
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.
</li>
</ul>

<p class="card-text">
If you wish to remove your account, please confirm your password
below.
</p>

<input placeholder="Current Password" class="form-control" id="currentPassword" type="password" name="currentPassword" required autofocus autocomplete="current-password">
@if ($errors->has('currentPassword'))
<span class="help-block">
<strong>{{ $errors->first('currentPassword') }}</strong>
</span><br>
@endif

</div>
<div class="card-footer">
<button type="submit" class="btn btn-danger btn-block">Remove Account</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
2 changes: 2 additions & 0 deletions resources/views/user/show.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
<a href="{{ route('membership.edit', Auth::user()->getId()) }}" class="btn btn-primary btn-block" >Update Details</a>
@elseif (($user == Auth::user() && Auth::user()->can('profile.edit.self')) || ($user->getId() != Auth::user()->getId() && Auth::user()->can('profile.edit.all')))
<a href="{{ route('users.edit', $user->getID()) }}" class="btn btn-info btn-block"><i class="fas fa-pencil" aria-hidden="true"></i> Edit</a>
<br />
<a href="{{ route('users.deleteAccount') }}" class="btn btn-danger btn-block"><i class="fas fa-trash" aria-hidden="true"></i> Remove Account</a>
@endif
</div>
@endsection
4 changes: 4 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
Expand Down