Apply Laravel Pint fixes

This commit is contained in:
Bubka 2022-11-22 15:15:52 +01:00
parent d84dd6659e
commit d6fd8e3c52
178 changed files with 2409 additions and 2899 deletions

View File

@ -2,18 +2,16 @@
namespace App\Api\v1\Controllers;
use App\Models\Group;
use App\Facades\Groups;
use App\Api\v1\Requests\GroupStoreRequest;
use App\Api\v1\Requests\GroupAssignRequest;
use App\Api\v1\Requests\GroupStoreRequest;
use App\Api\v1\Resources\GroupResource;
use App\Api\v1\Resources\TwoFAccountCollection;
use App\Facades\Groups;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\App;
use App\Models\Group;
class GroupController extends Controller
{
/**
* Display a listing of the resource.
*
@ -26,7 +24,6 @@ class GroupController extends Controller
return GroupResource::collection($groups);
}
/**
* Store a newly created resource in storage.
*
@ -44,7 +41,6 @@ class GroupController extends Controller
->setStatusCode(201);
}
/**
* Display the specified resource.
*
@ -56,12 +52,11 @@ class GroupController extends Controller
return new GroupResource($group);
}
/**
* Update the specified resource in storage.
*
* @param \App\Api\v1\Requests\GroupStoreRequest $request
* @param \App\Models\Group $group
* @param \App\Models\Group $group
* @return \App\Api\v1\Resources\GroupResource
*/
public function update(GroupStoreRequest $request, Group $group)
@ -71,10 +66,8 @@ class GroupController extends Controller
Groups::update($group, $validated);
return new GroupResource($group);
}
/**
* Associate the specified accounts with the group
*
@ -89,10 +82,8 @@ class GroupController extends Controller
Groups::assign($validated['ids'], $group);
return new GroupResource($group);
}
/**
* Get accounts assign to the group
*
@ -104,10 +95,8 @@ class GroupController extends Controller
$twofaccounts = Groups::getAccounts($group);
return new TwoFAccountCollection($twofaccounts);
}
/**
* Remove the specified resource from storage.
*
@ -120,5 +109,4 @@ class GroupController extends Controller
return response()->json(null, 204);
}
}

View File

@ -2,12 +2,11 @@
namespace App\Api\v1\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\Controller;
use App\Services\LogoService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
class IconController extends Controller
{
@ -31,7 +30,6 @@ class IconController extends Controller
: response()->json(['message' => __('errors.file_upload_failed')], 500);
}
/**
* Fetch a logo
*
@ -45,18 +43,17 @@ class IconController extends Controller
]);
$logoService = App::make(LogoService::class);
$icon = $logoService->getIcon($request->service);
$icon = $logoService->getIcon($request->service);
return $icon
? response()->json(['filename' => $icon], 201)
: response()->json(null, 204);
}
/**
* delete an icon
*
* @param string $icon
* @param string $icon
* @return \Illuminate\Http\JsonResponse
*/
public function delete(string $icon)

View File

@ -2,15 +2,13 @@
namespace App\Api\v1\Controllers;
use App\Models\TwoFAccount;
use App\Facades\QrCode;
use App\Api\v1\Requests\QrCodeDecodeRequest;
use App\Facades\QrCode;
use App\Http\Controllers\Controller;
use App\Models\TwoFAccount;
class QrCodeController extends Controller
{
/**
* Show a QR code image
*
@ -24,7 +22,6 @@ class QrCodeController extends Controller
return response()->json(['qrcode' => QrCode::encode($uri)], 200);
}
/**
* Decode an uploaded QR Code image
*
@ -39,5 +36,4 @@ class QrCodeController extends Controller
? response()->json(['data' => QrCode::decode($file)], 200)
: response()->json(['message' => __('errors.file_upload_failed')], 500);
}
}

View File

@ -2,12 +2,11 @@
namespace App\Api\v1\Controllers;
use App\Facades\Settings;
use App\Api\v1\Requests\SettingStoreRequest;
use App\Api\v1\Requests\SettingUpdateRequest;
use App\Facades\Settings;
use App\Http\Controllers\Controller;
class SettingController extends Controller
{
/**
@ -17,23 +16,22 @@ class SettingController extends Controller
*/
public function index()
{
$settings = Settings::all();
$settings = Settings::all();
$settingsResources = collect([]);
$settings->each(function (mixed $item, string $key) use ($settingsResources) {
$settingsResources->push([
'key' => $key,
'value' => $item
'key' => $key,
'value' => $item,
]);
});
return response()->json($settingsResources->all(), 200);
}
/**
* Display a setting
*
* @param string $settingName
* @param string $settingName
* @return \Illuminate\Http\JsonResponse
*/
public function show($settingName)
@ -45,16 +43,15 @@ class SettingController extends Controller
}
return response()->json([
'key' => $settingName,
'value' => $setting
'key' => $settingName,
'value' => $setting,
], 200);
}
/**
* Store a setting
*
* @param \App\Api\v1\Requests\SettingStoreRequest $request
* @param \App\Api\v1\Requests\SettingStoreRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function store(SettingStoreRequest $request)
@ -64,16 +61,15 @@ class SettingController extends Controller
Settings::set($validated['key'], $validated['value']);
return response()->json([
'key' => $validated['key'],
'value' => $validated['value']
'key' => $validated['key'],
'value' => $validated['value'],
], 201);
}
/**
* Update a setting
*
* @param \App\Api\v1\Requests\SettingUpdateRequest $request
* @param \App\Api\v1\Requests\SettingUpdateRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function update(SettingUpdateRequest $request, string $settingName)
@ -83,17 +79,15 @@ class SettingController extends Controller
Settings::set($settingName, $validated['value']);
return response()->json([
'key' => $settingName,
'value' => $validated['value']
'key' => $settingName,
'value' => $validated['value'],
], 200);
}
/**
* Delete a setting
*
* @param string $settingName
* @param string $settingName
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(string $settingName)
@ -105,16 +99,15 @@ class SettingController extends Controller
}
$optionsConfig = config('2fauth.options');
if(array_key_exists($settingName, $optionsConfig)) {
if (array_key_exists($settingName, $optionsConfig)) {
return response()->json(
['message' => 'bad request',
'reason' => [__('errors.delete_user_setting_only')]
], 400);
['message' => 'bad request',
'reason' => [__('errors.delete_user_setting_only')],
], 400);
}
Settings::delete($settingName);
return response()->json(null, 204);
}
}

View File

@ -2,26 +2,25 @@
namespace App\Api\v1\Controllers;
use App\Models\TwoFAccount;
use App\Api\v1\Requests\TwoFAccountBatchRequest;
use App\Api\v1\Requests\TwoFAccountDynamicRequest;
use App\Api\v1\Requests\TwoFAccountImportRequest;
use App\Api\v1\Requests\TwoFAccountReorderRequest;
use App\Api\v1\Requests\TwoFAccountStoreRequest;
use App\Api\v1\Requests\TwoFAccountUpdateRequest;
use App\Api\v1\Requests\TwoFAccountImportRequest;
use App\Api\v1\Requests\TwoFAccountBatchRequest;
use App\Api\v1\Requests\TwoFAccountUriRequest;
use App\Api\v1\Requests\TwoFAccountDynamicRequest;
use App\Api\v1\Resources\TwoFAccountCollection;
use App\Api\v1\Resources\TwoFAccountReadResource;
use App\Api\v1\Resources\TwoFAccountStoreResource;
use App\Facades\Groups;
use App\Facades\TwoFAccounts;
use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\TwoFAccount;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class TwoFAccountController extends Controller
{
/**
* List all resources
*
@ -32,12 +31,10 @@ class TwoFAccountController extends Controller
return new TwoFAccountCollection(TwoFAccount::ordered()->get());
}
/**
* Display a 2FA account
*
* @param \App\Models\TwoFAccount $twofaccount
*
* @return \App\Api\v1\Resources\TwoFAccountReadResource
*/
public function show(TwoFAccount $twofaccount)
@ -45,7 +42,6 @@ class TwoFAccountController extends Controller
return new TwoFAccountReadResource($twofaccount);
}
/**
* Store a new 2FA account
*
@ -60,13 +56,12 @@ class TwoFAccountController extends Controller
// - The advanced form has been used and all individual parameters
// -> We use the parameters array to define the account
$validated = $request->validated();
$validated = $request->validated();
$twofaccount = new TwoFAccount;
if (Arr::has($validated, 'uri')) {
$twofaccount->fillWithURI($validated['uri'], Arr::get($validated, 'custom_otp') === TwoFAccount::STEAM_TOTP);
}
else {
} else {
$twofaccount->fillWithOtpParameters($validated);
}
$twofaccount->save();
@ -79,8 +74,6 @@ class TwoFAccountController extends Controller
->setStatusCode(201);
}
/**
* Update a 2FA account
*
@ -98,10 +91,8 @@ class TwoFAccountController extends Controller
return (new TwoFAccountReadResource($twofaccount))
->response()
->setStatusCode(200);
}
/**
* Convert a migration resource to a valid TwoFAccounts collection
*
@ -118,13 +109,11 @@ class TwoFAccountController extends Controller
return $migrationResource instanceof \Illuminate\Http\UploadedFile
? new TwoFAccountCollection(TwoFAccounts::migrate($migrationResource->get()))
: response()->json(['message' => __('errors.file_upload_failed')], 500);
}
else {
} else {
return new TwoFAccountCollection(TwoFAccounts::migrate($request->payload));
}
}
/**
* Save 2FA accounts order
*
@ -140,7 +129,6 @@ class TwoFAccountController extends Controller
return response()->json(['message' => 'order saved'], 200);
}
/**
* Preview account using an uri, without any db moves
*
@ -155,12 +143,11 @@ class TwoFAccountController extends Controller
return new TwoFAccountStoreResource($twofaccount);
}
/**
* Get a One-Time Password
*
* @param \Illuminate\Http\Request $request
* @param string|null $id
* @param string|null $id
* @return \Illuminate\Http\JsonResponse
*/
public function otp(Request $request, $id = null)
@ -173,17 +160,16 @@ class TwoFAccountController extends Controller
}
// The request input is an uri
else if ( $request->has('uri') ) {
elseif ($request->has('uri')) {
// return 404 if uri is provided with any parameter other than otp_type
if ((count($inputs) == 2 && $request->missing('custom_otp')) || count($inputs) > 2) {
return response()->json([
'message' => 'bad request',
'reason' => ['uri' => __('validation.onlyCustomOtpWithUri')]
'reason' => ['uri' => __('validation.onlyCustomOtpWithUri')],
], 400);
}
else {
} else {
$validatedData = $request->validate((new TwoFAccountUriRequest)->rules());
$twofaccount = new TwoFAccount;
$twofaccount = new TwoFAccount;
$twofaccount->fillWithURI($validatedData['uri'], Arr::get($validatedData, 'custom_otp') === TwoFAccount::STEAM_TOTP, true);
}
}
@ -191,14 +177,13 @@ class TwoFAccountController extends Controller
// The request inputs should define an account
else {
$validatedData = $request->validate((new TwoFAccountStoreRequest)->rules());
$twofaccount = new TwoFAccount();
$twofaccount = new TwoFAccount();
$twofaccount->fillWithOtpParameters($validatedData, true);
}
return response()->json($twofaccount->getOTP(), 200);
}
/**
* A simple and light method to get the account count.
*
@ -207,15 +192,13 @@ class TwoFAccountController extends Controller
*/
public function count(Request $request)
{
return response()->json([ 'count' => TwoFAccount::count() ], 200);
return response()->json(['count' => TwoFAccount::count()], 200);
}
/**
*
* Withdraw one or more accounts from their group
*
* @param \App\Api\v1\Requests\TwoFAccountBatchRequest $request
* @param \App\Api\v1\Requests\TwoFAccountBatchRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function withdraw(TwoFAccountBatchRequest $request)
@ -225,16 +208,15 @@ class TwoFAccountController extends Controller
if ($this->tooManyIds($validated['ids'])) {
return response()->json([
'message' => 'bad request',
'reason' => [__('errors.too_many_ids')]
'reason' => [__('errors.too_many_ids')],
], 400);
}
TwoFAccounts::withdraw($validated['ids']);
return response()->json([ 'message' => 'accounts withdrawn' ], 200);
return response()->json(['message' => 'accounts withdrawn'], 200);
}
/**
* Remove the specified resource from storage.
*
@ -248,7 +230,6 @@ class TwoFAccountController extends Controller
return response()->json(null, 204);
}
/**
* Remove the specified resources from storage.
*
@ -262,7 +243,7 @@ class TwoFAccountController extends Controller
if ($this->tooManyIds($validated['ids'])) {
return response()->json([
'message' => 'bad request',
'reason' => [__('errors.too_many_ids')]
'reason' => [__('errors.too_many_ids')],
], 400);
}
@ -271,19 +252,17 @@ class TwoFAccountController extends Controller
return response()->json(null, 204);
}
/**
* Checks ids length
*
* @param string $ids comma-separated ids
* @param string $ids comma-separated ids
* @return bool whether or not the number of ids is acceptable
*/
private function tooManyIds(string $ids) : bool
{
$arIds = explode(',', $ids, 100);
$nb = count($arIds);
$nb = count($arIds);
return $nb > 99 ? true : false;
}
}

View File

@ -2,9 +2,9 @@
namespace App\Api\v1\Controllers;
use App\Models\User;
use App\Api\v1\Resources\UserResource;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
@ -24,6 +24,5 @@ class UserController extends Controller
return $user
? new UserResource($user)
: response()->json(['name' => null], 200);
}
}

View File

@ -25,8 +25,8 @@ class GroupAssignRequest extends FormRequest
public function rules()
{
return [
'ids' => 'required|array',
'ids.*' => 'integer'
'ids' => 'required|array',
'ids.*' => 'integer',
];
}
}

View File

@ -25,7 +25,7 @@ class SettingStoreRequest extends FormRequest
public function rules()
{
return [
'key' => 'required|alpha|max:128|unique:options,key',
'key' => 'required|alpha|max:128|unique:options,key',
'value' => 'required',
];
}

View File

@ -2,27 +2,27 @@
namespace App\Api\v1\Requests;
use Illuminate\Support\Arr;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
class TwoFAccountDynamicRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return Auth::check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = Arr::has($this->validationData(), 'uri')
@ -32,7 +32,6 @@ class TwoFAccountDynamicRequest extends FormRequest
return $rules;
}
/**
* Prepare the data for validation.
*
@ -41,7 +40,7 @@ class TwoFAccountDynamicRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'otp_type' => strtolower($this->otp_type),
'otp_type' => strtolower($this->otp_type),
'algorithm' => strtolower($this->algorithm),
]);
}

View File

@ -26,7 +26,7 @@ class TwoFAccountImportRequest extends FormRequest
{
return [
'payload' => 'required_without:file|string',
'file' => 'required_without:payload|mimes:txt,json,csv',
'file' => 'required_without:payload|mimes:txt,json,csv',
];
}
}

View File

@ -25,19 +25,18 @@ class TwoFAccountStoreRequest extends FormRequest
public function rules()
{
return [
'service' => 'nullable|string|regex:/^[^:]+$/i',
'account' => 'required|string|regex:/^[^:]+$/i',
'icon' => 'nullable|string',
'otp_type' => 'required|string|in:totp,hotp,steamtotp',
'secret' => ['string', 'bail', new \App\Rules\IsBase32Encoded],
'digits' => 'nullable|integer|between:5,10',
'service' => 'nullable|string|regex:/^[^:]+$/i',
'account' => 'required|string|regex:/^[^:]+$/i',
'icon' => 'nullable|string',
'otp_type' => 'required|string|in:totp,hotp,steamtotp',
'secret' => ['string', 'bail', new \App\Rules\IsBase32Encoded],
'digits' => 'nullable|integer|between:5,10',
'algorithm' => 'nullable|string|in:sha1,sha256,sha512,md5',
'period' => 'nullable|integer|min:1',
'counter' => 'nullable|integer|min:0',
'period' => 'nullable|integer|min:1',
'counter' => 'nullable|integer|min:0',
];
}
/**
* Prepare the data for validation.
*
@ -46,7 +45,7 @@ class TwoFAccountStoreRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'otp_type' => strtolower($this->otp_type),
'otp_type' => strtolower($this->otp_type),
'algorithm' => strtolower($this->algorithm),
]);
}

View File

@ -25,19 +25,18 @@ class TwoFAccountUpdateRequest extends FormRequest
public function rules()
{
return [
'service' => 'present|nullable|string|regex:/^[^:]+$/i',
'account' => 'required|string|regex:/^[^:]+$/i',
'icon' => 'present|nullable|string',
'otp_type' => 'required|string|in:totp,hotp,steamtotp',
'secret' => ['present', 'string', 'bail', new \App\Rules\IsBase32Encoded],
'digits' => 'present|integer|between:5,10',
'service' => 'present|nullable|string|regex:/^[^:]+$/i',
'account' => 'required|string|regex:/^[^:]+$/i',
'icon' => 'present|nullable|string',
'otp_type' => 'required|string|in:totp,hotp,steamtotp',
'secret' => ['present', 'string', 'bail', new \App\Rules\IsBase32Encoded],
'digits' => 'present|integer|between:5,10',
'algorithm' => 'present|string|in:sha1,sha256,sha512,md5',
'period' => 'nullable|integer|min:1',
'counter' => 'nullable|integer|min:0',
'period' => 'nullable|integer|min:1',
'counter' => 'nullable|integer|min:0',
];
}
/**
* Prepare the data for validation.
*
@ -46,7 +45,7 @@ class TwoFAccountUpdateRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'otp_type' => strtolower($this->otp_type),
'otp_type' => strtolower($this->otp_type),
'algorithm' => strtolower($this->algorithm),
]);
}

View File

@ -30,7 +30,6 @@ class TwoFAccountUriRequest extends FormRequest
];
}
/**
* Prepare the data for validation.
*

View File

@ -20,9 +20,9 @@ class GroupResource extends JsonResource
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'twofaccounts_count' => is_null($this->twofaccounts_count) ? 0 : $this->twofaccounts_count,
'id' => $this->id,
'name' => $this->name,
'twofaccounts_count' => is_null($this->twofaccounts_count) ? 0 : $this->twofaccounts_count,
];
}
}

View File

@ -3,7 +3,6 @@
namespace App\Api\v1\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
use App\Api\v1\Resources\TwoFAccountReadResource;
class TwoFAccountCollection extends ResourceCollection
{
@ -14,7 +13,6 @@ class TwoFAccountCollection extends ResourceCollection
*/
public $collects = TwoFAccountReadResource::class;
/**
* Transform the resource collection into an array.
*
@ -27,7 +25,7 @@ class TwoFAccountCollection extends ResourceCollection
// The underlying TwoFAccountReadResource hides the secret only when withSecret == false.
// When withSecret is provided the underlying resource will return secret according to the parameter value
// If no withSecret is set we force it to false to ensure the secret will not being returned.
if (!$request->has('withSecret')) {
if (! $request->has('withSecret')) {
$request->merge(['withSecret' => false]);
}

View File

@ -18,8 +18,8 @@ class TwoFAccountReadResource extends TwoFAccountStoreResource
{
return array_merge(
[
'id' => (int) $this->id,
'group_id' => is_null($this->group_id) ? null : (int) $this->group_id,
'id' => (int) $this->id,
'group_id' => is_null($this->group_id) ? null : (int) $this->group_id,
],
parent::toArray($request)
);

View File

@ -26,18 +26,18 @@ class TwoFAccountStoreResource extends JsonResource
public function toArray($request)
{
return [
'otp_type' => $this->otp_type,
'account' => $this->account,
'service' => $this->service,
'icon' => $this->icon,
'secret' => $this->when(
!$request->has('withSecret') || (int) filter_var($request->input('withSecret'), FILTER_VALIDATE_BOOLEAN) == 1,
$this->secret
),
'digits' => (int) $this->digits,
'algorithm' => $this->algorithm,
'period' => is_null($this->period) ? null : (int)$this->period,
'counter' => is_null($this->counter) ? null : (int)$this->counter
'otp_type' => $this->otp_type,
'account' => $this->account,
'service' => $this->service,
'icon' => $this->icon,
'secret' => $this->when(
! $request->has('withSecret') || (int) filter_var($request->input('withSecret'), FILTER_VALIDATE_BOOLEAN) == 1,
$this->secret
),
'digits' => (int) $this->digits,
'algorithm' => $this->algorithm,
'period' => is_null($this->period) ? null : (int) $this->period,
'counter' => is_null($this->counter) ? null : (int) $this->counter,
];
}
}

View File

@ -20,9 +20,9 @@ class UserResource extends JsonResource
public function toArray($request)
{
return [
'id' => $this->when(!is_null($request->user()), $this->id),
'id' => $this->when(! is_null($request->user()), $this->id),
'name' => $this->name,
'email' => $this->when(!is_null($request->user()), $this->email),
'email' => $this->when(! is_null($request->user()), $this->email),
];
}
}

View File

@ -4,7 +4,6 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class CheckDbConnection extends Command
{
@ -44,6 +43,7 @@ class CheckDbConnection extends Command
try {
DB::connection()->getPDO();
$this->line(DB::connection()->getDatabaseName());
return 1;
} catch (\Exception $e) {
return 0;

View File

@ -42,12 +42,13 @@ class FixUnsplittedAccounts extends Command
*/
public function handle()
{
if (!Schema::hasColumn('twofaccounts', 'legacy_uri')) {
if (! Schema::hasColumn('twofaccounts', 'legacy_uri')) {
$this->comment('2fauth:fix-unsplitted-accounts is useful only after SplitTwofaccountsUriInMultipleColumns migration ran');
return;
} else {
$this->line('Fetching accounts...');
}
else $this->line('Fetching accounts...');
$twofaccounts = TwoFAccount::where('otp_type', '')
->where('secret', '')
@ -61,6 +62,7 @@ class FixUnsplittedAccounts extends Command
if ($twofaccounts->count() == 0) {
$this->info('Nothing to fix');
return;
}
@ -69,16 +71,14 @@ class FixUnsplittedAccounts extends Command
foreach ($twofaccounts as $twofaccount) {
if ($twofaccount->legacy_uri === __('errors.indecipherable')) {
$this->error(sprintf('Account #%d cannot be deciphered', $twofaccount->id));
}
else {
} else {
try {
// Get a consistent account
$twofaccount->fillWithURI($twofaccount->legacy_uri, false, true);
$twofaccount->save();
$this->info(sprintf('Account #%d fixed', $twofaccount->id));
}
catch (\Exception $ex) {
} catch (\Exception $ex) {
$this->error(sprintf('Error while updating account #%d', $twofaccount->id));
}
}

View File

@ -2,8 +2,8 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Console\Commands\Utils\ResetTrait;
use Illuminate\Console\Command;
class ResetDemo extends Command
{
@ -40,15 +40,15 @@ class ResetDemo extends Command
*/
public function handle()
{
if( !config('2fauth.config.isDemoApp') ) {
if (! config('2fauth.config.isDemoApp')) {
$this->comment('2fauth:reset-demo can only run when isDemoApp option is On');
return;
}
if( $this->option('no-confirm') ) {
if ($this->option('no-confirm')) {
$demo = 'demo';
}
else {
} else {
$this->line('This will reset the app in order to run a clean and fresh demo.');
$demo = $this->ask('To prevent any mistake please type the word "demo" to go on');
}
@ -57,8 +57,7 @@ class ResetDemo extends Command
$this->resetIcons();
$this->resetDB('DemoSeeder');
$this->info('Demo app refreshed');
}
else {
} else {
$this->comment('Bad confirmation word, nothing appened');
}
}

View File

@ -40,15 +40,15 @@ class ResetTesting extends Command
*/
public function handle()
{
if( !config('2fauth.config.isTestingApp') ) {
if (! config('2fauth.config.isTestingApp')) {
$this->comment('2fauth:reset-testing can only run when isTestingApp option is On');
return;
}
if( $this->option('no-confirm') ) {
if ($this->option('no-confirm')) {
$testing = 'testing';
}
else {
} else {
$this->line('This will reset the app in order to run a clean and fresh testing app.');
$testing = $this->ask('To prevent any mistake please type the word "testing" to go on');
}
@ -58,10 +58,8 @@ class ResetTesting extends Command
$this->resetDB('TestingSeeder');
$this->info('Testing app refreshed');
}
else {
} else {
$this->comment('Bad confirmation word, nothing appened');
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,6 @@
namespace App\Console\Commands\Utils;
use App\Console\Commands\Utils\IconGenerator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
@ -81,10 +80,9 @@ trait ResetTrait
protected function seedDB(string $seeder) : void
{
$this->callSilent('db:seed', [
'--class' => $seeder
'--class' => $seeder,
]);
$this->line('Database seeded');
}
}

View File

@ -27,7 +27,7 @@ class Kernel extends ConsoleKernel
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}

View File

@ -44,64 +44,71 @@ class Handler extends ExceptionHandler
{
$this->renderable(function (\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $exception, $request) {
return response()->json([
'message' => 'not found'], 404);
'message' => 'not found',
], 404);
});
$this->renderable(function (InvalidOtpParameterException $exception, $request) {
return response()->json([
'message' => 'invalid OTP parameters',
'reason' => [$exception->getMessage()]
'reason' => [$exception->getMessage()],
], 400);
});
$this->renderable(function (InvalidQrCodeException $exception, $request) {
return response()->json([
'message' => 'not a valid QR code'], 400);
'message' => 'not a valid QR code', ], 400);
});
$this->renderable(function (InvalidSecretException $exception, $request) {
return response()->json([
'message' => 'not a valid base32 encoded secret'], 400);
'message' => 'not a valid base32 encoded secret', ], 400);
});
$this->renderable(function (DbEncryptionException $exception, $request) {
return response()->json([
'message' => $exception->getMessage()], 400);
'message' => $exception->getMessage(), ], 400);
});
$this->renderable(function (InvalidMigrationDataException $exception, $request) {
return response()->json([
'message' => __('errors.invalid_x_migration', ['appname' => $exception->getMessage()])], 400);
'message' => __('errors.invalid_x_migration', ['appname' => $exception->getMessage()]),
], 400);
});
$this->renderable(function (UnsupportedMigrationException $exception, $request) {
return response()->json([
'message' => __('errors.unsupported_migration')], 400);
'message' => __('errors.unsupported_migration'),
], 400);
});
$this->renderable(function (EncryptedMigrationException $exception, $request) {
return response()->json([
'message' => __('errors.encrypted_migration')], 400);
'message' => __('errors.encrypted_migration'),
], 400);
});
$this->renderable(function (UndecipherableException $exception, $request) {
return response()->json([
'message' => __('errors.cannot_decipher_secret')], 400);
'message' => __('errors.cannot_decipher_secret'),
], 400);
});
$this->renderable(function (UnsupportedOtpTypeException $exception, $request) {
return response()->json([
'message' => __('errors.unsupported_otp_type')], 400);
'message' => __('errors.unsupported_otp_type'),
], 400);
});
$this->renderable(function (\Illuminate\Auth\AuthenticationException $exception, $request) {
if ($exception->guards() === ['reverse-proxy-guard']) {
return response()->json([
'message' => $exception->getMessage()], 407);
}
else {
'message' => $exception->getMessage(),
], 407);
} else {
return response()->json([
'message' => $exception->getMessage()], 401);
'message' => $exception->getMessage(),
], 401);
}
});
}

