Add session swapping to LegacyHandlers

- Autowire LegacyHandlers as Symfony Services
-- Configure legacy handlers folder in service.yml
-- Replace handler instantiation with service injection

- Add init and close to legacy handlers
-- Swap sessions
-- Swap current dir
-- Add config to allow moving values between sessions
-- replace hard coded values with injected configuration

- Remove unused services and corresponding unit test
-- NavbarService.php
-- AuthenticationService.php

- Refactor Authentication Handler to
-- call init and close methods

- Refactor navbar handler to be used as a service
-- Adjust navbar unit tests
This commit is contained in:
Clemente Raposo 2020-03-06 12:59:10 +00:00 committed by Dillon-Brown
parent 93d49d2514
commit bd2e3d6e0c
14 changed files with 343 additions and 320 deletions

View file

@ -46,7 +46,6 @@
},
"autoload": {
"psr-4": {
"SuiteCRM\\Core\\src\\": "core/src/",
"SuiteCRM\\Core\\Legacy\\": "core/legacy/",
"App\\": "core/src/",
"App\\Entity\\": "core/src/Entity/",

View file

@ -3,6 +3,9 @@ framework:
parameters:
secret: ThisTokenIsNotSoSecretChangeIt
legacy.dir: '%kernel.project_dir%/legacy'
legacy.session_name: 'LEGACYSESSID'
default_session_name: 'PHPSESSID'
imports:
- { resource: services/*/*.yaml }
@ -14,6 +17,9 @@ services:
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind:
$projectDir: '%kernel.project_dir%'
$legacyDir: '%legacy.dir%'
$legacySessionName: '%legacy.session_name%'
$defaultSessionName: '%default_session_name%'
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
@ -21,6 +27,11 @@ services:
resource: '../core/src/*'
exclude: '../core/src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
# makes classes in legacy/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
SuiteCRM\Core\Legacy\:
resource: '../core/legacy/*'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:

View file

@ -4,14 +4,14 @@ namespace SuiteCRM\Core\Legacy;
use AuthenticationController;
use Exception;
use RuntimeException;
/**
* Class Authentication
* @package SuiteCRM\Core\Legacy
*/
class Authentication extends LegacyHandler
{
protected $config;
/**
* Set the config
*
@ -32,19 +32,21 @@ class Authentication extends LegacyHandler
* @param $password
* @param $grant_type
*
* @return boolean
* @return bool
* @throws Exception
*/
public function login($username, $password, $grant_type = 'password'): bool
{
if ($this->runLegacyEntryPoint()) {
$authController = new AuthenticationController();
$this->init();
$PARAMS = [];
$authController = new AuthenticationController();
return $authController->login($username, $password, $PARAMS);
}
$PARAMS = [];
throw new RuntimeException('Running legacy entry point failed');
$result = $authController->login($username, $password, $PARAMS);
$this->close();
return $result;
}
}

View file

@ -1,36 +0,0 @@
<?php
namespace SuiteCRM\Core\Legacy;
use SuiteCRM\Core\Legacy\Authentication;
/**
* Class AuthenticationService
* @package SuiteCRM\Core\Legacy
*/
class AuthenticationService
{
/**
* @return string
*/
public function getName(): string
{
return 'users.authentication';
}
/**
* @return string
*/
public function getDescription(): string
{
return 'This service will deal with legacy authentication';
}
/**
* @return mixed
*/
public function createService()
{
return new Authentication();
}
}

View file

@ -2,21 +2,77 @@
namespace SuiteCRM\Core\Legacy;
use RuntimeException;
/**
* Class LegacyHandler
* @package SuiteCRM\Core\Legacy
*/
class LegacyHandler
{
protected $config;
protected const MSG_LEGACY_BOOTSTRAP_FAILED = 'Running legacy entry point failed';
/**
* @var string
*/
protected $projectDir;
/**
* @var string
*/
private $legacyDir;
/**
* @var string
*/
private $legacySessionName;
/**
* @var string
*/
private $defaultSessionName;
/**
* LegacyHandler constructor.
* @param string $projectDir
* @param string $legacyDir
* @param string $legacySessionName
* @param string $defaultSessionName
*/
public function __construct(
string $projectDir,
string $legacyDir,
string $legacySessionName,
string $defaultSessionName
) {
$this->projectDir = $projectDir;
$this->legacyDir = $legacyDir;
$this->legacySessionName = $legacySessionName;
$this->defaultSessionName = $defaultSessionName;
}
/**
* Legacy handler initialization method
*/
public function init(): void
{
// Set working directory for legacy
chdir($this->legacyDir);
if (!$this->runLegacyEntryPoint()) {
throw new RuntimeException(self::MSG_LEGACY_BOOTSTRAP_FAILED);
}
$this->switchSession($this->legacySessionName);
}
/**
* Bootstraps legacy suite
* @return bool
*/
public function runLegacyEntryPoint(): bool
{
if (!defined('LEGACY_PATH')) {
define('LEGACY_PATH', dirname(__DIR__, 2) . '/legacy/');
if (defined('IS_LEGACY_BOOTSTRAPPED') && IS_LEGACY_BOOTSTRAPPED) {
return true;
}
// Set up sugarEntry
@ -24,13 +80,55 @@ class LegacyHandler
define('sugarEntry', true);
}
// Set working directory for legacy
chdir(LEGACY_PATH);
// Load in legacy
require_once LEGACY_PATH . 'include/MVC/preDispatch.php';
require_once LEGACY_PATH . 'include/entryPoint.php';
require_once 'include/MVC/preDispatch.php';
require_once 'include/entryPoint.php';
define('IS_LEGACY_BOOTSTRAPPED', true);
return true;
}
/**
* Close the legacy handler
*/
public function close(): void
{
if (!empty($this->projectDir)) {
chdir($this->projectDir);
}
$this->switchSession($this->defaultSessionName);
}
/**
* Swap symfony session with legacy suite session
* @param string $sessionName
* @param array $keysToSync
*/
protected function switchSession(string $sessionName, array $keysToSync = [])
{
$carryOver = [];
foreach ($keysToSync as $key) {
if (!empty($_SESSION[$key])) {
$carryOver[$key] = $_SESSION[$key];
}
}
session_write_close();
session_name($sessionName);
if (!isset($_COOKIE[$sessionName])) {
$_COOKIE[$sessionName] = session_create_id();
}
session_id($_COOKIE[$sessionName]);
session_start();
foreach ($carryOver as $key => $value) {
$_SESSION[$key] = $value;
}
}
}

View file

@ -1,134 +0,0 @@
<?php
namespace SuiteCRM\Core\Legacy;
use RuntimeException;
use TabController;
use GroupedTabStructure;
use TabGroupHelper;
/**
* Class Navbar
* @package SuiteCRM\Core\Legacy
*/
class Navbar extends LegacyHandler
{
/**
* @return array
*/
public function getNonGroupedNavTabs(): array
{
if ($this->runLegacyEntryPoint()) {
require LEGACY_PATH . 'modules/MySettings/TabController.php';
$tabArray = (new TabController())->get_user_tabs($GLOBALS['current_user']);
$tabArray = array_map('strtolower', $tabArray);
return $tabArray;
}
throw new RuntimeException('Running legacy entry point failed');
}
public function getModuleSubMenus(): array {
if ($this->runLegacyEntryPoint()) {
ob_start();
require_once LEGACY_PATH . 'modules/Home/sitemap.php';
ob_end_clean();
return sm_build_array();
}
}
/**
* @return array
*/
public function getGroupedNavTabs(): array
{
if ($this->runLegacyEntryPoint()) {
global $current_language;
require_once LEGACY_PATH . 'include/GroupedTabs/GroupedTabStructure.php';
require_once LEGACY_PATH . 'modules/Studio/TabGroups/TabGroupHelper.php';
$tg = new TabGroupHelper();
$selectedAppLanguages = return_application_language($current_language);
$availableModules = $tg->getAvailableModules($current_language);
$modList = array_keys($availableModules);
$modList = array_combine($modList, $modList);
$groupedTabStructure = (new GroupedTabStructure())->get_tab_structure($modList, '', true, true);
foreach ($groupedTabStructure as $mainTab => $subModules) {
$groupedTabStructure[$mainTab]['label'] = $mainTab;
$groupedTabStructure[$mainTab]['labelValue'] = strtolower($selectedAppLanguages[$mainTab]);
$submoduleArray = [];
foreach ($subModules['modules'] as $submodule) {
$submoduleArray[] = strtolower($submodule);
}
sort($submoduleArray);
$output[] = [
'name' => $groupedTabStructure[$mainTab]['labelValue'],
'labelKey' => $mainTab,
'modules' => array_values($submoduleArray)
];
}
return $output;
}
throw new RuntimeException('Running legacy entry point failed');
}
/**
* @return array
*/
public function getUserActionMenu(): array
{
if ($this->runLegacyEntryPoint()) {
$userActionMenu = [];
$global_control_links = [];
$userActionMenu[] = [
'name' => 'profile',
'labelKey' => 'LBL_PROFILE',
'url' => 'index.php?module=Users&action=EditView&record=1',
'icon' => '',
];
require LEGACY_PATH . 'include/globalControlLinks.php';
$labelKeys = [
'employees' => 'LBL_EMPLOYEES',
'training' => 'LBL_TRAINING',
'about' => 'LNK_ABOUT',
'users' => 'LBL_LOGOUT',
];
foreach ($global_control_links as $key => $value) {
foreach ($value as $linkAttribute => $attributeValue) {
// get the main link info
if ($linkAttribute === 'linkinfo') {
$userActionMenu[] = [
'name' => strtolower(key($attributeValue)),
'labelKey' => $labelKeys[$key],
'url' => current($attributeValue),
'icon' => '',
];
}
}
}
$item = $userActionMenu[3];
unset($userActionMenu[3]);
$userActionMenu[] = $item;
return array_values($userActionMenu);
}
throw new RuntimeException('Running legacy entry point failed');
}
}

View file

@ -0,0 +1,146 @@
<?php
namespace SuiteCRM\Core\Legacy;
use App\Entity\Navbar;
use TabController;
use GroupedTabStructure;
use TabGroupHelper;
/**
* Class NavbarHandler
*/
class NavbarHandler extends LegacyHandler
{
/**
* Get Navbar using legacy information
* @return Navbar
*/
public function getNavbar(): Navbar
{
$this->init();
$navbar = new Navbar();
$navbar->NonGroupedTabs = $this->fetchNonGroupedNavTabs();
$navbar->groupedTabs = $this->fetchGroupedNavTabs();
$navbar->userActionMenu = $this->fetchUserActionMenu();
$navbar->moduleSubmenus = $this->fetchModuleSubMenus();
$this->close();
return $navbar;
}
/**
* Fetch module navigation tabs
* @return array
*/
protected function fetchNonGroupedNavTabs(): array
{
require_once 'modules/MySettings/TabController.php';
$tabArray = (new TabController())->get_user_tabs($GLOBALS['current_user']);
$tabArray = array_map('strtolower', $tabArray);
return $tabArray;
}
/**
* Fetch the module submenus
* @return array
*/
protected function fetchModuleSubMenus(): array
{
ob_start();
require_once 'modules/Home/sitemap.php';
ob_end_clean();
return sm_build_array();
}
/**
* Fetch Grouped Navigation tabs
* @return array
*/
protected function fetchGroupedNavTabs(): array
{
$output = [];
global $current_language;
require_once 'include/GroupedTabs/GroupedTabStructure.php';
require_once 'modules/Studio/TabGroups/TabGroupHelper.php';
$tg = new TabGroupHelper();
$selectedAppLanguages = return_application_language($current_language);
$availableModules = $tg->getAvailableModules($current_language);
$modList = array_keys($availableModules);
$modList = array_combine($modList, $modList);
$groupedTabStructure = (new GroupedTabStructure())->get_tab_structure($modList, '', true, true);
foreach ($groupedTabStructure as $mainTab => $subModules) {
$groupedTabStructure[$mainTab]['label'] = $mainTab;
$groupedTabStructure[$mainTab]['labelValue'] = strtolower($selectedAppLanguages[$mainTab]);
$submoduleArray = [];
foreach ($subModules['modules'] as $submodule) {
$submoduleArray[] = strtolower($submodule);
}
sort($submoduleArray);
$output[] = [
'name' => $groupedTabStructure[$mainTab]['labelValue'],
'labelKey' => $mainTab,
'modules' => array_values($submoduleArray)
];
}
return $output;
}
/**
* Fetch the user action menu
*/
protected function fetchUserActionMenu(): array
{
$userActionMenu = [];
$global_control_links = [];
$userActionMenu[] = [
'name' => 'profile',
'labelKey' => 'LBL_PROFILE',
'url' => 'index.php?module=Users&action=EditView&record=1',
'icon' => '',
];
require 'include/globalControlLinks.php';
$labelKeys = [
'employees' => 'LBL_EMPLOYEES',
'training' => 'LBL_TRAINING',
'about' => 'LNK_ABOUT',
'users' => 'LBL_LOGOUT',
];
foreach ($global_control_links as $key => $value) {
foreach ($value as $linkAttribute => $attributeValue) {
// get the main link info
if ($linkAttribute === 'linkinfo') {
$userActionMenu[] = [
'name' => strtolower(key($attributeValue)),
'labelKey' => $labelKeys[$key],
'url' => current($attributeValue),
'icon' => '',
];
}
}
}
$item = $userActionMenu[3];
unset($userActionMenu[3]);
$userActionMenu[] = $item;
return array_values($userActionMenu);
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace SuiteCRM\Core\Legacy;
/**
* Class NavbarService
* @package SuiteCRM\Core\Legacy
*/
class NavbarService
{
/**
* @return string
*/
public function getName(): string
{
return 'template.navbar';
}
/**
* @return string
*/
public function getDescription(): string
{
return 'This service will deal with retrieval of the navbar structure';
}
/**
* @return mixed
*/
public function createService()
{
return new Navbar();
}
}

View file

@ -4,28 +4,51 @@ namespace App\DataProvider;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
use Symfony\Component\HttpFoundation\Request;
use SuiteCRM\Core\Legacy\NavbarHandler;
use App\Entity\Navbar;
use SuiteCRM\Core\Legacy\Navbar as LegacyNavbar;
final class NavbarItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface
{
/**
* @var NavbarHandler
*/
private $navbarHandler;
/**
* NavbarItemDataProvider constructor.
* @param NavbarHandler $navbarHandler
*/
public function __construct(NavbarHandler $navbarHandler)
{
$this->navbarHandler = $navbarHandler;
}
/**
* Define supported resources
* @param string $resourceClass
* @param string|null $operationName
* @param array $context
* @return bool
*/
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return Navbar::class === $resourceClass;
}
/**
* Get navbar
* @param string $resourceClass
* @param array|int|string $id
* @param string|null $operationName
* @param array $context
* @return Navbar|null
*/
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?Navbar
{
$navbarData = new LegacyNavbar();
$output = new Navbar();
$navbar = $this->navbarHandler->getNavbar();
// This should be updated once we have authentication.
$output->userID = 1;
$output->NonGroupedTabs = $navbarData->getNonGroupedNavTabs();
$output->groupedTabs = $navbarData->getGroupedNavTabs();
$output->userActionMenu = $navbarData->getUserActionMenu();
$output->moduleSubmenus = $navbarData->getModuleSubMenus();
return $output;
$navbar->userID = 1;
return $navbar;
}
}

View file

@ -10,6 +10,8 @@ use ApiPlatform\Core\Annotation\ApiProperty;
* itemOperations={
* "get"
* },
* collectionOperations={
* }
* )
*/
final class Navbar

View file

@ -3,6 +3,7 @@
namespace App\Security;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use SuiteCRM\Core\Legacy\Authentication;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
@ -31,8 +32,18 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements P
private $router;
private $csrfTokenManager;
private $passwordEncoder;
private $authentication;
/**
* LoginFormAuthenticator constructor.
* @param Authentication $authentication
* @param EntityManagerInterface $entityManager
* @param RouterInterface $router
* @param CsrfTokenManagerInterface $csrfTokenManager
* @param UserPasswordEncoderInterface $passwordEncoder
*/
public function __construct(
Authentication $authentication,
EntityManagerInterface $entityManager,
RouterInterface $router,
CsrfTokenManagerInterface $csrfTokenManager,
@ -42,6 +53,7 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements P
$this->router = $router;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
$this->authentication = $authentication;
}
/**
@ -91,10 +103,9 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements P
throw new CustomUserMessageAuthenticationException('Username could not be found.');
}
$authentication = new Authentication();
try {
$authentication->login($credentials['username'], $credentials['password']);
} catch (\Exception $e) {
$this->authentication->login($credentials['username'], $credentials['password']);
} catch (Exception $e) {
throw new CustomUserMessageAuthenticationException('Legacy username could not be found.');
}
@ -110,7 +121,6 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements P
{
// TODO: Password validation
return true;
// TODO: Password hash upgrading
}

View file

@ -1,39 +0,0 @@
<?php
declare(strict_types=1);
use Codeception\Test\Unit;
use SuiteCRM\Core\Legacy\Authentication;
use SuiteCRM\Core\Legacy\AuthenticationService;
final class AuthenticationServiceTest extends Unit
{
/**
* @var AuthenticationService
*/
private $authenticationService;
protected function _before()
{
$this->authenticationService = new AuthenticationService();
}
public function testGetName(): void
{
$this->assertSame('users.authentication', $this->authenticationService->getName());
}
public function testGetDescription(): void
{
$this->assertSame(
'This service will deal with legacy authentication',
$this->authenticationService->getDescription()
);
}
public function testCreateService(): void
{
$this->assertInstanceOf(Authentication::class, $this->authenticationService->createService());
}
}

View file

@ -1,36 +0,0 @@
<?php
declare(strict_types=1);
use Codeception\Test\Unit;
use SuiteCRM\Core\Legacy\Navbar;
use SuiteCRM\Core\Legacy\NavbarService;
final class NavbarServiceTest extends Unit
{
/**
* @var NavbarService
*/
private $navbarService;
protected function setUp(): void
{
$this->navbarService = new NavbarService();
}
public function testGetName(): void
{
$this->assertSame('template.navbar', $this->navbarService->getName());
}
public function testGetDescription(): void
{
$this->assertSame('This service will deal with retrieval of the navbar structure',
$this->navbarService->getDescription());
}
public function testCreateService(): void
{
$this->assertInstanceOf(Navbar::class, $this->navbarService->createService());
}
}

View file

@ -2,19 +2,30 @@
declare(strict_types=1);
use App\Entity\Navbar;
use Codeception\Test\Unit;
use SuiteCRM\Core\Legacy\Navbar;
use SuiteCRM\Core\Legacy\NavbarHandler;
final class NavbarTest extends Unit
{
/**
* @var NavbarHandler
*/
private $navbarHandler;
/**
* @var Navbar
*/
private $navbar;
protected $navbar;
protected function _before()
{
$this->navbar = new Navbar();
$projectDir = codecept_root_dir();
$legacyDir = $projectDir . '/legacy';
$legacySessionName = 'LEGACYSESSID';
$defaultSessionName = 'PHPSESSID';
$this->navbarHandler = new NavbarHandler($projectDir, $legacyDir, $legacySessionName, $defaultSessionName);
$this->navbar = $this->navbarHandler->getNavbar();
}
public function testGetUserActionMenu(): void
@ -55,7 +66,7 @@ final class NavbarTest extends Unit
$this->assertSame(
$expected,
$this->navbar->getUserActionMenu()
$this->navbar->userActionMenu
);
}
@ -103,7 +114,7 @@ final class NavbarTest extends Unit
$this->assertSame(
$expected,
$this->navbar->getNonGroupedNavTabs()
$this->navbar->NonGroupedTabs
);
}
@ -177,7 +188,7 @@ final class NavbarTest extends Unit
$this->assertSame(
$expected,
$this->navbar->getGroupedNavTabs()
$this->navbar->groupedTabs
);
}
@ -371,7 +382,7 @@ final class NavbarTest extends Unit
$this->assertSame(
$expected,
$this->navbar->getModuleSubMenus()
$this->navbar->moduleSubmenus
);
}
}