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 all 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
18 changes: 18 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org

root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.bat]
end_of_line = crlf

[*.yml]
indent_style = space
indent_size = 2
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: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [9.0.0] - 2024-02-09
- Two factor processors introduced. Prvided way to add new two factor processors on client level.

## [7.0.0] - 2021-10-30
- upgrade to cakephp 4.3.0
- upgrade to cakephp 4.3.0
- upgrade to phpunit 9.5

## [6.1.0] - 2021-05-14
Expand Down
17 changes: 17 additions & 0 deletions Docs/Documentation/TwoFactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Two Factor (2FA)
================

Two-factor authentication (2FA) is an identity and access management security method that requires two forms of identification to access resources and data. 2FA gives businesses the ability to monitor and help safeguard their most vulnerable information and networks.

Configuration
-------------

Processors defined as Configure storage with key `TwoFactorProcessors`


Processors
-------------

* `U2FProcessor` - *deprecated*. Universal 2nd Factor (U2F) is an open standard that strengthens and simplifies two-factor authentication (2FA) using specialized Universal Serial Bus (USB) or near-field communication (NFC) devices based on similar security technology found in smart cards.
* `OneTimePassword` - Authenticator is an authenticator app used as part of a two-factor/multi-factor authentication (2FA/MFA) scheme. It acts as an example of a “something you have” factor by generating one-time passwords (OTPs) on a smartphone or other mobile device.
* `Webauthn2fa` - WebAuthn is a browser-based API that allows for web applications to simplify and secure user authentication by using registered devices (phones, laptops, etc) as factors. It uses public key cryptography to protect users from advanced phishing attacks.
1 change: 1 addition & 0 deletions Docs/Home.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Home
====
* [Authentication](Documentation/Authentication.md)
* [Two Factor](Documentation/TwoFactor.md)
* [Authorization](Documentation/Authorization.md)
* [Social](Documentation/Social.md)
* [SimpleRbacAuthorize](Documentation/SimpleRbacAuthorize.md) RBAC Authorize based on configuration
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