View File

@ -6,10 +6,10 @@
namespace App\Extensions;
use App\Models\User;
use Exception;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Arr;
use Exception;
class RemoteUserProvider implements UserProvider
{
@ -23,7 +23,6 @@ class RemoteUserProvider implements UserProvider
// The downside of this approach is that we have to be sure that no change that needs
// to be persisted will be made to the user instance afterward (i.e through middlewares).
/**
* The currently authenticated user.
*
@ -31,7 +30,6 @@ class RemoteUserProvider implements UserProvider
*/
protected $user;
/**
* Get the In-memory user
*
@ -40,17 +38,16 @@ class RemoteUserProvider implements UserProvider
protected function getInMemoryUser()
{
if (is_null($this->user)) {
$this->user = new User;
$this->user->name = 'Remote User';
$this->user = new User;
$this->user->name = 'Remote User';
$this->user->email = 'fake.email@do.not.use';
}
return $this->user;
}
/**
* @inheritDoc
* {@inheritDoc}
*/
public function retrieveById($identifier)
{
@ -67,7 +64,7 @@ class RemoteUserProvider implements UserProvider
}
/**
* @inheritDoc
* {@inheritDoc}
*
* @codeCoverageIgnore
*/
@ -77,7 +74,7 @@ class RemoteUserProvider implements UserProvider
}
/**
* @inheritDoc
* {@inheritDoc}
*
* @codeCoverageIgnore
*/
@ -87,7 +84,7 @@ class RemoteUserProvider implements UserProvider
}
/**
* @inheritDoc
* {@inheritDoc}
*
* @codeCoverageIgnore
*/
@ -97,7 +94,7 @@ class RemoteUserProvider implements UserProvider
}
/**
* @inheritDoc
* {@inheritDoc}
*
* @codeCoverageIgnore
*/

View File

@ -2,8 +2,8 @@
namespace App\Extensions;
use Closure;
use App\Models\WebAuthnAuthenticatable;
use Closure;
use Illuminate\Auth\Passwords\PasswordBroker;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
@ -14,14 +14,13 @@ class WebauthnCredentialBroker extends PasswordBroker
*
* @param array $credentials
* @param \Closure|null $callback
*
* @return string
*/
public function sendResetLink(array $credentials, Closure $callback = null): string
public function sendResetLink(array $credentials, Closure $callback = null) : string
{
$user = $this->getUser($credentials);
if (!$user instanceof WebAuthnAuthenticatable) {
if (! $user instanceof WebAuthnAuthenticatable) {
return static::INVALID_USER;
}
@ -40,20 +39,18 @@ class WebauthnCredentialBroker extends PasswordBroker
return static::RESET_LINK_SENT;
}
/**
* Reset the password for the given token.
*
* @param array $credentials
* @param \Closure $callback
*
* @return \Illuminate\Contracts\Auth\CanResetPassword|string
*/
public function reset(array $credentials, Closure $callback)
{
$user = $this->validateReset($credentials);
if (!$user instanceof CanResetPasswordContract || !$user instanceof WebAuthnAuthenticatable) {
if (! $user instanceof CanResetPasswordContract || ! $user instanceof WebAuthnAuthenticatable) {
return $user;
}

View File

@ -2,67 +2,63 @@
namespace App\Factories;
use App\Services\Migrators\GoogleAuthMigrator;
use App\Exceptions\EncryptedMigrationException;
use App\Exceptions\UnsupportedMigrationException;
use App\Services\Migrators\AegisMigrator;
use App\Services\Migrators\GoogleAuthMigrator;
use App\Services\Migrators\Migrator;
use App\Services\Migrators\PlainTextMigrator;
use App\Services\Migrators\TwoFASMigrator;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Validator;
use App\Exceptions\UnsupportedMigrationException;
use App\Exceptions\EncryptedMigrationException;
class MigratorFactory implements MigratorFactoryInterface
{
/**
* Infer the type of migrator needed from a payload and create the migrator
*
* @param string $migrationPayload The migration payload used to infer the migrator type
* @param string $migrationPayload The migration payload used to infer the migrator type
* @return Migrator
*/
public function create(string $migrationPayload) : Migrator
{
if ($this->isAegisJSON($migrationPayload)) {
return App::make(AegisMigrator::class);
}
else if ($this->is2FASv2($migrationPayload)) {
} elseif ($this->is2FASv2($migrationPayload)) {
return App::make(TwoFASMigrator::class);
}
else if ($this->isGoogleAuth($migrationPayload)) {
} elseif ($this->isGoogleAuth($migrationPayload)) {
return App::make(GoogleAuthMigrator::class);
}
else if ($this->isPlainText($migrationPayload)) {
} elseif ($this->isPlainText($migrationPayload)) {
return App::make(PlainTextMigrator::class);
} else {
throw new UnsupportedMigrationException();
}
else throw new UnsupportedMigrationException();
}
/**
* Determine if a payload comes from Google Authenticator
*
* @param string $migrationPayload The payload to analyse
* @param string $migrationPayload The payload to analyse
* @return bool
*/
private function isGoogleAuth(string $migrationPayload) : bool
{
// - Google Auth migration URI : a string starting with otpauth-migration://offline?data= on a single line
$lines = preg_split('~\R~', $migrationPayload, -1 , PREG_SPLIT_NO_EMPTY);
$lines = preg_split('~\R~', $migrationPayload, -1, PREG_SPLIT_NO_EMPTY);
if (!$lines || count($lines) != 1)
if (! $lines || count($lines) != 1) {
return false;
}
return preg_match('/^otpauth-migration:\/\/offline\?data=.+$/', $lines[0]) == 1;
}
/**
* Determine if a payload is a plain text content
*
* @param string $migrationPayload The payload to analyse
* @param string $migrationPayload The payload to analyse
* @return bool
*/
private function isPlainText(string $migrationPayload) : bool
@ -70,18 +66,17 @@ class MigratorFactory implements MigratorFactoryInterface
// - Plain text : one or more otpauth URIs (otpauth://[t|h]otp/...), one per line
return Validator::make(
preg_split('~\R~', $migrationPayload, -1 , PREG_SPLIT_NO_EMPTY),
preg_split('~\R~', $migrationPayload, -1, PREG_SPLIT_NO_EMPTY),
[
'*' => 'regex:/^otpauth:\/\/[h,t]otp\//i',
]
)->passes();
}
/**
* Determine if a payload comes from Aegis Authenticator in JSON format
*
* @param string $migrationPayload The payload to analyse
* @param string $migrationPayload The payload to analyse
* @return bool
*/
private function isAegisJSON(string $migrationPayload) : mixed
@ -107,15 +102,14 @@ class MigratorFactory implements MigratorFactoryInterface
if (Arr::has($json, 'db')) {
if (is_string($json['db']) && is_array(Arr::get($json, 'header.slots'))) {
throw new EncryptedMigrationException();
}
else {
} else {
return count(Validator::validate(
$json,
[
'db.entries.*.type' => 'required',
'db.entries.*.name' => 'required',
'db.entries.*.type' => 'required',
'db.entries.*.name' => 'required',
'db.entries.*.issuer' => 'required',
'db.entries.*.info' => 'required'
'db.entries.*.info' => 'required',
]
)) > 0;
}
@ -124,11 +118,10 @@ class MigratorFactory implements MigratorFactoryInterface
return false;
}
/**
* Determine if a payload comes from 2FAS Authenticator
*
* @param string $migrationPayload The payload to analyse
* @param string $migrationPayload The payload to analyse
* @return bool
*/
private function is2FASv2(string $migrationPayload) : mixed
@ -159,14 +152,13 @@ class MigratorFactory implements MigratorFactoryInterface
if (Arr::get($json, 'schemaVersion') == 2 && (Arr::has($json, 'services') || Arr::has($json, 'servicesEncrypted'))) {
if (Arr::has($json, 'servicesEncrypted')) {
throw new EncryptedMigrationException();
}
else {
} else {
return count(Validator::validate(
$json,
[
'services.*.secret' => 'required',
'services.*.name' => 'required',
'services.*.otp' => 'required'
'services.*.name' => 'required',
'services.*.otp' => 'required',
]
)) > 0;
}
@ -174,5 +166,4 @@ class MigratorFactory implements MigratorFactoryInterface
return false;
}
}

View File

@ -9,7 +9,7 @@ interface MigratorFactoryInterface
/**
* Infer the type of migrator needed from a payload and create the migrator
*
* @param string $migrationPayload The migration payload used to infer the migrator type
* @param string $migrationPayload The migration payload used to infer the migrator type
* @return Migrator
*/
public function create(string $migrationPayload) : Migrator;

View File

@ -9,16 +9,21 @@ class Helpers
/**
* Generate a unique filename
*
* @param string $extension
* @param string $extension
* @return string The filename
*/
public static function getUniqueFilename(string $extension): string
public static function getUniqueFilename(string $extension) : string
{
return Str::random(40).'.'.$extension;
return Str::random(40) . '.' . $extension;
}
public static function cleanVersionNumber(?string $release): string|false
/**
* Clean a version number string
*
* @param string|null $release
* @return string|false
*/
public static function cleanVersionNumber(?string $release) : string|false
{
return preg_match('/([[0-9][0-9\.]*[0-9])/', $release, $version) ? $version[0] : false;
}

View File

@ -2,9 +2,9 @@
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
class ForgotPasswordController extends Controller
{
@ -21,7 +21,6 @@ class ForgotPasswordController extends Controller
use SendsPasswordResetEmails;
/**
* Validate the email for the given request.
*

View File

@ -2,17 +2,16 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\LoginRequest;
use Carbon\Carbon;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Lang;
use App\Http\Requests\LoginRequest;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class LoginController extends Controller
{
/*
@ -28,7 +27,6 @@ class LoginController extends Controller
use AuthenticatesUsers;
/**
* Handle a login request to the application.
*
@ -65,10 +63,10 @@ class LoginController extends Controller
return $this->sendFailedLoginResponse($request);
}
/**
* log out current user
* @param Request $request
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function logout(Request $request)
@ -79,7 +77,6 @@ class LoginController extends Controller
return response()->json(['message' => 'signed out'], Response::HTTP_OK);
}
/**
* Send the response after the user was authenticated.
*
@ -96,11 +93,10 @@ class LoginController extends Controller
return response()->json([
'message' => 'authenticated',
'name' => $name
'name' => $name,
], Response::HTTP_OK);
}
/**
* Get the failed login response instance.
*
@ -112,7 +108,6 @@ class LoginController extends Controller
return response()->json(['message' => 'unauthorised'], Response::HTTP_UNAUTHORIZED);
}
/**
* Redirect the user after determining they are locked out.
*
@ -128,7 +123,6 @@ class LoginController extends Controller
return response()->json(['message' => Lang::get('auth.throttle', ['seconds' => $seconds])], Response::HTTP_TOO_MANY_REQUESTS);
}
/**
* Get the needed authorization credentials from the request.
*
@ -139,13 +133,12 @@ class LoginController extends Controller
{
$credentials = [
$this->username() => strtolower($request->input($this->username())),
'password' => $request->get('password'),
'password' => $request->get('password'),
];
return $credentials;
}
/**
* The user has been authenticated.
*

View File

@ -2,8 +2,8 @@
namespace App\Http\Controllers\Auth;
use App\Http\Requests\UserPatchPwdRequest;
use App\Http\Controllers\Controller;
use App\Http\Requests\UserPatchPwdRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
@ -13,19 +13,20 @@ class PasswordController extends Controller
/**
* Update the user's password.
*
* @param \App\Http\Requests\UserPatchPwdRequest $request
* @param \App\Http\Requests\UserPatchPwdRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function update(UserPatchPwdRequest $request)
{
$validated = $request->validated();
if (!Hash::check( $validated['currentPassword'], Auth::user()->password) ) {
if (! Hash::check($validated['currentPassword'], Auth::user()->password)) {
Log::notice('Password update failed: wrong password provided');
return response()->json(['message' => __('errors.wrong_current_password')], 400);
}
if (!config('2fauth.config.isDemoApp') ) {
if (! config('2fauth.config.isDemoApp')) {
$request->user()->update([
'password' => bcrypt($validated['password']),
]);

View File

@ -2,12 +2,12 @@
namespace App\Http\Controllers\Auth;
use App\Models\User;
use App\Http\Requests\UserStoreRequest;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use App\Http\Requests\UserStoreRequest;
use App\Models\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
class RegisterController extends Controller
@ -25,7 +25,6 @@ class RegisterController extends Controller
use RegistersUsers;
/**
* Handle a registration request for the application.
*
@ -42,11 +41,10 @@ class RegisterController extends Controller
return response()->json([
'message' => 'account created',
'name' => $user->name,
'name' => $user->name,
], 201);
}
/**
* Create a new user instance after a valid registration.
*
@ -56,8 +54,8 @@ class RegisterController extends Controller
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}

View File

@ -19,5 +19,4 @@ class ResetPasswordController extends Controller
*/
use ResetsPasswords;
}

View File

@ -2,14 +2,14 @@
namespace App\Http\Controllers\Auth;
use App\Http\Requests\UserUpdateRequest;
use App\Http\Requests\UserDeleteRequest;
use App\Api\v1\Resources\UserResource;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
use App\Http\Requests\UserDeleteRequest;
use App\Http\Requests\UserUpdateRequest;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
class UserController extends Controller
@ -17,22 +17,23 @@ class UserController extends Controller
/**
* Update the user's profile information.
*
* @param \App\Http\Requests\UserUpdateRequest $request
* @param \App\Http\Requests\UserUpdateRequest $request
* @return \App\Api\v1\Resources\UserResource|\Illuminate\Http\JsonResponse
*/
public function update(UserUpdateRequest $request)
{
$user = $request->user();
$user = $request->user();
$validated = $request->validated();
if (!Hash::check( $request->password, Auth::user()->password) ) {
if (! Hash::check($request->password, Auth::user()->password)) {
Log::notice('Account update failed: wrong password provided');
return response()->json(['message' => __('errors.wrong_current_password')], 400);
}
if (!config('2fauth.config.isDemoApp') ) {
if (! config('2fauth.config.isDemoApp')) {
$user->update([
'name' => $validated['name'],
'name' => $validated['name'],
'email' => $validated['email'],
]);
}
@ -41,11 +42,10 @@ class UserController extends Controller
return new UserResource($user);
}
/**
* Delete the user's account.
*
* @param \App\Http\Requests\UserDeleteRequest $request
* @param \App\Http\Requests\UserDeleteRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function delete(UserDeleteRequest $request)
@ -53,7 +53,7 @@ class UserController extends Controller
Log::info('User deletion requested');
$validated = $request->validated();
if (!Hash::check( $validated['password'], Auth::user()->password) ) {
if (! Hash::check($validated['password'], Auth::user()->password)) {
return response()->json(['message' => __('errors.wrong_current_password')], 400);
}
@ -79,6 +79,7 @@ class UserController extends Controller
// @codeCoverageIgnoreStart
catch (\Throwable $e) {
Log::error('User deletion failed');
return response()->json(['message' => __('errors.user_deletion_failed')], 400);
}
// @codeCoverageIgnoreEnd

View File

@ -2,26 +2,25 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use App\Extensions\WebauthnCredentialBroker;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Password;
use App\Http\Controllers\Controller;
use App\Http\Requests\WebauthnDeviceLostRequest;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;
class WebAuthnDeviceLostController extends Controller
{
use ResetsPasswords;
/**
* Send a recovery email to the user.
*
* @param \App\Http\Requests\WebauthnDeviceLostRequest $request
* @param \App\Http\Requests\WebauthnDeviceLostRequest $request
* @param \App\Extensions\WebauthnCredentialBroker $broker
*
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function sendRecoveryEmail(WebauthnDeviceLostRequest $request, WebauthnCredentialBroker $broker)
@ -35,14 +34,13 @@ class WebAuthnDeviceLostController extends Controller
: $this->sendRecoveryLinkFailedResponse($request, $response);
}
/**
* Get the response for a failed account recovery link.
*
* @param \Illuminate\Http\Request $request
* @param string $response
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
protected function sendRecoveryLinkFailedResponse(Request $request, string $response)
@ -56,13 +54,11 @@ class WebAuthnDeviceLostController extends Controller
->withErrors(['email' => trans($response)]);
}
/**
* Get the response for a successful account recovery link.
*
* @param \Illuminate\Http\Request $request
* @param string $response
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendRecoveryLinkResponse(Request $request, string $response)

View File

@ -2,14 +2,14 @@
namespace App\Http\Controllers\Auth;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Laragear\WebAuthn\Http\Requests\AssertionRequest;
use Laragear\WebAuthn\Http\Requests\AssertedRequest;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Laragear\WebAuthn\Http\Requests\AssertedRequest;
use Laragear\WebAuthn\Http\Requests\AssertionRequest;
use Laragear\WebAuthn\WebAuthn;
class WebAuthnLoginController extends Controller
@ -31,7 +31,7 @@ class WebAuthnLoginController extends Controller
* @param \Laragear\WebAuthn\Http\Requests\AssertionRequest $request
* @return \Illuminate\Contracts\Support\Responsable|\Illuminate\Http\JsonResponse
*/
public function options(AssertionRequest $request): Responsable|JsonResponse
public function options(AssertionRequest $request) : Responsable|JsonResponse
{
switch (env('WEBAUTHN_USER_VERIFICATION')) {
case WebAuthn::USER_VERIFICATION_DISCOURAGED:
@ -50,11 +50,10 @@ class WebAuthnLoginController extends Controller
return $user
? $request->toVerify($user)
: response()->json([
'message' => 'no registered user'
'message' => 'no registered user',
], 400);
}
/**
* Log the user in.
*
@ -70,7 +69,7 @@ class WebAuthnLoginController extends Controller
// Some authenticators do not send a userHandle so we hack the response to be compliant
// with Larapass/webauthn-lib implementation that waits for a userHandle
if(!$response['userHandle']) {
if (! $response['userHandle']) {
$response['userHandle'] = User::getFromCredentialId($request->id)?->userHandle();
$request->merge(['response' => $response]);
}
@ -80,18 +79,17 @@ class WebAuthnLoginController extends Controller
if ($user) {
$this->authenticated($user);
return response()->noContent();
}
return response()->noContent(422);
}
/**
* The user has been authenticated.
*
* @param mixed $user
*
* @return void|\Illuminate\Http\JsonResponse
*/
protected function authenticated($user)

View File

@ -4,13 +4,12 @@ namespace App\Http\Controllers\Auth;
use App\Facades\Settings;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\WebauthnRenameRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class WebAuthnManageController extends Controller
{
/**
* List all WebAuthn registered credentials
*
@ -23,12 +22,11 @@ class WebAuthnManageController extends Controller
return response()->json($allUserCredentials, 200);
}
/**
* Rename a WebAuthn credential
*
* @param \App\Http\Requests\WebauthnRenameRequest $request
* @param string $credential
* @param \App\Http\Requests\WebauthnRenameRequest $request
* @param string $credential
* @return \Illuminate\Http\JsonResponse
*/
public function rename(WebauthnRenameRequest $request, string $credential)
@ -38,17 +36,15 @@ class WebAuthnManageController extends Controller
abort_if(! $request->user()->renameCredential($credential, $validated['name']), 404);
return response()->json([
'name' => $validated['name'],
], 200);
'name' => $validated['name'],
], 200);
}
/**
* Remove the specified credential from storage.
*
* @param \Illuminate\Http\Request $request
* @param string|array $credential
*
* @return \Illuminate\Http\JsonResponse
*/
public function delete(Request $request, $credential)

View File

@ -2,18 +2,17 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\WebauthnRecoveryRequest;
use App\Extensions\WebauthnCredentialBroker;
use App\Facades\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\WebauthnRecoveryRequest;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\App;
use Illuminate\Validation\ValidationException;
class WebAuthnRecoveryController extends Controller
{
@ -25,8 +24,8 @@ class WebAuthnRecoveryController extends Controller
*
* @param \App\Http\Requests\WebauthnRecoveryRequest $request
* @param \App\Extensions\WebauthnCredentialBroker $broker
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function recover(WebauthnRecoveryRequest $request, WebauthnCredentialBroker $broker)
@ -54,66 +53,57 @@ class WebAuthnRecoveryController extends Controller
$user->flushCredentials();
}
Settings::delete('useWebauthnOnly');
} else {
throw new AuthenticationException();
}
else throw new AuthenticationException();
}
);
return $response === Password::PASSWORD_RESET
? $this->sendRecoveryResponse($request, $response)
: $this->sendRecoveryFailedResponse($request, $response);
}
/**
* Check if the user has set to revoke all credentials.
*
* @param \App\Http\Requests\WebauthnRecoveryRequest $request
*
* @return bool|mixed
*/
protected function shouldRevokeAllCredentials(WebauthnRecoveryRequest $request): mixed
protected function shouldRevokeAllCredentials(WebauthnRecoveryRequest $request) : mixed
{
return filter_var($request->header('WebAuthn-Unique'), FILTER_VALIDATE_BOOLEAN)
?: $request->input('revokeAll', true);
}
/**
* Get the response for a successful account recovery.
*
* @param \Illuminate\Http\Request $request
* @param string $response
*
* @return \Illuminate\Http\JsonResponse
*
*/
protected function sendRecoveryResponse(Request $request, string $response): JsonResponse
protected function sendRecoveryResponse(Request $request, string $response) : JsonResponse
{
return response()->json(['message' => __('auth.webauthn.webauthn_login_disabled')]);
}
/**
* Get the response for a failed account recovery.
*
* @param \Illuminate\Http\Request $request
* @param string $response
*
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException
*
* @throws \Illuminate\Validation\ValidationException
*/
protected function sendRecoveryFailedResponse(Request $request, string $response): JsonResponse
protected function sendRecoveryFailedResponse(Request $request, string $response) : JsonResponse
{
switch ($response) {
case Password::INVALID_TOKEN:
throw ValidationException::withMessages(['token' => [__('auth.webauthn.invalid_reset_token')]]);
default:
throw ValidationException::withMessages(['email' => [trans($response)]]);
}
}
}

