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

Feature: Process Typoscript for groups before comparing titles #209

Closed
wants to merge 6 commits into from
Closed
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
32 changes: 23 additions & 9 deletions Classes/Controller/ModuleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,18 @@ public function importBackendUserGroupsAction(\Causal\IgLdapSsoAuth\Domain\Model
$pageRenderer->loadRequireJsModule('TYPO3/CMS/IgLdapSsoAuth/Import');

$groups = $this->getAvailableUserGroups($configuration, 'be');
$this->view->assign('groups', $groups);
$titles = [];
$uniqueGroups = array_filter($groups, function($group) use (&$titles) {
if (in_array($group['title'], $titles)) {
return false;
}
$titles[] = $group['title'];
return true;
});
usort($uniqueGroups, function($a, $b) {
return strnatcmp($a['title'], $b['title']);
});
$this->view->assign('groups', $uniqueGroups);
}

/**
Expand Down Expand Up @@ -535,24 +546,25 @@ public function ajaxGroupsImport(ServerRequestInterface $request): ResponseInter

$pid = Configuration::getPid($config['groups']['mapping']);
$table = $params['mode'] === 'be' ? 'be_groups' : 'fe_groups';
$typo3Groups = Authentication::getTypo3Groups(
$typo3Groups = Authentication::getOrCreateTypo3Groups(
[$ldapGroup],
$table,
$pid
$pid,
$config['groups']['mapping']
);

// Merge LDAP and TYPO3 information
$group = Authentication::merge($ldapGroup, $typo3Groups[0], $config['groups']['mapping']);

if ((int)$group['uid'] === 0) {
if (!isset($group['uid']) || (int)$group['uid'] === 0) {
$group = Typo3GroupRepository::add($table, $group);
} else {
// Restore group that may have been previously deleted
$group['deleted'] = 0;
$success = Typo3GroupRepository::update($table, $group);
}

if (!empty($config['groups']['mapping']['parentGroup'])) {
if (isset($config['groups']['mapping']['parentGroup'])) {
$fieldParent = $config['groups']['mapping']['parentGroup'];
if (preg_match("`<([^$]*)>`", $fieldParent, $attribute)) {
$fieldParent = $attribute[1];
Expand Down Expand Up @@ -630,10 +642,11 @@ protected function setParentGroup(array $ldapParentGroups, string $fieldParent,
// Populate an array of TYPO3 group records corresponding to the LDAP groups
// If a given LDAP group has no associated group in TYPO3, a fresh record
// will be created so that $ldapGroups[i] <=> $typo3Groups[i]
$typo3Groups = Authentication::getTypo3Groups(
$typo3Groups = Authentication::getOrCreateTypo3Groups(
$ldapGroups,
$table,
$pid
$pid,
$config['groups']['mapping']
);

foreach ($ldapGroups as $index => $ldapGroup) {
Expand Down Expand Up @@ -756,10 +769,11 @@ protected function getAvailableUserGroups(\Causal\IgLdapSsoAuth\Domain\Model\Con
// will be created so that $ldapGroups[i] <=> $typo3Groups[i]
$typo3GroupPid = Configuration::getPid($config['groups']['mapping']);
$table = ($mode === 'be') ? 'be_groups' : 'fe_groups';
$typo3Groups = Authentication::getTypo3Groups(
$typo3Groups = Authentication::getOrCreateTypo3Groups(
$ldapGroups,
$table,
$typo3GroupPid
$typo3GroupPid,
$config['groups']['mapping']
);

foreach ($ldapGroups as $index => $ldapGroup) {
Expand Down
198 changes: 115 additions & 83 deletions Classes/Library/Authentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ public static function synchroniseUser(string $userdn, ?string $username = null)
$typo3_users_pid = Configuration::getPid(static::$config['users']['mapping']);

// Get TYPO3 user from username, DN and pid.
$typo3_user = static::getTypo3User($username, $userdn, $typo3_users_pid);
$typo3_user = static::getOrCreateTypo3User($username, $userdn, $typo3_users_pid);
if ($typo3_user === null) {
// Non-existing local users are not allowed to authenticate
return false;
Expand All @@ -202,7 +202,7 @@ public static function synchroniseUser(string $userdn, ?string $username = null)
// Get LDAP and TYPO3 user groups for user
// First reset the LDAP groups
static::$ldapGroups = null;
$typo3_groups = static::getUserGroups($ldapUser);
$typo3_groups = static::getOrCreateUserGroups($ldapUser);
if ($typo3_groups === null) {
// Required LDAP groups are missing
static::$lastAuthenticationDiagnostic = 'Missing required LDAP groups.';
Expand Down Expand Up @@ -333,7 +333,7 @@ protected static function getLdapUser(?string $dn = null): ?array
* @return array|null Array of groups or null if required LDAP groups are missing
* @throws \Causal\IgLdapSsoAuth\Exception\InvalidUserGroupTableException
*/
public static function getUserGroups(array $ldapUser, array $configuration = null, string $groupTable = ''): ?array
public static function getOrCreateUserGroups(array $ldapUser, array $configuration = null, string $groupTable = ''): ?array
{
if ($configuration === null) {
$configuration = static::$config;
Expand All @@ -358,12 +358,14 @@ public static function getUserGroups(array $ldapUser, array $configuration = nul
} else {
// Get pid from group mapping
$typo3GroupPid = Configuration::getPid($configuration['groups']['mapping']);
$contentObj = static::initialiseContentObjectRenderer(0);

$typo3GroupsTemp = static::getTypo3Groups(
$typo3GroupsTemp = static::getOrCreateTypo3Groups(
$ldapGroups,
$groupTable,
$typo3GroupPid,
$configuration['groups']['mapping']
$configuration['groups']['mapping'],
$contentObj
);

if (!empty($requiredLDAPGroups)) {
Expand Down Expand Up @@ -395,28 +397,22 @@ public static function getUserGroups(array $ldapUser, array $configuration = nul
}
if (Configuration::getValue('GroupsNotSynchronize')) {
$typo3_groups[] = $typo3Group;
} elseif (empty($typo3Group['uid'])) {
$newGroup = Typo3GroupRepository::add(
$groupTable,
$typo3Group
);

$i++;
continue;
}
if (empty($typo3Group['uid'])) {
$typo3_group_merged = static::merge(
$ldapGroups[$i],
$newGroup,
$typo3Group,
$configuration['groups']['mapping']
);

Typo3GroupRepository::update(
$typo3Group = Typo3GroupRepository::add(
$groupTable,
$typo3_group_merged
);

$typo3Group = Typo3GroupRepository::fetch(
$groupTable,
$typo3_group_merged['uid']
);
$typo3_groups[] = $typo3Group[0];
$typo3_groups[] = $typo3Group;
} else {
// Restore group that may have been previously deleted
$typo3Group['deleted'] = 0;
Expand All @@ -431,11 +427,7 @@ public static function getUserGroups(array $ldapUser, array $configuration = nul
$typo3_group_merged
);

$typo3Group = Typo3GroupRepository::fetch(
$groupTable,
$typo3_group_merged['uid']
);
$typo3_groups[] = $typo3Group[0];
$typo3_groups[] = $typo3_group_merged;
}

$i++;
Expand Down Expand Up @@ -521,7 +513,7 @@ protected static function getLdapGroups(array $ldapUser = []): array
* @param int|null $pid
* @return array|null
*/
protected static function getTypo3User(string $username, string $userDn, ?int $pid = null): ?array
protected static function getOrCreateTypo3User(string $username, string $userDn, ?int $pid = null): ?array
{
$user = null;

Expand Down Expand Up @@ -574,11 +566,12 @@ protected static function getTypo3User(string $username, string $userDn, ?int $p
* @param array $mapping
* @return array
*/
public static function getTypo3Groups(
public static function getOrCreateTypo3Groups(
array $ldapGroups = [],
?string $table = null,
?int $pid = null,
array $mapping = []
array $mapping = [],
ContentObjectRenderer $contentObj = null
): array
{
if (empty($ldapGroups)) {
Expand All @@ -589,10 +582,9 @@ public static function getTypo3Groups(
$typo3Groups = [];

foreach ($ldapGroups as $ldapGroup) {
$groupName = null;
if (isset($mapping['title']) && preg_match("`<([^$]*)>`", $mapping['title'])) {
$groupName = static::replaceLdapMarkers($mapping['title'], $ldapGroup);
}
$ldapGroup = static::processTypoScript($contentObj, $mapping, $ldapGroup);
$groupName = $ldapGroup['__extraData']['title'] ?? null;

$existingTypo3Groups = Typo3GroupRepository::fetch($table, 0, $pid, $ldapGroup['dn'], $groupName);

if (!empty($existingTypo3Groups)) {
Expand Down Expand Up @@ -621,7 +613,7 @@ public static function getTypo3Groups(
* @param int|null $pid
* @return array
*/
public static function getTypo3Users(
public static function getOrCreateTypo3Users(
array $ldapUsers = [],
array $mapping = [],
?string $table = null,
Expand Down Expand Up @@ -707,56 +699,7 @@ public static function merge(
}
}

$backupTSFE = $GLOBALS['TSFE'] ?? null;

// Advanced stdWrap methods require a valid $GLOBALS['TSFE'] => create the most lightweight one
$pageId = $typo3['pid'];
// Use SiteFinder to get a Site object for the current page tree
$siteFinder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Site\SiteFinder::class);
try {
$currentSite = $siteFinder->getSiteByPageId($pageId);
} catch (SiteNotFoundException $e) {
$allSites = $siteFinder->getAllSites();
$currentSite = reset($allSites);
$pageId = $currentSite->getRootPageId();
}

// Context is a singleton, so we can get the current Context by instantiation
$currentContext = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Context\Context::class);

$typoBranch = (new Typo3Version())->getBranch();
if (version_compare($typoBranch, '11.5', '>=')) {
$pageArguments = GeneralUtility::makeInstance(
PageArguments::class,
$pageId,
PageRepository::DOKTYPE_SYSFOLDER,
[]
);
$frontendUserAuthentication = GeneralUtility::makeInstance(FrontendUserAuthentication::class);
} else {
$pageArguments = null;
$frontendUserAuthentication = null;
}

// Use Site & Context to instantiate TSFE properly for TYPO3 v10+
$GLOBALS['TSFE'] = GeneralUtility::makeInstance(
TypoScriptFrontendController::class,
$currentContext,
$currentSite,
$currentSite->getDefaultLanguage(),
$pageArguments,
$frontendUserAuthentication
);

// initTemplate() has been removed. The deprecation notice suggests setting the property directly
$GLOBALS['TSFE']->tmpl = GeneralUtility::makeInstance(
TemplateService::class,
$currentContext
);
$GLOBALS['TSFE']->renderCharset = 'utf-8';

/** @var $contentObj ContentObjectRenderer */
$contentObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$contentObj = static::initialiseContentObjectRenderer($out['pid']);
$contentObj->start($flattenedLdap, '');

// Process every TypoScript definition
Expand All @@ -767,13 +710,102 @@ public static function merge(
$value = $contentObj->stdWrap($value, $mapping[$typoScriptKey]);
$out = static::mergeSimple([$field => $value], $out, $field, $value);
}

$GLOBALS['TSFE'] = $backupTSFE;
}

return $out;
}

public static function processTypoScript($contentObj, $typoScript = [], $data = []) {

$out = $data;
$typoScriptKeys = [];
foreach($typoScript as $field => $value) {
if(str_ends_with($field, '.')) {
$typoScriptKeys[] = $field;
}
}

if(count($typoScriptKeys) > 0) {
$flattenedData = [];
foreach ($data as $key => $value) {
if (!is_numeric($key)) {
if (is_array($value)) {
unset($value['count']);
$value = implode(LF, $value);
}
$flattenedData[$key] = $value;
}
}
unset($flattenedData['count']);

$contentObj = static::initialiseContentObjectRenderer($out['pid'] ?? 0);
$contentObj->start($flattenedData, '');

// Process every TypoScript definition
foreach ($typoScriptKeys as $typoScriptKey) {
// Remove the trailing period to get corresponding field name
$field = substr($typoScriptKey, 0, -1);
$value = $out[$field] ?? '';
$value = $contentObj->stdWrap($value, $typoScript[$typoScriptKey]);
$out = static::mergeSimple([$field => $value], $out, $field, $value);
}
}

return $out;
}

protected static function initialiseContentObjectRenderer($pid = 0) {
// Advanced stdWrap methods require a valid $GLOBALS['TSFE'] => create the most lightweight one
$pageId = $pid;
// Use SiteFinder to get a Site object for the current page tree
$siteFinder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Site\SiteFinder::class);
try {
$currentSite = $siteFinder->getSiteByPageId($pageId);
} catch (SiteNotFoundException $e) {
$allSites = $siteFinder->getAllSites();
$currentSite = reset($allSites);
$pageId = $currentSite->getRootPageId();
}

// Context is a singleton, so we can get the current Context by instantiation
$currentContext = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Context\Context::class);

$typoBranch = (new Typo3Version())->getBranch();
if (version_compare($typoBranch, '11.5', '>=')) {
$pageArguments = GeneralUtility::makeInstance(
PageArguments::class,
$pageId,
PageRepository::DOKTYPE_SYSFOLDER,
[]
);
$frontendUserAuthentication = GeneralUtility::makeInstance(FrontendUserAuthentication::class);
} else {
$pageArguments = null;
$frontendUserAuthentication = null;
}

// Use Site & Context to instantiate TSFE properly for TYPO3 v10+
$frontendController = GeneralUtility::makeInstance(
TypoScriptFrontendController::class,
$currentContext,
$currentSite,
$currentSite->getDefaultLanguage(),
$pageArguments,
$frontendUserAuthentication
);

// initTemplate() has been removed. The deprecation notice suggests setting the property directly
$frontendController->tmpl = GeneralUtility::makeInstance(
TemplateService::class,
$currentContext
);

/** @var $contentObj ContentObjectRenderer */
$contentObj = GeneralUtility::makeInstance(ContentObjectRenderer::class, $frontendController);

return $contentObj;
}

/**
* Merges a field from LDAP into a TYPO3 record.
*
Expand Down
Loading