Skip to content
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

Two factor refactoring. #86

Open
wants to merge 7 commits into
base: 7.next-cake4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ on:

jobs:
testsuite:
runs-on: ubuntu-18.04
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
php-version: ['7.2', '7.3', '7.4', '8.0', '8.1']
php-version: ['7.4', '8.0', '8.1']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add 8.2 and 8.3

db-type: [sqlite, mysql, pgsql]
prefer-lowest: ['']

Expand Down Expand Up @@ -77,15 +77,15 @@ jobs:

cs-stan:
name: Coding Standard & Static Analysis
runs-on: ubuntu-18.04
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v2

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
php-version: '8.1'
extensions: mbstring, intl, apcu, memcached, redis
tools: cs2pr
coverage: none
Expand Down
5 changes: 5 additions & 0 deletions config/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@
]
],
],
'TwoFactorProcessors' => [
\CakeDC\Auth\Authentication\TwoFactorProcessor\OneTimePasswordProcessor::class,
\CakeDC\Auth\Authentication\TwoFactorProcessor\U2FProcessor::class,
\CakeDC\Auth\Authentication\TwoFactorProcessor\Webauthn2faProcessor::class,
],
'OneTimePasswordAuthenticator' => [
'checker' => \CakeDC\Auth\Authentication\DefaultOneTimePasswordAuthenticationChecker::class,
'verifyAction' => [
Expand Down
102 changes: 9 additions & 93 deletions src/Authentication/AuthenticationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
namespace CakeDC\Auth\Authentication;

use Authentication\AuthenticationService as BaseService;
use Authentication\Authenticator\Result;
use Authentication\Authenticator\ResultInterface;
use Authentication\Authenticator\StatelessInterface;
use Psr\Http\Message\ServerRequestInterface;
Expand Down Expand Up @@ -42,95 +41,21 @@ class AuthenticationService extends BaseService
protected $failures = [];

/**
* Proceed to google verify action after a valid result result
* Proceed to 2fa processor after a valid result result
*
* @param \CakeDC\Auth\Authentication\TwoFactorProcessorInterface $processor The processor.
* @param \Psr\Http\Message\ServerRequestInterface $request The request.
* @param \Authentication\Authenticator\ResultInterface $result The original result
* @return \Authentication\Authenticator\ResultInterface The result object.
*/
protected function proceedToGoogleVerify(ServerRequestInterface $request, ResultInterface $result)
protected function proceed2FA(TwoFactorProcessorInterface $processor, ServerRequestInterface $request, ResultInterface $result)
{
/**
* @var \Cake\Http\Session $session
*/
$session = $request->getAttribute('session');
$session->write(self::TWO_FACTOR_VERIFY_SESSION_KEY, $result->getData());
$result = new Result(null, self::NEED_TWO_FACTOR_VERIFY);
$result = $processor->proceed($request, $result);
$this->_successfulAuthenticator = null;

return $this->_result = $result;
}

/**
* Proceed to webauthn2fa flow after a valid result result
*
* @param \Psr\Http\Message\ServerRequestInterface $request response to manipulate
* @param \Authentication\Authenticator\ResultInterface $result valid result
* @return \Authentication\Authenticator\ResultInterface with result, request and response keys
*/
protected function proceedToWebauthn2fa(ServerRequestInterface $request, ResultInterface $result)
{
/**
* @var \Cake\Http\Session $session
*/
$session = $request->getAttribute('session');
$session->write(self::WEBAUTHN_2FA_SESSION_KEY, $result->getData());
$result = new Result(null, self::NEED_WEBAUTHN_2FA_VERIFY);
$this->_successfulAuthenticator = null;

return $this->_result = $result;
}

/**
* Proceed to U2f flow after a valid result result
*
* @param \Psr\Http\Message\ServerRequestInterface $request response to manipulate
* @param \Authentication\Authenticator\ResultInterface $result valid result
* @return \Authentication\Authenticator\ResultInterface with result, request and response keys
*/
protected function proceedToU2f(ServerRequestInterface $request, ResultInterface $result)
{
/**
* @var \Cake\Http\Session $session
*/
$session = $request->getAttribute('session');
$session->write(self::U2F_SESSION_KEY, $result->getData());
$result = new Result(null, self::NEED_U2F_VERIFY);
$this->_successfulAuthenticator = null;

return $this->_result = $result;
}

/**
* Get the configured one-time password authentication checker
*
* @return \CakeDC\Auth\Authentication\OneTimePasswordAuthenticationCheckerInterface
*/
protected function getOneTimePasswordAuthenticationChecker()
{
return (new OneTimePasswordAuthenticationCheckerFactory())->build();
}

/**
* Get the configured u2f authentication checker
*
* @return \CakeDC\Auth\Authentication\Webauthn2FAuthenticationCheckerInterface
*/
protected function getWebauthn2fAuthenticationChecker()
{
return (new Webauthn2fAuthenticationCheckerFactory())->build();
}

/**
* Get the configured u2f authentication checker
*
* @return \CakeDC\Auth\Authentication\U2fAuthenticationCheckerInterface
*/
protected function getU2fAuthenticationChecker()
{
return (new U2fAuthenticationCheckerFactory())->build();
}

/**
* {@inheritDoc}
*
Expand All @@ -145,26 +70,17 @@ public function authenticate(ServerRequestInterface $request): ResultInterface
}

$result = null;
$processors = $this->getConfig('processors');
foreach ($this->authenticators() as $authenticator) {
$result = $authenticator->authenticate($request);
if ($result->isValid()) {
$skipTwoFactorVerify = $authenticator->getConfig('skipTwoFactorVerify');
$userData = $result->getData()->toArray();
$webauthn2faChecker = $this->getWebauthn2fAuthenticationChecker();
if ($skipTwoFactorVerify !== true && $webauthn2faChecker->isRequired($userData)) {
return $this->proceedToWebauthn2fa($request, $result);
foreach ($processors as $processor) {
if ($skipTwoFactorVerify !== true && $processor->isRequired($userData)) {
return $this->proceed2FA($processor, $request, $result);
}
}

$u2fCheck = $this->getU2fAuthenticationChecker();
if ($skipTwoFactorVerify !== true && $u2fCheck->isRequired($userData)) {
return $this->proceedToU2f($request, $result);
}

$otpCheck = $this->getOneTimePasswordAuthenticationChecker();
if ($skipTwoFactorVerify !== true && $otpCheck->isRequired($userData)) {
return $this->proceedToGoogleVerify($request, $result);
}

$this->_successfulAuthenticator = $authenticator;
$this->_result = $result;

Expand Down
114 changes: 114 additions & 0 deletions src/Authentication/TwoFactorProcessor/OneTimePasswordProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php
declare(strict_types=1);

/**
* Copyright 2010 - 2024, Cake Development Corporation (https://www.cakedc.com)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright 2010 - 2024, Cake Development Corporation (https://www.cakedc.com)
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace CakeDC\Auth\Authentication\TwoFactorProcessor;

use Authentication\Authenticator\Result;
use Authentication\Authenticator\ResultInterface;
use Cake\Core\Configure;
use CakeDC\Auth\Authentication\OneTimePasswordAuthenticationCheckerFactory;
use CakeDC\Auth\Authentication\TwoFactorProcessorInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
* OneTimePasswordProcessor class
*/
class OneTimePasswordProcessor implements TwoFactorProcessorInterface
{
public const NEED_TWO_FACTOR_VERIFY = 'NEED_TWO_FACTOR_VERIFY';

public const TWO_FACTOR_VERIFY_SESSION_KEY = 'temporarySession';

/**
* Returns processor type.
*
* @return string
*/
public function getType(): string
{
return self::NEED_TWO_FACTOR_VERIFY;
}

/**
* Returns processor session key.
*
* @return string
*/
public function getSessionKey(): string
{
return self::TWO_FACTOR_VERIFY_SESSION_KEY;
}

/**
* Processor status detector.
*
* @return bool
*/
public function enabled(): bool
{
return Configure::read('OneTimePasswordAuthenticator.login') !== false;
}

/**
* Processor status detector.
*
* @return bool
*/
public function isRequired(array $userData): bool
{
return $this->getOneTimePasswordAuthenticationChecker()->isRequired($userData);
}

/**
* Proceed to 2fa processor after a valid result result.
*
* @param \Psr\Http\Message\ServerRequestInterface $request Request instance.
* @param \Authentication\Authenticator\ResultInterface $result Input result object.
* @return \Authentication\Authenticator\ResultInterface
*/
public function proceed(ServerRequestInterface $request, ResultInterface $result): ResultInterface
{
/**
* @var \Cake\Http\Session $session
*/
$session = $request->getAttribute('session');
$session->write($this->getSessionKey(), $result->getData());
$result = new Result(null, $this->getType());

return $result;
}

/**
* Generates 2fa url, if enable.
*
* @param string $type Processor type.
* @return array|null
*/
public function getUrlByType(string $type): ?array
{
if ($type == $this->getType()) {
return Configure::read('OneTimePasswordAuthenticator.verifyAction');
}

return null;
}

/**
* Get the configured one-time password authentication checker
*
* @return \CakeDC\Auth\Authentication\OneTimePasswordAuthenticationCheckerInterface
*/
protected function getOneTimePasswordAuthenticationChecker()
{
return (new OneTimePasswordAuthenticationCheckerFactory())->build();
}
}
Loading
Loading