View File

@ -17,7 +17,7 @@ class WebAuthnRegisterController extends Controller
* @param \Laragear\WebAuthn\Http\Requests\AttestationRequest $request
* @return \Illuminate\Contracts\Support\Responsable
*/
public function options(AttestationRequest $request): Responsable
public function options(AttestationRequest $request) : Responsable
{
switch (env('WEBAUTHN_USER_VERIFICATION')) {
case WebAuthn::USER_VERIFICATION_DISCOURAGED:
@ -34,14 +34,13 @@ class WebAuthnRegisterController extends Controller
->toCreate();
}
/**
* Registers a device for further WebAuthn authentication.
*
* @param \Laragear\WebAuthn\Http\Requests\AttestedRequest $request
* @return \Illuminate\Http\Response
*/
public function register(AttestedRequest $request): Response
public function register(AttestedRequest $request) : Response
{
$request->save();

View File

@ -2,10 +2,10 @@
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{

View File

@ -2,16 +2,15 @@
namespace App\Http\Controllers;
use App\Events\ScanForNewReleaseCalled;
use App\Facades\Settings;
use Illuminate\Support\Facades\App;
use App\Events\ScanForNewReleaseCalled;
class SinglePageController extends Controller
{
/**
* return the main view
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index()
@ -20,14 +19,14 @@ class SinglePageController extends Controller
return view('landing')->with([
'appSettings' => Settings::all()->toJson(),
'appConfig' => collect([
'proxyAuth' => config("auth.defaults.guard") === 'reverse-proxy-guard' ? true : false,
'proxyLogoutUrl' => config("2fauth.config.proxyLogoutUrl") ? config("2fauth.config.proxyLogoutUrl") : false,
'appConfig' => collect([
'proxyAuth' => config('auth.defaults.guard') === 'reverse-proxy-guard' ? true : false,
'proxyLogoutUrl' => config('2fauth.config.proxyLogoutUrl') ? config('2fauth.config.proxyLogoutUrl') : false,
])->toJson(),
'lang' => App::currentLocale(),
'isDemoApp' => config("2fauth.config.isDemoApp") ? 'true' : 'false',
'isTestingApp' => config("2fauth.config.isTestingApp") ? 'true' : 'false',
'locales' => collect(config("2fauth.locales"))->toJson() /** @phpstan-ignore-line */
'lang' => App::currentLocale(),
'isDemoApp' => config('2fauth.config.isDemoApp') ? 'true' : 'false',
'isTestingApp' => config('2fauth.config.isTestingApp') ? 'true' : 'false',
'locales' => collect(config('2fauth.locales'))->toJson(), /** @phpstan-ignore-line */
]);
}
}

View File

@ -2,9 +2,8 @@
namespace App\Http\Controllers;
use App\Services\ReleaseRadarService;
use App\Http\Controllers\Controller;
use App\Facades\Settings;
use App\Services\ReleaseRadarService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@ -17,30 +16,30 @@ class SystemController extends Controller
*/
public function infos(Request $request)
{
$infos = array();
$infos['Date'] = date(DATE_RFC2822);
$infos['userAgent'] = $request->header('user-agent');
$infos = [];
$infos['Date'] = date(DATE_RFC2822);
$infos['userAgent'] = $request->header('user-agent');
// App info
$infos['Version'] = config('2fauth.version');
$infos['Environment'] = config('app.env');
$infos['Debug'] = var_export(config('app.debug'), true);
$infos['Cache driver'] = config('cache.default');
$infos['Log channel'] = config('logging.default');
$infos['Log level'] = env('LOG_LEVEL');
$infos['DB driver'] = DB::getDriverName();
$infos['Version'] = config('2fauth.version');
$infos['Environment'] = config('app.env');
$infos['Debug'] = var_export(config('app.debug'), true);
$infos['Cache driver'] = config('cache.default');
$infos['Log channel'] = config('logging.default');
$infos['Log level'] = env('LOG_LEVEL');
$infos['DB driver'] = DB::getDriverName();
// PHP info
$infos['PHP version'] = PHP_VERSION;
$infos['Operating system'] = PHP_OS;
$infos['interface'] = PHP_SAPI;
$infos['PHP version'] = PHP_VERSION;
$infos['Operating system'] = PHP_OS;
$infos['interface'] = PHP_SAPI;
// Auth info
if ($request->user()) {
$infos['Auth guard'] = config('auth.defaults.guard');
$infos['Auth guard'] = config('auth.defaults.guard');
if ($infos['Auth guard'] === 'reverse-proxy-guard') {
$infos['Auth proxy header for user'] = config('auth.auth_proxy_headers.user');
$infos['Auth proxy header for user'] = config('auth.auth_proxy_headers.user');
$infos['Auth proxy header for email'] = config('auth.auth_proxy_headers.email');
}
$infos['webauthn user verification'] = config('larapass.login_verify');
$infos['Trusted proxies'] = config('2fauth.trustedProxies') ?: 'none';
$infos['Trusted proxies'] = config('2fauth.trustedProxies') ?: 'none';
}
// User info
if ($request->user()) {
@ -50,7 +49,6 @@ class SystemController extends Controller
return response()->json($infos);
}
/**
* Get latest release
*

View File

@ -71,13 +71,13 @@ class Kernel extends HttpKernel
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'guest' => \App\Http\Middleware\RejectIfAuthenticated::class,
'SkipIfAuthenticated' => \App\Http\Middleware\SkipIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'rejectIfDemoMode' => \App\Http\Middleware\RejectIfDemoMode::class,
'auth' => \App\Http\Middleware\Authenticate::class,
'guest' => \App\Http\Middleware\RejectIfAuthenticated::class,
'SkipIfAuthenticated' => \App\Http\Middleware\SkipIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'rejectIfDemoMode' => \App\Http\Middleware\RejectIfDemoMode::class,
'rejectIfReverseProxy' => \App\Http\Middleware\RejectIfReverseProxy::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
// 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
// 'signed' => \App\Http\Middleware\ValidateSignature::class,
];

View File

@ -20,8 +20,7 @@ class Authenticate extends Middleware
if (empty($guards)) {
// Will retreive the default guard
$guards = [null];
}
else {
} else {
// We replace routes guard by the reverse proxy guard if necessary
$proxyGuard = 'reverse-proxy-guard';
@ -33,11 +32,11 @@ class Authenticate extends Middleware
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
$this->auth->shouldUse($guard);
return;
}
}
$this->unauthenticated($request, $guards);
}
}

View File

@ -6,7 +6,6 @@ use Laravel\Passport\Http\Middleware\CreateFreshApiToken as CreateFreshApiToken;
class CustomCreateFreshApiToken extends CreateFreshApiToken
{
/**
* Determine if the request should receive a fresh token.
*
@ -15,6 +14,6 @@ class CustomCreateFreshApiToken extends CreateFreshApiToken
*/
protected function requestShouldReceiveFreshToken($request)
{
return !is_null($request->user($this->guard));
return ! is_null($request->user($this->guard));
}
}

View File

