forked from Alex/Pterodactyl-Panel
Implement changes to 2FA system (#761)
This commit is contained in:
parent
a0c96f2c15
commit
c7c2c1a45e
@ -17,6 +17,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
|
||||
|
||||
### Changed
|
||||
* Moved Docker image setting to be on the startup management page for a server rather than the details page. This value changes based on the Nest and Egg that are selected.
|
||||
* Two-Factor authentication tokens are now 32 bytes in length, and are stored encrypted at rest in the database.
|
||||
|
||||
## v0.7.0-beta.1 (Derelict Dermodactylus)
|
||||
### Added
|
||||
|
@ -202,7 +202,7 @@ class LoginController extends Controller
|
||||
return $this->sendFailedLoginResponse($request);
|
||||
}
|
||||
|
||||
if (! $G2FA->verifyKey($user->totp_secret, $request->input('2fa_token'), 2)) {
|
||||
if (! $G2FA->verifyKey(Crypt::decrypt($user->totp_secret), $request->input('2fa_token'), 2)) {
|
||||
event(new \Illuminate\Auth\Events\Failed($user, $credentials));
|
||||
|
||||
return $this->sendFailedLoginResponse($request);
|
||||
|
@ -27,7 +27,6 @@ namespace Pterodactyl\Http\Controllers\Base;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Illuminate\Contracts\Session\Session;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Services\Users\TwoFactorSetupService;
|
||||
use Pterodactyl\Services\Users\ToggleTwoFactorService;
|
||||
@ -52,11 +51,6 @@ class SecurityController extends Controller
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Session\Session
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService
|
||||
*/
|
||||
@ -72,7 +66,6 @@ class SecurityController extends Controller
|
||||
*
|
||||
* @param \Prologue\Alerts\AlertsMessageBag $alert
|
||||
* @param \Illuminate\Contracts\Config\Repository $config
|
||||
* @param \Illuminate\Contracts\Session\Session $session
|
||||
* @param \Pterodactyl\Contracts\Repository\SessionRepositoryInterface $repository
|
||||
* @param \Pterodactyl\Services\Users\ToggleTwoFactorService $toggleTwoFactorService
|
||||
* @param \Pterodactyl\Services\Users\TwoFactorSetupService $twoFactorSetupService
|
||||
@ -80,7 +73,6 @@ class SecurityController extends Controller
|
||||
public function __construct(
|
||||
AlertsMessageBag $alert,
|
||||
ConfigRepository $config,
|
||||
Session $session,
|
||||
SessionRepositoryInterface $repository,
|
||||
ToggleTwoFactorService $toggleTwoFactorService,
|
||||
TwoFactorSetupService $twoFactorSetupService
|
||||
@ -88,7 +80,6 @@ class SecurityController extends Controller
|
||||
$this->alert = $alert;
|
||||
$this->config = $config;
|
||||
$this->repository = $repository;
|
||||
$this->session = $session;
|
||||
$this->toggleTwoFactorService = $toggleTwoFactorService;
|
||||
$this->twoFactorSetupService = $twoFactorSetupService;
|
||||
}
|
||||
@ -122,7 +113,9 @@ class SecurityController extends Controller
|
||||
*/
|
||||
public function generateTotp(Request $request)
|
||||
{
|
||||
return response()->json($this->twoFactorSetupService->handle($request->user()));
|
||||
return response()->json([
|
||||
'qrImage' => $this->twoFactorSetupService->handle($request->user()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,6 +63,7 @@ class User extends Model implements
|
||||
'language',
|
||||
'use_totp',
|
||||
'totp_secret',
|
||||
'totp_authenticated_at',
|
||||
'gravatar',
|
||||
'root_admin',
|
||||
];
|
||||
@ -78,6 +79,11 @@ class User extends Model implements
|
||||
'gravatar' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = [self::CREATED_AT, self::UPDATED_AT, 'totp_authenticated_at'];
|
||||
|
||||
/**
|
||||
* The attributes excluded from the model's JSON form.
|
||||
*
|
||||
|
@ -1,66 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* This software is licensed under the terms of the MIT license.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Pterodactyl\Services\Users;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Pterodactyl\Models\User;
|
||||
use PragmaRX\Google2FA\Contracts\Google2FA;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
|
||||
|
||||
class ToggleTwoFactorService
|
||||
{
|
||||
/**
|
||||
* @var \PragmaRX\Google2FA\Contracts\Google2FA
|
||||
* @var \Illuminate\Contracts\Config\Repository
|
||||
*/
|
||||
protected $google2FA;
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Encryption\Encrypter
|
||||
*/
|
||||
private $encrypter;
|
||||
|
||||
/**
|
||||
* @var \PragmaRX\Google2FA\Google2FA
|
||||
*/
|
||||
private $google2FA;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
|
||||
*/
|
||||
protected $repository;
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* ToggleTwoFactorService constructor.
|
||||
*
|
||||
* @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA
|
||||
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
|
||||
* @param \PragmaRX\Google2FA\Google2FA $google2FA
|
||||
* @param \Illuminate\Contracts\Config\Repository $config
|
||||
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(
|
||||
Encrypter $encrypter,
|
||||
Google2FA $google2FA,
|
||||
Repository $config,
|
||||
UserRepositoryInterface $repository
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->encrypter = $encrypter;
|
||||
$this->google2FA = $google2FA;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|\Pterodactyl\Models\User $user
|
||||
* Toggle 2FA on an account only if the token provided is valid.
|
||||
*
|
||||
* @param \Pterodactyl\Models\User $user
|
||||
* @param string $token
|
||||
* @param null|bool $toggleState
|
||||
* @param bool|null $toggleState
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
* @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid
|
||||
*/
|
||||
public function handle($user, $token, $toggleState = null)
|
||||
public function handle(User $user, string $token, bool $toggleState = null): bool
|
||||
{
|
||||
if (! $user instanceof User) {
|
||||
$user = $this->repository->find($user);
|
||||
}
|
||||
$window = $this->config->get('pterodactyl.auth.2fa.window');
|
||||
$secret = $this->encrypter->decrypt($user->totp_secret);
|
||||
|
||||
if (! $this->google2FA->verifyKey($user->totp_secret, $token, 2)) {
|
||||
$isValidToken = $this->google2FA->verifyKey($secret, $token, $window);
|
||||
|
||||
if (! $isValidToken) {
|
||||
throw new TwoFactorAuthenticationTokenInvalid;
|
||||
}
|
||||
|
||||
$this->repository->withoutFresh()->update($user->id, [
|
||||
'totp_authenticated_at' => Carbon::now(),
|
||||
'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState),
|
||||
]);
|
||||
|
||||
|
@ -10,7 +10,8 @@
|
||||
namespace Pterodactyl\Services\Users;
|
||||
|
||||
use Pterodactyl\Models\User;
|
||||
use PragmaRX\Google2FA\Contracts\Google2FA;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
use Illuminate\Contracts\Config\Repository as ConfigRepository;
|
||||
|
||||
@ -19,58 +20,62 @@ class TwoFactorSetupService
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Config\Repository
|
||||
*/
|
||||
protected $config;
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var \PragmaRX\Google2FA\Contracts\Google2FA
|
||||
* @var \Illuminate\Contracts\Encryption\Encrypter
|
||||
*/
|
||||
protected $google2FA;
|
||||
private $encrypter;
|
||||
|
||||
/**
|
||||
* @var \PragmaRX\Google2FA\Google2FA
|
||||
*/
|
||||
private $google2FA;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
|
||||
*/
|
||||
protected $repository;
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* TwoFactorSetupService constructor.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Config\Repository $config
|
||||
* @param \PragmaRX\Google2FA\Contracts\Google2FA $google2FA
|
||||
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
|
||||
* @param \PragmaRX\Google2FA\Google2FA $google2FA
|
||||
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository
|
||||
*/
|
||||
public function __construct(
|
||||
ConfigRepository $config,
|
||||
Encrypter $encrypter,
|
||||
Google2FA $google2FA,
|
||||
UserRepositoryInterface $repository
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->encrypter = $encrypter;
|
||||
$this->google2FA = $google2FA;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a 2FA token and store it in the database.
|
||||
* Generate a 2FA token and store it in the database before returning the
|
||||
* QR code image.
|
||||
*
|
||||
* @param int|\Pterodactyl\Models\User $user
|
||||
* @return array
|
||||
* @param \Pterodactyl\Models\User $user
|
||||
* @return string
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
||||
*/
|
||||
public function handle($user)
|
||||
public function handle(User $user): string
|
||||
{
|
||||
if (! $user instanceof User) {
|
||||
$user = $this->repository->find($user);
|
||||
}
|
||||
|
||||
$secret = $this->google2FA->generateSecretKey();
|
||||
$secret = $this->google2FA->generateSecretKey($this->config->get('pterodactyl.auth.2fa.bytes'));
|
||||
$image = $this->google2FA->getQRCodeGoogleUrl($this->config->get('app.name'), $user->email, $secret);
|
||||
|
||||
$this->repository->withoutFresh()->update($user->id, ['totp_secret' => $secret]);
|
||||
$this->repository->withoutFresh()->update($user->id, [
|
||||
'totp_secret' => $this->encrypter->encrypt($secret),
|
||||
]);
|
||||
|
||||
return [
|
||||
'qrImage' => $image,
|
||||
'secret' => $secret,
|
||||
];
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@
|
||||
"mtdowling/cron-expression": "^1.2",
|
||||
"nesbot/carbon": "^1.22",
|
||||
"nicolaslopezj/searchable": "^1.9",
|
||||
"pragmarx/google2fa": "^1.0",
|
||||
"pragmarx/google2fa": "^2.0",
|
||||
"predis/predis": "^1.1",
|
||||
"prologue/alerts": "^0.4",
|
||||
"ramsey/uuid": "^3.7",
|
||||
@ -46,7 +46,7 @@
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^2.4",
|
||||
"barryvdh/laravel-ide-helper": "^2.4",
|
||||
"friendsofphp/php-cs-fixer": "^2.4",
|
||||
"friendsofphp/php-cs-fixer": "^2.8.0",
|
||||
"fzaninotto/faker": "^1.6",
|
||||
"mockery/mockery": "^0.9",
|
||||
"php-mock/php-mock-phpunit": "^1.1",
|
||||
|
189
composer.lock
generated
189
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3758867d4fb2d20e4b4e45b7c410f79b",
|
||||
"content-hash": "a393763d136e25a93fd5b636229496cf",
|
||||
"packages": [
|
||||
{
|
||||
"name": "appstract/laravel-blade-directives",
|
||||
@ -61,16 +61,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.36.37",
|
||||
"version": "3.38.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "a6d7fd9f32c63d018a6603a36174b4cb971fccd9"
|
||||
"reference": "9f704274f4748d2039a16d45b3388ed8dde74e89"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a6d7fd9f32c63d018a6603a36174b4cb971fccd9",
|
||||
"reference": "a6d7fd9f32c63d018a6603a36174b4cb971fccd9",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9f704274f4748d2039a16d45b3388ed8dde74e89",
|
||||
"reference": "9f704274f4748d2039a16d45b3388ed8dde74e89",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -137,61 +137,7 @@
|
||||
"s3",
|
||||
"sdk"
|
||||
],
|
||||
"time": "2017-11-03T16:39:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "christian-riesen/base32",
|
||||
"version": "1.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ChristianRiesen/base32.git",
|
||||
"reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa",
|
||||
"reference": "0a31e50c0fa9b1692d077c86ac188eecdcbaf7fa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*",
|
||||
"satooshi/php-coveralls": "0.*"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Base32\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Riesen",
|
||||
"email": "chris.riesen@gmail.com",
|
||||
"homepage": "http://christianriesen.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Base32 encoder/decoder according to RFC 4648",
|
||||
"homepage": "https://github.com/ChristianRiesen/base32",
|
||||
"keywords": [
|
||||
"base32",
|
||||
"decode",
|
||||
"encode",
|
||||
"rfc4648"
|
||||
],
|
||||
"time": "2016-05-05T11:49:03+00:00"
|
||||
"time": "2017-11-09T19:15:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "daneeveritt/login-notifications",
|
||||
@ -2055,6 +2001,68 @@
|
||||
],
|
||||
"time": "2017-11-04T11:48:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/constant_time_encoding",
|
||||
"version": "v2.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/constant_time_encoding.git",
|
||||
"reference": "9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4",
|
||||
"reference": "9e7d88e6e4015c2f06a3fa22f06e1d5faa77e6c4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6",
|
||||
"vimeo/psalm": "^0.3|^1"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ParagonIE\\ConstantTime\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Steve 'Sc00bz' Thomas",
|
||||
"email": "steve@tobtu.com",
|
||||
"homepage": "https://www.tobtu.com",
|
||||
"role": "Original Developer"
|
||||
}
|
||||
],
|
||||
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
|
||||
"keywords": [
|
||||
"base16",
|
||||
"base32",
|
||||
"base32_decode",
|
||||
"base32_encode",
|
||||
"base64",
|
||||
"base64_decode",
|
||||
"base64_encode",
|
||||
"bin2hex",
|
||||
"encoding",
|
||||
"hex",
|
||||
"hex2bin",
|
||||
"rfc4648"
|
||||
],
|
||||
"time": "2017-09-22T14:55:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v2.0.11",
|
||||
@ -2105,26 +2113,28 @@
|
||||
},
|
||||
{
|
||||
"name": "pragmarx/google2fa",
|
||||
"version": "v1.0.1",
|
||||
"version": "v2.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/antonioribeiro/google2fa.git",
|
||||
"reference": "b346dc138339b745c5831405d00cff7c1351aa0d"
|
||||
"reference": "bc2d654305e4d09254125f8cd390a7fbc4742d46"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/b346dc138339b745c5831405d00cff7c1351aa0d",
|
||||
"reference": "b346dc138339b745c5831405d00cff7c1351aa0d",
|
||||
"url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/bc2d654305e4d09254125f8cd390a7fbc4742d46",
|
||||
"reference": "bc2d654305e4d09254125f8cd390a7fbc4742d46",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"christian-riesen/base32": "~1.3",
|
||||
"paragonie/constant_time_encoding": "~1.0|~2.0",
|
||||
"paragonie/random_compat": "~1.4|~2.0",
|
||||
"php": ">=5.4",
|
||||
"symfony/polyfill-php56": "~1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpspec/phpspec": "~2.1"
|
||||
"bacon/bacon-qr-code": "~1.0",
|
||||
"phpspec/phpspec": "~2.1",
|
||||
"phpunit/phpunit": "~4"
|
||||
},
|
||||
"suggest": {
|
||||
"bacon/bacon-qr-code": "Required to generate inline QR Codes."
|
||||
@ -2132,11 +2142,8 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"component": "package",
|
||||
"frameworks": [
|
||||
"Laravel"
|
||||
],
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -2157,12 +2164,13 @@
|
||||
],
|
||||
"description": "A One Time Password Authentication package, compatible with Google Authenticator.",
|
||||
"keywords": [
|
||||
"2fa",
|
||||
"Authentication",
|
||||
"Two Factor Authentication",
|
||||
"google2fa",
|
||||
"laravel"
|
||||
],
|
||||
"time": "2016-07-18T20:25:04+00:00"
|
||||
"time": "2017-09-12T06:55:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "predis/predis",
|
||||
@ -3796,16 +3804,16 @@
|
||||
},
|
||||
{
|
||||
"name": "watson/validating",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dwightwatson/validating.git",
|
||||
"reference": "ade13078bf2e820e244603446114a28eda51b08c"
|
||||
"reference": "22edd06d45893f5d4f79c9e901bd7fbce174a79f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dwightwatson/validating/zipball/ade13078bf2e820e244603446114a28eda51b08c",
|
||||
"reference": "ade13078bf2e820e244603446114a28eda51b08c",
|
||||
"url": "https://api.github.com/repos/dwightwatson/validating/zipball/22edd06d45893f5d4f79c9e901bd7fbce174a79f",
|
||||
"reference": "22edd06d45893f5d4f79c9e901bd7fbce174a79f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -3842,7 +3850,7 @@
|
||||
"laravel",
|
||||
"validation"
|
||||
],
|
||||
"time": "2017-10-08T22:42:01+00:00"
|
||||
"time": "2017-11-06T21:35:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
@ -4291,16 +4299,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v2.8.0",
|
||||
"version": "v2.8.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
|
||||
"reference": "89e7b083f27241e03dd776cb8d6781c77e341db6"
|
||||
"reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/89e7b083f27241e03dd776cb8d6781c77e341db6",
|
||||
"reference": "89e7b083f27241e03dd776cb8d6781c77e341db6",
|
||||
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/04f71e56e03ba2627e345e8c949c80dcef0e683e",
|
||||
"reference": "04f71e56e03ba2627e345e8c949c80dcef0e683e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4367,7 +4375,7 @@
|
||||
}
|
||||
],
|
||||
"description": "A tool to automatically fix PHP code style",
|
||||
"time": "2017-11-03T02:21:46+00:00"
|
||||
"time": "2017-11-09T13:31:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fzaninotto/faker",
|
||||
@ -4421,23 +4429,23 @@
|
||||
},
|
||||
{
|
||||
"name": "gecko-packages/gecko-php-unit",
|
||||
"version": "v2.2",
|
||||
"version": "v3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GeckoPackages/GeckoPHPUnit.git",
|
||||
"reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1"
|
||||
"reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/ab525fac9a9ffea219687f261b02008b18ebf2d1",
|
||||
"reference": "ab525fac9a9ffea219687f261b02008b18ebf2d1",
|
||||
"url": "https://api.github.com/repos/GeckoPackages/GeckoPHPUnit/zipball/6a866551dffc2154c1b091bae3a7877d39c25ca3",
|
||||
"reference": "6a866551dffc2154c1b091bae3a7877d39c25ca3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3.6 || ^7.0"
|
||||
"php": "^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.4.3"
|
||||
"phpunit/phpunit": "^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "When testing with xml.",
|
||||
@ -4445,6 +4453,11 @@
|
||||
"phpunit/phpunit": "This is an extension for it so make sure you have it some way."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GeckoPackages\\PHPUnit\\": "src/PHPUnit"
|
||||
@ -4461,7 +4474,7 @@
|
||||
"filesystem",
|
||||
"phpunit"
|
||||
],
|
||||
"time": "2017-08-23T07:39:54+00:00"
|
||||
"time": "2017-08-23T07:46:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "hamcrest/hamcrest-php",
|
||||
|
@ -171,7 +171,6 @@ return [
|
||||
/*
|
||||
* Additional Dependencies
|
||||
*/
|
||||
PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider::class,
|
||||
igaster\laravelTheme\themeServiceProvider::class,
|
||||
Prologue\Alerts\AlertsServiceProvider::class,
|
||||
Krucas\Settings\Providers\SettingsServiceProvider::class,
|
||||
@ -213,7 +212,6 @@ return [
|
||||
'File' => Illuminate\Support\Facades\File::class,
|
||||
'Fractal' => Spatie\Fractal\FractalFacade::class,
|
||||
'Gate' => Illuminate\Support\Facades\Gate::class,
|
||||
'Google2FA' => PragmaRX\Google2FA\Vendor\Laravel\Facade::class,
|
||||
'Hash' => Illuminate\Support\Facades\Hash::class,
|
||||
'Input' => Illuminate\Support\Facades\Input::class,
|
||||
'Inspiring' => Illuminate\Foundation\Inspiring::class,
|
||||
|
@ -23,6 +23,11 @@ return [
|
||||
*/
|
||||
'auth' => [
|
||||
'notifications' => env('LOGIN_NOTIFICATIONS', false),
|
||||
'2fa' => [
|
||||
'bytes' => 32,
|
||||
'window' => env('APP_2FA_WINDOW', 4),
|
||||
'verify_newer' => true,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class Add2FaLastAuthorizationTimeColumn extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->text('totp_secret')->nullable()->change();
|
||||
$table->timestampTz('totp_authenticated_at')->after('totp_secret')->nullable();
|
||||
});
|
||||
|
||||
DB::transaction(function () {
|
||||
DB::table('users')->get()->each(function ($user) {
|
||||
if (is_null($user->totp_secret)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('users')->where('id', $user->id)->update([
|
||||
'totp_secret' => Crypt::encrypt($user->totp_secret),
|
||||
'updated_at' => Carbon::now()->toIso8601String(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
DB::transaction(function () {
|
||||
DB::table('users')->get()->each(function ($user) {
|
||||
if (is_null($user->totp_secret)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('users')->where('id', $user->id)->update([
|
||||
'totp_secret' => Crypt::decrypt($user->totp_secret),
|
||||
'updated_at' => Carbon::now()->toIso8601String(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
DB::statement('ALTER TABLE users MODIFY totp_secret CHAR(16) DEFAULT NULL');
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('totp_authenticated_at');
|
||||
});
|
||||
}
|
||||
}
|
@ -42,7 +42,6 @@ var TwoFactorModal = (function () {
|
||||
$('#qr_image_insert').attr('src', image.src).slideDown();
|
||||
});
|
||||
});
|
||||
$('#2fa_secret_insert').html(data.secret);
|
||||
$('#open2fa').modal('show');
|
||||
}).fail(function (jqXHR) {
|
||||
alert('An error occured while attempting to load the 2FA setup modal. Please try again.');
|
||||
|
@ -106,8 +106,8 @@
|
||||
<div class="col-md-12" id="notice_box_2fa" style="display:none;"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<center><span id="hide_img_load"><i class="fa fa-spinner fa-spin"></i> Loading QR Code...</span><img src="" id="qr_image_insert" style="display:none;"/><br /><code id="2fa_secret_insert"></code></center>
|
||||
<div class="col-md-6 text-center">
|
||||
<span id="hide_img_load"><i class="fa fa-spinner fa-spin"></i> Loading QR Code...</span><img src="" id="qr_image_insert" style="display:none;"/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="alert alert-info">@lang('base.security.2fa_checkpoint_help')</div>
|
||||
|
@ -1,69 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* This software is licensed under the terms of the MIT license.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Tests\Unit\Http\Controllers\Base;
|
||||
|
||||
use Mockery as m;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Models\User;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Illuminate\Contracts\Session\Session;
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Tests\Assertions\ControllerAssertionsTrait;
|
||||
use Tests\Unit\Http\Controllers\ControllerTestCase;
|
||||
use Pterodactyl\Services\Users\TwoFactorSetupService;
|
||||
use Pterodactyl\Services\Users\ToggleTwoFactorService;
|
||||
use Pterodactyl\Http\Controllers\Base\SecurityController;
|
||||
use Pterodactyl\Contracts\Repository\SessionRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
|
||||
|
||||
class SecurityControllerTest extends TestCase
|
||||
class SecurityControllerTest extends ControllerTestCase
|
||||
{
|
||||
use ControllerAssertionsTrait;
|
||||
|
||||
/**
|
||||
* @var \Prologue\Alerts\AlertsMessageBag
|
||||
* @var \Prologue\Alerts\AlertsMessageBag|\Mockery\Mock
|
||||
*/
|
||||
protected $alert;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Config\Repository
|
||||
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Http\Controllers\Base\SecurityController
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\SessionRepositoryInterface
|
||||
* @var \Pterodactyl\Contracts\Repository\SessionRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Http\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Session\Session
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService
|
||||
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService|\Mockery\Mock
|
||||
*/
|
||||
protected $toggleTwoFactorService;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Users\TwoFactorSetupService
|
||||
* @var \Pterodactyl\Services\Users\TwoFactorSetupService|\Mockery\Mock
|
||||
*/
|
||||
protected $twoFactorSetupService;
|
||||
|
||||
@ -77,19 +49,8 @@ class SecurityControllerTest extends TestCase
|
||||
$this->alert = m::mock(AlertsMessageBag::class);
|
||||
$this->config = m::mock(Repository::class);
|
||||
$this->repository = m::mock(SessionRepositoryInterface::class);
|
||||
$this->request = m::mock(Request::class);
|
||||
$this->session = m::mock(Session::class);
|
||||
$this->toggleTwoFactorService = m::mock(ToggleTwoFactorService::class);
|
||||
$this->twoFactorSetupService = m::mock(TwoFactorSetupService::class);
|
||||
|
||||
$this->controller = new SecurityController(
|
||||
$this->alert,
|
||||
$this->config,
|
||||
$this->session,
|
||||
$this->repository,
|
||||
$this->toggleTwoFactorService,
|
||||
$this->twoFactorSetupService
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,13 +58,12 @@ class SecurityControllerTest extends TestCase
|
||||
*/
|
||||
public function testIndexControllerWithDatabaseDriver()
|
||||
{
|
||||
$model = factory(User::class)->make();
|
||||
$model = $this->setRequestUser();
|
||||
|
||||
$this->config->shouldReceive('get')->with('session.driver')->once()->andReturn('database');
|
||||
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($model);
|
||||
$this->repository->shouldReceive('getUserSessions')->with($model->id)->once()->andReturn(['sessions']);
|
||||
|
||||
$response = $this->controller->index($this->request);
|
||||
$response = $this->getController()->index($this->request);
|
||||
$this->assertIsViewResponse($response);
|
||||
$this->assertViewNameEquals('base.security', $response);
|
||||
$this->assertViewHasKey('sessions', $response);
|
||||
@ -117,7 +77,7 @@ class SecurityControllerTest extends TestCase
|
||||
{
|
||||
$this->config->shouldReceive('get')->with('session.driver')->once()->andReturn('redis');
|
||||
|
||||
$response = $this->controller->index($this->request);
|
||||
$response = $this->getController()->index($this->request);
|
||||
$this->assertIsViewResponse($response);
|
||||
$this->assertViewNameEquals('base.security', $response);
|
||||
$this->assertViewHasKey('sessions', $response);
|
||||
@ -129,14 +89,13 @@ class SecurityControllerTest extends TestCase
|
||||
*/
|
||||
public function testGenerateTotpController()
|
||||
{
|
||||
$model = factory(User::class)->make();
|
||||
$model = $this->setRequestUser();
|
||||
|
||||
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($model);
|
||||
$this->twoFactorSetupService->shouldReceive('handle')->with($model)->once()->andReturn(['string']);
|
||||
$this->twoFactorSetupService->shouldReceive('handle')->with($model)->once()->andReturn('qrCodeImage');
|
||||
|
||||
$response = $this->controller->generateTotp($this->request);
|
||||
$response = $this->getController()->generateTotp($this->request);
|
||||
$this->assertIsJsonResponse($response);
|
||||
$this->assertResponseJsonEquals(['string'], $response);
|
||||
$this->assertResponseJsonEquals(['qrImage' => 'qrCodeImage'], $response);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -144,13 +103,12 @@ class SecurityControllerTest extends TestCase
|
||||
*/
|
||||
public function testDisableTotpControllerSuccess()
|
||||
{
|
||||
$model = factory(User::class)->make();
|
||||
$model = $this->setRequestUser();
|
||||
|
||||
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($model);
|
||||
$this->request->shouldReceive('input')->with('token')->once()->andReturn('testToken');
|
||||
$this->toggleTwoFactorService->shouldReceive('handle')->with($model, 'testToken', false)->once()->andReturnNull();
|
||||
|
||||
$response = $this->controller->disableTotp($this->request);
|
||||
$response = $this->getController()->disableTotp($this->request);
|
||||
$this->assertIsRedirectResponse($response);
|
||||
$this->assertRedirectRouteEquals('account.security', $response);
|
||||
}
|
||||
@ -160,16 +118,14 @@ class SecurityControllerTest extends TestCase
|
||||
*/
|
||||
public function testDisableTotpControllerWhenExceptionIsThrown()
|
||||
{
|
||||
$model = factory(User::class)->make();
|
||||
$model = $this->setRequestUser();
|
||||
|
||||
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($model);
|
||||
$this->request->shouldReceive('input')->with('token')->once()->andReturn('testToken');
|
||||
$this->toggleTwoFactorService->shouldReceive('handle')->with($model, 'testToken', false)->once()
|
||||
->andThrow(new TwoFactorAuthenticationTokenInvalid);
|
||||
$this->alert->shouldReceive('danger')->with(trans('base.security.2fa_disable_error'))->once()->andReturnSelf()
|
||||
->shouldReceive('flash')->withNoArgs()->once()->andReturnNull();
|
||||
$this->toggleTwoFactorService->shouldReceive('handle')->with($model, 'testToken', false)->once()->andThrow(new TwoFactorAuthenticationTokenInvalid);
|
||||
$this->alert->shouldReceive('danger')->with(trans('base.security.2fa_disable_error'))->once()->andReturnSelf();
|
||||
$this->alert->shouldReceive('flash')->withNoArgs()->once()->andReturnNull();
|
||||
|
||||
$response = $this->controller->disableTotp($this->request);
|
||||
$response = $this->getController()->disableTotp($this->request);
|
||||
$this->assertIsRedirectResponse($response);
|
||||
$this->assertRedirectRouteEquals('account.security', $response);
|
||||
}
|
||||
@ -179,13 +135,28 @@ class SecurityControllerTest extends TestCase
|
||||
*/
|
||||
public function testRevokeController()
|
||||
{
|
||||
$model = factory(User::class)->make();
|
||||
$model = $this->setRequestUser();
|
||||
|
||||
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($model);
|
||||
$this->repository->shouldReceive('deleteUserSession')->with($model->id, 123)->once()->andReturnNull();
|
||||
|
||||
$response = $this->controller->revoke($this->request, 123);
|
||||
$response = $this->getController()->revoke($this->request, 123);
|
||||
$this->assertIsRedirectResponse($response);
|
||||
$this->assertRedirectRouteEquals('account.security', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of the controller for testing with mocked dependencies.
|
||||
*
|
||||
* @return \Pterodactyl\Http\Controllers\Base\SecurityController
|
||||
*/
|
||||
private function getController(): SecurityController
|
||||
{
|
||||
return new SecurityController(
|
||||
$this->alert,
|
||||
$this->config,
|
||||
$this->repository,
|
||||
$this->toggleTwoFactorService,
|
||||
$this->twoFactorSetupService
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ class RunTaskJobTest extends TestCase
|
||||
{
|
||||
parent::setUp();
|
||||
Bus::fake();
|
||||
Carbon::setTestNow();
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$this->commandRepository = m::mock(CommandRepositoryInterface::class);
|
||||
$this->config = m::mock(Repository::class);
|
||||
|
@ -44,7 +44,7 @@ class DaemonKeyProviderServiceTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
Carbon::setTestNow();
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$this->keyCreationService = m::mock(DaemonKeyCreationService::class);
|
||||
$this->keyUpdateService = m::mock(DaemonKeyUpdateService::class);
|
||||
|
@ -1,37 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* This software is licensed under the terms of the MIT license.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Tests\Unit\Services\Users;
|
||||
|
||||
use Mockery as m;
|
||||
use Carbon\Carbon;
|
||||
use Tests\TestCase;
|
||||
use Pterodactyl\Models\User;
|
||||
use PragmaRX\Google2FA\Contracts\Google2FA;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Services\Users\ToggleTwoFactorService;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
|
||||
class ToggleTwoFactorServiceTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var \PragmaRX\Google2FA\Contracts\Google2FA
|
||||
*/
|
||||
protected $google2FA;
|
||||
const TEST_WINDOW_INT = 4;
|
||||
const USER_TOTP_SECRET = 'encryptedValue';
|
||||
const DECRYPTED_USER_SECRET = 'decryptedValue';
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
|
||||
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
|
||||
*/
|
||||
protected $repository;
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Users\ToggleTwoFactorService
|
||||
* @var \Illuminate\Contracts\Encryption\Encrypter|\Mockery\Mock
|
||||
*/
|
||||
protected $service;
|
||||
private $encrypter;
|
||||
|
||||
/**
|
||||
* @var \PragmaRX\Google2FA\Google2FA|\Mockery\Mock
|
||||
*/
|
||||
private $google2FA;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* Setup tests.
|
||||
@ -39,11 +44,15 @@ class ToggleTwoFactorServiceTest extends TestCase
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
Carbon::setTestNow(Carbon::now());
|
||||
|
||||
$this->config = m::mock(Repository::class);
|
||||
$this->encrypter = m::mock(Encrypter::class);
|
||||
$this->google2FA = m::mock(Google2FA::class);
|
||||
$this->repository = m::mock(UserRepositoryInterface::class);
|
||||
|
||||
$this->service = new ToggleTwoFactorService($this->google2FA, $this->repository);
|
||||
$this->config->shouldReceive('get')->with('pterodactyl.auth.2fa.window')->once()->andReturn(self::TEST_WINDOW_INT);
|
||||
$this->encrypter->shouldReceive('decrypt')->with(self::USER_TOTP_SECRET)->once()->andReturn(self::DECRYPTED_USER_SECRET);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,13 +60,15 @@ class ToggleTwoFactorServiceTest extends TestCase
|
||||
*/
|
||||
public function testTwoFactorIsEnabledForUser()
|
||||
{
|
||||
$model = factory(User::class)->make(['totp_secret' => 'secret', 'use_totp' => false]);
|
||||
$model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET, 'use_totp' => false]);
|
||||
|
||||
$this->google2FA->shouldReceive('verifyKey')->with($model->totp_secret, 'test-token', 2)->once()->andReturn(true);
|
||||
$this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf()
|
||||
->shouldReceive('update')->with($model->id, ['use_totp' => true])->once()->andReturnNull();
|
||||
$this->google2FA->shouldReceive('verifyKey')->with(self::DECRYPTED_USER_SECRET, 'test-token', self::TEST_WINDOW_INT)->once()->andReturn(true);
|
||||
$this->repository->shouldReceive('withoutFresh->update')->with($model->id, [
|
||||
'totp_authenticated_at' => Carbon::now(),
|
||||
'use_totp' => true,
|
||||
])->once()->andReturnNull();
|
||||
|
||||
$this->assertTrue($this->service->handle($model, 'test-token'));
|
||||
$this->assertTrue($this->getService()->handle($model, 'test-token'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,13 +76,15 @@ class ToggleTwoFactorServiceTest extends TestCase
|
||||
*/
|
||||
public function testTwoFactorIsDisabled()
|
||||
{
|
||||
$model = factory(User::class)->make(['totp_secret' => 'secret', 'use_totp' => true]);
|
||||
$model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET, 'use_totp' => true]);
|
||||
|
||||
$this->google2FA->shouldReceive('verifyKey')->with($model->totp_secret, 'test-token', 2)->once()->andReturn(true);
|
||||
$this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf()
|
||||
->shouldReceive('update')->with($model->id, ['use_totp' => false])->once()->andReturnNull();
|
||||
$this->google2FA->shouldReceive('verifyKey')->with(self::DECRYPTED_USER_SECRET, 'test-token', self::TEST_WINDOW_INT)->once()->andReturn(true);
|
||||
$this->repository->shouldReceive('withoutFresh->update')->with($model->id, [
|
||||
'totp_authenticated_at' => Carbon::now(),
|
||||
'use_totp' => false,
|
||||
])->once()->andReturnNull();
|
||||
|
||||
$this->assertTrue($this->service->handle($model, 'test-token'));
|
||||
$this->assertTrue($this->getService()->handle($model, 'test-token'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,13 +92,15 @@ class ToggleTwoFactorServiceTest extends TestCase
|
||||
*/
|
||||
public function testTwoFactorRemainsDisabledForUser()
|
||||
{
|
||||
$model = factory(User::class)->make(['totp_secret' => 'secret', 'use_totp' => false]);
|
||||
$model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET, 'use_totp' => false]);
|
||||
|
||||
$this->google2FA->shouldReceive('verifyKey')->with($model->totp_secret, 'test-token', 2)->once()->andReturn(true);
|
||||
$this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf()
|
||||
->shouldReceive('update')->with($model->id, ['use_totp' => false])->once()->andReturnNull();
|
||||
$this->google2FA->shouldReceive('verifyKey')->with(self::DECRYPTED_USER_SECRET, 'test-token', self::TEST_WINDOW_INT)->once()->andReturn(true);
|
||||
$this->repository->shouldReceive('withoutFresh->update')->with($model->id, [
|
||||
'totp_authenticated_at' => Carbon::now(),
|
||||
'use_totp' => false,
|
||||
])->once()->andReturnNull();
|
||||
|
||||
$this->assertTrue($this->service->handle($model, 'test-token', false));
|
||||
$this->assertTrue($this->getService()->handle($model, 'test-token', false));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,23 +110,19 @@ class ToggleTwoFactorServiceTest extends TestCase
|
||||
*/
|
||||
public function testExceptionIsThrownIfTokenIsInvalid()
|
||||
{
|
||||
$model = factory(User::class)->make();
|
||||
$model = factory(User::class)->make(['totp_secret' => self::USER_TOTP_SECRET]);
|
||||
$this->google2FA->shouldReceive('verifyKey')->once()->andReturn(false);
|
||||
|
||||
$this->service->handle($model, 'test-token');
|
||||
$this->getService()->handle($model, 'test-token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an integer can be passed in place of a user model.
|
||||
* Return an instance of the service with mocked dependencies.
|
||||
*
|
||||
* @return \Pterodactyl\Services\Users\ToggleTwoFactorService
|
||||
*/
|
||||
public function testIntegerCanBePassedInPlaceOfUserModel()
|
||||
private function getService(): ToggleTwoFactorService
|
||||
{
|
||||
$model = factory(User::class)->make(['totp_secret' => 'secret', 'use_totp' => false]);
|
||||
|
||||
$this->repository->shouldReceive('find')->with($model->id)->once()->andReturn($model);
|
||||
$this->google2FA->shouldReceive('verifyKey')->once()->andReturn(true);
|
||||
$this->repository->shouldReceive('withoutFresh->update')->once()->andReturnNull();
|
||||
|
||||
$this->assertTrue($this->service->handle($model->id, 'test-token'));
|
||||
return new ToggleTwoFactorService($this->encrypter, $this->google2FA, $this->config, $this->repository);
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Pterodactyl - Panel
|
||||
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
|
||||
*
|
||||
* This software is licensed under the terms of the MIT license.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
namespace Tests\Unit\Services\Users;
|
||||
|
||||
use Mockery as m;
|
||||
use Tests\TestCase;
|
||||
use Pterodactyl\Models\User;
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use PragmaRX\Google2FA\Contracts\Google2FA;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Services\Users\TwoFactorSetupService;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
|
||||
class TwoFactorSetupServiceTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Config\Repository
|
||||
* @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
|
||||
*/
|
||||
protected $config;
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var \PragmaRX\Google2FA\Contracts\Google2FA
|
||||
* @var \Illuminate\Contracts\Encryption\Encrypter|\Mockery\Mock
|
||||
*/
|
||||
protected $google2FA;
|
||||
private $encrypter;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
|
||||
* @var \PragmaRX\Google2FA\Google2FA|\Mockery\Mock
|
||||
*/
|
||||
protected $repository;
|
||||
private $google2FA;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Services\Users\TwoFactorSetupService
|
||||
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface|\Mockery\Mock
|
||||
*/
|
||||
protected $service;
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* Setup tests.
|
||||
@ -47,10 +41,9 @@ class TwoFactorSetupServiceTest extends TestCase
|
||||
parent::setUp();
|
||||
|
||||
$this->config = m::mock(Repository::class);
|
||||
$this->encrypter = m::mock(Encrypter::class);
|
||||
$this->google2FA = m::mock(Google2FA::class);
|
||||
$this->repository = m::mock(UserRepositoryInterface::class);
|
||||
|
||||
$this->service = new TwoFactorSetupService($this->config, $this->google2FA, $this->repository);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,34 +53,25 @@ class TwoFactorSetupServiceTest extends TestCase
|
||||
{
|
||||
$model = factory(User::class)->make();
|
||||
|
||||
$this->google2FA->shouldReceive('generateSecretKey')->withNoArgs()->once()->andReturn('secretKey');
|
||||
$this->config->shouldReceive('get')->with('pterodactyl.auth.2fa.bytes')->once()->andReturn(32);
|
||||
$this->google2FA->shouldReceive('generateSecretKey')->with(32)->once()->andReturn('secretKey');
|
||||
$this->config->shouldReceive('get')->with('app.name')->once()->andReturn('CompanyName');
|
||||
$this->google2FA->shouldReceive('getQRCodeGoogleUrl')->with('CompanyName', $model->email, 'secretKey')
|
||||
->once()->andReturn('http://url.com');
|
||||
$this->repository->shouldReceive('withoutFresh')->withNoArgs()->once()->andReturnSelf()
|
||||
->shouldReceive('update')->with($model->id, ['totp_secret' => 'secretKey'])->once()->andReturnNull();
|
||||
$this->google2FA->shouldReceive('getQRCodeGoogleUrl')->with('CompanyName', $model->email, 'secretKey')->once()->andReturn('http://url.com');
|
||||
$this->encrypter->shouldReceive('encrypt')->with('secretKey')->once()->andReturn('encryptedSecret');
|
||||
$this->repository->shouldReceive('withoutFresh->update')->with($model->id, ['totp_secret' => 'encryptedSecret'])->once()->andReturnNull();
|
||||
|
||||
$response = $this->service->handle($model);
|
||||
$response = $this->getService()->handle($model);
|
||||
$this->assertNotEmpty($response);
|
||||
$this->assertArrayHasKey('qrImage', $response);
|
||||
$this->assertArrayHasKey('secret', $response);
|
||||
$this->assertEquals('http://url.com', $response['qrImage']);
|
||||
$this->assertEquals('secretKey', $response['secret']);
|
||||
$this->assertSame('http://url.com', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an integer can be passed in place of the user model.
|
||||
* Return an instance of the service to test with mocked dependencies.
|
||||
*
|
||||
* @return \Pterodactyl\Services\Users\TwoFactorSetupService
|
||||
*/
|
||||
public function testIntegerCanBePassedInPlaceOfUserModel()
|
||||
private function getService(): TwoFactorSetupService
|
||||
{
|
||||
$model = factory(User::class)->make();
|
||||
|
||||
$this->repository->shouldReceive('find')->with($model->id)->once()->andReturn($model);
|
||||
$this->google2FA->shouldReceive('generateSecretKey')->withNoArgs()->once()->andReturnNull();
|
||||
$this->config->shouldReceive('get')->with('app.name')->once()->andReturnNull();
|
||||
$this->google2FA->shouldReceive('getQRCodeGoogleUrl')->once()->andReturnNull();
|
||||
$this->repository->shouldReceive('withoutFresh->update')->once()->andReturnNull();
|
||||
|
||||
$this->assertTrue(is_array($this->service->handle($model->id)));
|
||||
return new TwoFactorSetupService($this->config, $this->encrypter, $this->google2FA, $this->repository);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user