-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial rewrite for changes in the Bootstrapping module #18
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
--- | ||
engines: | ||
duplication: | ||
enabled: true | ||
config: | ||
languages: | ||
- php | ||
fixme: | ||
enabled: true | ||
phpmd: | ||
enabled: true | ||
ratings: | ||
paths: | ||
- "**.php" | ||
exclude_paths: | ||
- docs/* | ||
- tests/* | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
inherit: true | ||
|
||
build: | ||
nodes: | ||
analysis: | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,50 @@ | ||
{ | ||
"name": "elliot-sawyer/totp-authenticator", | ||
"description": "Enable 2FA authentication with TOTP", | ||
"type": "silverstripe-vendormodule", | ||
"license": "BSD-3-Clause", | ||
"keywords": [ | ||
"silverstripe", | ||
"2-factor", | ||
"authentication", | ||
"module", | ||
"security" | ||
], | ||
"authors": [ | ||
{ | ||
"name": "Elliot Sawyer" | ||
} | ||
], | ||
"require": { | ||
"firesphere/bootstrapmfa": "dev-master#f0fc8eb861b7e5dca3398dd9f59a382f5cdf5a23", | ||
"endroid/qr-code": "3.2.12", | ||
"lfkeitel/phptotp": "^1.0", | ||
"silverstripe/framework": "^4", | ||
"silverstripe/siteconfig": "^4" | ||
}, | ||
"require-dev": { | ||
"phpunit/phpunit": "^5.7", | ||
"squizlabs/php_codesniffer": "^3.0" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"ElliotSawyer\\TOTPAuthenticator\\": "src/", | ||
"ElliotSawyer\\TOTPAuthenticator\\Tests\\": "tests/" | ||
} | ||
}, | ||
"extra": { | ||
"branch-alias": { | ||
"dev-master": "1.0.x-dev" | ||
} | ||
}, | ||
"support": { | ||
"issues": "https://github.com/elliot-sawyer/totp-authenticator/issues", | ||
"source": "https://github.com/elliot-sawyer/totp-authenticator" | ||
}, | ||
"minimum-stability": "dev", | ||
"prefer-stable": true | ||
"name": "elliot-sawyer/totp-authenticator", | ||
"description": "Enable 2FA authentication with TOTP", | ||
"type": "silverstripe-vendormodule", | ||
"license": "BSD-3-Clause", | ||
"keywords": [ | ||
"silverstripe", | ||
"2-factor", | ||
"authentication", | ||
"module", | ||
"security", | ||
"TOTP", | ||
"Authy", | ||
"GoogleAuthenticator" | ||
], | ||
"authors": [ | ||
{ | ||
"name": "Elliot Sawyer" | ||
} | ||
], | ||
"require": { | ||
"firesphere/bootstrapmfa": "dev-extension-hook-before-mfa", | ||
"endroid/qr-code": "3.2.12", | ||
"spomky-labs/otphp": "^9.1", | ||
"silverstripe/framework": "^4" | ||
}, | ||
"require-dev": { | ||
"roave/security-advisories": "dev-master", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't a standardised dependency so we shouldn't add it yet (see silverstripe/silverstripe-framework#8548) |
||
"phpunit/phpunit": "^5.7", | ||
"squizlabs/php_codesniffer": "^3.0" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"ElliotSawyer\\TOTPAuthenticator\\": "src/", | ||
"ElliotSawyer\\TOTPAuthenticator\\Tests\\": "tests/" | ||
} | ||
}, | ||
"extra": { | ||
"installer-name": "totp-authenticator", | ||
"branch-alias": { | ||
"dev-master": "1.0.x-dev" | ||
} | ||
}, | ||
"support": { | ||
"issues": "https://github.com/elliot-sawyer/totp-authenticator/issues", | ||
"source": "https://github.com/elliot-sawyer/totp-authenticator" | ||
}, | ||
"minimum-stability": "dev", | ||
"prefer-stable": true | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -3,18 +3,21 @@ | |||||
namespace ElliotSawyer\TOTPAuthenticator; | ||||||
|
||||||
use Firesphere\BootstrapMFA\Authenticators\BootstrapMFAAuthenticator; | ||||||
use lfkeitel\phptotp\Base32; | ||||||
use lfkeitel\phptotp\Totp; | ||||||
use SilverStripe\Control\HTTPRequest; | ||||||
use SilverStripe\Core\Config\Configurable; | ||||||
use Firesphere\BootstrapMFA\Forms\BootstrapMFALoginForm; | ||||||
use Firesphere\BootstrapMFA\Handlers\BootstrapMFALoginHandler; | ||||||
use Firesphere\BootstrapMFA\Interfaces\MFAAuthenticator; | ||||||
use OTPHP\TOTP; | ||||||
use SilverStripe\Core\Injector\Injector; | ||||||
use SilverStripe\ORM\ValidationResult; | ||||||
use SilverStripe\Security\Member; | ||||||
|
||||||
/** | ||||||
* Class TOTPAuthenticator | ||||||
* @package ElliotSawyer\TOTPAuthenticator | ||||||
*/ | ||||||
class TOTPAuthenticator extends BootstrapMFAAuthenticator | ||||||
class TOTPAuthenticator extends BootstrapMFAAuthenticator implements MFAAuthenticator | ||||||
{ | ||||||
use Configurable; | ||||||
|
||||||
|
@@ -25,44 +28,54 @@ class TOTPAuthenticator extends BootstrapMFAAuthenticator | |||||
* a specific TOTP authenticator | ||||||
*/ | ||||||
private static $algorithm = 'sha1'; | ||||||
|
||||||
|
||||||
|
||||||
/** | ||||||
* @param string $link | ||||||
* @return \SilverStripe\Security\MemberAuthenticator\LoginHandler|static | ||||||
* Get configured algorithm for TOTP Authenticator | ||||||
* | ||||||
* Must be one of: "sha1", "sha256", "sha512" | ||||||
* If not specified or invalid, default to "sha1" | ||||||
* @return string | ||||||
*/ | ||||||
public function getLoginHandler($link) | ||||||
public static function get_algorithm() | ||||||
robbieaverill marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
{ | ||||||
return TOTPLoginHandler::create($link, $this); | ||||||
$algorithm = self::config()->get('algorithm'); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be |
||||||
|
||||||
return in_array(strtolower($algorithm), ['sha1', 'sha256', 'sha512']) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could make this either a config array OR a class constant (if we don't want it to be configurable) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd go with constants, but I think it came from a change @elliot-sawyer made somewhere else |
||||||
? $algorithm | ||||||
: 'sha1'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you did go with the above, then this could also be a constant e.g. |
||||||
} | ||||||
|
||||||
/** | ||||||
* @param array $data | ||||||
* @param HTTPRequest $request | ||||||
* @param $token | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor point: missing the type |
||||||
* @param ValidationResult $result | ||||||
* @return bool|null|Member | ||||||
* @throws \Exception | ||||||
*/ | ||||||
public function validateTOTP($data, $request, &$result) | ||||||
public function verifyMFA($data, $request, $token, &$result) | ||||||
{ | ||||||
$memberID = $request->getSession()->get(BootstrapMFAAuthenticator::SESSION_KEY . '.MemberID'); | ||||||
|
||||||
// First, let's see if we know the member | ||||||
/** @var Member $member */ | ||||||
/** @var Member|null $member */ | ||||||
$member = Member::get()->byID($memberID); | ||||||
|
||||||
// Continue if we have a valid member | ||||||
if ($member && $member instanceof Member) { | ||||||
if (!isset($data['token'])) { | ||||||
if (!$token) { | ||||||
$member->registerFailedLogin(); | ||||||
|
||||||
$result->addError(_t(self::class . '.NOTOKEN', 'No token sent')); | ||||||
} else { | ||||||
$secret = Base32::decode($member->TOTPSecret); | ||||||
$key = $this->getTokenFromTOTP($secret); | ||||||
$user_submitted_key = $data['token']; | ||||||
/** @var TOTPProvider $provider */ | ||||||
$provider = Injector::inst()->get(TOTPProvider::class); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this might be better as a dependency? private static $dependencies = [
'totpProvider' => TOTPProvider::class,
];
protected $totpProvider;
public function setTOTPProvider(TOTPProvider $provider) ;
public function getTOTPProvider(); |
||||||
$provider->setMember($member); | ||||||
/** @var TOTP $totp */ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At this point we know that Overloading it actually points out that the logic below is missing a check for a falsy return, which is a valid part of its API There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not overloading, it's my habit of adding these docs everywhere |
||||||
$totp = $provider->fetchToken($token); | ||||||
|
||||||
|
||||||
if ($user_submitted_key !== $key) { | ||||||
if (!$totp->verify($token)) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
$result->addError(_t(self::class . '.TOTPFAILED', 'TOTP Failed')); | ||||||
} | ||||||
} | ||||||
|
@@ -71,43 +84,26 @@ public function validateTOTP($data, $request, &$result) | |||||
if ($result->isValid()) { | ||||||
return $member; | ||||||
} | ||||||
} else { | ||||||
$result->addError(_t(self::class . '.NOMEMBER', 'Member not found')); | ||||||
} | ||||||
|
||||||
$result->addError(_t(self::class . '.NOMEMBER', 'Member not found')); | ||||||
|
||||||
return $result; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Given a TOTP secret, use Totp to resolve to a one time token | ||||||
* | ||||||
* @param string $secret | ||||||
* @param string $algorithm If not provided, will default to the configured algorithm | ||||||
* @return bool|int|string | ||||||
* @param BootstrapMFALoginHandler $controller | ||||||
* @param string $name | ||||||
* @return TOTPForm|BootstrapMFALoginForm | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could possibly benefit from being a bit tighter. I see two possibilities:
|
||||||
*/ | ||||||
protected function getTokenFromTOTP($secret, $algorithm = '') | ||||||
public function getMFAForm($controller, $name) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{ | ||||||
if (!$algorithm) { | ||||||
$algorithm = self::get_algorithm(); | ||||||
} | ||||||
|
||||||
$totp = new Totp($algorithm); | ||||||
return $totp->GenerateToken($secret); | ||||||
return TOTPForm::create($controller, $name, $this); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Get configured algorithm for TOTP Authenticator | ||||||
* | ||||||
* Must be one of: "sha1", "sha256", "sha512" | ||||||
* If not specified or invalid, default to "sha1" | ||||||
* @return string | ||||||
*/ | ||||||
public static function get_algorithm() | ||||||
public function getTokenField() | ||||||
{ | ||||||
$algorithm = self::config()->get('algorithm'); | ||||||
|
||||||
return in_array(strtolower($algorithm), ['sha1', 'sha256', 'sha512']) | ||||||
? $algorithm | ||||||
: 'sha1'; | ||||||
return 'token'; | ||||||
} | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't use codeclimate as a part of our regular CI toolchain at the moment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, it's just a copy-paste from other modules. I don't mind removing it