@ -2,12 +2,12 @@
namespace App\Http\Middleware;
use Closure;
use App\Facades\Settings;
use Carbon\Carbon;
use Closure;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use App\Facades\Settings;
class KickOutInactiveUser
{
@ -16,7 +16,7 @@ class KickOutInactiveUser
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $guards
* @param string $guards
* @return mixed
*/
public function handle($request, Closure $next, ...$guards)
@ -29,8 +29,8 @@ class KickOutInactiveUser
return $next($request);
}
$user = Auth::user();
$now = Carbon::now();
$user = Auth::user();
$now = Carbon::now();
$inactiveFor = $now->diffInSeconds(Carbon::parse($user->last_seen_at));
// Fetch all setting values
@ -38,7 +38,6 @@ class KickOutInactiveUser
// If user has been inactive longer than the allowed inactivity period
if ($kickUserAfterXSecond > 0 && $inactiveFor > $kickUserAfterXSecond) {
$user->last_seen_at = $now->format('Y-m-d H:i:s');
$user->save();

View File

@ -2,8 +2,8 @@
namespace App\Http\Middleware;
use Closure;
use Carbon\Carbon;
use Closure;
use Illuminate\Support\Facades\Auth;
class LogUserLastSeen
@ -13,7 +13,7 @@ class LogUserLastSeen
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $guards
* @param string $guards
* @return mixed
*/
public function handle($request, Closure $next, ...$guards)
@ -25,7 +25,7 @@ class LogUserLastSeen
// - Guest
// - User authenticated against a bearer token
// - User authenticated via a reverse-proxy
if (Auth::guard($guard)->check() && !$request->bearerToken() && config('auth.defaults.guard') !== 'reverse-proxy-guard') {
if (Auth::guard($guard)->check() && ! $request->bearerToken() && config('auth.defaults.guard') !== 'reverse-proxy-guard') {
Auth::guard($guard)->user()->last_seen_at = Carbon::now()->format('Y-m-d H:i:s');
Auth::guard($guard)->user()->save();
break;

View File

@ -22,7 +22,7 @@ class RejectIfAuthenticated
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return response()->json(['message' => __('auth.already_authenticated')], 400);
return response()->json(['message' => __('auth.already_authenticated')], 400);
}
}

View File

@ -17,8 +17,7 @@ class RejectIfDemoMode
*/
public function handle($request, Closure $next)
{
if( config('2fauth.config.isDemoApp') ) {
if (config('2fauth.config.isDemoApp')) {
Log::info('Cannot request this action in Demo mode');
return response()->json(['message' => __('auth.forms.disabled_in_demo')], Response::HTTP_UNAUTHORIZED);

View File

@ -20,7 +20,8 @@ class RejectIfReverseProxy
Log::info('Cannot request this action in Demo mode');
return response()->json([
'message' => __('errors.unsupported_with_reverseproxy')], 400);
'message' => __('errors.unsupported_with_reverseproxy'),
], 400);
}
return $next($request);

View File

@ -2,9 +2,9 @@
namespace App\Http\Middleware;
use App\Facades\Settings;
use Closure;
use Illuminate\Support\Facades\App;
use App\Facades\Settings;
class SetLanguage
{
@ -26,16 +26,17 @@ class SetLanguage
// FI: Settings::get() always returns a fallback value
$lang = Settings::get('lang');
if($lang === 'browser') {
$lang = config('app.fallback_locale');
$accepted = str_replace(' ', '', $request->header("Accept-Language"));
if ($lang === 'browser') {
$lang = config('app.fallback_locale');
$accepted = str_replace(' ', '', $request->header('Accept-Language'));
if ($accepted && $accepted !== '*') {
$prefLocales = array_reduce(
array_diff(explode(',', $accepted), ['*']),
function ($res, $el) {
list($l, $q) = array_merge(explode(';q=', $el), [1]);
[$l, $q] = array_merge(explode(';q=', $el), [1]);
$res[$l] = (float) $q;
return $res;
},
[]

View File

@ -26,7 +26,7 @@ class SkipIfAuthenticated
return response()->json([
'message' => 'authenticated',
'name' => $user
'name' => $user,
], 200);
}
}

View File

@ -19,8 +19,7 @@ class TrustProxies extends Middleware
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
protected $headers = Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |

View File

@ -2,10 +2,7 @@
namespace App\Http\Requests;
use Illuminate\Support\Facades\DB;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class LoginRequest extends FormRequest
{
@ -30,7 +27,7 @@ class LoginRequest extends FormRequest
'email' => [
'required',
'email',
new \App\Rules\CaseInsensitiveEmailExists
new \App\Rules\CaseInsensitiveEmailExists,
],
'password' => 'required|string',
];

View File

@ -5,7 +5,6 @@ namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class UserDeleteRequest extends FormRequest
{
/**

View File

@ -26,7 +26,7 @@ class UserPatchPwdRequest extends FormRequest
{
return [
'currentPassword' => 'required',
'password' => 'required|confirmed|string|min:8',
'password' => 'required|confirmed|string|min:8',
];
}
}

View File

@ -24,9 +24,9 @@ class UserStoreRequest extends FormRequest
public function rules()
{
return [
'name' => [new \App\Rules\FirstUser, 'required', 'string', 'max:255'],
'email' => 'required|string|email|max:255',
'password' => 'required|string|min:8|confirmed',
'name' => [new \App\Rules\FirstUser, 'required', 'string', 'max:255'],
'email' => 'required|string|email|max:255',
'password' => 'required|string|min:8|confirmed',
];
}
}

View File

@ -25,8 +25,8 @@ class UserUpdateRequest extends FormRequest
public function rules()
{
return [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255',
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255',
'password' => 'required',
];
}

View File

@ -3,7 +3,6 @@
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class WebauthnDeviceLostRequest extends FormRequest
{
@ -28,7 +27,7 @@ class WebauthnDeviceLostRequest extends FormRequest
'email' => [
'required',
'email',
new \App\Rules\CaseInsensitiveEmailExists
new \App\Rules\CaseInsensitiveEmailExists,
],
];
}

View File

@ -3,7 +3,6 @@
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class WebauthnRecoveryRequest extends FormRequest
{
@ -25,8 +24,8 @@ class WebauthnRecoveryRequest extends FormRequest
public function rules()
{
return [
'token' => 'required',
'email' => 'required|email',
'token' => 'required',
'email' => 'required|email',
'password' => 'required',
];
}

View File

@ -2,8 +2,8 @@
namespace App\Listeners;
use App\Models\TwoFAccount;
use App\Events\GroupDeleting;
use App\Models\TwoFAccount;
use Illuminate\Support\Facades\Log;
class DissociateTwofaccountFromGroup
@ -28,7 +28,7 @@ class DissociateTwofaccountFromGroup
{
TwoFAccount::where('group_id', $event->group->id)
->update(
['group_id' => NULL]
['group_id' => null]
);
Log::info(sprintf('TwoFAccounts dissociated from group #%d', $event->group->id));

View File

@ -4,22 +4,19 @@ namespace App\Listeners;
use App\Events\ScanForNewReleaseCalled;
use App\Services\ReleaseRadarService;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
class ReleaseRadar
{
/**
* @var ReleaseRadarService $releaseRadar
* @var ReleaseRadarService
*/
protected $releaseRadar;
/**
* Create the event listener.
*
* @param \App\Services\ReleaseRadarService $releaseRadar
*
* @return void
*/
public function __construct(ReleaseRadarService $releaseRadar)
@ -27,7 +24,6 @@ class ReleaseRadar
$this->releaseRadar = $releaseRadar;
}
/**
* Handle the event.
*

View File

@ -3,16 +3,15 @@
namespace App\Models;
use App\Events\GroupDeleting;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* @property int $twofaccounts_count
*/
class Group extends Model
{
use HasFactory;
/**
@ -22,7 +21,6 @@ class Group extends Model
*/
protected $fillable = ['name'];
/**
* The accessors to append to the model's array form.
*
@ -30,7 +28,6 @@ class Group extends Model
*/
protected $appends = [];
/**
* The attributes that should be hidden for arrays.
*
@ -38,7 +35,6 @@ class Group extends Model
*/
protected $hidden = ['created_at', 'updated_at'];
/**
* The attributes that should be cast.
*
@ -48,7 +44,6 @@ class Group extends Model
'twofaccounts_count' => 'integer',
];
/**
* The event map for the model.
*
@ -58,7 +53,6 @@ class Group extends Model
'deleting' => GroupDeleting::class,
];
/**
* Override The "booting" method of the model
*
@ -75,7 +69,6 @@ class Group extends Model
});
}
/**
* Get the TwoFAccounts of the group.
*

View File

@ -4,7 +4,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Option extends Model
{
/**
@ -17,7 +16,6 @@ class Option extends Model
'value',
];
/**
* Indicates if the model should be timestamped.
*
@ -25,12 +23,10 @@ class Option extends Model
*/
public $timestamps = false;
/**
* Casts.
*
* @var array<string, string>
*/
protected $casts = [];
}

View File

@ -2,9 +2,9 @@
namespace App\Models\Traits;
use Illuminate\Support\Str;
use App\Notifications\WebauthnRecoveryNotification;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
/**
* @see \App\Models\WebAuthnAuthenticatable
@ -17,7 +17,7 @@ trait WebAuthnManageCredentials
*
* @return string
*/
public function userHandle(): string
public function userHandle() : string
{
// Laragear\WebAuthn uses Ramsey\Uuid\Uuid::fromString()->getHex()->toString()
// to obtain a UUID v4 with dashes removed and uses it as user_id (aka userHandle)
@ -28,27 +28,25 @@ trait WebAuthnManageCredentials
?? str_replace('-', '', Str::uuid()->toString());
}
/**
* Saves a new alias for a given WebAuthn credential.
*
* @param string $id
* @param string $alias
* @param string $id
* @param string $alias
* @return bool
*/
public function renameCredential(string $id, string $alias): bool
public function renameCredential(string $id, string $alias) : bool
{
return boolval($this->webAuthnCredentials()->whereKey($id)->update(['alias' => $alias]));
}
/**
* Removes one or more credentials previously registered.
*
* @param string|array $id
* @return void
*/
public function flushCredential($id): void
public function flushCredential($id) : void
{
if (! $this->relationLoaded('webAuthnCredentials')) {
$this->webAuthnCredentials()->whereKey($id)->delete();
@ -63,15 +61,13 @@ trait WebAuthnManageCredentials
}
}
/**
* Sends a webauthn recovery email to the user.
*
* @param string $token
*
* @return void
*/
public function sendWebauthnRecoveryNotification(string $token): void
public function sendWebauthnRecoveryNotification(string $token) : void
{
// $accountRecoveryNotification = new WebauthnRecoveryNotification($token);
// $accountRecoveryNotification->toMailUsing(null);
@ -92,6 +88,5 @@ trait WebAuthnManageCredentials
// });
$this->notify(new WebauthnRecoveryNotification($token));
}
}

View File

@ -2,55 +2,62 @@
namespace App\Models;
use Exception;
use App\Services\LogoService;
use App\Facades\Settings;
use App\Models\Dto\TotpDto;
use App\Models\Dto\HotpDto;
use App\Events\TwoFAccountDeleted;
use App\Exceptions\InvalidSecretException;
use App\Exceptions\InvalidOtpParameterException;
use App\Exceptions\UnsupportedOtpTypeException;
use App\Exceptions\InvalidSecretException;
use App\Exceptions\UndecipherableException;
use Illuminate\Validation\ValidationException;
use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
use OTPHP\TOTP;
use OTPHP\HOTP;
use OTPHP\Factory;
use SteamTotp\SteamTotp;
use App\Exceptions\UnsupportedOtpTypeException;
use App\Facades\Settings;
use App\Helpers\Helpers;
use App\Models\Dto\HotpDto;
use App\Models\Dto\TotpDto;
use App\Services\LogoService;
use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Validation\ValidationException;
use OTPHP\Factory;
use OTPHP\HOTP;
use OTPHP\TOTP;
use ParagonIE\ConstantTime\Base32;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Http;
use App\Helpers\Helpers;
use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
use SteamTotp\SteamTotp;
class TwoFAccount extends Model implements Sortable
{
use SortableTrait, HasFactory;
const TOTP = 'totp';
const HOTP = 'hotp';
const TOTP = 'totp';
const HOTP = 'hotp';
const STEAM_TOTP = 'steamtotp';
const SHA1 = 'sha1';
const MD5 = 'md5';
const SHA256 = 'sha256';
const SHA512 = 'sha512';
const SHA1 = 'sha1';
const MD5 = 'md5';
const SHA256 = 'sha256';
const SHA512 = 'sha512';
const DEFAULT_PERIOD = 30;
const DEFAULT_COUNTER = 0;
const DEFAULT_DIGITS = 6;
const DEFAULT_ALGORITHM = self::SHA1;
const DUPLICATE_ID = -1;
const FAKE_ID = -2;
private const IMAGELINK_STORAGE_PATH = 'imagesLink/';
@ -80,7 +87,6 @@ class TwoFAccount extends Model implements Sortable
// 'icon'
];
/**
* The table associated with the model.
*
@ -88,7 +94,6 @@ class TwoFAccount extends Model implements Sortable
*/
protected $table = 'twofaccounts';
/**
* The accessors to append to the model's array form.
*
@ -96,18 +101,16 @@ class TwoFAccount extends Model implements Sortable
*/
public $appends = [];
/**
* The model's default values for attributes.
*
* @var array
*/
* The model's default values for attributes.
*
* @var array
*/
protected $attributes = [
'digits' => 6,
'digits' => 6,
'algorithm' => self::SHA1,
];
/**
* The attributes that should be hidden for arrays.
*
@ -115,7 +118,6 @@ class TwoFAccount extends Model implements Sortable
*/
protected $hidden = [];
/**
* The attributes that should be cast.
*
@ -123,7 +125,6 @@ class TwoFAccount extends Model implements Sortable
*/
protected $casts = [];
/**
* The event map for the model.
*
@ -133,7 +134,6 @@ class TwoFAccount extends Model implements Sortable
'deleted' => TwoFAccountDeleted::class,
];
/**
* Override The "booting" method of the model
*
@ -144,9 +144,15 @@ class TwoFAccount extends Model implements Sortable
parent::boot();
static::saving(function (TwoFAccount $twofaccount) {
if (!$twofaccount->legacy_uri) $twofaccount->legacy_uri = $twofaccount->getURI();
if ($twofaccount->otp_type == TwoFAccount::TOTP && !$twofaccount->period) $twofaccount->period = TwoFAccount::DEFAULT_PERIOD;
if ($twofaccount->otp_type == TwoFAccount::HOTP && !$twofaccount->counter) $twofaccount->counter = TwoFAccount::DEFAULT_COUNTER;
if (! $twofaccount->legacy_uri) {
$twofaccount->legacy_uri = $twofaccount->getURI();
}
if ($twofaccount->otp_type == TwoFAccount::TOTP && ! $twofaccount->period) {
$twofaccount->period = TwoFAccount::DEFAULT_PERIOD;
}
if ($twofaccount->otp_type == TwoFAccount::HOTP && ! $twofaccount->counter) {
$twofaccount->counter = TwoFAccount::DEFAULT_COUNTER;
}
});
// static::deleted(function ($model) {
@ -154,18 +160,16 @@ class TwoFAccount extends Model implements Sortable
// });
}
/**
* Settings for @spatie/eloquent-sortable package
*
* @var array
*/
public $sortable = [
'order_column_name' => 'order_column',
'order_column_name' => 'order_column',
'sort_when_creating' => true,
];
/**
* The OTP generator.
* Instanciated as null to keep the model light
@ -174,7 +178,6 @@ class TwoFAccount extends Model implements Sortable
*/
protected $generator = null;
/**
* Get legacy_uri attribute
*
@ -183,9 +186,9 @@ class TwoFAccount extends Model implements Sortable
*/
public function getLegacyUriAttribute($value)
{
return $this->decryptOrReturn($value);
}
/**
* Set legacy_uri attribute
*
@ -198,7 +201,6 @@ class TwoFAccount extends Model implements Sortable
$this->attributes['legacy_uri'] = $this->encryptOrReturn($value);
}
/**
* Get account attribute
*
@ -207,13 +209,13 @@ class TwoFAccount extends Model implements Sortable
*/
public function getAccountAttribute($value)
{
return $this->decryptOrReturn($value);
}
/**
* Set account attribute
*
* @param string $value
* @param string $value
* @return void
*/
public function setAccountAttribute($value)
@ -222,7 +224,6 @@ class TwoFAccount extends Model implements Sortable
$this->attributes['account'] = $this->encryptOrReturn($value);
}
/**
* Get secret attribute
*
@ -231,13 +232,13 @@ class TwoFAccount extends Model implements Sortable
*/
public function getSecretAttribute($value)
{
return $this->decryptOrReturn($value);
}
/**
* Set secret attribute
*
* @param string $value
* @param string $value
* @return void
*/
public function setSecretAttribute($value)
@ -246,47 +247,43 @@ class TwoFAccount extends Model implements Sortable
$this->attributes['secret'] = $this->encryptOrReturn($value);
}
/**
* Set digits attribute
*
* @param string $value
* @param string $value
* @return void
*/
public function setDigitsAttribute($value)
{
$this->attributes['digits'] = !$value ? 6 : $value;
$this->attributes['digits'] = ! $value ? 6 : $value;
}
/**
* Set algorithm attribute
*
* @param string $value
* @param string $value
* @return void
*/
public function setAlgorithmAttribute($value)
{
$this->attributes['algorithm'] = !$value ? self::SHA1 : strtolower($value);
$this->attributes['algorithm'] = ! $value ? self::SHA1 : strtolower($value);
}
/**
* Set period attribute
*
* @param string $value
* @param string $value
* @return void
*/
public function setPeriodAttribute($value)
{
$this->attributes['period'] = !$value && $this->otp_type === self::TOTP ? self::DEFAULT_PERIOD : $value;
$this->attributes['period'] = ! $value && $this->otp_type === self::TOTP ? self::DEFAULT_PERIOD : $value;
}
/**
* Set counter attribute
*
* @param string $value
* @param string $value
* @return void
*/
public function setCounterAttribute($value)
@ -294,19 +291,19 @@ class TwoFAccount extends Model implements Sortable
$this->attributes['counter'] = blank($value) && $this->otp_type === self::HOTP ? self::DEFAULT_COUNTER : $value;
}
/**
* Returns a One-Time Password with its parameters
*
* @return TotpDto|HotpDto
*
* @throws InvalidSecretException The secret is not a valid base32 encoded string
* @throws UndecipherableException The secret cannot be deciphered
* @throws UnsupportedOtpTypeException The defined OTP type is not supported
* @throws InvalidOtpParameterException One OTP parameter is invalid
* @return TotpDto|HotpDto
*/
public function getOTP()
{
Log::info(sprintf('OTP requested for TwoFAccount (%s)', $this->id ? 'id:'.$this->id: 'preview'));
Log::info(sprintf('OTP requested for TwoFAccount (%s)', $this->id ? 'id:' . $this->id : 'preview'));
// Early exit if the model has an undecipherable secret
if (strtolower($this->secret) === __('errors.indecipherable')) {
@ -318,36 +315,31 @@ class TwoFAccount extends Model implements Sortable
$this->initGenerator();
try {
if ( $this->otp_type === self::HOTP ) {
$OtpDto = new HotpDto();
$OtpDto->otp_type = $this->otp_type;
$counter = $this->generator->getParameter('counter');
$OtpDto->password = $this->generator->at($counter);
$OtpDto->counter = $this->counter = $counter + 1;
if ($this->otp_type === self::HOTP) {
$OtpDto = new HotpDto();
$OtpDto->otp_type = $this->otp_type;
$counter = $this->generator->getParameter('counter');
$OtpDto->password = $this->generator->at($counter);
$OtpDto->counter = $this->counter = $counter + 1;
// The updated HOTP counter must be saved to db for persisted account only
if ($this->id) {
$this->save();
}
}
else {
$OtpDto = new TotpDto();
$OtpDto->otp_type = $this->otp_type;
$OtpDto->generated_at = time();
$OtpDto->password = $this->otp_type === self::TOTP
} else {
$OtpDto = new TotpDto();
$OtpDto->otp_type = $this->otp_type;
$OtpDto->generated_at = time();
$OtpDto->password = $this->otp_type === self::TOTP
? $this->generator->at($OtpDto->generated_at)
: SteamTotp::getAuthCode(base64_encode(Base32::decodeUpper($this->secret)));
$OtpDto->period = $this->period;
$OtpDto->period = $this->period;
}
Log::info(sprintf('New OTP generated for TwoFAccount (%s)', $this->id ? 'id:'.$this->id: 'preview'));
Log::info(sprintf('New OTP generated for TwoFAccount (%s)', $this->id ? 'id:' . $this->id : 'preview'));
return $OtpDto;
}
catch (\Exception|\Throwable $ex) {
} catch (\Exception|\Throwable $ex) {
Log::error('An error occured, OTP generation aborted');
// Currently a secret issue is the only possible exception thrown by OTPHP for this stack
// so it is Ok to send the corresponding 2FAuth exception.
@ -356,7 +348,6 @@ class TwoFAccount extends Model implements Sortable
}
}
/**
* Fill the model using an array of OTP parameters.
* Missing parameters will be set with default values
@ -365,15 +356,15 @@ class TwoFAccount extends Model implements Sortable
*/
public function fillWithOtpParameters(array $parameters, bool $skipIconFetching = false)
{
$this->otp_type = strtolower(Arr::get($parameters, 'otp_type'));
$this->account = Arr::get($parameters, 'account');
$this->service = Arr::get($parameters, 'service');
$this->icon = Arr::get($parameters, 'icon');
$this->secret = Arr::get($parameters, 'secret');
$this->algorithm = strtolower(Arr::get($parameters, 'algorithm', self::SHA1));
$this->digits = Arr::get($parameters, 'digits', self::DEFAULT_DIGITS);
$this->period = Arr::get($parameters, 'period', $this->otp_type == self::TOTP ? self::DEFAULT_PERIOD : null);
$this->counter = Arr::get($parameters, 'counter', $this->otp_type == self::HOTP ? self::DEFAULT_COUNTER : null);
$this->otp_type = strtolower(Arr::get($parameters, 'otp_type'));
$this->account = Arr::get($parameters, 'account');
$this->service = Arr::get($parameters, 'service');
$this->icon = Arr::get($parameters, 'icon');
$this->secret = Arr::get($parameters, 'secret');
$this->algorithm = strtolower(Arr::get($parameters, 'algorithm', self::SHA1));
$this->digits = Arr::get($parameters, 'digits', self::DEFAULT_DIGITS);
$this->period = Arr::get($parameters, 'period', $this->otp_type == self::TOTP ? self::DEFAULT_PERIOD : null);
$this->counter = Arr::get($parameters, 'counter', $this->otp_type == self::HOTP ? self::DEFAULT_COUNTER : null);
$this->initGenerator();
@ -385,11 +376,11 @@ class TwoFAccount extends Model implements Sortable
$this->enforceAsSteam();
}
if (!$this->icon && $skipIconFetching) {
if (! $this->icon && $skipIconFetching) {
$this->icon = $this->getDefaultIcon();
}
if (!$this->icon && Settings::get('getOfficialIcons') && !$skipIconFetching) {
if (! $this->icon && Settings::get('getOfficialIcons') && ! $skipIconFetching) {
$this->icon = $this->getDefaultIcon();
}
@ -398,7 +389,6 @@ class TwoFAccount extends Model implements Sortable
return $this;
}
/**
* Fill the model by parsing an otpauth URI
*
@ -409,32 +399,31 @@ class TwoFAccount extends Model implements Sortable
// First we instanciate the OTP generator
try {
$this->generator = Factory::loadFromProvisioningUri($uri);
}
catch (\Assert\AssertionFailedException|\Assert\InvalidArgumentException|\Exception|\Throwable $ex) {
} catch (\Assert\AssertionFailedException|\Assert\InvalidArgumentException|\Exception|\Throwable $ex) {
throw ValidationException::withMessages([
'uri' => __('validation.custom.uri.regex', ['attribute' => 'uri'])
'uri' => __('validation.custom.uri.regex', ['attribute' => 'uri']),
]);
}
// As loadFromProvisioningUri() accept URI without label (nor account nor service) we check
// that the account is set
if ( ! $this->generator->getLabel() ) {
if (! $this->generator->getLabel()) {
Log::error('URI passed to fillWithURI() must contain a label');
throw ValidationException::withMessages([
'label' => __('validation.custom.label.required')
'label' => __('validation.custom.label.required'),
]);
}
$this->otp_type = $this->getGeneratorOtpType();
$this->account = $this->generator->getLabel();
$this->secret = $this->generator->getSecret();
$this->service = $this->generator->getIssuer();
$this->algorithm = $this->generator->getDigest();
$this->digits = $this->generator->getDigits();
$this->period = $this->generator->hasParameter('period') ? $this->generator->getParameter('period') : null;
$this->counter = $this->generator->hasParameter('counter') ? $this->generator->getParameter('counter') : null;
$this->legacy_uri = $uri;
$this->otp_type = $this->getGeneratorOtpType();
$this->account = $this->generator->getLabel();
$this->secret = $this->generator->getSecret();
$this->service = $this->generator->getIssuer();
$this->algorithm = $this->generator->getDigest();
$this->digits = $this->generator->getDigits();
$this->period = $this->generator->hasParameter('period') ? $this->generator->getParameter('period') : null;
$this->counter = $this->generator->hasParameter('counter') ? $this->generator->getParameter('counter') : null;
$this->legacy_uri = $uri;
if ($isSteamTotp || strtolower($this->service) === 'steam') {
$this->enforceAsSteam();
@ -443,7 +432,7 @@ class TwoFAccount extends Model implements Sortable
$this->icon = $this->storeImageAsIcon($this->generator->getParameter('image'));
}
if (!$this->icon && Settings::get('getOfficialIcons') && !$skipIconFetching) {
if (! $this->icon && Settings::get('getOfficialIcons') && ! $skipIconFetching) {
$this->icon = $this->getDefaultIcon();
}
@ -452,7 +441,6 @@ class TwoFAccount extends Model implements Sortable
return $this;
}
/**
* Sets model attributes to STEAM values
*/
@ -466,7 +454,6 @@ class TwoFAccount extends Model implements Sortable
Log::info(sprintf('TwoFAccount configured as Steam account'));
}
/**
* Returns the OTP type of the instanciated OTP generator
*
@ -477,7 +464,6 @@ class TwoFAccount extends Model implements Sortable
return Arr::get($this->generatorClassMap, get_class($this->generator));
}
/**
* Returns an otpauth URI built with model attribute values
*/
@ -488,9 +474,9 @@ class TwoFAccount extends Model implements Sortable
return $this->generator->getProvisioningUri();
}
/**
* Instanciates the OTP generator with model attribute values
*
* @throws UnsupportedOtpTypeException The defined OTP type is not supported
* @throws InvalidOtpParameterException One OTP parameter is invalid
*/
@ -524,14 +510,16 @@ class TwoFAccount extends Model implements Sortable
throw new UnsupportedOtpTypeException();
}
if ($this->service) $this->generator->setIssuer($this->service);
if ($this->account) $this->generator->setLabel($this->account);
}
catch (UnsupportedOtpTypeException $exception) {
if ($this->service) {
$this->generator->setIssuer($this->service);
}
if ($this->account) {
$this->generator->setLabel($this->account);
}
} catch (UnsupportedOtpTypeException $exception) {
Log::error(sprintf('%s is not an OTP type supported by the current generator', $this->otp_type));
throw $exception;
}
catch (\Exception|\Throwable $exception) {
} catch (\Exception|\Throwable $exception) {
throw new InvalidOtpParameterException($exception->getMessage());
}
}
@ -544,9 +532,9 @@ class TwoFAccount extends Model implements Sortable
private function storeImageAsIcon(string $url)
{
try {
$path_parts = pathinfo($url);
$newFilename = Helpers::getUniqueFilename($path_parts['extension']); //Str::random(40).'.'.$path_parts['extension'];
$imageFile = self::IMAGELINK_STORAGE_PATH . $newFilename;
$path_parts = pathinfo($url);
$newFilename = Helpers::getUniqueFilename($path_parts['extension']);
$imageFile = self::IMAGELINK_STORAGE_PATH . $newFilename;
try {
$response = Http::retry(3, 100)->get($url);
@ -554,22 +542,19 @@ class TwoFAccount extends Model implements Sortable
if ($response->successful()) {
Storage::disk('imagesLink')->put($newFilename, $response->body());
}
}
catch (\Exception $exception) {
} catch (\Exception $exception) {
Log::error(sprintf('Cannot fetch imageLink at "%s"', $url));
}
if ( in_array(Storage::mimeType($imageFile), ['image/png', 'image/jpeg', 'image/webp', 'image/bmp'])
&& getimagesize(storage_path() . '/app/' . $imageFile) )
{
if (in_array(Storage::mimeType($imageFile), ['image/png', 'image/jpeg', 'image/webp', 'image/bmp'])
&& getimagesize(storage_path() . '/app/' . $imageFile)) {
// Should be a valid image, we move it to the icons disk
if (Storage::disk('icons')->put($newFilename, Storage::disk('imagesLink')->get($newFilename))) {
Storage::disk('imagesLink')->delete($newFilename);
}
Log::info(sprintf('Icon file %s stored', $newFilename));
}
else {
} else {
// @codeCoverageIgnoreStart
Storage::disk('imagesLink')->delete($newFilename);
throw new \Exception('Unsupported mimeType or missing image on storage');
@ -581,12 +566,12 @@ class TwoFAccount extends Model implements Sortable
// @codeCoverageIgnoreStart
catch (\Exception|\Throwable $ex) {
Log::error(sprintf('Icon storage failed: %s', $ex->getMessage()));
return null;
}
// @codeCoverageIgnoreEnd
}
/**
* Fetch a logo in the tfa directory and store it as a new stand alone icon
*
@ -599,28 +584,23 @@ class TwoFAccount extends Model implements Sortable
return Settings::get('getOfficialIcons') ? $logoService->getIcon($this->service) : null;
}
/**
* Returns an acceptable value
*/
private function decryptOrReturn(mixed $value) : mixed
{
// Decipher when needed
if ( Settings::get('useEncryption') && $value )
{
if (Settings::get('useEncryption') && $value) {
try {
return Crypt::decryptString($value);
}
catch (Exception $ex) {
} catch (Exception $ex) {
return __('errors.indecipherable');
}
}
else {
} else {
return $value;
}
}
/**
* Encrypt a value
*/
@ -629,5 +609,4 @@ class TwoFAccount extends Model implements Sortable
// should be replaced by laravel 8 attribute encryption casting
return Settings::get('useEncryption') ? Crypt::encryptString($value) : $value;
}
}

View File

@ -2,14 +2,14 @@
namespace App\Models;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
use Illuminate\Support\Facades\Log;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Laragear\WebAuthn\WebAuthnAuthentication;
use App\Models\Traits\WebAuthnManageCredentials;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Log;
use Laragear\WebAuthn\WebAuthnAuthentication;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements WebAuthnAuthenticatable
{
@ -59,21 +59,21 @@ class User extends Authenticatable implements WebAuthnAuthenticatable
/**
* set Email attribute
* @param string $value
*
* @param string $value
*/
public function setEmailAttribute($value) : void
{
$this->attributes['email'] = strtolower($value);
}
/**
* Returns an WebAuthnAuthenticatable user from a given Credential ID.
*
* @param string $id
* @return WebAuthnAuthenticatable|null
*/
public static function getFromCredentialId(string $id): ?WebAuthnAuthenticatable
public static function getFromCredentialId(string $id) : ?WebAuthnAuthenticatable
{
return static::whereHas(
'webauthnCredentials',

View File

@ -11,18 +11,16 @@ interface WebAuthnAuthenticatable extends Authenticatable
*
* @return string
*/
public function userHandle(): string;
public function userHandle() : string;
/**
* Saves a new alias for a given WebAuthn credential.
*
* @param string $id
* @param string $alias
* @param string $id
* @param string $alias
* @return bool
*/
public function renameCredential(string $id, string $alias): bool;
public function renameCredential(string $id, string $alias) : bool;
/**
* Removes one or more credentials previously registered.
@ -30,8 +28,7 @@ interface WebAuthnAuthenticatable extends Authenticatable
* @param string|array $id
* @return void
*/
public function flushCredential($id): void;
public function flushCredential($id) : void;
/**
* Sends a webauthn recovery email to the user.
@ -39,5 +36,5 @@ interface WebAuthnAuthenticatable extends Authenticatable
* @param string $token
* @return void
*/
public function sendWebauthnRecoveryNotification(string $token): void;
public function sendWebauthnRecoveryNotification(string $token) : void;
}

View File

@ -66,16 +66,16 @@ class WebauthnRecoveryNotification extends Notification
// if (static::$createUrlCallback) {
// $url = call_user_func(static::$createUrlCallback, $notifiable, $this->token);
// } else {
$url = url(
route(
'webauthn.recover',
[
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
],
false
)
);
$url = url(
route(
'webauthn.recover',
[
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
],
false
)
);
// }
return (new MailMessage)

View File

@ -2,15 +2,14 @@
namespace App\Providers;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\JsonResource;
use Laravel\Passport\Console\ClientCommand;
use Laravel\Passport\Console\InstallCommand;
use Laravel\Passport\Console\KeysCommand;
class AppServiceProvider extends ServiceProvider
{
/**

View File

@ -2,16 +2,15 @@
namespace App\Providers;
use App\Extensions\RemoteUserProvider;
use App\Extensions\WebauthnCredentialBroker;
use App\Facades\Settings;
use App\Services\Auth\ReverseProxyGuard;
use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
use App\Services\Auth\ReverseProxyGuard;
use App\Extensions\RemoteUserProvider;
use App\Facades\Settings;
use Illuminate\Support\Facades\Config;
use RuntimeException;
use App\Extensions\WebauthnCredentialBroker;
use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Support\Str;
use RuntimeException;
class AuthServiceProvider extends ServiceProvider
{
@ -24,20 +23,19 @@ class AuthServiceProvider extends ServiceProvider
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register the service provider.
*
* @return void
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function register(): void
public function register() : void
{
$this->app->singleton(
WebauthnCredentialBroker::class,
static function ($app) {
if (!$config = $app['config']['auth.passwords.webauthn']) {
if (! $config = $app['config']['auth.passwords.webauthn']) {
throw new RuntimeException('You must set the [webauthn] key broker in [auth] config.');
}
@ -62,7 +60,6 @@ class AuthServiceProvider extends ServiceProvider
);
}
/**
* Register any authentication / authorization services.
*
@ -86,7 +83,6 @@ class AuthServiceProvider extends ServiceProvider
return new ReverseProxyGuard(Auth::createUserProvider($config['provider']));
});
// Previously we were using a custom user provider derived from the Larapass user provider
// in order to honor the "useWebauthnOnly" user option.
// Since Laragear\WebAuthn now replaces DarkGhostHunter\Larapass, the new approach is
@ -94,7 +90,7 @@ class AuthServiceProvider extends ServiceProvider
// with a custom closure that uses the "useWebauthnOnly" user option
Auth::provider(
'eloquent-webauthn',
static function (\Illuminate\Contracts\Foundation\Application $app, array $config): \Laragear\WebAuthn\Auth\WebAuthnUserProvider {
static function (\Illuminate\Contracts\Foundation\Application $app, array $config) : \Laragear\WebAuthn\Auth\WebAuthnUserProvider {
return new \Laragear\WebAuthn\Auth\WebAuthnUserProvider(
$app->make('hash'),
$config['model'],
@ -104,7 +100,6 @@ class AuthServiceProvider extends ServiceProvider
}
);
// Normally we should set the Passport routes here using Passport::routes().
// If so the passport routes would be set for both 'web' and 'api' middlewares without
// possibility to exclude the web middleware (we can only pass additional middlewares to Passport::routes())

View File

@ -2,8 +2,8 @@
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{

View File

@ -3,11 +3,11 @@
namespace App\Providers;
use App\Events\GroupDeleting;
use App\Events\TwoFAccountDeleted;
use App\Events\ScanForNewReleaseCalled;
use App\Listeners\ReleaseRadar;
use App\Events\TwoFAccountDeleted;
use App\Listeners\CleanIconStorage;
use App\Listeners\DissociateTwofaccountFromGroup;
use App\Listeners\ReleaseRadar;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

View File

@ -4,8 +4,8 @@ namespace App\Providers;
use App\Factories\MigratorFactory;
use App\Factories\MigratorFactoryInterface;
use App\Services\Migrators\GoogleAuthMigrator;
use App\Services\Migrators\AegisMigrator;
use App\Services\Migrators\GoogleAuthMigrator;
use App\Services\Migrators\PlainTextMigrator;
use App\Services\Migrators\TwoFASMigrator;
use Illuminate\Support\ServiceProvider;

View File

@ -2,13 +2,13 @@
namespace App\Providers;
use App\Services\LogoService;
use App\Services\SettingService;
use App\Services\ReleaseRadarService;
use App\Services\TwoFAccountService;
use App\Factories\MigratorFactoryInterface;
use Illuminate\Support\ServiceProvider;
use App\Services\LogoService;
use App\Services\ReleaseRadarService;
use App\Services\SettingService;
use App\Services\TwoFAccountService;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
class TwoFAuthServiceProvider extends ServiceProvider implements DeferrableProvider
{
@ -46,7 +46,6 @@ class TwoFAuthServiceProvider extends ServiceProvider implements DeferrableProvi
//
}
/**
* Get the services provided by the provider.
*

View File

@ -30,12 +30,14 @@ class CaseInsensitiveEmailExists implements Rule
->whereRaw('email = \'' . strtolower($value) . '\'' . ('sqlite' === config('database.default') ? ' COLLATE NOCASE' : ''))
->first();
return !$user ? false : true;
return ! $user ? false : true;
}
/**
* Get the validation error message.
*
* @codeCoverageIgnore
*
* @return array|string
*/
public function message()

View File

@ -5,9 +5,9 @@
namespace App\Services\Auth;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Support\Facades\Log;
class ReverseProxyGuard implements Guard
@ -24,7 +24,7 @@ class ReverseProxyGuard implements Guard
/**
* Create a new authentication guard.
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @return void
*/
public function __construct(UserProvider $provider)
@ -33,7 +33,7 @@ class ReverseProxyGuard implements Guard
}
/**
* @inheritDoc
* {@inheritDoc}
*/
public function user()
{
@ -47,17 +47,17 @@ class ReverseProxyGuard implements Guard
// Get the user identifier from $_SERVER or apache filtered headers
$remoteUserHeader = config('auth.auth_proxy_headers.user');
$remoteUserHeader = $remoteUserHeader ?: 'REMOTE_USER';
$identifier = array();
$identifier = [];
try {
$identifier['user'] = request()->server($remoteUserHeader) ?? apache_request_headers()[$remoteUserHeader] ?? null;
}
catch (\Throwable $e) {
} catch (\Throwable $e) {
$identifier['user'] = null;
}
if (!$identifier['user'] || is_array($identifier['user'])) {
if (! $identifier['user'] || is_array($identifier['user'])) {
Log::error(sprintf('Proxy remote-user header "%s" is empty or missing.', $remoteUserHeader));
return $this->user = null;
}
@ -66,9 +66,8 @@ class ReverseProxyGuard implements Guard
if ($remoteEmailHeader) {
try {
$remoteEmail = (string)(request()->server($remoteEmailHeader) ?? apache_request_headers()[$remoteEmailHeader] ?? null);
}
catch (\Throwable $e) {
$remoteEmail = (string) (request()->server($remoteEmailHeader) ?? apache_request_headers()[$remoteEmailHeader] ?? null);
} catch (\Throwable $e) {
$remoteEmail = null;
}

View File

@ -2,12 +2,11 @@
namespace App\Services;
use App\Facades\Settings;
use App\Models\Group;
use App\Models\TwoFAccount;
use App\Facades\Settings;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\App;
class GroupService
{
@ -29,20 +28,19 @@ class GroupService
// Create the pseudo group
$allGroup = new Group([
'name' => __('commons.all')
'name' => __('commons.all'),
]);
$allGroup->id = 0;
$allGroup->id = 0;
$allGroup->twofaccounts_count = TwoFAccount::count();
return $groups->prepend($allGroup);
}
/**
* Creates a group
*
* @param array $data
* @param array $data
* @return \App\Models\Group The created group
*/
public static function create(array $data) : Group
@ -58,12 +56,11 @@ class GroupService
return $group;
}
/**
* Updates a group using a list of parameters
*
* @param \App\Models\Group $group The group
* @param array $data The parameters
* @param \App\Models\Group $group The group
* @param array $data The parameters
* @return \App\Models\Group The updated group
*/
public static function update(Group $group, array $data) : Group
@ -77,11 +74,10 @@ class GroupService
return $group;
}
/**
* Deletes one or more groups
*
* @param int|array $ids group ids to delete
* @param int|array $ids group ids to delete
* @return int The number of deleted
*/
public static function delete($ids) : int
@ -112,25 +108,24 @@ class GroupService
return $deleted;
}
/**
* Assign one or more accounts to a group
*
* @param array|int $ids accounts ids to assign
* @param \App\Models\Group $group The target group
* @param array|int $ids accounts ids to assign
* @param \App\Models\Group $group The target group
* @return void
*/
public static function assign($ids, Group $group = null) : void
{
if (!$group) {
if (! $group) {
$group = self::defaultGroup();
}
if ($group) {
// saveMany() expect an iterable so we pass an array to
// find() to always obtain a list of TwoFAccount
if (!is_array($ids)) {
$ids = array($ids);
if (! is_array($ids)) {
$ids = [$ids];
}
$twofaccounts = TwoFAccount::find($ids);
@ -138,15 +133,15 @@ class GroupService
$group->loadCount('twofaccounts');
Log::info(sprintf('Twofaccounts #%s assigned to groups %s', implode(',#', $ids), var_export($group->name, true)));
} else {
Log::info('Cannot find a group to assign the TwoFAccounts to');
}
else Log::info('Cannot find a group to assign the TwoFAccounts to');
}
/**
* Finds twofaccounts assigned to the group
*
* @param \App\Models\Group $group The group
* @param \App\Models\Group $group The group
* @return Collection<int, TwoFAccount> The assigned accounts
*/
public static function getAccounts(Group $group) : Collection
@ -156,7 +151,6 @@ class GroupService
return $twofaccounts;
}
/**
* Determines the destination group
*

View File

@ -4,8 +4,8 @@ namespace App\Services;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class LogoService
@ -25,17 +25,15 @@ class LogoService
*/
const TFA_URL = 'https://2fa.directory/api/v3/tfa.json';
public function __construct()
{
$this->setTfaCollection();
}
/**
* Fetch a logo for the given service and save it as an icon
*
* @param string $serviceName Name of the service to fetch a logo for
* @param string $serviceName Name of the service to fetch a logo for
* @return string|null The icon filename or null if no logo has been found
*/
public function getIcon($serviceName)
@ -43,32 +41,32 @@ class LogoService
$logoFilename = $this->getLogo(strval($serviceName));
if ($logoFilename) {
$iconFilename = Str::random(40).'.svg';
return $this->copyToIcons($logoFilename, $iconFilename) ? $iconFilename : null;
}
else return null;
}
$iconFilename = Str::random(40) . '.svg';
return $this->copyToIcons($logoFilename, $iconFilename) ? $iconFilename : null;
} else {
return null;
}
}
/**
* Return the logo's filename for a given service
*
* @param string $serviceName Name of the service to fetch a logo for
* @param string $serviceName Name of the service to fetch a logo for
* @return string|null The logo filename or null if no logo has been found
*/
protected function getLogo($serviceName)
{
$domain = $this->tfas->get($this->cleanDomain(strval($serviceName)));
$logoFilename = $domain.'.svg';
$domain = $this->tfas->get($this->cleanDomain(strval($serviceName)));
$logoFilename = $domain . '.svg';
if ($domain && !Storage::disk('logos')->exists($logoFilename)) {
if ($domain && ! Storage::disk('logos')->exists($logoFilename)) {
$this->fetchLogo($logoFilename);
}
return Storage::disk('logos')->exists($logoFilename) ? $logoFilename : null;
}
/**
* Build and set the TFA directoy collection
*
@ -90,7 +88,6 @@ class LogoService
: collect([]);
}
/**
* Fetch and cache fresh TFA.Directory data using the https://2fa.directory API
*
@ -104,50 +101,44 @@ class LogoService
$coll = collect(json_decode(htmlspecialchars_decode($response->body()), true)) /** @phpstan-ignore-line */
->mapWithKeys(function ($item, $key) {
return [
strtolower(head($item)) => $item[1]["domain"]
strtolower(head($item)) => $item[1]['domain'],
];
});
Storage::disk('logos')->put(self::TFA_JSON, $coll->toJson())
? Log::info('Fresh tfa.json saved to logos dir')
: Log::notice('Cannot save tfa.json to logos dir');
}
catch (\Exception $e) {
} catch (\Exception $e) {
Log::error('Caching of tfa.json failed');
}
}
/**
* Fetch and cache a logo from 2fa.Directory repository
*
* @param string $logoFile Logo filename to fetch
* @param string $logoFile Logo filename to fetch
* @return void
*/
protected function fetchLogo(string $logoFile) : void
{
try {
$response = Http::retry(3, 100)
->get('https://raw.githubusercontent.com/2factorauth/twofactorauth/master/img/'.$logoFile[0].'/'.$logoFile);
->get('https://raw.githubusercontent.com/2factorauth/twofactorauth/master/img/' . $logoFile[0] . '/' . $logoFile);
if ($response->successful()) {
Storage::disk('logos')->put($logoFile, $response->body())
? Log::info(sprintf('Logo "%s" saved to logos dir.', $logoFile))
: Log::notice(sprintf('Cannot save logo "%s" to logos dir', $logoFile));
}
}
catch (\Exception $exception) {
} catch (\Exception $exception) {
Log::error(sprintf('Fetching of logo "%s" failed.', $logoFile));
}
}
/**
* Prepare and make some replacement to optimize logo fetching
*
* @param string $domain
* @param string $domain
* @return string Optimized domain name
*/
protected function cleanDomain(string $domain) : string
@ -155,12 +146,11 @@ class LogoService
return strtolower(str_replace(['+'], ['plus'], $domain));
}
/**
* Copy a logo file to the icons disk with a new name
*
* @param string $logoFilename
* @param string $iconFilename
* @param string $logoFilename
* @param string $iconFilename
* @return bool Weither the copy succed or not
*/
protected function copyToIcons($logoFilename, $iconFilename) : bool

