Skip to content

Commit

Permalink
Merge branch '9.next-cake4' into 10.next-cake5
Browse files Browse the repository at this point in the history
  • Loading branch information
skie committed Feb 9, 2024
2 parents f3f1327 + 91fede5 commit 0f8efc8
Show file tree
Hide file tree
Showing 24 changed files with 538 additions and 90 deletions.
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: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

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

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

## [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
4 changes: 2 additions & 2 deletions Docs/Documentation/Authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ At your Application::middleware add the authorization and request authorization

As usual create or update the Application::getAuthorizationService method:

````
public function getAuthorizationService(ServerRequestInterface $request, ResponseInterface $response)
```
public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
{
$map = new MapResolver();
$map->map(
Expand Down
16 changes: 16 additions & 0 deletions Docs/Documentation/TwoFactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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
-------------

* `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
4 changes: 4 additions & 0 deletions config/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@
]
],
],
'TwoFactorProcessors' => [
\CakeDC\Auth\Authentication\TwoFactorProcessor\OneTimePasswordProcessor::class,
\CakeDC\Auth\Authentication\TwoFactorProcessor\Webauthn2faProcessor::class,
],
'OneTimePasswordAuthenticator' => [
'checker' => \CakeDC\Auth\Authentication\DefaultOneTimePasswordAuthenticationChecker::class,
'verifyAction' => [
Expand Down
68 changes: 9 additions & 59 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 Cake\Datasource\EntityInterface;
Expand All @@ -39,65 +38,21 @@ class AuthenticationService extends BaseService
protected array $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): ResultInterface
protected function proceed2FA(TwoFactorProcessorInterface $processor, ServerRequestInterface $request, ResultInterface $result): ResultInterface
{
/**
* @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): ResultInterface
{
/**
* @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;
}

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

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

/**
* {@inheritDoc}
*
Expand All @@ -112,7 +67,7 @@ public function authenticate(ServerRequestInterface $request): ResultInterface
}

$result = null;
/** @var \Authentication\Authenticator\AbstractAuthenticator $authenticator */
$processors = $this->getConfig('processors');
foreach ($this->authenticators() as $authenticator) {
$result = $authenticator->authenticate($request);
if ($result->isValid()) {
Expand All @@ -121,16 +76,11 @@ public function authenticate(ServerRequestInterface $request): ResultInterface
if ($userData instanceof EntityInterface) {
$userData = $userData->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);
}
}

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function isEnabled(): bool
/**
* Check if two factor authentication is required for a user
*
* @param array $user user data
* @param array<mixed>|null $user user data
* @return bool
*/
public function isRequired(?array $user = null): bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function isEnabled(): bool
/**
* Check if two factor authentication is required for a user
*
* @param array $user user data
* @param array<mixed>|null $user user data
* @return bool
*/
public function isRequired(?array $user = null): bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function isEnabled(): bool;
/**
* Check if two factor authentication is required for a user
*
* @param array $user user data
* @param array<mixed>|null $user user data
* @return bool
*/
public function isRequired(?array $user = null): bool;
Expand Down
115 changes: 115 additions & 0 deletions src/Authentication/TwoFactorProcessor/OneTimePasswordProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?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\OneTimePasswordAuthenticationCheckerInterface;
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(): OneTimePasswordAuthenticationCheckerInterface
{
return (new OneTimePasswordAuthenticationCheckerFactory())->build();
}
}
Loading

0 comments on commit 0f8efc8

Please sign in to comment.