2FAuth/app/Services/Migrators/GoogleAuthMigrator.php

85 lines
3.9 KiB
PHP
Raw Normal View History

<?php
namespace App\Services\Migrators;
2022-11-22 15:15:52 +01:00
use App\Exceptions\InvalidMigrationDataException;
use App\Models\TwoFAccount;
use App\Protobuf\GAuthValueMapping;
use App\Protobuf\GoogleAuth\Payload;
use App\Protobuf\GoogleAuth\Payload\Algorithm;
use App\Protobuf\GoogleAuth\Payload\DigitCount;
2022-11-22 15:15:52 +01:00
use App\Protobuf\GoogleAuth\Payload\OtpType;
use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
2022-11-22 15:15:52 +01:00
use ParagonIE\ConstantTime\Base32;
class GoogleAuthMigrator extends Migrator
{
/**
* Convert Google Authenticator migration URI to a set of TwoFAccount objects.
2022-11-22 15:15:52 +01:00
*
2024-04-20 19:03:44 +02:00
* @param mixed $migrationPayload migration uri provided by Google Authenticator export feature
2022-11-21 11:16:43 +01:00
* @return \Illuminate\Support\Collection<int|string, \App\Models\TwoFAccount> The converted accounts
*/
2022-12-13 12:07:29 +01:00
public function migrate(mixed $migrationPayload) : Collection
{
try {
2024-04-20 19:03:44 +02:00
$migrationData = base64_decode(urldecode(Str::replace('otpauth-migration://offline?data=', '', strval($migrationPayload))));
2022-11-22 15:15:52 +01:00
$protobuf = new Payload();
$protobuf->mergeFromString($migrationData);
$otpParameters = $protobuf->getOtpParameters();
2022-11-22 15:15:52 +01:00
} catch (Exception $ex) {
Log::error('Protobuf failed to get OTP parameters from provided migration URI');
Log::error($ex->getMessage());
throw new InvalidMigrationDataException('Google Authenticator');
}
2022-11-22 15:15:52 +01:00
$twofaccounts = [];
2022-11-22 15:15:52 +01:00
foreach ($otpParameters->getIterator() as $key => $otp_parameters) {
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'] = $this->toBase32($otp_parameters->getSecret());
2022-11-22 15:15:52 +01:00
$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);
2022-11-22 15:15:52 +01:00
} catch (Exception $exception) {
Log::error(sprintf('Cannot instanciate a TwoFAccount object with OTP parameters from imported item #%s', $key));
2022-12-09 10:55:39 +01:00
Log::debug($exception->getMessage());
// The token failed to generate a valid account so we create a fake account to be returned.
2022-11-22 15:15:52 +01:00
$fakeAccount = new TwoFAccount();
2022-12-09 10:52:17 +01:00
$fakeAccount->id = TwoFAccount::FAKE_ID;
2022-11-22 15:15:52 +01:00
$fakeAccount->otp_type = $fakeAccount::TOTP;
// Only basic fields are filled to limit the risk of another exception.
2022-11-22 15:15:52 +01:00
$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.
2022-11-22 15:15:52 +01:00
$fakeAccount->secret = $exception->getMessage();
$twofaccounts[$key] = $fakeAccount;
}
}
return collect($twofaccounts);
}
/**
* Encode into uppercase Base32
*/
2024-05-29 11:53:41 +02:00
protected function toBase32(string $str)
{
return Base32::encodeUpper($str);
}
}