View File

@ -2,15 +2,14 @@
namespace App\Services\Migrators;
use App\Services\Migrators\Migrator;
use Illuminate\Support\Collection;
use App\Models\TwoFAccount;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Arr;
use App\Exceptions\InvalidMigrationDataException;
use Illuminate\Support\Facades\Storage;
use App\Helpers\Helpers;
use App\Facades\TwoFAccounts;
use App\Helpers\Helpers;
use App\Models\TwoFAccount;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class AegisMigrator extends Migrator
{
@ -33,7 +32,6 @@ class AegisMigrator extends Migrator
// }
// }
/**
* Convert migration data to a TwoFAccounts collection.
*
@ -49,19 +47,18 @@ class AegisMigrator extends Migrator
throw new InvalidMigrationDataException('Aegis');
}
$twofaccounts = array();
$twofaccounts = [];
foreach ($json['db']['entries'] as $key => $otp_parameters) {
$parameters = array();
$parameters['otp_type'] = $otp_parameters['type'] == 'steam' ? TwoFAccount::STEAM_TOTP : $otp_parameters['type'];
$parameters['service'] = $otp_parameters['issuer'];
$parameters['account'] = $otp_parameters['name'];
$parameters['secret'] = $this->padToValidBase32Secret($otp_parameters['info']['secret']);
$parameters['algorithm'] = $otp_parameters['info']['algo'];
$parameters['digits'] = $otp_parameters['info']['digits'];
$parameters['counter'] = $otp_parameters['info']['counter'] ?? null;
$parameters['period'] = $otp_parameters['info']['period'] ?? null;
$parameters = [];
$parameters['otp_type'] = $otp_parameters['type'] == 'steam' ? TwoFAccount::STEAM_TOTP : $otp_parameters['type'];
$parameters['service'] = $otp_parameters['issuer'];
$parameters['account'] = $otp_parameters['name'];
$parameters['secret'] = $this->padToValidBase32Secret($otp_parameters['info']['secret']);
$parameters['algorithm'] = $otp_parameters['info']['algo'];
$parameters['digits'] = $otp_parameters['info']['digits'];
$parameters['counter'] = $otp_parameters['info']['counter'] ?? null;
$parameters['period'] = $otp_parameters['info']['period'] ?? null;
try {
// Aegis supports 3 image extensions for icons
@ -92,29 +89,26 @@ class AegisMigrator extends Migrator
Log::info(sprintf('Image %s successfully stored for import', $filename));
}
}
}
catch (\Exception) {
} catch (\Exception) {
// we do nothing
}
try {
$twofaccounts[$key] = new TwoFAccount;
$twofaccounts[$key]->fillWithOtpParameters($parameters);
}
catch (\Exception $exception) {
$twofaccounts[$key] = new TwoFAccount;
$twofaccounts[$key]->fillWithOtpParameters($parameters);
} catch (\Exception $exception) {
Log::error(sprintf('Cannot instanciate a TwoFAccount object with OTP parameters from imported item #%s', $key));
Log::error($exception->getMessage());
// The token failed to generate a valid account so we create a fake account to be returned.
$fakeAccount = new TwoFAccount();
$fakeAccount->id = TwoFAccount::FAKE_ID;
$fakeAccount->otp_type = $otp_parameters['type'] ?? TwoFAccount::TOTP;
$fakeAccount = new TwoFAccount();
$fakeAccount->id = TwoFAccount::FAKE_ID;
$fakeAccount->otp_type = $otp_parameters['type'] ?? TwoFAccount::TOTP;
// Only basic fields are filled to limit the risk of another exception.
$fakeAccount->account = $otp_parameters['name'] ?? __('twofaccounts.import.invalid_account');
$fakeAccount->service = $otp_parameters['issuer'] ?? __('twofaccounts.import.invalid_service');
$fakeAccount->account = $otp_parameters['name'] ?? __('twofaccounts.import.invalid_account');
$fakeAccount->service = $otp_parameters['issuer'] ?? __('twofaccounts.import.invalid_service');
// The secret field is used to pass the error, not very clean but will do the job for now.
$fakeAccount->secret = $exception->getMessage();
$fakeAccount->secret = $exception->getMessage();
$twofaccounts[$key] = $fakeAccount;
}

View File

@ -2,23 +2,21 @@
namespace App\Services\Migrators;
use Exception;
use App\Exceptions\InvalidMigrationDataException;
use App\Models\TwoFAccount;
use App\Services\Migrators\Migrator;
use Illuminate\Support\Collection;
use ParagonIE\ConstantTime\Base32;
use App\Protobuf\GAuthValueMapping;
use App\Protobuf\GoogleAuth\Payload;
use App\Protobuf\GoogleAuth\Payload\OtpType;
use App\Protobuf\GoogleAuth\Payload\Algorithm;
use App\Protobuf\GoogleAuth\Payload\DigitCount;
use App\Exceptions\InvalidMigrationDataException;
use App\Protobuf\GoogleAuth\Payload\OtpType;
use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use ParagonIE\ConstantTime\Base32;
class GoogleAuthMigrator extends Migrator
{
/**
* Convert Google Authenticator migration URI to a set of TwoFAccount objects.
*
@ -29,49 +27,45 @@ class GoogleAuthMigrator extends Migrator
{
try {
$migrationData = base64_decode(urldecode(Str::replace('otpauth-migration://offline?data=', '', $migrationPayload)));
$protobuf = new Payload();
$protobuf = new Payload();
$protobuf->mergeFromString($migrationData);
$otpParameters = $protobuf->getOtpParameters();
}
catch (Exception $ex) {
Log::error("Protobuf failed to get OTP parameters from provided migration URI");
} catch (Exception $ex) {
Log::error('Protobuf failed to get OTP parameters from provided migration URI');
Log::error($ex->getMessage());
throw new InvalidMigrationDataException('Google Authenticator');
}
$twofaccounts = array();
$twofaccounts = [];
foreach ($otpParameters->getIterator() as $key => $otp_parameters) {
try {
$parameters = array();
$parameters['otp_type'] = GAuthValueMapping::OTP_TYPE[OtpType::name($otp_parameters->getType())];
$parameters['service'] = $otp_parameters->getIssuer();
$parameters['account'] = str_replace($parameters['service'].':', '', $otp_parameters->getName());
$parameters['secret'] = Base32::encodeUpper($otp_parameters->getSecret());
$parameters['algorithm'] = GAuthValueMapping::ALGORITHM[Algorithm::name($otp_parameters->getAlgorithm())];
$parameters['digits'] = GAuthValueMapping::DIGIT_COUNT[DigitCount::name($otp_parameters->getDigits())];
$parameters['counter'] = $parameters['otp_type'] === TwoFAccount::HOTP ? $otp_parameters->getCounter() : null;
$parameters['period'] = $parameters['otp_type'] === TwoFAccount::TOTP ? $otp_parameters->getPeriod() : null;
try {
$parameters = [];
$parameters['otp_type'] = GAuthValueMapping::OTP_TYPE[OtpType::name($otp_parameters->getType())];
$parameters['service'] = $otp_parameters->getIssuer();
$parameters['account'] = str_replace($parameters['service'] . ':', '', $otp_parameters->getName());
$parameters['secret'] = Base32::encodeUpper($otp_parameters->getSecret());
$parameters['algorithm'] = GAuthValueMapping::ALGORITHM[Algorithm::name($otp_parameters->getAlgorithm())];
$parameters['digits'] = GAuthValueMapping::DIGIT_COUNT[DigitCount::name($otp_parameters->getDigits())];
$parameters['counter'] = $parameters['otp_type'] === TwoFAccount::HOTP ? $otp_parameters->getCounter() : null;
$parameters['period'] = $parameters['otp_type'] === TwoFAccount::TOTP ? $otp_parameters->getPeriod() : null;
$twofaccounts[$key] = new TwoFAccount;
$twofaccounts[$key]->fillWithOtpParameters($parameters);
}
catch (Exception $exception) {
} catch (Exception $exception) {
Log::error(sprintf('Cannot instanciate a TwoFAccount object with OTP parameters from imported item #%s', $key));
Log::error($exception->getMessage());
// The token failed to generate a valid account so we create a fake account to be returned.
$fakeAccount = new TwoFAccount();
$fakeAccount->id = -2;
$fakeAccount->otp_type = $fakeAccount::TOTP;
$fakeAccount = new TwoFAccount();
$fakeAccount->id = -2;
$fakeAccount->otp_type = $fakeAccount::TOTP;
// Only basic fields are filled to limit the risk of another exception.
$fakeAccount->account = $otp_parameters->getName() ?? __('twofaccounts.import.invalid_account');
$fakeAccount->service = $otp_parameters->getIssuer() ?? __('twofaccounts.import.invalid_service');
$fakeAccount->account = $otp_parameters->getName() ?? __('twofaccounts.import.invalid_account');
$fakeAccount->service = $otp_parameters->getIssuer() ?? __('twofaccounts.import.invalid_service');
// The secret field is used to pass the error, not very clean but will do the job for now.
$fakeAccount->secret = $exception->getMessage();
$fakeAccount->secret = $exception->getMessage();
$twofaccounts[$key] = $fakeAccount;
}

View File

@ -14,16 +14,14 @@ abstract class Migrator
*/
abstract public function migrate(mixed $migrationPayload) : Collection;
/**
* Pad a string to 8 chars min
*
* @param string $string
* @param string $string
* @return string The padded string
*/
protected function padToValidBase32Secret(string $string)
{
return str_pad($string, 8, '=');
}
}

