2FAuth/app/Api/v1/Controllers/UserManagerController.php

247 lines
7.9 KiB
PHP

<?php
namespace App\Api\v1\Controllers;
use App\Api\v1\Requests\UserManagerPromoteRequest;
use App\Api\v1\Requests\UserManagerStoreRequest;
use App\Api\v1\Resources\UserAuthentication;
use App\Api\v1\Resources\UserManagerResource;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Password;
use Laravel\Passport\TokenRepository;
class UserManagerController extends Controller
{
/**
* Display all users.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function index(Request $request)
{
return UserManagerResource::collection(User::withCount('twofaccounts')->get());
}
/**
* Get a user
*
* @return \App\Api\v1\Resources\UserManagerResource
*/
public function show(User $user)
{
$this->authorize('view', $user);
return new UserManagerResource($user);
}
/**
* Reset user's password
*
* @return \Illuminate\Http\JsonResponse
*/
public function resetPassword(Request $request, User $user)
{
Log::info(sprintf('Password reset for User ID #%s requested by User ID #%s', $user->id, $request->user()->id));
$this->authorize('update', $user);
$credentials = [
'token' => $this->broker()->createToken($user),
'email' => $user->email,
'password' => $user->password,
];
$response = $this->broker()->reset(
$credentials, function ($user) {
$user->resetPassword();
$user->save();
}
);
if ($response == Password::PASSWORD_RESET) {
Log::info(sprintf('Temporary password set for User ID #%s', $user->id));
$response = $this->broker()->sendResetLink(
['email' => $credentials['email']]
);
} else {
return response()->json([
'message' => 'bad request',
'reason' => is_string($response) ? __($response) : __('errors.no_pwd_reset_for_this_user_type'),
], 400);
}
return $response == Password::RESET_LINK_SENT
? new UserManagerResource($user)
: response()->json([
'message' => 'bad request',
'reason' => __($response),
], 400);
}
/**
* Store a newly created user in storage.
*
* @return \Illuminate\Http\JsonResponse
*/
public function store(UserManagerStoreRequest $request)
{
$this->authorize('create', User::class);
$validated = $request->validated();
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
Log::info(sprintf('User ID #%s created by user ID #%s', $user->id, $request->user()->id));
if ($validated['is_admin']) {
if ($user->promoteToAdministrator()) {
$user->save();
Log::notice(sprintf('User ID #%s set as administrator at creation by user ID #%s', $user->id, $request->user()->id));
}
}
$user->refresh();
return (new UserManagerResource($user))
->response()
->setStatusCode(201);
}
/**
* Purge user's PATs.
*
* @return \Illuminate\Http\JsonResponse
*/
public function revokePATs(Request $request, User $user, TokenRepository $tokenRepository)
{
Log::info(sprintf('Deletion of all personal access tokens for User ID #%s requested by User ID #%s', $user->id, $request->user()->id));
$this->authorize('update', $user);
$tokens = $tokenRepository->forUser($user->getAuthIdentifier());
$tokens->load('client')->filter(function ($token) {
return $token->client->personal_access_client && ! $token->revoked; /** @phpstan-ignore-line */
})->each(function ($token) {
$token->revoke(); /** @phpstan-ignore-line */
});
Log::info(sprintf('All personal access tokens for User ID #%s have been revoked', $user->id));
return response()->json(null, 204);
}
/**
* Purge user's webauthn credentials.
*
* @return \Illuminate\Http\JsonResponse
*/
public function revokeWebauthnCredentials(Request $request, User $user)
{
Log::info(sprintf('Deletion of all security devices for User ID #%s requested by User ID #%s', $user->id, $request->user()->id));
$this->authorize('update', $user);
$user->flushCredentials();
// WebauthnOnly user options need to be reset to prevent impossible login when
// no more registered device exists.
// See #110
if (blank($user->webAuthnCredentials()->WhereEnabled()->get())) {
$user['preferences->useWebauthnOnly'] = false;
$user->save();
Log::notice(sprintf('No more Webauthn credential for user ID #%s, useWebauthnOnly user preference reset to false', $user->id));
}
Log::info(sprintf('All security devices for User ID #%s have been revoked', $user->id));
return response()->json(null, 204);
}
/**
* Remove the specified user from storage.
*
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, User $user)
{
$this->authorize('delete', $user);
// This will delete the user and all its 2FAs & Groups thanks to the onCascadeDelete constrains.
// Deletion will not be done (and returns False) if the user is the only existing admin (see UserObserver clas)
return $user->delete() === false
? response()->json([
'message' => __('errors.cannot_delete_the_only_admin'),
], 403)
: response()->json(null, 204);
}
/**
* Promote (or demote) a user
*
* @return \App\Api\v1\Resources\UserManagerResource|\Illuminate\Http\JsonResponse
*/
public function promote(UserManagerPromoteRequest $request, User $user)
{
$this->authorize('promote', $user);
if ($user->promoteToAdministrator($request->validated('is_admin'))) {
$user->save();
Log::info(sprintf('User ID #%s set is_admin=%s for User ID #%s', $request->user()->id, $user->isAdministrator(), $user->id));
return new UserManagerResource($user);
}
return response()->json([
'message' => __('errors.cannot_demote_the_only_admin'),
], 403);
}
/**
* Get the user's authentication logs
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function authentications(Request $request, User $user)
{
$this->authorize('view', $user);
// Here we purge the authentication log.
// Running the purge command when someone fetchs the auth log
// is not very elegant but it's straitforward compared
// to a scheduled task, and the delete query is light.
// => To enhance.
Artisan::call('2fauth:purge-log');
$validated = $this->validate($request, [
'period' => 'sometimes|numeric',
'limit' => 'sometimes|numeric',
]);
$authentications = $request->has('period') ? $user->authenticationsByPeriod($validated['period']) : $user->authentications;
$authentications = $request->has('limit') ? $authentications->take($validated['limit']) : $authentications;
return UserAuthentication::collection($authentications);
}
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker|\Illuminate\Auth\Passwords\PasswordBroker
*/
protected function broker()
{
return Password::broker();
}
}