mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-05-20 14:10:54 +02:00
Move G-Auth import logic from controller to service
This commit is contained in:
parent
e97f6cfbc6
commit
c20e5f79ef
@ -129,50 +129,11 @@ public function update(TwoFAccountUpdateRequest $request, TwoFAccount $twofaccou
|
|||||||
* @return \App\Api\v1\Resources\TwoFAccountCollection
|
* @return \App\Api\v1\Resources\TwoFAccountCollection
|
||||||
*/
|
*/
|
||||||
public function import(TwoFAccountImportRequest $request)
|
public function import(TwoFAccountImportRequest $request)
|
||||||
{
|
{
|
||||||
$ALGORITHM = [
|
$request->merge(['withSecret' => true]);
|
||||||
'',
|
$twofaccounts = $this->twofaccountService->convertMigrationFromGA($request->uri);
|
||||||
'sha1',
|
|
||||||
'sha256',
|
|
||||||
'sha512',
|
|
||||||
'md5'
|
|
||||||
];
|
|
||||||
|
|
||||||
$DIGIT_COUNT = [
|
return new TwoFAccountCollection($twofaccounts);
|
||||||
'',
|
|
||||||
6,
|
|
||||||
8
|
|
||||||
];
|
|
||||||
|
|
||||||
$OTP_TYPE = [
|
|
||||||
'',
|
|
||||||
'hotp',
|
|
||||||
'totp'
|
|
||||||
];
|
|
||||||
|
|
||||||
// require_once base_path('protobuf/SearchRequest.php');
|
|
||||||
// $uri = 'otpauth-migration://offline?data=CjUKCi8gSXtDdoRpZEkSEWVkb3VhcmRAZ2FuZWF1Lm1lGg5iYW5rLmdhbmVhdS5tZSABKAEwAhABGAEgAA==';
|
|
||||||
// $uri = base64_decode(urldecode('CjUKCi8gSXtDdoRpZEkSEWVkb3VhcmRAZ2FuZWF1Lm1lGg5iYW5rLmdhbmVhdS5tZSABKAEwAhABGAEgAA=='));
|
|
||||||
// $uri = 'otpauth-migration://offline?data=CiQKCj1PS8k1EUgVI0ESB0BidWJrYV8aB1R3aXR0ZXIgASgBMAIKIQoK6/l62ezmsWvMNRIFQnVia2EaBkdpdEh1YiABKAEwAhABGAEgAA==';
|
|
||||||
// $uri = base64_decode(urldecode('CiQKCj1PS8k1EUgVI0ESB0BidWJrYV8aB1R3aXR0ZXIgASgBMAIKIQoK6/l62ezmsWvMNRIFQnVia2EaBkdpdEh1YiABKAEwAhABGAEgAA=='));
|
|
||||||
|
|
||||||
$data = base64_decode(urldecode(Str::replace('otpauth-migration://offline?data=', '', $request->uri)));
|
|
||||||
|
|
||||||
$proto = new \App\Protobuf\GoogleAuth\Payload();
|
|
||||||
$proto->mergeFromString($data);
|
|
||||||
$otpParameters = $proto->getOtpParameters();
|
|
||||||
|
|
||||||
foreach ($otpParameters->getIterator() as $key => $otp_parameters) {
|
|
||||||
$out[$key]['secret'] = \ParagonIE\ConstantTime\Base32::encodeUpper($otp_parameters->getSecret());
|
|
||||||
$out[$key]['account'] = $otp_parameters->getName();
|
|
||||||
$out[$key]['service'] = $otp_parameters->getIssuer();
|
|
||||||
$out[$key]['algorithm'] = $ALGORITHM[$otp_parameters->getalgorithm()];
|
|
||||||
$out[$key]['digits'] = $DIGIT_COUNT[$otp_parameters->getDigits()];
|
|
||||||
$out[$key]['otp_type'] = $OTP_TYPE[$otp_parameters->getType()];
|
|
||||||
$out[$key]['counter'] = $otp_parameters->getCounter();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($out, 200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,6 +60,11 @@ public function register()
|
|||||||
'message' => $exception->getMessage()], 400);
|
'message' => $exception->getMessage()], 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->renderable(function (InvalidGoogleAuthMigration $exception, $request) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => __('errors.invalid_google_auth_migration')], 400);
|
||||||
|
});
|
||||||
|
|
||||||
$this->renderable(function (\Illuminate\Auth\AuthenticationException $exception, $request) {
|
$this->renderable(function (\Illuminate\Auth\AuthenticationException $exception, $request) {
|
||||||
if ($exception->guards() === ['reverse-proxy-guard']) {
|
if ($exception->guards() === ['reverse-proxy-guard']) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
14
app/Exceptions/InvalidGoogleAuthMigration.php
Normal file
14
app/Exceptions/InvalidGoogleAuthMigration.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class UndecipherableException.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
class InvalidGoogleAuthMigration extends Exception
|
||||||
|
{
|
||||||
|
}
|
30
app/Protobuf/GAuthValueMapping.php
Normal file
30
app/Protobuf/GAuthValueMapping.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||||
|
# source: GoogleAuth.proto
|
||||||
|
|
||||||
|
namespace App\Protobuf;
|
||||||
|
|
||||||
|
class GAuthValueMapping
|
||||||
|
{
|
||||||
|
const ALGORITHM = [
|
||||||
|
'ALGORITHM_UNSPECIFIED' => '',
|
||||||
|
'ALGORITHM_SHA1' => 'sha1',
|
||||||
|
'ALGORITHM_SHA256' => 'sha256',
|
||||||
|
'ALGORITHM_SHA512' => 'sha512',
|
||||||
|
'ALGORITHM_MD5' => 'md5'
|
||||||
|
];
|
||||||
|
|
||||||
|
const DIGIT_COUNT = [
|
||||||
|
'DIGIT_COUNT_UNSPECIFIED' => '',
|
||||||
|
'DIGIT_COUNT_SIX' => 6,
|
||||||
|
'DIGIT_COUNT_EIGHT' => 8
|
||||||
|
];
|
||||||
|
|
||||||
|
const OTP_TYPE = [
|
||||||
|
'OTP_TYPE_UNSPECIFIED' => '',
|
||||||
|
'OTP_TYPE_HOTP' => 'hotp',
|
||||||
|
'OTP_TYPE_TOTP' => 'totp'
|
||||||
|
];
|
||||||
|
|
||||||
|
private function __construct() {}
|
||||||
|
}
|
7
app/Protobuf/README.md
Normal file
7
app/Protobuf/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Protobuf class generation
|
||||||
|
|
||||||
|
## cli
|
||||||
|
|
||||||
|
```sh
|
||||||
|
protoc --proto_path=app/Protobuf/ --php_out=. app/Protobuf/GoogleAuth.proto
|
||||||
|
```
|
@ -6,16 +6,25 @@
|
|||||||
use App\Exceptions\InvalidSecretException;
|
use App\Exceptions\InvalidSecretException;
|
||||||
use App\Exceptions\InvalidOtpParameterException;
|
use App\Exceptions\InvalidOtpParameterException;
|
||||||
use App\Exceptions\UndecipherableException;
|
use App\Exceptions\UndecipherableException;
|
||||||
|
use App\Exceptions\InvalidGoogleAuthMigration;
|
||||||
use App\Services\Dto\OtpDto;
|
use App\Services\Dto\OtpDto;
|
||||||
use App\Services\Dto\TwoFAccountDto;
|
use App\Services\Dto\TwoFAccountDto;
|
||||||
|
use Exception;
|
||||||
use OTPHP\TOTP;
|
use OTPHP\TOTP;
|
||||||
use OTPHP\HOTP;
|
use OTPHP\HOTP;
|
||||||
use OTPHP\Factory;
|
use OTPHP\Factory;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
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;
|
||||||
|
|
||||||
class TwoFAccountService
|
class TwoFAccountService
|
||||||
{
|
{
|
||||||
@ -228,6 +237,66 @@ public function delete($ids) : int
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Google Authenticator migration URI to a set of TwoFAccount objects
|
||||||
|
*
|
||||||
|
* @param string $migrationUri migration uri provided by Google Authenticator export feature
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Support\Collection The converted accounts
|
||||||
|
*/
|
||||||
|
public function convertMigrationFromGA($migrationUri) : Collection
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$migrationData = base64_decode(urldecode(Str::replace('otpauth-migration://offline?data=', '', $migrationUri)));
|
||||||
|
$protobuf = new Payload();
|
||||||
|
$protobuf->mergeFromString($migrationData);
|
||||||
|
$otpParameters = $protobuf->getOtpParameters();
|
||||||
|
}
|
||||||
|
catch (Exception $ex) {
|
||||||
|
Log::error("Protobuf failed to get OTP parameters from provided migration URI");
|
||||||
|
Log::error($ex->getMessage());
|
||||||
|
|
||||||
|
throw new InvalidGoogleAuthMigration();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($otpParameters->getIterator() as $key => $otp_parameters) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
$parameters['otp_type'] = GAuthValueMapping::OTP_TYPE[OtpType::name($otp_parameters->getType())];
|
||||||
|
$parameters['account'] = $otp_parameters->getName();
|
||||||
|
$parameters['service'] = $otp_parameters->getIssuer();
|
||||||
|
$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'] = $otp_parameters->getCounter();
|
||||||
|
// $parameters['period'] = $otp_parameters->getPeriod();
|
||||||
|
|
||||||
|
$twofaccounts[$key] = $this->createFromParameters($parameters, false);
|
||||||
|
}
|
||||||
|
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 = 'totp';
|
||||||
|
// Only basic fields are filled to limit the risk of another exception.
|
||||||
|
$fakeAccount->account = $otp_parameters->getName();
|
||||||
|
$fakeAccount->service = $otp_parameters->getIssuer();
|
||||||
|
// The secret field is used to pass the error, not very clean but will do the job for now.
|
||||||
|
$fakeAccount->secret = $exception->getMessage();
|
||||||
|
|
||||||
|
$twofaccounts[$key] = $fakeAccount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->markAsDuplicate(collect($twofaccounts));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ########################################################################################################################
|
// ########################################################################################################################
|
||||||
// ########################################################################################################################
|
// ########################################################################################################################
|
||||||
// ########################################################################################################################
|
// ########################################################################################################################
|
||||||
@ -473,4 +542,33 @@ private function storeTokenImageAsIcon()
|
|||||||
}
|
}
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the given collection with items marked as Duplicates (using id=-1) if a similar record exists in database
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Support\Collection
|
||||||
|
* @return \Illuminate\Support\Collection
|
||||||
|
*/
|
||||||
|
private function markAsDuplicate($twofaccounts) : Collection
|
||||||
|
{
|
||||||
|
$storage = TwoFAccount::all();
|
||||||
|
|
||||||
|
$twofaccounts = $twofaccounts->map(function ($twofaccount, $key) use ($storage) {
|
||||||
|
if ($storage->contains(function ($value, $key) use ($twofaccount) {
|
||||||
|
return $value->secret == $twofaccount->secret
|
||||||
|
&& $value->service == $twofaccount->service
|
||||||
|
&& $value->account == $twofaccount->account
|
||||||
|
&& $value->otp_type == $twofaccount->otp_type
|
||||||
|
&& $value->digits == $twofaccount->digits
|
||||||
|
&& $value->algorithm == $twofaccount->algorithm;
|
||||||
|
})) {
|
||||||
|
$twofaccount->id = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $twofaccount;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $twofaccounts;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user