View File

@ -2,17 +2,15 @@
namespace App\Services\Migrators;
use App\Services\Migrators\Migrator;
use Illuminate\Support\Collection;
use App\Models\TwoFAccount;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use App\Exceptions\InvalidMigrationDataException;
use App\Models\TwoFAccount;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class PlainTextMigrator extends Migrator
{
/**
* Convert migration data to a TwoFAccounts collection.
*
@ -32,25 +30,22 @@ class PlainTextMigrator extends Migrator
}
foreach ($otpauthURIs as $key => $uri) {
try {
$twofaccounts[$key] = new TwoFAccount;
$twofaccounts[$key]->fillWithURI($uri);
}
catch (\Exception $exception) {
$twofaccounts[$key] = new TwoFAccount;
$twofaccounts[$key]->fillWithURI($uri);
} catch (\Exception $exception) {
Log::error(sprintf('Cannot instanciate a TwoFAccount object with OTP parameters from imported item #%s', $key));
Log::error($exception->getMessage());
// The token failed to generate a valid account so we create a fake account to be returned.
$fakeAccount = new TwoFAccount();
$fakeAccount->id = -2;
$fakeAccount->otp_type = substr($uri, 10, 4);
$fakeAccount = new TwoFAccount();
$fakeAccount->id = -2;
$fakeAccount->otp_type = substr($uri, 10, 4);
// Only basic fields are filled to limit the risk of another exception.
$fakeAccount->account = __('twofaccounts.import.invalid_account');
$fakeAccount->service = filter_input(INPUT_GET, 'issuer', FILTER_SANITIZE_ENCODED) ?? __('twofaccounts.import.invalid_service');
$fakeAccount->account = __('twofaccounts.import.invalid_account');
$fakeAccount->service = filter_input(INPUT_GET, 'issuer', FILTER_SANITIZE_ENCODED) ?? __('twofaccounts.import.invalid_service');
// The secret field is used to pass the error, not very clean but will do the job for now.
$fakeAccount->secret = $exception->getMessage();
$fakeAccount->secret = $exception->getMessage();
$twofaccounts[$key] = $fakeAccount;
}

View File

@ -2,12 +2,11 @@
namespace App\Services\Migrators;
use App\Services\Migrators\Migrator;
use Illuminate\Support\Collection;
use App\Models\TwoFAccount;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Arr;
use App\Exceptions\InvalidMigrationDataException;
use App\Models\TwoFAccount;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class TwoFASMigrator extends Migrator
{
@ -65,7 +64,6 @@ class TwoFASMigrator extends Migrator
// "appVersionName": "3.20.1"
// }
/**
* Convert migration data to a TwoFAccounts collection.
*
@ -81,38 +79,35 @@ class TwoFASMigrator extends Migrator
throw new InvalidMigrationDataException('2FAS Auth');
}
$twofaccounts = array();
$twofaccounts = [];
foreach ($json['services'] as $key => $otp_parameters) {
$parameters = array();
$parameters['otp_type'] = $otp_parameters['otp']['tokenType'];
$parameters['service'] = $otp_parameters['name'];
$parameters['account'] = $otp_parameters['otp']['account'] ?? $parameters['service'];
$parameters['secret'] = $this->padToValidBase32Secret($otp_parameters['secret']);
$parameters['algorithm'] = $otp_parameters['otp']['algorithm'];
$parameters['digits'] = $otp_parameters['otp']['digits'];
$parameters['counter'] = $otp_parameters['otp']['counter'] ?? null;
$parameters['period'] = $otp_parameters['otp']['period'] ?? null;
$parameters = [];
$parameters['otp_type'] = $otp_parameters['otp']['tokenType'];
$parameters['service'] = $otp_parameters['name'];
$parameters['account'] = $otp_parameters['otp']['account'] ?? $parameters['service'];
$parameters['secret'] = $this->padToValidBase32Secret($otp_parameters['secret']);
$parameters['algorithm'] = $otp_parameters['otp']['algorithm'];
$parameters['digits'] = $otp_parameters['otp']['digits'];
$parameters['counter'] = $otp_parameters['otp']['counter'] ?? null;
$parameters['period'] = $otp_parameters['otp']['period'] ?? null;
try {
$twofaccounts[$key] = new TwoFAccount;
$twofaccounts[$key]->fillWithOtpParameters($parameters);
}
catch (\Exception $exception) {
$twofaccounts[$key] = new TwoFAccount;
$twofaccounts[$key]->fillWithOtpParameters($parameters);
} catch (\Exception $exception) {
Log::error(sprintf('Cannot instanciate a TwoFAccount object with 2FAS imported item #%s', $key));
Log::error($exception->getMessage());
// The token failed to generate a valid account so we create a fake account to be returned.
$fakeAccount = new TwoFAccount();
$fakeAccount->id = TwoFAccount::FAKE_ID;
$fakeAccount->otp_type = $otp_parameters['otp']['tokenType'] ?? TwoFAccount::TOTP;
$fakeAccount = new TwoFAccount();
$fakeAccount->id = TwoFAccount::FAKE_ID;
$fakeAccount->otp_type = $otp_parameters['otp']['tokenType'] ?? TwoFAccount::TOTP;
// Only basic fields are filled to limit the risk of another exception.
$fakeAccount->account = $otp_parameters['otp']['account'] ?? __('twofaccounts.import.invalid_account');
$fakeAccount->service = $otp_parameters['name'] ?? __('twofaccounts.import.invalid_service');
$fakeAccount->account = $otp_parameters['otp']['account'] ?? __('twofaccounts.import.invalid_account');
$fakeAccount->service = $otp_parameters['name'] ?? __('twofaccounts.import.invalid_service');
// The secret field is used to pass the error, not very clean but will do the job for now.
$fakeAccount->secret = $exception->getMessage();
$fakeAccount->secret = $exception->getMessage();
$twofaccounts[$key] = $fakeAccount;
}

View File

@ -2,17 +2,17 @@
namespace App\Services;
use Zxing\QrReader;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use Illuminate\Support\Facades\Log;
use chillerlan\QRCode\{QRCode, QROptions};
use Zxing\QrReader;
class QrCodeService
{
/**
* Encode a string into a QR code image
*
* @param string $data The string to encode
*
* @param string $data The string to encode
* @return mixed
*/
public static function encode(string $data)
@ -29,19 +29,18 @@ class QrCodeService
return $qrcode->render($data);
}
/**
* Decode an uploaded QR code image
*
* @param \Illuminate\Http\UploadedFile $file
* @param \Illuminate\Http\UploadedFile $file
* @return string
*/
public static function decode(\Illuminate\Http\UploadedFile $file)
{
$qrcode = new QrReader($file->get(), QrReader::SOURCE_TYPE_BLOB);
$data = urldecode($qrcode->text());
$data = urldecode($qrcode->text());
if(!$data) {
if (! $data) {
throw new \App\Exceptions\InvalidQrCodeException;
}

View File

@ -21,7 +21,6 @@ class ReleaseRadarService
}
}
/**
* Run a manual release scan
*
@ -39,17 +38,15 @@ class ReleaseRadarService
*/
protected function newRelease() : false|string
{
if ($latestReleaseData = json_decode($this->getLatestReleaseData()))
{
$githubVersion = Helpers::cleanVersionNumber($latestReleaseData->tag_name);
if ($latestReleaseData = json_decode($this->getLatestReleaseData())) {
$githubVersion = Helpers::cleanVersionNumber($latestReleaseData->tag_name);
$installedVersion = Helpers::cleanVersionNumber(config('2fauth.version'));
if ($githubVersion > $installedVersion && $latestReleaseData->prerelease == false && $latestReleaseData->draft == false) {
Settings::set('latestRelease', $latestReleaseData->tag_name);
return $latestReleaseData->tag_name;
}
else {
} else {
Settings::delete('latestRelease');
}
@ -59,7 +56,6 @@ class ReleaseRadarService
return false;
}
/**
* Fetch releases on Github
*
@ -74,8 +70,7 @@ class ReleaseRadarService
if ($response->successful()) {
return $response->body();
}
}
catch (\Exception $exception) {
} catch (\Exception $exception) {
Log::error('cannot reach latestReleaseUrl endpoint');
}

View File

@ -2,20 +2,19 @@
namespace App\Services;
use Throwable;
use Exception;
use App\Exceptions\DbEncryptionException;
use App\Models\Option;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\App;
use App\Exceptions\DbEncryptionException;
use Throwable;
class SettingService
{
/**
* All user settings
*
@ -23,7 +22,6 @@ class SettingService
*/
private Collection $settings;
/**
* Constructor
*/
@ -32,11 +30,10 @@ class SettingService
self::build();
}
/**
* Get a setting
*
* @param string $setting A single setting name
* @param string $setting A single setting name
* @return mixed string|int|boolean|null
*/
public function get($setting)
@ -44,7 +41,6 @@ class SettingService
return $this->settings->get($setting);
}
/**
* Get all settings
*
@ -55,20 +51,18 @@ class SettingService
return $this->settings;
}
/**
* Set a setting
*
* @param string|array $setting A single setting name or an associative array of name:value settings
* @param string|int|boolean|null $value The value for single setting
* @param string|array $setting A single setting name or an associative array of name:value settings
* @param string|int|bool|null $value The value for single setting
*/
public function set($setting, $value = null) : void
{
$settings = is_array($setting) ? $setting : [$setting => $value];
foreach ($settings as $setting => $value) {
if( $setting === 'useEncryption')
{
if ($setting === 'useEncryption') {
$this->setEncryptionTo($value);
}
@ -83,11 +77,10 @@ class SettingService
self::build();
}
/**
* Delete a setting
*
* @param string $name The setting name
* @param string $name The setting name
*/
public function delete(string $name) : void
{
@ -95,7 +88,6 @@ class SettingService
Log::info(sprintf('Setting %s deleted', var_export($name, true)));
}
/**
* Determine if the given setting has been customized by the user
*
@ -107,7 +99,6 @@ class SettingService
return DB::table('options')->where('key', $key)->exists();
}
/**
* Set the settings collection
*
@ -123,19 +114,17 @@ class SettingService
// Merge 2fauth/app config values as fallback values
$settings = collect(config('2fauth.options'))->merge($userOptions); /** @phpstan-ignore-line */
if(!Arr::has($settings, 'lang')) {
if (! Arr::has($settings, 'lang')) {
$settings['lang'] = 'browser';
}
$this->settings = $settings;
}
/**
* Replaces boolean by a patterned string as appstrack/laravel-options package does not support var type
*
* @param mixed $value
* @param mixed $value
* @return string
*/
private function replaceBoolean(mixed $value)
@ -143,33 +132,30 @@ class SettingService
return is_bool($value) ? '{{' . $value . '}}' : $value;
}
/**
* Replaces patterned string that represent booleans with real booleans
*
* @param mixed $value
* @param mixed $value
* @return mixed
*/
private function restoreType(mixed $value)
{
$value = is_numeric($value) ? (int) $value : $value;
if( $value === '{{}}' ) {
if ($value === '{{}}') {
return false;
}
else if( $value === '{{1}}' ) {
} elseif ($value === '{{1}}') {
return true;
}
else {
} else {
return $value;
}
}
/**
* Enable or Disable encryption of 2FAccounts sensible data
*
* @return void
*
* @throws DbEncryptionException Something failed, everything have been rolled back
*/
private function setEncryptionTo(bool $state) : void
@ -177,39 +163,37 @@ class SettingService
// We don't want the records to be encrypted/decrypted multiple successive times
$isInUse = $this->get('useEncryption');
if ($isInUse === !$state) {
if ($isInUse === ! $state) {
if ($this->updateRecords($state)) {
if ($state) {
Log::notice('Sensible data are now encrypted');
} else {
Log::notice('Sensible data are now decrypted');
}
else Log::notice('Sensible data are now decrypted');
}
else {
} else {
Log::warning('Some data cannot be encrypted/decrypted, the useEncryption setting remain unchanged');
throw new DbEncryptionException($state === true ? __('errors.error_during_encryption') : __('errors.error_during_decryption'));
}
}
}
/**
* Encrypt/Decrypt accounts in database
*
* @param boolean $encrypted Whether the record should be encrypted or not
* @return boolean Whether the operation completed successfully
* @param bool $encrypted Whether the record should be encrypted or not
* @return bool Whether the operation completed successfully
*/
private function updateRecords(bool $encrypted) : bool
{
$success = true;
$success = true;
$twofaccounts = DB::table('twofaccounts')->get();
$twofaccounts->each(function ($item, $key) use(&$success, $encrypted) {
$twofaccounts->each(function ($item, $key) use (&$success, $encrypted) {
try {
$item->legacy_uri = $encrypted ? Crypt::encryptString($item->legacy_uri) : Crypt::decryptString($item->legacy_uri);
$item->account = $encrypted ? Crypt::encryptString($item->account) : Crypt::decryptString($item->account);
$item->secret = $encrypted ? Crypt::encryptString($item->secret) : Crypt::decryptString($item->secret);
}
catch (Exception $ex) {
$item->legacy_uri = $encrypted ? Crypt::encryptString($item->legacy_uri) : Crypt::decryptString($item->legacy_uri);
$item->account = $encrypted ? Crypt::encryptString($item->account) : Crypt::decryptString($item->account);
$item->secret = $encrypted ? Crypt::encryptString($item->secret) : Crypt::decryptString($item->secret);
} catch (Exception $ex) {
$success = false;
// Exit the each iteration
return false;
@ -228,20 +212,23 @@ class SettingService
->update([
'legacy_uri' => $item->legacy_uri,
'account' => $item->account,
'secret' => $item->secret
'secret' => $item->secret,
]);
});
DB::commit();
return true;
}
// @codeCoverageIgnoreStart
catch (Throwable $ex) {
DB::rollBack();
return false;
}
// @codeCoverageIgnoreEnd
} else {
return false;
}
else return false;
}
}

View File

@ -2,19 +2,18 @@
namespace App\Services;
use App\Models\TwoFAccount;
use App\Factories\MigratorFactoryInterface;
use Illuminate\Support\Facades\Log;
use App\Models\TwoFAccount;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class TwoFAccountService
{
/**
* @var MigratorFactoryInterface $migratorFactory The Migration service
* @var MigratorFactoryInterface The Migration service
*/
protected $migratorFactory;
/**
* Constructor
*/
@ -23,11 +22,10 @@ class TwoFAccountService
$this->migratorFactory = $migratorFactory;
}
/**
* Withdraw one or more twofaccounts from their group
*
* @param int|array|string $ids twofaccount ids to free
* @param int|array|string $ids twofaccount ids to free
*/
public static function withdraw($ids) : void
{
@ -40,32 +38,30 @@ class TwoFAccountService
TwoFAccount::whereIn('id', $ids)
->update(
['group_id' => NULL]
['group_id' => null]
);
Log::info(sprintf('TwoFAccounts #%s withdrawn', implode(',#', $ids)));
}
/**
* Convert a migration payload to a set of TwoFAccount objects
*
* @param string $migrationPayload Migration payload from 2FA apps export feature
* @param string $migrationPayload Migration payload from 2FA apps export feature
* @return \Illuminate\Support\Collection<int|string, TwoFAccount> The converted accounts
*/
public function migrate(string $migrationPayload) : Collection
{
$migrator = $this->migratorFactory->create($migrationPayload);
$migrator = $this->migratorFactory->create($migrationPayload);
$twofaccounts = $migrator->migrate($migrationPayload);
return self::markAsDuplicate($twofaccounts);
}
/**
* Delete one or more twofaccounts
*
* @param int|array|string $ids twofaccount ids to delete
* @param int|array|string $ids twofaccount ids to delete
* @return int The number of deleted
*/
public static function delete($ids) : int
@ -73,17 +69,16 @@ class TwoFAccountService
// $ids as string could be a comma-separated list of ids
// so in this case we explode the string to an array
$ids = self::commaSeparatedToArray($ids);
Log::info(sprintf('Deletion of TwoFAccounts #%s requested', is_array($ids) ? implode(',#', $ids) : $ids ));
Log::info(sprintf('Deletion of TwoFAccounts #%s requested', is_array($ids) ? implode(',#', $ids) : $ids));
$deleted = TwoFAccount::destroy($ids);
return $deleted;
}
/**
* Return the given collection with items marked as Duplicates (using id=-1) if a similar record exists in database
*
* @param \Illuminate\Support\Collection<int|string, TwoFAccount> $twofaccounts
* @param \Illuminate\Support\Collection<int|string, TwoFAccount> $twofaccounts
* @return \Illuminate\Support\Collection<int|string, TwoFAccount>
*/
private static function markAsDuplicate(Collection $twofaccounts) : Collection
@ -108,16 +103,14 @@ class TwoFAccountService
return $twofaccounts;
}
/**
* Explode a comma separated list of IDs to an array of IDs
*
* @param int|array|string $ids
* @param int|array|string $ids
*/
private static function commaSeparatedToArray($ids) : mixed
{
if(is_string($ids))
{
if (is_string($ids)) {
$regex = "/^\d+(,{1}\d+)*$/";
if (preg_match($regex, $ids)) {
$ids = explode(',', $ids);

View File

@ -1,3 +1,26 @@
{
"preset": "laravel"
"preset": "laravel",
"exclude": [
"app/Protobuf",
"bootstrap",
"config",
"database",
"public",
"resources"
],
"rules": {
"binary_operator_spaces": {
"default": "single_space",
"operators": {
"=>": "align_single_space_minimal",
"=": "align_single_space_minimal"
}
},
"concat_space": {
"spacing": "one"
},
"return_type_declaration": {
"space_before": "one"
}
}
}

View File

@ -9,21 +9,19 @@ class UserControllerTest extends FeatureTestCase
{
/**
* @var \App\Models\User
*/
*/
protected $user;
/**
* @test
*/
public function setUp(): void
public function setUp() : void
{
parent::setUp();
$this->user = User::factory()->create();
}
/**
* @test
*/
@ -39,7 +37,6 @@ class UserControllerTest extends FeatureTestCase
]);
}
/**
* @test
*/
@ -48,11 +45,10 @@ class UserControllerTest extends FeatureTestCase
$response = $this->json('GET', '/api/v1/user/name')
->assertOk()
->assertExactJson([
'name' => $this->user->name,
'name' => $this->user->name,
]);
}
/**
* @test
*/
@ -69,5 +65,4 @@ class UserControllerTest extends FeatureTestCase
'email' => $this->user->email,
]);
}
}

