Skip to content

Commit

Permalink
Initial rewrite for changes in the Bootstrapping module
Browse files Browse the repository at this point in the history
  • Loading branch information
Firesphere committed Nov 29, 2018
1 parent cc99cc4 commit b960f46
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 248 deletions.
17 changes: 17 additions & 0 deletions .codeclimate.yml
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/*
1 change: 0 additions & 1 deletion .scrutinizer.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
inherit: true

build:
nodes:
analysis:
Expand Down
12 changes: 0 additions & 12 deletions _config/security.yml

This file was deleted.

92 changes: 48 additions & 44 deletions composer.json
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",
"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
}
80 changes: 38 additions & 42 deletions src/Authenticators/TOTPAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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()
{
return TOTPLoginHandler::create($link, $this);
$algorithm = self::config()->get('algorithm');

return in_array(strtolower($algorithm), ['sha1', 'sha256', 'sha512'])
? $algorithm
: 'sha1';
}

/**
* @param array $data
* @param HTTPRequest $request
* @param $token
* @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);
$provider->setMember($member);
/** @var TOTP $totp */
$totp = $provider->fetchToken($token);


if ($user_submitted_key !== $key) {
if (!$totp->verify($token)) {
$result->addError(_t(self::class . '.TOTPFAILED', 'TOTP Failed'));
}
}
Expand All @@ -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
*/
protected function getTokenFromTOTP($secret, $algorithm = '')
public function getMFAForm($controller, $name)
{
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';
}
}
48 changes: 19 additions & 29 deletions src/Extensions/MemberExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@

namespace ElliotSawyer\TOTPAuthenticator;

use Endroid\QrCode\Exception\InvalidWriterException;
use Endroid\QrCode\QrCode;
use lfkeitel\phptotp\Base32;
use lfkeitel\phptotp\Totp;
use OTPHP\TOTP;
use ParagonIE\ConstantTime\Base32;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\LiteralField;
use SilverStripe\Forms\ToggleCompositeField;
use SilverStripe\ORM\DataExtension;
use SilverStripe\Security\Member;
use SilverStripe\SiteConfig\SiteConfig;

/**
* Class MemberExtension
*
* @package ElliotSawyer\TOTPAuthenticator
* @property MemberExtension $owner
* @property Member|MemberExtension $owner
* @property string $TOTPSecret
*/
class MemberExtension extends DataExtension
Expand All @@ -36,62 +36,52 @@ public function onBeforeWrite()
// Only regenerate if there is no secret and MFA is not enabled yet
// Inherits MFAEnabled from Bootstrap object extension
if (!$this->owner->TOTPSecret || !$this->owner->MFAEnabled) {
$secret = Totp::GenerateSecret(16);
$secret = Base32::encode($secret);
$secret = Base32::encodeUpper(random_bytes(128)); // We generate our own 1024 bits secret
$this->owner->TOTPSecret = $secret;
}
}

/**
* @param FieldList $fields
* @throws InvalidWriterException
*/
public function updateCMSFields(FieldList $fields)
{
if (!$this->owner->exists()) {
$fields->removeByName('TOTPSecret');
}

if (strlen($this->owner->TOTPSecret)) {
$qrcodeURI = $this->GoogleAuthenticatorQRCode();
if ($this->owner->TOTPSecret !== '') {
$qrcodeURI = $this->getQRCode();
$fields->addFieldToTab('Root.Main', ToggleCompositeField::create(
null,
_t(self::class . '.CMSTOGGLEQRCODELABEL', 'Second Factor Token Secret'),
LiteralField::create(null, sprintf("<img src=\"%s\" />", $qrcodeURI))
'Second Factor Token Secret',
LiteralField::create(null, sprintf('<img src="%s" />', $qrcodeURI))
));
$fields->removeByName('TOTPSecret');
}
}

/**
* @return string
* @throws InvalidWriterException
*/
public function GoogleAuthenticatorQRCode()
protected function getQRCode()
{
$qrCode = new QrCode($this->generateOTPAuthString());
$qrCode->setSize(300);
$qrCode->setWriterByName('png');
$qrcodeURI = $qrCode->writeDataUri();

return $qrcodeURI;
return $qrCode->writeDataUri();
}

/**
* @return string
*/
public function generateOTPAuthString()
protected function generateOTPAuthString()
{
$label = urlencode(SiteConfig::current_site_config()->Title);
$issuer = SiteConfig::current_site_config()->Title;
$secret = $this->owner->TOTPSecret;
$email = $this->owner->Email;
$label = $this->owner->Email;

$totp = TOTP::create($secret);
$totp->setIssuer($issuer);
$totp->setLabel($label);

return sprintf(
'otpauth://totp/%s:%s?secret=%s&issuer=%s',
$label,
$email,
$secret,
$label
);
return $totp->getProvisioningUri();
}
}
Loading

0 comments on commit b960f46

Please sign in to comment.