View File

@ -2,11 +2,10 @@
namespace Tests\Api\v1\Controllers;
use App\Models\User;
use App\Models\Group;
use Tests\FeatureTestCase;
use App\Models\TwoFAccount;
use App\Models\User;
use Tests\FeatureTestCase;
/**
* @covers \App\Api\v1\Controllers\GroupController
@ -16,21 +15,19 @@ class GroupControllerTest extends FeatureTestCase
{
/**
* @var \App\Models\User
*/
*/
protected $user;
/**
* @test
*/
public function setUp(): void
public function setUp() : void
{
parent::setUp();
$this->user = User::factory()->create();
}
/**
* @test
*/
@ -47,16 +44,15 @@ class GroupControllerTest extends FeatureTestCase
'id',
'name',
'twofaccounts_count',
]
],
])
->assertJsonFragment([
'id' => 0,
'name' => 'All',
'id' => 0,
'name' => 'All',
'twofaccounts_count' => 0,
]);
}
/**
* @test
*/
@ -68,12 +64,11 @@ class GroupControllerTest extends FeatureTestCase
])
->assertCreated()
->assertJsonFragment([
'name' => 'My second group',
'name' => 'My second group',
'twofaccounts_count' => 0,
]);
}
/**
* @test
*/
@ -86,7 +81,6 @@ class GroupControllerTest extends FeatureTestCase
->assertStatus(422);
}
/**
* @test
*/
@ -100,12 +94,11 @@ class GroupControllerTest extends FeatureTestCase
->json('GET', '/api/v1/groups/' . $group->id)
->assertOk()
->assertJsonFragment([
'name' => 'My group',
'name' => 'My group',
'twofaccounts_count' => 0,
]);
}
/**
* @test
*/
@ -115,11 +108,10 @@ class GroupControllerTest extends FeatureTestCase
->json('GET', '/api/v1/groups/1000')
->assertNotFound()
->assertJsonStructure([
'message'
'message',
]);
}
/**
* @test
*/
@ -133,12 +125,11 @@ class GroupControllerTest extends FeatureTestCase
])
->assertOk()
->assertJsonFragment([
'name' => 'name updated',
'name' => 'name updated',
'twofaccounts_count' => 0,
]);
}
/**
* @test
*/
@ -150,11 +141,10 @@ class GroupControllerTest extends FeatureTestCase
])
->assertNotFound()
->assertJsonStructure([
'message'
'message',
]);
}
/**
* @test
*/
@ -169,13 +159,12 @@ class GroupControllerTest extends FeatureTestCase
->assertStatus(422);
}
/**
* @test
*/
public function test_assign_accounts_returns_updated_group_resource()
{
$group = Group::factory()->create();
$group = Group::factory()->create();
$accounts = TwoFAccount::factory()->count(2)->create();
$response = $this->actingAs($this->user, 'api-guard')
@ -184,13 +173,12 @@ class GroupControllerTest extends FeatureTestCase
])
->assertOk()
->assertExactJson([
'id' => $group->id,
'name' => $group->name,
'id' => $group->id,
'name' => $group->name,
'twofaccounts_count' => 2,
]);
}
/**
* @test
*/
@ -204,17 +192,16 @@ class GroupControllerTest extends FeatureTestCase
])
->assertNotFound()
->assertJsonStructure([
'message'
'message',
]);
}
/**
* @test
*/
public function test_assign_invalid_accounts_returns_validation_error()
{
$group = Group::factory()->create();
$group = Group::factory()->create();
$accounts = TwoFAccount::factory()->count(2)->create();
$response = $this->actingAs($this->user, 'api-guard')
@ -224,13 +211,12 @@ class GroupControllerTest extends FeatureTestCase
->assertStatus(422);
}
/**
* @test
*/
public function test_get_assigned_accounts_returns_twofaccounts_collection()
{
$group = Group::factory()->create();
$group = Group::factory()->create();
$accounts = TwoFAccount::factory()->count(2)->create();
$assign = $this->actingAs($this->user, 'api-guard')
@ -252,18 +238,17 @@ class GroupControllerTest extends FeatureTestCase
'digits',
'algorithm',
'period',
'counter'
]
'counter',
],
]);
}
/**
* @test
*/
public function test_get_assigned_accounts_returns_twofaccounts_collection_with_secret()
{
$group = Group::factory()->create();
$group = Group::factory()->create();
$accounts = TwoFAccount::factory()->count(2)->create();
$assign = $this->actingAs($this->user, 'api-guard')
@ -286,12 +271,11 @@ class GroupControllerTest extends FeatureTestCase
'digits',
'algorithm',
'period',
'counter'
]
'counter',
],
]);
}
/**
* @test
*/
@ -301,11 +285,10 @@ class GroupControllerTest extends FeatureTestCase
->json('GET', '/api/v1/groups/1000/twofaccounts')
->assertNotFound()
->assertJsonStructure([
'message'
'message',
]);
}
/**
* test Group deletion via API
*
@ -320,7 +303,6 @@ class GroupControllerTest extends FeatureTestCase
->assertNoContent();
}
/**
* test Group deletion via API
*
@ -332,7 +314,7 @@ class GroupControllerTest extends FeatureTestCase
->json('DELETE', '/api/v1/groups/1000')
->assertNotFound()
->assertJsonStructure([
'message'
'message',
]);
}
}

View File

@ -2,21 +2,17 @@
namespace Tests\Api\v1\Controllers;
use Illuminate\Http\UploadedFile;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Http\UploadedFile;
use Tests\FeatureTestCase;
use App\Models\TwoFAccount;
/**
* @covers \App\Api\v1\Controllers\IconController
*/
class IconControllerTest extends FeatureTestCase
{
use WithoutMiddleware;
/**
* @test
*/
@ -25,27 +21,25 @@ class IconControllerTest extends FeatureTestCase
$file = UploadedFile::fake()->image('testIcon.jpg');
$response = $this->json('POST', '/api/v1/icons', [
'icon' => $file,
])
'icon' => $file,
])
->assertCreated()
->assertJsonStructure([
'filename'
'filename',
]);
}
/**
* @test
*/
public function test_upload_with_invalid_data_returns_validation_error()
{
$response = $this->json('POST', '/api/v1/icons', [
'icon' => null,
])
'icon' => null,
])
->assertStatus(422);
}
/**
* @test
*/
@ -55,7 +49,6 @@ class IconControllerTest extends FeatureTestCase
->assertNoContent(204);
}
/**
* @test
*/
@ -63,7 +56,5 @@ class IconControllerTest extends FeatureTestCase
{
$response = $this->json('DELETE', '/api/v1/icons/null')
->assertNoContent(204);
}
}

View File

@ -2,48 +2,44 @@
namespace Tests\Api\v1\Controllers;
use App\Models\User;
use Tests\FeatureTestCase;
use App\Models\TwoFAccount;
use App\Models\User;
use Tests\Classes\LocalFile;
use Tests\FeatureTestCase;
/**
* @covers \App\Api\v1\Controllers\QrCodeController
*/
class QrCodeControllerTest extends FeatureTestCase
{
/**
* @var \App\Models\User
*/
*/
protected $user;
/**
* @test
*/
public function setUp(): void
public function setUp() : void
{
parent::setUp();
$this->user = User::factory()->create();
}
/**
* @test
*/
public function test_show_qrcode_returns_base64_image()
{
$twofaccount = TwoFAccount::factory()->create([
'otp_type' => 'totp',
'account' => 'account',
'service' => 'service',
'secret' => 'A4GRFHZVRBGY7UIW',
'algorithm' => 'sha1',
'digits' => 6,
'period' => 30,
'otp_type' => 'totp',
'account' => 'account',
'service' => 'service',
'secret' => 'A4GRFHZVRBGY7UIW',
'algorithm' => 'sha1',
'digits' => 6,
'period' => 30,
'legacy_uri' => 'otpauth://hotp/service:account?secret=A4GRFHZVRBGY7UIW&issuer=service',
]);
@ -57,7 +53,6 @@ class QrCodeControllerTest extends FeatureTestCase
$this->assertStringStartsWith('data:image/png;base64', $response->getData()->qrcode);
}
/**
* @test
*/
@ -67,11 +62,10 @@ class QrCodeControllerTest extends FeatureTestCase
->json('GET', '/api/v1/twofaccounts/1000/qrcode')
->assertNotFound()
->assertJsonStructure([
'message'
'message',
]);
}
/**
* @test
*/
@ -82,8 +76,8 @@ class QrCodeControllerTest extends FeatureTestCase
$response = $this->withHeaders(['Content-Type' => 'multipart/form-data'])
->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/qrcode/decode', [
'qrcode' => $file,
'inputFormat' => 'fileUpload'
'qrcode' => $file,
'inputFormat' => 'fileUpload',
])
->assertOk()
->assertExactJson([
@ -91,7 +85,6 @@ class QrCodeControllerTest extends FeatureTestCase
]);
}
/**
* @test
*/
@ -104,7 +97,6 @@ class QrCodeControllerTest extends FeatureTestCase
->assertStatus(422);
}
/**
* @test
*/
@ -115,8 +107,8 @@ class QrCodeControllerTest extends FeatureTestCase
$response = $this->withHeaders(['Content-Type' => 'multipart/form-data'])
->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/qrcode/decode', [
'qrcode' => $file,
'inputFormat' => 'fileUpload'
'qrcode' => $file,
'inputFormat' => 'fileUpload',
])
->assertStatus(400)
->assertJsonStructure([

View File

@ -2,10 +2,9 @@
namespace Tests\Api\v1\Controllers;
use App\Facades\Settings;
use App\Models\User;
use Tests\FeatureTestCase;
use App\Facades\Settings;
/**
* @covers \App\Api\v1\Controllers\SettingController
@ -14,31 +13,36 @@ class SettingControllerTest extends FeatureTestCase
{
/**
* @var \App\Models\User
*/
*/
protected $user;
private const SETTING_JSON_STRUCTURE = [
'key',
'value'
'value',
];
private const TWOFAUTH_NATIVE_SETTING = 'showTokenAsDot';
private const TWOFAUTH_NATIVE_SETTING_DEFAULT_VALUE = false;
private const TWOFAUTH_NATIVE_SETTING_CHANGED_VALUE = true;
private const USER_DEFINED_SETTING = 'mySetting';
private const USER_DEFINED_SETTING_VALUE = 'mySetting';
private const USER_DEFINED_SETTING_CHANGED_VALUE = 'mySetting';
/**
* @test
*/
public function setUp(): void
public function setUp() : void
{
parent::setUp();
$this->user = User::factory()->create();
}
/**
* @test
*/
@ -48,11 +52,10 @@ class SettingControllerTest extends FeatureTestCase
->json('GET', '/api/v1/settings')
->assertOk()
->assertJsonStructure([
'*' => self::SETTING_JSON_STRUCTURE
'*' => self::SETTING_JSON_STRUCTURE,
]);
}
/**
* @test
*/
@ -62,12 +65,11 @@ class SettingControllerTest extends FeatureTestCase
->json('GET', '/api/v1/settings/' . self::TWOFAUTH_NATIVE_SETTING)
->assertOk()
->assertExactJson([
'key' => self::TWOFAUTH_NATIVE_SETTING,
'key' => self::TWOFAUTH_NATIVE_SETTING,
'value' => self::TWOFAUTH_NATIVE_SETTING_DEFAULT_VALUE,
]);
}
/**
* @test
*/
@ -79,12 +81,11 @@ class SettingControllerTest extends FeatureTestCase
->json('GET', '/api/v1/settings/' . self::TWOFAUTH_NATIVE_SETTING)
->assertOk()
->assertExactJson([
'key' => self::TWOFAUTH_NATIVE_SETTING,
'key' => self::TWOFAUTH_NATIVE_SETTING,
'value' => self::TWOFAUTH_NATIVE_SETTING_CHANGED_VALUE,
]);
}
/**
* @test
*/
@ -96,12 +97,11 @@ class SettingControllerTest extends FeatureTestCase
->json('GET', '/api/v1/settings/' . self::USER_DEFINED_SETTING)
->assertOk()
->assertExactJson([
'key' => self::USER_DEFINED_SETTING,
'key' => self::USER_DEFINED_SETTING,
'value' => self::USER_DEFINED_SETTING_VALUE,
]);
}
/**
* @test
*/
@ -112,7 +112,6 @@ class SettingControllerTest extends FeatureTestCase
->assertNotFound();
}
/**
* @test
*/
@ -120,17 +119,16 @@ class SettingControllerTest extends FeatureTestCase
{
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/settings', [
'key' => self::USER_DEFINED_SETTING,
'key' => self::USER_DEFINED_SETTING,
'value' => self::USER_DEFINED_SETTING_VALUE,
])
->assertCreated()
->assertExactJson([
'key' => self::USER_DEFINED_SETTING,
'key' => self::USER_DEFINED_SETTING,
'value' => self::USER_DEFINED_SETTING_VALUE,
]);
}
/**
* @test
*/
@ -138,13 +136,12 @@ class SettingControllerTest extends FeatureTestCase
{
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/settings', [
'key' => null,
'key' => null,
'value' => null,
])
->assertStatus(422);
}
/**
* @test
*/
@ -154,13 +151,12 @@ class SettingControllerTest extends FeatureTestCase
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/settings', [
'key' => self::USER_DEFINED_SETTING,
'key' => self::USER_DEFINED_SETTING,
'value' => self::USER_DEFINED_SETTING_VALUE,
])
->assertStatus(422);
}
/**
* @test
*/
@ -172,12 +168,11 @@ class SettingControllerTest extends FeatureTestCase
])
->assertOk()
->assertExactJson([
'key' => self::TWOFAUTH_NATIVE_SETTING,
'key' => self::TWOFAUTH_NATIVE_SETTING,
'value' => self::TWOFAUTH_NATIVE_SETTING_CHANGED_VALUE,
]);
}
/**
* @test
*/
@ -191,12 +186,11 @@ class SettingControllerTest extends FeatureTestCase
])
->assertOk()
->assertExactJson([
'key' => self::USER_DEFINED_SETTING,
'key' => self::USER_DEFINED_SETTING,
'value' => self::USER_DEFINED_SETTING_CHANGED_VALUE,
]);
}
/**
* @test
*/
@ -208,12 +202,11 @@ class SettingControllerTest extends FeatureTestCase
])
->assertOk()
->assertExactJson([
'key' => self::USER_DEFINED_SETTING,
'key' => self::USER_DEFINED_SETTING,
'value' => self::USER_DEFINED_SETTING_CHANGED_VALUE,
]);
}
/**
* @test
*/
@ -226,7 +219,6 @@ class SettingControllerTest extends FeatureTestCase
->assertNoContent();
}
/**
* @test
*/
@ -241,7 +233,6 @@ class SettingControllerTest extends FeatureTestCase
]);
}
/**
* @test
*/
@ -251,6 +242,4 @@ class SettingControllerTest extends FeatureTestCase
->json('DELETE', '/api/v1/settings/' . self::USER_DEFINED_SETTING)
->assertNotFound();
}
}

View File

@ -2,16 +2,15 @@
namespace Tests\Api\v1\Controllers;
use App\Models\User;
use App\Models\Group;
use App\Facades\Settings;
use Tests\FeatureTestCase;
use Tests\Classes\OtpTestData;
use App\Models\Group;
use App\Models\TwoFAccount;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Tests\Classes\LocalFile;
use Tests\Classes\OtpTestData;
use Tests\FeatureTestCase;
/**
* @covers \App\Api\v1\Controllers\TwoFAccountController
@ -22,16 +21,14 @@ class TwoFAccountControllerTest extends FeatureTestCase
{
/**
* @var \App\Models\User
*/
*/
protected $user;
/**
* @var \App\Models\Group
*/
*/
protected $group;
private const VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET = [
'id',
'group_id',
@ -42,8 +39,9 @@ class TwoFAccountControllerTest extends FeatureTestCase
'digits',
'algorithm',
'period',
'counter'
'counter',
];
private const VALID_RESOURCE_STRUCTURE_WITH_SECRET = [
'id',
'group_id',
@ -55,19 +53,22 @@ class TwoFAccountControllerTest extends FeatureTestCase
'digits',
'algorithm',
'period',
'counter'
'counter',
];
private const VALID_OTP_RESOURCE_STRUCTURE_FOR_TOTP = [
'generated_at',
'otp_type',
'password',
'period',
];
private const VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP = [
'otp_type',
'password',
'counter',
];
private const JSON_FRAGMENTS_FOR_CUSTOM_TOTP = [
'service' => OtpTestData::SERVICE,
'account' => OtpTestData::ACCOUNT,
@ -78,6 +79,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
'period' => OtpTestData::PERIOD_CUSTOM,
'counter' => null,
];
private const JSON_FRAGMENTS_FOR_DEFAULT_TOTP = [
'service' => null,
'account' => OtpTestData::ACCOUNT,
@ -88,6 +90,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
'period' => OtpTestData::PERIOD_DEFAULT,
'counter' => null,
];
private const JSON_FRAGMENTS_FOR_CUSTOM_HOTP = [
'service' => OtpTestData::SERVICE,
'account' => OtpTestData::ACCOUNT,
@ -98,8 +101,9 @@ class TwoFAccountControllerTest extends FeatureTestCase
'period' => null,
'counter' => OtpTestData::COUNTER_CUSTOM,
];
private const JSON_FRAGMENTS_FOR_DEFAULT_HOTP = [
'service' => null,
'service' => null,
'account' => OtpTestData::ACCOUNT,
'otp_type' => 'hotp',
'secret' => OtpTestData::SECRET,
@ -108,25 +112,24 @@ class TwoFAccountControllerTest extends FeatureTestCase
'period' => null,
'counter' => OtpTestData::COUNTER_DEFAULT,
];
private const ARRAY_OF_INVALID_PARAMETERS = [
'account' => null,
'otp_type' => 'totp',
'secret' => OtpTestData::SECRET,
];
private const ARRAY_OF_INVALID_PARAMETERS = [
'account' => null,
'otp_type' => 'totp',
'secret' => OtpTestData::SECRET,
];
/**
* @test
*/
public function setUp(): void
public function setUp() : void
{
parent::setUp();
$this->user = User::factory()->create();
$this->user = User::factory()->create();
$this->group = Group::factory()->create();
}
/**
* @test
*
@ -137,33 +140,31 @@ class TwoFAccountControllerTest extends FeatureTestCase
TwoFAccount::factory()->count(3)->create();
$response = $this->actingAs($this->user, 'api-guard')
->json('GET', '/api/v1/twofaccounts'.$urlParameter)
->json('GET', '/api/v1/twofaccounts' . $urlParameter)
->assertOk()
->assertJsonCount(3, $key = null)
->assertJsonStructure([
'*' => $expected
'*' => $expected,
]);
}
/**
* Provide data for index tests
*/
public function indexUrlParameterProvider()
{
return [
'VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET' => [
'VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET' => [
'',
self::VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET
self::VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET,
],
'VALID_RESOURCE_STRUCTURE_WITH_SECRET' => [
'VALID_RESOURCE_STRUCTURE_WITH_SECRET' => [
'?withSecret=1',
self::VALID_RESOURCE_STRUCTURE_WITH_SECRET
self::VALID_RESOURCE_STRUCTURE_WITH_SECRET,
],
];
}
/**
* @test
*/
@ -177,7 +178,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonStructure(self::VALID_RESOURCE_STRUCTURE_WITH_SECRET);
}
/**
* @test
*/
@ -191,7 +191,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonStructure(self::VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET);
}
/**
* @test
*/
@ -217,7 +216,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
// ]);
// }
/**
* @test
*/
@ -227,11 +225,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
->json('GET', '/api/v1/twofaccounts/1000')
->assertNotFound()
->assertJsonStructure([
'message'
'message',
]);
}
/**
* @dataProvider accountCreationProvider
* @test
@ -248,7 +245,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonFragment($expected);
}
/**
* @dataProvider accountCreationProvider
* @test
@ -265,57 +261,55 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonFragment($expected);
}
/**
* Provide data for TwoFAccount store tests
*/
public function accountCreationProvider()
{
return [
'TOTP_FULL_CUSTOM_URI' => [
'TOTP_FULL_CUSTOM_URI' => [
[
'uri' => OtpTestData::TOTP_FULL_CUSTOM_URI,
],
self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP
self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP,
],
'TOTP_SHORT_URI' => [
'TOTP_SHORT_URI' => [
[
'uri' => OtpTestData::TOTP_SHORT_URI,
],
self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP
self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP,
],
'ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP' => [
'ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP' => [
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_TOTP,
self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP
self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP,
],
'ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_TOTP' => [
'ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_TOTP' => [
OtpTestData::ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_TOTP,
self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP
self::JSON_FRAGMENTS_FOR_DEFAULT_TOTP,
],
'HOTP_FULL_CUSTOM_URI' => [
'HOTP_FULL_CUSTOM_URI' => [
[
'uri' => OtpTestData::HOTP_FULL_CUSTOM_URI,
],
self::JSON_FRAGMENTS_FOR_CUSTOM_HOTP
self::JSON_FRAGMENTS_FOR_CUSTOM_HOTP,
],
'HOTP_SHORT_URI' => [
'HOTP_SHORT_URI' => [
[
'uri' => OtpTestData::HOTP_SHORT_URI,
],
self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP
self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP,
],
'ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_HOTP' => [
'ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_HOTP' => [
OtpTestData::ARRAY_OF_FULL_VALID_PARAMETERS_FOR_CUSTOM_HOTP,
self::JSON_FRAGMENTS_FOR_CUSTOM_HOTP
self::JSON_FRAGMENTS_FOR_CUSTOM_HOTP,
],
'ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_HOTP' => [
'ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_HOTP' => [
OtpTestData::ARRAY_OF_MINIMUM_VALID_PARAMETERS_FOR_HOTP,
self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP
self::JSON_FRAGMENTS_FOR_DEFAULT_HOTP,
],
];
}
/**
* @test
*/
@ -328,7 +322,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertStatus(422);
}
/**
* @test
*/
@ -342,11 +335,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
'uri' => OtpTestData::TOTP_SHORT_URI,
])
->assertJsonFragment([
'group_id' => $this->group->id
'group_id' => $this->group->id,
]);
}
/**
* @test
*/
@ -362,11 +354,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
'uri' => OtpTestData::TOTP_SHORT_URI,
])
->assertJsonFragment([
'group_id' => $this->group->id
'group_id' => $this->group->id,
]);
}
/**
* @test
*/
@ -380,11 +371,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
'uri' => OtpTestData::TOTP_SHORT_URI,
])
->assertJsonFragment([
'group_id' => null
'group_id' => null,
]);
}
/**
* @test
*/
@ -398,11 +388,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
'uri' => OtpTestData::TOTP_SHORT_URI,
])
->assertJsonFragment([
'group_id' => null
'group_id' => null,
]);
}
/**
* @test
*/
@ -416,7 +405,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP);
}
/**
* @test
*/
@ -430,7 +418,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_HOTP);
}
/**
* @test
*/
@ -441,7 +428,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertNotFound();
}
/**
* @test
*/
@ -454,7 +440,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertStatus(422);
}
/**
* @test
*/
@ -462,7 +447,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
{
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/migration', [
'payload' => OtpTestData::GOOGLE_AUTH_MIGRATION_URI,
'payload' => OtpTestData::GOOGLE_AUTH_MIGRATION_URI,
'withSecret' => 1,
])
->assertOk()
@ -476,7 +461,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
'digits' => OtpTestData::DIGITS_DEFAULT,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'period' => OtpTestData::PERIOD_DEFAULT,
'counter' => null
'counter' => null,
])
->assertJsonFragment([
'id' => 0,
@ -487,11 +472,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
'digits' => OtpTestData::DIGITS_DEFAULT,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'period' => OtpTestData::PERIOD_DEFAULT,
'counter' => null
'counter' => null,
]);
}
/**
* @test
*/
@ -504,22 +488,21 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertStatus(422);
}
/**
* @test
*/
public function test_import_gauth_payload_with_duplicates_returns_negative_ids()
{
$twofaccount = TwoFAccount::factory()->create([
'otp_type' => 'totp',
'account' => OtpTestData::ACCOUNT,
'service' => OtpTestData::SERVICE,
'secret' => OtpTestData::SECRET,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'digits' => OtpTestData::DIGITS_DEFAULT,
'period' => OtpTestData::PERIOD_DEFAULT,
'otp_type' => 'totp',
'account' => OtpTestData::ACCOUNT,
'service' => OtpTestData::SERVICE,
'secret' => OtpTestData::SECRET,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'digits' => OtpTestData::DIGITS_DEFAULT,
'period' => OtpTestData::PERIOD_DEFAULT,
'legacy_uri' => OtpTestData::TOTP_SHORT_URI,
'icon' => '',
'icon' => '',
]);
$response = $this->actingAs($this->user, 'api-guard')
@ -528,13 +511,12 @@ class TwoFAccountControllerTest extends FeatureTestCase
])
->assertOk()
->assertJsonFragment([
'id' => -1,
'service' => OtpTestData::SERVICE,
'account' => OtpTestData::ACCOUNT,
'id' => -1,
'service' => OtpTestData::SERVICE,
'account' => OtpTestData::ACCOUNT,
]);
}
/**
* @test
*/
@ -546,11 +528,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
])
->assertStatus(400)
->assertJsonStructure([
'message'
'message',
]);
}
/**
* @test
*/
@ -561,7 +542,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
$response = $this->withHeaders(['Content-Type' => 'multipart/form-data'])
->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/migration', [
'file' => $file,
'file' => $file,
'withSecret' => 1,
])
->assertOk()
@ -575,7 +556,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
'digits' => OtpTestData::DIGITS_DEFAULT,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'period' => OtpTestData::PERIOD_DEFAULT,
'counter' => null
'counter' => null,
])
->assertJsonFragment([
'id' => 0,
@ -586,7 +567,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
'digits' => OtpTestData::DIGITS_CUSTOM,
'algorithm' => OtpTestData::ALGORITHM_CUSTOM,
'period' => OtpTestData::PERIOD_CUSTOM,
'counter' => null
'counter' => null,
])
->assertJsonFragment([
'id' => 0,
@ -597,7 +578,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
'digits' => OtpTestData::DIGITS_DEFAULT,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'period' => null,
'counter' => OtpTestData::COUNTER_DEFAULT
'counter' => OtpTestData::COUNTER_DEFAULT,
])
->assertJsonFragment([
'id' => 0,
@ -619,11 +600,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
'digits' => OtpTestData::DIGITS_STEAM,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'period' => OtpTestData::PERIOD_DEFAULT,
'counter' => null
'counter' => null,
]);
}
/**
* @test
*
@ -639,7 +619,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertStatus(400);
}
/**
* Provide invalid Aegis JSON files for import tests
*/
@ -647,15 +626,14 @@ class TwoFAccountControllerTest extends FeatureTestCase
{
return [
'validPlainTextFile' => [
LocalFile::fake()->encryptedAegisJsonFile()
LocalFile::fake()->encryptedAegisJsonFile(),
],
'validPlainTextFileWithNewLines' => [
LocalFile::fake()->invalidAegisJsonFile()
LocalFile::fake()->invalidAegisJsonFile(),
],
];
}
/**
* @test
*
@ -666,7 +644,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
$response = $this->withHeaders(['Content-Type' => 'multipart/form-data'])
->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/migration', [
'file' => $file,
'file' => $file,
'withSecret' => 1,
])
->assertOk()
@ -680,7 +658,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
'digits' => OtpTestData::DIGITS_CUSTOM,
'algorithm' => OtpTestData::ALGORITHM_CUSTOM,
'period' => OtpTestData::PERIOD_CUSTOM,
'counter' => null
'counter' => null,
])
->assertJsonFragment([
'id' => 0,
@ -691,7 +669,7 @@ class TwoFAccountControllerTest extends FeatureTestCase
'digits' => OtpTestData::DIGITS_CUSTOM,
'algorithm' => OtpTestData::ALGORITHM_CUSTOM,
'period' => null,
'counter' => OtpTestData::COUNTER_CUSTOM
'counter' => OtpTestData::COUNTER_CUSTOM,
])
->assertJsonFragment([
'id' => 0,
@ -702,11 +680,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
'digits' => OtpTestData::DIGITS_STEAM,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'period' => OtpTestData::PERIOD_DEFAULT,
'counter' => null
'counter' => null,
]);
}
/**
* Provide valid Plain Text files for import tests
*/
@ -714,15 +691,14 @@ class TwoFAccountControllerTest extends FeatureTestCase
{
return [
'validPlainTextFile' => [
LocalFile::fake()->validPlainTextFile()
LocalFile::fake()->validPlainTextFile(),
],
'validPlainTextFileWithNewLines' => [
LocalFile::fake()->validPlainTextFileWithNewLines()
LocalFile::fake()->validPlainTextFileWithNewLines(),
],
];
}
/**
* @test
*
@ -730,7 +706,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
*/
public function test_import_invalid_plain_text_file_returns_bad_request($file)
{
$response = $this->withHeaders(['Content-Type' => 'multipart/form-data'])
->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/migration', [
@ -739,7 +714,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertStatus(400);
}
/**
* Provide invalid Plain Text files for import tests
*/
@ -747,21 +721,20 @@ class TwoFAccountControllerTest extends FeatureTestCase
{
return [
'validPlainTextFile' => [
LocalFile::fake()->invalidPlainTextFileEmpty()
LocalFile::fake()->invalidPlainTextFileEmpty(),
],
'validPlainTextFileWithNewLines' => [
LocalFile::fake()->invalidPlainTextFileNoUri()
LocalFile::fake()->invalidPlainTextFileNoUri(),
],
'validPlainTextFileWithNewLines' => [
LocalFile::fake()->invalidPlainTextFileWithInvalidUri()
LocalFile::fake()->invalidPlainTextFileWithInvalidUri(),
],
'validPlainTextFileWithNewLines' => [
LocalFile::fake()->invalidPlainTextFileWithInvalidLine()
LocalFile::fake()->invalidPlainTextFileWithInvalidLine(),
],
];
}
/**
* @test
*/
@ -771,14 +744,13 @@ class TwoFAccountControllerTest extends FeatureTestCase
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/reorder', [
'orderedIds' => [3,2,1]])
'orderedIds' => [3, 2, 1], ])
->assertStatus(200)
->assertJsonStructure([
'message'
'message',
]);
}
/**
* @test
*/
@ -788,11 +760,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
$response = $this->actingAs($this->user, 'api-guard')
->json('POST', '/api/v1/twofaccounts/reorder', [
'orderedIds' => '3,2,1'])
'orderedIds' => '3,2,1', ])
->assertStatus(422);
}
/**
* @test
*/
@ -806,7 +777,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonFragment(self::JSON_FRAGMENTS_FOR_CUSTOM_TOTP);
}
/**
* @test
*/
@ -819,7 +789,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertStatus(422);
}
/**
* @test
*/
@ -831,26 +800,25 @@ class TwoFAccountControllerTest extends FeatureTestCase
])
->assertOk()
->assertJsonFragment([
'icon' => null
'icon' => null,
]);
}
/**
* @test
*/
public function test_get_otp_using_totp_twofaccount_id_returns_consistent_resource()
{
$twofaccount = TwoFAccount::factory()->create([
'otp_type' => 'totp',
'account' => OtpTestData::ACCOUNT,
'service' => OtpTestData::SERVICE,
'secret' => OtpTestData::SECRET,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'digits' => OtpTestData::DIGITS_DEFAULT,
'period' => OtpTestData::PERIOD_DEFAULT,
'otp_type' => 'totp',
'account' => OtpTestData::ACCOUNT,
'service' => OtpTestData::SERVICE,
'secret' => OtpTestData::SECRET,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'digits' => OtpTestData::DIGITS_DEFAULT,
'period' => OtpTestData::PERIOD_DEFAULT,
'legacy_uri' => OtpTestData::TOTP_SHORT_URI,
'icon' => '',
'icon' => '',
]);
$response = $this->actingAs($this->user, 'api-guard')
@ -859,11 +827,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_TOTP)
->assertJsonFragment([
'otp_type' => 'totp',
'period' => OtpTestData::PERIOD_DEFAULT,
'period' => OtpTestData::PERIOD_DEFAULT,
]);
}
/**
* @test
*/
@ -877,11 +844,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_TOTP)
->assertJsonFragment([
'otp_type' => 'totp',
'period' => OtpTestData::PERIOD_CUSTOM,
'period' => OtpTestData::PERIOD_CUSTOM,
]);
}
/**
* @test
*/
@ -893,26 +859,25 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_TOTP)
->assertJsonFragment([
'otp_type' => 'totp',
'period' => OtpTestData::PERIOD_CUSTOM,
'period' => OtpTestData::PERIOD_CUSTOM,
]);
}
/**
* @test
*/
public function test_get_otp_using_hotp_twofaccount_id_returns_consistent_resource()
{
$twofaccount = TwoFAccount::factory()->create([
'otp_type' => 'hotp',
'account' => OtpTestData::ACCOUNT,
'service' => OtpTestData::SERVICE,
'secret' => OtpTestData::SECRET,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'digits' => OtpTestData::DIGITS_DEFAULT,
'period' => null,
'otp_type' => 'hotp',
'account' => OtpTestData::ACCOUNT,
'service' => OtpTestData::SERVICE,
'secret' => OtpTestData::SECRET,
'algorithm' => OtpTestData::ALGORITHM_DEFAULT,
'digits' => OtpTestData::DIGITS_DEFAULT,
'period' => null,
'legacy_uri' => OtpTestData::HOTP_SHORT_URI,
'icon' => '',
'icon' => '',
]);
$response = $this->actingAs($this->user, 'api-guard')
@ -921,11 +886,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP)
->assertJsonFragment([
'otp_type' => 'hotp',
'counter' => OtpTestData::COUNTER_DEFAULT + 1,
'counter' => OtpTestData::COUNTER_DEFAULT + 1,
]);
}
/**
* @test
*/
@ -939,11 +903,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP)
->assertJsonFragment([
'otp_type' => 'hotp',
'counter' => OtpTestData::COUNTER_CUSTOM + 1,
'counter' => OtpTestData::COUNTER_CUSTOM + 1,
]);
}
/**
* @test
*/
@ -955,11 +918,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertJsonStructure(self::VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP)
->assertJsonFragment([
'otp_type' => 'hotp',
'counter' => OtpTestData::COUNTER_CUSTOM + 1,
'counter' => OtpTestData::COUNTER_CUSTOM + 1,
]);
}
/**
* @test
*/
@ -977,7 +939,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
]);
}
/**
* @test
*/
@ -1001,7 +962,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
]);
}
/**
* @test
*/
@ -1012,7 +972,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertNotFound();
}
/**
* @test
*/
@ -1025,7 +984,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertStatus(422);
}
/**
* @test
*/
@ -1036,7 +994,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertStatus(422);
}
/**
* @test
*/
@ -1048,11 +1005,10 @@ class TwoFAccountControllerTest extends FeatureTestCase
->json('GET', '/api/v1/twofaccounts/count')
->assertStatus(200)
->assertExactJson([
'count' => 3
'count' => 3,
]);
}
/**
* @test
*/
@ -1069,7 +1025,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
]);
}
/**
* @test
*/
@ -1087,7 +1042,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
]);
}
/**
* @test
*/
@ -1100,7 +1054,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertNoContent();
}
/**
* @test
*/
@ -1113,7 +1066,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertNotFound();
}
/**
* @test
*/
@ -1127,7 +1079,6 @@ class TwoFAccountControllerTest extends FeatureTestCase
->assertNoContent();
}
/**
* @test
*/
@ -1144,5 +1095,4 @@ class TwoFAccountControllerTest extends FeatureTestCase
'reason',
]);
}
}

View File

@ -4,13 +4,12 @@ namespace Tests\Api\v1\Requests;
use App\Api\v1\Requests\GroupAssignRequest;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
class GroupAssignRequestTest extends TestCase
{
use WithoutMiddleware;
/**
@ -32,7 +31,7 @@ class GroupAssignRequestTest extends TestCase
*/
public function test_valid_data(array $data) : void
{
$request = new GroupAssignRequest();
$request = new GroupAssignRequest();
$validator = Validator::make($data, $request->rules());
$this->assertFalse($validator->fails());
@ -46,8 +45,8 @@ class GroupAssignRequestTest extends TestCase
return [
[[
'ids' => [
1, 2, 3
]
1, 2, 3,
],
]],
];
}
@ -57,7 +56,7 @@ class GroupAssignRequestTest extends TestCase
*/
public function test_invalid_data(array $data) : void
{
$request = new GroupAssignRequest();
$request = new GroupAssignRequest();
$validator = Validator::make($data, $request->rules());
$this->assertTrue($validator->fails());
@ -70,22 +69,21 @@ class GroupAssignRequestTest extends TestCase
{
return [
[[
'ids' => null // required
'ids' => null, // required
]],
[[
'ids' => '1,2,3' // array
'ids' => '1,2,3', // array
]],
[[
'ids' => [
'a', 'b', 'c' // array of integers
]
'a', 'b', 'c', // array of integers
],
]],
[[
'ids' => [
true, false // array of integers
]
true, false, // array of integers
],
]],
];
}
}

View File

@ -2,21 +2,17 @@
namespace Tests\Api\v1\Requests;
use App\Models\Group;
use App\Api\v1\Requests\GroupStoreRequest;
use App\Models\Group;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Tests\FeatureTestCase;
class GroupStoreRequestTest extends FeatureTestCase
{
use WithoutMiddleware;
/**
*
*/
protected String $uniqueGroupName = 'MyGroup';
/**
@ -38,7 +34,7 @@ class GroupStoreRequestTest extends FeatureTestCase
*/
public function test_valid_data(array $data) : void
{
$request = new GroupStoreRequest();
$request = new GroupStoreRequest();
$validator = Validator::make($data, $request->rules());
$this->assertFalse($validator->fails());
@ -51,7 +47,7 @@ class GroupStoreRequestTest extends FeatureTestCase
{
return [
[[
'name' => 'validWord'
'name' => 'validWord',
]],
];
}
@ -67,7 +63,7 @@ class GroupStoreRequestTest extends FeatureTestCase
$group->save();
$request = new GroupStoreRequest();
$request = new GroupStoreRequest();
$validator = Validator::make($data, $request->rules());
$this->assertTrue($validator->fails());
@ -80,21 +76,20 @@ class GroupStoreRequestTest extends FeatureTestCase
{
return [
[[
'name' => '' // required
'name' => '', // required
]],
[[
'name' => true // string
'name' => true, // string
]],
[[
'name' => 8 // string
'name' => 8, // string
]],
[[
'name' => 'mmmmmmoooooorrrrrreeeeeeettttttthhhhhhaaaaaaannnnnn32cccccchhhhhaaaaaarrrrrrsssssss' // max:32
'name' => 'mmmmmmoooooorrrrrreeeeeeettttttthhhhhhaaaaaaannnnnn32cccccchhhhhaaaaaarrrrrrsssssss', // max:32
]],
[[
'name' => $this->uniqueGroupName // unique
'name' => $this->uniqueGroupName, // unique
]],
];
}
}

Some files were not shown because too many files have changed in this diff Show More